lunatic-solutions/lunatic-rs

Unsoundness with `Fn` trait

Noratrieb opened this issue · 3 comments

The Fn trait allows converting arbitrary integers into function pointers.

use lunatic::function::reference::Fn;

<fn() as Fn<fn()>>::from_id(0)();

This creates a null fn() pointer which is instant undefined behavior.

Thanks @Nilstrieb for bringing this up. This is a bit of an ugly workaround for the fact that Rust doesn't let us implement traits for function pointers. I talked a bit more about the issue here: https://internals.rust-lang.org/t/extending-implicit-coercion-of-function-items-to-function-pointers-during-trait-resolution/17093

I have an idea on how to make this interface safer and more ergonomic. One of my posts from discord:

The main reason why we can't use closures as entry points into new processes is that closures can hold data and have an anonymous type. This means that we can't serialize the data before passing it to a new process. However, we might be able to find a middle-ground here.

It is safe to send captured data to another process if it can be done as a memcopy and doesn't require serialization. Luckily, Rust has a built-in marker trait for this, Copy. Copy is also an auto-trait, meaning that it will be implemented for closures if all members also implement it. That's why I think that a type like this would work for entry functions:

trait Entry {}
impl<T> Entry for T
    where T: FnOnce() + Copy + 'static

Copy is also implemented for references and pointer types, but we work around this by requiring a 'static lifetime. The main idea is just to take T and memcopy it as is into the new process.

This would have two benefits for us:

  1. It relaxes the requirement that each entry function can't capture anything to "an entry function can capture copy types".
  2. It would give us more flexibility to express process properties based only on entry function signatures. For example, fn(Mailbox<T>) would spawn a regular process, fn(Protocol<T>) a protocol, etc.

This would also need a T: Send bound, but doesn't sound bad otherwise.

This would also need a T: Send bound, but doesn't sound bad otherwise.

I don't think so. Lunatic processes use different memory spaces, Send and Sync don't mean anything in that case. One process can never reference/observe the memory of another.