Description
(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::forget
s 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 containsT: 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::forget
s a stack frame, or drop
s 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.