top | item 37546567

(no title)

jeff-davis | 2 years ago

The Leak trait could be interesting when it comes to C code that calls rust code that calls C code that might longjmp() over the rust code in the middle.

Right now longjmp()ing over rust code is not a great idea for a couple reasons, but because leaks are currently always allowed in rust, it arguably follows the rules at least in some cases (doing so could cause problems, of course).

If the Leak trait were introduced, then skipping destructors would be clearly the wrong thing to do if there are types on the stack that don't implement Leak. That would clearly require some way to either prevent longjmp() from ever jumping over rust code (e.g. always catch it in C code and turn it into a special return code), or find some way to catch the longjmp and turn it into an unwind in rust (e.g. a panic?) and then turn it back into a longjmp to go back into C.

discuss

order

connicpu|2 years ago

Honestly, longjmp is just extremely problematic for any code that isn't pure C. Even just adding C++ into the mix can easily make longjmp something that can't be used without invoking UB.

"If replacing std::longjmp with throw and setjmp with catch would invoke a non-trivial destructor for any automatic object, the behavior of such std::longjmp is undefined."[1]

[1]: https://en.cppreference.com/w/cpp/utility/program/longjmp

jeff-davis|2 years ago

setjmp/longjmp exist in a lot of real world C code, so there is a lot of value in allowing rust to deal with longjmp in a defined way at least in some narrow cases.

And I don't think it's impossible -- it probably requires some special wrappers (perhaps bindgen could generate them?) that would call setjmp before entering the C code. setjmp has its own set of problems, but I don't believe those are impossible to solve, either.

LegionMammal978|2 years ago

> Right now longjmp()ing over rust code is not a great idea for a couple reasons, but because leaks are currently always allowed in rust, it arguably follows the rules at least in some cases (doing so could cause problems, of course).

In the general case, skipping destructors of objects you don't own (through longjmp(), killing a single thread, etc.) has never been allowed in Rust: at any particular point in the program, you're only allowed to skip destructors of objects that you currently own, or can otherwise gain ownership of.

For instance, the thread::scope() API, after running the spawning thread's closure, uses a destructor* to join all the created threads. However, if a program could longjmp() past that destructor, then the spawning thread could access its variables again while the created threads are still using them, resulting in a data race. (This is the same issue that the original thread::scoped() API had.)

  let mut x = 0;
  let mut jmp_buf = JmpBuf::default();
  if setjmp(&mut jmp_buf) == 0 {
      std::thread::scope(|s| {
          s.spawn(|| loop { x += 1; });
          longjmp(&jmp_buf, 1);
      });
  } else {
      loop { println!("{x}"); } /* the created thread is still changing the value of x! */
  }
That is to say, longjmp() has always been a very unsafe operation, which can only be sound if you're in control of every single destructor which it skips. A Leak trait wouldn't change anything in that regard.

* Or rather, a catch_unwind() followed by a resume_unwind(), which is mostly equivalent to a destructor in this context.

workingjubilee|2 years ago

You have not described a mechanism or API by which the compiler or programmer could enforce that only Leak types are on the stack and that a longjmp will not traverse `!Leak` types. And the only real way to get this would be to describe an interface that would have to be variadic to allow calling functions in general, as you would have to ensure that all args to a given `impl Fn*` are `T: Leak` for `T0` through `Tn`, and Rust currently lacks variadic generics. At that point, you might as well just do the same for `!Drop`.

jeff-davis|2 years ago

I'd be happy if there were a way to always unwind when a longjmp() happens, regardless of whether there are !Leak types on the stack.

pornel|2 years ago

Rust has extern "C-unwind" now, so if you can modify the C code to call Rust's panic instead of longjmp, it will unwind and run destructors properly.