diff --git a/src/Sema.zig b/src/Sema.zig index 385e1ae74a84..3595ec60ed9d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -18933,6 +18933,73 @@ fn addToInferredErrorSetPtr(sema: *Sema, ies: *InferredErrorSet, op_ty: Type) !v } } +fn isPointerToStackAllocation(sema: *Sema, ptr: Air.Inst.Ref) CompileError!bool { + const inst = ptr.toIndex() orelse return false; + const tag = sema.air_instructions.items(.tag)[@intFromEnum(inst)]; + const data = sema.air_instructions.items(.data)[@intFromEnum(inst)]; + + return switch (tag) { + .alloc => true, + .ret_ptr, .arg => false, + + // Simple ty_op instructions - check the operand + .bitcast, + .optional_payload_ptr, + .unwrap_errunion_payload_ptr, + .array_to_slice, + .struct_field_ptr_index_0, + .struct_field_ptr_index_1, + .struct_field_ptr_index_2, + .struct_field_ptr_index_3, + => try sema.isPointerToStackAllocation(data.ty_op.operand), + + // Instructions with binary operands in extra data + .ptr_elem_ptr, .ptr_add, .ptr_sub => blk: { + const tmp_air = sema.getTmpAir(); + const bin = tmp_air.extraData(Air.Bin, data.ty_pl.payload).data; + break :blk try sema.isPointerToStackAllocation(bin.lhs); + }, + + // Struct field pointer + .struct_field_ptr => blk: { + const tmp_air = sema.getTmpAir(); + const extra = tmp_air.extraData(Air.StructField, data.ty_pl.payload).data; + break :blk try sema.isPointerToStackAllocation(extra.struct_operand); + }, + + else => false, + }; +} + +fn isSliceToStackAllocation(sema: *Sema, slice_ref: Air.Inst.Ref) CompileError!bool { + const inst = slice_ref.toIndex() orelse return false; + const tag = sema.air_instructions.items(.tag)[@intFromEnum(inst)]; + const data = sema.air_instructions.items(.data)[@intFromEnum(inst)]; + + return switch (tag) { + .arg => false, + + // Slice creation from pointer + length + .slice => blk: { + const tmp_air = sema.getTmpAir(); + const bin = tmp_air.extraData(Air.Bin, data.ty_pl.payload).data; + break :blk try sema.isPointerToStackAllocation(bin.lhs); + }, + + // Array to slice conversion + .array_to_slice => try sema.isPointerToStackAllocation(data.ty_op.operand), + + // Recursive slice operations + .slice_ptr => try sema.isSliceToStackAllocation(data.ty_op.operand), + .slice_elem_ptr, .slice_elem_val => try sema.isSliceToStackAllocation(data.bin_op.lhs), + + // Internal slice field pointers + .ptr_slice_ptr_ptr, .ptr_slice_len_ptr => try sema.isPointerToStackAllocation(data.ty_op.operand), + + else => false, + }; +} + fn analyzeRet( sema: *Sema, block: *Block, @@ -18948,6 +19015,20 @@ fn analyzeRet( if (sema.fn_ret_ty_ies != null and sema.fn_ret_ty.zigTypeTag(zcu) == .error_union) { try sema.addToInferredErrorSet(uncasted_operand); } + + // Check for returning pointers or slices to stack-allocated memory + const operand_ty = sema.typeOf(uncasted_operand); + if (operand_ty.isSlice(zcu)) { + // Check if this is a slice of stack-allocated memory + if (try sema.isSliceToStackAllocation(uncasted_operand)) { + return sema.fail(block, operand_src, "cannot return slice of stack-allocated memory", .{}); + } + } else if (operand_ty.isPtrAtRuntime(zcu)) { + if (try sema.isPointerToStackAllocation(uncasted_operand)) { + return sema.fail(block, operand_src, "cannot return pointer to stack-allocated memory", .{}); + } + } + const operand = sema.coerceExtra(block, sema.fn_ret_ty, uncasted_operand, operand_src, .{ .is_ret = true }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, diff --git a/src/codegen/aarch64/Assemble.zig b/src/codegen/aarch64/Assemble.zig index 4b5aa8e04feb..5cf9c2da3c69 100644 --- a/src/codegen/aarch64/Assemble.zig +++ b/src/codegen/aarch64/Assemble.zig @@ -42,8 +42,11 @@ fn zonCast(comptime Result: type, zon_value: anytype, symbols: anytype) Result { .@"struct" => |zon_struct| switch (@typeInfo(Result)) { .pointer => |result_pointer| { comptime assert(result_pointer.size == .slice and result_pointer.is_const); - var elems: [zon_value.len]result_pointer.child = undefined; - inline for (&elems, zon_value) |*elem, zon_elem| elem.* = zonCast(result_pointer.child, zon_elem, symbols); + const elems = comptime blk: { + var temp_elems: [zon_value.len]result_pointer.child = undefined; + for (&temp_elems, zon_value) |*elem, zon_elem| elem.* = zonCast(result_pointer.child, zon_elem, symbols); + break :blk temp_elems; + }; return &elems; }, .@"struct" => |result_struct| { diff --git a/test/cases/compile_errors/issue_4207_coerce_from_non-terminated-slice_to_terminated-pointer.zig b/test/cases/compile_errors/issue_4207_coerce_from_non-terminated-slice_to_terminated-pointer.zig index 8cb3a37e29d0..357080aa3049 100644 --- a/test/cases/compile_errors/issue_4207_coerce_from_non-terminated-slice_to_terminated-pointer.zig +++ b/test/cases/compile_errors/issue_4207_coerce_from_non-terminated-slice_to_terminated-pointer.zig @@ -1,10 +1,11 @@ +var global_buffer: [64]u8 = undefined; + export fn foo() [*:0]const u8 { - var buffer: [64]u8 = undefined; - return buffer[0..]; + return global_buffer[0..]; } // error // -// :3:18: error: expected type '[*:0]const u8', found '*[64]u8' -// :3:18: note: destination pointer requires '0' sentinel -// :1:17: note: function return type declared here +// :4:25: error: expected type '[*:0]const u8', found '*[64]u8' +// :4:25: note: destination pointer requires '0' sentinel +// :3:17: note: function return type declared here diff --git a/test/cases/compile_errors/return_pointer_to_stack_allocated_memory_1.zig b/test/cases/compile_errors/return_pointer_to_stack_allocated_memory_1.zig new file mode 100644 index 000000000000..c870f0fa78b0 --- /dev/null +++ b/test/cases/compile_errors/return_pointer_to_stack_allocated_memory_1.zig @@ -0,0 +1,50 @@ +// Test: Stack escape detection - Part 1: Basic pointer escapes +// This file tests basic cases of returning pointers to stack-allocated memory + +// Force runtime evaluation with this global +var runtime_value: i32 = 42; + +fn returnLocalPtr() *i32 { + var x: i32 = runtime_value; + return &x; +} + +fn returnLocalPtrConst() *const i32 { + var x: i32 = runtime_value; + return &x; +} + +fn returnPtrFromBlock() *i32 { + var x: i32 = runtime_value; + { + return &x; + } +} + +fn returnPtrFromOptional() ?*i32 { + var x: i32 = runtime_value; + return &x; +} + +fn returnPtrFromErrorUnion() !*i32 { + var x: i32 = runtime_value; + return &x; +} + +pub fn main() void { + _ = returnLocalPtr(); + _ = returnLocalPtrConst(); + _ = returnPtrFromBlock(); + _ = returnPtrFromOptional(); + _ = returnPtrFromErrorUnion() catch {}; +} + +// error +// backend=auto +// target=native +// +// :9:12: error: cannot return pointer to stack-allocated memory +// :14:12: error: cannot return pointer to stack-allocated memory +// :20:16: error: cannot return pointer to stack-allocated memory +// :26:12: error: cannot return pointer to stack-allocated memory +// :31:12: error: cannot return pointer to stack-allocated memory diff --git a/test/cases/compile_errors/return_pointer_to_stack_allocated_memory_2.zig b/test/cases/compile_errors/return_pointer_to_stack_allocated_memory_2.zig new file mode 100644 index 000000000000..5d9ca2be063f --- /dev/null +++ b/test/cases/compile_errors/return_pointer_to_stack_allocated_memory_2.zig @@ -0,0 +1,59 @@ +// Test: Stack escape detection - Part 2: Struct field pointer escapes +// This file tests returning pointers to fields of stack-allocated structs + +// Force runtime evaluation with this global +var runtime_value: i32 = 42; + +fn returnStructFieldPtr() *i32 { + const S = struct { x: i32, y: i32 }; + var s = S{ .x = runtime_value, .y = 2 }; + return &s.x; +} + +fn returnStructFieldPtr2() *i32 { + const S = struct { x: i32, y: i32 }; + var s = S{ .x = 1, .y = runtime_value }; + return &s.y; +} + +fn returnAnonymousStructField() *u32 { + var s = struct { + a: i32, + b: u32, + c: u8, + }{ + .a = runtime_value, + .b = 100, + .c = 0, + }; + return &s.b; +} + +// Struct field by index (these use struct_field_ptr_index_0, etc. in AIR) +fn returnStructFieldIndex0() *i32 { + var s = struct { a: i32, b: i32 }{ .a = runtime_value, .b = 0 }; + return &s.a; +} + +fn returnStructFieldIndex1() *i32 { + var s = struct { a: i32, b: i32 }{ .a = 0, .b = runtime_value }; + return &s.b; +} + +pub fn main() void { + _ = returnStructFieldPtr(); + _ = returnStructFieldPtr2(); + _ = returnAnonymousStructField(); + _ = returnStructFieldIndex0(); + _ = returnStructFieldIndex1(); +} + +// error +// backend=auto +// target=native +// +// :10:12: error: cannot return pointer to stack-allocated memory +// :16:12: error: cannot return pointer to stack-allocated memory +// :29:12: error: cannot return pointer to stack-allocated memory +// :35:12: error: cannot return pointer to stack-allocated memory +// :40:12: error: cannot return pointer to stack-allocated memory diff --git a/test/cases/compile_errors/return_pointer_to_stack_allocated_memory_3.zig b/test/cases/compile_errors/return_pointer_to_stack_allocated_memory_3.zig new file mode 100644 index 000000000000..dc7cbbf0929a --- /dev/null +++ b/test/cases/compile_errors/return_pointer_to_stack_allocated_memory_3.zig @@ -0,0 +1,41 @@ +// Test: Stack escape detection - Part 3: Slice escapes +// This file tests returning slices to stack-allocated memory + +// Force runtime evaluation with this global +var runtime_value: i32 = 42; + +// Direct slice return from stack array +fn returnDirectSlice() []const u8 { + var buffer: [10]u8 = undefined; + buffer[0] = @intCast(runtime_value); + return buffer[0..5]; +} + +// Array to slice conversion +fn returnArrayToSlice() []const u8 { + var arr = [_]u8{ 1, 2, 3, 4, @intCast(runtime_value) }; + const slice: []const u8 = &arr; + return slice; +} + +// Slice pointer extraction +fn returnSlicePtr() [*]const u8 { + var buffer: [10]u8 = undefined; + buffer[0] = @intCast(runtime_value); + const slice = buffer[0..]; + return slice.ptr; +} + +pub fn main() void { + _ = returnDirectSlice(); + _ = returnArrayToSlice(); + _ = returnSlicePtr(); +} + +// error +// backend=auto +// target=native +// +// :11:18: error: cannot return pointer to stack-allocated memory +// :18:12: error: cannot return slice of stack-allocated memory +// :26:17: error: cannot return pointer to stack-allocated memory