authorgravatar for matthew.roush@proton.meMatthew Roush <matthew.roush@proton.me> 2025-04-07 16:53:38-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-04-07 20:53:38+00:00
logfb1d4990cb60e805236122256da99d80c50ee0d4
treea88b0bf565d8b6ac27598eda4eca1a46c5582e39
parentf2f36c49c8856674e0769300a5c2768a47302520
signaturebadge-check Signed by PGP key B5690EEEBB952194

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.

2 files changed, 179 insertions(+), 68 deletions(-)

src/translate_c.zig+97-68
......@@ -5226,6 +5226,7 @@ const MacroCtx = struct {
52265226 loc: clang.SourceLocation,
52275227 name: []const u8,
52285228 refs_var_decl: bool = false,
5229 fn_params: ?[]const ast.Payload.Param = null,
52295230
52305231 fn peek(self: *MacroCtx) ?CToken.Id {
52315232 if (self.i >= self.list.len) return null;
......@@ -5298,6 +5299,15 @@ const MacroCtx = struct {
52985299 }
52995300 return null;
53005301 }
5302
5303 fn checkFnParam(self: *MacroCtx, str: []const u8) bool {
5304 if (self.fn_params == null) return false;
5305
5306 for (self.fn_params.?) |param| {
5307 if (mem.eql(u8, param.name.?, str)) return true;
5308 }
5309 return false;
5310 }
53015311};
53025312
53035313fn 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 {
54745484 defer fn_params.deinit();
54755485
54765486 while (true) {
5477 switch (m.peek().?) {
5478 .identifier, .extended_identifier => _ = m.next(),
5479 else => break,
5480 }
5487 if (!m.peek().?.isMacroIdentifier()) break;
5488
5489 _ = m.next();
54815490
54825491 const mangled_name = try block_scope.makeMangledName(c, m.slice());
54835492 try fn_params.append(.{
......@@ -5490,6 +5499,8 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {
54905499 _ = m.next();
54915500 }
54925501
5502 m.fn_params = fn_params.items;
5503
54935504 try m.skip(c, .r_paren);
54945505
54955506 if (m.checkTranslatableMacro(scope, fn_params.items)) |err| switch (err) {
......@@ -5905,38 +5916,41 @@ fn parseCPrimaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
59055916 .pp_num => {
59065917 return parseCNumLit(c, m);
59075918 },
5908 .identifier, .extended_identifier => {
5909 if (c.global_scope.blank_macros.contains(slice)) {
5910 return parseCPrimaryExpr(c, m, scope);
5911 }
5912 const mangled_name = scope.getAlias(slice);
5913 if (builtin_typedef_map.get(mangled_name)) |ty| return Tag.type.create(c.arena, ty);
5914 const identifier = try Tag.identifier.create(c.arena, mangled_name);
5915 scope.skipVariableDiscard(identifier.castTag(.identifier).?.data);
5916 refs_var: {
5917 const ident_node = c.global_scope.sym_table.get(slice) orelse break :refs_var;
5918 const var_decl_node = ident_node.castTag(.var_decl) orelse break :refs_var;
5919 if (!var_decl_node.data.is_const) m.refs_var_decl = true;
5920 }
5921 return identifier;
5922 },
59235919 .l_paren => {
59245920 const inner_node = try parseCExpr(c, m, scope);
59255921
59265922 try m.skip(c, .r_paren);
59275923 return inner_node;
59285924 },
5929 else => {
5930 // for handling type macros (EVIL)
5931 // TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList?
5932 m.i -= 1;
5933 if (try parseCTypeName(c, m, scope, true)) |type_name| {
5934 return type_name;
5935 }
5936 try m.fail(c, "unable to translate C expr: unexpected token '{s}'", .{tok.symbol()});
5937 return error.ParseError;
5938 },
5925 else => {},
5926 }
5927
5928 // The C preprocessor has no knowledge of C, so C keywords aren't special in macros.
5929 // Thus the current token should be treated like an identifier if its name matches a parameter.
5930 if (tok == .identifier or tok == .extended_identifier or m.checkFnParam(slice)) {
5931 if (c.global_scope.blank_macros.contains(slice)) {
5932 return parseCPrimaryExpr(c, m, scope);
5933 }
5934 const mangled_name = scope.getAlias(slice);
5935 if (builtin_typedef_map.get(mangled_name)) |ty| return Tag.type.create(c.arena, ty);
5936 const identifier = try Tag.identifier.create(c.arena, mangled_name);
5937 scope.skipVariableDiscard(identifier.castTag(.identifier).?.data);
5938 refs_var: {
5939 const ident_node = c.global_scope.sym_table.get(slice) orelse break :refs_var;
5940 const var_decl_node = ident_node.castTag(.var_decl) orelse break :refs_var;
5941 if (!var_decl_node.data.is_const) m.refs_var_decl = true;
5942 }
5943 return identifier;
5944 }
5945
5946 // for handling type macros (EVIL)
5947 // TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList?
5948 m.i -= 1;
5949 if (try parseCTypeName(c, m, scope, true)) |type_name| {
5950 return type_name;
59395951 }
5952 try m.fail(c, "unable to translate C expr: unexpected token '{s}'", .{tok.symbol()});
5953 return error.ParseError;
59405954}
59415955
59425956fn macroIntFromBool(c: *Context, node: Node) !Node {
......@@ -6199,41 +6213,50 @@ fn parseCTypeName(c: *Context, m: *MacroCtx, scope: *Scope, allow_fail: bool) Pa
61996213
62006214fn parseCSpecifierQualifierList(c: *Context, m: *MacroCtx, scope: *Scope, allow_fail: bool) ParseError!?Node {
62016215 const tok = m.next().?;
6202 switch (tok) {
6203 .identifier, .extended_identifier => {
6204 if (c.global_scope.blank_macros.contains(m.slice())) {
6205 return try parseCSpecifierQualifierList(c, m, scope, allow_fail);
6206 }
6207 const mangled_name = scope.getAlias(m.slice());
6208 if (!allow_fail or c.typedefs.contains(mangled_name)) {
6209 if (builtin_typedef_map.get(mangled_name)) |ty| return try Tag.type.create(c.arena, ty);
6210 return try Tag.identifier.create(c.arena, mangled_name);
6211 }
6212 },
6213 .keyword_void => return try Tag.type.create(c.arena, "anyopaque"),
6214 .keyword_bool => return try Tag.type.create(c.arena, "bool"),
6215 .keyword_char,
6216 .keyword_int,
6217 .keyword_short,
6218 .keyword_long,
6219 .keyword_float,
6220 .keyword_double,
6221 .keyword_signed,
6222 .keyword_unsigned,
6223 .keyword_complex,
6224 => {
6225 m.i -= 1;
6226 return try parseCNumericType(c, m);
6227 },
6228 .keyword_enum, .keyword_struct, .keyword_union => {
6229 // struct Foo will be declared as struct_Foo by transRecordDecl
6230 const slice = m.slice();
6231 try m.skip(c, .identifier);
6216 const slice = m.slice();
6217 const mangled_name = scope.getAlias(slice);
6218 if (!m.checkFnParam(mangled_name)) {
6219 switch (tok) {
6220 .identifier, .extended_identifier => {
6221 if (c.global_scope.blank_macros.contains(m.slice())) {
6222 return try parseCSpecifierQualifierList(c, m, scope, allow_fail);
6223 }
6224 if (!allow_fail or c.typedefs.contains(mangled_name)) {
6225 if (builtin_typedef_map.get(mangled_name)) |ty| return try Tag.type.create(c.arena, ty);
6226 return try Tag.identifier.create(c.arena, mangled_name);
6227 }
6228 },
6229 .keyword_void => return try Tag.type.create(c.arena, "anyopaque"),
6230 .keyword_bool => return try Tag.type.create(c.arena, "bool"),
6231 .keyword_char,
6232 .keyword_int,
6233 .keyword_short,
6234 .keyword_long,
6235 .keyword_float,
6236 .keyword_double,
6237 .keyword_signed,
6238 .keyword_unsigned,
6239 .keyword_complex,
6240 => {
6241 m.i -= 1;
6242 return try parseCNumericType(c, m);
6243 },
6244 .keyword_enum, .keyword_struct, .keyword_union => {
6245 // struct Foo will be declared as struct_Foo by transRecordDecl
6246 try m.skip(c, .identifier);
62326247
6233 const name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ slice, m.slice() });
6234 return try Tag.identifier.create(c.arena, name);
6235 },
6236 else => {},
6248 const name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ slice, m.slice() });
6249 return try Tag.identifier.create(c.arena, name);
6250 },
6251 else => {},
6252 }
6253 } else {
6254 if (allow_fail) {
6255 m.i -= 1;
6256 return null;
6257 } else {
6258 return try Tag.identifier.create(c.arena, mangled_name);
6259 }
62376260 }
62386261
62396262 if (allow_fail) {
......@@ -6511,7 +6534,7 @@ fn parseCPostfixExprInner(c: *Context, m: *MacroCtx, scope: *Scope, type_name: ?
65116534}
65126535
65136536fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
6514 switch (m.next().?) {
6537 sw: switch (m.next().?) {
65156538 .bang => {
65166539 const operand = try macroIntToBool(c, try parseCCastExpr(c, m, scope));
65176540 return Tag.not.create(c.arena, operand);
......@@ -6534,6 +6557,9 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
65346557 return Tag.address_of.create(c.arena, operand);
65356558 },
65366559 .keyword_sizeof => {
6560 // 'sizeof' could be used as a parameter to a macro function.
6561 if (m.checkFnParam(m.slice())) break :sw;
6562
65376563 const operand = if (m.peek().? == .l_paren) blk: {
65386564 _ = m.next();
65396565 const inner = (try parseCTypeName(c, m, scope, false)).?;
......@@ -6544,6 +6570,9 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
65446570 return Tag.helpers_sizeof.create(c.arena, operand);
65456571 },
65466572 .keyword_alignof => {
6573 // 'alignof' could be used as a parameter to a macro function.
6574 if (m.checkFnParam(m.slice())) break :sw;
6575
65476576 // TODO this won't work if using <stdalign.h>'s
65486577 // #define alignof _Alignof
65496578 try m.skip(c, .l_paren);
......@@ -6556,11 +6585,11 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
65566585 try m.fail(c, "TODO unary inc/dec expr", .{});
65576586 return error.ParseError;
65586587 },
6559 else => {
6560 m.i -= 1;
6561 return try parseCPostfixExpr(c, m, scope, null);
6562 },
6588 else => {},
65636589 }
6590
6591 m.i -= 1;
6592 return try parseCPostfixExpr(c, m, scope, null);
65646593}
65656594
65666595fn getContainer(c: *Context, node: Node) ?Node {
test/cases/translate_c/c_keywords_as_macro_function_parameters.c created+82
......@@ -0,0 +1,82 @@
1#define GUARDED_INT_ADDITION(int) ((int) + 1)
2
3#define UNGUARDED_INT_SUBTRACTION(int) (int - 2)
4
5#define GUARDED_INT_MULTIPLY(int) ((int) * 3)
6
7#define UNGUARDED_INT_DIVIDE(int) (int / 4)
8
9#define WRAPPED_RETURN(return) ((return) % 2)
10
11#define UNWRAPPED_RETURN(return) (return ^ 0x7F)
12
13#define WITH_TWO_PARAMETERS(signed, x) ((signed) + (x) + 9)
14
15#define GUARDED_ALIGNOF(_Alignof) ((_Alignof) & 0x55)
16
17#define UNGUARDED_ALIGNOF(_Alignof) (_Alignof | 0x80)
18
19#define GUARDED_SIZEOF(sizeof) ((sizeof) == 64)
20
21#define UNGUARDED_SIZEOF(sizeof) (sizeof < 64)
22
23#define SIZEOF(x) ((int)sizeof(x))
24
25#define SIZEOF2(x) ((int)sizeof x)
26
27// translate-c
28// c_frontend=clang
29//
30// pub inline fn GUARDED_INT_ADDITION(int: anytype) @TypeOf(int + @as(c_int, 1)) {
31// _ = &int;
32// return int + @as(c_int, 1);
33// }
34// pub inline fn UNGUARDED_INT_SUBTRACTION(int: anytype) @TypeOf(int - @as(c_int, 2)) {
35// _ = &int;
36// return int - @as(c_int, 2);
37// }
38// pub inline fn GUARDED_INT_MULTIPLY(int: anytype) @TypeOf(int * @as(c_int, 3)) {
39// _ = &int;
40// return int * @as(c_int, 3);
41// }
42// pub inline fn UNGUARDED_INT_DIVIDE(int: anytype) @TypeOf(@import("std").zig.c_translation.MacroArithmetic.div(int, @as(c_int, 4))) {
43// _ = &int;
44// return @import("std").zig.c_translation.MacroArithmetic.div(int, @as(c_int, 4));
45// }
46// pub inline fn WRAPPED_RETURN(@"return": anytype) @TypeOf(@import("std").zig.c_translation.MacroArithmetic.rem(@"return", @as(c_int, 2))) {
47// _ = &@"return";
48// return @import("std").zig.c_translation.MacroArithmetic.rem(@"return", @as(c_int, 2));
49// }
50// pub inline fn UNWRAPPED_RETURN(@"return": anytype) @TypeOf(@"return" ^ @as(c_int, 0x7F)) {
51// _ = &@"return";
52// return @"return" ^ @as(c_int, 0x7F);
53// }
54// pub inline fn WITH_TWO_PARAMETERS(signed: anytype, x: anytype) @TypeOf((signed + x) + @as(c_int, 9)) {
55// _ = &signed;
56// _ = &x;
57// return (signed + x) + @as(c_int, 9);
58// }
59// pub inline fn GUARDED_ALIGNOF(_Alignof: anytype) @TypeOf(_Alignof & @as(c_int, 0x55)) {
60// _ = &_Alignof;
61// return _Alignof & @as(c_int, 0x55);
62// }
63// pub inline fn UNGUARDED_ALIGNOF(_Alignof: anytype) @TypeOf(_Alignof | @as(c_int, 0x80)) {
64// _ = &_Alignof;
65// return _Alignof | @as(c_int, 0x80);
66// }
67// pub inline fn GUARDED_SIZEOF(sizeof: anytype) @TypeOf(sizeof == @as(c_int, 64)) {
68// _ = &sizeof;
69// return sizeof == @as(c_int, 64);
70// }
71// pub inline fn UNGUARDED_SIZEOF(sizeof: anytype) @TypeOf(sizeof < @as(c_int, 64)) {
72// _ = &sizeof;
73// return sizeof < @as(c_int, 64);
74// }
75// pub inline fn SIZEOF(x: anytype) c_int {
76// _ = &x;
77// return @import("std").zig.c_translation.cast(c_int, @import("std").zig.c_translation.sizeof(x));
78// }
79// pub inline fn SIZEOF2(x: anytype) c_int {
80// _ = &x;
81// return @import("std").zig.c_translation.cast(c_int, @import("std").zig.c_translation.sizeof(x));
82// }