Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is mem::forget(mem::uninitialized()) defined behavior?

In mutagen, I'm injecting various mutations in the code. One thing I'd like to mutate is the pattern if let Ok(x) = y { .. }. However, this poses quite the challenge, as I cannot know the type of y – the user could have built their own enum with an unary Ok variant. I can still opportunistically mutate it for cases where we actually have a Result whose error type implements Default using a trait that looks like the following simplified:

#![feature(specialization)]

pub trait Errorer {
    fn err(self, mutate: bool) -> Self;
}

impl<X> Errorer for X {
    default fn err(self, _mutate: bool) -> Self {
        self
    }
}

impl<T, E> Errorer for Result<T, E>
where
    E: Default,
{
    fn err(self, mutate: bool) -> Self {
        if mutate {
            Err(Default::default())
        } else {
            self
        }
    }
}

Alas, there aren't that many errors which implement Default, so this is not too useful. Even an implementation for Result<T, Box<Error>> would give us more bang for the buck (and be completely possible). However, given that I don't care much about code actually inspecting the error, I wonder if I could do a general implementation by extending the mutation of the above code to

match Errorer::err(y, mutation) {
    Ok(x) => { .. }
    Err(x) => { mem::forget(x); }
}

and have err return Err(mem::uninitialized()) when mutating – so is this behavior safe? Note: I'm returning Err(mem::uninitialized()) from a method, only to mem::forget it later. I see no way this could panic, so we should assume that the value will be indeed forgotten.

Is this defined behavior or should I expect nasal demons?

like image 940
llogiq Avatar asked May 04 '18 22:05

llogiq


1 Answers

No, this is not defined behavior, at least not for all types. (I can't tell how your code would be called as part of mutation, so I don't know if you have control over the types here, but the generic impl sure makes it look like you do not.) That's demonstrated by the following piece of code:

#![feature(never_type)]
use std::mem;

fn main() {
    unsafe { mem::forget(mem::uninitialized::<!>()) }
}

If you run this on the playground, you will see the program die with a SIGILL. The ASM output shows that LLVM just optimized the entire program to immediate SIGILL because of the way it uses a value of the uninhabited type !:

playground::main:
    ud2

Generally speaking, it is near impossible to correctly use mem::uninitialized in generic code, see e.g. this issue of rc::Weak. For this reason, that function is in the process of being deprecated and replaced. But that won't help you here; what you want to do is just outright illegal for Result<T, !>.

like image 157
Ralf Jung Avatar answered Oct 06 '22 20:10

Ralf Jung