I am playing with attribute macros by writing one that will obfuscate strings in the source code, and it works great in debug mode, but in release mode, it doesn't seem to have any effect.
My guess is that the compiler sees that the SyncLazy will always produce the same value, and during optimizations, it goes ahead and evaluates at compile time, although I don't want this behavior.
I tried wrapping the evaluation in a black_box but the compiler didn't seem to take the hint. I also tried to use the optimize_attribute feature, though it looks like there isn't an optimize(none) available yet.
Example based on the results of cargo expand:
#![feature(once_cell)]
#![feature(bench_black_box)]
use std::hint;
use std::lazy::SyncLazy;
// everything in the lazy was generated by the macro to get the original string back at run-time
static VAL: SyncLazy<String> = SyncLazy::new(|| {
hint::black_box(String::from_utf8(
[
140, 155, 158, 142, 158, 142, 166, 156, 150, 130, 148, 171, 128, 134, 132, 150, 137,
]
.iter()
.enumerate()
.map(|(i, e)| i as u8 ^ !e as u8)
.collect::<Vec<u8>>(),
))
.unwrap()
});
fn main() {
println!("{}", *VAL);
}
# expected result:
$ rustc -C opt-level=1 main.rs && if grep 'secret' main; then echo '"secret" is still readable'; else echo "obfuscation worked"; fi
obfuscation worked
# does not hide data:
$ rustc -C opt-level=2 main.rs && if grep 'secret' main; then echo '"secret" is still readable'; else echo "obfuscation worked"; fi
grep: main: binary file matches
"secret" is still readable
My workaround is to set a lower release optimization level in my Cargo.toml, but I'm still curious about what's happening, or if there is a good way to postpone evaluations to runtime. Greedily evaluating a lazy type seems to defeat the purpose of it, so if there's a better explaination of what actually happened, that might help lead to a better solution, too.