Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust doesn't guarantee memory safety with GC #1532

Open
simon-frankau opened this issue Aug 2, 2019 · 6 comments
Open

Rust doesn't guarantee memory safety with GC #1532

simon-frankau opened this issue Aug 2, 2019 · 6 comments

Comments

@simon-frankau
Copy link
Contributor

One of the things I like about Rust is that (as long as you're not doing unsafe things) it provides memory safety guarantees. However, #845 brought home to me that it's quite possible to get live data GC'd by accidentally storing it only on the heap.

So, we're back to the C world of "If you don't want memory corruption you have to be really careful, the compiler won't keep you safe". And if there's a GC-related bug, it's likely to be a bit non-deterministic and hard to track down.

(I understand that the allocator and GC implementation is always going to need care to avoid corruption, but it would be nice to constrain that need to care to just a small part of the code base.)

I can see three approaches the project can take:

  1. Be careful Everyone needs to understand lisp objects must never be referenced only from the heap. I don't like this as I want the compiler to support memory safety.
  2. Prevent lisp objects reaching the non-emacs-allocator heap This sounds like banning vec and friends from the majority of the remacs codebase.
  3. Control when GC happens If GC only happens during lisp/bytecode execution steps, rather than whenever the allocator feels like it, we narrow down the opportunities for when GC may delete a live reference. The lisp/bytecode evaluators need to be carefully written, but the rest of the code should be safe.

I'm quite tempted to explore option number 2, and see quite how big the changes would need to be, but I'm interested in a) what people with more experience on the code base think, and b) if anyone has any alternative suggestions.

@pipcet
Copy link
Collaborator

pipcet commented Aug 3, 2019

I think this issue is fundamental, and at this point makes Rust unsuitable for implementing a garbage-collected language like Emacs Lisp.

In particular, I think it's going to turn out to be impossible to switch to a moving collector for remacs, even though it's quite possible with GNU Emacs.

Garbage collection fundamentally collides with the "single owner" concept of Rust, as far as I can tell. Rust is likely to optimize code, at some point in the future, by exploiting the fact that only one writable reference to a data item exists; and the garbage collector, which operates without interaction with Rust, violates that assumption.

My personal conclusion is that Rust and Emacs just don't play well together, right now. I think proper garbage collection is going to require Rust compiler support (ideally, DWARF information that makes it possible to tell which stack and heap locations hold Lisp Objects).

Anyway, that's just one opinion, and it's quite true that with the current rust toolchain, and the current mark-and-sweep collector, the problems that potentially might occur actually don't.

So I would suggest that we find a solution that works for moving collectors, too (and incidentally we might be able to use the SpiderMonkey collector directly), even if that requires work on the Rust compiler as well as remacs.

@shaleh
Copy link
Collaborator

shaleh commented Aug 3, 2019

I am not quite as concerned at the long term viability yet.

Our issues today stem from attempting to leave as much of the C infrastructure in place so we can port parts over in small, manageable pieces. Which is not a bad strategy but it leaves us vulnerable to issues like this where the C code is exploiting implementation details.

A potential solution is teaching the C based GC to also call a Rust based GC. The Rust GC could handle whatever special bits are needed for Rust LispObjects. There are likely issues in that, but it is a possibility. Perhaps you know more here than I do @pipcet .

@simon-frankau I hear you and agree, compiler support should be the goal. However, perhaps in the short to medium term we could instead leverage something like clippy? I know clippy itself doesn't support plugins (oh that would be nice!) but maybe there is a way to leverage that code and implement something? Or come up with a way to wrap items in a Dangerous type? Since no function would accept Dangerous there would need to be a mechanism to make them safe. One mechanism could be marking them for GC either by the C code or a Rust GC. Essentially, this would like re-implementing the lifetime checking manually.

@simon-frankau
Copy link
Contributor Author

I've tried a quick pass at option 2, stopping using the Rust heap by disabling the std library and then re-exporting the parts that don't use the heap. I've put it up at https://github.com/simon-frankau/remacs/tree/no-heap

Right now heap-allocating things are re-exported, but the idea would be to eliminate their usage and remove them from the re-exports.

It's not fool-proof - if you pass a LispObject to another library, it might in turn place it on the heap - but the risky surface is reduced.

This kind of approach doesn't address @pipcet's long-term concerns, but I think it reduces the risk of weird and hard-to-track-down bugs in the shorter term.

@shaleh
Copy link
Collaborator

shaleh commented Aug 5, 2019

It is a good and simple hack @simon-frankau. I like it until we can work out better solutions.

@pipcet
Copy link
Collaborator

pipcet commented Aug 5, 2019

I might have sounded a bit too concerned in the message above. I think a better solution will come around, and until then option 2 sounds best, and isn't as painful as I thought it would be

It's not fool-proof - if you pass a LispObject to another library, it might in turn place it on the heap - but the risky surface is reduced.

I think it's very rare for libraries to do that, then hand control back to the caller (so GC can run), then get called again and use the argument. Callbacks are one example of where this happens, and IIRC there's some GTK menu code that has to be very careful about that in the Emacs C code base.

@simon-frankau
Copy link
Contributor Author

It sounds like people like this, so I'll polish up what I've got, and maybe also have a go at removing the remaining uses of Vec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants