From 40e483d55c1bb4f3ad04232f274ff80afa8540af Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 24 Nov 2025 00:22:58 +0200 Subject: [PATCH 1/2] detect self-referential initializers Closes https://github.com/ziglang/translate-c/issues/223 --- src/Translator.zig | 40 ++++++++++++++++++-- test/cases/translate/self_referential_init.c | 20 ++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 test/cases/translate/self_referential_init.c diff --git a/src/Translator.zig b/src/Translator.zig index 01248b9..63acfd4 100644 --- a/src/Translator.zig +++ b/src/Translator.zig @@ -62,7 +62,7 @@ pub const QualTypeHashContext = struct { pub const Error = std.mem.Allocator.Error; pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; pub const TypeError = Error || error{UnsupportedType}; -pub const TransError = TypeError || error{UnsupportedTranslation}; +pub const TransError = TypeError || error{ UnsupportedTranslation, SelfReferential }; const Translator = @This(); @@ -117,6 +117,10 @@ typedefs: std.StringArrayHashMapUnmanaged(void) = .empty, /// The lhs lval of a compound assignment expression. compound_assign_dummy: ?ZigNode = null, +/// Set of variables whose initializers are currently being translated. +/// Used to detect self-referential initializers. +wip_var_inits: std.AutoHashMapUnmanaged(Node.Index, void) = .empty, + pub fn getMangle(t: *Translator) u32 { t.mangle_count += 1; return t.mangle_count; @@ -243,6 +247,7 @@ pub fn translate(options: Options) mem.Allocator.Error![]u8 { translator.anonymous_record_field_names.deinit(gpa); translator.typedefs.deinit(gpa); translator.global_scope.deinit(); + translator.wip_var_inits.deinit(gpa); } try translator.prepopulateGlobalNameTable(); @@ -420,7 +425,7 @@ fn transDecl(t: *Translator, scope: *Scope, decl: Node.Index) !void { .variable => |variable| { if (variable.definition != null) return; - try t.transVarDecl(scope, variable); + try t.transVarDecl(scope, variable, decl); }, .static_assert => |static_assert| { try t.transStaticAssert(&t.global_scope.base, static_assert); @@ -817,6 +822,7 @@ fn transFnDecl(t: *Translator, scope: *Scope, function: Node.Function) Error!voi t.transCompoundStmtInline(body_stmt, &block_scope) catch |err| switch (err) { error.OutOfMemory => |e| return e, + error.SelfReferential => unreachable, error.UnsupportedTranslation, error.UnsupportedType, => { @@ -833,7 +839,7 @@ fn transFnDecl(t: *Translator, scope: *Scope, function: Node.Function) Error!voi return t.addTopLevelDecl(fn_name, proto_node); } -fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!void { +fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable, decl_node: Node.Index) Error!void { const base_name = t.tree.tokSlice(variable.name_tok); const toplevel = scope.id == .root; const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; @@ -871,13 +877,21 @@ fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!vo var is_const = variable.qt.@"const" or (array_ty != null and array_ty.?.elem.@"const"); var is_extern = variable.storage_class == .@"extern"; + var self_referential = false; const init_node = init: { if (variable.initializer) |init| { const maybe_literal = init.get(t.tree); + if (!toplevel) try t.wip_var_inits.putNoClobber(t.gpa, decl_node, {}); + defer _ = t.wip_var_inits.remove(decl_node); + const init_node = (if (maybe_literal == .string_literal_expr) t.transStringLiteralInitializer(init, maybe_literal.string_literal_expr, type_node) else t.transExprCoercing(scope, init, .used)) catch |err| switch (err) { + error.SelfReferential => { + self_referential = true; + break :init ZigTag.undefined_literal.init(); + }, error.UnsupportedTranslation, error.UnsupportedType => { return t.failDecl(scope, variable.name_tok, name, "unable to resolve var init expr", .{}); }, @@ -950,6 +964,21 @@ fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!vo node = try ZigTag.wrapped_local.create(t.arena, .{ .name = name, .init = node }); } try scope.appendNode(node); + if (self_referential) { + var deferred_init = t.transExprCoercing(scope, variable.initializer.?, .used) catch |err| switch (err) { + error.SelfReferential => unreachable, + error.UnsupportedTranslation, error.UnsupportedType => { + return t.failDecl(scope, variable.name_tok, name, "unable to resolve var init expr", .{}); + }, + else => |e| return e, + }; + if (!variable.qt.is(t.comp, .bool) and deferred_init.isBoolRes()) { + deferred_init = try ZigTag.int_from_bool.create(t.arena, deferred_init); + } + const varname = try ZigTag.identifier.create(t.arena, name); + const assign = try ZigTag.assign.create(t.arena, .{ .lhs = varname, .rhs = deferred_init }); + try scope.appendNode(assign); + } try bs.discardVariable(name); if (variable.qt.getAttribute(t.comp, .cleanup)) |cleanup_attr| { @@ -1057,6 +1086,7 @@ fn transEnumDecl(t: *Translator, scope: *Scope, enum_qt: QualType) Error!void { fn transStaticAssert(t: *Translator, scope: *Scope, static_assert: Node.StaticAssert) Error!void { const condition = t.transExpr(scope, static_assert.cond, .used) catch |err| switch (err) { + error.SelfReferential => unreachable, error.UnsupportedTranslation, error.UnsupportedType => { return try t.warn(&t.global_scope.base, static_assert.cond.tok(t.tree), "unable to translate _Static_assert condition", .{}); }, @@ -1596,7 +1626,7 @@ fn transStmt(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode return ZigTag.declaration.init(); }, .variable => |variable| { - try t.transVarDecl(scope, variable); + try t.transVarDecl(scope, variable, stmt); return ZigTag.declaration.init(); }, .switch_stmt => |switch_stmt| return t.transSwitch(scope, switch_stmt), @@ -2616,6 +2646,8 @@ fn transPointerCastExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransEr } fn transDeclRefExpr(t: *Translator, scope: *Scope, decl_ref: Node.DeclRef) TransError!ZigNode { + if (t.wip_var_inits.contains(decl_ref.decl)) return error.SelfReferential; + const name = t.tree.tokSlice(decl_ref.name_tok); const maybe_alias = scope.getAlias(name); const mangled_name = maybe_alias orelse name; diff --git a/test/cases/translate/self_referential_init.c b/test/cases/translate/self_referential_init.c new file mode 100644 index 0000000..e1735f7 --- /dev/null +++ b/test/cases/translate/self_referential_init.c @@ -0,0 +1,20 @@ +int *a = (int*)(&a + 1); // TODO result missing casts + +extern void *alloc(unsigned long long); +void foo(void) { + int b = b + 1; + int *c = alloc(sizeof(*c)); +} + +// translate +// +// pub export var a: [*c]c_int = @ptrCast(@alignCast((&a) + @as(usize, @bitCast(@as(isize, @intCast(@as(c_int, 1))))))); +// pub extern fn alloc(c_ulonglong) ?*anyopaque; +// pub export fn foo() void { +// var b: c_int = undefined; +// b = b + @as(c_int, 1); +// _ = &b; +// var c: [*c]c_int = undefined; +// c = @ptrCast(@alignCast(alloc(@sizeOf(@TypeOf(c.*))))); +// _ = &c; +// } From 5ddbd5cb7d439b48702c5e6b57e7ad074157c56a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 24 Nov 2025 12:49:26 +0200 Subject: [PATCH 2/2] self referential vars can't be const --- src/Translator.zig | 2 +- test/cases/translate/self_referential_init.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Translator.zig b/src/Translator.zig index 63acfd4..8fbf147 100644 --- a/src/Translator.zig +++ b/src/Translator.zig @@ -946,7 +946,7 @@ fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable, decl_nod const alignment: ?c_uint = variable.qt.requestedAlignment(t.comp) orelse null; var node = try ZigTag.var_decl.create(t.arena, .{ .is_pub = toplevel, - .is_const = is_const, + .is_const = is_const and !self_referential, .is_extern = is_extern, .is_export = toplevel and variable.storage_class == .auto and linkage == .strong, .is_threadlocal = variable.thread_local, diff --git a/test/cases/translate/self_referential_init.c b/test/cases/translate/self_referential_init.c index e1735f7..9b25f8e 100644 --- a/test/cases/translate/self_referential_init.c +++ b/test/cases/translate/self_referential_init.c @@ -4,6 +4,7 @@ extern void *alloc(unsigned long long); void foo(void) { int b = b + 1; int *c = alloc(sizeof(*c)); + const double *d = alloc(sizeof(*d)); } // translate @@ -17,4 +18,7 @@ void foo(void) { // var c: [*c]c_int = undefined; // c = @ptrCast(@alignCast(alloc(@sizeOf(@TypeOf(c.*))))); // _ = &c; +// var d: [*c]const f64 = undefined; +// d = @ptrCast(@alignCast(alloc(@sizeOf(@TypeOf(d.*))))); +// _ = &d; // }