[dcl.attr.contract.check]/4:
During constant expression evaluation (7.7), only predicates of checked contracts are evaluated. In other contexts, it is unspecified whether the predicate for a contract that is not checked under the current build level is evaluated; if the predicate of such a contract would evaluate to false, the behavior is undefined.
This seems like an open hunting license for optimization: A compiler can probably assume that a predicate evaluates to true and optimize based on that. Perhaps even worse, a compiler can sometimes see that a predicate evaluates to false (but is not evaluated in the current checking mode), and do anything.
Well, this is courtesy of Peter Dimov:
Peter further explains:
Since the not-checked [[expects audit: x==2]] is assumed to pass, x is replaced with the constant 2 and all other checks are optimized out.test(): sub rsp, 8 call rand mov esi, 2 mov edi, OFFSET FLAT:.LC0 xor eax, eax call printf .L22: call bar() add rsp, 8 ret
Peter continues:
If you make `f`and add `extern` to `a` in `g`void f(int x) [[expects audit: x>=1 && x<=2]]
your (Herb's) point becomes clearer, as now `test` does write 42 to a random location:void g(int x) [[expects: x>=0 && x<3]] { extern int a[3]; a[x] = 42; }
https://godbolt.org/g/SfKWPu-2
The background of that exploration is this write-up by Herb:
Since we now agree about my multi-level contract example this morning about UB when only some contracts are enabled… carefully coming back to my earlier example, which I agree did not allow magic before, but now changing the first precondition to "audit" or "axiom":
And we run them in series approximately like this:// same as before, except now "XXX" contract level on f, see bullets below void f(int x) [[expects XXX: x==2]] { ... } void g(int x) [[expects: x>=0 && x<3]] { int a[3]; a[x] = 42; } void h(int x) [[expects: x>=1 && x<=3]] { switch(x) { case 1: foo(); case 2: bar(); case 3: baz(); } }
then if either of these two cases is true:int val = std::rand(); try { f(val); /*...*/ } catch(...) { /*...*/ } try { g(val); /*...*/ } catch(...) { /*...*/ } try { h(val); /*...*/ } catch(...) { /*...*/ }
First of all, I'm not sure. Second, I don't know what we intend with the UB in the specification of a contract check that is not evaluated in the current checking mode but might be evaluated anyway. What concerns me is that if Peter's contracts emulation does what an actual contract implementation would do, I need to be very VERY careful about never EVER writing a contract that would evaluate to false unless I'm sure that it would evaluate to false only in the current checking mode, and I also need to be very careful about writing contracts that would evaluate to true in any other mode than the current checking mode, because I wouldn't want the non-current-mode contracts end up optimizing away my contracts for the current-mode contracts I have. I'm not sure whether those are the only things I need to be careful about.
I'm not sure I know how to be so careful, especially when I'm not sure what I need to be careful about.
In an email discussion about this, here's what I wrote:
"It boils down to this: an unchecked predicate that evaluates to false is UB, if the compiler decides to lay down code that evaluates that predicate in the mode that wouldn't otherwise evaluate it. So combinations of different checking levels on the same function or even the same translation unit (or in the same program, even) can trigger magical powers. Using just one level of checking can do that too without any problems; put just audit-level contracts into your program, run in default mode. You can go boom, boom, *BOOM*."
Well, I would entertain some ideas: