Repository navigation
verify: Reach the fields and array elements of a record through a pointer - #50
Merged
Merged
Conversation
…aces checked against their own arrays.
SwayamInSync
force-pushed
the
fix/pointer-subobjects
branch
from
October 21, 2026 05:06
dd56f64 to
498aa56
Compare
This was referenced Oct 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Through a pointer, the verifier reached only a record's direct fields (
p->x,(*p).x). Every deeper place was an error:mainp->inner.y,p[k].xunsupported aggregate field base expressionr->buf[i],p->inner.arr[j]array-to-pointer decay of automatic storage is unsupported until lexical lifetime and escape effects are modeledSo a ring buffer, a fixed-capacity stack, or any record that keeps its storage inline could not be verified at all. The second message was also wrong about the cause:
r->bufis not automatic storage.Now each field and each fixed-array element of the object a pointer addresses is one heap cell, at any depth:
p->f,p->inner.arr[j],p->grid[i][j],(*p).items[k].v, andp[k].buf[i]for an element of avalid(p, n)extent. Code can read and write it, contracts andcppverify::modifiesfootprints can name it (cells and ranges such asp->buf[0 : 8]), a reference can bind to it, it can be passed to a reference parameter, and a spec can read it.Design
Each access is checked twice:
0 <= i < 8, kindbounds), as Frama-C's RTE plugin generatesindex_boundand as Verus requiresi < Nfor a[T; N]index.valid(p, n)extent, or one object), as before, which corresponds to Frama-C's\valid.Checking only one of them would miss errors. An object check alone (CompCert-style block validity) accepts
r->buf[8], because its address isr->head, inside*r; it also acceptsgrid[0][4], which isgrid[1][0]in memory. An index check alone says nothing aboutpitself.In the converter, the place builder for promoted locals becomes
objectPlace, which takes either root:pointeeRootPointerwalks the selection chain down top->,(*p)., orp[k]., andpointeePlaceuses that pointer as the base.Loads, stores, reference bindings (
lvaluePlace), and reference arguments all go throughobjectPlace. The new place carries the access condition that local arrays already had, so passivization, UB instrumentation, and every backend are unchanged.Two forms are rejected, with messages that name what is unsupported:
Ring copy = *r;)a record or array read as a whole through a pointer is unsupported; read its fieldsconst int *q = r->buf;)a pointer to the array member 'buf' is unsupported; index the member where it is used, as in 'buf[i]'Such a pointer would have to carry the member's extent, the way a Rust slice carries its length. That is the next 0.2 item: a decayed member array gets an origin whose object is the member, so the pointer's accesses are bounded by the array. A decayed global array or global record member is also named now (
the global array 'table' is unsupported,the array member 'buf' of the global 'g' is unsupported), instead of the automatic-storage message.Tests
suite/pointer_subobjects.cpp, three runs:modifiescells and ranges, a loop that clears a member array with a quantified invariant, a caller framed by that loop's function, a reference to an element, an element passed toint &, an element of avalid(p, n)extent, and a spec that reads elements.i = 8;i = -1;j = 4;i = j;peek's load isr + 0 + cast(load(r + 32)) * 4with the access condition0 <= head < 8.dynamic_loaded_values.cppnow expects the message that names the global array.Docs
push_fullcounterexample, a store that would land onsize.Checked
On
mainat 5165654, with #45 and #47:docs_examples.test,docs_coverage.test).main: every RUN line ofpointer_subobjects.cppfails, as does the updated check indynamic_loaded_values.cpp.mainrejects each function in the file with the automatic-storage message.main: 435 test sources, every function's status, reason, and qualifiers. Onlypointer_subobjects.cppdiffers, and no file's time changed (3531 s → 3506 s in total).*proot, or thep[k].root inside a selection;Rebased on #45 and #47
error(...), so they carry their location and caret, like every other conversion error.pointer_subobjects.cpp:N:18: error: decayed: ...).structs.rstpassesr->bufto a buffer parameter and shows the located error. The earlier example (const int *q = r->buf; return q[0];) also printed a second error, about readingq, that follows from the first; E2b removes such follow-on errors.limitations.rstReferences row keeps docs: Correct the public documentation the 0.2 audit found wrong #45's text and adds the fields and elements of a record a pointer addresses.