authorgravatar for matthew.roush@proton.meMatthew Roush <matthew.roush@proton.me> 2025-04-07 16:53:38-04:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-04-08 12:11:30+02:00
logfbb297fd2a666eca9c89b2afce7464abcc7daeec
tree4082d67260b544120fbd3be2b9d487002386e8bb
parent8bb7c85bd4959c837e1c03567a3dcffcba3f8d85
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

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 {...@@ -5226,6 +5226,7 @@ const MacroCtx = struct {
5226 loc: clang.SourceLocation,5226 loc: clang.SourceLocation,
5227 name: []const u8,5227 name: []const u8,
5228 refs_var_decl: bool = false,5228 refs_var_decl: bool = false,
5229 fn_params: ?[]const ast.Payload.Param = null,
52295230
5230 fn peek(self: *MacroCtx) ?CToken.Id {5231 fn peek(self: *MacroCtx) ?CToken.Id {
5231 if (self.i >= self.list.len) return null;5232 if (self.i >= self.list.len) return null;
...@@ -5298,6 +5299,15 @@ const MacroCtx = struct {...@@ -5298,6 +5299,15 @@ const MacroCtx = struct {
5298 }5299 }
5299 return null;5300 return null;
5300 }5301 }
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 }
5301};5311};
53025312
5303fn getMacroText(unit: *const clang.ASTUnit, c: *const Context, macro: *const clang.MacroDefinitionRecord) ![]const u8 {5313fn 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 {...@@ -5474,10 +5484,9 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {
5474 defer fn_params.deinit();5484 defer fn_params.deinit();
54755485
5476 while (true) {5486 while (true) {
5477 switch (m.peek().?) {5487 if (!m.peek().?.isMacroIdentifier()) break;
5478 .identifier, .extended_identifier => _ = m.next(),5488
5479 else => break,5489 _ = m.next();
5480 }
54815490
5482 const mangled_name = try block_scope.makeMangledName(c, m.slice());5491 const mangled_name = try block_scope.makeMangledName(c, m.slice());
5483 try fn_params.append(.{5492 try fn_params.append(.{
...@@ -5490,6 +5499,8 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {...@@ -5490,6 +5499,8 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {
5490 _ = m.next();5499 _ = m.next();
5491 }5500 }
54925501
5502 m.fn_params = fn_params.items;
5503
5493 try m.skip(c, .r_paren);5504 try m.skip(c, .r_paren);
54945505
5495 if (m.checkTranslatableMacro(scope, fn_params.items)) |err| switch (err) {5506 if (m.checkTranslatableMacro(scope, fn_params.items)) |err| switch (err) {
...@@ -5905,38 +5916,41 @@ fn parseCPrimaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {...@@ -5905,38 +5916,41 @@ fn parseCPrimaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
5905 .pp_num => {5916 .pp_num => {
5906 return parseCNumLit(c, m);5917 return parseCNumLit(c, m);
5907 },5918 },
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 },
5923 .l_paren => {5919 .l_paren => {
5924 const inner_node = try parseCExpr(c, m, scope);5920 const inner_node = try parseCExpr(c, m, scope);
59255921
5926 try m.skip(c, .r_paren);5922 try m.skip(c, .r_paren);
5927 return inner_node;5923 return inner_node;
5928 },5924 },
5929 else => {5925 else => {},
5930 // for handling type macros (EVIL)5926 }
5931 // TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList?5927
5932 m.i -= 1;5928 // The C preprocessor has no knowledge of C, so C keywords aren't special in macros.
5933 if (try parseCTypeName(c, m, scope, true)) |type_name| {5929 // Thus the current token should be treated like an identifier if its name matches a parameter.
5934 return type_name;5930 if (tok == .identifier or tok == .extended_identifier or m.checkFnParam(slice)) {
5935 }5931 if (c.global_scope.blank_macros.contains(slice)) {
5936 try m.fail(c, "unable to translate C expr: unexpected token '{s}'", .{tok.symbol()});5932 return parseCPrimaryExpr(c, m, scope);
5937 return error.ParseError;5933 }
5938 },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;
5939 }5951 }
5952 try m.fail(c, "unable to translate C expr: unexpected token '{s}'", .{tok.symbol()});
5953 return error.ParseError;
5940}5954}
59415955
5942fn macroIntFromBool(c: *Context, node: Node) !Node {5956fn macroIntFromBool(c: *Context, node: Node) !Node {
...@@ -6199,41 +6213,50 @@ fn parseCTypeName(c: *Context, m: *MacroCtx, scope: *Scope, allow_fail: bool) Pa...@@ -6199,41 +6213,50 @@ fn parseCTypeName(c: *Context, m: *MacroCtx, scope: *Scope, allow_fail: bool) Pa
61996213
6200fn parseCSpecifierQualifierList(c: *Context, m: *MacroCtx, scope: *Scope, allow_fail: bool) ParseError!?Node {6214fn parseCSpecifierQualifierList(c: *Context, m: *MacroCtx, scope: *Scope, allow_fail: bool) ParseError!?Node {
6201 const tok = m.next().?;6215 const tok = m.next().?;
6202 switch (tok) {6216 const slice = m.slice();
6203 .identifier, .extended_identifier => {6217 const mangled_name = scope.getAlias(slice);
6204 if (c.global_scope.blank_macros.contains(m.slice())) {6218 if (!m.checkFnParam(mangled_name)) {
6205 return try parseCSpecifierQualifierList(c, m, scope, allow_fail);6219 switch (tok) {
6206 }6220 .identifier, .extended_identifier => {
6207 const mangled_name = scope.getAlias(m.slice());6221 if (c.global_scope.blank_macros.contains(m.slice())) {
6208 if (!allow_fail or c.typedefs.contains(mangled_name)) {6222 return try parseCSpecifierQualifierList(c, m, scope, allow_fail);
6209 if (builtin_typedef_map.get(mangled_name)) |ty| return try Tag.type.create(c.arena, ty);6223 }
6210 return try Tag.identifier.create(c.arena, mangled_name);6224 if (!allow_fail or c.typedefs.contains(mangled_name)) {
6211 }6225 if (builtin_typedef_map.get(mangled_name)) |ty| return try Tag.type.create(c.arena, ty);
6212 },6226 return try Tag.identifier.create(c.arena, mangled_name);
6213 .keyword_void => return try Tag.type.create(c.arena, "anyopaque"),6227 }
6214 .keyword_bool => return try Tag.type.create(c.arena, "bool"),6228 },
6215 .keyword_char,6229 .keyword_void => return try Tag.type.create(c.arena, "anyopaque"),
6216 .keyword_int,6230 .keyword_bool => return try Tag.type.create(c.arena, "bool"),
6217 .keyword_short,6231 .keyword_char,
6218 .keyword_long,6232 .keyword_int,
6219 .keyword_float,6233 .keyword_short,
6220 .keyword_double,6234 .keyword_long,
6221 .keyword_signed,6235 .keyword_float,
6222 .keyword_unsigned,6236 .keyword_double,
6223 .keyword_complex,6237 .keyword_signed,
6224 => {6238 .keyword_unsigned,
6225 m.i -= 1;6239 .keyword_complex,
6226 return try parseCNumericType(c, m);6240 => {
6227 },6241 m.i -= 1;
6228 .keyword_enum, .keyword_struct, .keyword_union => {6242 return try parseCNumericType(c, m);
6229 // struct Foo will be declared as struct_Foo by transRecordDecl6243 },
6230 const slice = m.slice();6244 .keyword_enum, .keyword_struct, .keyword_union => {
6231 try m.skip(c, .identifier);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() });6248 const name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ slice, m.slice() });
6234 return try Tag.identifier.create(c.arena, name);6249 return try Tag.identifier.create(c.arena, name);
6235 },6250 },
6236 else => {},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 }
6237 }6260 }
62386261
6239 if (allow_fail) {6262 if (allow_fail) {
...@@ -6511,7 +6534,7 @@ fn parseCPostfixExprInner(c: *Context, m: *MacroCtx, scope: *Scope, type_name: ?...@@ -6511,7 +6534,7 @@ fn parseCPostfixExprInner(c: *Context, m: *MacroCtx, scope: *Scope, type_name: ?
6511}6534}
65126535
6513fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {6536fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
6514 switch (m.next().?) {6537 sw: switch (m.next().?) {
6515 .bang => {6538 .bang => {
6516 const operand = try macroIntToBool(c, try parseCCastExpr(c, m, scope));6539 const operand = try macroIntToBool(c, try parseCCastExpr(c, m, scope));
6517 return Tag.not.create(c.arena, operand);6540 return Tag.not.create(c.arena, operand);
...@@ -6534,6 +6557,9 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {...@@ -6534,6 +6557,9 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
6534 return Tag.address_of.create(c.arena, operand);6557 return Tag.address_of.create(c.arena, operand);
6535 },6558 },
6536 .keyword_sizeof => {6559 .keyword_sizeof => {
6560 // 'sizeof' could be used as a parameter to a macro function.
6561 if (m.checkFnParam(m.slice())) break :sw;
6562
6537 const operand = if (m.peek().? == .l_paren) blk: {6563 const operand = if (m.peek().? == .l_paren) blk: {
6538 _ = m.next();6564 _ = m.next();
6539 const inner = (try parseCTypeName(c, m, scope, false)).?;6565 const inner = (try parseCTypeName(c, m, scope, false)).?;
...@@ -6544,6 +6570,9 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {...@@ -6544,6 +6570,9 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
6544 return Tag.helpers_sizeof.create(c.arena, operand);6570 return Tag.helpers_sizeof.create(c.arena, operand);
6545 },6571 },
6546 .keyword_alignof => {6572 .keyword_alignof => {
6573 // 'alignof' could be used as a parameter to a macro function.
6574 if (m.checkFnParam(m.slice())) break :sw;
6575
6547 // TODO this won't work if using <stdalign.h>'s6576 // TODO this won't work if using <stdalign.h>'s
6548 // #define alignof _Alignof6577 // #define alignof _Alignof
6549 try m.skip(c, .l_paren);6578 try m.skip(c, .l_paren);
...@@ -6556,11 +6585,11 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {...@@ -6556,11 +6585,11 @@ fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
6556 try m.fail(c, "TODO unary inc/dec expr", .{});6585 try m.fail(c, "TODO unary inc/dec expr", .{});
6557 return error.ParseError;6586 return error.ParseError;
6558 },6587 },
6559 else => {6588 else => {},
6560 m.i -= 1;
6561 return try parseCPostfixExpr(c, m, scope, null);
6562 },
6563 }6589 }
6590
6591 m.i -= 1;
6592 return try parseCPostfixExpr(c, m, scope, null);
6564}6593}
65656594
6566fn getContainer(c: *Context, node: Node) ?Node {6595fn 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// }