SAW MIR override with slice argument fails to match
RyanGlScott opened this issue · comments
Here are two Rust functions, both of which take a slice argument:
// test.rs
pub fn f(s: &[u8]) -> u8 {
s[0] + s[1]
}
pub fn g(s: &[u8]) -> u8 {
f(s)
}
g
does nothing but call f
, so I would expect that the following compositional specification for g
would verify:
// test.saw
enable_experimental;
let oneThroughFive = mir_term {{ [1, 2, 3, 4, 5] : [5][8] }};
let f_spec = do {
a <- mir_alloc (mir_array 5 mir_u8);
mir_points_to a oneThroughFive;
mir_execute_func [mir_slice_value a];
mir_return (mir_term {{ 3 : [8] }});
};
m <- mir_load_module "test.linked-mir.json";
f_ov <- mir_verify m "test::f" [] false f_spec z3;
mir_verify m "test::g" [f_ov] false f_spec z3;
While f
does indeed verify against f_spec
, g
does not verify, much to my surprise:
$ ~/Software/saw-1.1/bin/saw test.saw
[13:23:34.144] Loading file "/home/ryanscott/Documents/Hacking/Haskell/saw-script/test.saw"
[13:23:34.209] Verifying test/8d8066da::f[0] ...
[13:23:34.224] Simulating test/8d8066da::f[0] ...
[13:23:34.225] Checking proof obligations test/8d8066da::f[0] ...
[13:23:34.241] Proof succeeded! test/8d8066da::f[0]
[13:23:34.280] Verifying test/8d8066da::g[0] ...
[13:23:34.294] Simulating test/8d8066da::g[0] ...
[13:23:34.295] Matching 1 overrides of test/8d8066da::f[0] ...
[13:23:34.295] Stack trace:
"mir_verify" (/home/ryanscott/Documents/Hacking/Haskell/saw-script/test.saw:18:1-18:11)
Symbolic execution failed.
Abort due to assertion failure:
test.rs:6:7: 6:8: error: in test/8d8066da::g[0]
All overrides failed during structural matching:
* Name: test/8d8066da::f[0]
Location: /home/ryanscott/Documents/Hacking/Haskell/saw-script/test.saw:17:9
Argument types:
- &[u8]
Return type: u8
Arguments:
- <slice>
at /home/ryanscott/Documents/Hacking/Haskell/saw-script/test.saw:8:3
could not match specified value with actual value:
actual (simulator) value: 0x1:[8]
specified value: let { x@1 = Prelude.Vec 8 Prelude.Bool
x@2 = Cryptol.PLiteralSeqBool
(Cryptol.TCNum 8)
}
in [ Cryptol.ecNumber (Cryptol.TCNum 1) x@1 x@2
, Cryptol.ecNumber (Cryptol.TCNum 2) x@1 x@2
, Cryptol.ecNumber (Cryptol.TCNum 3) x@1 x@2
, Cryptol.ecNumber (Cryptol.TCNum 4) x@1 x@2
, Cryptol.ecNumber (Cryptol.TCNum 5) x@1 x@2 ] : [5][8]
type of actual value: u8
type of specified value: [u8; 5]
The reason that SAW gives is rather bewildering, as the actual and specified values aren't even of the same type.
Here is what is happening when you run this program:
-
When
g
attempts to match thef
override, it first matches themir_slice_value a
argument in the specification against the actual slice value thatg
passes tof
in the simulator. The code that powers this lives here. This code ensures that (1) the lengths of the two slices are the same, and (2) the slice's underlying references match. -
The "slice's underlying references match" is the interesting part. In the spec, the underlying reference is derived from
a
, which is allocated at type&[u8; 5]
. However, whena
is passed tomir_slice_value
, we usecrucible-mir
's indexing operations to turn it into something of type*const u8
. This is because Rust actually does represent slices with pointers in this way, andcrucible-mir
models this slice representation faithfully.This means during matching, SAW "assigns"
a
to be a reference to something of typeu8
, not a reference to something of type[u8; 5]
. This is ultimately the root cause of the bug, and things will go awry after this point. -
After matching the
mir_slice_value a
argument, SAW will process themir_points_to a oneThroughFive
statement in the override forf_spec
to assert thata
does in fact point tooneThroughFive
before applying the override. The code that powers this lives here. This works by (1) reading the value thata
points to, based on the type ofa
, and (2) checking that the value is equal tooneThroughFive
.However, recall that we assigned
a
to be a reference to something of typeu8
earlier. This means that the value we read froma
will be1
, _not[1, 2, 3, 4, 5]
! This is a problem, and it's the reason why the error message has the type mismatch.
I'm not entirely sure what the best way to resolve this would be. Some possibilities:
- Register the underlying reference in
mir_slice_value a
at type[u8; 5]
, notu8
. This is a bit suspicious, however, given that it really does have the latter type, and I suspect thatcrucible-mir
might even error if you tried to perform the load at type[u8; 5]
later. - Have a special case for slice-derived references in step (3) above. That is, if the reference is derived from a slice, don't load a single value, but instead load multiple values corresponding to the length of the slice. This would require that we augment
MirPointer
with information about whether a pointer is associated with a slice or not.
Happily, there is a third, much more direct possibility:
- Recover the
a
from the expressionmir_slice_value a
and register that at type[u8; 5]
.
I originally thought this form of recovery not to be possible, but thankfully, crucible-mir
offers a way to do this using a lesser-known memory model operation (mirRef_peelIndexIO
) that I wasn't aware of until now. I'll submit a PR using this approach.