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:
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