# What makes "ban returning pointer to stack memory" difficult?

**URL:** <https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380>\
**Category:** Explain\
**Created:** [April 1, 2025, 4:27am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380 "2025-04-01T04:27:34Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![jeffective](https://ziggit.dev/user_avatar/ziggit.dev/jeffective/32/3116_2.png) [@jeffective](https://ziggit.dev/u/jeffective)\
**Post date:** [April 1, 2025, 4:27am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/1 "2025-04-01T04:27:34Z")

</div>

I recall reading somewhere from the zig core team that making returning pointers to stack memory (“dangling pointers”) a compile error was “difficult from a language specification perspective.”

Could someone elaborate more?  
Are there legitimate use-cases for returning pointers to stack memory that would be difficult to carve-out space for in the language specification? Could this not be solved by requiring `@intFromPtr` to return them?

---

<div class="post-metadata">

**Author:** ![andrewrk](https://ziggit.dev/user_avatar/ziggit.dev/andrewrk/32/7011_2.png) [@andrewrk](https://ziggit.dev/u/andrewrk)\
**Post date:** [April 1, 2025, 5:46am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/2 "2025-04-01T05:46:22Z")

</div>

```zig
fn get() *i32 {
    var x: i32 = 0;
    return &x;
}
fn illegal() void {
    get().* = 42;
}

```

Here, calling `illegal` is always a bug. In fact we can reduce it to:

```zig
fn illegal() void {
    unreachable;
}

```

At that point, why even have a function? It’s just the construct `unreachable`.

It’s not a compile error to have `unreachable` in your code. It’s a runtime bug to reach unreachable code.

---

<div class="post-metadata">

**Author:** ![tensorush](https://ziggit.dev/letter_avatar_proxy/v4/letter/t/6a8cbe/32.png) [@tensorush](https://ziggit.dev/u/tensorush)\
**Post date:** [April 1, 2025, 6:40am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/3 "2025-04-01T06:40:11Z")

</div>

I think @jeffective is asking about how difficult would it be to additionally analyze bodies of functions that return pointers, like `fn get() *i32`, such that analyzing functions that return pointers to stack memory would through a compile error.

For instance, analyzing either of these functions would be a compile error:

```zig
fn get() *i32 {
    var x: i32 = 0;
    return &x;
}

fn get2() *i32 {
    var x: i32 = 0;
    const ptr = &x;
    return ptr;
}

```

While analyzing any of these would still be fine:

```zig
const std = @import("std");

const X: i32 = 0;

fn getStatic() *const i32 {
    return &X;
}

fn getAlloc() !*i32 {
    const x_ptr = try std.heap.page_allocator.create(i32);
    return x_ptr;
}

fn getAlloc2() !**i32 {
    var x_ptr = try std.heap.page_allocator.create(i32);
    const ptr = &x_ptr;
    return ptr;
}

```

UPD: My bad, `getAlloc2` is invalid! This only makes it easier to detect stack pointers, though.

---

<div class="post-metadata">

**Author:** ![yataro](https://ziggit.dev/letter_avatar_proxy/v4/letter/y/6de8d8/32.png) [@yataro](https://ziggit.dev/u/yataro)\
**Post date:** [April 1, 2025, 7:31am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/4 "2025-04-01T07:31:03Z")

</div>

> ```zig
> fn getAlloc2() !**i32 {
> var x_ptr = try std.heap.page_allocator.create(i32);
> const ptr = &x_ptr;
> return ptr;
> }
> 
> ```

I don’t think that `getAlloc2` is fine, it returns pointer to stack-allocated pointer `x_ptr`, isn’t it?

---

<div class="post-metadata">

**Author:** ![tensorush](https://ziggit.dev/letter_avatar_proxy/v4/letter/t/6a8cbe/32.png) [@tensorush](https://ziggit.dev/u/tensorush)\
**Post date:** [April 1, 2025, 7:41am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/5 "2025-04-01T07:41:30Z")

</div>

Yeah, I would ban that case as well. But the Sema’s logic might be that since we’re returning a pointer to a pointer to `i32`, and the returned `i32` value isn’t on stack, then we can make it a valid case, as it is now:

```zig
const std = @import("std");

fn getAlloc2() !**i32 {
    var x_ptr = try std.heap.page_allocator.create(i32);
    x_ptr.* = 42;
    const ptr = &x_ptr;
    return ptr;
}

pub fn main() !void {
    std.debug.print("{d}\n", .{(try getAlloc2()).*.*});
}

```

---

<div class="post-metadata">

**Author:** ![tensorush](https://ziggit.dev/letter_avatar_proxy/v4/letter/t/6a8cbe/32.png) [@tensorush](https://ziggit.dev/u/tensorush)\
**Post date:** [April 1, 2025, 7:50am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/6 "2025-04-01T07:50:51Z")

</div>

Actually, not sure. It may be wrong even now 😅

---

<div class="post-metadata">

**Author:** ![Zen](https://ziggit.dev/letter_avatar_proxy/v4/letter/z/7c8e57/32.png) [@Zen](https://ziggit.dev/u/Zen)\
**Post date:** [April 1, 2025, 8:01am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/7 "2025-04-01T08:01:09Z")

</div>

```zig
const ptr = &x_ptr;
return ptr;

```

Wrong ! It will be invalid after return(it is in stack). How about returning \*x\_ptr(which should be in heap).

Regards!

---

<div class="post-metadata">

**Author:** ![yataro](https://ziggit.dev/letter_avatar_proxy/v4/letter/y/6de8d8/32.png) [@yataro](https://ziggit.dev/u/yataro)\
**Post date:** [April 1, 2025, 8:14am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/8 "2025-04-01T08:14:14Z")

</div>

Assuming that is a valid case, then

```zig
fn get() *i32 {
    var x: i32 = 0;
    return &x;
}

```

is also a valid case, with the same logic applied. But object (pointer in this case) should be placed somewhere? Where this pointer is stored? Your example crashes on `ReleaseSafe`. I think it reads leftover value from stack. I don’t think sema really knows if pointer points to heap allocated object.

Edited your example slightly for easier debugging:

> **[Compiler Explorer - Zig (zig 0.14.0)](https://godbolt.org/z/oEooWfM75)**
>
> const std = @import("std");
> 
> noinline fn getAlloc2() !\*\*i32 {
> var x\_ptr = try @call(.never\_inline, std.mem.Allocator.create, .{std.heap.page\_allocator, i32});
> x\_ptr.\* = 42;
> const ptr = &x\_ptr;
> return ptr;
> }
> 
> pub fn main() !void {
> ...

It indeed returns pointer to stack memory:

```zig
lea rcx, [rbp - 32] ; get address of `x_ptr` and write it to `rcx` (`ptr`)
mov qword ptr [rbx], rcx ; put resulting address of stack allocated `ptr` to `rbx`

```

EDIT: posted old godbolt link by accident

---

<div class="post-metadata">

**Author:** ![tensorush](https://ziggit.dev/letter_avatar_proxy/v4/letter/t/6a8cbe/32.png) [@tensorush](https://ziggit.dev/u/tensorush)\
**Post date:** [April 1, 2025, 8:19am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/9 "2025-04-01T08:19:10Z")

</div>

Thanks, my bad! I’ve updated the post above.

---

<div class="post-metadata">

**Author:** ![mnemnion](https://ziggit.dev/user_avatar/ziggit.dev/mnemnion/32/2478_2.png) [@mnemnion](https://ziggit.dev/u/mnemnion)\
**Post date:** [April 1, 2025, 3:18pm UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/10 "2025-04-01T15:18:26Z")

</div>

Here’s a minimal example of where the problem lies.

```zig
fn legalToPassTo(ptr: *const Something) Blah {
    var a_blah: Blah = .initial;
    a_bla.ptr = ptr;
    return a_blah;
}

fn legalToPassDown() Blah {
    const a_something: Something = .initial;
    const a_ptr = &a_something;
    return legalToPassTo(a_ptr);
}

```

Both of these functions are doing perfectly legal things, _on their own_. Passing a pointer down-stack is perfectly ok, taking a pointer and returning a struct incorporating it is also a-ok. Returning a value directly from a called function? Absolutely normal behavior.

This being the simplest possible example, is already far more difficult to catch than it may initially look like:

```zig
fn legalToPassTo2(ptr: *Something) Blah {
    var a_blah: Blah = .initial;
    a_bla.ptr = could_be_anything.maybeClone(ptr);
    return a_blah;
}

```

Some of you know the punchline: this is _lifetime analysis_.

---

<div class="post-metadata">

**Author:** ![jeffective](https://ziggit.dev/user_avatar/ziggit.dev/jeffective/32/3116_2.png) [@jeffective](https://ziggit.dev/u/jeffective)\
**Post date:** [April 1, 2025, 5:09pm UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/11 "2025-04-01T17:09:07Z")

</div>

It’s seems to my untrained eye that if the problems solved by [Proposal: restricted function types · Issue #23367 · ziglang/zig · GitHub](https://github.com/ziglang/zig/issues/23367) are actually solved (the compiler knowing the full function call graph), then the compiler could also know the lifetimes of all stack allocated memory, and thus detect pointers to invalid stack allocated memory.

Consider the example of an acyclic function call graph. The compiler would only need to check that a given stack variable pointer does not traverse upwards from its point of creation? I’m guessing it looks a bit like reference counting.

---

<div class="post-metadata">

**Author:** ![jeffective](https://ziggit.dev/user_avatar/ziggit.dev/jeffective/32/3116_2.png) [@jeffective](https://ziggit.dev/u/jeffective)\
**Post date:** [April 1, 2025, 6:54pm UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/12 "2025-04-01T18:54:19Z")

</div>

If I understand you correctly, you are saying the reason that use-after-stack-lifetime-expired can not be a compile error is similar to the reason that `std.debug.assert(false)` cannot be a compile error?

In other words, the (unwritten) language specification currently catagorizes use-after-stack-lifetime-expired as undefined behavior?

---

<div class="post-metadata">

**Author:** ![mnemnion](https://ziggit.dev/user_avatar/ziggit.dev/mnemnion/32/2478_2.png) [@mnemnion](https://ziggit.dev/u/mnemnion)\
**Post date:** [April 1, 2025, 6:54pm UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/13 "2025-04-01T18:54:32Z")

</div>

```zig
// A global:
var must_copy: bool = false;

fn legalToPassTo(ptr: *const Something) Blah {
    var a_blah: Blah = .initial;
    // A bug:
    if (must_copy) 
        a_blah.ptr = ptr;
    else
        a_blah.ptr = ptr.clone();
    return a_blah;
}

fn legalToPassDown() Blah {
    const a_something: Something = .initial;
    const a_ptr = &a_something;
    // doing everything right down here
    must_copy = true;
    const a_blah = legalToPassTo(a_ptr);
    // gotta clean up!
    must_copy = false;
    return a_blah;
}

```

Demonstrating that this is, in fact, subject to the Halting Problem, is left as an exercise for the interested reader.

---

<div class="post-metadata">

**Author:** ![jeffective](https://ziggit.dev/user_avatar/ziggit.dev/jeffective/32/3116_2.png) [@jeffective](https://ziggit.dev/u/jeffective)\
**Post date:** [April 1, 2025, 7:27pm UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/14 "2025-04-01T19:27:31Z")

</div>

This situation could be caught by an eager analysis, similar to how “unused function parameter” is currently analyzed? The compiler need only prove that it is possible based on the AST? Maybe that would be annoying due to false positives.

---

<div class="post-metadata">

**Author:** ![pachde](https://ziggit.dev/user_avatar/ziggit.dev/pachde/32/2485_2.png) [@pachde](https://ziggit.dev/u/pachde)\
**Post date:** [April 1, 2025, 7:45pm UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/15 "2025-04-01T19:45:06Z")

</div>

No pointer provenance survives the FFI boundary or bitcasting to some integer.

---

<div class="post-metadata">

**Author:** ![Dok8tavo](https://ziggit.dev/user_avatar/ziggit.dev/dok8tavo/32/707_2.png) [@Dok8tavo](https://ziggit.dev/u/Dok8tavo)\
**Post date:** [April 1, 2025, 7:46pm UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/16 "2025-04-01T19:46:30Z")

</div>

No, because this works:

```zig
fn f(param: u8) void {
    if (false)
        use(param);
}

```

The `param` can still actually be “unused at runtime”, and similarily, you could still leak a stack pointer at runtime with this approach.

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [April 1, 2025, 8:14pm UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/17 "2025-04-01T20:14:56Z")

</div>

On a more practical front, how hard would it be fill the stack space used by a function with `0xAA` on function exit?

---

<div class="post-metadata">

**Author:** ![Chaz](https://ziggit.dev/letter_avatar_proxy/v4/letter/c/4491bb/32.png) [@Chaz](https://ziggit.dev/u/Chaz)\
**Post date:** [April 1, 2025, 8:54pm UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/18 "2025-04-01T20:54:11Z")

</div>

Seems like there’s a difference between using &x when x is on the stack and when x is not on the stack. I don’t normally suggest language features, but how awful would it be if we had to write it as @someNewTypeOfCast(&x) before using it as a pointer, even for calling some x.func()?

I know it’s functionally just as easy to make a mistake, but with this I could _audit_ them, and maybe along the way I put a few more things into an arena and off the stack. Even a FixedBufferAllocator-backed arena that uses stack memory reduces the number of places to look for this error.

---

<div class="post-metadata">

**Author:** ![andrewrk](https://ziggit.dev/user_avatar/ziggit.dev/andrewrk/32/7011_2.png) [@andrewrk](https://ziggit.dev/u/andrewrk)\
**Post date:** [April 1, 2025, 8:57pm UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/19 "2025-04-01T20:57:47Z")

</div>

btw there is [a plan to catch this at runtime](https://github.com/ziglang/zig/issues/3180)

---

<div class="post-metadata">

**Author:** ![LucasSantos91](https://ziggit.dev/user_avatar/ziggit.dev/lucassantos91/32/440_2.png) [@LucasSantos91](https://ziggit.dev/u/LucasSantos91)\
**Post date:** [April 2, 2025, 4:54am UTC](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380/20 "2025-04-02T04:54:10Z")

</div>

I’m with @jeffective , it’s not as hard as people make it out. Gcc and clang’s static analyzer catch these very easily.  
Catching every possible situation is unsolvable, as has been pointed out, like due to external functions. But most bugs of this type are not complicated. We could catch the vast majority of them with interprocedural analysis, though it could be costly. But I think even a naive approach, like just looking at the AST, would already catch a whole lot of bugs, while being cheap.  
There are no false positives with the AST approach (though it would obviously have a lot of false negatives). And we could just design the interprocedural analysis to not have false positives, that is, do not complain unless you can prove with absolute certainty there is a dangling pointer (once again, such a high bar would mean a lot of false negatives).

[Next page](https://ziggit.dev/t/what-makes-ban-returning-pointer-to-stack-memory-difficult/9380.md?page=2)
