Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
7 changes: 5 additions & 2 deletions src/codegen/aarch64/Assemble.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Loading