Skip to content

mem::forget on stack frames #210

Closed
Closed
@gnzlbg

Description

@gnzlbg

(think longjmp / setjmp)

Some abstractions, like Pin (or crossbeam::scope), allow passing references to the current stack frame to other threads of execution, and use the destructor to make sure that these references do not dangle when the stack frame is dropped. That is, their safety invariant requires that their destructor is run, which does not happen if one mem::forgets their stack frame.

We guarantee that these abstractions are sound, that is, that safe Rust code cannot cause undefined behavior by using them. Therefore, a safe API that would allow to mem::forget a stack frame that contains a type with such safety invariants would be unsound if it does not require the user to restore this (*).

unsafe Rust code is actually allowed to temporarily break safety invariants as long as it restores them at the safe boundary. unsafe Rust can, in practice, also mem::forget stack frames by using its FFI feature to just call unknown code that does that (e.g. by calling longjmp). At the spec level, we do not say anywhere that calling such an FFI function is allowed, so I'd say that right now this is UB, at least by omission.

So what can unsafe code do with this unsafe feature ? I think that:

  • it can definitely mem::forget a stack-frame that only contains T: Copy types, since such types do not have the safety invariants mentioned above
  • it can also definitely mem::forget a stack-frame that contains types with destructors, as long as these types do not maintain any safety invariant like the one above
  • it can also mem::forget a stack-frame that contains a type with a safety invariant like the above, as long as it restores this invariant before leaving the safe code boundary (e.g. it could forget the frame, and push a previously saved frame to the end of the stack that restores it for these types)

I think it would be good to have a name for the safety invariant that these types rely on (e.g. "not mem::forget safe" ?).

I also think it would be good to guarantee somewhere when mem::forget on stack frames is correct.

By this I don't want to suggest that we should add a feature to Rust to do this. C has such features already, setjmp/longjmp, and currently at the implementation level we do guarantee that they work properly (**).

This issue isn't about setjmp/longjmp specifically, but about the possibility that Rust frames can get deallocated without calling their destructors, which is only one of the many ways in which longjmp can work.


(*) That is, I believe that if we had a way to "mark" those types, and could be able to tell if a stack frame contains one of them, it might be possible to build a safe API for this.


(**) For example, rlua passes C code that uses setjmp a Rust callback that calls some other C code that uses longjmp, such that Rust frames end up "sandwiched" between a setjmp and a longjmp. We have fixed bugs in the compiler to make sure that this "works" on all platforms, and have tests ensuring that this works.

Note that, in practice, a longjmp either mem::forgets a stack frame, or drops it, depending on the platform (some change the stack pointer, forgetting frames, others unwind calling destructors, and others unwind not calling destructors). We implement the Rust side of things such that no platform aborts when longjmp goes through an abort-on-panic shim, and also guarantee that longjmp works even when -C panic=abort. That is, on some platforms, doing a longjmp over Pin is actually safe because Pin destructor gets called.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-dropTopic: related to droppingA-unwindTopic: related to unwindingC-open-questionCategory: An open question that we should revisit

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions