diff --git a/lib/std/buffer.zig b/lib/std/buffer.zig index 55a5a890d667..6313d693b71c 100644 --- a/lib/std/buffer.zig +++ b/lib/std/buffer.zig @@ -82,11 +82,11 @@ pub const Buffer = struct { } pub fn toSlice(self: Buffer) [:0]u8 { - return self.list.toSlice()[0..self.len()]; + return self.list.toSlice()[0..self.len() :0]; } pub fn toSliceConst(self: Buffer) [:0]const u8 { - return self.list.toSliceConst()[0..self.len()]; + return self.list.toSliceConst()[0..self.len() :0]; } pub fn shrink(self: *Buffer, new_len: usize) void { diff --git a/lib/std/cstr.zig b/lib/std/cstr.zig index 6dbae8342ed8..5194dc1a13b4 100644 --- a/lib/std/cstr.zig +++ b/lib/std/cstr.zig @@ -31,13 +31,21 @@ fn testCStrFnsImpl() void { testing.expect(mem.len(u8, "123456789") == 9); } -/// Returns a mutable slice with 1 more byte of length which is a null byte. +/// Returns a mutable, null-terminated slice with the same length as `slice`. /// Caller owns the returned memory. pub fn addNullByte(allocator: *mem.Allocator, slice: []const u8) ![:0]u8 { const result = try allocator.alloc(u8, slice.len + 1); mem.copy(u8, result, slice); result[slice.len] = 0; - return result; + return result[0..slice.len :0]; +} + +test "addNullByte" { + var buf: [30]u8 = undefined; + const allocator = &std.heap.FixedBufferAllocator.init(&buf).allocator; + const slice = try addNullByte(allocator, "hello"[0..4]); + testing.expect(slice.len == 4); + testing.expect(slice[4] == 0); } pub const NullTerminated2DArray = struct { diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 9b24e1acd347..76685666adb9 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -219,7 +219,7 @@ pub fn panic(comptime format: []const u8, args: var) noreturn { } /// TODO multithreaded awareness -var panicking: u8 = 0; // TODO make this a bool +var panicking: u8 = 0; pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, comptime format: []const u8, args: var) noreturn { @setCold(true); @@ -230,21 +230,25 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c resetSegfaultHandler(); } - if (@atomicRmw(u8, &panicking, builtin.AtomicRmwOp.Xchg, 1, builtin.AtomicOrder.SeqCst) == 1) { - // Panicked during a panic. - - // TODO detect if a different thread caused the panic, because in that case - // we would want to return here instead of calling abort, so that the thread - // which first called panic can finish printing a stack trace. - os.abort(); - } - const stderr = getStderrStream(); - stderr.print(format ++ "\n", args) catch os.abort(); - if (trace) |t| { - dumpStackTrace(t.*); + switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) { + 0 => { + const stderr = getStderrStream(); + stderr.print(format ++ "\n", args) catch os.abort(); + if (trace) |t| { + dumpStackTrace(t.*); + } + dumpCurrentStackTrace(first_trace_addr); + }, + 1 => { + // TODO detect if a different thread caused the panic, because in that case + // we would want to return here instead of calling abort, so that the thread + // which first called panic can finish printing a stack trace. + warn("Panicked during a panic. Aborting.\n", .{}); + }, + else => { + // Panicked while printing "Panicked during a panic." + }, } - dumpCurrentStackTrace(first_trace_addr); - os.abort(); } diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 9e7daf0a9a4f..ea62086f590a 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -221,16 +221,16 @@ pub const AtomicFile = struct { } tmp_path_buf[tmp_path_len] = 0; + const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0]; const my_cwd = cwd(); while (true) { try crypto.randomBytes(rand_buf[0..]); - b64_fs_encoder.encode(tmp_path_buf[dirname_component_len..tmp_path_len], &rand_buf); + b64_fs_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf); - // TODO https://github.com/ziglang/zig/issues/3770 to clean up this @ptrCast const file = my_cwd.createFileC( - @ptrCast([*:0]u8, &tmp_path_buf), + tmp_path_slice, .{ .mode = mode, .exclusive = true }, ) catch |err| switch (err) { error.PathAlreadyExists => continue, @@ -1488,8 +1488,7 @@ pub fn openSelfExe() OpenSelfExeError!File { var buf: [MAX_PATH_BYTES]u8 = undefined; const self_exe_path = try selfExePath(&buf); buf[self_exe_path.len] = 0; - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 - return openFileAbsoluteC(@ptrCast([*:0]u8, self_exe_path.ptr), .{}); + return openFileAbsoluteC(self_exe_path[0..self_exe_path.len :0].ptr, .{}); } test "openSelfExe" { diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 8082d310632b..133e3b8a2dca 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -231,9 +231,10 @@ pub const Allocator = struct { pub fn free(self: *Allocator, memory: var) void { const Slice = @typeInfo(@TypeOf(memory)).Pointer; const bytes = @sliceToBytes(memory); - if (bytes.len == 0) return; + const bytes_len = bytes.len + @boolToInt(Slice.sentinel != null); + if (bytes_len == 0) return; const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr)); - const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes.len], Slice.alignment, 0, 1); + const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1); assert(shrink_result.len == 0); } }; @@ -363,11 +364,11 @@ pub fn len(comptime T: type, ptr: [*:0]const T) usize { } pub fn toSliceConst(comptime T: type, ptr: [*:0]const T) [:0]const T { - return ptr[0..len(T, ptr)]; + return ptr[0..len(T, ptr) :0]; } pub fn toSlice(comptime T: type, ptr: [*:0]T) [:0]T { - return ptr[0..len(T, ptr)]; + return ptr[0..len(T, ptr) :0]; } /// Returns true if all elements in a slice are equal to the scalar value provided diff --git a/lib/std/os.zig b/lib/std/os.zig index 6d49bcd38edd..3cc43357bd9c 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -805,9 +805,9 @@ pub fn execvpeC(file: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, e mem.copy(u8, &path_buf, search_path); path_buf[search_path.len] = '/'; mem.copy(u8, path_buf[search_path.len + 1 ..], file_slice); - path_buf[search_path.len + file_slice.len + 1] = 0; - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 - err = execveC(@ptrCast([*:0]u8, &path_buf), child_argv, envp); + const path_len = search_path.len + file_slice.len + 1; + path_buf[path_len] = 0; + err = execveC(path_buf[0..path_len :0].ptr, child_argv, envp); switch (err) { error.AccessDenied => seen_eacces = true, error.FileNotFound, error.NotDir => {}, @@ -841,18 +841,14 @@ pub fn execvpe( const arg_buf = try allocator.alloc(u8, arg.len + 1); @memcpy(arg_buf.ptr, arg.ptr, arg.len); arg_buf[arg.len] = 0; - - // TODO avoid @ptrCast using slice syntax with https://github.com/ziglang/zig/issues/3770 - argv_buf[i] = @ptrCast([*:0]u8, arg_buf.ptr); + argv_buf[i] = arg_buf[0..arg.len :0].ptr; } argv_buf[argv_slice.len] = null; + const argv_ptr = argv_buf[0..argv_slice.len :null].ptr; const envp_buf = try createNullDelimitedEnvMap(allocator, env_map); defer freeNullDelimitedEnvMap(allocator, envp_buf); - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 - const argv_ptr = @ptrCast([*:null]?[*:0]u8, argv_buf.ptr); - return execvpeC(argv_buf.ptr[0].?, argv_ptr, envp_buf.ptr); } @@ -869,16 +865,13 @@ pub fn createNullDelimitedEnvMap(allocator: *mem.Allocator, env_map: *const std. @memcpy(env_buf.ptr, pair.key.ptr, pair.key.len); env_buf[pair.key.len] = '='; @memcpy(env_buf.ptr + pair.key.len + 1, pair.value.ptr, pair.value.len); - env_buf[env_buf.len - 1] = 0; - - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 - envp_buf[i] = @ptrCast([*:0]u8, env_buf.ptr); + const len = env_buf.len - 1; + env_buf[len] = 0; + envp_buf[i] = env_buf[0..len :0].ptr; } assert(i == envp_count); } - // TODO avoid @ptrCast here using slice syntax with https://github.com/ziglang/zig/issues/3770 - assert(envp_buf[envp_count] == null); - return @ptrCast([*:null]?[*:0]u8, envp_buf.ptr)[0..envp_count]; + return envp_buf[0..envp_count :null]; } pub fn freeNullDelimitedEnvMap(allocator: *mem.Allocator, envp_buf: []?[*:0]u8) void { diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 47933917b14d..e27e62c09418 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -1701,6 +1701,7 @@ pub const Node = struct { pub const Slice = struct { start: *Node, end: ?*Node, + sentinel: ?*Node, }; }; @@ -1732,6 +1733,10 @@ pub const Node = struct { if (i < 1) return end; i -= 1; } + if (range.sentinel) |sentinel| { + if (i < 1) return sentinel; + i -= 1; + } }, .ArrayInitializer => |*exprs| { if (i < exprs.len) return exprs.at(i).*; diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index bcf0ea01da5f..5b168be2e122 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -2331,7 +2331,7 @@ fn parsePrefixTypeOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node } /// SuffixOp -/// <- LBRACKET Expr (DOT2 Expr?)? RBRACKET +/// <- LBRACKET Expr (DOT2 (Expr (COLON Expr)?)?)? RBRACKET /// / DOT IDENTIFIER /// / DOTASTERISK /// / DOTQUESTIONMARK @@ -2349,11 +2349,16 @@ fn parseSuffixOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node { if (eatToken(it, .Ellipsis2) != null) { const end_expr = try parseExpr(arena, it, tree); + const sentinel: ?*ast.Node = if (eatToken(it, .Colon) != null) + try parseExpr(arena, it, tree) + else + null; break :blk OpAndToken{ .op = Op{ .Slice = Op.Slice{ .start = index_expr, .end = end_expr, + .sentinel = sentinel, }, }, .token = try expectToken(it, tree, .RBracket), diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index ba022eec9517..f193e1b6ef3d 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -419,10 +419,13 @@ test "zig fmt: pointer of unknown length" { test "zig fmt: spaces around slice operator" { try testCanonical( \\var a = b[c..d]; + \\var a = b[c..d :0]; \\var a = b[c + 1 .. d]; \\var a = b[c + 1 ..]; \\var a = b[c .. d + 1]; + \\var a = b[c .. d + 1 :0]; \\var a = b[c.a..d.e]; + \\var a = b[c.a..d.e :0]; \\ ); } diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 41b8c48b1094..263f45a88be9 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -689,7 +689,13 @@ fn renderExpression( try renderExpression(allocator, stream, tree, indent, start_col, range.start, after_start_space); try renderToken(tree, stream, dotdot, indent, start_col, after_op_space); // .. if (range.end) |end| { - try renderExpression(allocator, stream, tree, indent, start_col, end, Space.None); + const after_end_space = if (range.sentinel != null) Space.Space else Space.None; + try renderExpression(allocator, stream, tree, indent, start_col, end, after_end_space); + } + if (range.sentinel) |sentinel| { + const colon = tree.prevToken(sentinel.firstToken()); + try renderToken(tree, stream, colon, indent, start_col, Space.None); // : + try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); } return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ] }, diff --git a/src/all_types.hpp b/src/all_types.hpp index bcef45f71844..ea46ab81a6dd 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -810,6 +810,7 @@ struct AstNodeSliceExpr { AstNode *array_ref_expr; AstNode *start; AstNode *end; + AstNode *sentinel; // can be null }; struct AstNodeFieldAccessExpr { @@ -1778,6 +1779,7 @@ enum PanicMsgId { PanicMsgIdResumedFnPendingAwait, PanicMsgIdBadNoAsyncCall, PanicMsgIdResumeNotSuspendedFn, + PanicMsgIdBadSentinel, PanicMsgIdCount, }; @@ -3388,6 +3390,7 @@ struct IrInstructionSliceSrc { IrInstruction *ptr; IrInstruction *start; IrInstruction *end; + IrInstruction *sentinel; ResultLoc *result_loc; }; diff --git a/src/codegen.cpp b/src/codegen.cpp index edc2c7f4351f..1455b4b74317 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -941,6 +941,8 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { return buf_create_from_str("async function called with noasync suspended"); case PanicMsgIdResumeNotSuspendedFn: return buf_create_from_str("resumed a non-suspended function"); + case PanicMsgIdBadSentinel: + return buf_create_from_str("sentinel mismatch"); } zig_unreachable(); } @@ -1419,6 +1421,27 @@ static void add_bounds_check(CodeGen *g, LLVMValueRef target_val, LLVMPositionBuilderAtEnd(g->builder, ok_block); } +static void add_sentinel_check(CodeGen *g, LLVMValueRef sentinel_elem_ptr, ZigValue *sentinel) { + LLVMValueRef expected_sentinel = gen_const_val(g, sentinel, ""); + + LLVMValueRef actual_sentinel = gen_load_untyped(g, sentinel_elem_ptr, 0, false, ""); + LLVMValueRef ok_bit; + if (sentinel->type->id == ZigTypeIdFloat) { + ok_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, actual_sentinel, expected_sentinel, ""); + } else { + ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, actual_sentinel, expected_sentinel, ""); + } + + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "SentinelFail"); + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "SentinelOk"); + LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); + + LLVMPositionBuilderAtEnd(g->builder, fail_block); + gen_safety_crash(g, PanicMsgIdBadSentinel); + + LLVMPositionBuilderAtEnd(g->builder, ok_block); +} + static LLVMValueRef gen_assert_zero(CodeGen *g, LLVMValueRef expr_val, ZigType *int_type) { LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, int_type)); LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, expr_val, zero, ""); @@ -5244,6 +5267,9 @@ static LLVMValueRef ir_render_slice(CodeGen *g, IrExecutable *executable, IrInst bool want_runtime_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base); + ZigType *res_slice_ptr_type = instruction->base.value->type->data.structure.fields[slice_ptr_index]->type_entry; + ZigValue *sentinel = res_slice_ptr_type->data.pointer.sentinel; + if (array_type->id == ZigTypeIdArray || (array_type->id == ZigTypeIdPointer && array_type->data.pointer.ptr_len == PtrLenSingle)) { @@ -5265,6 +5291,15 @@ static LLVMValueRef ir_render_slice(CodeGen *g, IrExecutable *executable, IrInst LLVMValueRef array_end = LLVMConstInt(g->builtin_types.entry_usize->llvm_type, array_type->data.array.len, false); add_bounds_check(g, end_val, LLVMIntEQ, nullptr, LLVMIntULE, array_end); + + if (sentinel != nullptr) { + LLVMValueRef indices[] = { + LLVMConstNull(g->builtin_types.entry_usize->llvm_type), + end_val, + }; + LLVMValueRef sentinel_elem_ptr = LLVMBuildInBoundsGEP(g->builder, array_ptr, indices, 2, ""); + add_sentinel_check(g, sentinel_elem_ptr, sentinel); + } } } if (!type_has_bits(array_type)) { @@ -5297,6 +5332,10 @@ static LLVMValueRef ir_render_slice(CodeGen *g, IrExecutable *executable, IrInst if (want_runtime_safety) { add_bounds_check(g, start_val, LLVMIntEQ, nullptr, LLVMIntULE, end_val); + if (sentinel != nullptr) { + LLVMValueRef sentinel_elem_ptr = LLVMBuildInBoundsGEP(g->builder, array_ptr, &end_val, 1, ""); + add_sentinel_check(g, sentinel_elem_ptr, sentinel); + } } if (type_has_bits(array_type)) { @@ -5337,18 +5376,24 @@ static LLVMValueRef ir_render_slice(CodeGen *g, IrExecutable *executable, IrInst end_val = prev_end; } + LLVMValueRef src_ptr_ptr = LLVMBuildStructGEP(g->builder, array_ptr, (unsigned)ptr_index, ""); + LLVMValueRef src_ptr = gen_load_untyped(g, src_ptr_ptr, 0, false, ""); + if (want_runtime_safety) { assert(prev_end); add_bounds_check(g, start_val, LLVMIntEQ, nullptr, LLVMIntULE, end_val); if (instruction->end) { add_bounds_check(g, end_val, LLVMIntEQ, nullptr, LLVMIntULE, prev_end); + + if (sentinel != nullptr) { + LLVMValueRef sentinel_elem_ptr = LLVMBuildInBoundsGEP(g->builder, src_ptr, &end_val, 1, ""); + add_sentinel_check(g, sentinel_elem_ptr, sentinel); + } } } - LLVMValueRef src_ptr_ptr = LLVMBuildStructGEP(g->builder, array_ptr, (unsigned)ptr_index, ""); - LLVMValueRef src_ptr = gen_load_untyped(g, src_ptr_ptr, 0, false, ""); LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, (unsigned)ptr_index, ""); - LLVMValueRef slice_start_ptr = LLVMBuildInBoundsGEP(g->builder, src_ptr, &start_val, (unsigned)len_index, ""); + LLVMValueRef slice_start_ptr = LLVMBuildInBoundsGEP(g->builder, src_ptr, &start_val, 1, ""); gen_store_untyped(g, slice_start_ptr, ptr_field_ptr, 0, false); LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, (unsigned)len_index, ""); diff --git a/src/ir.cpp b/src/ir.cpp index 3000269ad337..d60fe9ea7037 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -2967,18 +2967,21 @@ static IrInstruction *ir_build_memcpy(IrBuilder *irb, Scope *scope, AstNode *sou } static IrInstruction *ir_build_slice_src(IrBuilder *irb, Scope *scope, AstNode *source_node, - IrInstruction *ptr, IrInstruction *start, IrInstruction *end, bool safety_check_on, ResultLoc *result_loc) + IrInstruction *ptr, IrInstruction *start, IrInstruction *end, IrInstruction *sentinel, + bool safety_check_on, ResultLoc *result_loc) { IrInstructionSliceSrc *instruction = ir_build_instruction(irb, scope, source_node); instruction->ptr = ptr; instruction->start = start; instruction->end = end; + instruction->sentinel = sentinel; instruction->safety_check_on = safety_check_on; instruction->result_loc = result_loc; ir_ref_instruction(ptr, irb->current_basic_block); ir_ref_instruction(start, irb->current_basic_block); if (end) ir_ref_instruction(end, irb->current_basic_block); + if (sentinel) ir_ref_instruction(sentinel, irb->current_basic_block); return &instruction->base; } @@ -8483,6 +8486,7 @@ static IrInstruction *ir_gen_slice(IrBuilder *irb, Scope *scope, AstNode *node, AstNode *array_node = slice_expr->array_ref_expr; AstNode *start_node = slice_expr->start; AstNode *end_node = slice_expr->end; + AstNode *sentinel_node = slice_expr->sentinel; IrInstruction *ptr_value = ir_gen_node_extra(irb, array_node, scope, LValPtr, nullptr); if (ptr_value == irb->codegen->invalid_instruction) @@ -8501,7 +8505,17 @@ static IrInstruction *ir_gen_slice(IrBuilder *irb, Scope *scope, AstNode *node, end_value = nullptr; } - IrInstruction *slice = ir_build_slice_src(irb, scope, node, ptr_value, start_value, end_value, true, result_loc); + IrInstruction *sentinel_value; + if (sentinel_node) { + sentinel_value = ir_gen_node(irb, sentinel_node, scope); + if (sentinel_value == irb->codegen->invalid_instruction) + return irb->codegen->invalid_instruction; + } else { + sentinel_value = nullptr; + } + + IrInstruction *slice = ir_build_slice_src(irb, scope, node, ptr_value, start_value, end_value, + sentinel_value, true, result_loc); return ir_lval_wrap(irb, scope, slice, lval, result_loc); } @@ -10533,6 +10547,18 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted result.id = ConstCastResultIdInvalid; return result; } + bool ok_sentinels = + wanted_ptr_type->data.pointer.sentinel == nullptr || + (actual_ptr_type->data.pointer.sentinel != nullptr && + const_values_equal(ira->codegen, wanted_ptr_type->data.pointer.sentinel, + actual_ptr_type->data.pointer.sentinel)); + if (!ok_sentinels) { + result.id = ConstCastResultIdPtrSentinel; + result.data.bad_ptr_sentinel = allocate_nonzero(1); + result.data.bad_ptr_sentinel->wanted_type = wanted_ptr_type; + result.data.bad_ptr_sentinel->actual_type = actual_ptr_type; + return result; + } if ((!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) && actual_ptr_type->data.pointer.bit_offset_in_host == wanted_ptr_type->data.pointer.bit_offset_in_host && @@ -14441,6 +14467,32 @@ static bool optional_value_is_null(ZigValue *val) { } } +static void set_optional_value_to_null(ZigValue *val) { + assert(val->special == ConstValSpecialStatic); + if (val->type->id == ZigTypeIdNull) return; // nothing to do + assert(val->type->id == ZigTypeIdOptional); + if (get_codegen_ptr_type(val->type) != nullptr) { + val->data.x_ptr.special = ConstPtrSpecialNull; + } else if (is_opt_err_set(val->type)) { + val->data.x_err_set = nullptr; + } else { + val->data.x_optional = nullptr; + } +} + +static void set_optional_payload(ZigValue *opt_val, ZigValue *payload) { + assert(opt_val->special == ConstValSpecialStatic); + assert(opt_val->type->id == ZigTypeIdOptional); + if (payload == nullptr) { + set_optional_value_to_null(opt_val); + } else if (is_opt_err_set(opt_val->type)) { + assert(payload->type->id == ZigTypeIdErrorSet); + opt_val->data.x_err_set = payload->data.x_err_set; + } else { + opt_val->data.x_optional = payload; + } +} + static IrInstruction *ir_evaluate_bin_op_cmp(IrAnalyze *ira, ZigType *resolved_type, ZigValue *op1_val, ZigValue *op2_val, IrInstructionBinOp *bin_op_instruction, IrBinOp op_id, bool one_possible_value) { @@ -19313,6 +19365,20 @@ static ZigType *adjust_ptr_align(CodeGen *g, ZigType *ptr_type, uint32_t new_ali ptr_type->data.pointer.sentinel); } +static ZigType *adjust_ptr_sentinel(CodeGen *g, ZigType *ptr_type, ZigValue *new_sentinel) { + assert(ptr_type->id == ZigTypeIdPointer); + return get_pointer_to_type_extra2(g, + ptr_type->data.pointer.child_type, + ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, + ptr_type->data.pointer.ptr_len, + ptr_type->data.pointer.explicit_alignment, + ptr_type->data.pointer.bit_offset_in_host, ptr_type->data.pointer.host_int_bytes, + ptr_type->data.pointer.allow_zero, + ptr_type->data.pointer.vector_index, + ptr_type->data.pointer.inferred_struct_field, + new_sentinel); +} + static ZigType *adjust_slice_align(CodeGen *g, ZigType *slice_type, uint32_t new_align) { assert(is_slice(slice_type)); ZigType *ptr_type = adjust_ptr_align(g, slice_type->data.structure.fields[slice_ptr_index]->type_entry, @@ -22691,7 +22757,7 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_ent fields[6]->special = ConstValSpecialStatic; if (attrs_type->data.pointer.child_type->id != ZigTypeIdOpaque) { fields[6]->type = get_optional_type(ira->codegen, attrs_type->data.pointer.child_type); - fields[6]->data.x_optional = attrs_type->data.pointer.sentinel; + set_optional_payload(fields[6], attrs_type->data.pointer.sentinel); } else { fields[6]->type = ira->codegen->builtin_types.entry_null; } @@ -25051,50 +25117,72 @@ static IrInstruction *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstruction end = nullptr; } - ZigType *return_type; + ZigType *non_sentinel_slice_ptr_type; + ZigType *elem_type; if (array_type->id == ZigTypeIdArray) { + elem_type = array_type->data.array.child_type; bool is_comptime_const = ptr_ptr->value->special == ConstValSpecialStatic && ptr_ptr->value->data.x_ptr.mut == ConstPtrMutComptimeConst; - ZigType *slice_ptr_type = get_pointer_to_type_extra(ira->codegen, array_type->data.array.child_type, + non_sentinel_slice_ptr_type = get_pointer_to_type_extra(ira->codegen, elem_type, ptr_ptr_type->data.pointer.is_const || is_comptime_const, ptr_ptr_type->data.pointer.is_volatile, PtrLenUnknown, ptr_ptr_type->data.pointer.explicit_alignment, 0, 0, false); - return_type = get_slice_type(ira->codegen, slice_ptr_type); } else if (array_type->id == ZigTypeIdPointer) { if (array_type->data.pointer.ptr_len == PtrLenSingle) { ZigType *main_type = array_type->data.pointer.child_type; if (main_type->id == ZigTypeIdArray) { - ZigType *slice_ptr_type = get_pointer_to_type_extra(ira->codegen, - main_type->data.pointer.child_type, + elem_type = main_type->data.pointer.child_type; + non_sentinel_slice_ptr_type = get_pointer_to_type_extra(ira->codegen, + elem_type, array_type->data.pointer.is_const, array_type->data.pointer.is_volatile, PtrLenUnknown, array_type->data.pointer.explicit_alignment, 0, 0, false); - return_type = get_slice_type(ira->codegen, slice_ptr_type); } else { ir_add_error(ira, &instruction->base, buf_sprintf("slice of single-item pointer")); return ira->codegen->invalid_instruction; } } else { + elem_type = array_type->data.pointer.child_type; if (array_type->data.pointer.ptr_len == PtrLenC) { array_type = adjust_ptr_len(ira->codegen, array_type, PtrLenUnknown); } - return_type = get_slice_type(ira->codegen, array_type); + ZigType *maybe_sentineled_slice_ptr_type = array_type; + non_sentinel_slice_ptr_type = adjust_ptr_sentinel(ira->codegen, maybe_sentineled_slice_ptr_type, nullptr); if (!end) { ir_add_error(ira, &instruction->base, buf_sprintf("slice of pointer must include end value")); return ira->codegen->invalid_instruction; } } } else if (is_slice(array_type)) { - ZigType *ptr_type = array_type->data.structure.fields[slice_ptr_index]->type_entry; - return_type = get_slice_type(ira->codegen, ptr_type); + ZigType *maybe_sentineled_slice_ptr_type = array_type->data.structure.fields[slice_ptr_index]->type_entry; + non_sentinel_slice_ptr_type = adjust_ptr_sentinel(ira->codegen, maybe_sentineled_slice_ptr_type, nullptr); + elem_type = non_sentinel_slice_ptr_type->data.pointer.child_type; } else { ir_add_error(ira, &instruction->base, buf_sprintf("slice of non-array type '%s'", buf_ptr(&array_type->name))); return ira->codegen->invalid_instruction; } + ZigType *return_type; + ZigValue *sentinel_val = nullptr; + if (instruction->sentinel) { + IrInstruction *uncasted_sentinel = instruction->sentinel->child; + if (type_is_invalid(uncasted_sentinel->value->type)) + return ira->codegen->invalid_instruction; + IrInstruction *sentinel = ir_implicit_cast(ira, uncasted_sentinel, elem_type); + if (type_is_invalid(sentinel->value->type)) + return ira->codegen->invalid_instruction; + sentinel_val = ir_resolve_const(ira, sentinel, UndefBad); + if (sentinel_val == nullptr) + return ira->codegen->invalid_instruction; + ZigType *slice_ptr_type = adjust_ptr_sentinel(ira->codegen, non_sentinel_slice_ptr_type, sentinel_val); + return_type = get_slice_type(ira->codegen, slice_ptr_type); + } else { + return_type = get_slice_type(ira->codegen, non_sentinel_slice_ptr_type); + } + if (instr_is_comptime(ptr_ptr) && value_is_comptime(casted_start->value) && (!end || value_is_comptime(end->value))) diff --git a/src/parser.cpp b/src/parser.cpp index 4b5e0e3ebb28..a0c5cf794bac 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2723,7 +2723,7 @@ static AstNode *ast_parse_prefix_type_op(ParseContext *pc) { } // SuffixOp -// <- LBRACKET Expr (DOT2 Expr?)? RBRACKET +// <- LBRACKET Expr (DOT2 (Expr (COLON Expr)?)?)? RBRACKET // / DOT IDENTIFIER // / DOTASTERISK // / DOTQUESTIONMARK @@ -2733,12 +2733,17 @@ static AstNode *ast_parse_suffix_op(ParseContext *pc) { AstNode *start = ast_expect(pc, ast_parse_expr); AstNode *end = nullptr; if (eat_token_if(pc, TokenIdEllipsis2) != nullptr) { + AstNode *sentinel = nullptr; end = ast_parse_expr(pc); + if (eat_token_if(pc, TokenIdColon) != nullptr) { + sentinel = ast_parse_expr(pc); + } expect_token(pc, TokenIdRBracket); AstNode *res = ast_create_node(pc, NodeTypeSliceExpr, lbracket); res->data.slice_expr.start = start; res->data.slice_expr.end = end; + res->data.slice_expr.sentinel = sentinel; return res; } @@ -3041,6 +3046,7 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont visit_field(&node->data.slice_expr.array_ref_expr, visit, context); visit_field(&node->data.slice_expr.start, visit, context); visit_field(&node->data.slice_expr.end, visit, context); + visit_field(&node->data.slice_expr.sentinel, visit, context); break; case NodeTypeFieldAccessExpr: visit_field(&node->data.field_access_expr.struct_expr, visit, context); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index c826d45ceaf9..450f91b9be5d 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,17 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add("slice sentinel mismatch", + \\fn foo() [:0]u8 { + \\ var x: []u8 = undefined; + \\ return x; + \\} + \\comptime { _ = foo; } + , &[_][]const u8{ + "tmp.zig:3:12: error: expected type '[:0]u8', found '[]u8'", + "tmp.zig:3:12: note: destination pointer requires a terminating '0' sentinel", + }); + cases.add("intToPtr with misaligned address", \\pub fn main() void { \\ var y = @intToPtr([*]align(4) u8, 5); diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index eec5f7a86b60..2217a7f2dfd0 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -1,12 +1,85 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompareOutputContext) void { + cases.addRuntimeSafety("slice sentinel mismatch - optional pointers", + \\const std = @import("std"); + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ if (std.mem.eql(u8, message, "sentinel mismatch")) { + \\ std.process.exit(126); // good + \\ } + \\ std.process.exit(0); // test failed + \\} + \\pub fn main() void { + \\ var buf: [4]?*i32 = undefined; + \\ const slice = buf[0..3 :null]; + \\} + ); + + cases.addRuntimeSafety("slice sentinel mismatch - floats", + \\const std = @import("std"); + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ if (std.mem.eql(u8, message, "sentinel mismatch")) { + \\ std.process.exit(126); // good + \\ } + \\ std.process.exit(0); // test failed + \\} + \\pub fn main() void { + \\ var buf: [4]f32 = undefined; + \\ const slice = buf[0..3 :1.2]; + \\} + ); + + cases.addRuntimeSafety("pointer slice sentinel mismatch", + \\const std = @import("std"); + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ if (std.mem.eql(u8, message, "sentinel mismatch")) { + \\ std.process.exit(126); // good + \\ } + \\ std.process.exit(0); // test failed + \\} + \\pub fn main() void { + \\ var buf: [4]u8 = undefined; + \\ const ptr = buf[0..].ptr; + \\ const slice = ptr[0..3 :0]; + \\} + ); + + cases.addRuntimeSafety("slice slice sentinel mismatch", + \\const std = @import("std"); + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ if (std.mem.eql(u8, message, "sentinel mismatch")) { + \\ std.process.exit(126); // good + \\ } + \\ std.process.exit(0); // test failed + \\} + \\pub fn main() void { + \\ var buf: [4]u8 = undefined; + \\ const slice = buf[0..]; + \\ const slice2 = slice[0..3 :0]; + \\} + ); + + cases.addRuntimeSafety("array slice sentinel mismatch", + \\const std = @import("std"); + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ if (std.mem.eql(u8, message, "sentinel mismatch")) { + \\ std.process.exit(126); // good + \\ } + \\ std.process.exit(0); // test failed + \\} + \\pub fn main() void { + \\ var buf: [4]u8 = undefined; + \\ const slice = buf[0..3 :0]; + \\} + ); + cases.addRuntimeSafety("intToPtr with misaligned address", + \\const std = @import("std"); \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { - \\ if (@import("std").mem.eql(u8, message, "incorrect alignment")) { - \\ @import("std").os.exit(126); // good + \\ if (std.mem.eql(u8, message, "incorrect alignment")) { + \\ std.os.exit(126); // good \\ } - \\ @import("std").os.exit(0); // test failed + \\ std.os.exit(0); // test failed \\} \\pub fn main() void { \\ var x: usize = 5; diff --git a/test/stage1/behavior/slice.zig b/test/stage1/behavior/slice.zig index e325c6c8c850..b985ec1f8e82 100644 --- a/test/stage1/behavior/slice.zig +++ b/test/stage1/behavior/slice.zig @@ -78,3 +78,22 @@ test "access len index of sentinel-terminated slice" { S.doTheTest(); comptime S.doTheTest(); } + +test "obtaining a null terminated slice" { + // here we have a normal array + var buf: [50]u8 = undefined; + + buf[0] = 'a'; + buf[1] = 'b'; + buf[2] = 'c'; + buf[3] = 0; + + // now we obtain a null terminated slice: + const ptr = buf[0..3 :0]; + + var runtime_len: usize = 3; + const ptr2 = buf[0..runtime_len :0]; + // ptr2 is a null-terminated slice + comptime expect(@TypeOf(ptr2) == [:0]u8); + comptime expect(@TypeOf(ptr2[0..2]) == []u8); +}