Skip to content

Commit 5fe1929

Browse files
nikswamyCopilot
andcommitted
Add array_read snapshot regression
The suspected missing PAL feature was support for relating a value read from a C array back to the logical `array_value_of` snapshot of that array. The motivating shape is code like `entry_t entry = entries[i]` or `uint32_t x = entries[i].x`, followed by ghost code that proves a property of the concrete local from a precondition quantified over `array_value_of entries`. The new `array_read_snapshot.c` test captures that requirement directly. It defines `entries_ok` only over `array_value_of entries`, then checks two source patterns: copying a whole struct out of an `_array`, and copying a field out of an `_array` element. The post-read ghost assertions intentionally mention only the concrete locals, not the `Seq.index` expression. This makes the test a focused regression for whether PAL's generated Pulse preserves the connection between the C local and the array snapshot. After reducing the example, no emitter extension was needed. PAL's ordinary assignment translation emits the direct form `var_entry := array_read entries i` (and, for fields, projection from that direct read). Pulse can use the `array_read` postcondition through that assignment: the read result rewrites to `Some?.v (Seq.index (array_value_of entries) i)`, and the mutable local receives exactly that value. Therefore the quantified `entries_ok (array_value_of entries) count` precondition instantiates at the loop index and proves the assertions about the concrete locals. This commit keeps that behavior covered by a regression test and documents the lesson in the test source: do not add extra generated pure assertions or a second temporary binding for this case. The important restriction remains that this works for direct `_array` reads; `_arrayptr` reads do not expose the same parent-array snapshot relation and are not covered by this test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3f11c61 commit 5fe1929

1 file changed

Lines changed: 101 additions & 0 deletions

File tree

test/array_read_snapshot.c

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#include "pal.h"
2+
#include <stdint.h>
3+
4+
/*
5+
* Regression test for preserving the logical snapshot fact produced by
6+
* Pulse.Lib.C.Array.array_read.
7+
*
8+
* PAL models an _array value with the ghost snapshot
9+
*
10+
* array_value_of entries : Seq.seq (option entry_t)
11+
*
12+
* and array_read proves that the concrete value read at index i rewrites to
13+
*
14+
* Some?.v (Seq.index (array_value_of entries) i)
15+
*
16+
* provided the array element exists. This regression records that PAL's
17+
* ordinary translation of a C local initialization such as
18+
*
19+
* entry_t entry = entries[i];
20+
*
21+
* as a direct read into the mutable local is strong enough: the array_read
22+
* postcondition remains available through the assignment, so a ghost assertion
23+
* about entry can use a precondition quantified over array_value_of entries.
24+
*
25+
* The key point is that no special emitter machinery is needed here: PAL should
26+
* neither introduce a second read temporary nor emit an extra pure equality
27+
* assertion. These tests exercise both whole-struct reads and field reads, and
28+
* fail if the direct array_read assignment loses the snapshot refinement.
29+
*/
30+
31+
typedef struct {
32+
uint32_t x;
33+
} entry_t;
34+
35+
_include_pulse(
36+
$declare(entry_t e)
37+
38+
let entry_ok ($(e): $type(entry_t)) : prop =
39+
UInt32.v $(e.x) < 10
40+
41+
// Specification supplied by the caller: every initialized entry in the first
42+
// n array slots satisfies entry_ok. The proof obligations below are about
43+
// concrete locals read from entries[i], so they require PAL to expose the
44+
// connection between those locals and this ghost snapshot.
45+
let entries_ok (s: Seq.seq (option $type(entry_t))) (n: nat) : prop =
46+
forall i. i < n ==> Some? (Seq.index s i) ==>
47+
entry_ok (Some?.v (Seq.index s i))
48+
)
49+
50+
/*
51+
* Whole-struct read case.
52+
*
53+
* The assertion after the read intentionally mentions only the concrete local
54+
* entry. It does not restate the Seq.index expression; the proof relies on the
55+
* ordinary array_read assignment preserving enough information to connect entry
56+
* back to array_value_of entries.
57+
*/
58+
void read_struct_from_snapshot(
59+
_refine(this._length == 64) _array entry_t *entries,
60+
uint32_t count)
61+
_requires(count <= entries._length)
62+
_requires((bool) _inline_pulse(
63+
entries_ok (array_value_of $(entries)) (UInt32.v $(count))))
64+
{
65+
for (uint32_t i = 0; i < count; i = i + 1)
66+
_invariant(_live(i) && _live(count))
67+
_invariant(i <= count)
68+
_invariant(count <= entries._length)
69+
_invariant((bool) _inline_pulse(
70+
entries_ok (array_value_of $(entries)) (UInt32.v $(count))))
71+
{
72+
entry_t entry = entries[i];
73+
_ghost_stmt(assert pure (entry_ok $(entry)));
74+
}
75+
}
76+
77+
/*
78+
* Field-read case.
79+
*
80+
* C code often reads only a field of an array element, e.g. entries[i].x. The
81+
* same snapshot fact must be available after projection: the local x should be
82+
* known equal to the x field of the snapshot entry at i.
83+
*/
84+
void read_field_from_snapshot(
85+
_refine(this._length == 64) _array entry_t *entries,
86+
uint32_t count)
87+
_requires(count <= entries._length)
88+
_requires((bool) _inline_pulse(
89+
entries_ok (array_value_of $(entries)) (UInt32.v $(count))))
90+
{
91+
for (uint32_t i = 0; i < count; i = i + 1)
92+
_invariant(_live(i) && _live(count))
93+
_invariant(i <= count)
94+
_invariant(count <= entries._length)
95+
_invariant((bool) _inline_pulse(
96+
entries_ok (array_value_of $(entries)) (UInt32.v $(count))))
97+
{
98+
uint32_t x = entries[i].x;
99+
_ghost_stmt(assert pure (UInt32.v $(x) < 10));
100+
}
101+
}

0 commit comments

Comments
 (0)