authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-09-16 22:38:47+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-09-17 12:41:11+01:00
log0fa8cf44f69dcb00dacf180d88ebfa09bc462120
tree99b05b6dad404a5008677de5b056adec44db24f2
parent28caaea0938e938de96cc66a49f670005a2df9d4
signaturelock-open Commit is signed but in an unrecognized format.

translate-c: do not translate macros which use arguments as struct/union/enum names

Consider this C macro: ```c #define FOO(x) struct x ``` Previously, translate-c did not detect that the `x` in the body referred to the argument, so wrongly translated this code as using the nonexistent `struct_x`. Since undefined identifiers are noticed in AstGen, this prevents the translated file from being usable at all. translate-c now instead detects this case and emits an appropriate compile error in the macro's place.

2 files changed, 32 insertions(+), 6 deletions(-)

src/translate_c.zig+26-6
......@@ -5524,22 +5524,38 @@ const MacroCtx = struct {
55245524 return MacroSlicer{ .source = self.source, .tokens = self.list };
55255525 }
55265526
5527 fn containsUndefinedIdentifier(self: *MacroCtx, scope: *Scope, params: []const ast.Payload.Param) ?[]const u8 {
5527 const MacroTranslateError = union(enum) {
5528 undefined_identifier: []const u8,
5529 invalid_arg_usage: []const u8,
5530 };
5531
5532 fn checkTranslatableMacro(self: *MacroCtx, scope: *Scope, params: []const ast.Payload.Param) ?MacroTranslateError {
55285533 const slicer = self.makeSlicer();
5534 var last_is_type_kw = false;
55295535 var i: usize = 1; // index 0 is the macro name
55305536 while (i < self.list.len) : (i += 1) {
55315537 const token = self.list[i];
55325538 switch (token.id) {
55335539 .Period, .Arrow => i += 1, // skip next token since field identifiers can be unknown
5540 .Keyword_struct, .Keyword_union, .Keyword_enum => if (!last_is_type_kw) {
5541 last_is_type_kw = true;
5542 continue;
5543 },
55345544 .Identifier => {
55355545 const identifier = slicer.slice(token);
55365546 const is_param = for (params) |param| {
55375547 if (param.name != null and mem.eql(u8, identifier, param.name.?)) break true;
55385548 } else false;
5539 if (!scope.contains(identifier) and !isBuiltinDefined(identifier) and !is_param) return identifier;
5549 if (is_param and last_is_type_kw) {
5550 return .{ .invalid_arg_usage = identifier };
5551 }
5552 if (!scope.contains(identifier) and !isBuiltinDefined(identifier) and !is_param) {
5553 return .{ .undefined_identifier = identifier };
5554 }
55405555 },
55415556 else => {},
55425557 }
5558 last_is_type_kw = false;
55435559 }
55445560 return null;
55455561 }
......@@ -5649,8 +5665,10 @@ fn transPreprocessorEntities(c: *Context, unit: *clang.ASTUnit) Error!void {
56495665fn transMacroDefine(c: *Context, m: *MacroCtx) ParseError!void {
56505666 const scope = &c.global_scope.base;
56515667
5652 if (m.containsUndefinedIdentifier(scope, &.{})) |ident|
5653 return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident});
5668 if (m.checkTranslatableMacro(scope, &.{})) |err| switch (err) {
5669 .undefined_identifier => |ident| return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident}),
5670 .invalid_arg_usage => unreachable, // no args
5671 };
56545672
56555673 const init_node = try parseCExpr(c, m, scope);
56565674 const last = m.next().?;
......@@ -5698,8 +5716,10 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {
56985716
56995717 try m.skip(c, .RParen);
57005718
5701 if (m.containsUndefinedIdentifier(scope, fn_params.items)) |ident|
5702 return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident});
5719 if (m.checkTranslatableMacro(scope, fn_params.items)) |err| switch (err) {
5720 .undefined_identifier => |ident| return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident}),
5721 .invalid_arg_usage => |ident| return m.fail(c, "unable to translate macro: untranslatable usage of arg `{s}`", .{ident}),
5722 };
57035723
57045724 const expr = try parseCExpr(c, m, scope);
57055725 const last = m.next().?;
test/translate_c.zig+6
......@@ -4129,4 +4129,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
41294129 \\ }) != 0) {}
41304130 \\}
41314131 });
4132
4133 cases.add("macro using argument as struct name is not translated",
4134 \\#define FOO(x) struct x
4135 , &[_][]const u8{
4136 \\pub const FOO = @compileError("unable to translate macro: untranslatable usage of arg `x`");
4137 });
41324138}