authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-09-18 02:05:35+01:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2023-09-18 14:12:33+03:00
log9ea2076663730ab6ac9cad5cb5f84e58198d4d95
tree2069985d954463dd2e9ef9309d62b44640b8fe99
parentd2a937838e26ad0bb380b843dee9781a96a01ef5

translate-c: prevent variable names conflicting with type names

This introduces the concept of a "weak global name" into translate-c. translate-c consists of two passes. The first is important, because it discovers all global names, which are used to prevent naming conflicts: whenever we see an identifier in the second pass, we can mangle it if it conflicts with any global or any other in-scope identifier. Unfortunately, this is a bit tricky for structs, unions, and enums. In C, these types are not represented by normal identifers, but by separate tags - `struct foo` does not prevent an unrelated identifier `foo` existing. In general, we want to translate type names to user-friendly ones such as `struct_foo` and `foo` where possible, but we can't guarantee such names will not conflict with real variable names. This is where weak global names come in. In the initial pass, when a global type declaration is seen, `struct_foo` and `foo` are both added as weak global names. This essentially means that we will use these names for the type *if possible*, but if there is another global with the same name, we will mangle the type name instead. Then, when actually translating the declaration, we check whether there's a "true" global with a conflicting name, in which case we mangle our name. If the user-friendly alias `foo` conflicts, we do not attempt to mangle it: we just don't emit it, because a mangled alias isn't particularly helpful.

3 files changed, 110 insertions(+), 15 deletions(-)

