diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 8052f0b31a..9f1e52428c 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -144,6 +144,7 @@ pub const TypeInfo = union(enum) { alignment: comptime_int, child: type, is_allowzero: bool, + is_null_terminated: bool, /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 903c3a12b3..01d240f31f 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -558,3 +558,4 @@ pub fn refAllDecls(comptime T: type) void { if (!builtin.is_test) return; _ = declarations(T); } + diff --git a/src/all_types.hpp b/src/all_types.hpp index c4178cb130..f5c9c67414 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -55,6 +55,7 @@ enum PtrLen { PtrLenUnknown, PtrLenSingle, PtrLenC, + PtrLenNull, }; // This one corresponds to the builtin.zig enum. @@ -825,6 +826,7 @@ struct AstNodePointerType { Token *allow_zero_token; bool is_const; bool is_volatile; + bool is_null_terminated; }; struct AstNodeInferredArrayType { @@ -838,6 +840,7 @@ struct AstNodeArrayType { Token *allow_zero_token; bool is_const; bool is_volatile; + bool is_null_terminated; }; struct AstNodeUsingNamespace { diff --git a/src/analyze.cpp b/src/analyze.cpp index 0a8b32dca7..d84ba1d37c 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -460,6 +460,8 @@ static const char *ptr_len_to_star_str(PtrLen ptr_len) { return "[*]"; case PtrLenC: return "[*c]"; + case PtrLenNull: + return "[*]null "; } zig_unreachable(); } @@ -7032,7 +7034,7 @@ uint32_t type_id_hash(TypeId x) { return hash_ptr(x.data.error_union.err_set_type) ^ hash_ptr(x.data.error_union.payload_type); case ZigTypeIdPointer: return hash_ptr(x.data.pointer.child_type) + - ((x.data.pointer.ptr_len == PtrLenSingle) ? (uint32_t)1120226602 : (uint32_t)3200913342) + + (uint32_t)x.data.pointer.ptr_len * 1120226602u + (x.data.pointer.is_const ? (uint32_t)2749109194 : (uint32_t)4047371087) + (x.data.pointer.is_volatile ? (uint32_t)536730450 : (uint32_t)1685612214) + (x.data.pointer.allow_zero ? (uint32_t)3324284834 : (uint32_t)3584904923) + diff --git a/src/dump_analysis.cpp b/src/dump_analysis.cpp index 98bbcc6a42..825ae4baa5 100644 --- a/src/dump_analysis.cpp +++ b/src/dump_analysis.cpp @@ -992,6 +992,10 @@ static void anal_dump_type(AnalDumpCtx *ctx, ZigType *ty) { jw_object_field(jw, "len"); jw_int(jw, 3); break; + case PtrLenNull: + jw_object_field(jw, "len"); + jw_int(jw, 4); + break; } anal_dump_pointer_attrs(ctx, ty); break; diff --git a/src/ir.cpp b/src/ir.cpp index 7bca551852..77fc1307ca 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -6043,13 +6043,25 @@ static PtrLen star_token_to_ptr_len(TokenId token_id) { static IrInstruction *ir_gen_pointer_type(IrBuilder *irb, Scope *scope, AstNode *node) { assert(node->type == NodeTypePointerType); + PtrLen ptr_len = star_token_to_ptr_len(node->data.pointer_type.star_token->id); + if (node->data.pointer_type.is_null_terminated) { + if (ptr_len == PtrLenUnknown) { + ptr_len = PtrLenNull; + } else { + exec_add_error_node(irb->codegen, irb->exec, node, + buf_sprintf("null-terminated pointer must be specified with [*] token")); + return irb->codegen->invalid_instruction; + } + } + bool is_const = node->data.pointer_type.is_const; bool is_volatile = node->data.pointer_type.is_volatile; bool is_allow_zero = node->data.pointer_type.allow_zero_token != nullptr; AstNode *expr_node = node->data.pointer_type.op_expr; AstNode *align_expr = node->data.pointer_type.align_expr; + IrInstruction *align_value; if (align_expr != nullptr) { align_value = ir_gen_node(irb, align_expr, scope); @@ -9793,6 +9805,7 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted // alignment can be decreased // bit offset attributes must match exactly // PtrLenSingle/PtrLenUnknown must match exactly, but PtrLenC matches either one + // PtrLenNull can coerce into PtrLenUnknown ZigType *wanted_ptr_type = get_src_ptr_type(wanted_type); ZigType *actual_ptr_type = get_src_ptr_type(actual_type); bool wanted_allows_zero = ptr_allows_addr_zero(wanted_type); @@ -9843,7 +9856,10 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted return result; } bool ptr_lens_equal = actual_ptr_type->data.pointer.ptr_len == wanted_ptr_type->data.pointer.ptr_len; - if ((ptr_lens_equal || wanted_is_c_ptr || actual_is_c_ptr) && + bool ok_null_term_ptrs = + actual_ptr_type->data.pointer.ptr_len == PtrLenNull || + wanted_ptr_type->data.pointer.ptr_len == PtrLenUnknown; + if ((ptr_lens_equal || wanted_is_c_ptr || actual_is_c_ptr || ok_null_term_ptrs) && type_has_bits(wanted_type) == type_has_bits(actual_type) && (!actual_ptr_type->data.pointer.is_const || wanted_ptr_type->data.pointer.is_const) && (!actual_ptr_type->data.pointer.is_volatile || wanted_ptr_type->data.pointer.is_volatile) && @@ -14532,6 +14548,7 @@ static bool is_pointer_arithmetic_allowed(ZigType *lhs_type, IrBinOp op) { case PtrLenSingle: return false; case PtrLenUnknown: + case PtrLenNull: case PtrLenC: break; } @@ -21166,6 +21183,7 @@ static BuiltinPtrSize ptr_len_to_size_enum_index(PtrLen ptr_len) { case PtrLenSingle: return BuiltinPtrSizeOne; case PtrLenUnknown: + case PtrLenNull: return BuiltinPtrSizeMany; case PtrLenC: return BuiltinPtrSizeC; @@ -21210,7 +21228,7 @@ static ConstExprValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_ty result->special = ConstValSpecialStatic; result->type = type_info_pointer_type; - ConstExprValue **fields = alloc_const_vals_ptrs(6); + ConstExprValue **fields = alloc_const_vals_ptrs(7); result->data.x_struct.fields = fields; // size: Size @@ -21246,6 +21264,11 @@ static ConstExprValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_ty fields[5]->special = ConstValSpecialStatic; fields[5]->type = ira->codegen->builtin_types.entry_bool; fields[5]->data.x_bool = attrs_type->data.pointer.allow_zero; + // is_null_terminated: bool + ensure_field_index(result->type, "is_null_terminated", 6); + fields[6]->special = ConstValSpecialStatic; + fields[6]->type = ira->codegen->builtin_types.entry_bool; + fields[6]->data.x_bool = attrs_type->data.pointer.ptr_len == PtrLenNull; return result; }; diff --git a/src/parser.cpp b/src/parser.cpp index e87c83a276..3da0d8a643 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2618,6 +2618,11 @@ static AstNode *ast_parse_prefix_type_op(ParseContext *pc) { if (array != nullptr) { assert(array->type == NodeTypeArrayType); while (true) { + if (eat_token_if(pc, TokenIdKeywordNull) != nullptr) { + array->data.array_type.is_null_terminated = true; + continue; + } + Token *allowzero_token = eat_token_if(pc, TokenIdKeywordAllowZero); if (allowzero_token != nullptr) { array->data.array_type.allow_zero_token = allowzero_token; @@ -2653,6 +2658,11 @@ static AstNode *ast_parse_prefix_type_op(ParseContext *pc) { if (child == nullptr) child = ptr; while (true) { + if (eat_token_if(pc, TokenIdKeywordNull) != nullptr) { + child->data.pointer_type.is_null_terminated = true; + continue; + } + Token *allowzero_token = eat_token_if(pc, TokenIdKeywordAllowZero); if (allowzero_token != nullptr) { child->data.pointer_type.allow_zero_token = allowzero_token; diff --git a/src/translate_c.cpp b/src/translate_c.cpp index 3acfb05e85..6df61ab13c 100644 --- a/src/translate_c.cpp +++ b/src/translate_c.cpp @@ -291,6 +291,7 @@ static TokenId ptr_len_to_token_id(PtrLen ptr_len) { case PtrLenSingle: return TokenIdStar; case PtrLenUnknown: + case PtrLenNull: return TokenIdBracketStarBracket; case PtrLenC: return TokenIdBracketStarCBracket; @@ -302,6 +303,7 @@ static AstNode *trans_create_node_ptr_type(Context *c, bool is_const, bool is_vo AstNode *node = trans_create_node(c, NodeTypePointerType); node->data.pointer_type.star_token = allocate(1); node->data.pointer_type.star_token->id = ptr_len_to_token_id(ptr_len); + node->data.pointer_type.is_null_terminated = (ptr_len == PtrLenNull); node->data.pointer_type.is_const = is_const; node->data.pointer_type.is_volatile = is_volatile; node->data.pointer_type.op_expr = child_node; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index c23897a51a..039afe52df 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -68,6 +68,18 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:9:27: error: @atomicRmw on enum only works with .Xchg", ); + cases.add( + "disallow coercion from non-null-terminated pointer to null-terminated pointer", + \\extern fn puts(s: [*]null const u8) c_int; + \\pub fn main() void { + \\ const no_zero_array = [_]u8{'h', 'e', 'l', 'l', 'o'}; + \\ const no_zero_ptr: [*]const u8 = &no_zero_array; + \\ _ = puts(no_zero_ptr); + \\} + , + "tmp.zig:5:14: error: expected type '[*]null const u8', found '[*]const u8'", + ); + cases.add( "atomic orderings of atomicStore Acquire or AcqRel", \\export fn entry() void { diff --git a/test/stage1/behavior/pointers.zig b/test/stage1/behavior/pointers.zig index bf292bad0d..fd11e68473 100644 --- a/test/stage1/behavior/pointers.zig +++ b/test/stage1/behavior/pointers.zig @@ -200,3 +200,17 @@ test "assign null directly to C pointer and test null equality" { } comptime expect((y1 orelse &othery) == y1); } + +test "null terminated pointer" { + const S = struct { + fn doTheTest() void { + var array_with_zero = [_]u8{'h', 'e', 'l', 'l', 'o', 0}; + var zero_ptr: [*]null const u8 = @ptrCast([*]null const u8, &array_with_zero); + var no_zero_ptr: [*]const u8 = zero_ptr; + expect(std.mem.eql(u8, std.mem.toSliceConst(u8, no_zero_ptr), "hello")); + } + }; + S.doTheTest(); + // TODO test fails at comptime + //comptime S.doTheTest(); +}