mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 12:59:04 +00:00
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
This commit is contained in:
parent
d57c0cc3bf
commit
3b25205833
@ -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, 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, 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, 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);
|
||||
}
|
||||
|
||||
@ -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, "}");
|
||||
|
||||
|
||||
@ -1749,4 +1749,22 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
cases.add("Allow non-const char* string literals. Issue #9126",
|
||||
\\#include <stdlib.h>
|
||||
\\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;
|
||||
\\}
|
||||
, "");
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user