Commit fb1d4990cb

Matthew Roush <matthew.roush@proton.me>
2025-04-07 22:53:38
Make translate-c more robust in handling macro functions.
Translate-c didn't properly account for C macro functions having parameter names that are C keywords. So something like `#define FOO(float) ((float) + 10)` would've been interpreted as casting `+10` to a `float` type, instead of adding `10` to the parameter `float`. An example of a real-world macro function like this is SDL3's `SDL_DEFINE_AUDIO_FORMAT` from `SDL_audio.h`, which uses `signed` as a parameter.
1 parent f2f36c4
Changed files (2)
src/translate_c.zig
@@ -5226,6 +5226,7 @@ const MacroCtx = struct {
     loc: clang.SourceLocation,
     name: []const u8,
     refs_var_decl: bool = false,
+    fn_params: ?[]const ast.Payload.Param = null,
 
     fn peek(self: *MacroCtx) ?CToken.Id {
         if (self.i >= self.list.len) return null;
@@ -5298,6 +5299,15 @@ const MacroCtx = struct {
         }
         return null;
     }
+
+    fn checkFnParam(self: *MacroCtx, str: []const u8) bool {
+        if (self.fn_params == null) return false;
+
+        for (self.fn_params.?) |param| {
+            if (mem.eql(u8, param.name.?, str)) return true;
+        }
+        return false;
+    }
 };
 
 fn getMacroText(unit: *const clang.ASTUnit, c: *const Context, macro: *const clang.MacroDefinitionRecord) ![]const u8 {
@@ -5474,10 +5484,9 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {
     defer fn_params.deinit();
 
     while (true) {
-        switch (m.peek().?) {
-            .identifier, .extended_identifier => _ = m.next(),
-            else => break,
-        }
+        if (!m.peek().?.isMacroIdentifier()) break;
+
+        _ = m.next();
 
         const mangled_name = try block_scope.makeMangledName(c, m.slice());
         try fn_params.append(.{
@@ -5490,6 +5499,8 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {
         _ = m.next();
     }
 
+    m.fn_params = fn_params.items;
+
     try m.skip(c, .r_paren);
 
     if (m.checkTranslatableMacro(scope, fn_params.items)) |err| switch (err) {
@@ -5905,38 +5916,41 @@ fn parseCPrimaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
         .pp_num => {
             return parseCNumLit(c, m);
         },
-        .identifier, .extended_identifier => {
-            if (c.global_scope.blank_macros.contains(slice)) {
-                return parseCPrimaryExpr(c, m, scope);
-            }
-            const mangled_name = scope.getAlias(slice);
-            if (builtin_typedef_map.get(mangled_name)) |ty| return Tag.type.create(c.arena, ty);
-            const identifier = try Tag.identifier.create(c.arena, mangled_name);
-            scope.skipVariableDiscard(identifier.castTag(.identifier).?.data);
-            refs_var: {
-                const ident_node = c.global_scope.sym_table.get(slice) orelse break :refs_var;
-                const var_decl_node = ident_node.castTag(.var_decl) orelse break :refs_var;
-                if (!var_decl_node.data.is_const) m.refs_var_decl = true;
-            }
-            return identifier;
-        },
         .l_paren => {
             const inner_node = try parseCExpr(c, m, scope);
 
             try m.skip(c, .r_paren);
             return inner_node;
         },
-        else => {
-            // for handling type macros (EVIL)
-            // TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList?
-            m.i -= 1;
-            if (try parseCTypeName(c, m, scope, true)) |type_name| {
-                return type_name;
-            }
-            try m.fail(c, "unable to translate C expr: unexpected token '{s}'", .{tok.symbol()});
-            return error.ParseError;
-        },
+        else => {},
+    }
+
+    // The C preprocessor has no knowledge of C, so C keywords aren't special in macros.
+    // Thus the current token should be treated like an identifier if its name matches a parameter.
+    if (tok == .identifier or tok == .extended_identifier or m.checkFnParam(slice)) {
+        if (c.global_scope.blank_macros.contains(slice)) {
+            return parseCPrimaryExpr(c, m, scope);
+        }
+        const mangled_name = scope.getAlias(slice);
+        if (builtin_typedef_map.get(mangled_name)) |ty| return Tag.type.create(c.arena, ty);
+        const identifier = try Tag.identifier.create(c.arena, mangled_name);
+        scope.skipVariableDiscard(identifier.castTag(.identifier).?.data);
+        refs_var: {
+            const ident_node = c.global_scope.sym_table.get(slice) orelse break :refs_var;
+            const var_decl_node = ident_node.castTag(.var_decl) orelse break :refs_var;
+            if (!var_decl_node.data.is_const) m.refs_var_decl = true;
+        }
+        return identifier;
+    }
+
+    // for handling type macros (EVIL)
+    // TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList?
+    m.i -= 1;
+    if (try parseCTypeName(c, m, scope, true)) |type_name| {
+        return type_name;
     }
+    try m.fail(c, "unable to translate C expr: unexpected token '{s}'", .{tok.symbol()});
+    return error.ParseError;
 }
 
 fn macroIntFromBool(c: *Context, node: Node) !Node {
@@ -6199,41 +6213,50 @@ fn parseCTypeName(c: *Context, m: *MacroCtx, scope: *Scope, allow_fail: bool) Pa
 
 fn parseCSpecifierQualifierList(c: *Context, m: *MacroCtx, scope: *Scope, allow_fail: bool) ParseError!?Node {
     const tok = m.next().?;
-    switch (tok) {
-        .identifier, .extended_identifier => {
-            if (c.global_scope.blank_macros.contains(m.slice())) {
-                return try parseCSpecifierQualifierList(c, m, scope, allow_fail);
-            }
-            const mangled_name = scope.getAlias(m.slice());
-            if (!allow_fail or c.typedefs.contains(mangled_name)) {
-                if (builtin_typedef_map.get(mangled_name)) |ty| return try Tag.type.create(c.arena, ty);
-                return try Tag.identifier.create(c.arena, mangled_name);
-            }
-        },
-        .keyword_void => return try Tag.type.create(c.arena, "anyopaque"),
-        .keyword_bool => return try Tag.type.create(c.arena, "bool"),
-        .keyword_char,
-        .keyword_int,
-        .keyword_short,
-        .keyword_long,
-        .keyword_float,
-        .keyword_double,
-        .keyword_signed,
-        .keyword_unsigned,
-        .keyword_complex,
-        => {
-            m.i -= 1;
-            return try parseCNumericType(c, m);
-        },
-        .keyword_enum, .keyword_struct, .keyword_union => {
-            // struct Foo will be declared as struct_Foo by transRecordDecl
-            const slice = m.slice();
-            try m.skip(c, .identifier);
+    const slice = m.slice();
+    const mangled_name = scope.getAlias(slice);
+    if (!m.checkFnParam(mangled_name)) {
+        switch (tok) {
+            .identifier, .extended_identifier => {
+                if (c.global_scope.blank_macros.contains(m.slice())) {
+                    return try parseCSpecifierQualifierList(c, m, scope, allow_fail);
+                }
+                if (!allow_fail or c.typedefs.contains(mangled_name)) {
+                    if (builtin_typedef_map.get(mangled_name)) |ty| return try Tag.type.create(c.arena, ty);
+                    return try Tag.identifier.create(c.arena, mangled_name);
+                }
+            },
+            .keyword_void => return try Tag.type.create(c.arena, "anyopaque"),
+            .keyword_bool => return try Tag.type.create(c.arena, "bool"),
+            .keyword_char,
+            .keyword_int,
+            .keyword_short,
+            .keyword_long,
+            .keyword_float,
+            .keyword_double,
+            .keyword_signed,
+            .keyword_unsigned,
+            .keyword_complex,
+            => {
+                m.i -= 1;
+                return try parseCNumericType(c, m);
+            },
+            .keyword_enum, .keyword_struct, .keyword_union => {
+                // struct Foo will be declared as struct_Foo by transRecordDecl
+                try m.skip(c, .identifier);
 
-            const name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ slice, m.slice() });
-            return try Tag.identifier.create(c.arena, name);
-        },
-        else => {},
+                const name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ slice, m.slice() });
+                return try Tag.identifier.create(c.arena, name);
+            },
+            else => {},
+        }
+    } else {
+        if (allow_fail) {
+            m.i -= 1;
+            return null;
+        } else {
+            return try Tag.identifier.create(c.arena, mangled_name);
+        }
     }
 
     if (allow_fail) {
@@ -6511,7 +6534,7 @@ fn parseCPostfixExprInner(c: *Context, m: *MacroCtx, scope: *Scope, type_name: ?
 }
 
 fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
-    switch (m.next().?) {
+    sw: switch (m.next().?) {
         .bang => {
             const operand = try macroIntToBool(c, try parseCCastExpr(c, m, scope));
             return Tag.not.create(c.arena, operand);
@@ -6534,6 +6557,9 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
             return Tag.address_of.create(c.arena, operand);
         },
         .keyword_sizeof => {
+            // 'sizeof' could be used as a parameter to a macro function.
+            if (m.checkFnParam(m.slice())) break :sw;
+
             const operand = if (m.peek().? == .l_paren) blk: {
                 _ = m.next();
                 const inner = (try parseCTypeName(c, m, scope, false)).?;
@@ -6544,6 +6570,9 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
             return Tag.helpers_sizeof.create(c.arena, operand);
         },
         .keyword_alignof => {
+            // 'alignof' could be used as a parameter to a macro function.
+            if (m.checkFnParam(m.slice())) break :sw;
+
             // TODO this won't work if using <stdalign.h>'s
             // #define alignof _Alignof
             try m.skip(c, .l_paren);
@@ -6556,11 +6585,11 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
             try m.fail(c, "TODO unary inc/dec expr", .{});
             return error.ParseError;
         },
-        else => {
-            m.i -= 1;
-            return try parseCPostfixExpr(c, m, scope, null);
-        },
+        else => {},
     }
+
+    m.i -= 1;
+    return try parseCPostfixExpr(c, m, scope, null);
 }
 
 fn getContainer(c: *Context, node: Node) ?Node {
test/cases/translate_c/c_keywords_as_macro_function_parameters.c
@@ -0,0 +1,82 @@
+#define GUARDED_INT_ADDITION(int) ((int) + 1)
+
+#define UNGUARDED_INT_SUBTRACTION(int) (int - 2)
+
+#define GUARDED_INT_MULTIPLY(int) ((int) * 3)
+
+#define UNGUARDED_INT_DIVIDE(int) (int / 4)
+
+#define WRAPPED_RETURN(return) ((return) % 2)
+
+#define UNWRAPPED_RETURN(return) (return ^ 0x7F)
+
+#define WITH_TWO_PARAMETERS(signed, x) ((signed) + (x) + 9)
+
+#define GUARDED_ALIGNOF(_Alignof) ((_Alignof) & 0x55)
+
+#define UNGUARDED_ALIGNOF(_Alignof) (_Alignof | 0x80)
+
+#define GUARDED_SIZEOF(sizeof) ((sizeof) == 64)
+
+#define UNGUARDED_SIZEOF(sizeof) (sizeof < 64)
+
+#define SIZEOF(x) ((int)sizeof(x))
+
+#define SIZEOF2(x) ((int)sizeof x)
+
+// translate-c
+// c_frontend=clang
+//
+// pub inline fn GUARDED_INT_ADDITION(int: anytype) @TypeOf(int + @as(c_int, 1)) {
+//     _ = &int;
+//     return int + @as(c_int, 1);
+// }
+// pub inline fn UNGUARDED_INT_SUBTRACTION(int: anytype) @TypeOf(int - @as(c_int, 2)) {
+//     _ = &int;
+//     return int - @as(c_int, 2);
+// }
+// pub inline fn GUARDED_INT_MULTIPLY(int: anytype) @TypeOf(int * @as(c_int, 3)) {
+//     _ = &int;
+//     return int * @as(c_int, 3);
+// }
+// pub inline fn UNGUARDED_INT_DIVIDE(int: anytype) @TypeOf(@import("std").zig.c_translation.MacroArithmetic.div(int, @as(c_int, 4))) {
+//     _ = &int;
+//     return @import("std").zig.c_translation.MacroArithmetic.div(int, @as(c_int, 4));
+// }
+// pub inline fn WRAPPED_RETURN(@"return": anytype) @TypeOf(@import("std").zig.c_translation.MacroArithmetic.rem(@"return", @as(c_int, 2))) {
+//     _ = &@"return";
+//     return @import("std").zig.c_translation.MacroArithmetic.rem(@"return", @as(c_int, 2));
+// }
+// pub inline fn UNWRAPPED_RETURN(@"return": anytype) @TypeOf(@"return" ^ @as(c_int, 0x7F)) {
+//     _ = &@"return";
+//     return @"return" ^ @as(c_int, 0x7F);
+// }
+// pub inline fn WITH_TWO_PARAMETERS(signed: anytype, x: anytype) @TypeOf((signed + x) + @as(c_int, 9)) {
+//     _ = &signed;
+//     _ = &x;
+//     return (signed + x) + @as(c_int, 9);
+// }
+// pub inline fn GUARDED_ALIGNOF(_Alignof: anytype) @TypeOf(_Alignof & @as(c_int, 0x55)) {
+//     _ = &_Alignof;
+//     return _Alignof & @as(c_int, 0x55);
+// }
+// pub inline fn UNGUARDED_ALIGNOF(_Alignof: anytype) @TypeOf(_Alignof | @as(c_int, 0x80)) {
+//     _ = &_Alignof;
+//     return _Alignof | @as(c_int, 0x80);
+// }
+// pub inline fn GUARDED_SIZEOF(sizeof: anytype) @TypeOf(sizeof == @as(c_int, 64)) {
+//     _ = &sizeof;
+//     return sizeof == @as(c_int, 64);
+// }
+// pub inline fn UNGUARDED_SIZEOF(sizeof: anytype) @TypeOf(sizeof < @as(c_int, 64)) {
+//     _ = &sizeof;
+//     return sizeof < @as(c_int, 64);
+// }
+// pub inline fn SIZEOF(x: anytype) c_int {
+//     _ = &x;
+//     return @import("std").zig.c_translation.cast(c_int, @import("std").zig.c_translation.sizeof(x));
+// }
+// pub inline fn SIZEOF2(x: anytype) c_int {
+//     _ = &x;
+//     return @import("std").zig.c_translation.cast(c_int, @import("std").zig.c_translation.sizeof(x));
+// }