authorgravatar for evan@lagerdata.comEvan Haas <evan@lagerdata.com> 2021-08-01 17:14:57-07:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2021-08-21 16:32:09+03:00
log3b2520583390c7ebbb1e1ea458c9ced04180d9c4
tree9bd342ec64c9f1852a3ef49f85bc201d15232248
parentd57c0cc3bfeff9af297279759ec2b631e6d95140

translate-c: allow string literals to be used as `char *`

In C the type of string literals is `char *`, so when using them in a non-const context we have to cast the const away. Fixes #9126

3 files changed, 115 insertions(+), 11 deletions(-)

src/translate_c.zig+87-6
...@@ -719,6 +719,44 @@ fn transQualTypeMaybeInitialized(c: *Context, scope: *Scope, qt: clang.QualType,...@@ -719,6 +719,44 @@ fn transQualTypeMaybeInitialized(c: *Context, scope: *Scope, qt: clang.QualType,
719 transQualType(c, scope, qt, loc);719 transQualType(c, scope, qt, loc);
720}720}
721721
722/// This is used in global scope to convert a string literal `S` to [*c]u8:
723/// &(struct {
724/// var static: @TypeOf(S.*) = S.*;
725/// }).static;
726fn stringLiteralToCharStar(c: *Context, str: Node) Error!Node {
727 const var_name = Scope.Block.StaticInnerName;
728
729 const derefed = try Tag.deref.create(c.arena, str);
730 const var_type = try Tag.typeof.create(c.arena, derefed);
731
732 const variables = try c.arena.alloc(Node, 1);
733 variables[0] = try Tag.var_decl.create(c.arena, .{
734 .is_pub = false,
735 .is_const = false,
736 .is_extern = false,
737 .is_export = false,
738 .is_threadlocal = false,
739 .linksection_string = null,
740 .alignment = null,
741 .name = var_name,
742 .type = var_type,
743 .init = derefed,
744 });
745
746 const anon_struct = try Tag.@"struct".create(c.arena, .{
747 .layout = .none,
748 .fields = &.{},
749 .functions = &.{},
750 .variables = variables,
751 });
752
753 const member_access = try Tag.field_access.create(c.arena, .{
754 .lhs = anon_struct,
755 .field_name = var_name,
756 });
757 return Tag.address_of.create(c.arena, member_access);
758}
759
722/// if mangled_name is not null, this var decl was declared in a block scope.760/// if mangled_name is not null, this var decl was declared in a block scope.
723fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]const u8) Error!void {761fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]const u8) Error!void {
724 const var_name = mangled_name orelse try c.str(@ptrCast(*const clang.NamedDecl, var_decl).getName_bytes_begin());762 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...@@ -779,6 +817,8 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co
779 };817 };
780 if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node.?)) {818 if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node.?)) {
781 init_node = try Tag.bool_to_int.create(c.arena, init_node.?);819 init_node = try Tag.bool_to_int.create(c.arena, init_node.?);
820 } else if (init_node.?.tag() == .string_literal and qualTypeIsCharStar(qual_type)) {
821 init_node = try stringLiteralToCharStar(c, init_node.?);
782 }822 }
783 } else {823 } else {
784 init_node = Tag.undefined_literal.init();824 init_node = Tag.undefined_literal.init();
...@@ -1101,9 +1141,10 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD...@@ -1101,9 +1141,10 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD
1101 record_payload.* = .{1141 record_payload.* = .{
1102 .base = .{ .tag = ([2]Tag{ .@"struct", .@"union" })[@boolToInt(is_union)] },1142 .base = .{ .tag = ([2]Tag{ .@"struct", .@"union" })[@boolToInt(is_union)] },
1103 .data = .{1143 .data = .{
1104 .is_packed = is_packed,1144 .layout = if (is_packed) .@"packed" else .@"extern",
1105 .fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items),1145 .fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items),
1106 .functions = try c.arena.dupe(Node, functions.items),1146 .functions = try c.arena.dupe(Node, functions.items),
1147 .variables = &.{},
1107 },1148 },
1108 };1149 };
1109 break :blk Node.initPayload(&record_payload.base);1150 break :blk Node.initPayload(&record_payload.base);
...@@ -1805,6 +1846,9 @@ fn transDeclStmtOne(...@@ -1805,6 +1846,9 @@ fn transDeclStmtOne(
1805 Tag.undefined_literal.init();1846 Tag.undefined_literal.init();
1806 if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node)) {1847 if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node)) {
1807 init_node = try Tag.bool_to_int.create(c.arena, init_node);1848 init_node = try Tag.bool_to_int.create(c.arena, init_node);
1849 } else if (init_node.tag() == .string_literal and qualTypeIsCharStar(qual_type)) {
1850 const dst_type_node = try transQualType(c, scope, qual_type, loc);
1851 init_node = try removeCVQualifiers(c, dst_type_node, init_node);
1808 }1852 }
18091853
1810 const var_name: []const u8 = if (is_static_local) Scope.Block.StaticInnerName else mangled_name;1854 const var_name: []const u8 = if (is_static_local) Scope.Block.StaticInnerName else mangled_name;
...@@ -2522,9 +2566,19 @@ fn transInitListExprRecord(...@@ -2522,9 +2566,19 @@ fn transInitListExprRecord(
2522 raw_name = try mem.dupe(c.arena, u8, name);2566 raw_name = try mem.dupe(c.arena, u8, name);
2523 }2567 }
25242568
2569 var init_expr = try transExpr(c, scope, elem_expr, .used);
2570 const field_qt = field_decl.getType();
2571 if (init_expr.tag() == .string_literal and qualTypeIsCharStar(field_qt)) {
2572 if (scope.id == .root) {
2573 init_expr = try stringLiteralToCharStar(c, init_expr);
2574 } else {
2575 const dst_type_node = try transQualType(c, scope, field_qt, loc);
2576 init_expr = try removeCVQualifiers(c, dst_type_node, init_expr);
2577 }
2578 }
2525 try field_inits.append(.{2579 try field_inits.append(.{
2526 .name = raw_name,2580 .name = raw_name,
2527 .value = try transExpr(c, scope, elem_expr, .used),2581 .value = init_expr,
2528 });2582 });
2529 }2583 }
2530 if (ty_node.castTag(.identifier)) |ident_node| {2584 if (ty_node.castTag(.identifier)) |ident_node| {
...@@ -3459,6 +3513,10 @@ fn transCallExpr(c: *Context, scope: *Scope, stmt: *const clang.CallExpr, result...@@ -3459,6 +3513,10 @@ fn transCallExpr(c: *Context, scope: *Scope, stmt: *const clang.CallExpr, result
3459 const param_qt = fn_proto.getParamType(@intCast(c_uint, i));3513 const param_qt = fn_proto.getParamType(@intCast(c_uint, i));
3460 if (isBoolRes(arg) and cIsNativeInt(param_qt)) {3514 if (isBoolRes(arg) and cIsNativeInt(param_qt)) {
3461 arg = try Tag.bool_to_int.create(c.arena, arg);3515 arg = try Tag.bool_to_int.create(c.arena, arg);
3516 } else if (arg.tag() == .string_literal and qualTypeIsCharStar(param_qt)) {
3517 const loc = @ptrCast(*const clang.Stmt, stmt).getBeginLoc();
3518 const dst_type_node = try transQualType(c, scope, param_qt, loc);
3519 arg = try removeCVQualifiers(c, dst_type_node, arg);
3462 }3520 }
3463 }3521 }
3464 },3522 },
...@@ -3835,6 +3893,12 @@ fn transCreateCompoundAssign(...@@ -3835,6 +3893,12 @@ fn transCreateCompoundAssign(
3835 return block_scope.complete(c);3893 return block_scope.complete(c);
3836}3894}
38373895
3896// Casting away const or volatile requires us to use @intToPtr
3897fn removeCVQualifiers(c: *Context, dst_type_node: Node, expr: Node) Error!Node {
3898 const ptr_to_int = try Tag.ptr_to_int.create(c.arena, expr);
3899 return Tag.int_to_ptr.create(c.arena, .{ .lhs = dst_type_node, .rhs = ptr_to_int });
3900}
3901
3838fn transCPtrCast(3902fn transCPtrCast(
3839 c: *Context,3903 c: *Context,
3840 scope: *Scope,3904 scope: *Scope,
...@@ -3854,10 +3918,7 @@ fn transCPtrCast(...@@ -3854,10 +3918,7 @@ fn transCPtrCast(
3854 (src_child_type.isVolatileQualified() and3918 (src_child_type.isVolatileQualified() and
3855 !child_type.isVolatileQualified())))3919 !child_type.isVolatileQualified())))
3856 {3920 {
3857 // Casting away const or volatile requires us to use @intToPtr3921 return removeCVQualifiers(c, dst_type_node, expr);
3858 const ptr_to_int = try Tag.ptr_to_int.create(c.arena, expr);
3859 const int_to_ptr = try Tag.int_to_ptr.create(c.arena, .{ .lhs = dst_type_node, .rhs = ptr_to_int });
3860 return int_to_ptr;
3861 } else {3922 } else {
3862 // Implicit downcasting from higher to lower alignment values is forbidden,3923 // Implicit downcasting from higher to lower alignment values is forbidden,
3863 // use @alignCast to side-step this problem3924 // use @alignCast to side-step this problem
...@@ -4217,6 +4278,26 @@ fn typeIsOpaque(c: *Context, ty: *const clang.Type, loc: clang.SourceLocation) b...@@ -4217,6 +4278,26 @@ fn typeIsOpaque(c: *Context, ty: *const clang.Type, loc: clang.SourceLocation) b
4217 }4278 }
4218}4279}
42194280
4281/// plain `char *` (not const; not explicitly signed or unsigned)
4282fn qualTypeIsCharStar(qt: clang.QualType) bool {
4283 if (qualTypeIsPtr(qt)) {
4284 const child_qt = qualTypeCanon(qt).getPointeeType();
4285 return cIsUnqualifiedChar(child_qt) and !child_qt.isConstQualified();
4286 }
4287 return false;
4288}
4289
4290/// C `char` without explicit signed or unsigned qualifier
4291fn cIsUnqualifiedChar(qt: clang.QualType) bool {
4292 const c_type = qualTypeCanon(qt);
4293 if (c_type.getTypeClass() != .Builtin) return false;
4294 const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type);
4295 return switch (builtin_ty.getKind()) {
4296 .Char_S, .Char_U => true,
4297 else => false,
4298 };
4299}
4300
4220fn cIsInteger(qt: clang.QualType) bool {4301fn cIsInteger(qt: clang.QualType) bool {
4221 return cIsSignedInteger(qt) or cIsUnsignedInteger(qt);4302 return cIsSignedInteger(qt) or cIsUnsignedInteger(qt);
4222}4303}
src/translate_c/ast.zig+10-5
...@@ -558,9 +558,10 @@ pub const Payload = struct {...@@ -558,9 +558,10 @@ pub const Payload = struct {
558 pub const Record = struct {558 pub const Record = struct {
559 base: Payload,559 base: Payload,
560 data: struct {560 data: struct {
561 is_packed: bool,561 layout: enum { @"packed", @"extern", none },
562 fields: []Field,562 fields: []Field,
563 functions: []Node,563 functions: []Node,
564 variables: []Node,
564 },565 },
565566
566 pub const Field = struct {567 pub const Field = struct {
...@@ -1952,9 +1953,9 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {...@@ -1952,9 +1953,9 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
19521953
1953fn renderRecord(c: *Context, node: Node) !NodeIndex {1954fn renderRecord(c: *Context, node: Node) !NodeIndex {
1954 const payload = @fieldParentPtr(Payload.Record, "base", node.ptr_otherwise).data;1955 const payload = @fieldParentPtr(Payload.Record, "base", node.ptr_otherwise).data;
1955 if (payload.is_packed)1956 if (payload.layout == .@"packed")
1956 _ = try c.addToken(.keyword_packed, "packed")1957 _ = try c.addToken(.keyword_packed, "packed")
1957 else1958 else if (payload.layout == .@"extern")
1958 _ = try c.addToken(.keyword_extern, "extern");1959 _ = try c.addToken(.keyword_extern, "extern");
1959 const kind_tok = if (node.tag() == .@"struct")1960 const kind_tok = if (node.tag() == .@"struct")
1960 try c.addToken(.keyword_struct, "struct")1961 try c.addToken(.keyword_struct, "struct")
...@@ -1963,8 +1964,9 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex {...@@ -1963,8 +1964,9 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex {
19631964
1964 _ = try c.addToken(.l_brace, "{");1965 _ = try c.addToken(.l_brace, "{");
19651966
1967 const num_vars = payload.variables.len;
1966 const num_funcs = payload.functions.len;1968 const num_funcs = payload.functions.len;
1967 const total_members = payload.fields.len + num_funcs;1969 const total_members = payload.fields.len + num_vars + num_funcs;
1968 const members = try c.gpa.alloc(NodeIndex, std.math.max(total_members, 2));1970 const members = try c.gpa.alloc(NodeIndex, std.math.max(total_members, 2));
1969 defer c.gpa.free(members);1971 defer c.gpa.free(members);
1970 members[0] = 0;1972 members[0] = 0;
...@@ -2006,8 +2008,11 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex {...@@ -2006,8 +2008,11 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex {
2006 });2008 });
2007 _ = try c.addToken(.comma, ",");2009 _ = try c.addToken(.comma, ",");
2008 }2010 }
2011 for (payload.variables) |variable, i| {
2012 members[payload.fields.len + i] = try renderNode(c, variable);
2013 }
2009 for (payload.functions) |function, i| {2014 for (payload.functions) |function, i| {
2010 members[payload.fields.len + i] = try renderNode(c, function);2015 members[payload.fields.len + num_vars + i] = try renderNode(c, function);
2011 }2016 }
2012 _ = try c.addToken(.r_brace, "}");2017 _ = try c.addToken(.r_brace, "}");
20132018
test/run_translated_c.zig+18
...@@ -1749,4 +1749,22 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {...@@ -1749,4 +1749,22 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
1749 \\ return 0;1749 \\ return 0;
1750 \\}1750 \\}
1751 , "");1751 , "");
1752
1753 cases.add("Allow non-const char* string literals. Issue #9126",
1754 \\#include <stdlib.h>
1755 \\int func(char *x) { return x[0]; }
1756 \\struct S { char *member; };
1757 \\struct S global_struct = { .member = "global" };
1758 \\char *g = "global";
1759 \\int main(void) {
1760 \\ if (g[0] != 'g') abort();
1761 \\ if (global_struct.member[0] != 'g') abort();
1762 \\ char *string = "hello";
1763 \\ if (string[0] != 'h') abort();
1764 \\ struct S s = {.member = "hello"};
1765 \\ if (s.member[0] != 'h') abort();
1766 \\ if (func("foo") != 'f') abort();
1767 \\ return 0;
1768 \\}
1769 , "");
1752}1770}