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

patch* functions should be marked unsafe #5

Open
lemmabit opened this issue Nov 10, 2018 · 9 comments
Open

patch* functions should be marked unsafe #5

lemmabit opened this issue Nov 10, 2018 · 9 comments
Labels
help wanted Extra attention is needed question Further information is requested

Comments

@lemmabit
Copy link

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.

@mehcode
Copy link
Owner

mehcode commented Nov 10, 2018

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
Copy link
Owner

mehcode commented Nov 10, 2018

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

@lemmabit
Copy link
Author

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

@mehcode
Copy link
Owner

mehcode commented Nov 13, 2018

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 { ... }.

@lemmabit
Copy link
Author

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

@mehcode
Copy link
Owner

mehcode commented Nov 13, 2018

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 mehcode added help wanted Extra attention is needed question Further information is requested labels Nov 13, 2018
@HeroicKatora
Copy link

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.

@kotauskas
Copy link

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.

@mehcode
Copy link
Owner

mehcode commented Apr 9, 2020

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants