guerrilla icon indicating copy to clipboard operation
guerrilla copied to clipboard

patch* functions should be marked unsafe

Open lemmabit opened this issue 7 years ago • 9 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.

lemmabit avatar Nov 10 '18 01:11 lemmabit

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.

mehcode avatar Nov 10 '18 03:11 mehcode

@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 avatar Nov 10 '18 05:11 mehcode

@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);
    }
}

lemmabit avatar Nov 10 '18 06:11 lemmabit

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 avatar Nov 13 '18 19:11 mehcode

@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.

lemmabit avatar Nov 13 '18 23:11 lemmabit

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.

mehcode avatar Nov 13 '18 23:11 mehcode

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.

197g avatar Jan 21 '19 06:01 197g

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.

kotauskas avatar Apr 03 '20 16:04 kotauskas

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?

mehcode avatar Apr 09 '20 09:04 mehcode