zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 36069a2a7dbd1a3b38ecfced917733127d7dbb38 (tree)
parent 3a48f4bb40d219efc9b96df44850bcedf83f6285
Author: Justus Klausecker <justus@klausecker.de>
Date:   Fri, 19 Jun 2026 16:16:17 +0200

Sema: make dereferences of comptime-known null C pointers runtime-known

This allows `@TypeOf(@as([*c]T, null).*.x)` to (continue to) work.
In the long term these are probably not the semantics we want, unwrapping
a comptime-known `null` pointer should always result in `unreachable`.

Also adds missing runtime safety checks for loading null C pointers.

Diffstat:
Msrc/Sema.zig | 33++++++++++++++++++++++++---------
Mtest/behavior/sizeof_and_typeof.zig | 5+++++
Dtest/cases/compile_errors/ref_deref_of_null_c_ptr.zig | 8--------
Atest/cases/safety/deref_null_c_pointer.zig | 17+++++++++++++++++
Atest/cases/safety/ref_deref_null_c_pointer.zig | 17+++++++++++++++++
5 files changed, 63 insertions(+), 17 deletions(-)

diff --git a/src/Sema.zig b/src/Sema.zig @@ -3095,7 +3095,6 @@ fn zirRefDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const zcu = pt.zcu; const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node; const src = block.nodeOffset(inst_data.src_node); - const ptr_src = block.src(.{ .node_offset_deref_ptr = inst_data.src_node }); const operand = sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); @@ -3104,15 +3103,26 @@ fn zirRefDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const ptr_info = operand_ty.ptrInfo(zcu); return switch (ptr_info.flags.size) { .many, .slice => unreachable, // cannot be dereferenced - .c => ptr: { - var single_ptr_flags = ptr_info.flags; - single_ptr_flags.size = .one; - single_ptr_flags.is_allowzero = false; - const single_ptr_ty = try pt.ptrType(.{ - .child = ptr_info.child, - .flags = single_ptr_flags, + .c => single_ptr: { + const single_ptr_ty = try pt.ptrType(p: { + var p = ptr_info; + p.flags.size = .one; + p.flags.is_allowzero = false; + break :p p; }); - break :ptr try sema.coerceCompatiblePtrs(block, single_ptr_ty, operand, ptr_src); + // https://github.com/ziglang/zig/issues/6597 + if (sema.resolveValue(operand)) |operand_val| { + if (!operand_val.isNull(zcu)) { + break :single_ptr try sema.coerceInMemory(operand_val, single_ptr_ty); + } + } + if (block.wantSafety()) { + const is_non_null = try block.addUnOp(.is_non_null, operand); + try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null); + } + const single_ptr = try block.addBitCast(single_ptr_ty, operand); + try sema.checkKnownAllocPtr(block, operand, single_ptr); + break :single_ptr single_ptr; }, .one => operand, }; @@ -30615,6 +30625,11 @@ fn analyzeLoad( break :msg msg; }); + // https://github.com/ziglang/zig/issues/6597 + if (block.wantSafety() and ptr_ty.isCPtr(zcu)) { + const is_non_null = try block.addUnOp(.is_non_null, ptr); + try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null); + } return block.addTyOp(.load, elem_ty, ptr); } diff --git a/test/behavior/sizeof_and_typeof.zig b/test/behavior/sizeof_and_typeof.zig @@ -423,3 +423,8 @@ test "@sizeOf struct is resolved when used as operand of slicing" { S.buf[@sizeOf(dummy)..][0] = 0; try expect(S.buf[0] == 0); } + +test "@TypeOf null C pointer dereference" { + comptime assert(@TypeOf(@as([*c]u8, null).*) == u8); + comptime assert(@TypeOf(&@as([*c]u8, null).*) == *u8); +} diff --git a/test/cases/compile_errors/ref_deref_of_null_c_ptr.zig b/test/cases/compile_errors/ref_deref_of_null_c_ptr.zig @@ -1,8 +0,0 @@ -export fn entry() void { - const ptr: [*c]u8 = null; - _ = &ptr.*; -} - -// error -// -// :3:10: error: null pointer casted to type '*u8' diff --git a/test/cases/safety/deref_null_c_pointer.zig b/test/cases/safety/deref_null_c_pointer.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "attempt to use null value")) { + std.process.exit(0); + } + std.process.exit(1); +} +pub fn main() !void { + const p: [*c]u8 = null; + _ = p.*; +} + +// run +// backend=selfhosted,llvm +// target=x86_64-linux,aarch64-linux diff --git a/test/cases/safety/ref_deref_null_c_pointer.zig b/test/cases/safety/ref_deref_null_c_pointer.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "attempt to use null value")) { + std.process.exit(0); + } + std.process.exit(1); +} +pub fn main() !void { + const p: [*c]u8 = null; + _ = &p.*; +} + +// run +// backend=selfhosted,llvm +// target=x86_64-linux,aarch64-linux