diff --git a/src/translate_c.zig b/src/translate_c.zig index e11fc5b736c6..2fc72f41c419 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -719,6 +719,44 @@ fn transQualTypeMaybeInitialized(c: *Context, scope: *Scope, qt: clang.QualType, transQualType(c, scope, qt, loc); } +/// This is used in global scope to convert a string literal `S` to [*c]u8: +/// &(struct { +/// var static: @TypeOf(S.*) = S.*; +/// }).static; +fn stringLiteralToCharStar(c: *Context, scope: *Scope, str: Node) Error!Node { + const var_name = Scope.Block.StaticInnerName; + + const derefed = try Tag.deref.create(c.arena, str); + const var_type = try Tag.typeof.create(c.arena, derefed); + + const variables = try c.arena.alloc(Node, 1); + variables[0] = try Tag.var_decl.create(c.arena, .{ + .is_pub = false, + .is_const = false, + .is_extern = false, + .is_export = false, + .is_threadlocal = false, + .linksection_string = null, + .alignment = null, + .name = var_name, + .type = var_type, + .init = derefed, + }); + + const anon_struct = try Tag.@"struct".create(c.arena, .{ + .layout = .none, + .fields = &.{}, + .functions = &.{}, + .variables = variables, + }); + + const member_access = try Tag.field_access.create(c.arena, .{ + .lhs = anon_struct, + .field_name = var_name, + }); + return Tag.address_of.create(c.arena, member_access); +} + /// if mangled_name is not null, this var decl was declared in a block scope. fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]const u8) Error!void { const var_name = mangled_name orelse try c.str(@ptrCast(*const clang.NamedDecl, var_decl).getName_bytes_begin()); @@ -779,6 +817,8 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co }; if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node.?)) { init_node = try Tag.bool_to_int.create(c.arena, init_node.?); + } else if (init_node.?.tag() == .string_literal and qualTypeIsCharStar(qual_type)) { + init_node = try stringLiteralToCharStar(c, scope, init_node.?); } } else { init_node = Tag.undefined_literal.init(); @@ -1101,9 +1141,10 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD record_payload.* = .{ .base = .{ .tag = ([2]Tag{ .@"struct", .@"union" })[@boolToInt(is_union)] }, .data = .{ - .is_packed = is_packed, + .layout = if (is_packed) .@"packed" else .@"extern", .fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items), .functions = try c.arena.dupe(Node, functions.items), + .variables = &.{}, }, }; break :blk Node.initPayload(&record_payload.base); @@ -1805,6 +1846,9 @@ fn transDeclStmtOne( Tag.undefined_literal.init(); if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node)) { init_node = try Tag.bool_to_int.create(c.arena, init_node); + } else if (init_node.tag() == .string_literal and qualTypeIsCharStar(qual_type)) { + const dst_type_node = try transQualType(c, scope, qual_type, loc); + init_node = try removeCVQualifiers(c, dst_type_node, init_node); } const var_name: []const u8 = if (is_static_local) Scope.Block.StaticInnerName else mangled_name; @@ -2522,9 +2566,19 @@ fn transInitListExprRecord( raw_name = try mem.dupe(c.arena, u8, name); } + var init_expr = try transExpr(c, scope, elem_expr, .used); + const field_qt = field_decl.getType(); + if (init_expr.tag() == .string_literal and qualTypeIsCharStar(field_qt)) { + if (scope.id == .root) { + init_expr = try stringLiteralToCharStar(c, scope, init_expr); + } else { + const dst_type_node = try transQualType(c, scope, field_qt, loc); + init_expr = try removeCVQualifiers(c, dst_type_node, init_expr); + } + } try field_inits.append(.{ .name = raw_name, - .value = try transExpr(c, scope, elem_expr, .used), + .value = init_expr, }); } if (ty_node.castTag(.identifier)) |ident_node| { @@ -3459,6 +3513,10 @@ fn transCallExpr(c: *Context, scope: *Scope, stmt: *const clang.CallExpr, result const param_qt = fn_proto.getParamType(@intCast(c_uint, i)); if (isBoolRes(arg) and cIsNativeInt(param_qt)) { arg = try Tag.bool_to_int.create(c.arena, arg); + } else if (arg.tag() == .string_literal and qualTypeIsCharStar(param_qt)) { + const loc = @ptrCast(*const clang.Stmt, stmt).getBeginLoc(); + const dst_type_node = try transQualType(c, scope, param_qt, loc); + arg = try removeCVQualifiers(c, dst_type_node, arg); } } }, @@ -3835,6 +3893,12 @@ fn transCreateCompoundAssign( return block_scope.complete(c); } +// Casting away const or volatile requires us to use @intToPtr +fn removeCVQualifiers(c: *Context, dst_type_node: Node, expr: Node) Error!Node { + const ptr_to_int = try Tag.ptr_to_int.create(c.arena, expr); + return Tag.int_to_ptr.create(c.arena, .{ .lhs = dst_type_node, .rhs = ptr_to_int }); +} + fn transCPtrCast( c: *Context, scope: *Scope, @@ -3854,10 +3918,7 @@ fn transCPtrCast( (src_child_type.isVolatileQualified() and !child_type.isVolatileQualified()))) { - // Casting away const or volatile requires us to use @intToPtr - const ptr_to_int = try Tag.ptr_to_int.create(c.arena, expr); - const int_to_ptr = try Tag.int_to_ptr.create(c.arena, .{ .lhs = dst_type_node, .rhs = ptr_to_int }); - return int_to_ptr; + return removeCVQualifiers(c, dst_type_node, expr); } else { // Implicit downcasting from higher to lower alignment values is forbidden, // use @alignCast to side-step this problem @@ -4217,6 +4278,26 @@ fn typeIsOpaque(c: *Context, ty: *const clang.Type, loc: clang.SourceLocation) b } } +/// plain `char *` (not const; not explicitly signed or unsigned) +fn qualTypeIsCharStar(qt: clang.QualType) bool { + if (qualTypeIsPtr(qt)) { + const child_qt = qualTypeCanon(qt).getPointeeType(); + return cIsUnqualifiedChar(child_qt) and !child_qt.isConstQualified(); + } + return false; +} + +/// C `char` without explicit signed or unsigned qualifier +fn cIsUnqualifiedChar(qt: clang.QualType) bool { + const c_type = qualTypeCanon(qt); + if (c_type.getTypeClass() != .Builtin) return false; + const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type); + return switch (builtin_ty.getKind()) { + .Char_S, .Char_U => true, + else => false, + }; +} + fn cIsInteger(qt: clang.QualType) bool { return cIsSignedInteger(qt) or cIsUnsignedInteger(qt); } diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index fa6b749589f6..f7d5022a2f5f 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -558,9 +558,10 @@ pub const Payload = struct { pub const Record = struct { base: Payload, data: struct { - is_packed: bool, + layout: enum { @"packed", @"extern", none }, fields: []Field, functions: []Node, + variables: []Node, }, pub const Field = struct { @@ -1952,9 +1953,9 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { fn renderRecord(c: *Context, node: Node) !NodeIndex { const payload = @fieldParentPtr(Payload.Record, "base", node.ptr_otherwise).data; - if (payload.is_packed) + if (payload.layout == .@"packed") _ = try c.addToken(.keyword_packed, "packed") - else + else if (payload.layout == .@"extern") _ = try c.addToken(.keyword_extern, "extern"); const kind_tok = if (node.tag() == .@"struct") try c.addToken(.keyword_struct, "struct") @@ -1963,8 +1964,9 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex { _ = try c.addToken(.l_brace, "{"); + const num_vars = payload.variables.len; const num_funcs = payload.functions.len; - const total_members = payload.fields.len + num_funcs; + const total_members = payload.fields.len + num_vars + num_funcs; const members = try c.gpa.alloc(NodeIndex, std.math.max(total_members, 2)); defer c.gpa.free(members); members[0] = 0; @@ -2006,8 +2008,11 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex { }); _ = try c.addToken(.comma, ","); } + for (payload.variables) |variable, i| { + members[payload.fields.len + i] = try renderNode(c, variable); + } for (payload.functions) |function, i| { - members[payload.fields.len + i] = try renderNode(c, function); + members[payload.fields.len + num_vars + i] = try renderNode(c, function); } _ = try c.addToken(.r_brace, "}"); diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index 2e579b06c750..28ba7aa704ce 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -1749,4 +1749,22 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\ return 0; \\} , ""); + + cases.add("Allow non-const char* string literals. Issue #9126", + \\#include + \\int func(char *x) { return x[0]; } + \\struct S { char *member; }; + \\struct S global_struct = { .member = "global" }; + \\char *g = "global"; + \\int main(void) { + \\ if (g[0] != 'g') abort(); + \\ if (global_struct.member[0] != 'g') abort(); + \\ char *string = "hello"; + \\ if (string[0] != 'h') abort(); + \\ struct S s = {.member = "hello"}; + \\ if (s.member[0] != 'h') abort(); + \\ if (func("foo") != 'f') abort(); + \\ return 0; + \\} + , ""); }