Semantic fuzzing of the Rust compiler and interpreter [pdf]
ethz.ch
ethz.ch
>This resulted in the Rust compiler apparently “proving” the unresolved Collatz conjecture by optimising this function to return true [1].
pub fn collatz(n: usize) -> bool {
match n {
1 => true,
n if n % 2 == 0 => collatz(n/2),
_ => collatz(3\*n + 1),
}
}
That's funny. Hey maybe it was just that good!> That is, true for all value represent-able in usize.
A slight understatement!
There might be a cycle in theory.
So basically rustc_codegen_gcc and cranelift can compile code that don't with LLVM and then introduce (or solve) new bugs?
But in general, yes, if this was unintended behavior of LLVM, then we would expect alternative backends to exhibit differing behavior.
>Infinite loops and recursions are Undefined Behaviour in C and C++, therefore the compiler can assume that a loop always terminates.
... which is wrong - only C++ requires loops to terminate. C allows non-terminating loops (with some specific requirements), and LLVM was already known to miscompile C in this same way until it was fixed in LLVM 12 with the `mustprogress` attribute.
https://bugs.llvm.org/show_bug.cgi?id=965
https://stackoverflow.com/questions/59925618/how-do-i-make-a...
Then this:
>While LLVM does not require loops to terminate and should compile Rust loops correctly, its code contained assumptions that only hold for C/C++, thus miscompiling Rust code.
... is implied to be a different LLVM bug after the first one was already fixed, ie despite using `mustprogress` LLVM had additional miscompilations for Rust code because of "assumptions that only hold for C/C++". I can't tell which one in table 4.2 it is, though; none of them see to match.
It also doesn't implement the collatz conjecture. It implements the collatz-conjecture mod (usize::MAX + 1). I don't know about that modulus specifically, but there are m where the collatz conjecture mod m is false.
https://www.researchgate.net/profile/Cganesa-Moorthy/publica...
Edit: (2^64 - 1) / 3 is an integer and also trivially infinitely loops (by going to 0).
Only if you have overflow checking disabled.
int foo(int a, int b) { int c = bar(a); return b; }
and we can see bar's body, and therefore we can determine that it has no side effects, we can optimize the call to bar away, because we can assume that it terminates and it doesn't affect the result.
But if bar writes to global variables, that's a side effect. If we can't see the body of bar we have to assume that it might.
We got hurt by that in side-channel attacks.
Before he pivoted to LLVM work, Nikita was one of the key individual contributors to the official PHP project in the last decade. He's an incredible force for good.
[1] https://research.ralfj.de/ [2] https://news.ycombinator.com/user?id=ralfj
The approach described in this section (just insert a bunch of calls to a dump_var() function which dumps its results into the graph) is really interesting to me. A problem I've had with fuzzing is getting some high-bandwidth error data other than "the program crashed". You can eg instrument pointer reads and writes, but sometimes reordering or deleting them is a legitimate optimization.
It's really cool to see Rust developers pioneer these revolutionary new methods nobody thought of before. </CunninghamsLaw> (But seriously, if someone has a name for this technique, I'm interested.)