mehcode / guerrilla

☢ Guerrilla (or Monkey) Patching in Rust for (unsafe) fun and profit.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

patch* functions should be marked unsafe

lemmabit opened this issue · comments

Setting aside the question of whether the patching routine itself should be considered safe, these functions can be used to break invariants, which makes them unsafe.

Fair. It'll make it slightly more annoying to use in tests but it's better to call this kind of hankey panky out.

I'll accept a PR or I'll get to it in the next couple days.

@Permutatrix Do you mind writing a small example to demonstrate an invariant being broken I can add to documentation? I can't think of something concrete.

@mehcode Here's a small example of guerrilla being used to cause memory violations in safe code:

extern crate smallvec;
extern crate guerrilla;

use smallvec::SmallVec;
use guerrilla::patch2;

fn main() {
    let mut sv = SmallVec::<[u32; 0]>::new();

    // spill sv onto the heap.
    // we'll trigger a debug assertion without this line, which is
    // still bad but perhaps not quite as convincing.
    sv.push(0);

    let _guard = patch2(SmallVec::<[u32; 0]>::grow, |_self, _new_cap| {
        // instead of growing, do nothing.
    });

    // now loop until we trigger a segmentation fault.
    loop {
        sv.push(0);
    }
}

Thinking back on this I've changed my mind.

Regarding your example, rust unsafe vs safe doesn't cover logical behavior. If I change the behavior as demonstrated in your example its still perfectly safe with respect to Rust's rules. Yes it breaks an invariant that SmallVec depends on but that's more of an explosive footgun than unsafe { ... }.

@mehcode Niko Matsakis wrote a blog post on the composability of safe abstractions that may be of interest: http://smallcultfollowing.com/babysteps/blog/2016/10/02/observational-equivalence-and-unsafe-code/

Regardless of whether you accept his model, there are other things that make these functions unsafe. For instance, multiple threads patching and unpatching the same function can cause race conditions.

For instance, multiple threads patching and unpatching the same function can cause race conditions.

I'm classing that as a bug for now that will be fixed soon enough (tm).


Niko Matsakis wrote a blog post on the composability of safe abstractions that may be of interest [...]

I do agree with most of that. I just have some reservations on making this function unsafe because of its limited intended usage. I'll think on it.


I'll leave this issue open to collect feedback for awhile on if we should make this function unsafe. This crate is designed for use in a (currently) single-threaded #[test] environment. Any other usage is undefined and unsupported by this crate.

Regarding your example, rust unsafe vs safe doesn't cover logical behavior. If I change the behavior as demonstrated in your example its still perfectly safe with respect to Rust's rules. Yes it breaks an invariant that SmallVec depends on but that's more of an explosive footgun than unsafe { ... }.

The internal state invariants are the only possible justification for being able to use unsafe at all. If there were no invariants upheld, it would be impossible to guarantee anything that is not expressible in safe Rust, regardless of the class (from the blog post) of your choosing. That would make any kind of 'abstraction over unsafe' pointless. Hence, I don't think arbitrary meddling with internal invariants can be seen as a reasonably safe operation in any of the classes.

One possible remedy for #[test] environments: Create a single global function unsafe fn accept_all_unsafeness() and make all other functions panic when they are called before that one.

IMO it should still be unsafe. There isn't anything preventing any unsafe code from relying on the fact that a function runs, even if it's pub, for safety. Safe abstractions over unsafe code are, from a soundness standpoint, purely black boxes, meaning that tampering with the code or the internal state (private fields) leads to UB, which is unsafe. Replacing a function without a return value (replacing it with, say, a function that does nothing) is an example of tampering the internals of said black box, i.e. is UB.

As a compromise for test UX, what about if we deprecate the functions, rely only on a patch! ( as in #11 ) macro, and have that macro emit #[cfg(test)] blocks so if the macro is used in a unit test, it doesn't require unsafe { ... } but if it's not, it does?