src/translate_c.zig+66-10
...@@ -218,7 +218,7 @@ const Scope = struct {...@@ -218,7 +218,7 @@ const Scope = struct {
218218
219 /// Check if the global scope contains the name, includes all decls that haven't been translated yet.219 /// Check if the global scope contains the name, includes all decls that haven't been translated yet.
220 fn contains(scope: *Root, name: []const u8) bool {220 fn contains(scope: *Root, name: []const u8) bool {
221 return scope.containsNow(name) or scope.context.global_names.contains(name);221 return scope.containsNow(name) or scope.context.global_names.contains(name) or scope.context.weak_global_names.contains(name);
222 }222 }
223 };223 };
224224
...@@ -335,6 +335,15 @@ pub const Context = struct {...@@ -335,6 +335,15 @@ pub const Context = struct {
335 /// up front in a pre-processing step.335 /// up front in a pre-processing step.
336 global_names: std.StringArrayHashMapUnmanaged(void) = .{},336 global_names: std.StringArrayHashMapUnmanaged(void) = .{},
337337
338 /// This is similar to `global_names`, but contains names which we would
339 /// *like* to use, but do not strictly *have* to if they are unavailable.
340 /// These are relevant to types, which ideally we would name like
341 /// 'struct_foo' with an alias 'foo', but if either of those names is taken,
342 /// may be mangled.
343 /// This is distinct from `global_names` so we can detect at a type
344 /// declaration whether or not the name is available.
345 weak_global_names: std.StringArrayHashMapUnmanaged(void) = .{},
346
338 pattern_list: PatternList,347 pattern_list: PatternList,
339348
340 fn getMangle(c: *Context) u32 {349 fn getMangle(c: *Context) u32 {
...@@ -425,10 +434,8 @@ pub fn translate(...@@ -425,10 +434,8 @@ pub fn translate(
425434
426 try addMacros(&context);435 try addMacros(&context);
427 for (context.alias_list.items) |alias| {436 for (context.alias_list.items) |alias| {
428 if (!context.global_scope.sym_table.contains(alias.alias)) {437 const node = try Tag.alias.create(arena, .{ .actual = alias.alias, .mangled = alias.name });
429 const node = try Tag.alias.create(arena, .{ .actual = alias.alias, .mangled = alias.name });438 try addTopLevelDecl(&context, alias.alias, node);
430 try addTopLevelDecl(&context, alias.alias, node);
431 }
432 }439 }
433440
434 return ast.render(gpa, context.global_scope.nodes.items);441 return ast.render(gpa, context.global_scope.nodes.items);
...@@ -493,7 +500,29 @@ fn declVisitorC(context: ?*anyopaque, decl: *const clang.Decl) callconv(.C) bool...@@ -493,7 +500,29 @@ fn declVisitorC(context: ?*anyopaque, decl: *const clang.Decl) callconv(.C) bool
493fn declVisitorNamesOnly(c: *Context, decl: *const clang.Decl) Error!void {500fn declVisitorNamesOnly(c: *Context, decl: *const clang.Decl) Error!void {
494 if (decl.castToNamedDecl()) |named_decl| {501 if (decl.castToNamedDecl()) |named_decl| {
495 const decl_name = try c.str(named_decl.getName_bytes_begin());502 const decl_name = try c.str(named_decl.getName_bytes_begin());
496 try c.global_names.put(c.gpa, decl_name, {});503
504 switch (decl.getKind()) {
505 .Record, .Enum => {
506 // These types are prefixed with the container kind.
507 const container_prefix = if (decl.getKind() == .Record) prefix: {
508 const record_decl: *const clang.RecordDecl = @ptrCast(decl);
509 if (record_decl.isUnion()) {
510 break :prefix "union";
511 } else {
512 break :prefix "struct";
513 }
514 } else "enum";
515 const prefixed_name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_prefix, decl_name });
516 // `decl_name` and `prefixed_name` are the preferred names for this type.
517 // However, we can name it anything else if necessary, so these are "weak names".
518 try c.weak_global_names.ensureUnusedCapacity(c.gpa, 2);
519 c.weak_global_names.putAssumeCapacity(decl_name, {});
520 c.weak_global_names.putAssumeCapacity(prefixed_name, {});
521 },
522 else => {
523 try c.global_names.put(c.gpa, decl_name, {});
524 },
525 }
497526
498 // Check for typedefs with unnamed enum/record child types.527 // Check for typedefs with unnamed enum/record child types.
499 if (decl.getKind() == .Typedef) {528 if (decl.getKind() == .Typedef) {
...@@ -1079,6 +1108,21 @@ fn flexibleArrayField(c: *Context, record_def: *const clang.RecordDecl) ?*const...@@ -1079,6 +1108,21 @@ fn flexibleArrayField(c: *Context, record_def: *const clang.RecordDecl) ?*const
1079 return flexible_field;1108 return flexible_field;
1080}1109}
10811110
1111fn mangleWeakGlobalName(c: *Context, want_name: []const u8) ![]const u8 {
1112 var cur_name = want_name;
1113
1114 if (!c.weak_global_names.contains(want_name)) {
1115 // This type wasn't noticed by the name detection pass, so nothing has been treating this as
1116 // a weak global name. We must mangle it to avoid conflicts with locals.
1117 cur_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ want_name, c.getMangle() });
1118 }
1119
1120 while (c.global_names.contains(cur_name)) {
1121 cur_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ want_name, c.getMangle() });
1122 }
1123 return cur_name;
1124}
1125
1082fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordDecl) Error!void {1126fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordDecl) Error!void {
1083 if (c.decl_table.get(@intFromPtr(record_decl.getCanonicalDecl()))) |_|1127 if (c.decl_table.get(@intFromPtr(record_decl.getCanonicalDecl()))) |_|
1084 return; // Avoid processing this decl twice1128 return; // Avoid processing this decl twice
...@@ -1113,6 +1157,9 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD...@@ -1113,6 +1157,9 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
1113 is_unnamed = true;1157 is_unnamed = true;
1114 }1158 }
1115 name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_kind_name, bare_name });1159 name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_kind_name, bare_name });
1160 if (toplevel and !is_unnamed) {
1161 name = try mangleWeakGlobalName(c, name);
1162 }
1116 }1163 }
1117 if (!toplevel) name = try bs.makeMangledName(c, name);1164 if (!toplevel) name = try bs.makeMangledName(c, name);
1118 try c.decl_table.putNoClobber(c.gpa, @intFromPtr(record_decl.getCanonicalDecl()), name);1165 try c.decl_table.putNoClobber(c.gpa, @intFromPtr(record_decl.getCanonicalDecl()), name);
...@@ -1217,7 +1264,10 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD...@@ -1217,7 +1264,10 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
1217 const node = Node.initPayload(&payload.base);1264 const node = Node.initPayload(&payload.base);
1218 if (toplevel) {1265 if (toplevel) {
1219 try addTopLevelDecl(c, name, node);1266 try addTopLevelDecl(c, name, node);
1220 if (!is_unnamed)1267 // Only add the alias if the name is available *and* it was caught by
1268 // name detection. Don't bother performing a weak mangle, since a
1269 // mangled name is of no real use here.
1270 if (!is_unnamed and !c.global_names.contains(bare_name) and c.weak_global_names.contains(bare_name))
1221 try c.alias_list.append(.{ .alias = bare_name, .name = name });1271 try c.alias_list.append(.{ .alias = bare_name, .name = name });
1222 } else {1272 } else {
1223 try scope.appendNode(node);1273 try scope.appendNode(node);
...@@ -1246,6 +1296,9 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const clang.EnumDecl) E...@@ -1246,6 +1296,9 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const clang.EnumDecl) E
1246 is_unnamed = true;1296 is_unnamed = true;
1247 }1297 }
1248 name = try std.fmt.allocPrint(c.arena, "enum_{s}", .{bare_name});1298 name = try std.fmt.allocPrint(c.arena, "enum_{s}", .{bare_name});
1299 if (toplevel and !is_unnamed) {
1300 name = try mangleWeakGlobalName(c, name);
1301 }
1249 }1302 }
1250 if (!toplevel) name = try bs.makeMangledName(c, name);1303 if (!toplevel) name = try bs.makeMangledName(c, name);
1251 try c.decl_table.putNoClobber(c.gpa, @intFromPtr(enum_decl.getCanonicalDecl()), name);1304 try c.decl_table.putNoClobber(c.gpa, @intFromPtr(enum_decl.getCanonicalDecl()), name);
...@@ -1313,7 +1366,10 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const clang.EnumDecl) E...@@ -1313,7 +1366,10 @@ fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const clang.EnumDecl) E
1313 const node = Node.initPayload(&payload.base);1366 const node = Node.initPayload(&payload.base);
1314 if (toplevel) {1367 if (toplevel) {
1315 try addTopLevelDecl(c, name, node);1368 try addTopLevelDecl(c, name, node);
1316 if (!is_unnamed)1369 // Only add the alias if the name is available *and* it was caught by
1370 // name detection. Don't bother performing a weak mangle, since a
1371 // mangled name is of no real use here.
1372 if (!is_unnamed and !c.global_names.contains(bare_name) and c.weak_global_names.contains(bare_name))
1317 try c.alias_list.append(.{ .alias = bare_name, .name = name });1373 try c.alias_list.append(.{ .alias = bare_name, .name = name });
1318 } else {1374 } else {
1319 try scope.appendNode(node);1375 try scope.appendNode(node);
...@@ -4881,7 +4937,7 @@ fn transType(c: *Context, scope: *Scope, ty: *const clang.Type, source_loc: clan...@@ -4881,7 +4937,7 @@ fn transType(c: *Context, scope: *Scope, ty: *const clang.Type, source_loc: clan
4881 var trans_scope = scope;4937 var trans_scope = scope;
4882 if (@as(*const clang.Decl, @ptrCast(record_decl)).castToNamedDecl()) |named_decl| {4938 if (@as(*const clang.Decl, @ptrCast(record_decl)).castToNamedDecl()) |named_decl| {
4883 const decl_name = try c.str(named_decl.getName_bytes_begin());4939 const decl_name = try c.str(named_decl.getName_bytes_begin());
4884 if (c.global_names.get(decl_name)) |_| trans_scope = &c.global_scope.base;4940 if (c.weak_global_names.contains(decl_name)) trans_scope = &c.global_scope.base;
4885 }4941 }
4886 try transRecordDecl(c, trans_scope, record_decl);4942 try transRecordDecl(c, trans_scope, record_decl);
4887 const name = c.decl_table.get(@intFromPtr(record_decl.getCanonicalDecl())).?;4943 const name = c.decl_table.get(@intFromPtr(record_decl.getCanonicalDecl())).?;
...@@ -4894,7 +4950,7 @@ fn transType(c: *Context, scope: *Scope, ty: *const clang.Type, source_loc: clan...@@ -4894,7 +4950,7 @@ fn transType(c: *Context, scope: *Scope, ty: *const clang.Type, source_loc: clan
4894 var trans_scope = scope;4950 var trans_scope = scope;
4895 if (@as(*const clang.Decl, @ptrCast(enum_decl)).castToNamedDecl()) |named_decl| {4951 if (@as(*const clang.Decl, @ptrCast(enum_decl)).castToNamedDecl()) |named_decl| {
4896 const decl_name = try c.str(named_decl.getName_bytes_begin());4952 const decl_name = try c.str(named_decl.getName_bytes_begin());
4897 if (c.global_names.get(decl_name)) |_| trans_scope = &c.global_scope.base;4953 if (c.weak_global_names.contains(decl_name)) trans_scope = &c.global_scope.base;
4898 }4954 }
4899 try transEnumDecl(c, trans_scope, enum_decl);4955 try transEnumDecl(c, trans_scope, enum_decl);
4900 const name = c.decl_table.get(@intFromPtr(enum_decl.getCanonicalDecl())).?;4956 const name = c.decl_table.get(@intFromPtr(enum_decl.getCanonicalDecl())).?;
test/run_translated_c.zig+24
...@@ -1905,4 +1905,28 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {...@@ -1905,4 +1905,28 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
1905 \\ return 0;1905 \\ return 0;
1906 \\}1906 \\}
1907 , "");1907 , "");
1908
1909 cases.add("struct without global declaration does not conflict with local variable name",
1910 \\#include <stdlib.h>
1911 \\static void foo(struct foobar *unused) {}
1912 \\int main(void) {
1913 \\ int struct_foobar = 123;
1914 \\ if (struct_foobar != 123) abort();
1915 \\ int foobar = 456;
1916 \\ if (foobar != 456) abort();
1917 \\ return 0;
1918 \\}
1919 , "");
1920
1921 cases.add("struct without global declaration does not conflict with global variable name",
1922 \\#include <stdlib.h>
1923 \\static void foo(struct foobar *unused) {}
1924 \\static int struct_foobar = 123;
1925 \\static int foobar = 456;
1926 \\int main(void) {
1927 \\ if (struct_foobar != 123) abort();
1928 \\ if (foobar != 456) abort();
1929 \\ return 0;
1930 \\}
1931 , "");
1908}1932}
test/translate_c.zig+20-5
...@@ -148,16 +148,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -148,16 +148,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
148 \\} a = {};148 \\} a = {};
149 \\#define PTR void *149 \\#define PTR void *
150 , &[_][]const u8{150 , &[_][]const u8{
151 \\pub const struct_Bar = extern struct {151 \\pub const struct_Bar_1 = extern struct {
152 \\ a: c_int,152 \\ a: c_int,
153 \\};153 \\};
154 \\pub const struct_Foo = extern struct {154 \\pub const struct_Foo = extern struct {
155 \\ a: c_int,155 \\ a: c_int,
156 \\ b: struct_Bar,156 \\ b: struct_Bar_1,
157 \\};157 \\};
158 \\pub export var a: struct_Foo = struct_Foo{158 \\pub export var a: struct_Foo = struct_Foo{
159 \\ .a = 0,159 \\ .a = 0,
160 \\ .b = @import("std").mem.zeroes(struct_Bar),160 \\ .b = @import("std").mem.zeroes(struct_Bar_1),
161 \\};161 \\};
162 ,162 ,
163 \\pub const PTR = ?*anyopaque;163 \\pub const PTR = ?*anyopaque;
...@@ -2361,11 +2361,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -2361,11 +2361,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
2361 \\ struct Bar c;2361 \\ struct Bar c;
2362 \\};2362 \\};
2363 , &[_][]const u8{2363 , &[_][]const u8{
2364 \\pub const struct_Bar = extern struct {2364 \\pub const struct_Bar_1 = extern struct {
2365 \\ b: c_int,2365 \\ b: c_int,
2366 \\};2366 \\};
2367 \\pub const struct_Foo = extern struct {2367 \\pub const struct_Foo = extern struct {
2368 \\ c: struct_Bar,2368 \\ c: struct_Bar_1,
2369 \\};2369 \\};
2370 });2370 });
2371 }2371 }
...@@ -4135,4 +4135,19 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -4135,4 +4135,19 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
4135 , &[_][]const u8{4135 , &[_][]const u8{
4136 \\pub const FOO = @compileError("unable to translate macro: untranslatable usage of arg `x`");4136 \\pub const FOO = @compileError("unable to translate macro: untranslatable usage of arg `x`");
4137 });4137 });
4138
4139 cases.add("global struct whose default name conflicts with global is mangled",
4140 \\struct foo {
4141 \\ int x;
4142 \\};
4143 \\const char *struct_foo = "hello world";
4144 , &[_][]const u8{
4145 \\pub const struct_foo_1 = extern struct {
4146 \\ x: c_int,
4147 \\};
4148 ,
4149 \\pub const foo = struct_foo_1;
4150 ,
4151 \\pub export var struct_foo: [*c]const u8 = "hello world";
4152 });
4138}4153}