authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-23 16:37:53-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-09-23 23:37:53+00:00
logba137783ed4152ebe8d7b98dca855c551fc924a7
tree2657d21ad497ec9d4e1050803e4f1b1e28063457
parent7b92d5f4052be651e9bc5cd4ad78a69ccbee865d
signaturebadge-check Signed by PGP key B5690EEEBB952194

forbid trivial local address returned from functions (#25333)

progress towards #25312

3 files changed, 34 insertions(+), 9 deletions(-)

lib/std/zig/AstGen.zig+23-8
......@@ -383,6 +383,8 @@ const ResultInfo = struct {
383383 assignment,
384384 /// No specific operator in particular.
385385 none,
386 /// The expression is operand to address-of which is the operand to a return expression.
387 return_addrof,
386388 };
387389};
388390
......@@ -955,7 +957,14 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
955957 _ = try gz.addUnTok(.validate_ref_ty, res_ty_inst, tree.firstToken(node));
956958 break :rl .{ .ref_coerced_ty = res_ty_inst };
957959 } else .ref;
958 const result = try expr(gz, scope, .{ .rl = operand_rl }, tree.nodeData(node).node);
960 const operand_node = tree.nodeData(node).node;
961 const result = try expr(gz, scope, .{
962 .rl = operand_rl,
963 .ctx = switch (ri.ctx) {
964 .@"return" => .return_addrof,
965 else => .none,
966 },
967 }, operand_node);
959968 return rvalue(gz, ri, result, node);
960969 },
961970 .optional_type => {
......@@ -8420,13 +8429,19 @@ fn localVarRef(
84208429 local_ptr.used = .fromToken(ident_token);
84218430 }
84228431
8423 // Can't close over a runtime variable
8424 if (num_namespaces_out != 0 and !local_ptr.maybe_comptime and !gz.is_typeof) {
8425 const ident_name = try astgen.identifierTokenString(ident_token);
8426 return astgen.failNodeNotes(ident, "mutable '{s}' not accessible from here", .{ident_name}, &.{
8427 try astgen.errNoteTok(local_ptr.token_src, "declared mutable here", .{}),
8428 try astgen.errNoteNode(capturing_namespace.node, "crosses namespace boundary here", .{}),
8429 });
8432 if (!local_ptr.maybe_comptime and !gz.is_typeof) {
8433 if (num_namespaces_out != 0) {
8434 const ident_name = try astgen.identifierTokenString(ident_token);
8435 return astgen.failNodeNotes(ident, "mutable '{s}' not accessible from here", .{ident_name}, &.{
8436 try astgen.errNoteTok(local_ptr.token_src, "declared mutable here", .{}),
8437 try astgen.errNoteNode(capturing_namespace.node, "crosses namespace boundary here", .{}),
8438 });
8439 } else if (ri.ctx == .return_addrof) {
8440 const ident_name = try astgen.identifierTokenString(ident_token);
8441 return astgen.failNodeNotes(ident, "returning address of expired local variable '{s}'", .{ident_name}, &.{
8442 try astgen.errNoteTok(local_ptr.token_src, "declared runtime-known here", .{}),
8443 });
8444 }
84308445 }
84318446
84328447 switch (ri.rl) {
test/behavior/fn.zig+2-1
......@@ -758,7 +758,8 @@ test "return undefined pointer from function, directly and by expired local" {
758758 /// Semantically equivalent to `returnUndefPointer`.
759759 fn returnStackPointer() *i32 {
760760 var stack_allocation: i32 = 1234;
761 return &stack_allocation;
761 const ptr = &stack_allocation; // defeats ast-check detection
762 return ptr;
762763 }
763764 };
764765
test/cases/compile_errors/return_addr_of_local.zig created+9
......@@ -0,0 +1,9 @@
1export fn b() void {
2 var x: i32 = 1234;
3 return &x;
4}
5
6// error
7//
8// :3:13: error: returning address of expired local variable 'x'
9// :2:9: note: declared runtime-known here