silicon icon indicating copy to clipboard operation
silicon copied to clipboard

Triggers silently ignored/replaced

Open fpoli opened this issue 3 years ago • 8 comments
trafficstars

For some reason, the following program verifies even if the apply_on tigger is not used in test. Removing the precondition of apply_on makes the program fail as expected. Both versions of the program don't verify in Carbon.

function apply_on(a: Int): Bool
  requires 0 <= a && a <= 18446744073709551615

function opaque_property(a: Int, k: Int): Int

method test() {
  var a: Int
  assume forall k: Int :: { apply_on(k) } opaque_property(a, k) == 100

  assert opaque_property(a, 123) == 100
}

fpoli avatar Nov 21 '22 12:11 fpoli

That's due to how Silicon handles triggers. In your example, the trigger { apply_on(k) } is silently (good design choice) ignored because 1. it is a program function (heap-dependent function) and 2. does not occur in the quantifier body. If you use a domain function instead, the trigger makes it to the SMT solver and the final assertion cannot be verified.

To understand the root problem, here's a bit of context. Imagine the following program (concrete types irrelevant):

function foo(x)
  requires A

method main() {
  ...
  inhale forall y :: {foo(x)} A1 && (A2 ==> A3) && A4
}

Symbolically evaluating foo(x) entails computing foo's heap footprint, which (in current Silicon) entails temporarily exhaling A. However, it is not clear when A is supposed to hold: before inhaling A1? After A1 and A2? Etc. Silicon sidesteps the problem by recording the symbolic terms corresponding to function application while going over the quantifier body, and reusing them to translate triggers. If a trigger doesn't occur in the body, however, it is silently ignored.

mschwerhoff avatar Nov 22 '22 14:11 mschwerhoff

Couldn't we trigger on the stateless version of the function in that case?

marcoeilers avatar Nov 22 '22 14:11 marcoeilers

Yes, that should work. We should get rid of heap-dependent triggers anyway ... :-)

mschwerhoff avatar Nov 22 '22 14:11 mschwerhoff

Thanks! So far I thought that Viper functions are heap-dependent only when they require predicates or permissions in their precondition.

That's due to how Silicon handles triggers. In your example, the trigger { apply_on(k) } is silently (good design choice) ignored because 1. it is a program function (heap-dependent function) and 2. does not occur in the quantifier body. If you use a domain function instead, the trigger makes it to the SMT solver and the final assertion cannot be verified.

Wouldn't a warning or error message be better than silently ignoring explicit triggers? Especially if ignoring the triggers makes Silicon/Z3 choose triggers on its own, which can introduce matching loops.

fpoli avatar Nov 23 '22 09:11 fpoli

We don't have a good term for non-domain functions, and simply called them heap-dependent so far. If they don't access the heap, their footprint is empty, but they nonetheless have one.

Yes, emitting a warning would be better. I don't think these are shown in the IDE, though.

mschwerhoff avatar Nov 23 '22 10:11 mschwerhoff

Warnings, at least some kinds (see for example this https://github.com/viperproject/silicon/blob/228387ad9fd600171dfc9fa98b0a24e93acdc482/src/main/scala/verifier/DefaultMainVerifier.scala#L160) are shown in the IDE.

marcoeilers avatar Nov 23 '22 10:11 marcoeilers

(Tangential:) For front-ends it might make sense to have a mode like -Werror for C compilers, which would turn any warning into an error that we must handle as part of the verification result…

Aurel300 avatar Nov 23 '22 11:11 Aurel300

We now emit warnings for this, so it's no longer silently ignored.

marcoeilers avatar Mar 16 '23 18:03 marcoeilers