From fa9e61cba0d5dbbb18c32947b0af55c08d44ed63 Mon Sep 17 00:00:00 2001 From: Owen Carey <37121709+owenthcarey@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:19:45 -0700 Subject: [PATCH 1/6] docs: record recursive workflow contract design and validation Adds the accepted design for inferring reusable memory contracts over stateful, recursive C workflows, and the validation record covering its 103 implementation candidates. The record is kept deliberately complete, including the candidates that failed, the three false proofs found and corrected, and the first re-measurement of the corpus completion and cost gates. That measurement is a release blocker for the implementation and is documented as such: checked contract mode regressed far past the mandatory 1.10x cost gate, driven by a 6.6x growth in checked input-case requests, and one corpus project now exceeds its 600-second deadline. The design stays Accepted rather than Implemented, as its own acceptance section requires while those gates are unmet. --- .../0029-compositional-recursive-workflows.md | 1640 +++++++++++++ docs/rfcs/README.md | 1 + docs/validation-rfc0029.md | 2045 +++++++++++++++++ 3 files changed, 3686 insertions(+) create mode 100644 docs/rfcs/0029-compositional-recursive-workflows.md create mode 100644 docs/validation-rfc0029.md diff --git a/docs/rfcs/0029-compositional-recursive-workflows.md b/docs/rfcs/0029-compositional-recursive-workflows.md new file mode 100644 index 00000000..7090d294 --- /dev/null +++ b/docs/rfcs/0029-compositional-recursive-workflows.md @@ -0,0 +1,1640 @@ +# RFC 0029: Compositional invariants for recursive C workflows + +- **Status**: Accepted +- **Authors**: WeaveC authors +- **Created**: 2026-09-16 +- **Tracking issue**: TBD +- **Supersedes / superseded by**: Extends RFCs 0017–0028; supersedes RFC + 0026's exact two-counter discovery restriction and RFC 0027's direct, + single-parameter cleanup induction restriction where the rules below apply. + +## Summary + +Infer reusable memory contracts for stateful, recursive C workflows. Discover +buffer and cursor roles from operations, retain relationships between entry +values and current storage through helpers, and check recursive contract +candidates as a group. Express required synchronous callback behavior as a +caller obligation rather than treating an unavailable binding as a proof. +Use complete parsing, serialization and cleanup workflows to validate that +the existing ownership, initialization, buffer, callback and recursive models +compose across source files, compiler objects and validated checkpoints. + +The owner explicitly requested drafting this RFC followed by implementation +end to end. Accepted records that authorization; it does not claim independent +review, a separate RFC merge, or completed implementation. Any additional +semantic decision must be recorded here before the corresponding code changes. + +## Motivation + +At `bbf14c3`, the latest retained validation reports 150 complete conditional +contracts among 1,619 selected definitions. The fixed evaluation detects all +44 bugs and accepts all 32 clean programs. These denominators measure their +specific populations, not arbitrary-C soundness or recall. + +Three separate-source probes establish the practical gap. With unchanged +pinned cJSON and an explicit `cJSON_InitHooks(0)`, a closed client that creates +and deletes an object succeeds with zero entry requirements and only modeled +C-library trust. A client that parses a small JSON document and deletes the +result remains incomplete. So does a client that creates an object, prints it, +and releases the object and printed buffer. The retained RFC 0028 binary has +SHA-256 `47b881a29c1bb9b7ee547f144e72ef0342376d9a12a2d4ca72db6417156510a3`. + +Current buffer discovery requires exactly two unsigned counters; cJSON's +buffer records also contain depth. Recursive may-effect fixed points already +exist, but reusable recursive memory postconditions remain restricted. +Requirements projected through helpers frequently lose their interval or +entry identity. Concrete contexts cannot replace a reusable relational +contract and reach explicit bounds on large recursive programs. + +This milestone targets those compositional limitations. A recognized record +shape or familiar library name cannot certify an implementation. No cJSON +or other third-party function receives a handwritten trusted summary. + +## Soundness + +Retain RFC 0018's conditional, single-threaded source guarantee, target C +semantics, explicit trusted boundaries, and failure on unresolved selected +obligations. Required premises must be discharged by actual callers. A new +contract candidate, callback prototype, layout descriptor, capacity field or +recursive declaration contributes no independent evidence. + +The following distinctions are mandatory: + +- Accessible storage versus initialized contents and logical cursor position. +- Current pointer identity versus an entry pointer or a saved pre-growth alias. +- Unsigned C arithmetic versus unbounded mathematical interval endpoints. +- Successful output publication versus partial construction and failed calls. +- A supported finite ownership forest versus cyclic or shared owned children. +- An assumed recursive contract being checked versus a published verified one. +- A callback's demanded behavior versus the behavior established for every + target that can actually return. +- Permission to release an allocation versus proof of complete cleanup. + +Required rejected counterparts include truncated/uninitialized input, forged +capacity, cursor escape, overflowing growth, stale aliases after replacement, +false-success returns, failure cleanup that leaks or releases twice, skipped +recursive children, duplicate ownership, nondecreasing recursive proof cycles, +and callbacks that fail to establish required outputs. Every local access and +every relevant return remains an obligation, including error paths. + +Unknown mutation invalidates dependent facts. A field unchanged by a verified +callee may retain its value; an unmodeled write cannot. Joins retain only facts +established by every incoming alternative. Dropping a guard, interface path, +callback target or recursive premise loses the dependent proof as well. + +A current container predicate's initialized head selectors and empty head +links may answer the same value query in call-case capture and return-guard +checking. Require the exact head cell, a non-null live holder and the validated +target field type. These are values already established by the predicate, +not new facts inferred from a record's layout. A changed selector, replaced +holder, released object, invalidated predicate or nullable head supplies no +value. Queries do not install persistent scalar facts; ordinary call-entry +snapshots remain necessary when the call changes a guard operand. + +A represented store may retain zero-initialized byte intervals outside its +proved write range. Constant intervals split at the written boundaries; +an established half-open ordering may retain an entire disjoint symbolic +interval as well. Prove the write ends no later than the interval begins, or +begins no earlier than the interval ends, using the current unchanged values. +Unrepresented overlaps lose the evidence. Zero facts in a distinct concrete +automatic object or a distinct live allocation from this frame may survive +only with actual storage separation. Different indirect path names alone do +not establish separation. This permits attachment to one calloc-initialized +child slot without discarding the zero initialization of the other slots. + +A verified structural output for a current pointer also applies to its +definite zero-offset aliases that still denote the same actual head storage +and ownership share. Recheck those identities after the call's effects; +may aliases, interior offsets and replaced pointer cells supply no equality. +Install only the verified output descriptor, including its actual outcome, +and propagate a footprint equality only after that output's independent +conservation relation has been applied. This does not restore a pre-call +descriptor, a released allocation or an ancestor's larger forest. +Before a checked store into an established live non-null container head, +unfold its existing footprint while the old fields and ownership selector +still hold. This also applies when non-nullness and empty slots came from +entry case premises and no branch has yet been visited. The subsequent store +retires affected contributions normally; it cannot reconstruct entry ownership +using the newly written selector. +A completed local structural fold likewise updates every current definite +zero-offset alias with the same sharing identity. Those aliases denote the +proved live head even when a reused allocation site has retired its old +concrete storage name. Require each alias to remain unmoved and uninvalidated; +copy only the newly proved shape and conserved footprint, publish the aliases +as attached, and retain the fold's established separation. This rule cannot +restore a merely possible alias or an enclosing ancestor forest. +An enclosing forest instead needs its independently retained prefix frame: +the current node was an established suffix or proper child, the updated +subtree still entails the ancestor's node capability, and every newly attached +region is disjoint from that ancestor. Save the ancestor-minus-subtree +footprint before invalidation and compose it with the verified replacement. +Definite head aliases are updated as aliases, never restored with an old +prefix descriptor. Retain verified ancestor hints for the updated subtree +and all live aliases of its parent so rebinding one cursor cannot erase the +only surviving name of the relation. A newly attached child also inherits +each independently retained proper ancestor of its parent; subsequent +mutation of that child must preserve every such enclosing prefix, not only +the nearest cursor name. An enclosing prefix retains its prior separation +from a surviving neighbor only when every nonempty replacement child is +independently separated from that neighbor too. Reestablish this relation +after composing the prefix; restoring structure alone loses this evidence. +The same prefix composition applies across a complete helper with a verified +preserved or unconditionally extended footprint on the same root parameter. +Capture the actual proper ancestors and their ancestor-minus-subtree and head +footprints before effects. Every represented write or consumption must be +confined to the passed subtree; an additional write needs the existing +independent local/input/fresh separation frame for that entire ancestor. +Unknown destinations, escaping effects and consumption outside the subtree +lose the frame. The returned subtree must entail the ancestor's node +capability, and its conservation output must already have been applied. +Then recompose the captured prefix with that verified output, restore its +unchanged live head, and retain only separation from independently surviving +neighbors also separated from the returned subtree. A structural output alone, +a changed root path or an incomplete helper cannot restore an ancestor. + +Two complete concrete graph witnesses with no summarized suffix or incoming +region may prove separation by disjoint actual node and payload storage. +Every member must still be a live concrete object or an established current +local allocation; distinct allocation-site names for summarized instances +provide no such proof. + +Zero-byte evidence in distinct indirect objects may also survive a write under +an explicit separation premise on their unchanged entry identities. Project +that premise using the same immutable backing identities as checked memory +calls, and discharge it at actual callers. A different field spelling or a +replaced pointer alone never establishes this separation. + +An actual write to a private automatic scalar cell is separated from incoming +byte storage when that cell's address is never taken. Preserve the existing +content and zero facts of other storage across that write. Resolve the written +object itself, not a referent reached through a local pointer. Records, arrays, +static storage, exposed scalar cells, writes to the same object and unknown +write locations do not qualify for this frame rule. +An indirect pointer-cell read or update may resolve to its actual automatic +pointer variable when the referring pointer identifies exactly that whole +initialized cell. Validate ordinary compatible pointer types, zero cell offset, +full cell bounds and live storage; exclude volatile/atomic cells and ambiguous +storage. Use that variable's existing cursor for the value and update that +same cursor. The pointer variable's mutability authorizes its own cell only, +not the storage it points into. This rule preserves facts +already established by a scan; it introduces no new input contents. +An independently live unchanged entry byte object is also separate from this +invocation's automatic cells and established fresh allocations. Retain its +contents across a represented write confined to those objects, including a +local pointer cell whose address is later passed to a helper. Require the exact +current entry storage and live pointer; a changed, escaped, released or replaced +entry supplies no frame. For an allocation, actual freshness and current +validity must hold independently. Merely different indirect names and unknown +write locations supply no separation. + +A complete inferred helper whose every represented write is confined to this +invocation's actual automatic storage may preserve exact byte records in +different automatic objects or independently live unchanged entry storage. +Resolve each effect to its actual object, including addressed scalar cells; +all write roots must be separate. Unknown destinations, unmodeled alternatives, +consumption, replacement and escaping effects decline this frame. No callee +name, declaration or incomplete contract supplies it, and preserved bytes still +require independent validity and bounds when read. + +An ordinary integer cell in automatic storage may recover the value zero from +a current unconditional zero-byte interval covering its complete target +representation. Resolve record offsets and exact constant array selections +against the actual C layout and object extent. Partial coverage, ambiguous +selectors, bit-fields, volatile/atomic storage, stale writes and source-copy +ranges supply no such value. Derive this fact from the current byte evidence +when needed; a past `memset` alone does not establish a persistent scalar fact. + +A nonzero test of an ordinary byte read is infeasible when its live initialized +position is proved equal to the zero of a current termination witness. Check +that equality before learning a strict pre-terminator bound; an impossible +negative coordinate must not enter a loop join. Volatile/atomic reads, changed +contents, invalidated witnesses and merely initialized bytes cannot refute +the branch. The witness and all of its guards must already hold independently. + +Bounded exact byte contents may refine a scan of an actual initialized object. +An ordinary eight-bit character-array initializer can establish at most 64 +consecutive byte values in one initialized-range record. The same rule applies +to an ordinary const-qualified static local array: C requires its constant +initializer and prohibits modifying that object. Mutable static arrays do not +receive this premise, since a previous invocation may have changed them. +Volatile and atomic storage remain excluded. Its constant endpoints +must match the stored byte count. This is a content must-fact: overlapping or +unknown writes retire it, separated writes preserve it, and joins keep only +identical supported contents. Partial writes may retain exact unaffected slices. +Do not recover an initializer after mutation or use volatile/atomic reads. + +A byte read may query that record only after independent live-storage, bounds, +and initialization checks, with a represented finite interval of possible byte +positions wholly covered by the record. Evaluate comparisons in the actual +target integer type. The minimum and maximum matching positions bound the +selected edge; no matching byte makes it infeasible. Gaps between matching +positions remain possible in the affine abstraction. This neither assumes +input syntax nor grants permission to read outside the established interval. + +For an input case carrying actual bounded byte contents, a single-entry `while` +or `do` region may use the existing `MaxTraversalIterations` iteration partitions. +Counted `for` loops retain their existing bounded-range nomination. In a byte +context, a single `for` region with an ordinary character read controlling a +switch and a plain incremented integer index may use the same partitions when +its bound is expressed through an offset. This nominates input dispatch scans; +subsequent copy or replacement loops retain their existing nominations. Removing +the region's header backedges must +leave an acyclic region, and nested or irreducible regions are not nominated. +Each partition executes the ordinary CFG transfer and checked branch rules; +byte contents nominate extra precision but establish no loop exit or operation. +At the existing last partition, retain every further backedge and apply the +ordinary fixed-point join and widening. Never truncate execution or infer that +the loop terminates at the partition limit. This can retain the first actual +delimiter exit instead of joining it with later equal bytes. Existing context, +per-block visit and traversal bounds remain unchanged. + +A modeled `memcmp`, `strcmp` or `strncmp` may retain its result sign when +independently live initialized byte values establish it within the existing +64-byte bound. Compare bytes as unsigned characters, stop string comparisons +at the first zero, and honor a proved exact nonnegative count for bounded +comparisons. Literal bytes come only from the actual ordinary narrow literal +expression, never from a shared literal-storage identifier. A mismatch proves +only negative or positive, not a particular magnitude. Equality requires the +whole count or a shared terminator. Unknown, mutated, partially initialized, +volatile/atomic or inaccessible contents supply no result fact. Runtime access +obligations remain independently required and the existing library trust is +reported; this does not introduce a trusted user-function summary. + +Actual call contexts may carry these bytes relative to a byte-pointer input +whose current offset is exact. Capture only an entirely proved initialized +slice before call effects and install it under the callee's corresponding +entry storage. Count byte payloads against the existing 64-fact bound and input +paths against the existing path bound; decline excess rather than raise either +limit. Portable contexts encode payloads canonically as hexadecimal bytes and +reject malformed, oversized, duplicate or unmappable entries. Callback types, +library names, parameter names and merely const-qualified pointers supply no +contents. These are explicit context premises, not trusted library contracts; +ordinary object and checkpoint validation applies to every dependent result. +A complete conditional generic contract may still be specialized when actual +byte contents are captured: its sufficient generic requirements can cover +branches impossible for that input. Recheck the body with all captured premises; +never erase a generic requirement without a separately completed call case. +A byte-content entry denotes a live initialized slice of that length, as proved +at capture. The callee may use that minimum accessible length for checked +bounds, but it is not an exact physical allocation extent or write permission. + +An exact-byte record may additionally retain that its actual object is a +const-qualified ordinary character array. This requires the array declaration +itself, not a const-qualified pointer or cast, and does not cover allocated +storage. Its bytes may survive a represented write to another storage identity: +defined C cannot modify that constant object through a hidden alias. A write +to the represented object still retires overlapping contents, and an unknown +write retires the record. This frame neither proves a write legal nor supplies +liveness, bounds or release permission; all those obligations remain checked. +The same frame applies to a complete helper only when every represented write +destination resolves to an actual storage identity other than the constant +object. Unknown write destinations and incomplete or unsafe calls supply no +frame. Pointer replacement still retires its current referent evidence. +Joins retain the immutable premise only when every alternative establishes it. +Call contexts may carry it only beside captured exact bytes, encoded as a +canonical `k:` path record, counting each marker against the existing fact +budget. Missing byte premises, duplicate markers and failed path remapping +invalidate the context. Mutating or releasing a constant array remains rejected. + +General concurrent safety, tracing collectors, arbitrary shared graphs, +unrestricted nonlinear arithmetic, arbitrary type punning and nonlocal control +transfer remain outside the model. Supported C can still be conservatively +rejected when its invariant cannot be represented or established. An upstream +defect discovered during validation remains a rejection with a concrete +counterexample; acceptance must not suppress it to obtain a green workflow. + +## Detailed design + +### 1. Semantic discovery of state roles + +Replace exact record-field-count discovery with bounded candidates selected +from actual uses. Candidate data fields are non-function pointers to supported +fixed-size scalar or pointer elements. Candidate counters are represented +unsigned non-Boolean integer fields. Additional counters, flags, nested hooks +and unrelated fields do not by themselves disqualify the record. + +Collect evidence from indexing and pointer arithmetic, allocation extents, +guarded accesses, initialization/copy lengths, and assignments. Distinguish +accessible capacity, initialized logical length, and consumed/produced cursor +position. The same field may have different roles in different proposed +predicates; only a consistent proved predicate is published. Ambiguous usage +declines inference or retains bounded alternatives; field names and declaration +order must not authorize stronger semantics. + +Even a record with exactly two unsigned counters must have an evaluated +indexing or pointer-arithmetic use of its candidate backing field before the +legacy two-counter fallback is nominated. Merely dereferencing a scalar +payload beside two unrelated counters does not nominate a buffer predicate. +This filter supplies no new proof and does not alter established predicates. + +Keep immutable discovery within function/TU preparation. Candidate descriptors +are validated against Clang's actual target layout, and the flow-sensitive +facts remain in Core. Preserve the existing 16-descriptor and 64-instance +bounds. Candidate enumeration must be bounded before allocating a Cartesian +product. A limit remains explicit incomplete coverage. + +A forwarding helper may import a callee's validated layout candidate even +when that callee's generic contract is incomplete. This nominates roles only: +the helper must export the sufficient input predicate, and each actual caller +must establish it. An incomplete callee still supplies no guaranteed output +or complete-call proof. Layout import cannot strengthen a post-call state. +Likewise, a forwarded forest requirement may nominate an entry predicate for +a complete record type whose layout matches the transported descriptor. The +existing opaque-record route and complete-record route carry the same explicit +caller premise; neither a record declaration nor a forwarded call proves it. + +Establish a predicate from actual initialized fields, live storage, extents, +initialized intervals and permissions, or as an explicit sufficient entry +requirement. Requiring a predicate at entry is not permission to restore it +after a mutation. Existing buffer ownership and element ownership remain +independent of initialized bytes. + +### 2. Versioned relational inputs and outputs + +Use the existing immutable entry snapshots as the basis for relational +contracts. Factor the common capture, projection, substitution and invalidation +operations used by buffers, cursors and checked memory calls into shared +implementation helpers. Preserve separate may-effects and must-proofs; neither +may be substituted for the other. + +A relational predicate may describe a pointer's object and offset, an +accessible interval, an initialized interval, a terminated prefix, and +relations between represented scalar endpoints. Its portable interface paths +refer to entry snapshots or established output values explicitly. Mutable +field spellings do not become immutable values merely because they appear +in a summary. Output requirements continue to carry outcome and input guards. + +For a reader, relate `0 <= position <= initialized <= capacity` to its actual +backing storage. For a writer, separate the written prefix from unused +capacity, and require a real zero store when promising termination. A helper +may return a pointer at the old cursor and advance the cursor only after +proving its guarded extent. Read-only forwarding preserves identity and +premises through copied records and nested fields. + +Reader inputs may use the existing initialized-interval and extent requirements +without introducing a distinct predicate encoding. A changing field cursor +cannot be projected to its entry field spelling. Affine projection follows the +same numeric-write exclusion as typed integer-expression projection, including +equalities used to find alternative interface names. Existing relation envelopes +may then export a stable upper endpoint that covers every iteration. Failure to +find such an endpoint remains incomplete. This sufficient readable interval +supplies no write permission or ownership; a const-qualified field alone proves +neither initialized bytes nor accessible extent. + +A byte reader with a fixed backing pointer, initialized input length and a +changing cursor may use a distinct `reader1:` variant of the buffer descriptor. +The descriptor's length field is the cursor; its capacity field is the readable +input length. It requires `0 <= cursor <= capacity`, actual accessible storage, +and initialization of the entire `[0, capacity)` interval. It grants no backing +write permission. The first variant applies to const-qualified byte pointers; +constness nominates a candidate but establishes none of its premises. Reader +and writer descriptors cannot entail one another. Ownership and an actual zero +at the cursor remain separate capabilities. Every cursor or backing change +invalidates affected facts and every returning reader output must be proved +from the actual final state, including partially consumed failure returns. +The variant shares bounded layout validation, strict remapping and transport +with the existing descriptor; malformed or noncanonical encodings are rejected. +Reader nomination requires an indexed counter and an observed comparison with +the readable extent counter. A constant-offset read beside unrelated counters +does not nominate this predicate. A concrete exhausted or escaped cursor case +may decline the generic reader premise and check its actual early-return path; +an executed read still needs ordinary storage and initialization evidence. + +A zero-based unit-stride local index may traverse a stable reader with a strict +unsigned guard `position + index < capacity`. The established reader premise +gives `position <= capacity` at entry. Induction then proves +`index < capacity - position` in the body: the initial sum cannot wrap, and a +successful strict test leaves room for the next unit increment. Require the +index, sum and counters to have the same unsigned non-Boolean type. The index +must not be address-taken or changed in the body, and the body must not change +the reader, call unknown code or admit an entry through a label or outer switch. +Early exits, outgoing gotos and internal switches do not weaken this invariant. +Bound eligibility with the existing loop syntax budget; non-unit increments, +non-strict guards, narrowing conversions and unrepresented mutation decline +this rule. It provides arithmetic bounds only, never initialized bytes, writable +storage, an exit count or a promise that the loop processes all input. +The same induction establishes `index <= capacity - position` at the loop +condition and on every exit. Retain this weaker bound alongside the strict +body bound so ordinary CFG joins preserve it across exhaustion and early exits. +It does not establish equality to the available length or a produced count; +relationships to other counters still require actual scalar evidence. + +A comparison of an established same-array byte-pointer difference may refine +the corresponding byte coordinate. First prove live in-array/one-past operands, +the target `ptrdiff_t` range and every intervening integer conversion. The +initial projection covers a difference from a constant-offset array base and +a represented integer bound. Record the actual edge inequality even when a +particular iteration already satisfies it, so ordinary nonwrapping pointer +updates and CFG joins may preserve the relation. No difference with unknown +provenance, a potentially lossy conversion or an unproved range refines a path. +A branch comparing independently live in-bounds same-object pointers may be +refuted by the current mathematical coordinate relations. Check the opposite +inequality before learning the branch's relation; an impossible initial loop +exit must not overwrite an established bound. A shared spelling, unrelated +objects, invalid pointer formation or an unproved coordinate supplies no such +refutation. + +A loop's cursor boundary may be another unchanged pointer's represented +same-object offset, including an evaluated conditional value that is never +stepped. Both incoming states must independently establish the inequality and +agree on that pointer's storage and offset. Preserve only the established +boundary across widening; a changed holder, replaced storage, self-dependent +endpoint or unproved edge supplies no invariant. Existing candidate and +relational bounds still apply. + +An evaluated side-effect-free ordinary integer conditional expression may +retain the union of its target-typed arm ranges in an evaluation-site value. +It selects one value from that union; it does not assert which arm ran. Require +valid represented ranges and exclude volatile/atomic accesses or side effects. +Capture at the actual CFG expression evaluation, then use the saved value in +pointer offsets and integer expressions. Before reusing the site on another +iteration, snapshot or invalidate every dependent prior value exactly as for +existing numeric call results. Later condition changes cannot rewrite the +value used by an earlier pointer or allocation. + +The same independently validated ordinary byte-pointer subtraction may enter +the existing typed integer-expression domain. Require identical live storage, +proved in-array or one-past coordinates, a representable target difference, +and nonnegative coordinates individually representable in the target signed +difference type. Capture the actual coordinates, preserving subsequent C casts +and arithmetic rather than substituting the difference's range. An assignment +may retain an affine equality derived from that expression only when every +conversion and operation preserves its mathematical value. Ordinary dependency +snapshots freeze it on later writes; advancing an endpoint never resizes an +older allocation. Unknown provenance, element scaling, unproved coordinate +conversions or overflow decline the symbolic expression. +A saved evaluated cursor, including a postfix-increment result, may use the +writability of its exact current allocation when another live represented +holder still establishes that allocation's writable `free`-family origin. +Require actual storage identity, satisfied acquisition guards and a live, +unescaped holder. This confers write permission only; bounds, validity, +initialization and release ownership remain independent. A stale cursor, +changed object, declared-only resource or another allocation supplies no proof. +For an established byte-buffer predicate with a stable projected entry extent, +subtraction may +export the sufficient premise that the extent is at most target `PTRDIFF_MAX`. +Encode this with the existing unsigned 64-bit byte `sum-fits` requirement by +adding `UINT64_MAX - PTRDIFF_MAX`; callers must discharge it normally. Only +already established same-array, live, bounded positions use this premise. +It establishes a difference range, not array storage or an in-bounds cursor. +An established current buffer predicate may discharge a copied cursor's extent +requirement when both still identify the same backing storage and the access +fits that predicate's capacity. Do not export an unrelated entry interval +envelope merely because the copied pointer retains an entry spelling. Replaced +storage and lost buffer predicates supply no such discharge. +The capacity proves an accessible lower bound, not the allocation's exact +physical size. Materializing that predicate must not install capacity as an +exact ordinary spatial extent: an access beyond the advertised capacity still +needs independent bounds evidence, but is not thereby a demonstrated access +beyond the physical allocation. Preserve actual allocation and declaration +extents, and retain ordinary violations established from those extents. +A buffer whose backing is still its unchanged live entry object may export +additional extent and initialized-interval requirements on that same entry +pointer. These are explicit caller premises beyond the buffer predicate's +minimum guarantees. Require actual storage identity and absence of replacement +on every path. A retained separation identity after growth does not qualify; +ordinary unknown-call, release and pointer invalidation still apply. + +Checked loop widening applies the existing zero threshold to constant bounds +as well as differences between two variables. A growing positive upper bound +becomes unbounded; a growing nonpositive upper bound first weakens to zero. +The symmetric rule applies to decreasing lower bounds. Unchanged bounds and +acyclic joins retain their ordinary precision. This weakening contains every +incoming value and prevents an unbounded sequence of constant thresholds; +it introduces no assumed invariant and changes no work limit. +At a widening join, a current cursor's unchanged accessible endpoint or current +termination-witness endpoint may be retained as a canonical bound only when +both incoming states independently prove it. Recheck the represented cursor +after the join. This preserves a common endpoint when one edge has a tighter +temporary bound; a larger step that exceeds that endpoint fails the same proof. +The same join may preserve canonical non-strict and strict orders between two +current numeric cursor coordinates. Nominate only differences zero and minus +one and prove the chosen inequality on both incoming states before the join. +Restore it only while both represented cursors survive. This prevents widening +an exact initial distance from erasing a loop body's already-proved strict +guard. It neither assumes that guard at the loop header nor permits a larger +advance: a returning edge that violates the candidate removes it. Candidate +enumeration and difference queries retain their existing bounded limits. + +A helper receiving separate byte-pointer endpoints may require an explicit +`initialized-span` input. Its `path` is the first pointer and `other` is the +exclusive endpoint. Both must be live positions in the same byte array, +`first <= last`, their byte distance must be at most the positive constant in +`end` (the helper target's `PTRDIFF_MAX`), and every byte in `[first,last)` must +be initialized and accessible. `begin` is zero and `family` is empty. This is +an input requirement only; it grants neither write permission nor ownership. +Empty spans still require valid same-array positions. All ordinary path/global +remapping and strict decoding rules apply. + +Nominate a span only from evaluated subtraction of two distinct byte-pointer +parameters, allowing a bounded chain of unchanged local copies. A parameter +may belong to at most one nominated pair; ambiguous candidates are declined. +Both parameters and the local-copy chain must be unchanged and not +address-taken, volatile or atomic. Enumeration retains the existing 65,536 +syntax-node limit and at most 16 disjoint endpoint pairs. +Nomination installs the sufficient requirement once at function entry, with a +stable internal distance coordinate bounded by zero and target `PTRDIFF_MAX`. +It does not recover storage after mutation, replacement, release or an unknown +call. The endpoint bounds permit ordinary pointer-difference guards to refine +the distance, and accesses still require their actual intervals to fit. Callers +must discharge every part from actual storage evidence; pointer ordering, a +may-alias edge or a callback signature alone cannot discharge the requirement. +Forwarding is permitted only when an already established span covers the +actual argument interval; unknown endpoints remain unresolved. +An established span's two read-only endpoints do not require mutual separation +merely because a different parameter is written. Exclude that pair from the +generic separation premises only when neither endpoint's may-effects write, +consume or escape its referent. Preserve all other separation premises. The +span does not grant an ownership share or justify overlapping output writes. + +A complete integer-returning span helper may establish `count-within-span`: +its returned C integer is nonnegative and no greater than the byte distance +between the unchanged entry endpoints named by `path` and `other`. The record +requires a matching unconditional `initialized-span` input, has zero `begin` +and `end`, empty `family`, and no guard, outcome or non-null flag. It is an +output only. Prove it independently at every reachable return and intersect +the outputs normally; an invalid or unrepresented result establishes nothing. +This bounds a count only, without asserting that bytes were processed, written +or consumed. Capture caller endpoint coordinates before applying call effects, +and relate the actual captured integer result to their nonwrapping byte +difference. Released storage and changed pointer identities gain no memory +capability from this numeric fact. Checked decoding rejects a missing span +premise or any noncanonical reserved field; strict remapping must retain both +endpoints and the result relationship. +For a represented nonnegative byte step bounded by `last - current`, an +actual same-value relation between the captured and current coordinates may +prove `current + step <= last`. Constant endpoints are permitted. First prove +`current <= last` so unsigned subtraction has its mathematical meaning; retain +the actual C types and conversions. This is a bounded implication from the +captured count contract, not an assumption about arbitrary returned counts. +Before overwriting a cursor coordinate, retire expressions that depend on its +old value. Never equate the new coordinate to an expression that now refers to +itself. A separately proved bound within a stable extent may be retained on +the new coordinate, including the initial edge, so ordinary loop joins can +preserve it. Mutation and missing count or extent evidence lose the bound. +When installing a cursor that is already proved no later than an initialized +terminator, retain that actual stronger upper bound separately from the +allocation extent. The terminator must refer to the same unchanged storage, +and its coordinate must not depend on the overwritten cursor. The accessible +extent still permits forming one-past pointers, but that weaker bound must not +replace an independently established bound used by a terminated scan. + +A constant positive cursor step may use an already proved transitive constant +upper bound to establish that its unsigned coordinate addition cannot wrap. +The bound must fit the coordinate type after adding the actual step; a type +maximum or a missing range on a neighboring cursor supplies no substitute. +This preserves ordinary difference relations when scalar widening has retained +the relational bound but generalized that neighbor's standalone range. + +A constant cursor step may also retain an independently proved bound on its +actual updated coordinate against its unchanged physical extent. Prove that +bound before replacing the old coordinate, then carry it through the ordinary +update and joins. This canonical bound survives joins between different +proved step counts; it is not inferred from the mere existence of an extent. +An out-of-bounds step supplies no such fact, and an extent depending on the +updated coordinate cannot be reused as an unchanged bound. + +An evaluated comparison of a byte-pointer distance from an unchanged input +base against an unchanged unsigned input count may nominate an initialized +entry interval. Require a unique base/count pairing, target byte-pointer +compatibility and bounded syntax discovery. Publish explicit live-storage, +extent and initialized-byte requirements for `[0, count)`, plus the count's +`ptrdiff_t` representability bound. Actual callers must establish every premise. +The comparison alone establishes none of them, and no cursor provenance is +inferred from the nomination. Materialize the interval once at entry; ordinary +writes, replacement, release and joins still invalidate dependent facts. +Changed or exposed base/count cells and ambiguous pairings decline nomination. + +Represented local loop counters assigned the same exact C integer value may +seed a mathematical equality when their integer types agree. Nominate only a +bounded set of counter cells; nomination itself supplies no equality. Ordinary +assignment invalidation, proved nonwrapping adjustments and CFG joins must +preserve or discard that relation on each actual path. Similar increment syntax +alone does not prove counters equal, nor does it establish an exit count. +An actual exact assignment to such a counter may also seed matching lower and +upper constant bounds. This preserves the assigned value's numeric evidence +when a scalar join generalizes two distinct constants to an outcome class; +the ordinary relation updates and loop widening still apply. +The same bounded nomination may include a directly returned ordinary local +integer cell. For an established entry span, an actual exact nonnegative count +and a proved lower bound on the span distance may seed `count <= distance`. +Reconsider this implication after assignments and pointer-difference guards, +so either ordering of assignment and guard has the same evidence. Every join +must retain the relation on all alternatives and every later mutation must +update or invalidate it. A returned variable name alone supplies no bound. +Range queries may follow one counter equality to the other counter's represented +bounds, using a fixed one-hop query depth. This preserves nonwrapping adjustment +proofs such as `count == index < limit` without iterating a new relational +closure or treating modular equality as a mathematical offset. +The same numeric relation may connect represented byte coordinates in different +objects. Nominate only current cursor coordinates and independently prove each +candidate inequality on every incoming CFG edge before retaining it at a join. +The inequality may carry a finite constant displacement derived from each +edge's bounded difference constraints; retain the larger of the two proved +upper bounds, weakened to zero when both are negative. Avoid introducing a +temporary negative displacement merely because the initial loop iteration is +exact; ordinary guard refinements remain responsible for strict inequalities. +This preserves a helper's temporary output lead before the +input advance. Ordinary loop widening still removes an increasing bound; +neither a saved call input nor a candidate displacement is an assumption. +Two cursors initialized at offset zero may then retain equal offsets through +matched nonwrapping advances. This supplies no shared-object provenance and +never licenses pointer subtraction or ordering across distinct objects. An +initial displacement, unequal advance, changed backing, or missing coordinate +must still satisfy the ordinary per-edge proof and position-join rules. +Before an exact assignment resets a nominated counter, an unchanged nominated +counter related by a proved equality may retain its current target-typed range. +Compute that range from the pre-write state with the same one-hop query bound, +then retire the equality normally. Do not retain facts in any possibly written +alias cell. This preserves evidence about an unchanged count when an index is +reused; it supplies no equality to the reset value and no range on a later write. + +A sufficient access interval may use a constant upper envelope from the +current scalar range of its endpoint, when its upper bound is below the +declared type maximum (or the actual value is an exact constant) and its mathematical scaling and +displacement fit. Require a nonnegative access start as for other widened +interval requirements. This projects an actual value fact from initialization, +control flow and joins; a type maximum alone does not nominate a traversal +capacity. It supplies only a caller requirement for bounds, initialization or +writability and never a must-written interval or an output count. +The same rule applies to internal cursor coordinates with a represented target +integer range. Their synthetic identity does not require a C declaration; +the captured type and narrowed range remain mandatory evidence. +A proved comparison against a nonwrapping unsigned remaining length may refine +its byte endpoints even when one endpoint is an exact representable constant. +Prove the endpoints' order before using the comparison; the comparison cannot +justify its own no-wrap premise. Use the compared value's actual target range +and checked mathematical displacements. An endpoint conversion may be removed +only when it preserves every value of the operand's established target range. +When capturing a span endpoint held in an ordinary integer cell, retain that +cell's actual call-entry value and validated type. Its earlier initializer, +including a lossy conversion, need not be substituted for the stored value. +This retains a verified span count +when a successful helper result becomes constant, without granting storage, +initialization, or a larger step than the helper actually proves. + +The normal exit of a reverse unit-stride byte-writing loop may establish its +visited suffix. Require an ordinary local integer index initialized from a +stable represented nonnegative value, the strict test `index > 0`, a unit +decrement, and exactly one unconditional byte store to `base[index]` per +iteration. The resulting initialized interval is `[1, initial_index + 1)`; +index zero remains uninitialized unless separately written. Other assignments +may update unrelated ordinary local scalars, but cannot change the index, +initial-bound inputs or any pointer. Reject branches, early exits, nested +loops, calls, labels, volatile/atomic storage and exhausted syntax bounds. +Every actual write still needs ordinary bounds and write permission. A base +loaded from a pointer slot additionally requires separation of the slot from +the written bytes, so byte stores cannot change the next iteration's base. +No sufficient interval requirement alone supplies this must-write guarantee. + +A complete helper may establish `initialized-advance` for an output pointer +slot. `other` names the unchanged entry pointer, and `path` names its final +position. Every byte from that entry position to the actual final position is +initialized. Require a matching unconditional `position` output with the same +paths and an outcome that covers this guarantee. The final displacement must +be nonnegative and the entire prefix must be initialized at every applicable +return. The record has zero `begin` and `end`, empty `family`, no input guard +or non-null flag, and may have a return outcome. The initial representation +covers non-result output slots only. It is an output-only record and conveys +neither ownership, write permission, termination nor an exact produced count. +Capture the entry storage and coordinate before effects, then use the actual +installed final coordinate in that same storage. Never initialize the whole +upper envelope of possible positions. Missing position evidence, changed +storage, a missing outcome or unproved ordering discards the dependent fact. +Strict decoding and remapping preserve both paths and the matching position. +When every represented return outcome establishes a constant position interval +for the same output and entry paths, their enclosing interval may be published +unconditionally. Each outcome contributes its proved interval; an absent, +guarded or nonconstant alternative declines this generalization. This weakens +the displacement bounds only. It neither combines different storage identities +nor makes outcome-specific initialized bytes unconditional. +An actual target-typed lower bound on a cursor coordinate may similarly narrow +the exported interval's first endpoint, with checked mathematical scaling. +When an unconditional position supplies the final coordinate, a guarded +initialized-advance output may retain its return-outcome guard on that exact +interval. Testing the actual result then activates the initialization through +the existing guarded-range mechanism; changing or ignoring the result does not. +When an unconditional installed position has a stable internal coordinate, +constant outcome-specific bounds may refine that coordinate through the existing +pending-outcome integer facts. The actual result test must select the outcome; +different storage, missing unconditional positions or unrepresented bounds +supply no refinement. Pointer mutation retires the coordinate's pending facts +before a new coordinate value is installed. These are local call-frame facts, +not new portable arithmetic assumptions. + +The existing `terminated-within` interval may also be an explicit input +requirement. It requires live accessible bytes, an initialized prefix starting +at `begin`, and an actual zero somewhere before the exclusive `end` bound. +It does not require every byte up to `end` to be initialized. Both endpoints +refer to immutable entry values. The reserved `other` and `family` fields stay +empty/default, and input requirements have no return-outcome or result guards. +A caller must prove a witness inside that interval or a stronger established +bounded-termination fact. An ordinary unbounded string premise cannot satisfy +this requirement merely because a buffer also has a capacity field. + +A modeled `strlen` may use such an established bounded prefix. Its returned +length is the distance from the actual starting pointer to the first zero, +not necessarily to an arbitrary previously known zero. Introduce a bounded +flow-local first-zero identity between the start and a proved zero or exclusive +upper bound. The model establishes initialization through that first zero and +an actual zero there. A buffer cursor update can then reestablish its initialized +prefix and termination from those facts. When a generic buffer helper needs +this relationship, it may export the bounded-termination premise using the +unchanged backing, entry cursor and capacity. Unknown writes and pointer +replacement retire the dependent witnesses. No capacity spelling supplies a +terminator, and no uninitialized tail is silently initialized. + +The same first-zero construction applies to an ordinary modeled `strlen` +with an explicit initialized-termination input premise. Materialize that +entry witness only for the unchanged entry storage, recording the actual +guarded premise at the call. It supplies a readable prefix through its zero, +not an exact first-zero location. The measured first zero is independently +bounded by that witness; copying `strlen(input) + 1` may then use the proved +prefix. Unknown writes, replacement and release must still invalidate it; +an extra byte beyond the first zero receives no access permission. + +Target-unsigned expressions may normalize `a + (b - a)` to `b` when all +operands have the same target type and the removed evaluations are total. +This is a C modular identity, not an assumption that an intermediate operation +did not wrap. Invalid subexpressions, incompatible conversions and signed +operations retain their ordinary evaluation. Bounds on the final cursor still +need independent evidence about `b`. + +A side-effect-free conditional integer argument may use its actual target-typed +range as a captured call-entry value when its expression is not representable. +Both possible arms contribute after the actual argument conversions. The +captured identity serves conditional requirements and interval endpoints; +use the range as bounds, never substitute its maximum as an exact value. +The callee's guarded premise is assumed only while checking that implication. +Retire the preceding invocation's range and expression before recapture, and +preserve ordinary unknown results when neither expression nor range is valid. +This local fallback adds no portable arithmetic operation or inferred output. + +Capture all required inputs before effects. Invalidate facts affected by writes +and replacement, then install only independently verified outputs. Project +conditional release and reassigned parameters using their incoming identity; +`free(p); p = 0;` still consumes the entry allocation. A new allocation does +not discharge an old allocation's cleanup obligation or revive an alias. + +A side-effect-free pointer conditional return may describe each of its two +result alternatives under the condition's established true or false facts. +Reuse the ordinary branch refinement and output intersection rules; an +unknown alternative must still contribute its actual outputs. The initial +rule covers a single conditional with nonconditional arms. Side effects, +volatile accesses and nested conditionals retain conservative output handling. +This recovers the same pointer-position result as equivalent explicit return +statements without reevaluating a state-changing condition. + +Scalar must-values distinguish the actual cells reached by pointers. A may-alias +edge or an element-summary overlap authorizes invalidation only; installing a +value needs a definite same-cell identity. Proved disjoint byte intervals may +retain their values. Advancing a pointer retires its old pointee values without +changing values under aliases that still name their original cells. Apply the +same rule to direct stores and numeric outputs from verified calls. These rules +correct stale-value proofs under RFCs 0017 and 0021; they introduce no stronger +pointer provenance or type-punning permission. +A copied pointer may mirror pointee facts only for an exact zero displacement +or a represented field projection. Nonzero element offsets and unknown offsets +retain their ordinary alias effects but do not copy scalar values, nullness, +pointee ownership, spatial facts or nested equalities into a different cell. +This applies to initial pointer assignments as well as subsequent advances. + +An established scalar fact in this invocation's live concrete automatic storage +or independently fresh allocation may refute an indirect-read branch, provided +the current access is represented, in bounds and initialized. This does not +authorize branch pruning from unrelated generic input pointers. Unknown writes, +escapes, invalidated pointers and ambiguous cells retain their ordinary losses +of evidence. Numeric output projection must preserve the actual argument's +offset; an unrepresented interior argument cannot overwrite a base-cell value. +Concrete automatic integer arrays whose constant element count fits the existing +cell bound may use the existing exact scalar-cell selectors and initialization +rules. Unknown or overlapping indices invalidate affected values; byte writes +and callees still invalidate every possibly changed cell. This bounded layout +support does not unroll runtime loops or establish a fact about an arbitrary +element. Larger or unrepresented arrays keep their existing conservative route. + +Arithmetic retains target-width conversions and overflow checks. A relation +that only holds for mathematical addition cannot justify wrapping C addition. +Guarded floating-to-integer conversions encountered in numeric workflow code +require a separately established finite representable range; unsupported +floating predicates stay incomplete. This is not a full floating-point +functional-correctness analysis. + +The initial floating conversion proof is local and target-aware. A constant +operand must be finite and convertible to the actual target integer type with +rounding toward zero. For a scalar local or parameter, lexically dominating +true comparisons may establish finite lower and upper endpoints (or equality +to a finite constant). Conjunctions contribute both bounds; disjunctions and +false floating comparisons do not, since unordered NaN alternatives remain +possible. The scalar must be unchanged and have no taken address anywhere in +the function. Reject intervening floating conversions, volatile/atomic storage, +nonstandard floating modes and exhausted syntax bounds. LLVM's target floating +semantics and integer width validate each endpoint; host floating arithmetic +must not decide representability. No numerical return relation is inferred +merely because a conversion is safe. + +A local initialized byte interval may additionally prove that every byte is +one of the target execution characters `0` through `9`, `+`, `-`, `.`, `e`, +`E`, or the zero terminator. This is a content must-fact, independent of plain +initialization. It follows actual constant arrays, narrowed one-byte stores +and byte-preserving library copies. Joins intersect evidence; an overlapping +write removes the content fact unless the new bytes independently satisfy it. +A summary that promises only initialized output does not preserve contents. +The fact remains local and introduces no portable record or caller assumption. + +A bounded unit-stride reader scan may establish this content fact for its +already visited prefix. The existing zero-start, stable-reader and nonwrapping +index proof must hold. The loop body must enter a switch on the current byte; +every non-default label must be a numeric-alphabet constant and the default +must exit the loop before its increment. No memory writes, calls, index writes, +volatile accesses or alternate entries may change the scanned bytes or bypass +the test. Short-circuit condition operands retain the non-strict visited-prefix +invariant before their test; the strict current-index bound belongs only to +the body after the complete condition succeeds. A condition operand alone +does not prove that the complete condition holds. The default exit certifies +only bytes before the rejected current byte. The prefix remains attached to +the actual storage and offset; its end +uses the actual visited index, not an unrelated count or capacity. Later exact +counter equality may project that end through a byte copy. A same-storage +one-byte store of independently numeric text preserves the existing numeric +alphabet throughout its old interval, but does not preserve zeros or initialize +new bytes outside the proved write. Unknown replacement bytes lose this fact. + +For a live initialized terminated interval with this numeric alphabet, the +modeled numeric conversion boundary may exclude NaN while retaining infinity, +underflow and failed conversion. This uses the standard subject-sequence and +zero-on-no-conversion behavior, not successful parsing or finite output (see +[WG14's subject-sequence correction](https://www.open-std.org/jtc1/sc22/wg14/issues/c99/issue0225.html)). +The scalar fact is invalidated by writes and unknown effects and intersected +at joins; it is never inferred merely from the function name or a clamp. +When NaN is independently excluded at a floating-to-integer conversion, +dominating false comparisons may supply the complementary finite bounds. +Each bound still needs unchanged storage between its test and the conversion, +no alternate entry into the branch, standard target floating semantics, and +representable target integer endpoints. A preceding scalar assignment outside +those branches does not itself invalidate a later dominating bound. No floating +arithmetic result range or NaN exclusion is inferred through unsupported +operations. + +A preceding `if` whose true arm immediately returns and has no else arm may +supply its false comparison to a later conversion in the same compound block. +For this lexical early-return proof, require the floating scalar to be +unchanged and unexposed throughout the function and reject jumps, labels, +switches and assembly anywhere in the function. Only a direct return or a +compound containing exactly one return terminates the nominated arm. Thus +neither a mutation between guards nor an alternate entry can bypass the bound. +False comparisons still require independent exclusion of NaN. + +Bounded caller cases may carry an independently proved non-NaN property for +an ordinary by-value real-floating parameter. Capture the actual converted +argument at the call, and validate the parameter's floating type on entry. +Use the existing fact budget; encode a distinct `n` record for a root parameter +path, reject duplicate or conflicting pointer/integer premises, and retain it +in strict remapping and cache identity. It implies neither finiteness nor an +exact value. Assignment, address exposure, unsupported arithmetic and joins +retain the local non-NaN domain's existing invalidation rules. A callee may +forward the property only while it still holds; NaN and unknown alternatives +cannot satisfy it. Generic definitions remain incomplete when they require +this unproved floating premise. +Compiler builtins that the target evaluator proves to be floating constant +expressions without side effects need no external call contract. Their actual +constant may be infinite or NaN; constant evaluation alone does not supply a +finite conversion range or exclude NaN. + +The conversion routines' optional end-pointer store is guarded by the actual +output argument being non-null. A refuted store guard must also suppress that +store's source-escape effect, as required by RFC 0009. Passing null does not +export the input allocation through a nonexistent output slot; other stores, +unknown guards and escaping destinations keep their ordinary effects. + +The existing modeled C-library boundary may cover `strtod`, `strtof` and +`strtold` memory behavior. They require a live initialized terminated input. +A non-null end-pointer argument needs a writable pointer slot separated from +the input. The stored end pointer belongs to that same input object, between +its start and an established terminator, inclusive. A null slot is permitted. +Validate the actual C signature and library provenance; a user definition or +incompatible prototype receives no library evidence. This model supplies no +numerical return value, finite-range guarantee, or exclusion of NaN/infinity. + +A complete callee's copied-pointer store into a confined automatic pointer +slot does not itself escape the source allocation. This narrows RFC 0007's +conservative rule that every summary copy store escapes its source. The actual +output argument must directly address that local slot, with no other address +use in the function's bounded syntax scan; the callee may neither retain nor +consume the slot address. A modeled library contract or complete inferred +contract supplies the effects. Preserve ordinary may-aliases, release-family +and interior-offset restrictions, and subsequent mutation/unknown-call +invalidation. Other stores and escaping effects still retire ownership. +Unresolved, indirect, global, caller-owned and unconfined output destinations +retain the conservative escape rule. A local alias does not discharge cleanup: +losing all local holders without release still reports the allocation. + +For the supported Clang target, a pointer cell may be accessed through another +character-pointer type when the pointer representations have matching size, +alignment and address space. This narrow rule includes the `char **` view of +an `unsigned char *` end-pointer slot. Clang's +[pointer alias implementation](https://github.com/llvm/llvm-project/blob/main/clang/lib/CodeGen/CodeGenTBAA.cpp) +uses the same character base type for these views. It does not generalize to +unrelated pointee types or additional pointer nesting. Volatile/atomic views +remain unsupported; ordinary slot validity, initialization and write permission +still apply, including the original object's const qualification. A view +supplies neither pointee storage nor a numerical result from a parsing call. + +### 3. Recursive contract groups + +Use the existing call-graph strongly connected components to organize candidate +checked contracts. Candidates describe sufficient entry predicates and proposed +memory/ownership outputs, separately from ordinary recursive may-effects. +Do not install candidates as ordinary completed summaries or cached results. + +Check every body under the group's proposed interfaces. A recursive edge may +use a candidate only with established input premises and a supported progress +relation: a proper child of a finite ownership forest, or a strictly smaller +nonnegative remaining initialized input interval. Non-strict forwarding edges +are permitted only when every cycle contains a proved strict edge; removing +strict edges must leave an acyclic graph. Neither a syntactic recursive call +nor a scalar decrement without an established lower bound supplies progress. +The actual pointer must identify the exact node. Ownership identity resolution +that strips arithmetic cannot establish this premise; nonzero, unknown or +interior offsets do not become proper children or unchanged forwarding edges. + +Verify all base cases, local operations, returning outcomes and promised +outputs. Construction additionally accounts for newly acquired allocations and +all failure exits. Cleanup must conserve and consume the complete relevant +footprint. Traversal/serialization must preserve borrowed input ownership and +prove the output storage and initialized prefix. Calls on unrelated or +unproved child objects cannot use the induction hypothesis. + +Allocation accounting also covers ordinary byte allocations in functions that +handle forests. Returning a live free-compatible allocation base may transfer +that single allocation's proved footprint. This supplies no forest predicate, +child ownership or initialized contents. Returning an interior pointer, a stale +alias, null, or only the head of a larger owned forest does not discharge the +remaining allocations. +The same single-allocation transfer applies when a function directly returns +the fresh free-compatible result of a complete callee without a local holder. +The exit ledger must already relate this invocation's acquisitions to that one +returned allocation identity at offset zero. Its null alternative contributes +the empty footprint, and its non-null alternative transfers that allocation; +an opaque, borrowed, offset or non-free result transfers nothing. +A modeled nullable release may settle a represented ordinary allocation head +without first splitting the null and non-null paths. The current pointer must +be known null-or-live, unescaped, unreplaced and at the allocation base, and +its acquisition guard must hold whenever the pointer is non-null. The null +alternative contributes the empty footprint. This accounting supplies no new +release permission and cannot settle a stale or interior pointer. +A complete helper's unconditional free-family `allocation-consumed` output +settles the same represented allocation head under the same conditions. The +helper's own release requirement is still discharged at the call; a guarded +or outcome-specific consumption output settles nothing here. +Publishing an ordinary payload into an established container may use a local +alias of a fresh allocation, including a proved allocating helper's result. +Allocation conservation through a visible output must survive summary joining. +For each returning path that needs an output to settle a local acquisition, +retain the bounded alternatives of output paths that account for its ledger. +After joining all returns, require at least one complete alternative to retain +portable footprint guarantees applicable to that outcome. A structural fact +alone is insufficient. Fresh output fields must use a supported caller-side +acquisition carrier (the existing single success-published, null-on-failure +slot); a fresh result or verified in-place extension uses its existing carrier. +Losing a path-specific fresh output at a void-return join leaves conservation +incomplete, even if each return separately exposed some reachable allocation. +Payload fields may be nominated from actual local release operations or from +imported container descriptors on functions taking the same record. Revalidate +the local field type and layout; nomination alone establishes no ownership, +initialization, nullness or release permission. Imported nominations retain +their summary dependencies and are invalidated with the database generation. +Import the corresponding conditional-ownership selectors as nominations too, +checking their descriptors against the local target layout. Conflicting local +and imported selector candidates cannot establish an ownership condition. +Before capturing a call case, an independently proved concrete container may +supply its actual empty links and payload slots. Capture those null values +under the existing context bounds. A specialized input predicate may retain +them as explicit premises. Publishing a new owned payload may strengthen an +unchanged input predicate to require release ownership; this is an additional +caller obligation, never ownership inferred from a write or pointer type. +A context that establishes every recursive link and owned payload slot null +may nominate that complete singleton input for a forwarding helper without +direct release operations too. Cleanup retains its general owned input. Keep +it as an explicit input descriptor, so a verified extension can cross helper +boundaries without treating the incoming head as newly allocated. Partial +empty-slot information does not nominate an unchanged singleton footprint. +Such a complete singleton consists of exactly its head object. Two incoming +singleton heads related by an explicit distinct-object case premise therefore +have separated container footprints at entry. A head with a possibly non-null +link or owned payload keeps the ordinary explicit container-separation +premise; pointer inequality or different parameter names prove nothing here. +A helper with a store into a nominated recursive link may likewise nominate +release ownership when its caller case establishes the complete singleton +input. Require that owned predicate on every return, including allocation +failure, before using it in extension conservation. The generic non-singleton +contract keeps its existing access requirement; no pointer write establishes +ownership of a caller object. +A helper that stores a possibly non-null pointer into a nominated owned payload +may nominate that release-capable input for its whole body. Its failure paths +then preserve the same explicitly required ownership as its success paths. +Install this nominated premise before checking returning outcomes; CFG block +visitation order must not determine whether an early return retains it. +Current ordinary null facts may refine an established live head's empty-link +and empty-payload descriptors when discharging a call requirement. They must +refer to the exact current cells; a stale null fact or an invalidated head +cannot supply this refinement. +Require the actual live non-null allocation base, compatible release family, +an unescaped current acquisition, its represented head footprint, and separation +from every allocation already in the established container. Preserve the full +acquisition ledger; a copied field name alone supplies no ownership. Interior, +released, merely borrowed, duplicated, escaped or unaccounted allocations do not +fold into the parent. Failure paths must release or retain every acquisition. +A payload relocation may temporarily leave two slots naming one allocation. +Such a state supplies no complete forest. After clearing the old slot, a +concrete graph may be established again only by checking every current link, +payload, live allocation base and ownership condition independently. Retain the +original acquisition ledger throughout; re-establishing structure cannot erase +an overwritten allocation or excuse an uncleared duplicate or stale pointer. +For two adjacent pure stores in one CFG block, an already established owned +forest can also prove `head->destination = head->source; head->source = 0`. +Both accesses must use the same unchanged pointer variable and compatible +payload field types and release families. Require an actually empty destination, +active ownership for both slots, and a live complete input forest. Snapshot +the whole footprint and source payload before the copy; after the clear, the +same allocations occur exactly once under the destination slot. Retain the +original acquisition ledger and only unchanged definite aliases and separation +facts. The intermediate duplicated state establishes no forest. Any intervening +operation, control-flow edge, non-null clear, changed holder, unknown ownership +condition or failed ordinary memory obligation prevents this transfer. The +snapshot is local to that block execution and never survives a join or call. + +Modeled C-library byte/string comparisons preserve established container +predicates because their represented behavior only reads memory. Require a +recognized builtin comparison and a compatible C signature; all ordinary +argument validity, bounds and initialization obligations still apply. An +unavailable function, body-defined replacement or incompatible signature has +no such frame, and a failed read cannot make the selected contract complete. +The existing modeled floating parser also preserves an incoming forest when +its end-pointer output is null, or writes only to an actual automatic pointer +cell separate from that forest. Resolve the output object's identity and apply +the existing local-write frame; a cell within the forest, an unknown output or +an attached local member supplies no frame. Retain every input-string, output +bounds, writability and separation obligation. The modeled scalar math family +does not mutate a forest either; any floating result still obeys its existing +NaN, infinity and conversion rules. +A modeled release of a fresh local allocation may preserve an unchanged +incoming forest whose every represented member still has entry provenance. +Capture this frame before release invalidates the allocation's pointer. Require +an unescaped live allocation base with the matching release family and actual +local allocation identity; a borrowed or interior pointer supplies no frame. +Retire any forest containing local or unrepresented members, and retain the +ordinary release checks and complete acquisition ledger. This rule cannot +restore the released allocation, an attached payload or a stale alias. + +For a modeled positive-size reallocation, snapshot the old allocation's head +footprint before effects. A successful result acquires its distinct new +allocation and releases precisely that old head; a null result acquires +nothing and leaves the old allocation outstanding. Retain the conditional +release in the flow state until the result is established non-null. Joins keep +only identical pending evidence, and replacing its result holder or losing its +storage identity retires it. This accounting does not restore old aliases, +transfer owned children, or prove a size, release permission or library call. +Use the existing footprint bounds and keep unresolved outcomes conservative. + +Publish the group's contracts only after every participating candidate and +every recursive edge verifies. Failed or exhausted candidates retain explicit +unresolved obligations and publish no dependent outputs. Dependency changes +invalidate the entire affected proof group. Verified recursive contracts then +compose through the existing summary, callback and cross-unit machinery. + +An independently rechecked input case retains its own exhaustion state. A +generic recursive summary's fixed-point limit is not an executed operation in +a case whose CFG proves the recursive branch unreachable. Such a case may +complete only from its own checked operations and complete callees; using an +exhausted generic approximation, reaching an unavailable recursive case, or +exhausting the case's own budget still leaves it incomplete. Selecting the +generic definition continues to report its original limit. This follows the +case-local proof rule of RFC 0025 and does not raise any analysis budget. +An executed edge within the syntactic recursive group may use a separately +completed input case whose actual path reaches a base case. Merely calling a +group member does not prove a cycle occurred. Falling back to the group's +generic approximation still retains the exhaustion marker, even when an +intermediate optimistic approximation has no local failed obligation. +Case capture may follow an exact pointer to a live initialized scalar object +of the same C type to obtain its current value. Unknown offsets, partial +objects, incompatible views and invalidated storage supply no scalar premise. +Stateful helpers may nominate entry selectors forwarded from their callees as +well as selectors read locally. Mutating other cells does not prohibit entry +case capture; actual writes and aliasing still invalidate dependent facts +during the case's ordinary CFG analysis. + +Bounded case nomination may decline an unverified recursive target when its +only scalar refinements concern mutable output cells or non-exact ranges. +An exact scalar or null input not overwritten by the target's may-effects can +nominate a case; pointer alias and ordering cases retain their existing route. +By-value scalar inputs remain eligible even when the private copy changes. +This filter saves context slots for informative input cases and contributes no +proof: admitted cases still check every operation, and declined cases retain +the original contract with its actual completeness. Apply the nomination filter +also during the ordinary fixed point's optimistic initial rounds; otherwise +uninformative requests can consume all slots before iteration settles. A +completed generic contract can be used directly without spending a case slot. +Verified induction groups retain their established contextual route. The filter +cannot evict completed cases, reset a +budget, or increase the existing context count or nesting bounds. +If an attempted input case is incomplete or unavailable, a complete generic +contract may still be used with all of its original caller obligations. No +outputs or diagnostics from the abandoned case are installed, and the generic +contract's stronger input premises must be discharged normally. This fallback +does not complete, cache as complete, or erase the incomplete case itself. +When the same target already has an active input case, a recursive request +whose bindings differ only in facts about overwritten or replaced cells may +also decline nomination. Compare a temporary projection for this scheduling +decision only; admitted cases retain every original premise and guard. The +existing generic contract remains the fallback, with its actual completeness. +No projected context is analyzed or published as if it were the actual input. + +During a recursive component's generic may-effect fixed point, postpone +scalar-only checked case requests. Its provisional output state and selector +inventory must not consume the permanent case budget before the component's +effects settle. Actual alias and ordering contexts retain their existing route; +postponed calls keep the generic approximation and explicit incompleteness. +After the component settles, ordinary callers may request their actual cases. +This is a scheduling rule, not a recursive proof or a budget reset. + +When that may-effect iteration has actually converged, the existing final body +pass may retain ordinary null/non-null pointer outcome facts and integer return +values re-established by that pass. Earlier less precise approximations are not additional executable +return paths. Check every applicable return against the settled conservative +callee effects, preserve all input requirements and ordinary may-effects, and +replace only these pointer-value outcome maps and the numeric output at the +return-value root. Retain the actual guards and outcome restrictions on each +numeric alternative; an unrepresented alternative remains unknown. Numeric +writes to caller memory keep their existing widening. This can exclude a +spurious negative success result only when every current returning alternative +establishes a nonnegative result. The final pass must run against +the current dependencies rather than reuse a pre-finalization result. An +exhausted component cannot use this refinement. This adds no fixed-point round +or analysis bound. It establishes no checked memory output, recursion progress, +allocation accounting, initialization or complete-call guarantee; those still +require their independent proofs and existing group verification. +When an outcome test selects sign classes, intersect its pending classes with +independently represented facts about that same tested value. Follow RFC 0017's +existing trust boundary for direct call results, private scalars and contextual +values, and prove any intervening conversion preserves the tested relation. +Do not project facts from a stale mutable heap cell or from a lossy conversion. + +When an unverified recursive interface has a represented read-only record +input as well as mutable output state, nominate scalar cases from exact facts +about the read-only input. Constants describing only the mutable state cannot +stand in for its missing input selector. Identify read-only inputs from actual +read effects and absence of writes, replacement or consumption under that +parameter; C constness alone is insufficient. Interfaces without such a +read-only record input keep the existing nomination rule. Every admitted case +retains all of its actual premises, including mutable-state facts. + +When a direct call supplies both represented callback bindings and a captured +memory/scalar case, check their combined entry context directly. An intermediate +callback-only analysis need not first explore all unknown selector alternatives +and populate unrelated nested cases. Capture the combined premises from the +same pre-call state and generic selector inventory, retain every actual callback +alternative, and use the existing dependency and publication machinery. Calls +without a represented memory case keep callback-only checking. A declined or +incomplete combined case supplies no proof and does not increase either budget. + +The initial construction interface takes an immutable byte input and an +unsigned remaining count and returns a nullable fresh initialized forest. +Its explicit sufficient entry premise is live input with the entire remaining +interval initialized. Recursive calls must stay inside that entry interval; +progress compares against an immutable entry count, including when the count +is mutated through an alias. Complete external helper contracts may compose +through ordinary call transfer, including cleanup of a partially constructed +tree. Every helper requirement must follow from the candidate input premise, +every promised output must hold on the corresponding return, and all acquired +allocations must be accounted for on success and failure. The candidate grants +no hidden writes, captures or releases of caller inputs. Private proof members +cannot publish nested callback or memory specializations during verification. +Broader record-reader and output-parameter interfaces remain part of this +milestone's required workflow goals; this initial rule does not replace them. + +An output-slot construction interface may instead take the same immutable byte +input and unsigned remaining count followed by a pointer to a node-pointer slot, +and return an integer success indication. Its candidate requires a live writable +slot separated from the input. Positive returns must publish a non-null fresh +initialized forest; zero returns must actually leave the slot null. Negative +returns, unchanged failure slots and other stores are not covered by this +candidate. Validate actual final null facts on every failure exit, not the +ordinary may-store summary's weaker null-or-no-store information. The slot's +previous contents confer no ownership or initialization premise. Recursive +calls use the same interval progress proof, and all allocations remain subject +to the ordinary failure-exit ledger. This adds no portable contract kind. + +A copied reader may package the immutable byte input and remaining count in a +record passed by value. Nominate the byte pointer from evaluated byte accesses +and its unsigned remaining counter from decrementing operations, requiring a +unique bounded candidate across the group. Unrelated fields contribute no +premises. The proposed contract still requires the complete initialized entry +interval, and progress uses immutable entry snapshots of both field values. +Local changes to the copied record do not change the caller's record. Every +recursive actual must name a subinterval of the same entry storage with a +nonnegative smaller remaining length; decrement syntax alone proves nothing. +This rule uses existing field interface paths and fresh return contracts. +Mutable reader pointers and partial consumption require their own established +relational outputs; passing a record by value does not certify those interfaces. + +An in-place constructor may instead extend a live owned initialized head. +Its initial supported input predicate has no owned successors or payloads: +the existing terminal flag covers every recursive link, and every payload +slot is explicitly null. A private candidate for `(node *, unsigned count)` +requires that predicate and a non-null head. Recursive calls must decrease +the immutable entry count on every group cycle. The head remains the same +live allocation on every return, including allocation failures. Every newly +allocated descendant must be initialized and uniquely attached or released; +all return paths retain the ordinary acquisition/release ledger. Merely +returning success supplies no ownership evidence. + +Transport this relation as the output-only `container-extended` record. Its +`path` is the final forest and `other` is the unchanged entry head, with zero +`begin` and `end`, an owned release-capable descriptor in `family`, no +non-null flag, and no outcome or input guard. Require a matching unconditional +owned singleton-head input predicate. It promises that the final footprint +is the disjoint union of that complete entry allocation and a possibly empty +fresh region acquired by this call. It does not call the original head fresh. +Prove actual unchanged live head identity, complete output structure, exclusive +entry provenance and allocation conservation before publishing it. A replaced +or released head, another input region, a borrowed new member, or an unresolved +output provides no such relation. At a complete call, capture the actual entry +footprint before mutation, add the fresh region to the caller's acquisition +ledger once, and establish the final union on every returning outcome. +Do not settle ownership merely from the callee's may-store effects. Preserve +this record through strict remapping, joins, reports, sidecars and checkpoints; +missing singleton premises or incompatible layouts invalidate the record. +If the same applicable output also preserves exactly its input footprint, +its extension region is empty. Prefer that preservation relation when applying +the call, rather than introducing an unrelated fresh region for the redundant +extension guarantee. +For this initial encoding, both paths name the same root pointer parameter. +The unchanged head retains its live storage identity through the call. A +forest proved separate from that head before the call remains separate from +its extension only if its own complete fact survives the call unchanged: +the added region is fresh, and the original head was already separate. +Invalidated or replaced neighboring facts supply no such frame. +A verified derived output has the same separation frame when every named +incoming region is separately proved disjoint from the neighboring forest. +Capture those facts before the call and require the complete neighbor fact to +survive unchanged. All other members of a derived output are fresh to this +invocation; an ordinary structural output without verified provenance supplies +no frame. Checking just one of several incoming regions is insufficient. +A verified fresh output is disjoint from each live forest established before +the invocation whose complete fact survives the call unchanged. This remains +true when a loop revisits the same allocation site; a site identifier alone +is neither a new allocation identity nor evidence of aliasing with the older +instance. Capture the surviving candidates before applying any output, exclude +every destination of this invocation's outputs, and exclude definite aliases +of the newly installed result. Multiple fresh outputs from one invocation do +not become mutually disjoint without an independent separation guarantee. +Replacing a temporary pointer may retire ancestor or tail names from a +surviving forest without changing the forest itself. Such loss of relational +hints may be ignored when comparing these snapshots; require identical shape, +membership, incoming provenance, allocation capability and release state. +Do not ignore a changed descriptor, region membership or any newly introduced +relation to another forest. +The extension descriptor may retain head selectors and empty links or payloads +proved on every applicable return. Joining extensions of the same unchanged +head intersects those facts using the container must-domain; incompatible +layouts or capabilities lose the relation. Every outcome must still prove the +extension, even when their stronger structural descriptors differ. A caller +therefore retains a slot known empty on both success and allocation failure, +without assuming that a possibly populated neighboring slot is empty. +An attaching helper may also publish a payload acquired by the same call. +`container-combined` may therefore carry the constant `end` value one. The +final forest at `path` is then the disjoint union of the two complete entry +footprints named by `other` and `begin` and a possibly empty fresh region +acquired by this invocation. Both inputs need owned release-capable +predicates, an explicit container-separation premise and a live `other` head +premise. The output head must be that unchanged live entry head, every other +member must come from one of the two inputs or from this invocation, and the +exit ledger must equal exactly that union. At a complete call, capture both +entry footprints before effects and add the fresh region to the caller's +acquisition ledger once. The region is empty on every outcome the output does +not cover, so an established other outcome constrains it to the empty +footprint; an untested result leaves the caller's ledger unsettled. The value +zero keeps its exact two-input meaning and every other `end` value is +rejected. Joins, remapping, reports, sidecars and checkpoints keep the field. + +A function whose return expression is exactly a complete call's own integer +result may likewise install that call's outcome-specific structural and +footprint outputs while checking each of its own returning outcomes. Require +the returned value to be that call's actual result identity, with no +intervening evaluation, and check every ordinary obligation of the outcome +under those installed outputs. This forwards an already verified guarantee; +it neither assumes an outcome nor republishes consumption or effects. + +When a more specific verified output then states that the same path's final +footprint is exactly its incoming regions, the call's fresh region for that +path is empty. Both are outputs of the same checked contract, so retiring the +region keeps the caller's acquisition ledger consistent; it establishes no +allocation and never revives one the callee actually released. + +A verified ownership transfer of a complete call may be installed on the CFG +edge of an immediate test of that call's own result, as for the output-slot +constructor. The call must be the tested expression with no intervening +evaluation, and the selected outcome must imply the transfer's outcome. Only +that transfer's own structural outputs, on its path and the paths below it, +accompany it: replaying an unrelated outcome-specific output could replace +current caller evidence with a weaker captured entry description. An output already installed at the call because its outcome was then +established is not installed twice; unconditional outputs, consumption and +other effects are never replayed. A saved or later-tested result still needs +separately invalidated pending evidence. + +A recursive extension candidate accepts an inferred output with the same +paths, guards and footprint relation when its structural descriptor entails +the candidate's descriptor. Additional proved head facts do not invalidate +that weaker candidate; incompatible structure supplies no proof. +The same must-join applies to ordinary, fresh and input-derived structural +outputs. Join only matching output paths, capabilities and guards, intersecting +head-selector and empty-slot facts. Derived outputs retain the union of every +required input source under the existing three-source bound. A structural +output common to every returning outcome may be published without an outcome +guard, even when stronger per-outcome descriptors differ. Preserve separate +footprint conservation obligations: structure alone does not account for an +input allocation or a newly acquired region. A missing outcome, changed +layout, incompatible ownership condition or lost source premise drops the +dependent output rather than generalizing it into an ownership proof. +A represented selector store may retain an untouched payload when the prior +head predicate proves the selector bits and the new value selects the same +ownership branch. Require the prior complete payload capability; changing +borrowed storage into owned storage still requires independent evidence. +Across a helper that may write an ownership selector, retire the current +conditional footprint names for every possibly affected forest before applying +its outputs. Immutable entry snapshots retain their old allocations, but stale +conditional contributions cannot reconnect a new structure to an old ledger. +Only separately verified conservation outputs may restore that equality. + +A mutable pointer-reader constructor may use the same discovered byte interval +and fresh-forest result without promising any reader outputs. Its additional +entry premises require a live initialized writable record, separated from the +readable bytes. Only the data and remaining-count cells may be changed; those +effects invalidate their caller facts on every returning outcome. Recursive +progress is checked against the original input interval before applying these +effects. Each body must prove its complete fresh output and failure cleanup +without using any unstated post-call cursor or count fact. Clients that need +partial-consumption bounds still require independently established outputs. +An unchanged live entry object is separate from allocations acquired during +this invocation. Represented writes confined to that object may preserve a +forest proved wholly fresh in this invocation; they may not preserve an input +forest, an escaped or replaced entry identity, or facts reached through a +changed pointer cell. + +Conversely, a represented write into this invocation's automatic storage +cannot alter a forest whose entire current membership still comes from live +entry witnesses. Preserve only such unchanged entry forests across direct +stores and modeled byte writes to that automatic object. A forest containing +an attached local node, an unknown call region, or a replaced holder receives +no such frame. Unknown pointer destinations and writes through an input root +or any descendant retain ordinary invalidation. The write's own bounds, +initialization and permission obligations remain independent. +That automatic-storage write likewise cannot alter a forest with no incoming +region whose every node was acquired as a free-compatible allocation during +this invocation: an automatic object and a heap allocation are distinct +objects. Holder names reached through the written object are still retired, +and a forest with an incoming region, an attached automatic or static node or +an unknown callee region keeps the rules above. For the same reason an +allocation identity acquired by this invocation stays separated from this +invocation's automatic objects after attachment retires its resource record. +Pointer validity, bounds and initialization remain independent obligations. +The same entry-only frame applies to writes confined to an independently +established fresh allocation from this invocation, and to complete callees +whose represented effects identify only these separate objects. Check every +effect; unknown destinations, consumption and mutation of a potentially +overlapping input still retire the forest. This rule establishes neither +freshness nor callee completeness from an allocation-site spelling. + +A forest containing an unchanged singleton entry head and allocations acquired +in this invocation may survive a represented store to a separate unchanged +entry object. Require the singleton input descriptor for every incoming member +and an explicit object-separation premise between each such head and the written +entry object. Every other member must already be proved fresh in this invocation; +an unknown callee region or an incoming descendant supplies no frame. The same +rule applies to a complete helper's represented stores. A cursor that actually +aliases the head or its ownership selector cannot discharge the separation +premise. This rule preserves an existing forest and acquisition ledger; it +establishes no allocation, initialization or ownership by itself. + +Verified fresh and derived helper outputs retain their allocation provenance +when composed into that mixed forest. A derived predicate already establishes +that every non-fresh member belongs to one of its explicit incoming regions; +require a separated singleton descriptor for each such region. Synthetic +call-region identifiers need not enumerate fresh descendants. An ordinary +structural output without this provenance, an unrepresented input, or an +incoming non-singleton still receives no frame. Footprint conservation remains +an independent obligation, including failed construction paths. + +Bound candidate discovery and validation using the existing function/context +budgets. Deduplicate equivalent candidates and cache immutable preparation. + +A byte-interval writer may take an immutable byte pointer, an unsigned remaining +count, and a pointer to a discovered byte-buffer record, returning an integer. +Its private candidate requires the initialized input interval, the buffer's +actual capacity and initialized prefix, writable header and backing storage, +and separation of the input, header and backing. Only the logical-length cell +and backing bytes may change. Every returning outcome must establish the buffer +predicate anew, including failures after a partial write. Input progress uses +the same immutable interval snapshots as construction. A buffer output alone +promises neither that all input was consumed nor a particular output length; +clients must check the established current length before reading the prefix. +Returning a success value without writing bytes cannot authorize an advertised +longer initialized prefix. Stronger serialization length and termination claims +remain separate obligations. This interface reuses the existing buffer and +interval encodings and introduces no trusted recursive body. + +The initial cleanup group limit is 32 members and 256 proof edges; the bounded +syntactic eligibility scan visits at most 65,536 statements per member. +Do not increase analysis bounds or use bounded execution as proof of arbitrary +runtime input length. Direct cleanup uses this same machinery once migrated; +remove superseded special-case proof paths after equivalence is tested. + +### 4. Synchronous callback requirements + +Allow supported generic helpers to export a requirement on the behavior of a +callback input or represented callback field. The initial vocabulary covers +allocation of a requested extent, release of an input allocation with the +matching family, and represented synchronous memory input/output contracts. +Requirements include the relevant argument/result paths, null alternatives, +guards and side effects. A function type alone does not satisfy them. + +The initial portable allocation/release demands are input-only checked +requirements `callback-allocate` and `callback-release`. Their `path` identifies +an immutable entry callback, `family` is `free`, and `other`, `begin` and `end` +are the reserved zero/default values. Outcome guards are prohibited; input +guards have their ordinary meaning. The callback itself must be non-null. +Allocation demands a synchronous `void *(size_t)` operation that returns null +or a fresh free-compatible allocation of the requested extent, with no other +observable writes, captures or releases. Release demands a synchronous +`void (void *)` operation accepting null and otherwise consuming precisely the +argument's free-compatible allocation. An unknown entry callback may satisfy +either operation conditionally by exporting that demand. A replaced local, +unknown cast, unresolved global or known incompatible alternative cannot. +Forwarding the demand requires the same immutable-entry evidence. + +The initial actual-target matcher accepts the existing modeled malloc/free +boundary and complete inferred wrappers with the same precise interface and +effects. It does not infer compatibility from a spelling, prototype, isolated +fresh return or may-release effect. Every returning alternative and all +preconditions must be covered. An unrecognized wrapper remains incomplete. +More general memory callback contracts continue through the existing explicit +function-type contract and concrete-binding mechanisms until a separately +specified behavioral matcher supports them. + +An actual target satisfies a callback requirement only through a complete +verified inferred contract or an existing explicit modeled/trusted boundary. +All possible targets must satisfy the requirement; output guarantees intersect +across returning alternatives. Unknown or null alternatives remain unresolved +unless the actual call path excludes them. Copies, setters and nested hooks +retain their actual identities and dependencies. + +This representation does not infer an arbitrary callback's body from its use. +The demanded contract is a visible caller premise, and closed clients must +establish actual bindings. Asynchronous invocation, escaping callback protocols +and arbitrary behavioral subtyping remain outside this change. + +### 5. Portable representation and implementation boundaries + +Core owns Clang-free value/relational/predicate records and their joins, +validation and canonical encodings. Analysis supplies evaluated C operations, +target types/layout, candidate discovery and body verification. Frontend +transports only completed contracts and explicit incomplete results, and +preserves source/header/command/object binding and dependency invalidation. + +Any new portable records must participate in equality, strict path/global +remapping, summary I/O, checked reports, sidecars and persistent checkpoints. +Reject dangling references, missing premises, contradictory capabilities, +noncanonical encodings and oversized records. Retain source-level locations +and proof origins independently of semantic convergence. + +Portable storage descriptions must retain the typedef identity of an anonymous +record when Clang uses it in the canonical layout view. Record the typedef name +separately from a tag name; an anonymous record may have one identifier here, +whereas a tagged record must not carry this anonymous-record field. The `it2` +interface encoding adds that bounded identifier and rejects earlier encodings. +Materialize its typedef only inside the analysis adapter, without publishing it +in the client's declarations or lookup tables. Recheck the complete target +layout and canonical view, including qualified and nested references. A missing, +forged or incompatible typedef identity loses the adapter; it never permits +dropping a private global from a dependent contract. + +Portable records use summary format 26, sidecar format 27 and checked encoding +12, incrementing the RFC 0028 formats 23/24/9 and the interim development +formats 24/25/10 and 25/26/11. Rebuild older objects and discard old caches. Do +not add compatibility readers. Expanded and compact reports remain current +representations of the same evidence, not different proof modes. + +Remove superseded inference and projection paths when the common machinery +covers them. Preserve useful domain-specific transfer rules rather than +duplicating the whole checker behind a new mode. No new compiler option, +pointer ABI, runtime instrumentation or source-language annotation is required. + +### 6. Frozen acceptance and validation + +Preserve an immutable baseline executable and source revision. Before checker +changes, freeze manifests, expected properties and source hashes for: + +1. Stateful reader/writer helpers with extra unrelated fields, copied state, + helper-mediated cursor updates and success/failure outputs. +2. Direct and mutual recursive traversal, construction and cleanup, with + runtime-sized inputs and concrete nondecreasing/skipped-child counterparts. +3. Generic allocator/releaser and synchronous callback helpers, including + compatible multiple bindings and incompatible/null/unknown alternatives. +4. Closed parser/serializer lifecycles through separate source files, ordinary + compiler objects with checked linking, and validated cold/warm checkpoints. + +Keep the original populations immutable. Independently discovered regressions +receive separate manifests and provenance; do not rewrite initial expectations +to fit the implementation. A negative must fail for its intended obligation, +not for a parse error, unrelated unsupported operation, crash or timeout. + +Use pinned unchanged cJSON public APIs for parse/delete and +create/serialize/delete/output-release clients, covering nested values, +malformed input and allocation failure. Record exactly which inputs and API +contracts are proved. Also use an independent parser or serializer with +runtime input and separately selected generic helpers. Audit claimed positive +workflows for real upstream defects before requiring successful proof. +No third-party library-name certificates, upstream source edits, annotation +trust or unsafe trust may establish a positive inference result. Existing +modeled C-library trust remains explicit. + +Add adversarial mutations and equivalent-source variants: bounds off by one, +uninitialized tails, capacity lies, overflow, saved aliases, missing cleanup, +duplicate ownership, reordered independent fields, helper extraction and +callback forwarding. Independent concrete oracles validate relevant finite +memory/ownership cases. Runtime sanitizers corroborate counterexamples but +are not a soundness proof of the analyzer. + +Run full Debug and ASan/UBSan suites, strict changed-file clang-tidy, +formatting, the Core dependency boundary check and the unchanged fixed +evaluation. Preserve every valid baseline-complete corpus identity; remove a +false proof only with a documented counterexample. Compare complete canonical +reports across uncached/cold/warm execution, requiring positive warm reuse and +zero function analyses for unchanged reusable populations. + +Measure three isolated sequential ordinary Release runs before and after; +median elapsed time and peak RSS must each remain at most 1.10 times baseline. +Every checked corpus project retains its 600-second deadline and the same +report encoding. Record function/context work, report and checkpoint sizes, +peak RSS and all failed development observations. Publish validation and +machine-readable evidence. No acceptance denominator, resource bound or +required workflow may be silently reduced to meet these gates. + +## Annotation surface + +None. Existing annotations retain their meanings. Positive inference cases +require no additional annotation or unsafe trust. + +## Diagnostics + +Retain `checking-incomplete` for missing evidence and `checking-failed` for +demonstrated violations, independently of warning severity controls. Existing +ownership, bounds, validity, integer and leak identifiers retain their meanings. +Pin new explanations for ambiguous state roles, unavailable relational +projection, failed recursive progress, unverified group outputs and unsatisfied +callback behavior with RFC 0029 unit and lit tests. Explain the originating +premise and call route without changing completeness when explanations share +storage. No new diagnostic identifier is planned. + +## Drawbacks + +More general contracts increase projection and invalidation complexity. +Recursive construction and callback requirements can accidentally authorize +facts circularly. More discovery can increase cost even for unrelated code. +Explicit hypotheses, group validation, bounded preparation, independent +counterexamples and measured whole-program gates address these risks. + +## Alternatives + +Raising unrolling/context limits cannot establish arbitrary-length invariants. +Library-specific trusted summaries would hide the inference gap. A complete +new semantic IR would offer stronger long-term isolation but adds migration +risk before delivering these workflows. This RFC extracts shared operations +where their semantics and acceptance cases are concrete. Archive packaging +would improve adoption but would transport the same incomplete contracts. + +## Prior art + +RFC 0013 supplies immutable entry identity and heap outputs. RFCs 0017 and +0021 supply target arithmetic and checked induction over cursor progress. +RFCs 0018–0020 separate sufficient requirements, verified outputs, explanations +and validated reuse. RFCs 0022 and 0028 preserve actual callback bindings and +opaque/private storage evidence. RFCs 0023, 0026 and 0027 separate structural +discovery from proof and allocation conservation. This RFC extends those +specific mechanisms while retaining their trust and mutation boundaries. + +## Unresolved questions + +Candidate ranking, internal indexes and precise factoring may be refined +during implementation without changing proof rules. The validation record +must state measured workflow coverage and cost. Any semantic extension or +acceptance change requires an explicit amendment before implementation; +unmet mandatory gates keep this RFC Accepted rather than Implemented. + +## Future work + +A general semantic IR, source-free checked library distribution, shared +recursive ownership, tracing collectors, asynchronous callbacks and concurrent +execution require separate designs. This milestone makes no whole-library or +whole-executable certificate claim beyond its actual selected contracts. diff --git a/docs/rfcs/README.md b/docs/rfcs/README.md index c610657a..6f856b83 100644 --- a/docs/rfcs/README.md +++ b/docs/rfcs/README.md @@ -75,5 +75,6 @@ decision is a new RFC that supersedes the relevant section. | [0026](0026-growable-buffer-contracts.md) | Inferred relational contracts for growable buffers and vectors | Implemented | | [0027](0027-recursive-object-ownership.md) | Recursive object ownership and complete cleanup contracts | Implemented | | [0028](0028-opaque-objects-and-library-state.md) | Inferred contracts for opaque objects and private library state | Implemented | +| [0029](0029-compositional-recursive-workflows.md) | Compositional invariants for recursive C workflows | Accepted | The [roadmap](../roadmap.md) links each milestone to the RFCs that define it. diff --git a/docs/validation-rfc0029.md b/docs/validation-rfc0029.md new file mode 100644 index 00000000..0fcc7a41 --- /dev/null +++ b/docs/validation-rfc0029.md @@ -0,0 +1,2045 @@ +# RFC 0029 implementation and validation + +This record accompanies the in-progress implementation of +[RFC 0029](rfcs/0029-compositional-recursive-workflows.md). The RFC remains +Accepted. Passing the smaller workflow populations does not satisfy the full +cJSON parse/print goals or establish arbitrary recursive construction. + +## Implemented mechanisms + +- Semantic buffer candidates with additional fields, loop-index bounds, and + validated roles imported from separate-source contracts. +- Flow-sensitive entry allocation identity across later pointer reassignment, + without exporting local parameter-slot initialization as caller memory. +- Shared affine projection excludes overwritten numeric fields, so a changing + reader cursor requires the complete iteration interval rather than its entry + cell. Existing relational envelopes supply stable interface endpoints. +- Private direct/mutual cleanup, read-only traversal and fresh-construction + hypotheses, complete group validation and a bounded progress graph. + Forwarding edges require strict progress on every cycle. Specialized direct + cleanup uses the same progress machinery. +- Construction over decreasing initialized byte intervals, immutable entry + count snapshots, fresh forest outputs and every-exit allocation accounting. + Complete helper contracts support reads and partial-tree cleanup. Successful + attachment retains a child's proved parent relationship across later stores + to other child slots, without admitting shared ownership. +- In-place extension of an owned singleton head with an unsigned decreasing + count, an unchanged live head on every return, and an explicit fresh-region + ledger. Preserved neighboring forests retain their established separation. +- Private group members cannot publish nested callback or memory contexts. +- Integer success interfaces can publish a fresh forest through an output + pointer. Positive returns establish the full forest and zero returns must + actually leave the slot null. Immediate result tests transfer the conditional + allocation footprint, including a fresh child written into a parent slot. + The single-slot helper has no other mutating effects. General deferred result + tests still require separately invalidated pending evidence. +- Copied reader records nominate a unique byte pointer and remaining count + from evaluated accesses and decrements. The proof requires the full input + interval and compares recursive progress with immutable field snapshots. + Direct updates to unexposed automatic cells preserve separate heap facts. +- Mutable reader construction requires a live initialized writable record, + separated from its readable byte interval. Its hypothesis invalidates the + changed reader fields and supplies no post-call bounds. Writes confined to + a live unchanged entry object preserve only independently proved fresh + forests, while retiring changed pointer-cell names. +- Explicit allocation and release callback premises, checking of actual targets, + visible trusted boundaries, and portable input-only contract records. +- Exact zero-initialization frames outside represented writes and concrete + ownership-forest establishment after attachment. +- Target-aware proofs for finite floating constants and unchanged scalar inputs + under dominating true bounds. NaN, infinity, rounded integer limits, mutated + values, address exposure, bypassing jumps and nonstandard modes remain checked. +- Checked `strtod`/`strtof`/`strtold` memory boundaries preserve bounded + end-pointer provenance, require terminated initialized inputs and writable + separate output slots, and establish no floating result range. +- Summary format 25, checked encoding 11 and sidecar format 26, with no older + sidecar reader. + +The recursive group rule currently covers same-record, one-pointer-parameter +cleanup and read-only traversal without hidden global effects or arbitrary +external helper calls. Initial constructors take a constant byte pointer and +unsigned remaining count and return a nullable fresh initialized forest. +Partial-consumption/in-place construction, owned-tree writers and broader +floating relational inference remain outstanding. Byte-interval writers are +covered by candidate25 below. No broader interface is +certified by these smaller contracts. + +## Frozen populations + +The primary 15-case population was frozen before checker edits. Its baseline +rejected stateful runtime/generic writers, reassigned release, mutual cleanup +and generic allocation callbacks. The unchanged reader and adversarial cases +are preservation checks. The full primary population passes the development +candidate. + +The independent serializer freezes runtime/generic hexadecimal encoding and a +short-input counterpart. The transport population freezes a closed client of +mutual cleanup plus allocation/release callbacks, generic interfaces and an +undersized allocator. Each population carries source hashes and provenance. + +Development object checks pass both closed clients and their intended negative +counterparts. Cold, warm, uncached and compact reports match; the unchanged +transport workflow reuses two units with zero function analyses. Removing a +child release invalidates the proof. Corrupt checkpoints recompute and older +sidecars reject. The reproducible runner is `scripts/checked-workflows.py`. + +The pinned cJSON print/delete client passes candidate71a with zero entry +requirements. The original parse/delete client remains incomplete; its +automatic input also has a separately corroborated error-pointer lifetime +defect on allocation failure. Original expectations remain unchanged in the +upstream manifest, with the defect recorded in a separate audit population. +General parser construction, string decoding, nested serialization, and the +remaining whole-project validation gates are still outstanding. +No upstream source was changed and no third-party trusted summary was added. + +The later reader population proves a runtime consume loop and separate-source +client, and rejects truncated and partially initialized input through source and +ordinary objects. Its original probe included an invalid negative: `take` +returns before accessing a cursor already beyond the extent. The original +manifest and failed observation remain unchanged. A separately frozen reviewed +manifest expects that safe case to pass and adds an actual escaped access. + +The later `traversal` population proves borrowed direct/mutual traversal and +rejects interior pointers, mutation and cycles without progress. An audited +forwarding case was correctly safe: its cycle still contained a strict child +edge. Its original manifest and failed observation are preserved, alongside a +separate reviewed manifest and an actual nondecreasing cycle. + +The `construction`, `mutual-construction` and `construction-helpers` populations +cover runtime input, forwarding cycles, binary-tree construction and partial +failure cleanup. Short/uninitialized input, missing or repeated releases, and +nondecreasing recursion reject for their intended obligations. Each population +has its own immutable inventory and provenance. The combined +`recursive-transport` population verifies construction, traversal and cleanup +across separate units. Cold/warm/uncached/compact reports match, with zero warm +function analyses; changing cleanup invalidates dependent proofs. Construction +and combined clients also run through ordinary objects and checked linking. + +The `reader-construction` population covers a reader passed by value, including +an unrelated depth field. Its seven frozen cases accept construction and reject +truncated/uninitialized input, nondecreasing recursion, escaped intervals, +leaks and repeated release. Candidate 22's source run and two focused unit tests +pass. The separately frozen `mutable-reader-construction` population applies +the same seven intended properties to a pointer-reader interface; its candidate +22 baseline rejects the positive, and candidate 23's source run passes all seven. + +The `numeric-input` population freezes seven libc-boundary cases. Candidate 24b +passes the two positives and the intended initialization, write-permission, +bounds and user-definition rejections. Its initial development candidate +mistook an address of a null-valued slot for a null slot; the corrected test +requires an actual null pointer value, and the failed observation is retained. + +## Development regressions + +Broader validation found and corrected two precision regressions in role +selection: local loop-index bounds and opaque/imported layouts. It also exposed +a soundness gap in existing cleanup specialization: a root node's `dispose` +callback had been reused as evidence for child callbacks. Recursive hypotheses +now reject that per-node substitution. The existing adversarial test with a +child callback that frees twice remains a required rejection. + +The reader probes exposed a pre-existing false proof in both HEAD and candidate +15. A loop advancing `r->position` exported a requirement for only the incoming +cell. A two-byte input with an advertised end of four was accepted; running that +unchanged client with ASan produces a stack-buffer-overflow in `consume`. +Affine projection now applies the numeric-write exclusion already used by typed +integer projection. The inferred requirement covers `[0, r->end)` instead. The +valid client remains complete and both invalid inputs reject. Full Debug +validation after this correction passes all 1,372 tests. + +The proof-boundary review then found a new candidate-17 defect: recursive +forwarding resolved `p + 1` to the ownership identity `p` and could consume its +footprint using the unchanged-pointer hypothesis. The corrected rule requires +an exact zero-offset copy, including represented spatial offsets and element +identity. The frozen offset population rejects nonzero forwarding and child +arithmetic while preserving `p + 0`. The invalid forwarding client now reports +the actual release one element past its allocation. Candidate 17's ordinary +measurement was interrupted and retained as an aborted development observation; +it is not final cost evidence. + +Core tests exhaust all 19,683 absent/non-strict/strict three-node progress graphs +against an independent transitive-closure oracle. Byte-frame tests compare +retained ranges against concrete byte masks. Analysis and lit tests exercise +null exclusions, callback forwarding and hidden effects, wrong allocation +sizes, same-object recursion, skipped children, shared/cyclic ownership and +stale allocation aliases. + +The first resumed full Debug suite passed all 1,390 tests after preserving the +existing cleanup diagnostic wording and fixing object-harness handling of an +ordinary double-free diagnostic. Object generation now lowers ordinary WeaveC +errors to warnings; checked linking must still reject the same frozen mutant. +After the tree-construction changes, all 1,395 tests pass in both Debug and +ASan/UBSan builds. The unchanged fixed evaluation detects 44/44 bugs and accepts +32/32 clean cases, with no parse/tool failures or timeouts. A separately run +concrete allocation ledger covers all 3, 3 and 7 allocation failure points in +the frozen direct-chain, mutual-chain and binary-tree clients. Leak mutants +leave live allocations; repeated cleanup triggers the ledger or an ASan +heap-use-after-free while traversing the already freed child. + +The isolated three-run ordinary Release gate passes against the retained RFC +0028 executable (`47b881a…`). Across the unchanged five-project corpus, median +elapsed time is 150.386 seconds versus 151.293 seconds (0.9940×), and median +peak RSS is 650,428,416 bytes versus 651,575,296 bytes (0.9982×). Both satisfy +the 1.10× ceiling. Candidate `c46122a…` and all raw observations are retained in +`build/rfc29-validation/candidate19-bin` and `performance19-ordinary`. + +Candidate 19's checked corpus run found two precision regressions and was +interrupted during Lua; its partial reports are preserved. An external child +destructor in cJSON_Utils incorrectly nominated a partial local topology, +masking the imported tree's next link. Discovery now uses actual call-graph +components for mutual edges. Separately, discovering a buffer hid the scalar +entry-validity premise needed by `update_offset`. Pointer formation may now +request that premise only for its exact unchanged entry backing, with no +replacement or invalidation. The frozen `corpus-regressions` reductions pass; +their unterminated-input counterpart still rejects. The broader corpus and +final performance gates must be repeated after these corrections. + +Candidate 20 passes all 1,398 Debug and ASan/UBSan tests, strict lint for the +corpus corrections, and formatting. Both lost helpers are complete again in +the actual cJSON/cJSON_Utils analysis. Its unchanged upstream workflow run still +rejects both required positive clients; the double-release counterpart rejects +as intended. That failed observation is retained in `upstream20`. + +Candidate 21 adds output-slot construction, with eight frozen source cases, +five unit tests, source transport, and complete cold/warm/uncached/compact +checkpoint comparisons. The candidate-20 baseline rejects the positive and +misses several intended negative explanations; those observations are retained. +Mutual forwarding also proves through the group progress check. The broader +Debug run passes all 1,406 tests. The subsequent subobject-bounds correction +passes six focused unit tests and 25 targeted CTest checks, including lit, +objects, allocation oracles and output transport/checkpoints. +Addressed members retain their own subobject bounds even +while their memory identity is projected through the enclosing allocation. + +Candidate 22 passes all 1,410 Debug tests, including the seven copied-reader +source cases, object linking and the allocation-failure oracle. The first +candidate lacked field initialization and discarded fresh-forest facts on a +copied cursor update; those failed observations are retained as `reader22` and +`reader22f`. Its copied-reader positive uses actual interval and allocation +proofs, and its seven-case mutable-reader baseline remains incomplete. + +Candidate 23 passes all 1,348 unit tests and 67 integration tests, including +mutable-reader source/object/oracle checks and mutual reader forwarding. The +initial strict lint observation found four style issues, subsequently corrected. +Candidate 24 passes the focused numeric-boundary tests, strict lint for the +changed files, formatting, the Core include boundary and the unchanged fixed +evaluation (44/44 bugs, 32/32 clean). Its Release executables are retained in +`candidate24-bin` with an identity manifest; full Debug, sanitizer and upstream +observations are being recorded separately. + +## Remaining validation gates + +Formatting and the recent changed-file lint and Core include checks pass. +The earlier isolated ordinary performance result applies to candidate 19; +the final code still needs that gate and complete checked corpus identity +preservation. Mandatory upstream and unimplemented semantic goals must also +be satisfied. Earlier development observations are retained under +`build/rfc29-validation`; generated binaries and reports are not committed. + +### Candidate24 completion checks + +The full ASan/UBSan suite passed all 1,419 tests (1,351 unit and 68 +integration; 241.03 seconds), retained in `asan24-full.log`. The Debug run +passed 1,418/1,419; its sole failure was the numeric-input lit expectation +using `checking-incomplete` instead of the actual `checking-failed` write to +read-only storage. After correcting that expectation, all 143 lit cases passed +in `lit24-message.log`; no checker code changed for that correction. + +Immutable Release candidate24 (`candidate24-bin/identity.json`) still fails +both mandatory cJSON positives, parse/delete and create/print/delete/release. +The double-release negative passes. Reports are in `upstream24/source.json`; +these are unresolved gates, not waived acceptance cases. The fixed evaluation +remains 44/44 bugs detected and 32/32 clean programs accepted (`fixed24.json`). + +The new writer candidate development retains `writer25-baseline.json` and the +original `recursive-writer24-baseline.json`. The original writer expectation +audit is recorded beside its immutable fixtures. A separately frozen reviewed +population distinguishes complete initialized-prefix evidence from a functional +claim that a success result copied all input. + +### Candidate25 recursive byte writers + +Private writer hypotheses share the construction interval-progress machinery +and require an actual writable buffer with separated header, input and backing. +Each body must establish its initialized prefix on every return. All 11 reviewed +source cases pass (`writer25c.json`), as do three targeted unit tests. The first +attempt failed because separation forwarding lost a buffer entry identity; +the second still lacked an explicit failed-writer-output diagnostic. Both +observations are retained. The shared separation projection now uses the same +immutable backing evidence as `memcpy`. + +Separate-source transport passes four cases, including an independently +selected generic writer (`writer25-transport`). Cold/warm/uncached and compact +reports agree; warm execution performs zero function analyses, and mutation, +corrupt cache and old-sidecar checks pass (`writer25-cache`). The independent +ASan/UBSan oracle checks 18,513 finite input/capacity/initial-prefix combinations +and detects the false-prefix mutant (`writer25-oracle`). These finite runs do +not establish unbounded recursion. Tree serialization, complete-copy length +claims, in-place parsing and the cJSON gates remain required and unresolved. + +Candidate25's full Debug run passed 1,425/1,426 tests. The one failure was +object-runner selection: it selected only `main` for the writer off-by-one case, +although that fixture's bound error is in the independently selected generic +`emit` (the concrete client stops before that bad boundary). The runner now +preserves both selections for the reviewed writer population; no fixture or +expectation changed. `objects25e` retains the corrected run. Strict lint's three +style findings (explicit pointer-to-bool comparison and two qualified `auto` +variables) were corrected; `tidy25e.log` is clean, and the other candidate25 +changed implementation files passed their original strict lint invocation. + +### Candidate26 cases and candidate27 reader cursors + +Candidate26 admits a separately rechecked case that proves its recursive edge +unreachable, while retaining the selected generic summary's exhaustion. Exact +live scalar pointees can supply case values; incompatible, partial and dead +storage cannot. All five frozen recursive-case checks pass. The first full +Debug run passed 1,421/1,430. One new test used an unavailable standard header; +the other failures exposed imported buffer registration after the tightened +nomination filter. Returned objects now validate their transported descriptor, +and forwarding helpers import that descriptor before entry initialization. +The stable candidate27c rerun passes all RFC0026 source transport, object and +cache checks. RFC0028 source/object and regression checks passed candidate27; +a subsequent full-version suite is still required. + +The `reader1:` descriptor distinguishes a cursor into fully initialized input +from a writable initialized output prefix. It shares strict layout transport +but cannot imply writer permissions or ownership. Candidate27 passes all 11 +reviewed cursor-reader source cases, 13 Core buffer tests, and the targeted +reader/scalar-capture unit checks after correcting two test harness mistakes. +The original cursor fixture audit and baseline remain alongside the reviewed +inventory: generic contracts may have premises, and an advertised unread tail +alone does not constitute a concrete erroneous access. Partial consumption +exports only the bounded reader predicate, not a specific consumed amount. +The mutation/cache and current full sanitizer gates remain pending. + +A separate frozen recursive-context population demonstrates exhaustion caused +by many refinements of mutable output cells before a concrete input selector +arrives. Candidate27 rejects its positive. Candidate28 investigates bounded +nomination without increasing, evicting or resetting the context budget. Its +outcome and the mandatory cJSON workflows must be recorded independently. + +### Candidates28–29: bounded input cases and mutable output contexts + +Candidate28 completed 1,431/1,437 Debug tests. Its six failures exposed reader +candidate overnomination and propagation of generic exhaustion into cases with +separately verified peer base cases. Reader nomination now requires an observed +indexed counter/extent comparison, and actual exhausted-reader cases may check +their early-return path without importing an inapplicable generic predicate. +The audited cursor inventory retains the original cases and adds the actual +extent explanation to one negative's diagnostic matcher. It does not change +that case's rejection requirement. + +Candidate29b passes the full Debug suite: 1,440/1,440, including 1,361 unit tests +and 79 integration tests (282.96 seconds, `dev29b-full.log`). The mutual-case +population now passes all four cases: stateful helpers capture forwarded input +selectors and an executed call to a separately completed peer base case can +complete, while the unbounded and invalid alternatives remain rejected. +The recursive-output population passed its baseline as well; it is preservation +coverage, not evidence of a prior missed positive. No context count or nesting +bound was increased, reset or evicted. Three strict lint style findings were +corrected in candidate30; their original reports remain retained. + +Candidate28's cursor oracle checks 1,123 finite cases for each ordinary and +partial reader and detects 74 escaped-cursor mutant failures. Its cursor cache +checks pass, including identical canonical cold/warm/uncached reports, zero +warm function analyses, compact encoding, mutation, corruption and old sidecar +rejection. These finite oracles do not certify arbitrary runtime input sizes. + +The immutable candidate29b Release executable has SHA-256 +`246dd010435569ba0b37ddf29065927678dae28c86685b50fe489bd931ac602b`; +its driver is +`7c06668610c6de23ba83caf6217b3a5ce6a0165cd012ac4ed7ca1ec8e3b95152`. +The matching development identity is retained separately under +`candidate29b-dev-bin/identity.json`. Upstream validation remains **failed**: +`upstream29b/source.json` rejects parse/delete and print/delete, while the +parse/double-release negative passes. Candidate28 had the same three outcomes. +Those observations preserve the unchanged upstream sources and mandatory gates. + +### Candidate30: record arrays and current zero-byte evidence (in progress) + +A reduced stack-array writer exposed two RFC0015 implementation gaps: arrow +field access lacked a checked byte offset, and arrow selection did not name the +same exact record cell as subscript/dereference spellings. Selected field paths +now agree through forwarded contracts, and buffer arguments validate the actual +selected record's layout. The first candidate passes five of the six frozen +record-array cases; its writer still lost a terminating zero after a header +update. Original and intermediate observations are retained. + +Zero-valued ordinary integer cells can now be recovered from complete current +zero-byte intervals in automatic storage. All six isolated zero-counter cases +pass candidate30c, including partial memset and later direct/helper writes. +The original multi-function inventory and failing baseline remain immutable; +`zero-counters/audit.md` explains source isolation needed to avoid unselected +negative functions making positive command invocations fail. Its diagnostic +matcher also records the bounds explanation omitted from the original regex. +No expected safety property changed. Candidate30d passes all 76 focused array +and buffer unit tests. Broader validation and zero-frame preservation are +pending; this is not the RFC's final validation. + +Candidate30g passes the full Debug suite: **1,448/1,448**, comprising 1,366 unit +and 82 integration tests (`dev30g-full.log`, 292.54 seconds). All six record-array, +six audited zero-counter and three zero-frame cases pass. The terminating-byte +regression has a focused unit test, and record-array/partial-byte diagnostics +are pinned by lit. Zero preservation now reuses the shared immutable-entry +separation projection, including backing identity retained when a logical +length update retires the full buffer predicate. Neither aliased writes nor +later nonzero stores preserve the zero. + +The immutable candidate30g Release executable is +`7e693145e977a4ce0200a06bba32c65e655f9fb68b34965b3a98a997d81481a4`; +its driver is +`f784fd3f7d5da86138d831a0580e63f964db062e771d76d0b184fa3559c09c3a`. +The candidate30e upstream run still fails both positive workflows (183.74 +seconds for the three-case population), while preserving the double-release +rejection. Its print context now captures the object's type, null child, +zero cursor/depth, capacity and concrete hooks; downstream helper case limits +remain unresolved. Candidate30e predates the final zero-frame fix, so it is not +a claim about the candidate30g upstream outcome. Formatting and the Core +boundary pass. Strict lint found a repeated branch body, consolidated in +candidate30h; full current sanitizer, fixed evaluation, corpus and final cost +gates remain pending. + +Candidate30g also preserves the unchanged fixed evaluation: 44/44 detected +bugs and 32/32 clean cases, with no parse errors, tool errors or timeouts +(`fixed30g.json`). Candidate30h adds an early zero-range lookup to avoid layout +walks for integer cells with no applicable byte evidence and consolidates the +identical array access branch flagged by clang-tidy. Its full sanitizer run is +recorded separately from the following callback scheduler change. + +### Candidate31: combined callback and scalar entry cases (in progress) + +Direct calls that already supply both callback bindings and captured memory or +scalar facts now request their combined context directly. This avoids first +checking an intermediate callback-only body with unknown selectors. All actual +callback alternatives and case premises remain in the existing canonical +context; budgets and incomplete fallback behavior are unchanged. The five +frozen separate-source cases pass both their candidate30g baseline and +candidate31, so this population establishes preservation, not a previously +missed positive. + +The first targeted unit run passed 88/89. The remaining test assumed concrete +callback bindings always appeared in the callback-only request map. It now +checks both callback-only and combined requests for the same required known +target, while still requiring the generic function to remain incomplete and +the concrete caller to be complete with no entry requirements. A new focused +test verifies that a combined call does not create the intermediate request; +both tests pass candidate31b. The first unit failure remains in +`context31-unit.log`. Broader validation and the upstream outcome are pending. + + +Candidate31b's full Debug suite passed 1,450/1,450 (1,367 unit and 83 +integration tests, 391.35 seconds). Candidate30h's ASan/UBSan suite passed +1,448/1,448 (1,366 unit and 82 integration tests, 397.49 seconds). These are +separate executable identities and do not validate subsequent changes. +Candidate31 still rejects both required cJSON positives; its three-case +population took 277.45 seconds while other test work ran. The negative +retained its intended rejection. This is not an isolated cost measurement. + +### Candidate32: nomination from settled recursive inputs (in progress) + +The immutable `recursive-state-cases` population has 36 generic callers with +varying constants in a mutable output record. Its concrete client supplies a +known input node; the other cases supply a null output or select the generic +recursive definition. Candidate31 rejects the positive and retains both +negatives (`recursive-state-cases31.json`). The proposed scheduler postpones +scalar-only cases inside generic recursive approximation and prioritizes +represented read-only record inputs over unrelated output-state constants. +No existing case is evicted and no bound is changed. + +Candidate32 passes all three `recursive-state-cases` cases and all 91 targeted +compositional/recursive unit tests. Its context and translation-unit files +pass strict clang-tidy. The extended upstream population was frozen against +candidate31 before its first run; it adds nested parse/print/release, malformed +partial construction, first-allocation parse failure, and print allocation +failure after creating the input object. These positive proof obligations +remain mandatory. + +A separately frozen finite cJSON oracle checks eight valid/malformed documents +with a concrete allocation ledger, expected serialized bytes, and each +allocation failure point observed on the successful path. All 68 executions, +including 52 injected failures, passed ASan/UBSan. This audits only these finite +executions and supplies no checker summary or abstract proof. + +Candidate32's full Debug suite passed 1,452/1,452 (1,368 unit and 84 +integration tests, 330.31 seconds). Its Release executable is +`a449728a1960c52452b92b54d5baec2e991d2e02083e9aa98556ce8836cf1340` +and driver is +`284e483be784df212bd91a4b215bad74c140e4ce76499005c96d2dd6c25d071e`. +Its cJSON population still fails both positive proofs and preserves the +negative (69.65 seconds total, not isolated). The empty serializer now has +one relevant print-object case and six growth-helper cases; its remaining +print-object failure concerns the initialized prefix at the second reserve. +All four extended upstream cases fail their candidate31 baseline; their +original expected acceptance is retained. + +### Candidate33: buffer identity and physical extent (in progress) + +The new immutable `writer-return-aliases` population writes through a helper's +returned pointer, advances the header, obtains another pointer, terminates the +output, reads it, and frees it. Candidate32 rejects the positive and rejects +both missing/incorrect-first-byte negatives for initialization/termination. +Candidate33 preserves an independently established physical allocation extent +when materializing a smaller logical buffer capacity. It also normalizes newly +registered imported descriptors and retains surviving entry backing identities +when applying verified buffer outputs. No logical capacity establishes new +storage and no pointer replacement revives the old entry identity. + +The focused source population passes all three cases in candidate33e. Temporary +trace observations are retained under `return-aliases33*-trace.log` and +`print33b-trace.log`; trace instrumentation has been removed from the source. +Broader validation and current upstream results remain pending. + +The first candidate33 buffer unit run passed 24/26. The existing +`FailedPositiveAllocationDoesNotBecomeLiveAtZeroCapacity` negative exposed +an unsound intermediate change: the larger physical extent was also being +used to infer non-null storage. Candidate33f separates that extent from the +logical buffer's conditional-validity premise; the original negative passes +again. Candidate33 binaries/results remain retained as failed development +observations and must not be used as final soundness evidence. + +The other failure is the new positive unit's equivalent conditional-return +helper. Its separately frozen `writer-return-expressions` population rejects +the positive on candidate33 while preserving its two negatives. Candidate34 +checks the two pure pointer-return alternatives with ordinary branch refinement +and output intersection. Side-effecting or nested conditional returns retain +conservative output handling. Both the RFC amendment and frozen baseline +precede that implementation. Candidate33 still fails all required positive +upstream workflows; those expectations remain unchanged. + + +Candidate34 passes all 26 buffer unit tests, including the failed positive-size +allocation negative. The three immutable conditional-return cases also pass. +Candidate35 preserves the tighter proved pointer position when a callee exports +both an exact position and a wider envelope for the same storage and extent. +All three frozen `writer-position-bounds` cases pass; both focused buffer units +pass. Strict lint for the touched safety and buffer files is clean. + +Candidate35's immutable Release executable is +`a6a9ea9b552fc3fffde0f9b940b07008175785efce70627e1414a5f28d53c9a6` +and driver is +`2d0badffb6e50e4c3dcadb8b373a6c034273567330355104257b4c796e11dfbf`. +Its required cJSON positive workflows still fail and its double-release +negative still passes. The concrete empty-object `print_object` case now has a +complete contract, but its forwarding caller lacks the buffer entry predicate. +The outer printer also loses its input forest across writes to local storage. + +### Candidate36: forwarding candidates and local storage frames (in progress) + +`container-local-frames` freezes one generic traversal wrapper with a local +byte initialization and field store, plus corruption of the input root and a +child. Candidate35 rejects the positive and both negatives. Candidate36's first +implementation still rejects the positive because array-arrow stores bypassed +the ordinary holder path; that observation is retained. + +`writer-forwarding` retains an initial population whose added forwarding helper +was accidentally unused. Its reviewed inventory actually calls the forwarding +helper, while preserving the positive and two byte-initialization negatives. +Candidate35 rejects the reviewed positive and rejects both negatives. Both +inventories and their observations remain available; the reviewed sources were +frozen before changing discovery. The proposed change imports only a validated +layout candidate from an incomplete generic callee; it supplies no call proof +and every caller must still discharge the buffer predicate. + + +Candidate36d passes all three reviewed forwarding cases after applying candidate +import consistently at discovery and entry initialization. The local-frame +positive still fails: the container forwarder previously nominated predicates +only for opaque records. Complete record parameters now validate the transported +layout and nominate the same explicit entry premise. The first trace-only debug +build failed because the temporary instrumentation used a nonexistent PlaceTable +method; it produced no executable, and the instrumentation was removed. + + +The local-frame unit now passes its positive plus three corruption variants, +including attaching an automatic child and then corrupting it. The remaining +array-arrow failure was an identity mismatch: the actual lvalue belongs to the +automatic array, while the generic pointer-holder path named a synthetic +indirection. Framing now uses the actual lvalue storage in this case. Temporary +trace code has been removed. + +The first new forwarding unit did not reproduce the intended incomplete-generic +case and failed on a separate terminated-output projection. That observation is +retained in `forward-unit36.c` and `forward-unit36.json`. The unit now uses the +already frozen reviewed regression's reserve/emit/forward route, with its same +missing-byte counterpart; it does not change an acceptance expectation. + + +Candidate36i passes the full Debug suite: **1,460/1,460**, comprising 1,371 unit +and 89 integration tests (282.17 seconds). The immutable candidate36g Release +checker is `c29ba955484f00563729cf676b02f237161463b5485cc11c4c6aeb42faee7045` +and driver is `d4e4eca4d34e5d05f81e07790c249dd4f639dbe691f75f735dc23794a3cce3f6`. +Strict lint of the container transfer, container discovery and buffer files is +clean. The local-frame source population passes all three cases. Its cJSON +positives remain incomplete, while the negative retains its rejection. Both +the concrete `print_object` and `print_value` cases now complete; the outer +printer still has four unresolved obligation categories. No upstream acceptance +expectation has changed. + +The new `container-call-frames` population freezes a wrapper that writes a local +header and fresh byte allocation through a complete helper, plus root and child +corruption variants. Candidate36g rejects its positive and both negatives. +Candidate37 extends entry-only framing through represented complete call effects. +The upstream runner now includes dedicated ordinary-object and cold/warm/uncached +checkpoint modes for all seven original and extended clients. These modes are +not claimed as passing until their actual observations are recorded. + + +Candidate37 passes all three `container-call-frames` cases. Its cJSON positives +still fail. `mixed-allocation-ledger` freezes plain byte return, lost allocation, +double release and successful/failed resize in a wrapper that also traverses a +borrowed forest. Candidate37 rejects both positives and preserves both negatives. +Candidate38 accepts the plain return while preserving both negatives; resize +remains incomplete. The return discharges only the proved live allocation base's +head footprint and exports no forest predicate. + +Candidate39 adds conditional reallocation accounting: capture the old head before +call effects, track the independently fresh replacement, and add the captured +release only after a non-null result. Its first Debug build failed a shadowing +warning treated as an error; the local variable was renamed before rebuilding. +These development results do not satisfy the outstanding whole-workflow gates. + +Candidate39d passes all four mixed-allocation cases, all six reallocation-failure +cases, and the new allocation-ledger Analysis and Core units. Its immutable +Release executable is `d9374707a644f21517563d6a2c727e45f15da4dd72c3d0a36f9c3be6242c5607`; +the driver is `7568d3f669d1c0f26085d058df8d49aec0d49ec5d4ce67f38f6de72c9a24f5f4`. +Both original cJSON positive workflows remain incomplete; the double-release +counterpart remains rejected. These results do not establish the final cost, +corpus, sanitizer or transport gates. + +The separately frozen `scalar-write-offsets` inventories investigate scalar +facts below advancing pointers. Candidate37's summary for `*out++=7; *out=0` +incorrectly says the original first byte is zero. The initial four clients +reject their two unsafe cases but also reject both safe counterparts; those +observations alone do not demonstrate a false acceptance. A further frozen +`advanced-manifest.json` does: `advanced` writes 7, increments its pointer, and +returns the next byte, which its client initialized to 42. The client writes +past its array when that result differs from 7. Both the exact RFC0028 baseline +and candidate39d accept the selected client. Running the unchanged fixture at +`-O0` with ASan/UBSan reports the executed out-of-bounds index and stack write. +The retained `scalar-advanced-baseline`, `scalar-advanced39d`, and +`scalar-advanced-oracle` logs record this counterexample. + +Candidate40b rejects that counterexample and passes the dedicated unit covering +increment, pointer assignment, unchanged aliases, and conditional aliases. The +initial candidate40 focused run passed 116/117 tests: using the promoted LHS +type accidentally discarded bit-field storage width. Restoring the declared +width fixes that regression; its focused retest passes. Broader scalar-output +and corpus checks remain outstanding. The RFC remains Accepted. + +### Candidate41: bounded string lengths and reusable generic fallbacks (in progress) + +The resumed Debug baseline passed 1,462/1,468 tests. Cursor traversal and +formatted-output regressions came from newly nominated scalar cases replacing +complete generic contracts. An incomplete or unavailable optional case now +falls back to the complete generic contract, preserving all its original caller +requirements and publishing no dependent case output. The eight focused +regression and Core checks pass, including the unchanged RFC0021 and RFC0024 +populations. Original observations remain in `resume-dev-tests.log` and +`resume-regression-retest.log`. + +Bounded `strlen` now distinguishes its first zero from an arbitrary known zero, +exports an explicit bounded-termination input where needed, and retains the +proved range and initialized prefix through a cursor update. A target-unsigned +cancellation rule preserves the modular identity without discarding invalid +operand evaluations; signed and Boolean arithmetic are excluded. The frozen +six-case `bounded-string-cursor` population passes both positive cases and all +four intended rejections (`resume-bounded3`). This population is now registered +in CTest and ordinary-object validation. These focused observations do not yet +satisfy the upstream or final validation gates. + +### Candidate 41 completed Debug observation + +The Debug suite passed all 1,472 tests in 290.86 seconds, including the new +bounded-string unit and source cases, the separate-object population and lit. +The upstream source observation remains a failed acceptance run. The closed +print client's selected contract is now complete with zero entry requirements; +two ordinary diagnostics in unselected generic cJSON bodies still make the +invocation unsuccessful. The parse client remains incomplete. This does not +claim either mandatory upstream workflow gate has passed. + +### Candidate 42: transparent byte casts + +A reduced upstream reader exposed a projection bug: explicit pointer casts +around cursor arithmetic bypassed the checked arithmetic path. RFC 0004 already +requires pointer-to-pointer casts to preserve object identity. The checked +memory path now strips those transparent casts while retaining the arithmetic +operand's original element size. The separately frozen `cast-reader-intervals` +population rejected both positives before the repair and passes all five cases +after it. Forged capacity, uninitialized contents and a scaled one-past read +remain rejected. A reduced unchanged `skip_utf8_bom` body now has a complete +conditional checked contract. No upstream source or frozen expectation changed. + +### Candidate 43: stable reader index induction (in progress) + +The separately frozen `reader-index-loops` population records the remaining +strict cursor-plus-index traversal gap. Both positive cases failed before the +rule; five adversarial cases were rejected. The RFC now specifies the precise +zero-based, unit-stride induction and its stability/entry restrictions before +implementation. This candidate is under development; the complete upstream, +sanitizer, corpus and performance gates remain outstanding. + +Candidate 43e passes the seven frozen reader-index cases, both new focused +unit tests, the cast-reader and bounded-string source populations, the finite +8-bit induction oracle and the full lit suite (7 selected CTest entries). +Discovery recognizes a counter-plus-index comparison as candidate evidence; +actual storage and initialization still come from caller premises. Mathematical +byte displacements remain outside the typed symbolic base, preserving the +strict bound through the exclusive access endpoint. The first reader access in +the reduced cJSON numeric parser is now proved. Later copy/count and numeric +conversion obligations remain unresolved. + +### Candidate 44: equal entry counter values (in progress) + +The new frozen `paired-reader-counters` population records failure of its clean +copy client and rejection of three count/stride mutants before implementation. +The proposed repair seeds a relation only from two actual equal integer +constants of the same type; increment spelling merely nominates a bounded set +of cells. CFG joins and proved nonwrapping adjustments remain responsible for +maintaining that equality. The first build caught a `-Wshadow` error; the local +was renamed and the warnings-as-errors rebuild is underway. + +Candidate 44f passes the four paired-reader source cases. Equal constants now +seed a bounded counter relation, and range queries follow one such equality. +The reader induction retains its non-strict condition/exit bound as well as +its strict body bound. Actual constant reader extents narrow these bounds in +specialized cases. Candidates 44b through 44e still rejected the positive; +44f resolves that case without accepting any count/stride mutant. The generic +copy helper remains incomplete for unrestricted capacity, and this observation +does not satisfy the upstream workflow gates. + +The candidate 44g focused run passes both counter/reader units and all six +source, finite-oracle and lit CTest entries. Its clean Release upstream rerun +still fails both mandatory positives and rejects the double release. The +print client's selected contract is complete; its invocation still reports +the two ordinary diagnostics in unselected generic bodies. The parser's +numeric-copy obligations decreased, but recursive construction, string +traversal and numeric conversion remain incomplete. The first Release-copy +attempt preceded completion of linking and failed with missing executables; +it produced no acceptance result and was repeated only after the build ended. + +### Candidate 45: character-pointer slot views + +A separately frozen five-case population rejects its two positives before +the change. The target-aware character-pointer view rule follows Clang's +pointer alias implementation and requires matching representation and address +space. Candidate 45a accepts both positives and rejects read-only slots, +out-of-bounds end-pointer reads and unrelated pointer types. Actual library +provenance, writable storage and initialized input obligations remain active. +This supplies no floating-point value or range guarantee. + +The full candidate 44g Debug suite passed 1,478/1,479 tests in 310.69 seconds. +Its failing existing mutable-storage test exposed a regression in candidate 42: +a casted const field address could inherit its mutable enclosing record's write +permission. Candidate 45b retains the const subobject identity and passes that +regression plus all 15 other selected numeric/slot/source/lit checks. This is a +repair to RFC 0018's existing writable-storage requirement, not a new permission. + +### Candidate 46: pointer-difference reader guards (in progress) + +The seven-case `pointer-reader-offsets` population was frozen before this +change. Candidate 45a rejects both positives and all five intended negatives. +Candidate 46a accepts the closed positive and preserves all five negatives; +its unrestricted generic helper remains incomplete. A reduced upstream string +parser gains bounds in its initial scan but reaches the existing dataflow +iteration limit. The limit remains unchanged and the failed observation is +retained. Candidate 46b adds an explicit sufficient entry extent bound for +representable byte-pointer differences; this is still under validation. + +Candidate 46c passes all seven frozen pointer-offset cases. Candidates 46d–f +exposed and repaired two separate issues: constant relation bounds previously +grew until the existing visit limit, and the initial extent premise was too +broad for the unchanged RFC 0021 generic subtraction rejection. Constant bounds +now use the existing zero-threshold widening only at checked loop joins. The +explicit extent premise is restricted to an established byte-buffer predicate. +The five focused checks, including that existing rejection, the widening unit, +the pointer-reader unit/source population and lit, pass. Trace instrumentation +was removed. The first format attempt lacked clang-format on PATH; the rerun +used the LLVM installation explicitly and succeeded. + +Candidate 46g's clean Release upstream observation still rejects both mandatory +positive invocations and the intended double-release negative. The selected +print client remains complete with zero requirements, but its invocation has +the same two ordinary diagnostics. Reader discovery from the upstream TU lets +the concrete string-parser case prove the initial pointer scan. Its second +loop, the UTF conversion helper, numeric conversion and recursive construction +remain incomplete. The full Debug run is recorded separately and was started +against the fixed candidate 46g binaries before subsequent checker edits. + +### Candidate 47: explicit initialized byte spans (in progress) + +The nine-case `initialized-spans` population was frozen before implementation. +Candidate 46g rejects all three positives and all six unsafe counterparts. +The RFC now specifies a strict input-only requirement for a live, initialized +same-array interval with representable byte distance. Entry nomination is +bounded and excludes changed/address-taken endpoints and ambiguous pairs. +Implementation and validation are in progress; no mandatory gate is claimed. + +The full candidate 46g Debug suite passed all 1,484 tests in 398.26 seconds. +Candidate 47a passed eight of nine initialized-span cases; its nonzero-start +positive exposed overflow in the checker's sufficient upper-bound construction. +Candidate 48a repairs that comparison and passes all nine. The separately +frozen four-case `span-outputs` population already passed before the separation +refinement and remains a regression check. Its new unit additionally rejects +an unnecessary mutual-separation premise between read-only endpoints. + +### Candidate 48: scalar interval envelopes + +The five-case `reverse-byte-writes` population was frozen before this change; +candidate 47a rejects both positives and all three intended negatives. The RFC +specifies projection of actual narrowed scalar facts into sufficient interval +requirements. Candidate 48a accepts the generic reverse writer, but a concrete +specialization loses its bound when distinct exact counter constants join. +Candidate 48b seeds the actual constant assignment's lower and upper bounds in +the existing relation tracker. All ten focused Core/Analysis/source/lit checks +now pass, including unchanged unbounded-subtraction rejection. The reduced +unchanged UTF conversion helper now has a complete conditional contract. +The string parser still lacks second-loop and call-output evidence; the full +upstream parse, sanitizer, corpus and performance gates remain outstanding. + +Candidate 48b's Release upstream observation confirms that the generic UTF +conversion helper and both observed specialized cases are complete. Both +mandatory positive invocations still fail, and the intended double release is +rejected. No frozen upstream input or expectation changed. + +### Candidate 49: bounded counts from byte-span helpers (in progress) + +The separately frozen `span-counts` population rejects all three positives and +both intended negatives on candidate 48b. The RFC now specifies an output-only +nonnegative count bound, tied to a matching explicit entry span and verified +at every returning path. The first build failed because a private field used a +type alias before its declaration. The field was moved after the alias, and +capture preserves the types of saved byte coordinates. Candidate 49b is still +building; no acceptance result is claimed. + +The full candidate 48b Debug run passed all 1,492 tests in 816.48 seconds. +This was a regression run concurrent with development builds and an upstream +observation, not an isolated cost measurement. Candidate 49b's build rejected +passing a const state to expression materialization; output verification now +uses a local state copy only for functions with nominated spans. Candidate 49c +builds and correctly emits the count guarantee for the helper, but still +rejects all three positive callers because the numeric guarantee had not been +connected to the interval relation solver. Both negatives remain rejected. +Candidate 49d projects a representable captured distance through the existing +nonwrapping linear-expression machinery; validation is pending. + + +Candidate 49d passes the five frozen span-count cases and both focused unit +checks. Candidate 50's separately frozen `span-count-joins` population fails +all three positives and rejects both unsafe counterparts on 49d. It tests +counts assigned on different branches, including assignment before the guard; +implementation is pending and mandatory upstream gates remain incomplete. + +Candidate 50a still rejects the joined positives: the nomination function had +been extended, but its callers only visited loop operations. Candidate 50b +also visits return statements in the existing bounded syntax scan. + +Candidate 50b passes all five joined-count cases. The positive helper now +exports the count guarantee across the assignment/guard branch join; the false +count and later mutation remain rejected. Unit, lit and upstream observations +follow independently. + +Candidate 50b passes five focused count checks. Its unchanged upstream run +still rejects both mandatory positives and the intended double-release +counterpart. The UTF helper now additionally exports `count-within-span`. +A reduced numeric client proves copying and termination but loses ownership +when `strtod` writes its end pointer into a local slot. The newly frozen +six-case `local-callee-copies` population rejects all three positives on 50b +and rejects its three unsafe counterparts. Candidate 51 narrows the existing +copy-store escape rule only for a complete callee and a confined automatic slot. + +Candidate 51a's first build failed the existing shadow-warning check; the +local syntax-worklist name was corrected. Candidate 51b also keeps the escape +rule for additional store destinations, heap outputs or returned slot addresses. + +Candidate 51b accepts the signed/unsigned character `strtod` clients but still +rejects the inferred copy helper, whose equivalent final heap-root description +hit the conservative exclusion. Candidate 51c permits that same root-only +output description while still declining nested or additional destinations. +The reduced numeric-parser case now retains ownership; its remaining failures +are target ptrdiff representability and the floating conversion. + +Candidate 51c passes all six local-copy cases, including the inferred helper +and actual leak rejection. The next Debug build includes dedicated units and +lit coverage; no mandatory whole-program gate is claimed from these results. + +The eight-case `reverse-initialization` population was frozen before candidate +52. Candidate 51c accepts its generic helper conditionally but rejects both +closed positives; all five unsafe counterparts reject. The generic helper's +pre-existing acceptance therefore does not demonstrate its must-write output. +Candidate 52 adds a separately validated normal-exit rule for the visited suffix. + +Candidate 52a publishes the reverse helper's initialized suffix, but both +closed positives still reject. One needs direct automatic-array bases added +to the stable-base eligibility; the other exposes a separately retained +limitation when an interval endpoint is supplied as a conditional scalar call +argument. All five unsafe counterparts remain rejected. + +Candidate 52b still declined automatic arrays because their ordinary decay +marks their address taken. Candidate 52c keeps that restriction on mutable +pointer cells only; an automatic array's identity cannot be reassigned by the +eligible body. The full candidate 51c suite remains isolated from these edits. + +The five-case `conditional-count-arguments` inventory was frozen in the build +validation directory while the stable full suite was running. Candidate 51c +rejects all three positives and both unsafe counterparts. It will be moved +unchanged, including its hashes, into the test inventory after that suite. +An early candidate 52c snapshot was copied before linking finished; its result +is retained but is not attributed to the completed 52c build. A fresh snapshot +and observation will follow verified build completion. + +The completed candidate 52c snapshot accepts the direct local reverse-write +positive; its remaining closed rejection is the conditional-argument case. +Candidate 53a passes all five conditional-argument cases and all eight reverse +initialization cases. Candidate 53b restricts additional legacy endpoint capture +to actual conditional arguments; ordinary represented arguments retain their +existing path. Recapture explicitly retires an older saved expression as well +as its scalar range. The full candidate 51c suite is still running separately. + +Candidate 51c's complete Debug regression run passes 1,500/1,500 tests in +609.92 seconds (`resume-candidate51c-full.log`). It overlapped Release builds, +so this elapsed time is not performance acceptance evidence. Candidate 53b +passes all eight reverse-initialization and five conditional-argument cases. +The latter inventory has now been copied unchanged into the evaluation tree. + +The separate five-case `initialized-advance` inventory was frozen before +candidate 54. Candidate 53a rejects both closed positives and correctly rejects +all three unsafe counterparts. Candidate 54 introduces an output-only relation +between initialized bytes and the actual advanced pointer; it must never use +an upper position envelope as a must-initialized endpoint. The frozen inventory +has been copied unchanged into the test tree with its original hashes. + +Candidate 54a failed to compile because it used a private requirement-set +accessor; candidate 54b uses the public iterator interface. Candidate 54b builds, +but still rejects both initialized-advance positives. Its internal final cursor +has the actual range `{1,4}`, yet the upper-envelope exporter only consults +ranges on declared C cells, so no position output is published. The next fix +extends the already specified range rule to typed internal cursor coordinates. +All three unsafe counterparts still reject. Candidate 53b's unchanged upstream +run continues to fail parse/delete; print/delete has a complete selected client +but the invocation still fails ordinary upstream diagnostics. + +Candidate 54c failed compilation because the represented integer type is a +field, not an accessor. Candidate 54d exports the position and initialized +advance, and accepts the interior-pointer positive. The base-pointer positive +still hit a stale ordinary spatial offset. Candidate 54e retires that offset +when the verified final cursor is symbolic; all five initialized-advance cases +pass. Its core schema unit passes. The reverse-write unit was corrected to +check a closed caller: a generic reader can legitimately require initialized +input, so generic completeness alone was the wrong negative assertion. The +frozen source inventories and their expectations were not changed. + +The five-case `advance-outcomes` inventory is frozen before candidate 54f. +Candidate 54e rejects both positives and all three unsafe counterparts. A +common initialized-advance fact was lost when its matching position envelopes +differed between early and successful returns. Candidate 54f retains the +independently proved enclosing constant position envelope across all outcomes. + +Candidate 54f passes all five early-outcome cases, and the unchanged cJSON +UTF conversion helper now exports its actual initialized advance across success +and zero returns. The focused Debug units and source cases pass after supplying +the runner's JSON inventory alongside the original SHA256SUMS (same hashes). +The whole upstream workflow remains incomplete; no acceptance is claimed. + +A counter-reset probe isolates the numeric parser's ptrdiff loss: removing only +the later index reuse, or retaining an explicit count bound, removes that loss. +Before candidate 55, the exploratory `counter-reset-ranges` inventory rejects +both positives. Its negative matcher accidentally omitted the actual `leaked` +and `after it was freed` wording. Those failures and original manifest remain +retained. The separate `counter-reset-ranges-reviewed` inventory corrects the +matchers before implementation, preserves the original manifest, and changes +neither source bytes nor acceptance expectations. Its baseline rejects both +positives and matches all three unsafe counterparts. + +Candidate 55a preserves a proved range on an unchanged equal counter before +an exact index reset. All five reviewed cases pass. The reduced unchanged +numeric-parser client now has only the unsupported floating conversion; +its pointer subtraction is proved representable. A changed-count unit and +regular source/object registration accompany this fix. + +Candidate 55a's focused Debug counter-reset tests pass, including the +changed-count rejection. Formatting passes with the configured clang-format; +the Core include boundary and all five newly registered frozen inventories +verify. The complete 1,511-test Debug suite is running against stable 55a +binaries. Subsequent source changes use separate Release builds only. + +The five-case `guarded-advance` inventory was frozen in the build validation +directory before candidate 56 while the stable suite runs. Candidate 55a +rejects both positives and all unsafe counterparts. Candidate 56a retains the +conditional initialized interval but still lacks a positive lower cursor bound. +Candidate 56b exports the actual narrowed lower bound and passes all five cases. +A separate five-case `guarded-cursor-bounds` inventory covers zero advance on +failure and positive advance on success. Candidate 56a rejects its two positives +and all unsafe counterparts. Candidate 56c carries constant outcome-specific +bounds on the actual installed cursor through existing pending integer facts; +result and coordinate mutation retain their normal invalidation. + +Candidate 55a's full Debug suite passes 1,511/1,511 tests in 530.23 seconds +(`resume-candidate55a-full.log`). Release development overlapped this run, so +it remains regression evidence, not an isolated performance measurement. +Candidate 56c passes all five guarded-cursor cases and the focused Debug unit +and both guarded source populations. Both inventories were copied unchanged +into the evaluation tree after the stable full suite completed. + +The six-case `span-advances` population is frozen before candidate 57 in the +build validation directory. Candidate 56c accepts the generic count helper +and the single-call client, but rejects repeated traversal. All three unsafe +counterparts reject. Candidate 57 extends the existing bounded-sum implication +to constant endpoints and actual equal captured coordinates, and retains only +independently established bounds when replacing a cursor's old value. + +Candidate 57a passes all six span-advance cases and both focused Debug tests. +The frozen population is registered for regular source and object testing. +The unchanged cJSON workflows remain incomplete. + +The five-case `paired-cursor-loops` population was frozen before candidate 58. +Candidate 57a rejects both positives and all three unsafe counterparts. +Candidate 58a preserves unit-step relations using an established transitive +constant upper bound and passes all five cases. The reduced unchanged string +parser remains incomplete because its escape scan and allocation-size relation +are not yet established; passing the cursor regression is no parser claim. + +Candidate 58a's focused Debug cursor tests pass. Its full suite is running +against stable binaries. The seven-case `numeric-text` inventory was frozen +before candidate 59: candidate 58a rejects both positives and all five unsafe +counterparts. Candidate 59a failed compilation due to a missing internal helper +header; candidate 59b adds that include before further validation. + +Candidate 59b builds. Review before the next candidate identified two content +invalidation requirements: unknown writes must also remove pending numeric +content, and an unrepresented existential string endpoint cannot count as an +empty interval. Candidate 59c applies both and preserves content through only +recognized byte-copy primitives. Zero-byte facts keep their existing canonical +representation. The separate seven-case `numeric-scans` inventory is frozen +before scan inference; its candidate 58a baseline rejects both positives and +all five unsafe counterparts. + +Candidates 59b and 59c pass all seven frozen numeric-text cases. A separate +copy probe remains incomplete: byte-copy primitives return from the runtime +path before general postcondition capture. Candidate 60a adds only the bounded +switch-scan prefix and alphabet-preserving stores; candidate 60b adds explicit +snapshot-based content transfer for modeled memcpy/memmove and accounts for +the source interval's actual storage offset. General helper output contracts +continue to discard content guarantees. + +The complete candidate 58a Debug run reports 1,517/1,518 passing tests in +849.54 seconds, overlapping Release builds. The existing terminated scan after +compaction test regressed; its original assertion remains unchanged and the +failure is under investigation. Candidate 60b still rejects both numeric-scan +positives: the platform's fortified builtin spelling was not normalized during +content capture. Candidate 60c uses the existing builtin-name normalization. + +Candidate 60c proves the numeric-scan floating conversion, but both positives +retain a pre-existing release failure after strtod with a null end pointer. +Candidate 61b guards that modeled store and applies RFC 0009's refuted-store +rule to source escape. The reduced unchanged numeric parser still lacks its +content proof. Exploratory declaration/constant variants are separate files; +one allocation-declaration variant had a source-generation typo and is invalid +evidence. Candidate 61a's stronger symbolic cursor bound does not fix the +compaction regression; candidate 61c restricts that retained terminator bound +to independently proved constant endpoints. Both numeric populations retain +their original inventories and are now registered with source, object and lit +checks, plus Core and Analysis unit coverage. + +Candidate 61b passes all seven numeric-scan cases. The unchanged reduced +numeric parser loses its scanned numeric prefix on a later private local +pointer assignment, before memcpy. A separate three-case `numeric-scan-locals` +inventory freezes two equivalent local-write positives and an actual input +alias mutation before candidate 62. Candidate 61b rejects both positives and +the intended unsafe counterpart. Candidate 62 preserves existing byte facts +across writes confined to unexposed automatic scalar cells. + +Candidate 62a passes all three private-scalar cases, all seven numeric-scan +cases, all seven numeric-text cases, all six span-advance cases and all five +paired-cursor cases. The original compacted-string traversal probe also passes. +The unchanged numeric-parser probe still rejects its floating conversion, so +these results do not establish the mandatory upstream workflow. The private +scalar inventory is copied unchanged into the regular source/object tests. + +Candidate 62a's six focused Debug tests pass, including the original compacted +string regression. Its full 1,526-test suite is running against stable binaries. +The separate five-case numeric-short-circuit inventory was frozen before +candidate 63: both equivalent positives reject at 62a while all three unsafe +cases retain their intended rejection. Candidate 63a maps bounded condition +subexpressions to the loop and preserves the non-strict visited-prefix fact +before every short-circuit test. All five cases pass, and the reduced client +using unchanged cJSON parse_number now passes. This is a helper-client result, +not completion of the complete parser lifecycle. Formatting and the Core +include boundary pass at 63a. + +The complete candidate 62a Debug run passes 1,525/1,526 in 634.53 seconds, +overlapping Release development. The one failure is Builtins.Entries, whose +exact strtol store expectation still omits the newly required non-null output +guard. The compaction regression and every new source/object population pass. +Candidate 63a's complete upstream run still fails both positives; print-delete +has a complete zero-requirement selected entry but retains the two ordinary +generic-body diagnostics. Parse-delete retains 174 unresolved entry obligations. + +Candidate 64a retains a proved updated physical cursor bound across branch +joins. It passes the inline two-pass cursor case and the compacted-string +probe, but its equivalent raw-pointer/count helper remains incomplete. The +original cursor-envelope-joins inventory is retained; before implementation, +review found its forged-capacity literal stopped at an early quote. The +separate reviewed inventory removes that closing quote. ASan confirms that +reviewed counterpart's actual out-of-bounds read. The over-step counterpart +forms an out-of-object pointer but does not dereference it; ASan/UBSan does not +diagnose that pointer-formation UB. Both inventories retain every observation. +The actual reduced string parser still fails, although its isolated two-pass +input traversal now passes. The pointer-count-readers inventory is frozen +before candidate 65, with both positives rejected and all three unsafe cases +rejected by candidate 64a. + +Candidate 65a adds explicit pointer/count interval premises but both helper +positives still reject. Temporary diagnostic builds (65debug and 65debug2) +showed a deeper identity error: an adjusted pointer inherited the original +pointee's exact value. The four-case shifted-pointee-facts population freezes +three actual out-of-bounds writes that candidate 65a falsely accepts, plus +an unchanged-pointer positive. Candidate 66 removes cross-cell subtree facts +for nonzero or unknown element displacements. The temporary debug logging is +removed before that candidate; the original reports remain available. + +Candidate 66a passes all four shifted-pointee cases, all five pointer/count +cases and all five reviewed cursor-join cases. ASan and UBSan independently +confirm all three shifted-pointee unsafe executions; the unchanged-pointer +control runs clean. The numeric-parser client remains complete. These three +frozen populations are registered for regular source/object validation and +the relevant lit and unit tests. The original cursor inventory and failed +candidates remain retained in the build validation directory. + +Candidate 66a's full Debug suite passes 1,532/1,532 in 561.67 seconds, +overlapping Release development; this is not an isolated performance run. +Its seven focused Debug checks also pass. The complete upstream population +still has 174 unresolved selected parser obligations, while the complete empty +print entry is blocked by two ordinary generic-body diagnostics. + +The frozen buffer-lower-extents population reproduces a false physical bound: +a four-byte backing array advertised as capacity two is diagnosed as an actual +two-byte allocation. Candidate 67a keeps this capacity in the positive checked +memory domain without inventing an exact ordinary spatial extent. It removes +the cJSON ensure memcpy bounds diagnostic, leaving the ordinary print null +warning. Candidate 67a still cannot project the additional entry interval. + +The six-case buffer-entry-intervals population was frozen before candidate 67b. +67b projects additional bytes only from unchanged entry backing, but also +projects an unnecessary non-null premise that regresses six existing RFC 0026 +positives. Candidate 67c restricts this additional projection to extent and +initialization. Both new positives complete; every intended unsafe case rejects. +Two original reason regexes omitted the word `extent` although the bounds +precondition is reported. Their original manifests remain unchanged in the +build validation directory. The separately reviewed inventory adds that word, +keeps all source bytes and outcomes unchanged, and passes all six cases. It is +registered for source/object and lit checks, with a focused unit test. + +Candidate 67c preserves all 28 RFC 0026 source cases and the unchanged fixed +evaluation: 44/44 bugs, 32/32 clean programs, zero parse/tool errors or timeouts. +The numeric-parser client remains complete at 67b. Three temporary tracing +builds fail compilation due to trace-only API mismatches; they supply no proof +or performance evidence. The production candidate67c snapshot is unchanged. + +The first focused 67c Debug run passes both source populations but the new +unit's additional assertion about the unrestricted generic helper fails. Its +actual positive/negative caller assertions pass; the generic assertion was +unrelated to the physical-capacity regression and is removed. A subsequent +run passes the caller unit. An initial Werror build rejected the new unit's +signed initializer-list literals; they were corrected to unsigned literals. +The separate ordinary recursive-null-output unit already passes at 67c, so it +does not reproduce the remaining cJSON null warning. Further diagnosis is +required before changing recursive result inference. + + +### Candidate 68: settled ordinary value outcomes (in progress) + +A temporary trace of cJSON's recursive print group shows a positive non-null +buffer outcome in the freshly checked body, lost when intersected with an early +SCC approximation. Candidate 68a preserves the final rechecked null outcome +maps after actual convergence. Its Release build succeeds, but the unchanged +upstream source population still fails parse/delete and print/delete. Parse's +selected main has 168 unresolved obligations and no entry requirements. Print's +selected main is complete with zero entry requirements, while an ordinary +null diagnostic at cJSON.c:1280 still fails the invocation. The double-release +counterpart continues to reject. + +Further traces show that the non-null fact now reaches the actual +`buffer[0].buffer` cell, but an earlier numeric approximation still permits a +negative success flag. The final body independently establishes only 0 and 1. +Candidate 68b extends the same existing final pass to retain the numeric return +root's actual alternatives, preserving all guards, unknown alternatives and +existing widening of numeric writes to caller memory. No additional SCC round, +checked memory output, completed call or recursive hypothesis is introduced. +Temporary trace sources were removed before the production build. The traces +and failed upstream observation remain in `build/rfc29-validation`. + +Separate frozen caller and three-member value-outcome probes include real +negative failure results, false success, and a pointer overwritten after the +call. The first simpler caller population already passes at candidate67c and +is supplementary coverage, not a reproducer. All production candidate67c and +68a binaries remain immutable. + +Candidate 68b passes the fixed 44-bug/32-clean evaluation, all 28 RFC 0026 +source cases, and 81 focused Debug unit tests. Both source upstream positives +remain rejected; the double-release case still rejects. The upstream source +population takes 177.42 seconds while a Debug build and other checks run, so +this is not isolated performance evidence. Both smaller ordinary recursive +value-outcome cases already pass at candidate67c and remain supplementary. + +The remaining ordinary print warning exposes an independent cross-domain bug: +the tested numeric value's range is checked for a wholly impossible branch, +but its surviving sign classes are not used to narrow pending call outcomes. +Consequently a real 0-or-1 result still retains an obsolete negative outcome +when selecting nonzero. A separate-source unit exercises a conservatively widened outcome set and +independently inferred numeric returns, with actual negative-failure and saved +result counterparts. It already passes on candidate68b and does not reproduce +the cJSON failure; the unchanged upstream case remains the reproducer. + +Candidate 68c intersects pending call outcome selections with the current +trusted numeric value's sign classes after the existing conversion checks. +The standalone ordinary cJSON print null diagnostic disappears. Candidate68d +also avoids creating null outcome maps when the widened summary has no +represented outcome partition. Release and Debug builds pass. The unchanged +upstream source population now accepts print/delete with a complete selected +main, zero entry requirements and no ordinary error; parse/delete remains +incomplete and the double-release counterpart rejects. The 107 focused Debug +integer, recursion and buffer units pass. Full transport and remaining gates +are still required. + +The frozen `in-place-extension` population precedes any in-place construction +change: runtime-depth extension of an existing owned head fails at candidate68b, +while lost-child, duplicate-child, nondecreasing recursion and failed-cleanup +counterparts reject. These are development observations, not a completed gate. + +Candidate68d also passes the fixed 44-bug/32-clean population. Its four frozen +extended upstream cases remain incomplete: nested printing, malformed-input +cleanup, forced parse allocation failure, and forced print allocation failure. +The forced parse client has two unresolved obligations, including a missing +numeric global during contract transport. These failures remain in scope. + +### Candidate 69: extension of an existing owned head (in progress) + +The RFC now specifies an unconditional `container-extended` output relating +an unchanged singleton input head to fresh descendants. Candidate69a adds strict +encoding and input-premise validation, call ledger transfer, and a private +recursive candidate with an unsigned decreasing count. Its first Release and +Debug builds fail because the contract-kind table size was not increased; both +builds pass after correcting the table size. Five focused Core contract tests +pass. In the frozen in-place population, the safe recursive case remains +incomplete and all four negative cases reject. This is not acceptance evidence +for recursive extension. + +The private group trace shows that its recursive call establishes the new +footprint, but loses separation from the original head when reinstalling the +call output. Candidate69b carries that pre-call separation only while the +neighboring complete forest fact survives unchanged, and retains the unchanged +head's live storage identity. Validation is pending. + +Candidate69b passes all five frozen source cases and all five ordinary-object +checked links. The safe constructor and closed client are complete; the four +counterparts retain their intended ownership/progress failures. The registered +unit/source checks pass, as do 113 focused Debug regression units and the new +lit test. A separate ASan/UBSan oracle enumerates depths zero through nine and +each allocation-failure position: the positive releases every acquisition; +lost-child, duplicate-child and failed-cleanup variants fail the allocation +ledger or sanitizer. Nondecreasing recursion is a proof-progress rejection, +not a claim corroborated by that finite memory oracle. The first object harness +attempt omitted its binary-identity argument, and the first lit invocation used +a nonexistent executable path; corrected invocations pass. These do not replace +the required full sanitizer suite or transport/checkpoint gates. + +### Candidate 70: anonymous typedef storage metadata (in progress) + +A temporary tracing binary identifies `global_error` as the missing global in +the forced parse-failure client. Its anonymous typedef record has a canonical +view containing the typedef identity, which the imported adapter cannot +reproduce. A frozen two-source `anonymous-private-state` reproducer rejects at +candidate69b. Candidate70a retains that anonymous typedef identity in strict +`it2` interface metadata, creates an internal typedef for the adapter, and +retains full target-layout/view validation. Forged or missing identities still +fail materialization. Temporary trace code is removed from production sources. + +A separately frozen global-cell/forest-frame probe already passes all four +cases at candidate69b. It is supplementary evidence, not a reproducer of the +remaining forced-print failure; no frame-rule change is justified by it. + +Candidate70a passes the frozen anonymous-typedef source reproducer, all 24 +focused interface/extension tests, all 553 Core tests, all 86 Frontend tests, +and its new lit test. Both new populations pass all six ordinary-object checked +links. Their expanded uncached/cold/warm and expanded compact reports match; +warm and compact runs reuse every source unit with zero function analyses. +The extended upstream rerun and full Debug suite are running. A temporary +forest-transfer trace build fails Werror on a shadowed member name; the local +trace label is corrected. No failed tracing build is validation evidence. + + +### Resumed candidate 71: validation and retained head values + +Candidate70a's complete Debug suite finishes at 1,536/1,541 in 1,150.46 +seconds while development commands overlap. Two reader unit assertions and +one lit message predate candidate67's additional physical-interval requirements: +the generic helpers now export the required extra interval, and their actual +undersized clients still reject. The units now check both that premise and +the concrete rejection. The recursive reuse assertion also predates candidate68's +mandatory final body recheck and now requires that recheck. These three units, +the complete 176-case lit suite and the two recent object populations pass in +`candidate71-focused.log`. + +The remaining Debug failure is the aggregate RFC0029 object test's 600-second +deadline. It combines 66 independently frozen populations. CTest now schedules +each population separately under the same deadline, retaining every case, +selection and expected outcome. The runner retains its all-populations mode. +The anonymous-private-state split command passes at the immutable candidate70a. + +The forced parse-failure client now rejects only the actual escaped automatic +input stored in cJSON's global error state. The static-storage development +counterpart is complete with zero requirements (`failed-parse-static70a.json`). +The separately frozen `upstream-lifetime-audit` population records both cases; +the original upstream fixture and accepted expectation remain unchanged. +Its committed oracle independently reproduces stack-use-after-return (or scope) +when the error pointer is read after the helper returns, and accepts the static +counterpart. Both sanitizer observations pass in `lifetime-audit71-oracle`. +No invalid read is attributed to the original client, which does not use the +retained pointer after its input lifetime ends. + +Temporary print-failure traces locate a tag value known only through the live +container's head predicate. Call-context capture uses it, but the returned +null guard cannot query it. `failed-print71guards.log` retains this reproducer. +An initial trace probe was launched before its new executable finished linking, +then deliberately terminated; it is not candidate validation. Temporary trace +code is removed from production sources. + + +Candidate71a's shared container-value query proves the unchanged forced-print +failure client with a complete selected main, zero requirements and no ordinary +error (`failed-print71a.json`). The seven reviewed reduced cases pass through +source and ordinary objects. The initial six-case inventory mistakenly expected +a replacement under the deliberately failing allocator to reach its tag write; +the baseline correctly accepts that early-return path. Both original files and +failed observation remain frozen. A separate reviewed manifest retains that +safe case and adds an actual live replacement before the invalid access. + +The focused head-value unit passes all seven variants. Its first lit run used +an ordinary exact-extent diagnostic for the alias-mutated case; the actual +checked rejection reports the bounds obligation after retiring its predicate. +The expectation now pins that explanation; no frozen fixture or outcome changed. +The first candidate71 build failed Werror because a new local name shadowed a +range-query local, and was corrected. An attempted sanitizer command targeted +the obsolete dev-asan configuration, whose make path is unavailable; the active +rfc28-asan build is used instead. Full Debug/sanitizer, strict lint, and final +corpus/cost gates are still pending. + +### Candidate 72: independent byte cursor coordinates (in progress) + +Candidate 71a's fixed evaluation passes all 76 cases (44 bugs and 32 clean +programs). The guard lit test passes after its diagnostic expectation correction. +The concurrently run full Debug suite observed two RFC0026 transport subprocess +timeouts; those need an isolated rerun without competing builds. The original +upstream source population now accepts print/delete with no entry requirements +and rejects the double release. Parse/delete remains incomplete. + +The frozen `independent-cursors` population reduces one decoder failure to a +copy loop whose input and output are distinct arrays. Candidate 71a rejects all +four intended positives. Candidate 72b retains inequalities between their +numeric byte coordinates only when both incoming edges establish them. It +accepts the closed and separate-source clients, including reordered declarations, +and rejects the four bad variants. The generic helper still loses a relation. +Candidate 72c additionally seeds equal exact coordinates at assignments but does +not yet resolve that generic failure. Both observations are retained; the +population is not yet counted as complete. + +The first `candidate72a-bin` snapshot was accidentally made before the linker +finished and has exactly candidate71a's identity. Its evaluation is a repeated +baseline observation, not evidence for the new code. A duplicate upstream +runner was also stopped after inspecting the process tree; its aborted +observation supplies no cost evidence. Production snapshots 72b and 72c were +made only after their builds completed. + +A temporary cursor trace showed the generic copy losing its relation at `q++`: +the previous increment retained the numeric equality, but the nonwrapping query +looked only at explicitly stored integer ranges. Widening had removed a +redundant full-range fact. Candidate72d also queries the validated C type and +current cursor type of represented relation endpoints. A type maximum is used +only to prove arithmetic does not wrap, never as an inferred allocation extent. +Production trace code was removed before this build. + +The separately frozen `fixed-span-steps` population exposes a second gap: +`consume(first,last)` is complete and proves its zero-or-two result fits the +input span, but even, odd and runtime-length closed callers lose the bound when +the nonzero result folds to two. Candidate72c rejects those three positives and +preserves all four bad counterparts. Candidate72d extends the existing +nonwrapping difference query to exact constant endpoints; validation is pending. + +Candidate72d completes all eight independent-cursor source cases. Its constant +span endpoints restore even and odd fixed-size traversal; candidate72e also +removes only proved value-preserving endpoint conversions. A runtime client +whose length comes from `(unsigned)argc` still failed because capture expanded +the stored length back into that earlier conversion. Candidate72f snapshots +the actual typed endpoint cell instead. The runtime source client and both new +unit tests, including the converted-length case, pass under ASan/UBSan. Full +source/object populations and final code gates still need completion. + +The full candidate71a Debug run completed 1,609 tests in 1,956.17 seconds under +concurrent build load. All 1,418 unit tests pass. Eight integration tests fail: +the already-corrected guard lit expectation, two 60-second RFC0026 transport +subprocess timeouts, and six newly separated object-harness tests. Five object +tests previously skipped every case because the harness accepted only the exact +selection `[main]`; one rejected a supported singular `source`/`function` +manifest. The harness now keeps helper-plus-main selections, normalizes the +existing manifest spellings, and supplies an unused ordinary main for the +named closed zero-counter functions without changing their selected functions. +All six corrected object populations pass with immutable candidate72e binaries. +The failed full observation is preserved; it is not a successful full-suite gate. + +Candidate72h extends cursor join completion to finite constant displacements +proved independently by both incoming difference-constraint systems. This +preserves `output <= input + 1` between a decoding helper and its input advance, +without equating the two storage objects. The frozen `helper-cursor-pairs` +population then passes seven of eight cases: the helper, once-only client, +helper-only loop, and all four intended rejections pass. The mixed direct/helper +loop still loses its input upper bound. Evidence is +`helper-cursor-pairs72h.json` and its log; the failed mixed case is retained. +The source run used the completed Debug72h build, before temporary diagnostic +instrumentation. It is development evidence, not an isolated cost observation. +All eight frozen `fixed-span-steps` cases pass production ASan72f in +`fixed-span-steps72f-asan.json`. All six formerly skipped/malformed object +populations pass after the harness correction, in their `objects-*72e` results. + +Candidate72k completes all eight frozen helper-cursor cases. Before recording a +nonzero byte's strict pre-terminator bound, it refutes an edge at the actual +current initialized zero. This prevents an impossible negative coordinate from +polluting a loop merge. The generic helper and mixed direct/helper clients pass; +actual writes and escaped or insufficient intervals remain rejected. + +### Candidate 73: bounded byte contents (in progress) + +The separate `byte-cursor-content` population was frozen before this extension. +Completed Debug72k rejects its three intended positives and rejects all five +bad counterparts. Exact initialized byte records, strict context transport, +write slicing/invalidation, and byte comparisons were then added under the +amended RFC. Candidate73c passes all 555 Core tests but still fails those three +positives and regresses the mixed helper-cursor unit. Candidate73e preserves +only independently proved canonical cursor endpoints through widening and avoids +adding temporary bounds when a byte comparison excludes no positions. It +accepts the local scan and restores the three focused cursor unit tests; its +two separate-source positives still fail. An initial focused test command used +the wrong suite name and ran zero tests; the corrected command ran all three. + +Candidate73f rechecks complete conditional contracts when actual byte contents +are captured, because a generic sufficient requirement can cover an impossible +branch for that input. All eight frozen byte-content source cases pass, including +changed bytes, unknown writes, partial initialization, and an offset pointer. +Two Core tests pass canonical binary encoding, strict decoding and remapping, +shifted alias consistency, and the existing context budget. Temporary context +tracing has been removed. Parser, object/checkpoint, full-suite, and final cost +validation remain outstanding; these focused observations are not final gates. + +Candidate73f also passes the byte-content object population and exact-message +lit test, plus all 37 BufferAnalysis and new byte-context unit cases. Candidate73g +passes its cold/warm/uncached and expanded/compact checkpoint equivalence gates, +including zero unchanged warm function analyses, changed helper and changed byte +invalidation, corrupt checkpoint recomputation, and rejection of older sidecars. +The accompanying compiler binary was still 73f; both identities are retained by +the runner. This is focused transport evidence, not final compiler validation. + +The broader Debug73g Analysis run passes 777 of 779 tests in 125.718 seconds. +Two existing sequential-cursor positives fail. Candidate73i avoids nominating +incidental negative initial distances at cursor joins; both regressions and the +mixed helper loop pass. Candidate73k additionally retains only independently +proved canonical strict/non-strict cursor orders at widening joins. Its five +focused tests pass, including the new postfix allocation permission regression. +The extracted cJSON string routine remains incomplete. + +The separately frozen `byte-cursor-frames` population records further gaps. +Its original positive cases use a local pointer-to-pointer alias for increment; +73f and 73g reject them. An aliased-input negative is rejected for the intended +separation obligation, but the original reason expression omitted the word +`separated`; that manifest and failed observation remain unchanged. A direct +increment reduction also exposed lost write permission for a saved postfix +result into malloc storage. Candidate73j accepts that reduction; its focused +unit still rejects released storage and a substituted string literal. General +indirect pointer-cell updates remain under investigation. + +Candidate73l's byte query intersects ordinary scalar bounds with the already +proved bounded difference constraints. Previously, a valid read through +`cursor < end` could still have a loose scalar maximum one past its array, so +the content query declined and introduced impossible escape paths. The original +extracted cJSON string routine now accepts its `"abc"` client with no selected +entry requirements. All 41 focused tests pass, including an escaped-byte +counterpart. The production binaries are retained in `candidate73l-bin`. + +The immutable-static population was frozen before enabling const static local +array contents. Debug73l fails its three positives and preserves all three +required rejections. The new `upstream-static-inputs` population preserves the +original nested and malformed JSON texts while avoiding the separately audited +automatic-storage lifetime defect. Baselines and subsequent observations remain +separate from the original upstream manifests. + +Portable versions advance to summary 25, sidecar 26 and checked encoding 11 so +interim development caches cannot supply older byte-context semantics. Release, +Debug and sanitizer rebuilds are in progress; no final suite or cost claim is +made by those development builds. + +The first Release rebuild after the artifact-version increment failed while +compiling the new local pointer-cell resolution: the code used a nonexistent +`Affine::isZero` helper. It now compares with the zero affine value. The failed +build log is retained, and no snapshot or validation result uses that unfinished +compiler pair. A fresh build of both tools is underway. + +## Candidate 74: static input, pointer cells and byte forwarding + +The six frozen `byte-cursor-static` cases pass candidate74a. Both unchanged +upstream inputs in the separate static-input population still fail at 74a. +Their safe storage duration removes the audited escaped-error-pointer defect, +but does not by itself establish parser construction. Original upstream +populations and source identities remain unchanged. + +The new local-pointer test exposed inconsistent indirect cursor handling: an +ordinary increment was incomplete and an oversized compound increment was +accepted. Canonical memory lookup now uses the actual exact initialized local +pointer cell's current cursor before consulting older ordinary pointer offsets. +Updates advance that same holder. The clean candidate74g build passes the test +five consecutive times, accepting a unit step and rejecting the out-of-object +step. Earlier 74a/74c/74e failures and the temporary 74d/74f trace observations +remain under `build/rfc29-validation`; no trace code remains in the candidate. + +The separately frozen `byte-cursor-forwarding` population failed its two +forwarded positive cases at 74a. Installing an actual captured byte payload +now restores its independently proved live readable interval, as specified by +the RFC, without granting write permission or an exact physical allocation +size. All eight source cases pass at 74e. + +Candidate74g also gives every `byte-cursor-frames` case its expected proof +outcome. Its original alias diagnostic expectation missed the actual required +separation premise; the original failed manifest is retained. A reviewed +manifest changes only that diagnostic pattern. All 22 ordinary-object cases +across static bytes, forwarding and pointer frames pass. The byte checkpoint +population passes canonical cold/warm/uncached and compact equivalence, changed +helper and changed input-byte invalidation, corrupt recomputation and old +sidecar rejection. Core has 556 passing tests; the refreshed Frontend build has +86 passing tests after correcting its stale model-version expectation. Full +Analysis, sanitizer, integration and final cost gates remain pending. + +Candidate74g's full Debug unit runs completed: 556 Core, 782 Analysis and 86 +Frontend tests pass. The default 18-case checked evaluation passes; the full +76-case fixed population still needs the latest-candidate run. The original Debug73l upstream run exhausted +the unchanged 600-second deadline for each of its three cases under concurrent +development load; all three failed observations remain recorded in +`upstream73l`. Release74g still rejects both static-input upstream positives. + +Candidate75 development freezes ten `byte-global-frames` cases before its +checker changes. At 74g the two forwarded constant-array positives fail, while +the eight remaining expected outcomes pass. The proposed immutable-byte +premise is restricted to the actual constant array object and is recorded in +the RFC before implementation. The primitive byte premises, target arithmetic, +write permissions and lifetime checks remain separate obligations. + +The full 76-case fixed evaluation on candidate74g passes separately from the +18-case checked population: 44/44 bugs detected, 32/32 clean cases, no unexpected +reports, parse failures, tool failures or timeouts. Candidate75a's ten immutable +byte-frame cases pass through both source and ordinary objects, and 30 focused +Core plus three focused Analysis tests pass. + +The 74h sanitizer integration run exposed redundant byte-context nomination +for complete inductive writers and output-slot constructors: their generic +contracts remained complete, but an extra specialized case emitted a failure +diagnostic. Candidate75c retains existing constructor, scalar and alias cases +while declining this additional byte-only nomination for verified complete +induction. Both unchanged original positive programs pass again. The same run +found the RFC0023 cache harness's stale sidecar-version assertion, updated to +26; its deliberate old-format mutation still targets version 20. The 75b build +failed on a shadowed local variable; 75c fixes the naming without suppressions. + +The completed 74h ASan/UBSan run passes 1,626 of 1,635 CTest entries in +879.51 seconds. The nine failed entries are retained in its log: stale static +byte/forwarding objects, the recursive byte-specialization regression, the +RFC0023 sidecar-version expectation, and lit (which also picked up the newly +added immutable-global test). No checker sanitizer crash occurred. A consistent +full rebuild is underway; the build wrapper detects source edits during a +compilation pass and forces those inputs to rebuild before recording source +identities, avoiding misleadingly newer object timestamps from an in-flight +compile. These development observations are not a final sanitizer gate. + +Candidate75a carries constant input bytes through global error-state updates +and into the upstream BOM helper. Its returned call still erased those bytes +when updating the separate local reader header. Eight `byte-helper-frames` +cases were frozen against 75c: its two forwarded positives fail and all six +other outcomes pass. After the RFC amendment, candidate75e preserves exact +input bytes only when a complete helper's every represented write resolves to +separate automatic storage, with no consumption, replacement or escaping +effects. All eight source cases pass. + +### Candidate 75f and bounded comparisons (76) + +The consistent 75f ASan/UBSan rebuild completed a second pass after detecting +four source inputs changed during the first pass. All 13 selected reruns passed +in 25.66 seconds, including the earlier static-byte, recursive-writer, output +construction and container-cache failures and the new global/helper byte frames. +The full 75f sanitizer suite is running separately. Strict changed-file tidy +passed all 19 files rerun after the candidate 74 findings. + +The unchanged upstream static-input parser remains incomplete in 75e, although +exact bytes now reach the whitespace/value/number helpers. Its number case at +the actual offset completes. The string parser still loses its output bound +when the actual input contains multiple later quotes; this is an open precision +gap, not a passing lifecycle claim. + +The `byte-comparisons` population was frozen before comparison-result inference. +Candidate 75e rejected all five intended positives and retained all five +negatives. Candidate 76b passes all ten source cases; the immutable 76c Release +pair passes all ten object cases. The cases distinguish memcmp from string +termination, unsigned character ordering and sign from implementation-specific +nonzero magnitude, and reject changed, partial and short input. Existing modeled +C-library trust remains explicit. Strict tidy passes DataflowRuntime.cpp. + +Development failures retained: 76a used the wrong IntegerRange query name and +failed compilation; 76b uses `constant()` and `signedValue()`. The first focused +unit invocation failed to find string.h in the headerless unit-test environment; +the test now declares the compatible standard signatures like existing runtime +tests. The frozen integration sources and their outcomes were unchanged. + +The full consistent 75f ASan/UBSan suite passed all 1,643 tests (1,433 unit and +210 integration) in 527.12 seconds. This precedes the candidate 76 comparison +and candidate 77 pointer-expression changes, so it is a preservation checkpoint, +not the final RFC gate. Candidate 76's focused comparison unit and lit tests +also pass. Candidate 76c still rejects both upstream static-input lifecycles. + +Candidate 77a exposes a symbolic pointer-difference expression but initially +retained an intermediate expression identifier instead of its validated affine +coordinate. Candidate 77b retains the actual affine equality; the unmodified +extracted parse_string body now completes with the nested JSON bytes and offset +one in `string-nested76-probe.c`. This does not certify the full upstream. +The separately frozen `pointer-difference-sizes` population rejects its two +positives in baseline 76c and intermediate 77a/77d, while keeping all four +negatives. Candidate 77d establishes the pure conditional offset and copy-body +bounds; its remaining failure is the terminator, because widening forgets the +boundary represented by an unchanged pointer's unstepped offset. These failed +observations remain retained. + +Candidate 77h passes all six frozen pointer-difference-size cases through both +source and object checking. The final terminator needed both the independently +proved unchanged-pointer boundary and refutation of an impossible initial +loop-exit edge before it entered the join. The old impossible edge had replaced +the established inequality with its contradictory condition. + +The complete Debug Analysis suite at 77h passed 786/787 tests in 136.03 seconds. +`InitializedAdvanceEndsAtActualOutputPointer` regressed because an opaque +conditional evaluation value entered reusable numeric outputs. Candidate 77j +keeps such captured values in affine queries, while ordinary scalar assignment +and reusable integer expressions retain their existing projection. The failed +observation is retained in all-analysis77h.log. Both initialized-advance tests +and the new six-variant pointer-difference unit pass at 77j, including the +narrowing-conversion negative. The latest upstream 77f static-input lifecycles +still fail; the exact parse_string case is now left only with its allocation +footprint obligation. + +The separately frozen payload-publication population fails both intended +positives in baseline 77f and preserves all four negative outcomes. It tests +publication of an actual live allocation through a local alias, helper allocation, +duplicate ownership, interior and released pointers, and a lost allocation. + +Candidates 78a/78c/78d/78e/78f retained the two rejected payload positives; +their observations remain in payload78*.json/log. The causes were distinct: +missing cross-unit payload nomination, lost input forests across element +writes into fresh byte allocations, uncaptured empty-head values, unaccounted +helper-returned byte allocations, and missing explicit live-entry premises +on portable extension outputs. Candidate 78g passes all six frozen source +cases, including both local and helper allocation and all four negatives. +The caller may ignore the helper's success flag because every returning +outcome proves the unchanged owned head plus its actually acquired region. + +The full Debug Analysis suite at 78c passes all 788 tests in 131.860 seconds. +That suite predates the final payload transfer changes; it is not the final +RFC validation gate. The mandatory nested upstream workflows and final +cost/reuse/sanitizer gates remain outstanding. + +Candidate 78h passes the six payload-publication object cases and the three +new byte-comparison/pointer-difference/payload lit tests. Its eight-variant +payload unit also rejects an overwritten old payload and a leaking helper. +The subsequent full Analysis run passed 785/789: four older positive tests +regressed. Candidate 78i fixes nullable byte-allocation release accounting and +refines actual null slots when discharging a container call, restoring three +tests. Candidate 78l restricts strengthened empty-slot input witnesses to +payload-writing helpers and restores detachment as well. Failed runs and their +reports are retained; these targeted fixes do not substitute for the full gate. + +The separately frozen payload-write-frames population fails all three intended +positives in baseline 78h and retains its three negatives. Candidate 79a verifies +the actual extracted parse_string case with its reader update intact, but the +cross-unit frame clients still lack imported conditional ownership selectors. +Candidate 79b imports those nominations with generation/dependency tracking and +passes all six frame cases and all six original payload-publication source +cases. Aliased-selector and aliased-helper fail specifically on input separation. +The independent payload-oracle79 allocation ledger, built with ASan/UBSan, +confirms all twelve concrete outcomes and exercises every allocation-failure +point of the five positive lifecycles. These extracted-body results do not +certify the full unchanged upstream program; that mandatory run remains separate. + +Candidate 79b passes all 790 Debug Analysis tests (256.534 seconds while other +validation work was running), six payload-write-frame object cases, and all six +payload checkpoint tests. The unchanged warm checkpoint run performs zero +function analyses; changed publisher and imported cleanup bodies invalidate +dependent evidence. Corrupt checkpoints and the old sidecar are rejected. +The unchanged upstream static-input run still rejects both nested and malformed +parse/delete clients. Its exact string-parser case is complete, but caller +composition loses reader storage evidence and the larger parser remains +incomplete. The retained 132 MB report is upstream79b-static/source.json; these +focused successes do not satisfy the mandatory upstream acceptance gate. + +Candidate 80c passes all 790 Debug Analysis tests (141.043 seconds). The exact +unchanged string-parser case now exports its ownership extension on every +return: payload-writer entry premises previously depended on final CFG block +visitation order. Its reader-forwarding wrapper is complete, although the +full unchanged nested parser still fails (upstream80c-static). The new frozen +payload-early-exits population preserves six baseline outcomes. + +The separately frozen payload-reader-returns population rejects both positives +in baseline 80c and passes all six outcomes at 80e. Its forwarding helper +needs the captured complete singleton entry descriptor. Broad nomination +regresses the existing detachment lifecycle by over-specializing cleanup; +reader-forward80e-unit.log retains that failure. The subsequent refinement +keeps direct-release helpers on their existing general ownership input. + + +Candidates 81a–83a add bounded exact-byte while/do-loop partitions and preserve +immutable byte objects across complete helpers with represented writes to +other objects. The frozen byte-loop-partitions population passes all seven +source and object cases; byte-callee-frames passes seven source cases. The +loop unit also rejects an invalid access after the existing 32-partition bound. +The first broader nomination regressed a counter-reset `for` loop (791/792 +Analysis tests); restricting this nomination to while/do loops restores that +case without changing any bound. The reader-return population passes all six +object cases. The unchanged upstream 83a run still fails both required clients, +but its exact number-parser case at input offset 6 is now complete. + +Candidates 82a and 84a fail the helper payload-relocation positive while +retaining the local positive and four negatives. The helper output had dropped +an unused destination's null-slot evidence. Candidate 84c retains common +structural facts in extension outputs and passes all six frozen source cases. +Its first alias unit fails because equivalent current aliases carry distinct +provenance metadata. Candidate 84d preserves each independently live definite +alias's provenance and passes all eight relocation unit variants. These are +intermediate observations, not the final upstream or full validation gates. + + +The full candidate84d Analysis run passes 793/794; the stronger extension +head descriptor exposed an exact-equality check in recursive candidate +validation. Candidate85a checks structural entailment and restores the +existing recursive extension positive. All 558 Core tests pass at 85b and +formatting is clean. The comparison-container-frames population's original +signed recursive sum can overflow, so its claimed positives are invalid; +that immutable population and its failing reports are retained. The separately +frozen comparison-container-frames-reviewed population uses defined unsigned +accumulation. Its three positives fail at baseline84d, and all seven source +and object outcomes pass at85a. The numeric parse_value wrapper now verifies +for the exact offset-6 input, but its closed cleanup client still fails because +modeled strtod invalidates the incoming forest despite writing only a local +end-pointer cell. The mandatory full nested clients remain incomplete at85a. + + +Candidate85b passes all 795 Analysis tests in 142.785 seconds. Candidate86a +passes all seven numeric-container-frame source and object outcomes; baseline85a +rejects its three positives. Candidate87a similarly passes all seven temporary +release source and object outcomes, where baseline86a rejects all three positives. +These retain uninitialized strings, writes into owned payload slots, stale nodes, +interior releases, double releases, leaks and attached released payloads as +negative cases. The exact number-parser body then exports conditional structure +on both returns, but forwarding loses it because selector values differ. + +Candidate88a passes the extracted unchanged number-parser plus forwarding and +cleanup probe, but the new container-outcome-frames population exposes a false +proof: changing the payload ownership bit is accepted when stale conditional +footprint names reconnect a changed structure to the caller's entry ledger. +The immutable baseline87a rejects that mutation and accepts its explicitly +branched positive; direct and forwarded positives still fail at88a. This failed +candidate is retained and must not serve as final soundness evidence. The next +revision invalidates those affected conditional names at helper boundaries and +allows a selector store to retain an existing payload only when its old and new +proved bits select the same ownership branch. + + +Candidate88c restores all six container-outcome-frame source and object +outcomes, including the rejected ownership-bit mutation. The independent +outcome88-oracle uses an allocation ledger under ASan/UBSan: all 24 expected +outcomes agree, including each of the three allocation-failure points in each +of the six workflows. It detects the released-head access and both payload +leaks when construction succeeds. All 559 Core tests, three focused Analysis +units and six selected new lit tests pass; source formatting is clean. These +runs are intermediate, pending the complete upstream and final regression gates. + + +The full candidate88c Debug Analysis run passes all 798 tests in 164.589 +seconds. The unchanged fixed evaluation detects 44/44 bugs and accepts 32/32 +clean cases, without parse errors, tool failures or timeouts. CMake formatting +and the Core Clang/LLVM dependency boundary also pass. These observations do +not replace the still-running unchanged upstream workflow or the final isolated +performance, corpus identity, reuse and sanitizer gates. + + +All eight files in the targeted strict tidy88 run pass after correcting braces +and nested test-condition syntax; the changed recursive-extension and dataflow +files also passed the preceding tidy86 run. The unchanged upstream88c static +parser population still rejects both clients. The number-parser wrapper is +complete and preserves its forest, but its successful offset remains unknown: +the offset-bounded for scan merged delimiter exits before conversion. + +Candidate89a nominates ordinary byte-switch for scans under the unchanged +32-partition limit. The extracted unchanged number-parser lifecycle now exports +exact successful offset 7 from entry offset 6. The original byte-switch fixture +had colliding helper/client filenames and is retained with its invocation +failures. The separate byte-switch-partitions-reviewed population passes all +eight outcomes at89a; baseline88c rejects its three positives. The existing +decimal counter-reset positive regresses, losing its initialized terminator. +The next revision retains a zero interval only when current half-open bounds +prove the store wholly disjoint; overlapping and unrepresented writes still +lose that evidence. + +A separately frozen upstream-construction population exercises public nested +object/array/string creation, serialization, deletion and output release, +without the parser's retained automatic-input lifetime defect. Its normal +ASan/UBSan run passes before freezing. The first allocation-ledger harness +omitted realloc and correctly aborted on an untracked release; that failed +instrumentation is retained. The corrected construction88-oracle models actual +realloc identity and failure preservation and passes all twelve outcomes: +one complete lifecycle, all nine allocation-failure points, and two output +release mutations. The analyzer's first candidate88c construction probe still +fails; runtime success is not claimed as an inferred proof. + + +Candidate89d passes all eight reviewed byte-switch object outcomes and all +five counter-reset object outcomes; the five counter-reset source outcomes +also pass. Candidate89f passes all 800 Analysis tests in 143.440 seconds. +The new symbolic-zero unit initially used `i < count + 0`, whose numeric +projection loses the required ordering; its plain `i < count` variant passes. +The finalized unit isolates disjoint writes versus actually overwriting the +zero, while the exploratory additive-zero failure remains in the build logs. + + +Candidate89d's unchanged nested parser reaches complete cases for all three +children at offsets 6, 8 and 13. Its array owner still loses head/forest +evidence while composing aliases and tail updates. Both mandatory static +upstream clients remain incomplete. The seven separately frozen string-length +copy cases reject all three positives at89d and pass all seven source and +object outcomes at90b. The targeted unit plus all 24 traversal units pass. +A transparent pointer cast additionally hid a literal's terminator; using +the existing storage-preserving cast classifier makes the unchanged extracted +cJSON duplication helper pass at90c. This does not claim the full serializer. +The seven container-alias-output cases are separately frozen against90b: +two positives fail, its payload positive and all four negatives pass. + + +All seven container-alias-output source outcomes pass at91a. The 91c full +Analysis run passes 802/803; the new alias unit lacks a calloc declaration, +so its failures are frontend errors rather than checker observations. That +fixture declaration is corrected before the next run. Temporary array traces +were removed from source, but restoring a backup's old timestamp initially +left the trace in the Debug object. The source was touched and 91c rebuilt; +these exploratory Debug traces are not final build artifacts. +The separately frozen mixed-helper-frame population still rejects its two +positives at92a. Its bare direct calloc-return wrapper supplies no structural +output; an explicit local initialized constructor isolates a separate missing +owned entry-head conservation premise. These failures remain recorded. + + +Candidate93a adds strict portable non-NaN parameter contexts (summary 26, +sidecar 27, checked encoding 12). Its Core encoding/remapping/bounds unit +passes, but the first Analysis unit reveals unsupported early-return clamps. +Candidate93b proves finite and forwarded clamps; its infinite fixture still +requires a modeled constant compiler intrinsic. Candidate94a passes all six +floating source and object outcomes and all three focused floating units, +including jump, mutation and address-exposure counterexamples. + +Candidate94a's complete Analysis run passes 803/805 tests in 141.390 seconds. +ExplicitReturnExportsAttachedOwnership and DetachmentPreservesBothAllocationPartitions +regress. The independently frozen singleton-link-ownership population exposes +a false proof of a leaked disowned child at93a and94a; immutable91b rejects it. +The direct positive improves at94a but the forwarded positive remains incomplete. +The unchanged extracted one-element array still loses its footprint. Temporary +94 tracing identifies lost separation after a derived callee output, before the +parent selector/child stores. Traces are restored from text backups with current +modification timestamps; they are not production instrumentation. These are +failed development observations, not completion evidence. + + +Candidate95a's extracted one-element array passes after verified derived +outputs retain separation from surviving neighbors and definite head aliases +retain that separation too. Reverting the92a mixed frame only hides the new +negative: the same disowned-child helper without its reader write is also +falsely accepted by91b. A trace confirms that the successful exit transfers +through a fresh child slot, but joining its void returns drops that output. +Candidate96 adds final validation of the portable output carriers used by +per-exit allocation accounting. Its first revision incorrectly rejects nullable +fresh results; the null arm of a proved proper returned forest contributes no +allocation, which96b now handles. The new four-variant transfer unit passes. + +The singleton96-oracle independently tracks allocations under ASan/UBSan. +All fifteen outcomes agree: two positive workflows, lost/released/disowned +children, and failure of either of the two acquisitions for each workflow. +The disowned and lost successes each retain one live allocation; the released +child success trips the sanitizer. These runtime observations corroborate +the counterexamples rather than certifying analyzer soundness. + +Candidate96b passes805/806 Analysis tests in140.971seconds. The remaining +detachment regression was already present in93a and came from over-refining +release-capable entry descriptors at92b. Restoring the generic owned input +except for actual construction nominations fixes it at96e. Redundant preserved +and extended outputs now use the exact preservation instead of adding an +unrelated fresh region. All three focused attachment/detachment/transfer units +pass. Registering actual FieldDecl identities while installing call contexts +restores the independently typed reader field's helper frame; all five frozen +singleton source outcomes pass at96d. The directory initially labeled96b-bin +was found by source hashes to contain96a, was renamed96a-bin, and is not96b +evidence. Singleton96b-source therefore repeats96a's failure;96c-source and +96d-source are the later Debug observations. + + +Candidate96e passes the targeted attachment, detachment and surviving-transfer +units; all five singleton object outcomes and all seven alias-output source +and object outcomes pass. Candidate97a passes all109 recursive/ownership units +in65.905seconds. The separately frozen repeated-fresh-output population rejects +its three positives at96e and97a while retaining four negatives. The independent +repeated97 allocation ledger under ASan/UBSan agrees on all49 normal and injected +failure outcomes. Tracing finds a genuine lost relation name: reassigning the +temporary allocation holder removes it from an older head's ancestor set, +although its shape, members, inputs and ownership are unchanged. Candidate97b +permits only this loss of hints when retaining a fresh-call separation frame; +the extracted inline three-element array remains incomplete. The next local +fold change shares a verified updated head with its still-live definite aliases. + + +Candidate97e retains the existing24 container-analysis tests but fails the new +local-fold alias positive and the three-element array. Candidate97g fixes the +local-fold unit by proving separation between complete concrete allocation +graphs and carries all retained proper ancestors to a newly attached child. +The array then has valid structure through its loop, but restoring an enclosing +prefix loses its prior separation from the outer item. The item selector write +therefore retires the completed list. Candidate97i preserves independently +proved prefix separation and passes the extracted inline three-element array; +its ordinary per-element helper variant remains incomplete. All110 recursive +and ownership units pass in65.486seconds. The temporary97f/97h tracing is removed +from production source. The repeated-fresh-output population still fails its +three positives at97g and keeps all four negatives; no successful full nested +upstream workflow is claimed. Candidate98 adds verified helper prefix frames, +with final regressions and upstream acceptance still pending. + +### Candidates 99-102: outcome-specific ownership transfer + +Candidate 98d's retained state failed three regressions before any new rule. +A conditional size argument such as `min(a, b)` lost its relation to the +operands because the evaluation-site union replaced the recognized minimum; +preferring the structured expression restores the cJSON print copy. Directly +returning a complete callee's fresh byte result settled no allocation, and a +complete release helper did not settle a represented ordinary allocation head. +Both now use the same single-allocation accounting as the modeled release. +With those three corrections the unchanged pinned `print-delete` upstream +client passes again; it had regressed between candidates 77f and 78h and the +retained candidate98b binary still rejects it. + +The helper-frame reader variant also failed: a write confined to an automatic +object retired a forest whose every node was allocated in this invocation. +An automatic object and a heap allocation are distinct objects, so that frame +now preserves such forests, and an allocation identity stays separated from +this invocation's automatic objects after attachment retires its resource +record. The stale Frontend model-version assertion was corrected to 26 and the +paired-reader-counters lit expectation to the actual extent diagnostic; no +checker behaviour changed for either. + +Candidates 99-102 add the attaching-helper transfer the upstream construction +workflow needs. `container-combined` may now carry the constant `end` value +one: the final forest is the disjoint union of both complete entry footprints +and a possibly empty fresh region this call acquired. Two incoming complete +singleton heads related by an explicit distinct-object premise are separated +at entry, which is what lets an attach helper prove the relation at all. A +function whose return expression is exactly a complete call's integer result +forwards that call's outcome-specific outputs into its own outcomes, and an +immediate test of a call's own result installs the verified transfer on its +CFG edge together with the structural outputs on that transfer's own paths. +A more specific output stating that a path's final footprint is exactly its +incoming regions retires that call's fresh region for the path. + +The first revision replayed every outcome-specific output on the tested edge. +That accepted two leaks: replaying an unrelated path's captured entry +description erased the caller's evidence for a still-live allocation. The +failed candidate and its four-variant reproducer are retained. Restricting the +structural replay to the transfer's own paths restores both rejections while +keeping the positive workflow. + +The separately frozen `attached-payload-transfer` population covers this rule +across two units: a successful attach transfers both owned inputs plus the key +it allocated, and losing the item on the failure path, releasing it twice, or +ignoring the result all reject. All four cases pass at candidate 102 and the +immutable candidate98b binary rejects the positive. Extracted unchanged cJSON +object and array attach lifecycles also pass, including `cJSON_CreateObject` +with `cJSON_AddItemToObject` and `cJSON_Delete`. Attaching a second element to +a non-empty parent, and the full nested construction and parse clients, remain +incomplete. + +Candidate 103 fixes two strict-tidy findings exposed by the changed files, one +of them an excessive-padding report caused by a new flag; the bounded scan now +marks exhaustion with a null entry in its own set. All eleven changed checker +files pass strict clang-tidy with no warning or error. + +At candidate 103 the Debug unit suites pass 811 Analysis, 561 Core and 86 +Frontend tests, the complete 205-case lit suite passes, source and CMake +formatting are clean, the Core Clang/LLVM dependency boundary holds, and the +unchanged fixed evaluation detects 44/44 bugs and accepts 32/32 clean programs +with no parse failure, tool failure or timeout. Twenty-one related frozen +populations covering payload publication, relocation, reader returns, early +exits, write frames, singleton ownership, in-place extension, outcome and +alias frames, container call and local frames, construction, mutual +construction, construction helpers, temporary release, numeric and comparison +container frames, the mixed and reallocation ledgers, and the recursive and +output transports all pass with no failure. + +Of the mandatory upstream clients, `print-delete`, `parse-double-bad`, both +construction output-release mutations and both lifetime-audit cases pass, +while `parse-delete`, `malformed-delete`, `nested-serialize` and `nested-print` +remain rejected. Full sanitizer, corpus identity, warm-reuse and isolated cost +gates have not been rerun since candidate 88c and remain outstanding. RFC 0029 +therefore stays Accepted, not Implemented. + +### Candidate 103 cost measurement and the outstanding blocker + +The corpus completion and cost gates were re-measured for the first time since +the RFC 0028 baseline. A baseline checker was rebuilt from `bbf14c3` in a +separate worktree with the same Release, LTO and LLVM configuration, so the +linenoise numbers below are measured on both binaries with one command on one +machine. The other baseline seconds are the committed RFC 0028 cold-coverage +record rather than a rebuild, and every candidate-103 figure is a single +repetition rather than the three isolated runs the gate requires. + +Checked whole-program contract mode: + +| project | selected | complete before | complete now | seconds before | seconds now | +| --- | --- | --- | --- | --- | --- | +| log.c | 12 | 5 | 5 | 0.2 | 0.6 | +| cJSON-program | 151 | 33 | 38 | 30 | 60 | +| linenoise-program | 88 | 27 | 27 | 17 (10 measured) | 277 (over 400 measured) | +| jansson | 211 | 24 | 25 | 118 | 183 | +| lua | 1157 | 61 | no report | 593 | exceeded the 600-second deadline | + +Two results follow. Completion improved by six contracts across the four +projects that still finish, five of them in cJSON and one in jansson, and the +RFC's own 150-of-1,619 headline can no longer be computed because lua no +longer produces a report. Cost regressed far past the mandatory gate of 1.10 +times baseline, and lua now breaches the 600-second project deadline. + +The driver is case-specialization growth, not a single slow rule. One +linenoise unit takes 3 seconds on the baseline and 67 seconds now, with these +counters: + +| counter | baseline | candidate 103 | ratio | +| --- | --- | --- | --- | +| checked_case_requests | 2,581 | 17,087 | 6.6 | +| checked_case_analyses | 265 | 1,396 | 5.3 | +| specialization_hits | 2,398 | 15,081 | 6.3 | +| function_analyses | 602 | 1,719 | 2.9 | + +Ordinary analysis is unaffected at 3 seconds before and 2 seconds now, so the +continuous-integration pinned-corpus gate, which runs ordinary mode under a +120-second timeout, is not implicated. The regression is confined to checked +contract mode and was already present in the earliest retained candidate +binary, candidate20, so it entered within the first twenty candidates and went +unobserved for more than eighty. + +This is a release blocker for the implementation. The nomination filters that +are specified to decline uninformative cases are not holding against the +byte-content, non-NaN, read-only-record and combined callback contexts added +by this milestone. Until the request count is brought back near baseline, the +checker portion of this work cannot satisfy section 6. + From 9be55b9a8b897813050133877a0378ea33f6d94b Mon Sep 17 00:00:00 2001 From: Owen Carey <37121709+owenthcarey@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:19:45 -0700 Subject: [PATCH 2/6] feat!: infer ownership transfer for attaching helpers Implements the accepted recursive-workflow design: semantic discovery of buffer and cursor roles, relational inputs and outputs projected through helpers, recursive contract groups checked as a unit, synchronous callback requirements as caller obligations, and the byte-level reasoning the parser workflows need. The headline new capability is the attaching-helper transfer. A complete attach now publishes the disjoint union of both owned inputs and the payload it allocated itself, forwards that guarantee through a direct return, and installs it on an immediate test of the call's result. This verifies the unchanged upstream object and array attach lifecycles. Also corrects four general defects found while validating it: a recognized minimum losing its relation to its operands, a directly returned fresh result settling no allocation, a complete release helper not settling a represented allocation head, and an automatic-storage write retiring a forest built entirely from this invocation's allocations. The third of these had silently broken a mandatory upstream client for roughly twenty candidates. Portable records move to summary format 26, sidecar format 27 and checked encoding 12, with no compatibility readers. Existing objects must be rebuilt and existing analysis caches discarded. Not ready to merge: checked contract mode fails the mandatory cost gate. See docs/validation-rfc0029.md for the measurements and the diagnosis. --- README.md | 10 +- docs/architecture.md | 72 +- docs/checked-code.md | 223 +- include/weavec/Analysis/Summaries.h | 47 + .../weavec/Analysis/TranslationUnitAnalysis.h | 1 + include/weavec/Core/Buffer.h | 3 + include/weavec/Core/CallContext.h | 13 +- include/weavec/Core/CheckedIO.h | 4 +- include/weavec/Core/Container.h | 2 + include/weavec/Core/Induction.h | 28 + include/weavec/Core/IntegerExpression.h | 32 + include/weavec/Core/Interface.h | 1 + include/weavec/Core/Safety.h | 21 + include/weavec/Core/Summary.h | 13 +- include/weavec/Core/SummaryIO.h | 2 +- include/weavec/Frontend/Sidecar.h | 2 +- lib/Analysis/Builtins.cpp | 9 +- lib/Analysis/CMakeLists.txt | 10 + lib/Analysis/CallContextSummaries.cpp | 92 +- lib/Analysis/CallbackSummaries.cpp | 2 + lib/Analysis/Dataflow.cpp | 509 ++++- lib/Analysis/Dataflow.h | 199 +- lib/Analysis/DataflowArrays.cpp | 78 +- lib/Analysis/DataflowBufferContracts.cpp | 33 +- lib/Analysis/DataflowBufferDiscovery.cpp | 269 +++ lib/Analysis/DataflowBuffers.cpp | 254 ++- lib/Analysis/DataflowByteContents.cpp | 90 + lib/Analysis/DataflowCallContext.cpp | 217 +- lib/Analysis/DataflowCallbackContracts.cpp | 211 ++ lib/Analysis/DataflowCallbacks.cpp | 101 +- lib/Analysis/DataflowCheckedIntegers.cpp | 4 +- lib/Analysis/DataflowContainerContracts.cpp | 126 +- lib/Analysis/DataflowContainerTransfer.cpp | 652 +++++- lib/Analysis/DataflowContainers.cpp | 210 +- lib/Analysis/DataflowCursors.cpp | 170 +- lib/Analysis/DataflowFloating.cpp | 157 ++ lib/Analysis/DataflowFootprints.cpp | 735 ++++++- lib/Analysis/DataflowIntegerExpressions.cpp | 80 +- lib/Analysis/DataflowIntegerStatements.cpp | 10 +- lib/Analysis/DataflowIntegers.cpp | 31 +- lib/Analysis/DataflowNumericInputs.cpp | 23 +- lib/Analysis/DataflowNumericOutputs.cpp | 39 +- .../DataflowRecursiveConstruction.cpp | 423 ++++ lib/Analysis/DataflowRecursiveExtension.cpp | 204 ++ lib/Analysis/DataflowRecursiveWriting.cpp | 160 ++ lib/Analysis/DataflowRuntime.cpp | 117 +- lib/Analysis/DataflowSafety.cpp | 371 +++- lib/Analysis/DataflowSafetyCalls.cpp | 175 +- lib/Analysis/DataflowSafetyContracts.cpp | 209 +- lib/Analysis/DataflowSafetyLoops.cpp | 327 +++ lib/Analysis/DataflowSafetyMemory.cpp | 355 ++- lib/Analysis/DataflowSpans.cpp | 297 +++ lib/Analysis/DataflowStringTraversal.cpp | 267 ++- lib/Analysis/DataflowStrings.cpp | 4 +- lib/Analysis/DataflowTraversalRelations.cpp | 367 +++- lib/Analysis/FloatingCastSupport.cpp | 209 ++ lib/Analysis/FloatingCastSupport.h | 25 + lib/Analysis/FunctionAnalysis.cpp | 2 +- lib/Analysis/InterfaceTypes.cpp | 12 + lib/Analysis/PlaceBuilder.cpp | 11 +- lib/Analysis/RecursiveContracts.cpp | 312 +++ lib/Analysis/RuntimeModels.cpp | 23 +- lib/Analysis/RuntimeModels.h | 3 +- lib/Analysis/Summaries.cpp | 133 +- lib/Analysis/TranslationUnitAnalysis.cpp | 32 +- lib/Core/AnalysisState.cpp | 3 +- lib/Core/Buffer.cpp | 8 +- lib/Core/CMakeLists.txt | 1 + lib/Core/CallContext.cpp | 89 +- lib/Core/CheckedContract.cpp | 85 +- lib/Core/CheckedIO.cpp | 74 +- lib/Core/Container.cpp | 13 + lib/Core/Induction.cpp | 39 + lib/Core/Interface.cpp | 13 +- lib/Core/Relation.cpp | 14 +- lib/Core/Safety.cpp | 90 +- scripts/checked-containers.py | 4 +- scripts/checked-workflows.py | 810 +++++++ test/Analysis/rfc0002-borrows.c | 2 +- test/Analysis/rfc0029-callback-contracts.c | 13 + .../rfc0029-character-pointer-slots.c | 4 + .../Analysis/rfc0029-container-value-guards.c | 6 + test/Analysis/rfc0029-floating-conversions.c | 20 + test/Analysis/rfc0029-numeric-input.c | 7 + test/Analysis/rfc0029-record-arrays.c | 11 + test/Analysis/rfc0029-recursive-cases.c | 7 + test/Analysis/rfc0029-recursive-traversal.c | 6 + test/Analysis/rfc0029-scalar-cell-identity.c | 4 + test/CMakeLists.txt | 230 ++ test/Driver/rfc0005-weavec-cc.c | 2 +- test/Driver/rfc0014-callback-link.c | 2 +- test/Driver/rfc0016-composition-link.c | 2 +- test/Driver/rfc0017-numeric-link.c | 2 +- test/WholeProgram/rfc0011-extents.c | 2 +- test/WholeProgram/rfc0012-sized-fields.c | 2 +- test/WholeProgram/rfc0013-heap.c | 2 +- test/WholeProgram/rfc0015-arrays.c | 2 +- test/WholeProgram/rfc0029-advance-outcomes.c | 4 + .../rfc0029-anonymous-private-state.c | 3 + .../rfc0029-attached-payload-transfer.c | 6 + .../rfc0029-bounded-string-cursor.c | 6 + .../rfc0029-buffer-entry-intervals.c | 7 + .../WholeProgram/rfc0029-byte-callee-frames.c | 3 + test/WholeProgram/rfc0029-byte-comparisons.c | 6 + .../rfc0029-byte-cursor-content.c | 6 + .../rfc0029-byte-cursor-forwarding.c | 6 + .../WholeProgram/rfc0029-byte-cursor-frames.c | 6 + .../WholeProgram/rfc0029-byte-cursor-static.c | 6 + .../WholeProgram/rfc0029-byte-global-frames.c | 6 + .../WholeProgram/rfc0029-byte-helper-frames.c | 6 + .../rfc0029-byte-loop-partitions.c | 4 + .../rfc0029-byte-switch-partitions.c | 3 + .../rfc0029-cast-reader-intervals.c | 5 + .../rfc0029-comparison-container-frames.c | 3 + .../rfc0029-conditional-count-arguments.c | 4 + .../rfc0029-container-alias-outputs.c | 3 + .../rfc0029-container-outcome-frames.c | 3 + .../rfc0029-counter-reset-ranges.c | 4 + test/WholeProgram/rfc0029-fixed-span-steps.c | 4 + .../rfc0029-floating-call-premises.c | 3 + test/WholeProgram/rfc0029-guarded-advance.c | 4 + .../rfc0029-guarded-cursor-bounds.c | 4 + .../rfc0029-helper-cursor-pairs.c | 6 + .../WholeProgram/rfc0029-in-place-extension.c | 7 + .../rfc0029-independent-cursors.c | 5 + .../rfc0029-initialized-advance.c | 4 + test/WholeProgram/rfc0029-initialized-spans.c | 5 + .../rfc0029-local-callee-copies.c | 5 + .../rfc0029-numeric-container-frames.c | 3 + .../rfc0029-numeric-scan-locals.c | 4 + test/WholeProgram/rfc0029-numeric-scans.c | 4 + .../rfc0029-numeric-short-circuit.c | 4 + test/WholeProgram/rfc0029-numeric-text.c | 4 + .../rfc0029-output-construction.c | 7 + .../rfc0029-paired-cursor-loops.c | 4 + .../rfc0029-paired-reader-counters.c | 4 + .../rfc0029-payload-early-exits.c | 4 + .../rfc0029-payload-publication.c | 5 + .../rfc0029-payload-reader-returns.c | 3 + .../WholeProgram/rfc0029-payload-relocation.c | 3 + .../rfc0029-payload-write-frames.c | 5 + .../rfc0029-pointer-count-readers.c | 4 + .../rfc0029-pointer-difference-sizes.c | 5 + .../rfc0029-pointer-reader-offsets.c | 5 + .../rfc0029-reader-construction.c | 9 + .../WholeProgram/rfc0029-reader-index-loops.c | 5 + test/WholeProgram/rfc0029-reader-projection.c | 7 + .../rfc0029-recursive-construction.c | 9 + test/WholeProgram/rfc0029-recursive-writer.c | 6 + .../rfc0029-reverse-byte-writes.c | 5 + .../rfc0029-reverse-initialization.c | 4 + .../rfc0029-shifted-pointee-facts.c | 4 + .../rfc0029-singleton-link-ownership.c | 5 + test/WholeProgram/rfc0029-span-advances.c | 4 + test/WholeProgram/rfc0029-span-count-joins.c | 5 + test/WholeProgram/rfc0029-span-counts.c | 5 + .../rfc0029-string-length-copies.c | 3 + .../rfc0029-temporary-release-frames.c | 3 + test/WholeProgram/rfc0029-workflows.c | 9 + test/evaluation/rfc0029/README.md | 112 + .../rfc0029/advance-outcomes/README.md | 4 + .../rfc0029/advance-outcomes/SHA256SUMS | 8 + .../rfc0029/advance-outcomes/bad-failure.c | 9 + .../advance-outcomes/frozen-sha256.json | 10 + .../rfc0029/advance-outcomes/good.c | 2 + .../rfc0029/advance-outcomes/interior.c | 2 + .../rfc0029/advance-outcomes/library.c | 7 + .../rfc0029/advance-outcomes/manifest.json | 83 + .../rfc0029/advance-outcomes/short.c | 2 + .../rfc0029/advance-outcomes/skipped.c | 9 + .../rfc0029/anonymous-private-state/README.md | 1 + .../rfc0029/anonymous-private-state/client.c | 2 + .../frozen-sha256.json | 6 + .../anonymous-private-state/manifest.json | 21 + .../rfc0029/anonymous-private-state/state.c | 3 + .../attached-payload-transfer/PROVENANCE.md | 8 + .../rfc0029/attached-payload-transfer/api.h | 5 + .../frozen-sha256.json | 10 + .../rfc0029/attached-payload-transfer/good.c | 4 + .../attached-payload-transfer/ignored.c | 4 + .../rfc0029/attached-payload-transfer/leak.c | 4 + .../attached-payload-transfer/library.c | 18 + .../attached-payload-transfer/manifest.json | 37 + .../rfc0029/attached-payload-transfer/twice.c | 4 + .../rfc0029/bounded-string-cursor/bounded.c | 14 + .../bounded-string-cursor/earlier-zero.c | 14 + .../bounded-string-cursor/escaped-cursor.c | 14 + .../rfc0029/bounded-string-cursor/finish.c | 7 + .../bounded-string-cursor/forged-capacity.c | 14 + .../bounded-string-cursor/frozen-sha256.json | 11 + .../bounded-string-cursor/manifest.json | 99 + .../bounded-string-cursor/uninitialized.c | 14 + .../bounded-string-cursor/unterminated.c | 14 + .../rfc0029/bounded-string-cursor/writer.h | 3 + .../buffer-entry-intervals-reviewed/README.md | 15 + .../frozen-sha256.json | 9 + .../full-physical.c | 8 + .../manifest.json | 75 + .../past-physical.c | 8 + .../replaced-short.c | 8 + .../short-physical.c | 8 + .../spare-physical.c | 8 + .../uninitialized-tail.c | 8 + .../rfc0029/byte-callee-frames/PROVENANCE.md | 1 + .../rfc0029/byte-callee-frames/api.h | 1 + .../byte-callee-frames/changed-library.c | 3 + .../byte-callee-frames/frozen-sha256.json | 13 + .../rfc0029/byte-callee-frames/library.c | 4 + .../rfc0029/byte-callee-frames/local.c | 2 + .../rfc0029/byte-callee-frames/manifest.json | 117 + .../rfc0029/byte-callee-frames/mutable.c | 2 + .../rfc0029/byte-callee-frames/partial.c | 2 + .../rfc0029/byte-callee-frames/static.c | 2 + .../byte-callee-frames/unknown-library.c | 3 + .../rfc0029/byte-callee-frames/wrong.c | 2 + .../rfc0029/byte-comparisons/PROVENANCE.md | 1 + .../rfc0029/byte-comparisons/bom-absent.c | 2 + .../rfc0029/byte-comparisons/bom-present.c | 2 + .../rfc0029/byte-comparisons/changed.c | 2 + .../byte-comparisons/embedded-zero-memory.c | 2 + .../byte-comparisons/embedded-zero-string.c | 2 + .../rfc0029/byte-comparisons/equal.c | 2 + .../byte-comparisons/frozen-sha256.json | 15 + .../rfc0029/byte-comparisons/library.c | 10 + .../rfc0029/byte-comparisons/manifest.json | 165 ++ .../rfc0029/byte-comparisons/partial.c | 2 + .../rfc0029/byte-comparisons/short.c | 2 + .../byte-comparisons/sign-not-magnitude.c | 2 + .../rfc0029/byte-comparisons/unsigned-order.c | 2 + .../rfc0029/byte-cursor-content/PROVENANCE.md | 1 + .../rfc0029/byte-cursor-content/api.h | 1 + .../rfc0029/byte-cursor-content/bad.c | 2 + .../rfc0029/byte-cursor-content/changed.c | 2 + .../byte-cursor-content/frozen-sha256.json | 14 + .../rfc0029/byte-cursor-content/good.c | 2 + .../rfc0029/byte-cursor-content/library.c | 1 + .../rfc0029/byte-cursor-content/local-write.c | 1 + .../rfc0029/byte-cursor-content/local.c | 1 + .../rfc0029/byte-cursor-content/manifest.json | 131 ++ .../rfc0029/byte-cursor-content/offset.c | 2 + .../byte-cursor-content/uninitialized.c | 2 + .../byte-cursor-content/unknown-write.c | 2 + .../byte-cursor-forwarding/PROVENANCE.md | 3 + .../rfc0029/byte-cursor-forwarding/api.h | 1 + .../rfc0029/byte-cursor-forwarding/bad.c | 2 + .../rfc0029/byte-cursor-forwarding/changed.c | 2 + .../byte-cursor-forwarding/frozen-sha256.json | 13 + .../rfc0029/byte-cursor-forwarding/good.c | 2 + .../rfc0029/byte-cursor-forwarding/library.c | 2 + .../byte-cursor-forwarding/local-write.c | 1 + .../rfc0029/byte-cursor-forwarding/local.c | 1 + .../byte-cursor-forwarding/manifest.json | 131 ++ .../rfc0029/byte-cursor-forwarding/offset.c | 2 + .../byte-cursor-forwarding/uninitialized.c | 2 + .../byte-cursor-forwarding/unknown-write.c | 2 + .../rfc0029/byte-cursor-frames/PROVENANCE.md | 1 + .../rfc0029/byte-cursor-frames/REVIEW.md | 3 + .../rfc0029/byte-cursor-frames/alias.c | 2 + .../rfc0029/byte-cursor-frames/api.h | 1 + .../rfc0029/byte-cursor-frames/bad.c | 2 + .../rfc0029/byte-cursor-frames/changed.c | 2 + .../byte-cursor-frames/frozen-sha256.json | 14 + .../rfc0029/byte-cursor-frames/good.c | 2 + .../rfc0029/byte-cursor-frames/library.c | 11 + .../rfc0029/byte-cursor-frames/local-write.c | 1 + .../rfc0029/byte-cursor-frames/local.c | 1 + .../rfc0029/byte-cursor-frames/manifest.json | 130 ++ .../rfc0029/byte-cursor-frames/offset.c | 2 + .../byte-cursor-frames/reviewed-manifest.json | 130 ++ .../byte-cursor-frames/reviewed-sha256.json | 4 + .../byte-cursor-frames/uninitialized.c | 2 + .../rfc0029/byte-cursor-static/PROVENANCE.md | 1 + .../rfc0029/byte-cursor-static/api.h | 1 + .../rfc0029/byte-cursor-static/bad.c | 2 + .../rfc0029/byte-cursor-static/changed.c | 2 + .../byte-cursor-static/frozen-sha256.json | 12 + .../rfc0029/byte-cursor-static/good.c | 2 + .../rfc0029/byte-cursor-static/library.c | 1 + .../rfc0029/byte-cursor-static/local-write.c | 1 + .../rfc0029/byte-cursor-static/local.c | 1 + .../rfc0029/byte-cursor-static/manifest.json | 99 + .../rfc0029/byte-cursor-static/offset.c | 2 + .../rfc0029/byte-global-frames/PROVENANCE.md | 3 + .../rfc0029/byte-global-frames/api.h | 1 + .../rfc0029/byte-global-frames/bad.c | 2 + .../rfc0029/byte-global-frames/changed.c | 2 + .../rfc0029/byte-global-frames/const-write.c | 2 + .../byte-global-frames/frozen-sha256.json | 16 + .../rfc0029/byte-global-frames/good.c | 2 + .../rfc0029/byte-global-frames/library.c | 2 + .../rfc0029/byte-global-frames/local-write.c | 1 + .../rfc0029/byte-global-frames/local.c | 1 + .../rfc0029/byte-global-frames/manifest.json | 161 ++ .../rfc0029/byte-global-frames/offset.c | 2 + .../byte-global-frames/release-const.c | 2 + .../byte-global-frames/uninitialized.c | 2 + .../byte-global-frames/unknown-write.c | 2 + .../rfc0029/byte-helper-frames/PROVENANCE.md | 3 + .../rfc0029/byte-helper-frames/api.h | 1 + .../rfc0029/byte-helper-frames/bad.c | 2 + .../rfc0029/byte-helper-frames/changed.c | 2 + .../byte-helper-frames/frozen-sha256.json | 14 + .../rfc0029/byte-helper-frames/good.c | 2 + .../rfc0029/byte-helper-frames/library.c | 2 + .../rfc0029/byte-helper-frames/local-write.c | 1 + .../rfc0029/byte-helper-frames/local.c | 1 + .../rfc0029/byte-helper-frames/manifest.json | 131 ++ .../rfc0029/byte-helper-frames/offset.c | 2 + .../byte-helper-frames/uninitialized.c | 2 + .../byte-helper-frames/unknown-write.c | 2 + .../byte-loop-partitions/PROVENANCE.md | 1 + .../rfc0029/byte-loop-partitions/api.h | 1 + .../rfc0029/byte-loop-partitions/changed.c | 2 + .../rfc0029/byte-loop-partitions/do.c | 2 + .../byte-loop-partitions/frozen-sha256.json | 13 + .../rfc0029/byte-loop-partitions/good.c | 2 + .../rfc0029/byte-loop-partitions/helper.c | 3 + .../byte-loop-partitions/manifest.json | 117 + .../rfc0029/byte-loop-partitions/missing.c | 2 + .../rfc0029/byte-loop-partitions/skip.c | 2 + .../byte-loop-partitions/uninitialized.c | 2 + .../rfc0029/byte-loop-partitions/while.c | 2 + .../PROVENANCE.md | 1 + .../byte-switch-partitions-reviewed/api.h | 3 + .../beyond-bound.c | 2 + .../byte-switch-partitions-reviewed/changed.c | 2 + .../byte-switch-partitions-reviewed/direct.c | 2 + .../byte-switch-partitions-reviewed/forward.c | 2 + .../frozen-sha256.json | 16 + .../manifest.json | 141 ++ .../byte-switch-partitions-reviewed/missing.c | 2 + .../byte-switch-partitions-reviewed/scanner.c | 2 + .../byte-switch-partitions-reviewed/shifted.c | 2 + .../skip-scanner.c | 2 + .../byte-switch-partitions-reviewed/skip.c | 2 + .../uninitialized.c | 2 + .../byte-switch-partitions-reviewed/wrapper.c | 2 + .../byte-switch-partitions/PROVENANCE.md | 1 + .../rfc0029/byte-switch-partitions/api.h | 3 + .../byte-switch-partitions/beyond-bound.c | 2 + .../rfc0029/byte-switch-partitions/changed.c | 2 + .../rfc0029/byte-switch-partitions/direct.c | 2 + .../rfc0029/byte-switch-partitions/forward.c | 2 + .../byte-switch-partitions/frozen-sha256.json | 14 + .../byte-switch-partitions/manifest.json | 141 ++ .../rfc0029/byte-switch-partitions/missing.c | 2 + .../rfc0029/byte-switch-partitions/scanner.c | 2 + .../rfc0029/byte-switch-partitions/shifted.c | 2 + .../rfc0029/byte-switch-partitions/skip.c | 2 + .../byte-switch-partitions/uninitialized.c | 2 + test/evaluation/rfc0029/callback-allocate.c | 12 + test/evaluation/rfc0029/callback-short-bad.c | 13 + .../rfc0029/cast-reader-intervals/README.md | 1 + .../rfc0029/cast-reader-intervals/forged.c | 6 + .../cast-reader-intervals/frozen-sha256.json | 11 + .../rfc0029/cast-reader-intervals/good.c | 6 + .../rfc0029/cast-reader-intervals/library.c | 11 + .../cast-reader-intervals/manifest.json | 83 + .../rfc0029/cast-reader-intervals/reader.h | 3 + .../cast-reader-intervals/scaled-bad.c | 5 + .../cast-reader-intervals/scaled-good.c | 5 + .../cast-reader-intervals/uninitialized.c | 6 + .../rfc0029/character-pointer-slots/README.md | 1 + .../rfc0029/character-pointer-slots/end.c | 2 + .../frozen-sha256.json | 9 + .../character-pointer-slots/manifest.json | 80 + .../character-pointer-slots/past-end.c | 2 + .../character-pointer-slots/readonly.c | 2 + .../character-pointer-slots/unrelated.c | 2 + .../rfc0029/character-pointer-slots/write.c | 2 + .../rfc0029/combined-callback-cases/README.md | 1 + .../rfc0029/combined-callback-cases/api.c | 4 + .../rfc0029/combined-callback-cases/api.h | 2 + .../rfc0029/combined-callback-cases/client.c | 3 + .../frozen-sha256.json | 11 + .../combined-callback-cases/inactive.c | 2 + .../combined-callback-cases/manifest.json | 85 + .../rfc0029/combined-callback-cases/missing.c | 3 + .../rfc0029/combined-callback-cases/mixed.c | 4 + .../rfc0029/combined-callback-cases/null.c | 2 + .../PROVENANCE.md | 1 + .../api.h | 6 + .../bounded-inspect.c | 3 + .../bounded.c | 2 + .../bytes-inspect.c | 3 + .../bytes.c | 2 + .../extent-inspect.c | 3 + .../extent.c | 2 + .../frozen-sha256.json | 20 + .../manifest.json | 124 ++ .../reader.c | 3 + .../released-inspect.c | 3 + .../released.c | 2 + .../string-inspect.c | 3 + .../string.c | 2 + .../uninitialized-inspect.c | 3 + .../uninitialized.c | 2 + .../unknown-inspect.c | 3 + .../unknown.c | 2 + .../comparison-container-frames/PROVENANCE.md | 1 + .../rfc0029/comparison-container-frames/api.h | 6 + .../bounded-inspect.c | 3 + .../comparison-container-frames/bounded.c | 2 + .../bytes-inspect.c | 3 + .../comparison-container-frames/bytes.c | 2 + .../extent-inspect.c | 3 + .../comparison-container-frames/extent.c | 2 + .../frozen-sha256.json | 20 + .../comparison-container-frames/manifest.json | 124 ++ .../comparison-container-frames/reader.c | 3 + .../released-inspect.c | 3 + .../comparison-container-frames/released.c | 2 + .../string-inspect.c | 3 + .../comparison-container-frames/string.c | 2 + .../uninitialized-inspect.c | 3 + .../uninitialized.c | 2 + .../unknown-inspect.c | 3 + .../comparison-container-frames/unknown.c | 2 + .../conditional-count-arguments/README.md | 6 + .../conditional-count-arguments/changed.c | 2 + .../conditional-count-arguments/converted.c | 2 + .../frozen-sha256.json | 10 + .../conditional-count-arguments/good.c | 2 + .../conditional-count-arguments/library.c | 1 + .../conditional-count-arguments/manifest.json | 85 + .../conditional-count-arguments/short.c | 2 + .../conditional-count-arguments/zero.c | 2 + .../rfc0029/construction-helpers/build.c | 18 + .../rfc0029/construction-helpers/double.c | 18 + .../construction-helpers/frozen-sha256.json | 8 + .../rfc0029/construction-helpers/leak.c | 18 + .../construction-helpers/manifest.json | 68 + .../construction-helpers/provenance.md | 1 + .../rfc0029/construction-helpers/short.c | 18 + test/evaluation/rfc0029/construction/build.c | 15 + test/evaluation/rfc0029/construction/cycle.c | 15 + test/evaluation/rfc0029/construction/double.c | 15 + .../rfc0029/construction/frozen-sha256.json | 10 + test/evaluation/rfc0029/construction/leak.c | 15 + .../rfc0029/construction/manifest.json | 100 + .../rfc0029/construction/provenance.md | 1 + test/evaluation/rfc0029/construction/short.c | 15 + .../rfc0029/construction/uninitialized.c | 15 + .../container-alias-outputs/PROVENANCE.md | 5 + .../rfc0029/container-alias-outputs/api.h | 3 + .../rfc0029/container-alias-outputs/direct.c | 2 + .../rfc0029/container-alias-outputs/disown.c | 2 + .../container-alias-outputs/disowned.c | 2 + .../rfc0029/container-alias-outputs/drop.c | 2 + .../rfc0029/container-alias-outputs/forward.c | 2 + .../container-alias-outputs/forwarded.c | 2 + .../frozen-sha256.json | 16 + .../container-alias-outputs/interior.c | 2 + .../container-alias-outputs/lost-payload.c | 2 + .../container-alias-outputs/manifest.json | 131 ++ .../rfc0029/container-alias-outputs/payload.c | 2 + .../container-alias-outputs/released.c | 2 + .../rfc0029/container-alias-outputs/update.c | 2 + .../rfc0029/container-call-frames/child.c | 24 + .../container-call-frames/frozen-sha256.json | 6 + .../rfc0029/container-call-frames/local.c | 24 + .../container-call-frames/manifest.json | 52 + .../rfc0029/container-call-frames/root.c | 24 + .../rfc0029/container-local-frames/child.c | 17 + .../container-local-frames/frozen-sha256.json | 6 + .../rfc0029/container-local-frames/local.c | 17 + .../container-local-frames/manifest.json | 52 + .../rfc0029/container-local-frames/root.c | 17 + .../container-outcome-frames/PROVENANCE.md | 1 + .../rfc0029/container-outcome-frames/api.h | 5 + .../container-outcome-frames/branch-inspect.c | 2 + .../rfc0029/container-outcome-frames/branch.c | 2 + .../container-outcome-frames/direct-inspect.c | 2 + .../rfc0029/container-outcome-frames/direct.c | 2 + .../disowned-inspect.c | 2 + .../container-outcome-frames/disowned.c | 2 + .../rfc0029/container-outcome-frames/drop.c | 2 + .../container-outcome-frames/forward.c | 2 + .../frozen-sha256.json | 19 + .../container-outcome-frames/helper-inspect.c | 2 + .../rfc0029/container-outcome-frames/helper.c | 2 + .../container-outcome-frames/lost-inspect.c | 2 + .../rfc0029/container-outcome-frames/lost.c | 2 + .../container-outcome-frames/manifest.json | 113 + .../released-inspect.c | 2 + .../container-outcome-frames/released.c | 2 + .../rfc0029/container-value-guards/README.md | 15 + .../actual-allocation.c | 13 + .../container-value-guards/changed-alias.c | 13 + .../container-value-guards/changed-selector.c | 13 + .../rfc0029/container-value-guards/forest.h | 15 + .../container-value-guards/frozen-sha256.json | 11 + .../container-value-guards/manifest.json | 95 + .../container-value-guards/null-result.c | 13 + .../container-value-guards/released-head.c | 13 + .../container-value-guards/replaced-head.c | 13 + .../replaced-live-head.c | 13 + .../rfc0029/container-value-guards/review.md | 8 + .../reviewed-manifest.json | 110 + .../reviewed-sha256.json | 5 + .../rfc0029/corpus-regressions/api.h | 4 + .../discovered-manifest.json | 66 + .../corpus-regressions/discovered-sha256.json | 5 + .../rfc0029/corpus-regressions/discovery.md | 1 + .../corpus-regressions/frozen-sha256.json | 9 + .../rfc0029/corpus-regressions/library.c | 3 + .../rfc0029/corpus-regressions/lookup.c | 9 + .../rfc0029/corpus-regressions/manifest.json | 51 + .../rfc0029/corpus-regressions/provenance.md | 1 + .../rfc0029/corpus-regressions/unterminated.c | 9 + .../corpus-regressions/update-discovered.c | 13 + .../rfc0029/corpus-regressions/update.c | 9 + .../counter-reset-ranges-reviewed/README.md | 8 + .../counter-reset-ranges-reviewed/SHA256SUMS | 9 + .../counter-reset-ranges-reviewed/decimal.c | 2 + .../frozen-sha256.json | 12 + .../counter-reset-ranges-reviewed/good.c | 2 + .../counter-reset-ranges-reviewed/leak.c | 20 + .../counter-reset-ranges-reviewed/library.c | 19 + .../manifest-original.json | 82 + .../manifest.json | 82 + .../counter-reset-ranges-reviewed/reader.h | 3 + .../counter-reset-ranges-reviewed/released.c | 20 + .../counter-reset-ranges-reviewed/short.c | 20 + .../cursor-envelope-joins-reviewed/README.md | 2 + .../frozen-sha256.json | 9 + .../cursor-envelope-joins-reviewed/good.c | 11 + .../cursor-envelope-joins-reviewed/helper.c | 12 + .../manifest.json | 80 + .../over-capacity.c | 11 + .../over-step.c | 11 + .../uninitialized.c | 11 + .../rfc0029/cursor-readers/README.md | 1 + .../rfc0029/cursor-readers/audit.md | 1 + .../cursor-readers/audited-manifest.json | 181 ++ .../cursor-readers/audited-sha256.json | 4 + .../rfc0029/cursor-readers/capacity-read.c | 2 + .../rfc0029/cursor-readers/capacity.c | 2 + .../rfc0029/cursor-readers/client.c | 2 + test/evaluation/rfc0029/cursor-readers/copy.c | 2 + .../rfc0029/cursor-readers/cursor.c | 2 + .../cursor-readers/diagnostic-audit.md | 1 + .../evaluation/rfc0029/cursor-readers/empty.c | 2 + .../rfc0029/cursor-readers/endpoint-read.c | 2 + .../rfc0029/cursor-readers/escape.c | 7 + .../rfc0029/cursor-readers/escaped-read.c | 2 + .../rfc0029/cursor-readers/frozen-sha256.json | 16 + .../rfc0029/cursor-readers/library.c | 7 + .../rfc0029/cursor-readers/manifest.json | 184 ++ .../rfc0029/cursor-readers/off-by-one.c | 7 + .../rfc0029/cursor-readers/partial.c | 8 + .../rfc0029/cursor-readers/reader.h | 3 + .../rfc0029/cursor-readers/readonly.c | 8 + .../cursor-readers/reviewed-manifest.json | 181 ++ .../cursor-readers/reviewed-sha256.json | 8 + .../cursor-readers/uninitialized-read.c | 2 + .../rfc0029/cursor-readers/uninitialized.c | 2 + .../rfc0029/fixed-span-steps/README.md | 5 + .../evaluation/rfc0029/fixed-span-steps/api.h | 1 + .../fixed-span-steps/frozen-sha256.json | 13 + .../rfc0029/fixed-span-steps/good.c | 2 + .../rfc0029/fixed-span-steps/library.c | 1 + .../rfc0029/fixed-span-steps/manifest.json | 130 ++ .../rfc0029/fixed-span-steps/odd-tail.c | 2 + .../rfc0029/fixed-span-steps/over-count.c | 2 + .../rfc0029/fixed-span-steps/over-step.c | 2 + .../rfc0029/fixed-span-steps/runtime.c | 2 + .../rfc0029/fixed-span-steps/uninitialized.c | 2 + .../rfc0029/fixed-span-steps/unrelated.c | 2 + .../floating-call-premises/PROVENANCE.md | 4 + .../rfc0029/floating-call-premises/api.h | 2 + .../floating-call-premises/changed-clamp.c | 2 + .../rfc0029/floating-call-premises/changed.c | 2 + .../rfc0029/floating-call-premises/clamp.c | 2 + .../rfc0029/floating-call-premises/finite.c | 2 + .../rfc0029/floating-call-premises/forward.c | 2 + .../floating-call-premises/forwarded.c | 2 + .../floating-call-premises/frozen-sha256.json | 14 + .../rfc0029/floating-call-premises/infinite.c | 2 + .../floating-call-premises/manifest.json | 107 + .../rfc0029/floating-call-premises/mixed.c | 2 + .../rfc0029/floating-call-premises/nan.c | 2 + test/evaluation/rfc0029/frozen-sha256.json | 16 + .../rfc0029/guarded-advance/README.md | 4 + .../rfc0029/guarded-advance/changed.c | 2 + .../rfc0029/guarded-advance/failure.c | 2 + .../guarded-advance/frozen-sha256.json | 10 + .../evaluation/rfc0029/guarded-advance/good.c | 2 + .../rfc0029/guarded-advance/interior.c | 2 + .../rfc0029/guarded-advance/library.c | 7 + .../rfc0029/guarded-advance/manifest.json | 85 + .../rfc0029/guarded-advance/short.c | 2 + .../rfc0029/guarded-cursor-bounds/README.md | 4 + .../rfc0029/guarded-cursor-bounds/changed.c | 2 + .../guarded-cursor-bounds/frozen-sha256.json | 10 + .../rfc0029/guarded-cursor-bounds/good.c | 2 + .../rfc0029/guarded-cursor-bounds/interior.c | 2 + .../rfc0029/guarded-cursor-bounds/library.c | 7 + .../guarded-cursor-bounds/manifest.json | 85 + .../rfc0029/guarded-cursor-bounds/moved.c | 2 + .../rfc0029/guarded-cursor-bounds/short.c | 2 + .../rfc0029/helper-cursor-pairs/README.md | 6 + .../rfc0029/helper-cursor-pairs/api.h | 1 + .../helper-cursor-pairs/frozen-sha256.json | 13 + .../rfc0029/helper-cursor-pairs/good.c | 2 + .../rfc0029/helper-cursor-pairs/library.c | 1 + .../rfc0029/helper-cursor-pairs/manifest.json | 130 ++ .../rfc0029/helper-cursor-pairs/mixed.c | 2 + .../rfc0029/helper-cursor-pairs/no-progress.c | 2 + .../rfc0029/helper-cursor-pairs/once.c | 2 + .../rfc0029/helper-cursor-pairs/short.c | 2 + .../helper-cursor-pairs/uninitialized.c | 2 + .../rfc0029/helper-cursor-pairs/wrong-step.c | 2 + .../rfc0029/in-place-extension/README.md | 1 + .../in-place-extension/duplicate-child.c | 5 + .../in-place-extension/failed-cleanup.c | 5 + .../in-place-extension/frozen-sha256.json | 9 + .../rfc0029/in-place-extension/lost-child.c | 5 + .../rfc0029/in-place-extension/manifest.json | 80 + .../in-place-extension/nondecreasing.c | 5 + .../rfc0029/in-place-extension/recursive.c | 5 + .../rfc0029/independent-cursors/README.md | 10 + .../rfc0029/independent-cursors/api.h | 2 + .../rfc0029/independent-cursors/closed.c | 1 + .../rfc0029/independent-cursors/extra-step.c | 1 + .../independent-cursors/frozen-sha256.json | 13 + .../rfc0029/independent-cursors/good.c | 2 + .../rfc0029/independent-cursors/library.c | 5 + .../rfc0029/independent-cursors/manifest.json | 127 ++ .../rfc0029/independent-cursors/offset.c | 1 + .../rfc0029/independent-cursors/reordered.c | 1 + .../rfc0029/independent-cursors/short.c | 2 + .../independent-cursors/uninitialized.c | 2 + .../rfc0029/initialized-advance/README.md | 6 + .../initialized-advance/frozen-sha256.json | 10 + .../rfc0029/initialized-advance/good.c | 2 + .../rfc0029/initialized-advance/interior.c | 2 + .../rfc0029/initialized-advance/library.c | 6 + .../rfc0029/initialized-advance/manifest.json | 83 + .../initialized-advance/over-advance.c | 2 + .../rfc0029/initialized-advance/short.c | 2 + .../rfc0029/initialized-advance/skipped.c | 2 + .../rfc0029/initialized-spans/README.md | 1 + .../rfc0029/initialized-spans/clobber.c | 5 + .../rfc0029/initialized-spans/empty.c | 2 + .../rfc0029/initialized-spans/freed.c | 3 + .../initialized-spans/frozen-sha256.json | 13 + .../rfc0029/initialized-spans/good.c | 2 + .../rfc0029/initialized-spans/library.c | 5 + .../rfc0029/initialized-spans/manifest.json | 145 ++ .../rfc0029/initialized-spans/outside.c | 2 + .../rfc0029/initialized-spans/uninitialized.c | 2 + .../rfc0029/initialized-spans/unrelated.c | 2 + .../rfc0029/initialized-spans/write.c | 4 + .../rfc0029/local-callee-copies/README.md | 6 + .../rfc0029/local-callee-copies/copy.c | 5 + .../rfc0029/local-callee-copies/freed-alias.c | 5 + .../local-callee-copies/frozen-sha256.json | 10 + .../local-callee-copies/interior-release.c | 4 + .../rfc0029/local-callee-copies/leak.c | 5 + .../rfc0029/local-callee-copies/manifest.json | 95 + .../rfc0029/local-callee-copies/numeric.c | 4 + .../rfc0029/local-callee-copies/unsigned.c | 4 + test/evaluation/rfc0029/manifest.json | 231 ++ .../rfc0029/mixed-allocation-ledger/double.c | 18 + .../frozen-sha256.json | 7 + .../rfc0029/mixed-allocation-ledger/lost.c | 18 + .../mixed-allocation-ledger/manifest.json | 67 + .../rfc0029/mixed-allocation-ledger/resize.c | 18 + .../rfc0029/mixed-allocation-ledger/return.c | 18 + .../rfc0029/mixed-helper-frames/PROVENANCE.md | 4 + .../rfc0029/mixed-helper-frames/api.h | 4 + .../rfc0029/mixed-helper-frames/client.c | 2 + .../rfc0029/mixed-helper-frames/direct.c | 2 + .../rfc0029/mixed-helper-frames/disowned.c | 2 + .../rfc0029/mixed-helper-frames/drop.c | 2 + .../rfc0029/mixed-helper-frames/freed.c | 2 + .../mixed-helper-frames/frozen-sha256.json | 14 + .../rfc0029/mixed-helper-frames/helper.c | 2 + .../rfc0029/mixed-helper-frames/lost.c | 2 + .../rfc0029/mixed-helper-frames/make.c | 2 + .../rfc0029/mixed-helper-frames/manifest.json | 100 + .../rfc0029/mixed-helper-frames/step.c | 2 + .../mutable-reader-construction/build.c | 16 + .../mutable-reader-construction/cycle.c | 16 + .../mutable-reader-construction/double.c | 16 + .../mutable-reader-construction/escape.c | 16 + .../frozen-sha256.json | 11 + .../mutable-reader-construction/leak.c | 16 + .../mutable-reader-construction/manifest.json | 116 + .../mutable-reader-construction/provenance.md | 1 + .../mutable-reader-construction/short.c | 16 + .../uninitialized.c | 16 + .../evaluation/rfc0029/mutual-cases/README.md | 1 + .../rfc0029/mutual-cases/frozen-sha256.json | 7 + test/evaluation/rfc0029/mutual-cases/live.c | 13 + .../rfc0029/mutual-cases/manifest.json | 66 + test/evaluation/rfc0029/mutual-cases/null.c | 13 + .../rfc0029/mutual-cases/unbounded.c | 13 + test/evaluation/rfc0029/mutual-cleanup.c | 24 + .../rfc0029/mutual-construction/cycle.c | 17 + .../rfc0029/mutual-construction/forwarding.c | 17 + .../mutual-construction/frozen-sha256.json | 8 + .../rfc0029/mutual-construction/leak.c | 17 + .../rfc0029/mutual-construction/manifest.json | 72 + .../rfc0029/mutual-construction/provenance.md | 1 + .../rfc0029/mutual-construction/short.c | 17 + test/evaluation/rfc0029/mutual-skipped-bad.c | 26 + .../numeric-container-frames/PROVENANCE.md | 1 + .../rfc0029/numeric-container-frames/api.h | 5 + .../frozen-sha256.json | 20 + .../local-end-inspect.c | 3 + .../numeric-container-frames/local-end.c | 2 + .../numeric-container-frames/manifest.json | 124 ++ .../null-end-inspect.c | 3 + .../numeric-container-frames/null-end.c | 2 + .../owned-end-inspect.c | 3 + .../numeric-container-frames/owned-end.c | 2 + .../rfc0029/numeric-container-frames/reader.c | 3 + .../record-end-inspect.c | 3 + .../numeric-container-frames/record-end.c | 2 + .../released-inspect.c | 3 + .../numeric-container-frames/released.c | 2 + .../uninitialized-inspect.c | 3 + .../numeric-container-frames/uninitialized.c | 2 + .../unknown-inspect.c | 3 + .../numeric-container-frames/unknown.c | 2 + test/evaluation/rfc0029/numeric-input/end.c | 2 + .../evaluation/rfc0029/numeric-input/forged.c | 2 + .../rfc0029/numeric-input/frozen-sha256.json | 11 + .../rfc0029/numeric-input/manifest.json | 108 + .../rfc0029/numeric-input/null-end.c | 2 + .../rfc0029/numeric-input/past-end.c | 2 + .../rfc0029/numeric-input/provenance.md | 1 + .../rfc0029/numeric-input/readonly-slot.c | 2 + .../rfc0029/numeric-input/uninitialized.c | 2 + .../rfc0029/numeric-input/unterminated.c | 2 + .../rfc0029/numeric-scan-locals/README.md | 1 + .../rfc0029/numeric-scan-locals/assignment.c | 23 + .../numeric-scan-locals/frozen-sha256.json | 6 + .../rfc0029/numeric-scan-locals/manifest.json | 50 + .../rfc0029/numeric-scan-locals/reset.c | 24 + .../numeric-scan-locals/source-write.c | 24 + .../rfc0029/numeric-scans/README.md | 1 + .../rfc0029/numeric-scans/default.c | 22 + .../rfc0029/numeric-scans/frozen-sha256.json | 10 + test/evaluation/rfc0029/numeric-scans/good.c | 22 + .../rfc0029/numeric-scans/manifest.json | 110 + .../rfc0029/numeric-scans/overwrite.c | 23 + .../rfc0029/numeric-scans/permissive.c | 22 + .../rfc0029/numeric-scans/reordered.c | 22 + test/evaluation/rfc0029/numeric-scans/short.c | 22 + .../rfc0029/numeric-scans/uninitialized.c | 22 + .../rfc0029/numeric-short-circuit/README.md | 1 + .../rfc0029/numeric-short-circuit/SHA256SUMS | 6 + .../rfc0029/numeric-short-circuit/bypass.c | 23 + .../numeric-short-circuit/frozen-sha256.json | 8 + .../rfc0029/numeric-short-circuit/left.c | 23 + .../numeric-short-circuit/manifest.json | 80 + .../rfc0029/numeric-short-circuit/mutated.c | 23 + .../numeric-short-circuit/permissive.c | 23 + .../rfc0029/numeric-short-circuit/right.c | 23 + .../evaluation/rfc0029/numeric-text/README.md | 1 + .../evaluation/rfc0029/numeric-text/changed.c | 3 + .../rfc0029/numeric-text/frozen-sha256.json | 10 + .../evaluation/rfc0029/numeric-text/literal.c | 3 + .../rfc0029/numeric-text/manifest.json | 110 + test/evaluation/rfc0029/numeric-text/nan.c | 3 + .../rfc0029/numeric-text/overwrite.c | 3 + test/evaluation/rfc0029/numeric-text/stores.c | 4 + .../rfc0029/numeric-text/uninitialized.c | 3 + test/evaluation/rfc0029/numeric-text/wide.c | 4 + test/evaluation/rfc0029/offsets/child.c | 13 + test/evaluation/rfc0029/offsets/forward.c | 12 + .../rfc0029/offsets/frozen-sha256.json | 7 + test/evaluation/rfc0029/offsets/manifest.json | 50 + test/evaluation/rfc0029/offsets/provenance.md | 9 + test/evaluation/rfc0029/offsets/zero.c | 12 + .../rfc0029/output-construction/build.c | 14 + .../rfc0029/output-construction/cycle.c | 14 + .../rfc0029/output-construction/double.c | 14 + .../output-construction/false-success.c | 14 + .../output-construction/frozen-sha256.json | 12 + .../rfc0029/output-construction/leak.c | 14 + .../rfc0029/output-construction/manifest.json | 132 ++ .../rfc0029/output-construction/null-slot.c | 14 + .../rfc0029/output-construction/provenance.md | 1 + .../rfc0029/output-construction/short.c | 14 + .../output-construction/uncleared-failure.c | 13 + .../evaluation/rfc0029/output-transport/api.h | 5 + .../rfc0029/output-transport/client.c | 2 + .../rfc0029/output-transport/double.c | 2 + .../output-transport/frozen-sha256.json | 9 + .../rfc0029/output-transport/library.c | 14 + .../rfc0029/output-transport/manifest.json | 69 + .../rfc0029/output-transport/provenance.md | 1 + .../rfc0029/output-transport/short.c | 2 + .../rfc0029/paired-cursor-loops/README.md | 4 + .../rfc0029/paired-cursor-loops/for.c | 5 + .../paired-cursor-loops/frozen-sha256.json | 9 + .../rfc0029/paired-cursor-loops/good.c | 5 + .../rfc0029/paired-cursor-loops/manifest.json | 80 + .../rfc0029/paired-cursor-loops/over-end.c | 5 + .../rfc0029/paired-cursor-loops/over-step.c | 5 + .../rfc0029/paired-cursor-loops/unrelated.c | 5 + .../rfc0029/paired-reader-counters/README.md | 1 + .../paired-reader-counters/frozen-sha256.json | 10 + .../rfc0029/paired-reader-counters/good.c | 7 + .../rfc0029/paired-reader-counters/library.c | 19 + .../paired-reader-counters/manifest.json | 66 + .../rfc0029/paired-reader-counters/nonzero.c | 20 + .../rfc0029/paired-reader-counters/reader.h | 3 + .../paired-reader-counters/skipped-index.c | 20 + .../rfc0029/paired-reader-counters/too-many.c | 20 + .../rfc0029/payload-early-exits/PROVENANCE.md | 1 + .../rfc0029/payload-early-exits/api.h | 2 + .../rfc0029/payload-early-exits/client.c | 3 + .../rfc0029/payload-early-exits/drop.c | 3 + .../rfc0029/payload-early-exits/duplicate.c | 3 + .../rfc0029/payload-early-exits/early.c | 3 + .../payload-early-exits/frozen-sha256.json | 13 + .../rfc0029/payload-early-exits/goto.c | 3 + .../rfc0029/payload-early-exits/leak.c | 3 + .../rfc0029/payload-early-exits/manifest.json | 107 + .../payload-early-exits/released-head.c | 3 + .../payload-early-exits/released-payload.c | 3 + .../rfc0029/payload-publication/PROVENANCE.md | 1 + .../rfc0029/payload-publication/api.h | 2 + .../rfc0029/payload-publication/client.c | 3 + .../rfc0029/payload-publication/drop.c | 3 + .../rfc0029/payload-publication/duplicate.c | 3 + .../payload-publication/frozen-sha256.json | 13 + .../rfc0029/payload-publication/good.c | 3 + .../rfc0029/payload-publication/helper.c | 4 + .../rfc0029/payload-publication/interior.c | 3 + .../rfc0029/payload-publication/lost.c | 3 + .../rfc0029/payload-publication/manifest.json | 107 + .../rfc0029/payload-publication/released.c | 3 + .../payload-reader-returns/PROVENANCE.md | 1 + .../rfc0029/payload-reader-returns/api.h | 4 + .../rfc0029/payload-reader-returns/client.c | 3 + .../rfc0029/payload-reader-returns/drop.c | 3 + .../payload-reader-returns/duplicate.c | 4 + .../rfc0029/payload-reader-returns/early.c | 4 + .../payload-reader-returns/frozen-sha256.json | 14 + .../rfc0029/payload-reader-returns/good.c | 4 + .../rfc0029/payload-reader-returns/leak.c | 4 + .../payload-reader-returns/manifest.json | 113 + .../payload-reader-returns/released-head.c | 4 + .../payload-reader-returns/released-payload.c | 4 + .../rfc0029/payload-reader-returns/wrap.c | 2 + .../rfc0029/payload-relocation/PROVENANCE.md | 1 + .../rfc0029/payload-relocation/api.h | 2 + .../rfc0029/payload-relocation/drop.c | 3 + .../rfc0029/payload-relocation/duplicate.c | 3 + .../rfc0029/payload-relocation/fill.c | 3 + .../payload-relocation/frozen-sha256.json | 13 + .../rfc0029/payload-relocation/helper.c | 3 + .../rfc0029/payload-relocation/interior.c | 3 + .../rfc0029/payload-relocation/local.c | 3 + .../rfc0029/payload-relocation/manifest.json | 107 + .../rfc0029/payload-relocation/overwritten.c | 3 + .../rfc0029/payload-relocation/released.c | 3 + .../payload-write-frames/PROVENANCE.md | 1 + .../payload-write-frames/aliased-client.c | 3 + .../rfc0029/payload-write-frames/api.h | 2 + .../rfc0029/payload-write-frames/client.c | 3 + .../rfc0029/payload-write-frames/drop.c | 3 + .../rfc0029/payload-write-frames/duplicate.c | 3 + .../rfc0029/payload-write-frames/early.c | 3 + .../payload-write-frames/frozen-sha256.json | 12 + .../rfc0029/payload-write-frames/good.c | 3 + .../rfc0029/payload-write-frames/helper.c | 4 + .../payload-write-frames/manifest.json | 107 + .../rfc0029/pointer-count-readers/README.md | 1 + .../pointer-count-readers/frozen-sha256.json | 9 + .../rfc0029/pointer-count-readers/good.c | 12 + .../pointer-count-readers/manifest.json | 80 + .../rfc0029/pointer-count-readers/over-step.c | 12 + .../rfc0029/pointer-count-readers/reordered.c | 12 + .../rfc0029/pointer-count-readers/short.c | 12 + .../pointer-count-readers/uninitialized.c | 12 + .../pointer-difference-sizes/PROVENANCE.md | 1 + .../pointer-difference-sizes/changed-end.c | 16 + .../pointer-difference-sizes/cross-object.c | 17 + .../frozen-sha256.json | 10 + .../rfc0029/pointer-difference-sizes/good.c | 16 + .../pointer-difference-sizes/manifest.json | 95 + .../rfc0029/pointer-difference-sizes/offset.c | 16 + .../pointer-difference-sizes/overrun.c | 16 + .../pointer-difference-sizes/undersized.c | 16 + .../rfc0029/pointer-reader-offsets/README.md | 1 + .../rfc0029/pointer-reader-offsets/escape.c | 12 + .../rfc0029/pointer-reader-offsets/forged.c | 2 + .../pointer-reader-offsets/frozen-sha256.json | 12 + .../rfc0029/pointer-reader-offsets/good.c | 2 + .../rfc0029/pointer-reader-offsets/library.c | 10 + .../pointer-reader-offsets/manifest.json | 112 + .../rfc0029/pointer-reader-offsets/past-end.c | 11 + .../rfc0029/pointer-reader-offsets/reader.h | 3 + .../pointer-reader-offsets/uninitialized.c | 2 + .../pointer-reader-offsets/unrelated.c | 12 + .../rfc0029/reader-construction/build.c | 16 + .../rfc0029/reader-construction/cycle.c | 16 + .../rfc0029/reader-construction/double.c | 16 + .../rfc0029/reader-construction/escape.c | 16 + .../reader-construction/frozen-sha256.json | 11 + .../rfc0029/reader-construction/leak.c | 16 + .../rfc0029/reader-construction/manifest.json | 116 + .../rfc0029/reader-construction/provenance.md | 1 + .../rfc0029/reader-construction/short.c | 16 + .../reader-construction/uninitialized.c | 16 + test/evaluation/rfc0029/reader-good.c | 9 + .../rfc0029/reader-index-loops/README.md | 1 + .../rfc0029/reader-index-loops/body-cursor.c | 10 + .../rfc0029/reader-index-loops/body-index.c | 10 + .../rfc0029/reader-index-loops/forged.c | 2 + .../reader-index-loops/frozen-sha256.json | 12 + .../rfc0029/reader-index-loops/good.c | 2 + .../rfc0029/reader-index-loops/library.c | 12 + .../rfc0029/reader-index-loops/manifest.json | 112 + .../rfc0029/reader-index-loops/off-by-one.c | 8 + .../rfc0029/reader-index-loops/reader.h | 3 + .../reader-index-loops/uninitialized.c | 2 + test/evaluation/rfc0029/reader-short-bad.c | 6 + test/evaluation/rfc0029/reader.h | 10 + test/evaluation/rfc0029/readers/audit.md | 15 + test/evaluation/rfc0029/readers/client.c | 8 + .../rfc0029/readers/cursor-escape.c | 7 + test/evaluation/rfc0029/readers/cursor.c | 15 + test/evaluation/rfc0029/readers/cursor.h | 4 + .../rfc0029/readers/escaped-access.c | 7 + .../rfc0029/readers/frozen-sha256.json | 10 + test/evaluation/rfc0029/readers/manifest.json | 84 + test/evaluation/rfc0029/readers/provenance.md | 8 + .../rfc0029/readers/reviewed-manifest.json | 99 + .../rfc0029/readers/reviewed-sha256.json | 5 + test/evaluation/rfc0029/readers/short.c | 6 + .../rfc0029/readers/uninitialized.c | 6 + .../failure-leak.c | 18 + .../frozen-sha256.json | 9 + .../lost-result.c | 18 + .../manifest.json | 100 + .../reallocation-ledger-failures/null-input.c | 18 + .../overwrite-failure.c | 18 + .../stale-release.c | 18 + .../reallocation-ledger-failures/zero-size.c | 18 + test/evaluation/rfc0029/reassigned-release.c | 8 + .../rfc0029/record-arrays/README.md | 12 + .../rfc0029/record-arrays/alias-bad.c | 6 + .../rfc0029/record-arrays/bounds-bad.c | 5 + .../rfc0029/record-arrays/forward.c | 7 + .../rfc0029/record-arrays/frozen-sha256.json | 10 + .../rfc0029/record-arrays/manifest.json | 95 + .../rfc0029/record-arrays/spellings.c | 11 + .../rfc0029/record-arrays/uninit-bad.c | 5 + .../evaluation/rfc0029/record-arrays/writer.c | 19 + .../recursive-cases/frozen-sha256.json | 7 + .../rfc0029/recursive-cases/generic.c | 9 + .../evaluation/rfc0029/recursive-cases/live.c | 9 + .../rfc0029/recursive-cases/manifest.json | 81 + .../evaluation/rfc0029/recursive-cases/null.c | 9 + .../rfc0029/recursive-cases/provenance.md | 1 + .../rfc0029/recursive-cases/unknown.c | 9 + .../evaluation/rfc0029/recursive-cases/zero.c | 9 + .../rfc0029/recursive-contexts/README.md | 1 + .../rfc0029/recursive-contexts/bad.c | 45 + .../rfc0029/recursive-contexts/client.c | 45 + .../recursive-contexts/frozen-sha256.json | 7 + .../rfc0029/recursive-contexts/generic.c | 44 + .../rfc0029/recursive-contexts/manifest.json | 50 + .../rfc0029/recursive-output-cases/README.md | 1 + .../rfc0029/recursive-output-cases/client.c | 12 + .../recursive-output-cases/frozen-sha256.json | 6 + .../recursive-output-cases/manifest.json | 50 + .../rfc0029/recursive-output-cases/null.c | 12 + .../rfc0029/recursive-state-cases/bad.c | 45 + .../rfc0029/recursive-state-cases/client.c | 45 + .../recursive-state-cases/frozen-sha256.json | 6 + .../rfc0029/recursive-state-cases/generic.c | 8 + .../recursive-state-cases/manifest.json | 50 + .../rfc0029/recursive-transport/api.h | 5 + .../rfc0029/recursive-transport/client.c | 2 + .../recursive-transport/frozen-sha256.json | 9 + .../rfc0029/recursive-transport/library.c | 15 + .../rfc0029/recursive-transport/manifest.json | 69 + .../rfc0029/recursive-transport/provenance.md | 1 + .../rfc0029/recursive-transport/short.c | 2 + .../recursive-transport/uninitialized.c | 2 + .../rfc0029/recursive-writer-reviewed/alias.c | 10 + .../recursive-writer-reviewed/capacity.c | 10 + .../rfc0029/recursive-writer-reviewed/emit.c | 10 + .../recursive-writer-reviewed/false-prefix.c | 10 + .../false-success-read.c | 10 + .../frozen-sha256.json | 13 + .../recursive-writer-reviewed/manifest.json | 178 ++ .../recursive-writer-reviewed/mutual.c | 12 + .../recursive-writer-reviewed/off-by-one.c | 10 + .../recursive-writer-reviewed/partial.c | 10 + .../recursive-writer-reviewed/prefix.c | 10 + .../recursive-writer-reviewed/provenance.md | 1 + .../rfc0029/recursive-writer-reviewed/short.c | 10 + .../recursive-writer-reviewed/uninitialized.c | 10 + .../rfc0029/recursive-writer/audit.md | 19 + .../rfc0029/recursive-writer/capacity.c | 10 + .../rfc0029/recursive-writer/cycle.c | 10 + .../rfc0029/recursive-writer/emit.c | 10 + .../rfc0029/recursive-writer/false-success.c | 10 + .../recursive-writer/frozen-sha256.json | 11 + .../rfc0029/recursive-writer/manifest.json | 116 + .../rfc0029/recursive-writer/off-by-one.c | 10 + .../rfc0029/recursive-writer/provenance.md | 1 + .../rfc0029/recursive-writer/short.c | 10 + .../rfc0029/recursive-writer/uninitialized.c | 10 + .../repeated-fresh-outputs/PROVENANCE.md | 6 + .../rfc0029/repeated-fresh-outputs/api.h | 4 + .../repeated-fresh-outputs/direct-build.c | 2 + .../repeated-fresh-outputs/direct-client.c | 2 + .../rfc0029/repeated-fresh-outputs/drop.c | 2 + .../repeated-fresh-outputs/five-build.c | 2 + .../repeated-fresh-outputs/five-client.c | 2 + .../repeated-fresh-outputs/forwarded-build.c | 2 + .../repeated-fresh-outputs/forwarded-client.c | 2 + .../repeated-fresh-outputs/frozen-sha256.json | 22 + .../repeated-fresh-outputs/interior-build.c | 2 + .../repeated-fresh-outputs/interior-client.c | 2 + .../repeated-fresh-outputs/lost-build.c | 2 + .../repeated-fresh-outputs/lost-client.c | 2 + .../rfc0029/repeated-fresh-outputs/make.c | 2 + .../repeated-fresh-outputs/manifest.json | 138 ++ .../rfc0029/repeated-fresh-outputs/mark.c | 3 + .../repeated-fresh-outputs/released-build.c | 2 + .../repeated-fresh-outputs/released-client.c | 2 + .../repeated-fresh-outputs/reused-build.c | 2 + .../repeated-fresh-outputs/reused-client.c | 2 + .../rfc0029/reverse-byte-writes/README.md | 1 + .../reverse-byte-writes/frozen-sha256.json | 9 + .../rfc0029/reverse-byte-writes/good.c | 2 + .../rfc0029/reverse-byte-writes/library.c | 11 + .../rfc0029/reverse-byte-writes/manifest.json | 82 + .../rfc0029/reverse-byte-writes/past-end.c | 12 + .../rfc0029/reverse-byte-writes/readonly.c | 2 + .../rfc0029/reverse-byte-writes/short.c | 2 + .../rfc0029/reverse-initialization/README.md | 6 + .../rfc0029/reverse-initialization/early.c | 1 + .../reverse-initialization/frozen-sha256.json | 12 + .../rfc0029/reverse-initialization/good.c | 2 + .../rfc0029/reverse-initialization/library.c | 8 + .../rfc0029/reverse-initialization/local.c | 6 + .../reverse-initialization/manifest.json | 126 ++ .../rfc0029/reverse-initialization/short.c | 2 + .../rfc0029/reverse-initialization/skipped.c | 1 + .../rfc0029/reverse-initialization/stride.c | 1 + .../rfc0029/reverse-initialization/zero.c | 1 + .../advanced-manifest.json | 20 + .../scalar-write-offsets/advanced-read.c | 6 + .../scalar-write-offsets/advanced-sha256.json | 4 + .../rfc0029/scalar-write-offsets/audit.md | 17 + .../audited-manifest.json | 78 + .../scalar-write-offsets/audited-sha256.json | 8 + .../extended-manifest.json | 63 + .../scalar-write-offsets/extended-sha256.json | 5 + .../scalar-write-offsets/frozen-sha256.json | 5 + .../heap-unreachable-write.c | 14 + .../heap-wrong-base-byte.c | 14 + .../scalar-write-offsets/manifest.json | 34 + .../scalar-write-offsets/unreachable-write.c | 11 + .../scalar-write-offsets/wrong-base-byte.c | 11 + test/evaluation/rfc0029/serializer/client.c | 13 + .../rfc0029/serializer/frozen-sha256.json | 8 + test/evaluation/rfc0029/serializer/hex.c | 30 + test/evaluation/rfc0029/serializer/hex.h | 14 + .../rfc0029/serializer/manifest.json | 54 + .../rfc0029/serializer/provenance.md | 1 + test/evaluation/rfc0029/serializer/short.c | 8 + .../rfc0029/shifted-pointee-facts/README.md | 1 + .../rfc0029/shifted-pointee-facts/back.c | 2 + .../shifted-pointee-facts/frozen-sha256.json | 8 + .../shifted-pointee-facts/manifest.json | 65 + .../rfc0029/shifted-pointee-facts/same.c | 2 + .../rfc0029/shifted-pointee-facts/shift.c | 2 + .../rfc0029/shifted-pointee-facts/unknown.c | 2 + .../singleton-link-ownership/PROVENANCE.md | 6 + .../rfc0029/singleton-link-ownership/api.h | 4 + .../rfc0029/singleton-link-ownership/client.c | 2 + .../rfc0029/singleton-link-ownership/direct.c | 2 + .../singleton-link-ownership/disowned.c | 2 + .../rfc0029/singleton-link-ownership/drop.c | 2 + .../singleton-link-ownership/forwarded.c | 2 + .../frozen-sha256.json | 14 + .../rfc0029/singleton-link-ownership/lost.c | 2 + .../rfc0029/singleton-link-ownership/make.c | 2 + .../singleton-link-ownership/manifest.json | 100 + .../singleton-link-ownership/released.c | 2 + .../rfc0029/singleton-link-ownership/step.c | 2 + .../rfc0029/span-advances/README.md | 4 + .../rfc0029/span-advances/frozen-sha256.json | 10 + test/evaluation/rfc0029/span-advances/good.c | 5 + .../rfc0029/span-advances/library.c | 5 + .../rfc0029/span-advances/manifest.json | 98 + test/evaluation/rfc0029/span-advances/once.c | 5 + .../rfc0029/span-advances/over-count.c | 9 + .../rfc0029/span-advances/over-step.c | 5 + .../rfc0029/span-advances/uninitialized.c | 5 + .../rfc0029/span-count-joins/README.md | 5 + .../rfc0029/span-count-joins/changed-count.c | 9 + .../rfc0029/span-count-joins/false-count.c | 8 + .../span-count-joins/frozen-sha256.json | 9 + .../rfc0029/span-count-joins/good.c | 2 + .../rfc0029/span-count-joins/library.c | 13 + .../rfc0029/span-count-joins/manifest.json | 81 + .../rfc0029/span-count-joins/short.c | 2 + test/evaluation/rfc0029/span-counts/README.md | 1 + .../rfc0029/span-counts/false-count.c | 12 + .../rfc0029/span-counts/frozen-sha256.json | 9 + test/evaluation/rfc0029/span-counts/good.c | 2 + test/evaluation/rfc0029/span-counts/library.c | 11 + .../rfc0029/span-counts/manifest.json | 82 + test/evaluation/rfc0029/span-counts/short.c | 2 + .../rfc0029/span-counts/uninitialized.c | 2 + .../evaluation/rfc0029/span-outputs/README.md | 1 + .../rfc0029/span-outputs/frozen-sha256.json | 8 + test/evaluation/rfc0029/span-outputs/good.c | 2 + .../evaluation/rfc0029/span-outputs/library.c | 4 + .../rfc0029/span-outputs/manifest.json | 67 + .../rfc0029/span-outputs/overflow.c | 2 + .../rfc0029/span-outputs/readonly.c | 2 + test/evaluation/rfc0029/state-capacity-bad.c | 11 + test/evaluation/rfc0029/state-good.c | 14 + test/evaluation/rfc0029/state-stale-bad.c | 10 + test/evaluation/rfc0029/state-tail-bad.c | 9 + test/evaluation/rfc0029/state.h | 31 + .../string-length-copies/PROVENANCE.md | 5 + .../rfc0029/string-length-copies/api.h | 4 + .../rfc0029/string-length-copies/callback.c | 3 + .../rfc0029/string-length-copies/client.c | 2 + .../rfc0029/string-length-copies/direct.c | 2 + .../rfc0029/string-length-copies/forward.c | 3 + .../string-length-copies/frozen-sha256.json | 13 + .../string-length-copies/manifest.json | 117 + .../rfc0029/string-length-copies/missing.c | 2 + .../rfc0029/string-length-copies/overread.c | 2 + .../rfc0029/string-length-copies/stale.c | 2 + .../rfc0029/string-length-copies/tail.c | 2 + .../temporary-release-frames/PROVENANCE.md | 1 + .../rfc0029/temporary-release-frames/api.h | 5 + .../temporary-release-frames/attached.c | 3 + .../rfc0029/temporary-release-frames/client.c | 2 + .../frozen-sha256.json | 14 + .../temporary-release-frames/guarded.c | 3 + .../rfc0029/temporary-release-frames/helper.c | 3 + .../temporary-release-frames/interior.c | 3 + .../rfc0029/temporary-release-frames/lost.c | 3 + .../temporary-release-frames/manifest.json | 124 ++ .../temporary-release-frames/nullable.c | 3 + .../rfc0029/temporary-release-frames/reader.c | 3 + .../rfc0029/temporary-release-frames/twice.c | 3 + test/evaluation/rfc0029/transport/api.h | 9 + test/evaluation/rfc0029/transport/bad.c | 4 + test/evaluation/rfc0029/transport/client.c | 13 + .../rfc0029/transport/frozen-sha256.json | 8 + test/evaluation/rfc0029/transport/library.c | 17 + .../rfc0029/transport/manifest.json | 54 + .../rfc0029/transport/provenance.md | 1 + test/evaluation/rfc0029/traversal/cycle.c | 15 + .../rfc0029/traversal/frozen-sha256.json | 8 + test/evaluation/rfc0029/traversal/interior.c | 15 + .../rfc0029/traversal/manifest.json | 72 + test/evaluation/rfc0029/traversal/mutation.c | 15 + test/evaluation/rfc0029/traversal/mutual.c | 15 + .../rfc0029/traversal/nondecreasing.c | 15 + .../rfc0029/traversal/provenance.md | 1 + test/evaluation/rfc0029/traversal/review.md | 1 + .../rfc0029/traversal/reviewed-manifest.json | 87 + .../rfc0029/traversal/reviewed-sha256.json | 5 + .../rfc0029/upstream-construction/README.md | 1 + .../upstream-construction/frozen-sha256.json | 8 + .../upstream-construction/manifest.json | 53 + .../upstream-construction/nested-serialize.c | 20 + .../upstream-construction/output-leak.c | 20 + .../upstream-construction/output-twice.c | 20 + .../upstream-identity.json | 11 + .../rfc0029/upstream-extended/failed-parse.c | 12 + .../rfc0029/upstream-extended/failed-print.c | 15 + .../upstream-extended/frozen-sha256.json | 8 + .../upstream-extended/malformed-delete.c | 8 + .../rfc0029/upstream-extended/manifest.json | 69 + .../rfc0029/upstream-extended/nested-print.c | 11 + .../upstream-extended/upstream-identity.json | 11 + .../rfc0029/upstream-lifetime-audit/README.md | 19 + .../failed-parse-stack.c | 12 + .../failed-parse-static.c | 12 + .../frozen-sha256.json | 8 + .../upstream-lifetime-audit/manifest.json | 37 + .../rfc0029/upstream-lifetime-audit/oracle.c | 19 + .../upstream-identity.json | 11 + .../upstream-oracle/frozen-sha256.json | 4 + .../rfc0029/upstream-oracle/oracle.c | 93 + .../upstream-oracle/upstream-identity.json | 11 + .../rfc0029/upstream-static-inputs/README.md | 1 + .../upstream-static-inputs/frozen-sha256.json | 7 + .../upstream-static-inputs/malformed-delete.c | 8 + .../upstream-static-inputs/manifest.json | 37 + .../upstream-static-inputs/parse-delete.c | 9 + .../upstream-identity.json | 11 + .../rfc0029/upstream/frozen-sha256.json | 7 + .../evaluation/rfc0029/upstream/manifest.json | 53 + .../rfc0029/upstream/parse-delete.c | 9 + .../rfc0029/upstream/parse-double-bad.c | 10 + .../rfc0029/upstream/print-delete.c | 10 + .../rfc0029/upstream/upstream-identity.json | 11 + .../rfc0029/writer-forwarding/audit.md | 1 + .../writer-forwarding/frozen-sha256.json | 6 + .../rfc0029/writer-forwarding/manifest.json | 50 + .../writer-forwarding/reviewed-manifest.json | 50 + .../writer-forwarding/reviewed-sha256.json | 7 + .../rfc0029/writer-forwarding/reviewed-skip.c | 36 + .../writer-forwarding/reviewed-write.c | 36 + .../writer-forwarding/reviewed-wrong-byte.c | 36 + .../rfc0029/writer-forwarding/skip.c | 36 + .../rfc0029/writer-forwarding/write.c | 36 + .../rfc0029/writer-forwarding/wrong-byte.c | 36 + .../writer-position-bounds/frozen-sha256.json | 6 + .../writer-position-bounds/manifest.json | 50 + .../rfc0029/writer-position-bounds/skip.c | 38 + .../rfc0029/writer-position-bounds/write.c | 38 + .../writer-position-bounds/wrong-byte.c | 38 + .../writer-return-aliases/frozen-sha256.json | 6 + .../writer-return-aliases/manifest.json | 50 + .../rfc0029/writer-return-aliases/skip.c | 34 + .../rfc0029/writer-return-aliases/write.c | 34 + .../writer-return-aliases/wrong-byte.c | 34 + .../frozen-sha256.json | 6 + .../writer-return-expressions/manifest.json | 50 + .../rfc0029/writer-return-expressions/skip.c | 33 + .../rfc0029/writer-return-expressions/write.c | 33 + .../writer-return-expressions/wrong-byte.c | 33 + .../evaluation/rfc0029/writer-transport/api.h | 3 + .../rfc0029/writer-transport/client.c | 2 + .../writer-transport/frozen-sha256.json | 7 + .../rfc0029/writer-transport/library.c | 8 + .../rfc0029/writer-transport/manifest.json | 67 + .../rfc0029/writer-transport/provenance.md | 1 + .../rfc0029/writer-transport/short.c | 2 + .../rfc0029/writer-transport/uninitialized.c | 2 + .../rfc0029/zero-counters/README.md | 1 + test/evaluation/rfc0029/zero-counters/array.c | 3 + .../evaluation/rfc0029/zero-counters/audit.md | 9 + .../zero-counters/audited-manifest.json | 95 + .../rfc0029/zero-counters/audited-sha256.json | 10 + test/evaluation/rfc0029/zero-counters/cases.c | 9 + test/evaluation/rfc0029/zero-counters/cells.c | 3 + .../rfc0029/zero-counters/frozen-sha256.json | 5 + .../evaluation/rfc0029/zero-counters/helper.c | 4 + .../rfc0029/zero-counters/manifest.json | 95 + .../rfc0029/zero-counters/partial.c | 3 + test/evaluation/rfc0029/zero-counters/plain.c | 3 + .../rfc0029/zero-counters/replaced.c | 3 + test/evaluation/rfc0029/zero-frames/README.md | 1 + test/evaluation/rfc0029/zero-frames/alias.c | 7 + .../rfc0029/zero-frames/frozen-sha256.json | 7 + .../rfc0029/zero-frames/manifest.json | 50 + .../rfc0029/zero-frames/overwrite.c | 11 + test/evaluation/rfc0029/zero-frames/writer.c | 11 + unittests/Analysis/ArrayOwnershipTest.cpp | 34 + unittests/Analysis/BufferTest.cpp | 589 +++++ unittests/Analysis/CMakeLists.txt | 1 + unittests/Analysis/CallContextTest.cpp | 532 ++++- unittests/Analysis/CheckedCodeTest.cpp | 13 + unittests/Analysis/DataflowTest.cpp | 2 +- unittests/Analysis/IntegerSemanticsTest.cpp | 149 ++ unittests/Analysis/InterfaceTypesTest.cpp | 32 + unittests/Analysis/RecursiveContainerTest.cpp | 485 ++++ unittests/Analysis/RecursiveContractsTest.cpp | 1946 +++++++++++++++++ unittests/Analysis/SummariesTest.cpp | 6 +- unittests/Analysis/TraversalTest.cpp | 19 + unittests/Core/BufferTest.cpp | 47 + unittests/Core/CMakeLists.txt | 1 + unittests/Core/CallContextTest.cpp | 108 + unittests/Core/ContainerTest.cpp | 224 ++ unittests/Core/FormatTest.cpp | 28 + unittests/Core/InductionTest.cpp | 55 + unittests/Core/IntegerExpressionTest.cpp | 47 + unittests/Core/InterfaceTest.cpp | 23 +- unittests/Core/RelationTest.cpp | 33 + unittests/Core/SafetyTest.cpp | 367 ++++ unittests/Core/SummaryIOTest.cpp | 2 +- unittests/Frontend/CheckedReportTest.cpp | 2 +- unittests/Frontend/SidecarTest.cpp | 20 +- 1289 files changed, 33103 insertions(+), 658 deletions(-) create mode 100644 include/weavec/Core/Induction.h create mode 100644 lib/Analysis/DataflowBufferDiscovery.cpp create mode 100644 lib/Analysis/DataflowByteContents.cpp create mode 100644 lib/Analysis/DataflowCallbackContracts.cpp create mode 100644 lib/Analysis/DataflowFloating.cpp create mode 100644 lib/Analysis/DataflowRecursiveConstruction.cpp create mode 100644 lib/Analysis/DataflowRecursiveExtension.cpp create mode 100644 lib/Analysis/DataflowRecursiveWriting.cpp create mode 100644 lib/Analysis/DataflowSpans.cpp create mode 100644 lib/Analysis/FloatingCastSupport.cpp create mode 100644 lib/Analysis/FloatingCastSupport.h create mode 100644 lib/Analysis/RecursiveContracts.cpp create mode 100644 lib/Core/Induction.cpp create mode 100644 scripts/checked-workflows.py create mode 100644 test/Analysis/rfc0029-callback-contracts.c create mode 100644 test/Analysis/rfc0029-character-pointer-slots.c create mode 100644 test/Analysis/rfc0029-container-value-guards.c create mode 100644 test/Analysis/rfc0029-floating-conversions.c create mode 100644 test/Analysis/rfc0029-numeric-input.c create mode 100644 test/Analysis/rfc0029-record-arrays.c create mode 100644 test/Analysis/rfc0029-recursive-cases.c create mode 100644 test/Analysis/rfc0029-recursive-traversal.c create mode 100644 test/Analysis/rfc0029-scalar-cell-identity.c create mode 100644 test/WholeProgram/rfc0029-advance-outcomes.c create mode 100644 test/WholeProgram/rfc0029-anonymous-private-state.c create mode 100644 test/WholeProgram/rfc0029-attached-payload-transfer.c create mode 100644 test/WholeProgram/rfc0029-bounded-string-cursor.c create mode 100644 test/WholeProgram/rfc0029-buffer-entry-intervals.c create mode 100644 test/WholeProgram/rfc0029-byte-callee-frames.c create mode 100644 test/WholeProgram/rfc0029-byte-comparisons.c create mode 100644 test/WholeProgram/rfc0029-byte-cursor-content.c create mode 100644 test/WholeProgram/rfc0029-byte-cursor-forwarding.c create mode 100644 test/WholeProgram/rfc0029-byte-cursor-frames.c create mode 100644 test/WholeProgram/rfc0029-byte-cursor-static.c create mode 100644 test/WholeProgram/rfc0029-byte-global-frames.c create mode 100644 test/WholeProgram/rfc0029-byte-helper-frames.c create mode 100644 test/WholeProgram/rfc0029-byte-loop-partitions.c create mode 100644 test/WholeProgram/rfc0029-byte-switch-partitions.c create mode 100644 test/WholeProgram/rfc0029-cast-reader-intervals.c create mode 100644 test/WholeProgram/rfc0029-comparison-container-frames.c create mode 100644 test/WholeProgram/rfc0029-conditional-count-arguments.c create mode 100644 test/WholeProgram/rfc0029-container-alias-outputs.c create mode 100644 test/WholeProgram/rfc0029-container-outcome-frames.c create mode 100644 test/WholeProgram/rfc0029-counter-reset-ranges.c create mode 100644 test/WholeProgram/rfc0029-fixed-span-steps.c create mode 100644 test/WholeProgram/rfc0029-floating-call-premises.c create mode 100644 test/WholeProgram/rfc0029-guarded-advance.c create mode 100644 test/WholeProgram/rfc0029-guarded-cursor-bounds.c create mode 100644 test/WholeProgram/rfc0029-helper-cursor-pairs.c create mode 100644 test/WholeProgram/rfc0029-in-place-extension.c create mode 100644 test/WholeProgram/rfc0029-independent-cursors.c create mode 100644 test/WholeProgram/rfc0029-initialized-advance.c create mode 100644 test/WholeProgram/rfc0029-initialized-spans.c create mode 100644 test/WholeProgram/rfc0029-local-callee-copies.c create mode 100644 test/WholeProgram/rfc0029-numeric-container-frames.c create mode 100644 test/WholeProgram/rfc0029-numeric-scan-locals.c create mode 100644 test/WholeProgram/rfc0029-numeric-scans.c create mode 100644 test/WholeProgram/rfc0029-numeric-short-circuit.c create mode 100644 test/WholeProgram/rfc0029-numeric-text.c create mode 100644 test/WholeProgram/rfc0029-output-construction.c create mode 100644 test/WholeProgram/rfc0029-paired-cursor-loops.c create mode 100644 test/WholeProgram/rfc0029-paired-reader-counters.c create mode 100644 test/WholeProgram/rfc0029-payload-early-exits.c create mode 100644 test/WholeProgram/rfc0029-payload-publication.c create mode 100644 test/WholeProgram/rfc0029-payload-reader-returns.c create mode 100644 test/WholeProgram/rfc0029-payload-relocation.c create mode 100644 test/WholeProgram/rfc0029-payload-write-frames.c create mode 100644 test/WholeProgram/rfc0029-pointer-count-readers.c create mode 100644 test/WholeProgram/rfc0029-pointer-difference-sizes.c create mode 100644 test/WholeProgram/rfc0029-pointer-reader-offsets.c create mode 100644 test/WholeProgram/rfc0029-reader-construction.c create mode 100644 test/WholeProgram/rfc0029-reader-index-loops.c create mode 100644 test/WholeProgram/rfc0029-reader-projection.c create mode 100644 test/WholeProgram/rfc0029-recursive-construction.c create mode 100644 test/WholeProgram/rfc0029-recursive-writer.c create mode 100644 test/WholeProgram/rfc0029-reverse-byte-writes.c create mode 100644 test/WholeProgram/rfc0029-reverse-initialization.c create mode 100644 test/WholeProgram/rfc0029-shifted-pointee-facts.c create mode 100644 test/WholeProgram/rfc0029-singleton-link-ownership.c create mode 100644 test/WholeProgram/rfc0029-span-advances.c create mode 100644 test/WholeProgram/rfc0029-span-count-joins.c create mode 100644 test/WholeProgram/rfc0029-span-counts.c create mode 100644 test/WholeProgram/rfc0029-string-length-copies.c create mode 100644 test/WholeProgram/rfc0029-temporary-release-frames.c create mode 100644 test/WholeProgram/rfc0029-workflows.c create mode 100644 test/evaluation/rfc0029/README.md create mode 100644 test/evaluation/rfc0029/advance-outcomes/README.md create mode 100644 test/evaluation/rfc0029/advance-outcomes/SHA256SUMS create mode 100644 test/evaluation/rfc0029/advance-outcomes/bad-failure.c create mode 100644 test/evaluation/rfc0029/advance-outcomes/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/advance-outcomes/good.c create mode 100644 test/evaluation/rfc0029/advance-outcomes/interior.c create mode 100644 test/evaluation/rfc0029/advance-outcomes/library.c create mode 100644 test/evaluation/rfc0029/advance-outcomes/manifest.json create mode 100644 test/evaluation/rfc0029/advance-outcomes/short.c create mode 100644 test/evaluation/rfc0029/advance-outcomes/skipped.c create mode 100644 test/evaluation/rfc0029/anonymous-private-state/README.md create mode 100644 test/evaluation/rfc0029/anonymous-private-state/client.c create mode 100644 test/evaluation/rfc0029/anonymous-private-state/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/anonymous-private-state/manifest.json create mode 100644 test/evaluation/rfc0029/anonymous-private-state/state.c create mode 100644 test/evaluation/rfc0029/attached-payload-transfer/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/attached-payload-transfer/api.h create mode 100644 test/evaluation/rfc0029/attached-payload-transfer/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/attached-payload-transfer/good.c create mode 100644 test/evaluation/rfc0029/attached-payload-transfer/ignored.c create mode 100644 test/evaluation/rfc0029/attached-payload-transfer/leak.c create mode 100644 test/evaluation/rfc0029/attached-payload-transfer/library.c create mode 100644 test/evaluation/rfc0029/attached-payload-transfer/manifest.json create mode 100644 test/evaluation/rfc0029/attached-payload-transfer/twice.c create mode 100644 test/evaluation/rfc0029/bounded-string-cursor/bounded.c create mode 100644 test/evaluation/rfc0029/bounded-string-cursor/earlier-zero.c create mode 100644 test/evaluation/rfc0029/bounded-string-cursor/escaped-cursor.c create mode 100644 test/evaluation/rfc0029/bounded-string-cursor/finish.c create mode 100644 test/evaluation/rfc0029/bounded-string-cursor/forged-capacity.c create mode 100644 test/evaluation/rfc0029/bounded-string-cursor/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/bounded-string-cursor/manifest.json create mode 100644 test/evaluation/rfc0029/bounded-string-cursor/uninitialized.c create mode 100644 test/evaluation/rfc0029/bounded-string-cursor/unterminated.c create mode 100644 test/evaluation/rfc0029/bounded-string-cursor/writer.h create mode 100644 test/evaluation/rfc0029/buffer-entry-intervals-reviewed/README.md create mode 100644 test/evaluation/rfc0029/buffer-entry-intervals-reviewed/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/buffer-entry-intervals-reviewed/full-physical.c create mode 100644 test/evaluation/rfc0029/buffer-entry-intervals-reviewed/manifest.json create mode 100644 test/evaluation/rfc0029/buffer-entry-intervals-reviewed/past-physical.c create mode 100644 test/evaluation/rfc0029/buffer-entry-intervals-reviewed/replaced-short.c create mode 100644 test/evaluation/rfc0029/buffer-entry-intervals-reviewed/short-physical.c create mode 100644 test/evaluation/rfc0029/buffer-entry-intervals-reviewed/spare-physical.c create mode 100644 test/evaluation/rfc0029/buffer-entry-intervals-reviewed/uninitialized-tail.c create mode 100644 test/evaluation/rfc0029/byte-callee-frames/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-callee-frames/api.h create mode 100644 test/evaluation/rfc0029/byte-callee-frames/changed-library.c create mode 100644 test/evaluation/rfc0029/byte-callee-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-callee-frames/library.c create mode 100644 test/evaluation/rfc0029/byte-callee-frames/local.c create mode 100644 test/evaluation/rfc0029/byte-callee-frames/manifest.json create mode 100644 test/evaluation/rfc0029/byte-callee-frames/mutable.c create mode 100644 test/evaluation/rfc0029/byte-callee-frames/partial.c create mode 100644 test/evaluation/rfc0029/byte-callee-frames/static.c create mode 100644 test/evaluation/rfc0029/byte-callee-frames/unknown-library.c create mode 100644 test/evaluation/rfc0029/byte-callee-frames/wrong.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-comparisons/bom-absent.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/bom-present.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/changed.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/embedded-zero-memory.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/embedded-zero-string.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/equal.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-comparisons/library.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/manifest.json create mode 100644 test/evaluation/rfc0029/byte-comparisons/partial.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/short.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/sign-not-magnitude.c create mode 100644 test/evaluation/rfc0029/byte-comparisons/unsigned-order.c create mode 100644 test/evaluation/rfc0029/byte-cursor-content/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-cursor-content/api.h create mode 100644 test/evaluation/rfc0029/byte-cursor-content/bad.c create mode 100644 test/evaluation/rfc0029/byte-cursor-content/changed.c create mode 100644 test/evaluation/rfc0029/byte-cursor-content/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-cursor-content/good.c create mode 100644 test/evaluation/rfc0029/byte-cursor-content/library.c create mode 100644 test/evaluation/rfc0029/byte-cursor-content/local-write.c create mode 100644 test/evaluation/rfc0029/byte-cursor-content/local.c create mode 100644 test/evaluation/rfc0029/byte-cursor-content/manifest.json create mode 100644 test/evaluation/rfc0029/byte-cursor-content/offset.c create mode 100644 test/evaluation/rfc0029/byte-cursor-content/uninitialized.c create mode 100644 test/evaluation/rfc0029/byte-cursor-content/unknown-write.c create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/api.h create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/bad.c create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/changed.c create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/good.c create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/library.c create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/local-write.c create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/local.c create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/manifest.json create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/offset.c create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/uninitialized.c create mode 100644 test/evaluation/rfc0029/byte-cursor-forwarding/unknown-write.c create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/REVIEW.md create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/alias.c create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/api.h create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/bad.c create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/changed.c create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/good.c create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/library.c create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/local-write.c create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/local.c create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/manifest.json create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/offset.c create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/reviewed-manifest.json create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/reviewed-sha256.json create mode 100644 test/evaluation/rfc0029/byte-cursor-frames/uninitialized.c create mode 100644 test/evaluation/rfc0029/byte-cursor-static/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-cursor-static/api.h create mode 100644 test/evaluation/rfc0029/byte-cursor-static/bad.c create mode 100644 test/evaluation/rfc0029/byte-cursor-static/changed.c create mode 100644 test/evaluation/rfc0029/byte-cursor-static/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-cursor-static/good.c create mode 100644 test/evaluation/rfc0029/byte-cursor-static/library.c create mode 100644 test/evaluation/rfc0029/byte-cursor-static/local-write.c create mode 100644 test/evaluation/rfc0029/byte-cursor-static/local.c create mode 100644 test/evaluation/rfc0029/byte-cursor-static/manifest.json create mode 100644 test/evaluation/rfc0029/byte-cursor-static/offset.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-global-frames/api.h create mode 100644 test/evaluation/rfc0029/byte-global-frames/bad.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/changed.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/const-write.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-global-frames/good.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/library.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/local-write.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/local.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/manifest.json create mode 100644 test/evaluation/rfc0029/byte-global-frames/offset.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/release-const.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/uninitialized.c create mode 100644 test/evaluation/rfc0029/byte-global-frames/unknown-write.c create mode 100644 test/evaluation/rfc0029/byte-helper-frames/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-helper-frames/api.h create mode 100644 test/evaluation/rfc0029/byte-helper-frames/bad.c create mode 100644 test/evaluation/rfc0029/byte-helper-frames/changed.c create mode 100644 test/evaluation/rfc0029/byte-helper-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-helper-frames/good.c create mode 100644 test/evaluation/rfc0029/byte-helper-frames/library.c create mode 100644 test/evaluation/rfc0029/byte-helper-frames/local-write.c create mode 100644 test/evaluation/rfc0029/byte-helper-frames/local.c create mode 100644 test/evaluation/rfc0029/byte-helper-frames/manifest.json create mode 100644 test/evaluation/rfc0029/byte-helper-frames/offset.c create mode 100644 test/evaluation/rfc0029/byte-helper-frames/uninitialized.c create mode 100644 test/evaluation/rfc0029/byte-helper-frames/unknown-write.c create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/api.h create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/changed.c create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/do.c create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/good.c create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/helper.c create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/manifest.json create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/missing.c create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/skip.c create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/uninitialized.c create mode 100644 test/evaluation/rfc0029/byte-loop-partitions/while.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/api.h create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/beyond-bound.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/changed.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/direct.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/forward.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/manifest.json create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/missing.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/scanner.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/shifted.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/skip-scanner.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/skip.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/uninitialized.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions-reviewed/wrapper.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/api.h create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/beyond-bound.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/changed.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/direct.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/forward.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/manifest.json create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/missing.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/scanner.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/shifted.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/skip.c create mode 100644 test/evaluation/rfc0029/byte-switch-partitions/uninitialized.c create mode 100644 test/evaluation/rfc0029/callback-allocate.c create mode 100644 test/evaluation/rfc0029/callback-short-bad.c create mode 100644 test/evaluation/rfc0029/cast-reader-intervals/README.md create mode 100644 test/evaluation/rfc0029/cast-reader-intervals/forged.c create mode 100644 test/evaluation/rfc0029/cast-reader-intervals/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/cast-reader-intervals/good.c create mode 100644 test/evaluation/rfc0029/cast-reader-intervals/library.c create mode 100644 test/evaluation/rfc0029/cast-reader-intervals/manifest.json create mode 100644 test/evaluation/rfc0029/cast-reader-intervals/reader.h create mode 100644 test/evaluation/rfc0029/cast-reader-intervals/scaled-bad.c create mode 100644 test/evaluation/rfc0029/cast-reader-intervals/scaled-good.c create mode 100644 test/evaluation/rfc0029/cast-reader-intervals/uninitialized.c create mode 100644 test/evaluation/rfc0029/character-pointer-slots/README.md create mode 100644 test/evaluation/rfc0029/character-pointer-slots/end.c create mode 100644 test/evaluation/rfc0029/character-pointer-slots/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/character-pointer-slots/manifest.json create mode 100644 test/evaluation/rfc0029/character-pointer-slots/past-end.c create mode 100644 test/evaluation/rfc0029/character-pointer-slots/readonly.c create mode 100644 test/evaluation/rfc0029/character-pointer-slots/unrelated.c create mode 100644 test/evaluation/rfc0029/character-pointer-slots/write.c create mode 100644 test/evaluation/rfc0029/combined-callback-cases/README.md create mode 100644 test/evaluation/rfc0029/combined-callback-cases/api.c create mode 100644 test/evaluation/rfc0029/combined-callback-cases/api.h create mode 100644 test/evaluation/rfc0029/combined-callback-cases/client.c create mode 100644 test/evaluation/rfc0029/combined-callback-cases/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/combined-callback-cases/inactive.c create mode 100644 test/evaluation/rfc0029/combined-callback-cases/manifest.json create mode 100644 test/evaluation/rfc0029/combined-callback-cases/missing.c create mode 100644 test/evaluation/rfc0029/combined-callback-cases/mixed.c create mode 100644 test/evaluation/rfc0029/combined-callback-cases/null.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/api.h create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/bounded-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/bounded.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/bytes-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/bytes.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/extent-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/extent.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/manifest.json create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/reader.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/released-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/released.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/string-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/string.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/uninitialized-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/uninitialized.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/unknown-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames-reviewed/unknown.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/comparison-container-frames/api.h create mode 100644 test/evaluation/rfc0029/comparison-container-frames/bounded-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/bounded.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/bytes-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/bytes.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/extent-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/extent.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/comparison-container-frames/manifest.json create mode 100644 test/evaluation/rfc0029/comparison-container-frames/reader.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/released-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/released.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/string-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/string.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/uninitialized-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/uninitialized.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/unknown-inspect.c create mode 100644 test/evaluation/rfc0029/comparison-container-frames/unknown.c create mode 100644 test/evaluation/rfc0029/conditional-count-arguments/README.md create mode 100644 test/evaluation/rfc0029/conditional-count-arguments/changed.c create mode 100644 test/evaluation/rfc0029/conditional-count-arguments/converted.c create mode 100644 test/evaluation/rfc0029/conditional-count-arguments/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/conditional-count-arguments/good.c create mode 100644 test/evaluation/rfc0029/conditional-count-arguments/library.c create mode 100644 test/evaluation/rfc0029/conditional-count-arguments/manifest.json create mode 100644 test/evaluation/rfc0029/conditional-count-arguments/short.c create mode 100644 test/evaluation/rfc0029/conditional-count-arguments/zero.c create mode 100644 test/evaluation/rfc0029/construction-helpers/build.c create mode 100644 test/evaluation/rfc0029/construction-helpers/double.c create mode 100644 test/evaluation/rfc0029/construction-helpers/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/construction-helpers/leak.c create mode 100644 test/evaluation/rfc0029/construction-helpers/manifest.json create mode 100644 test/evaluation/rfc0029/construction-helpers/provenance.md create mode 100644 test/evaluation/rfc0029/construction-helpers/short.c create mode 100644 test/evaluation/rfc0029/construction/build.c create mode 100644 test/evaluation/rfc0029/construction/cycle.c create mode 100644 test/evaluation/rfc0029/construction/double.c create mode 100644 test/evaluation/rfc0029/construction/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/construction/leak.c create mode 100644 test/evaluation/rfc0029/construction/manifest.json create mode 100644 test/evaluation/rfc0029/construction/provenance.md create mode 100644 test/evaluation/rfc0029/construction/short.c create mode 100644 test/evaluation/rfc0029/construction/uninitialized.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/container-alias-outputs/api.h create mode 100644 test/evaluation/rfc0029/container-alias-outputs/direct.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/disown.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/disowned.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/drop.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/forward.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/forwarded.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/container-alias-outputs/interior.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/lost-payload.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/manifest.json create mode 100644 test/evaluation/rfc0029/container-alias-outputs/payload.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/released.c create mode 100644 test/evaluation/rfc0029/container-alias-outputs/update.c create mode 100644 test/evaluation/rfc0029/container-call-frames/child.c create mode 100644 test/evaluation/rfc0029/container-call-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/container-call-frames/local.c create mode 100644 test/evaluation/rfc0029/container-call-frames/manifest.json create mode 100644 test/evaluation/rfc0029/container-call-frames/root.c create mode 100644 test/evaluation/rfc0029/container-local-frames/child.c create mode 100644 test/evaluation/rfc0029/container-local-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/container-local-frames/local.c create mode 100644 test/evaluation/rfc0029/container-local-frames/manifest.json create mode 100644 test/evaluation/rfc0029/container-local-frames/root.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/container-outcome-frames/api.h create mode 100644 test/evaluation/rfc0029/container-outcome-frames/branch-inspect.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/branch.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/direct-inspect.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/direct.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/disowned-inspect.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/disowned.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/drop.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/forward.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/container-outcome-frames/helper-inspect.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/helper.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/lost-inspect.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/lost.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/manifest.json create mode 100644 test/evaluation/rfc0029/container-outcome-frames/released-inspect.c create mode 100644 test/evaluation/rfc0029/container-outcome-frames/released.c create mode 100644 test/evaluation/rfc0029/container-value-guards/README.md create mode 100644 test/evaluation/rfc0029/container-value-guards/actual-allocation.c create mode 100644 test/evaluation/rfc0029/container-value-guards/changed-alias.c create mode 100644 test/evaluation/rfc0029/container-value-guards/changed-selector.c create mode 100644 test/evaluation/rfc0029/container-value-guards/forest.h create mode 100644 test/evaluation/rfc0029/container-value-guards/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/container-value-guards/manifest.json create mode 100644 test/evaluation/rfc0029/container-value-guards/null-result.c create mode 100644 test/evaluation/rfc0029/container-value-guards/released-head.c create mode 100644 test/evaluation/rfc0029/container-value-guards/replaced-head.c create mode 100644 test/evaluation/rfc0029/container-value-guards/replaced-live-head.c create mode 100644 test/evaluation/rfc0029/container-value-guards/review.md create mode 100644 test/evaluation/rfc0029/container-value-guards/reviewed-manifest.json create mode 100644 test/evaluation/rfc0029/container-value-guards/reviewed-sha256.json create mode 100644 test/evaluation/rfc0029/corpus-regressions/api.h create mode 100644 test/evaluation/rfc0029/corpus-regressions/discovered-manifest.json create mode 100644 test/evaluation/rfc0029/corpus-regressions/discovered-sha256.json create mode 100644 test/evaluation/rfc0029/corpus-regressions/discovery.md create mode 100644 test/evaluation/rfc0029/corpus-regressions/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/corpus-regressions/library.c create mode 100644 test/evaluation/rfc0029/corpus-regressions/lookup.c create mode 100644 test/evaluation/rfc0029/corpus-regressions/manifest.json create mode 100644 test/evaluation/rfc0029/corpus-regressions/provenance.md create mode 100644 test/evaluation/rfc0029/corpus-regressions/unterminated.c create mode 100644 test/evaluation/rfc0029/corpus-regressions/update-discovered.c create mode 100644 test/evaluation/rfc0029/corpus-regressions/update.c create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/README.md create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/SHA256SUMS create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/decimal.c create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/good.c create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/leak.c create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/library.c create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/manifest-original.json create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/manifest.json create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/reader.h create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/released.c create mode 100644 test/evaluation/rfc0029/counter-reset-ranges-reviewed/short.c create mode 100644 test/evaluation/rfc0029/cursor-envelope-joins-reviewed/README.md create mode 100644 test/evaluation/rfc0029/cursor-envelope-joins-reviewed/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/cursor-envelope-joins-reviewed/good.c create mode 100644 test/evaluation/rfc0029/cursor-envelope-joins-reviewed/helper.c create mode 100644 test/evaluation/rfc0029/cursor-envelope-joins-reviewed/manifest.json create mode 100644 test/evaluation/rfc0029/cursor-envelope-joins-reviewed/over-capacity.c create mode 100644 test/evaluation/rfc0029/cursor-envelope-joins-reviewed/over-step.c create mode 100644 test/evaluation/rfc0029/cursor-envelope-joins-reviewed/uninitialized.c create mode 100644 test/evaluation/rfc0029/cursor-readers/README.md create mode 100644 test/evaluation/rfc0029/cursor-readers/audit.md create mode 100644 test/evaluation/rfc0029/cursor-readers/audited-manifest.json create mode 100644 test/evaluation/rfc0029/cursor-readers/audited-sha256.json create mode 100644 test/evaluation/rfc0029/cursor-readers/capacity-read.c create mode 100644 test/evaluation/rfc0029/cursor-readers/capacity.c create mode 100644 test/evaluation/rfc0029/cursor-readers/client.c create mode 100644 test/evaluation/rfc0029/cursor-readers/copy.c create mode 100644 test/evaluation/rfc0029/cursor-readers/cursor.c create mode 100644 test/evaluation/rfc0029/cursor-readers/diagnostic-audit.md create mode 100644 test/evaluation/rfc0029/cursor-readers/empty.c create mode 100644 test/evaluation/rfc0029/cursor-readers/endpoint-read.c create mode 100644 test/evaluation/rfc0029/cursor-readers/escape.c create mode 100644 test/evaluation/rfc0029/cursor-readers/escaped-read.c create mode 100644 test/evaluation/rfc0029/cursor-readers/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/cursor-readers/library.c create mode 100644 test/evaluation/rfc0029/cursor-readers/manifest.json create mode 100644 test/evaluation/rfc0029/cursor-readers/off-by-one.c create mode 100644 test/evaluation/rfc0029/cursor-readers/partial.c create mode 100644 test/evaluation/rfc0029/cursor-readers/reader.h create mode 100644 test/evaluation/rfc0029/cursor-readers/readonly.c create mode 100644 test/evaluation/rfc0029/cursor-readers/reviewed-manifest.json create mode 100644 test/evaluation/rfc0029/cursor-readers/reviewed-sha256.json create mode 100644 test/evaluation/rfc0029/cursor-readers/uninitialized-read.c create mode 100644 test/evaluation/rfc0029/cursor-readers/uninitialized.c create mode 100644 test/evaluation/rfc0029/fixed-span-steps/README.md create mode 100644 test/evaluation/rfc0029/fixed-span-steps/api.h create mode 100644 test/evaluation/rfc0029/fixed-span-steps/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/fixed-span-steps/good.c create mode 100644 test/evaluation/rfc0029/fixed-span-steps/library.c create mode 100644 test/evaluation/rfc0029/fixed-span-steps/manifest.json create mode 100644 test/evaluation/rfc0029/fixed-span-steps/odd-tail.c create mode 100644 test/evaluation/rfc0029/fixed-span-steps/over-count.c create mode 100644 test/evaluation/rfc0029/fixed-span-steps/over-step.c create mode 100644 test/evaluation/rfc0029/fixed-span-steps/runtime.c create mode 100644 test/evaluation/rfc0029/fixed-span-steps/uninitialized.c create mode 100644 test/evaluation/rfc0029/fixed-span-steps/unrelated.c create mode 100644 test/evaluation/rfc0029/floating-call-premises/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/floating-call-premises/api.h create mode 100644 test/evaluation/rfc0029/floating-call-premises/changed-clamp.c create mode 100644 test/evaluation/rfc0029/floating-call-premises/changed.c create mode 100644 test/evaluation/rfc0029/floating-call-premises/clamp.c create mode 100644 test/evaluation/rfc0029/floating-call-premises/finite.c create mode 100644 test/evaluation/rfc0029/floating-call-premises/forward.c create mode 100644 test/evaluation/rfc0029/floating-call-premises/forwarded.c create mode 100644 test/evaluation/rfc0029/floating-call-premises/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/floating-call-premises/infinite.c create mode 100644 test/evaluation/rfc0029/floating-call-premises/manifest.json create mode 100644 test/evaluation/rfc0029/floating-call-premises/mixed.c create mode 100644 test/evaluation/rfc0029/floating-call-premises/nan.c create mode 100644 test/evaluation/rfc0029/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/guarded-advance/README.md create mode 100644 test/evaluation/rfc0029/guarded-advance/changed.c create mode 100644 test/evaluation/rfc0029/guarded-advance/failure.c create mode 100644 test/evaluation/rfc0029/guarded-advance/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/guarded-advance/good.c create mode 100644 test/evaluation/rfc0029/guarded-advance/interior.c create mode 100644 test/evaluation/rfc0029/guarded-advance/library.c create mode 100644 test/evaluation/rfc0029/guarded-advance/manifest.json create mode 100644 test/evaluation/rfc0029/guarded-advance/short.c create mode 100644 test/evaluation/rfc0029/guarded-cursor-bounds/README.md create mode 100644 test/evaluation/rfc0029/guarded-cursor-bounds/changed.c create mode 100644 test/evaluation/rfc0029/guarded-cursor-bounds/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/guarded-cursor-bounds/good.c create mode 100644 test/evaluation/rfc0029/guarded-cursor-bounds/interior.c create mode 100644 test/evaluation/rfc0029/guarded-cursor-bounds/library.c create mode 100644 test/evaluation/rfc0029/guarded-cursor-bounds/manifest.json create mode 100644 test/evaluation/rfc0029/guarded-cursor-bounds/moved.c create mode 100644 test/evaluation/rfc0029/guarded-cursor-bounds/short.c create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/README.md create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/api.h create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/good.c create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/library.c create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/manifest.json create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/mixed.c create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/no-progress.c create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/once.c create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/short.c create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/uninitialized.c create mode 100644 test/evaluation/rfc0029/helper-cursor-pairs/wrong-step.c create mode 100644 test/evaluation/rfc0029/in-place-extension/README.md create mode 100644 test/evaluation/rfc0029/in-place-extension/duplicate-child.c create mode 100644 test/evaluation/rfc0029/in-place-extension/failed-cleanup.c create mode 100644 test/evaluation/rfc0029/in-place-extension/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/in-place-extension/lost-child.c create mode 100644 test/evaluation/rfc0029/in-place-extension/manifest.json create mode 100644 test/evaluation/rfc0029/in-place-extension/nondecreasing.c create mode 100644 test/evaluation/rfc0029/in-place-extension/recursive.c create mode 100644 test/evaluation/rfc0029/independent-cursors/README.md create mode 100644 test/evaluation/rfc0029/independent-cursors/api.h create mode 100644 test/evaluation/rfc0029/independent-cursors/closed.c create mode 100644 test/evaluation/rfc0029/independent-cursors/extra-step.c create mode 100644 test/evaluation/rfc0029/independent-cursors/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/independent-cursors/good.c create mode 100644 test/evaluation/rfc0029/independent-cursors/library.c create mode 100644 test/evaluation/rfc0029/independent-cursors/manifest.json create mode 100644 test/evaluation/rfc0029/independent-cursors/offset.c create mode 100644 test/evaluation/rfc0029/independent-cursors/reordered.c create mode 100644 test/evaluation/rfc0029/independent-cursors/short.c create mode 100644 test/evaluation/rfc0029/independent-cursors/uninitialized.c create mode 100644 test/evaluation/rfc0029/initialized-advance/README.md create mode 100644 test/evaluation/rfc0029/initialized-advance/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/initialized-advance/good.c create mode 100644 test/evaluation/rfc0029/initialized-advance/interior.c create mode 100644 test/evaluation/rfc0029/initialized-advance/library.c create mode 100644 test/evaluation/rfc0029/initialized-advance/manifest.json create mode 100644 test/evaluation/rfc0029/initialized-advance/over-advance.c create mode 100644 test/evaluation/rfc0029/initialized-advance/short.c create mode 100644 test/evaluation/rfc0029/initialized-advance/skipped.c create mode 100644 test/evaluation/rfc0029/initialized-spans/README.md create mode 100644 test/evaluation/rfc0029/initialized-spans/clobber.c create mode 100644 test/evaluation/rfc0029/initialized-spans/empty.c create mode 100644 test/evaluation/rfc0029/initialized-spans/freed.c create mode 100644 test/evaluation/rfc0029/initialized-spans/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/initialized-spans/good.c create mode 100644 test/evaluation/rfc0029/initialized-spans/library.c create mode 100644 test/evaluation/rfc0029/initialized-spans/manifest.json create mode 100644 test/evaluation/rfc0029/initialized-spans/outside.c create mode 100644 test/evaluation/rfc0029/initialized-spans/uninitialized.c create mode 100644 test/evaluation/rfc0029/initialized-spans/unrelated.c create mode 100644 test/evaluation/rfc0029/initialized-spans/write.c create mode 100644 test/evaluation/rfc0029/local-callee-copies/README.md create mode 100644 test/evaluation/rfc0029/local-callee-copies/copy.c create mode 100644 test/evaluation/rfc0029/local-callee-copies/freed-alias.c create mode 100644 test/evaluation/rfc0029/local-callee-copies/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/local-callee-copies/interior-release.c create mode 100644 test/evaluation/rfc0029/local-callee-copies/leak.c create mode 100644 test/evaluation/rfc0029/local-callee-copies/manifest.json create mode 100644 test/evaluation/rfc0029/local-callee-copies/numeric.c create mode 100644 test/evaluation/rfc0029/local-callee-copies/unsigned.c create mode 100644 test/evaluation/rfc0029/manifest.json create mode 100644 test/evaluation/rfc0029/mixed-allocation-ledger/double.c create mode 100644 test/evaluation/rfc0029/mixed-allocation-ledger/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/mixed-allocation-ledger/lost.c create mode 100644 test/evaluation/rfc0029/mixed-allocation-ledger/manifest.json create mode 100644 test/evaluation/rfc0029/mixed-allocation-ledger/resize.c create mode 100644 test/evaluation/rfc0029/mixed-allocation-ledger/return.c create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/api.h create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/client.c create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/direct.c create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/disowned.c create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/drop.c create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/freed.c create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/helper.c create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/lost.c create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/make.c create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/manifest.json create mode 100644 test/evaluation/rfc0029/mixed-helper-frames/step.c create mode 100644 test/evaluation/rfc0029/mutable-reader-construction/build.c create mode 100644 test/evaluation/rfc0029/mutable-reader-construction/cycle.c create mode 100644 test/evaluation/rfc0029/mutable-reader-construction/double.c create mode 100644 test/evaluation/rfc0029/mutable-reader-construction/escape.c create mode 100644 test/evaluation/rfc0029/mutable-reader-construction/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/mutable-reader-construction/leak.c create mode 100644 test/evaluation/rfc0029/mutable-reader-construction/manifest.json create mode 100644 test/evaluation/rfc0029/mutable-reader-construction/provenance.md create mode 100644 test/evaluation/rfc0029/mutable-reader-construction/short.c create mode 100644 test/evaluation/rfc0029/mutable-reader-construction/uninitialized.c create mode 100644 test/evaluation/rfc0029/mutual-cases/README.md create mode 100644 test/evaluation/rfc0029/mutual-cases/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/mutual-cases/live.c create mode 100644 test/evaluation/rfc0029/mutual-cases/manifest.json create mode 100644 test/evaluation/rfc0029/mutual-cases/null.c create mode 100644 test/evaluation/rfc0029/mutual-cases/unbounded.c create mode 100644 test/evaluation/rfc0029/mutual-cleanup.c create mode 100644 test/evaluation/rfc0029/mutual-construction/cycle.c create mode 100644 test/evaluation/rfc0029/mutual-construction/forwarding.c create mode 100644 test/evaluation/rfc0029/mutual-construction/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/mutual-construction/leak.c create mode 100644 test/evaluation/rfc0029/mutual-construction/manifest.json create mode 100644 test/evaluation/rfc0029/mutual-construction/provenance.md create mode 100644 test/evaluation/rfc0029/mutual-construction/short.c create mode 100644 test/evaluation/rfc0029/mutual-skipped-bad.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/numeric-container-frames/api.h create mode 100644 test/evaluation/rfc0029/numeric-container-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/numeric-container-frames/local-end-inspect.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/local-end.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/manifest.json create mode 100644 test/evaluation/rfc0029/numeric-container-frames/null-end-inspect.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/null-end.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/owned-end-inspect.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/owned-end.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/reader.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/record-end-inspect.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/record-end.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/released-inspect.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/released.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/uninitialized-inspect.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/uninitialized.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/unknown-inspect.c create mode 100644 test/evaluation/rfc0029/numeric-container-frames/unknown.c create mode 100644 test/evaluation/rfc0029/numeric-input/end.c create mode 100644 test/evaluation/rfc0029/numeric-input/forged.c create mode 100644 test/evaluation/rfc0029/numeric-input/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/numeric-input/manifest.json create mode 100644 test/evaluation/rfc0029/numeric-input/null-end.c create mode 100644 test/evaluation/rfc0029/numeric-input/past-end.c create mode 100644 test/evaluation/rfc0029/numeric-input/provenance.md create mode 100644 test/evaluation/rfc0029/numeric-input/readonly-slot.c create mode 100644 test/evaluation/rfc0029/numeric-input/uninitialized.c create mode 100644 test/evaluation/rfc0029/numeric-input/unterminated.c create mode 100644 test/evaluation/rfc0029/numeric-scan-locals/README.md create mode 100644 test/evaluation/rfc0029/numeric-scan-locals/assignment.c create mode 100644 test/evaluation/rfc0029/numeric-scan-locals/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/numeric-scan-locals/manifest.json create mode 100644 test/evaluation/rfc0029/numeric-scan-locals/reset.c create mode 100644 test/evaluation/rfc0029/numeric-scan-locals/source-write.c create mode 100644 test/evaluation/rfc0029/numeric-scans/README.md create mode 100644 test/evaluation/rfc0029/numeric-scans/default.c create mode 100644 test/evaluation/rfc0029/numeric-scans/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/numeric-scans/good.c create mode 100644 test/evaluation/rfc0029/numeric-scans/manifest.json create mode 100644 test/evaluation/rfc0029/numeric-scans/overwrite.c create mode 100644 test/evaluation/rfc0029/numeric-scans/permissive.c create mode 100644 test/evaluation/rfc0029/numeric-scans/reordered.c create mode 100644 test/evaluation/rfc0029/numeric-scans/short.c create mode 100644 test/evaluation/rfc0029/numeric-scans/uninitialized.c create mode 100644 test/evaluation/rfc0029/numeric-short-circuit/README.md create mode 100644 test/evaluation/rfc0029/numeric-short-circuit/SHA256SUMS create mode 100644 test/evaluation/rfc0029/numeric-short-circuit/bypass.c create mode 100644 test/evaluation/rfc0029/numeric-short-circuit/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/numeric-short-circuit/left.c create mode 100644 test/evaluation/rfc0029/numeric-short-circuit/manifest.json create mode 100644 test/evaluation/rfc0029/numeric-short-circuit/mutated.c create mode 100644 test/evaluation/rfc0029/numeric-short-circuit/permissive.c create mode 100644 test/evaluation/rfc0029/numeric-short-circuit/right.c create mode 100644 test/evaluation/rfc0029/numeric-text/README.md create mode 100644 test/evaluation/rfc0029/numeric-text/changed.c create mode 100644 test/evaluation/rfc0029/numeric-text/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/numeric-text/literal.c create mode 100644 test/evaluation/rfc0029/numeric-text/manifest.json create mode 100644 test/evaluation/rfc0029/numeric-text/nan.c create mode 100644 test/evaluation/rfc0029/numeric-text/overwrite.c create mode 100644 test/evaluation/rfc0029/numeric-text/stores.c create mode 100644 test/evaluation/rfc0029/numeric-text/uninitialized.c create mode 100644 test/evaluation/rfc0029/numeric-text/wide.c create mode 100644 test/evaluation/rfc0029/offsets/child.c create mode 100644 test/evaluation/rfc0029/offsets/forward.c create mode 100644 test/evaluation/rfc0029/offsets/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/offsets/manifest.json create mode 100644 test/evaluation/rfc0029/offsets/provenance.md create mode 100644 test/evaluation/rfc0029/offsets/zero.c create mode 100644 test/evaluation/rfc0029/output-construction/build.c create mode 100644 test/evaluation/rfc0029/output-construction/cycle.c create mode 100644 test/evaluation/rfc0029/output-construction/double.c create mode 100644 test/evaluation/rfc0029/output-construction/false-success.c create mode 100644 test/evaluation/rfc0029/output-construction/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/output-construction/leak.c create mode 100644 test/evaluation/rfc0029/output-construction/manifest.json create mode 100644 test/evaluation/rfc0029/output-construction/null-slot.c create mode 100644 test/evaluation/rfc0029/output-construction/provenance.md create mode 100644 test/evaluation/rfc0029/output-construction/short.c create mode 100644 test/evaluation/rfc0029/output-construction/uncleared-failure.c create mode 100644 test/evaluation/rfc0029/output-transport/api.h create mode 100644 test/evaluation/rfc0029/output-transport/client.c create mode 100644 test/evaluation/rfc0029/output-transport/double.c create mode 100644 test/evaluation/rfc0029/output-transport/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/output-transport/library.c create mode 100644 test/evaluation/rfc0029/output-transport/manifest.json create mode 100644 test/evaluation/rfc0029/output-transport/provenance.md create mode 100644 test/evaluation/rfc0029/output-transport/short.c create mode 100644 test/evaluation/rfc0029/paired-cursor-loops/README.md create mode 100644 test/evaluation/rfc0029/paired-cursor-loops/for.c create mode 100644 test/evaluation/rfc0029/paired-cursor-loops/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/paired-cursor-loops/good.c create mode 100644 test/evaluation/rfc0029/paired-cursor-loops/manifest.json create mode 100644 test/evaluation/rfc0029/paired-cursor-loops/over-end.c create mode 100644 test/evaluation/rfc0029/paired-cursor-loops/over-step.c create mode 100644 test/evaluation/rfc0029/paired-cursor-loops/unrelated.c create mode 100644 test/evaluation/rfc0029/paired-reader-counters/README.md create mode 100644 test/evaluation/rfc0029/paired-reader-counters/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/paired-reader-counters/good.c create mode 100644 test/evaluation/rfc0029/paired-reader-counters/library.c create mode 100644 test/evaluation/rfc0029/paired-reader-counters/manifest.json create mode 100644 test/evaluation/rfc0029/paired-reader-counters/nonzero.c create mode 100644 test/evaluation/rfc0029/paired-reader-counters/reader.h create mode 100644 test/evaluation/rfc0029/paired-reader-counters/skipped-index.c create mode 100644 test/evaluation/rfc0029/paired-reader-counters/too-many.c create mode 100644 test/evaluation/rfc0029/payload-early-exits/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/payload-early-exits/api.h create mode 100644 test/evaluation/rfc0029/payload-early-exits/client.c create mode 100644 test/evaluation/rfc0029/payload-early-exits/drop.c create mode 100644 test/evaluation/rfc0029/payload-early-exits/duplicate.c create mode 100644 test/evaluation/rfc0029/payload-early-exits/early.c create mode 100644 test/evaluation/rfc0029/payload-early-exits/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/payload-early-exits/goto.c create mode 100644 test/evaluation/rfc0029/payload-early-exits/leak.c create mode 100644 test/evaluation/rfc0029/payload-early-exits/manifest.json create mode 100644 test/evaluation/rfc0029/payload-early-exits/released-head.c create mode 100644 test/evaluation/rfc0029/payload-early-exits/released-payload.c create mode 100644 test/evaluation/rfc0029/payload-publication/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/payload-publication/api.h create mode 100644 test/evaluation/rfc0029/payload-publication/client.c create mode 100644 test/evaluation/rfc0029/payload-publication/drop.c create mode 100644 test/evaluation/rfc0029/payload-publication/duplicate.c create mode 100644 test/evaluation/rfc0029/payload-publication/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/payload-publication/good.c create mode 100644 test/evaluation/rfc0029/payload-publication/helper.c create mode 100644 test/evaluation/rfc0029/payload-publication/interior.c create mode 100644 test/evaluation/rfc0029/payload-publication/lost.c create mode 100644 test/evaluation/rfc0029/payload-publication/manifest.json create mode 100644 test/evaluation/rfc0029/payload-publication/released.c create mode 100644 test/evaluation/rfc0029/payload-reader-returns/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/payload-reader-returns/api.h create mode 100644 test/evaluation/rfc0029/payload-reader-returns/client.c create mode 100644 test/evaluation/rfc0029/payload-reader-returns/drop.c create mode 100644 test/evaluation/rfc0029/payload-reader-returns/duplicate.c create mode 100644 test/evaluation/rfc0029/payload-reader-returns/early.c create mode 100644 test/evaluation/rfc0029/payload-reader-returns/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/payload-reader-returns/good.c create mode 100644 test/evaluation/rfc0029/payload-reader-returns/leak.c create mode 100644 test/evaluation/rfc0029/payload-reader-returns/manifest.json create mode 100644 test/evaluation/rfc0029/payload-reader-returns/released-head.c create mode 100644 test/evaluation/rfc0029/payload-reader-returns/released-payload.c create mode 100644 test/evaluation/rfc0029/payload-reader-returns/wrap.c create mode 100644 test/evaluation/rfc0029/payload-relocation/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/payload-relocation/api.h create mode 100644 test/evaluation/rfc0029/payload-relocation/drop.c create mode 100644 test/evaluation/rfc0029/payload-relocation/duplicate.c create mode 100644 test/evaluation/rfc0029/payload-relocation/fill.c create mode 100644 test/evaluation/rfc0029/payload-relocation/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/payload-relocation/helper.c create mode 100644 test/evaluation/rfc0029/payload-relocation/interior.c create mode 100644 test/evaluation/rfc0029/payload-relocation/local.c create mode 100644 test/evaluation/rfc0029/payload-relocation/manifest.json create mode 100644 test/evaluation/rfc0029/payload-relocation/overwritten.c create mode 100644 test/evaluation/rfc0029/payload-relocation/released.c create mode 100644 test/evaluation/rfc0029/payload-write-frames/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/payload-write-frames/aliased-client.c create mode 100644 test/evaluation/rfc0029/payload-write-frames/api.h create mode 100644 test/evaluation/rfc0029/payload-write-frames/client.c create mode 100644 test/evaluation/rfc0029/payload-write-frames/drop.c create mode 100644 test/evaluation/rfc0029/payload-write-frames/duplicate.c create mode 100644 test/evaluation/rfc0029/payload-write-frames/early.c create mode 100644 test/evaluation/rfc0029/payload-write-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/payload-write-frames/good.c create mode 100644 test/evaluation/rfc0029/payload-write-frames/helper.c create mode 100644 test/evaluation/rfc0029/payload-write-frames/manifest.json create mode 100644 test/evaluation/rfc0029/pointer-count-readers/README.md create mode 100644 test/evaluation/rfc0029/pointer-count-readers/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/pointer-count-readers/good.c create mode 100644 test/evaluation/rfc0029/pointer-count-readers/manifest.json create mode 100644 test/evaluation/rfc0029/pointer-count-readers/over-step.c create mode 100644 test/evaluation/rfc0029/pointer-count-readers/reordered.c create mode 100644 test/evaluation/rfc0029/pointer-count-readers/short.c create mode 100644 test/evaluation/rfc0029/pointer-count-readers/uninitialized.c create mode 100644 test/evaluation/rfc0029/pointer-difference-sizes/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/pointer-difference-sizes/changed-end.c create mode 100644 test/evaluation/rfc0029/pointer-difference-sizes/cross-object.c create mode 100644 test/evaluation/rfc0029/pointer-difference-sizes/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/pointer-difference-sizes/good.c create mode 100644 test/evaluation/rfc0029/pointer-difference-sizes/manifest.json create mode 100644 test/evaluation/rfc0029/pointer-difference-sizes/offset.c create mode 100644 test/evaluation/rfc0029/pointer-difference-sizes/overrun.c create mode 100644 test/evaluation/rfc0029/pointer-difference-sizes/undersized.c create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/README.md create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/escape.c create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/forged.c create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/good.c create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/library.c create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/manifest.json create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/past-end.c create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/reader.h create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/uninitialized.c create mode 100644 test/evaluation/rfc0029/pointer-reader-offsets/unrelated.c create mode 100644 test/evaluation/rfc0029/reader-construction/build.c create mode 100644 test/evaluation/rfc0029/reader-construction/cycle.c create mode 100644 test/evaluation/rfc0029/reader-construction/double.c create mode 100644 test/evaluation/rfc0029/reader-construction/escape.c create mode 100644 test/evaluation/rfc0029/reader-construction/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/reader-construction/leak.c create mode 100644 test/evaluation/rfc0029/reader-construction/manifest.json create mode 100644 test/evaluation/rfc0029/reader-construction/provenance.md create mode 100644 test/evaluation/rfc0029/reader-construction/short.c create mode 100644 test/evaluation/rfc0029/reader-construction/uninitialized.c create mode 100644 test/evaluation/rfc0029/reader-good.c create mode 100644 test/evaluation/rfc0029/reader-index-loops/README.md create mode 100644 test/evaluation/rfc0029/reader-index-loops/body-cursor.c create mode 100644 test/evaluation/rfc0029/reader-index-loops/body-index.c create mode 100644 test/evaluation/rfc0029/reader-index-loops/forged.c create mode 100644 test/evaluation/rfc0029/reader-index-loops/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/reader-index-loops/good.c create mode 100644 test/evaluation/rfc0029/reader-index-loops/library.c create mode 100644 test/evaluation/rfc0029/reader-index-loops/manifest.json create mode 100644 test/evaluation/rfc0029/reader-index-loops/off-by-one.c create mode 100644 test/evaluation/rfc0029/reader-index-loops/reader.h create mode 100644 test/evaluation/rfc0029/reader-index-loops/uninitialized.c create mode 100644 test/evaluation/rfc0029/reader-short-bad.c create mode 100644 test/evaluation/rfc0029/reader.h create mode 100644 test/evaluation/rfc0029/readers/audit.md create mode 100644 test/evaluation/rfc0029/readers/client.c create mode 100644 test/evaluation/rfc0029/readers/cursor-escape.c create mode 100644 test/evaluation/rfc0029/readers/cursor.c create mode 100644 test/evaluation/rfc0029/readers/cursor.h create mode 100644 test/evaluation/rfc0029/readers/escaped-access.c create mode 100644 test/evaluation/rfc0029/readers/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/readers/manifest.json create mode 100644 test/evaluation/rfc0029/readers/provenance.md create mode 100644 test/evaluation/rfc0029/readers/reviewed-manifest.json create mode 100644 test/evaluation/rfc0029/readers/reviewed-sha256.json create mode 100644 test/evaluation/rfc0029/readers/short.c create mode 100644 test/evaluation/rfc0029/readers/uninitialized.c create mode 100644 test/evaluation/rfc0029/reallocation-ledger-failures/failure-leak.c create mode 100644 test/evaluation/rfc0029/reallocation-ledger-failures/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/reallocation-ledger-failures/lost-result.c create mode 100644 test/evaluation/rfc0029/reallocation-ledger-failures/manifest.json create mode 100644 test/evaluation/rfc0029/reallocation-ledger-failures/null-input.c create mode 100644 test/evaluation/rfc0029/reallocation-ledger-failures/overwrite-failure.c create mode 100644 test/evaluation/rfc0029/reallocation-ledger-failures/stale-release.c create mode 100644 test/evaluation/rfc0029/reallocation-ledger-failures/zero-size.c create mode 100644 test/evaluation/rfc0029/reassigned-release.c create mode 100644 test/evaluation/rfc0029/record-arrays/README.md create mode 100644 test/evaluation/rfc0029/record-arrays/alias-bad.c create mode 100644 test/evaluation/rfc0029/record-arrays/bounds-bad.c create mode 100644 test/evaluation/rfc0029/record-arrays/forward.c create mode 100644 test/evaluation/rfc0029/record-arrays/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/record-arrays/manifest.json create mode 100644 test/evaluation/rfc0029/record-arrays/spellings.c create mode 100644 test/evaluation/rfc0029/record-arrays/uninit-bad.c create mode 100644 test/evaluation/rfc0029/record-arrays/writer.c create mode 100644 test/evaluation/rfc0029/recursive-cases/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/recursive-cases/generic.c create mode 100644 test/evaluation/rfc0029/recursive-cases/live.c create mode 100644 test/evaluation/rfc0029/recursive-cases/manifest.json create mode 100644 test/evaluation/rfc0029/recursive-cases/null.c create mode 100644 test/evaluation/rfc0029/recursive-cases/provenance.md create mode 100644 test/evaluation/rfc0029/recursive-cases/unknown.c create mode 100644 test/evaluation/rfc0029/recursive-cases/zero.c create mode 100644 test/evaluation/rfc0029/recursive-contexts/README.md create mode 100644 test/evaluation/rfc0029/recursive-contexts/bad.c create mode 100644 test/evaluation/rfc0029/recursive-contexts/client.c create mode 100644 test/evaluation/rfc0029/recursive-contexts/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/recursive-contexts/generic.c create mode 100644 test/evaluation/rfc0029/recursive-contexts/manifest.json create mode 100644 test/evaluation/rfc0029/recursive-output-cases/README.md create mode 100644 test/evaluation/rfc0029/recursive-output-cases/client.c create mode 100644 test/evaluation/rfc0029/recursive-output-cases/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/recursive-output-cases/manifest.json create mode 100644 test/evaluation/rfc0029/recursive-output-cases/null.c create mode 100644 test/evaluation/rfc0029/recursive-state-cases/bad.c create mode 100644 test/evaluation/rfc0029/recursive-state-cases/client.c create mode 100644 test/evaluation/rfc0029/recursive-state-cases/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/recursive-state-cases/generic.c create mode 100644 test/evaluation/rfc0029/recursive-state-cases/manifest.json create mode 100644 test/evaluation/rfc0029/recursive-transport/api.h create mode 100644 test/evaluation/rfc0029/recursive-transport/client.c create mode 100644 test/evaluation/rfc0029/recursive-transport/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/recursive-transport/library.c create mode 100644 test/evaluation/rfc0029/recursive-transport/manifest.json create mode 100644 test/evaluation/rfc0029/recursive-transport/provenance.md create mode 100644 test/evaluation/rfc0029/recursive-transport/short.c create mode 100644 test/evaluation/rfc0029/recursive-transport/uninitialized.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/alias.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/capacity.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/emit.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/false-prefix.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/false-success-read.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/manifest.json create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/mutual.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/off-by-one.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/partial.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/prefix.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/provenance.md create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/short.c create mode 100644 test/evaluation/rfc0029/recursive-writer-reviewed/uninitialized.c create mode 100644 test/evaluation/rfc0029/recursive-writer/audit.md create mode 100644 test/evaluation/rfc0029/recursive-writer/capacity.c create mode 100644 test/evaluation/rfc0029/recursive-writer/cycle.c create mode 100644 test/evaluation/rfc0029/recursive-writer/emit.c create mode 100644 test/evaluation/rfc0029/recursive-writer/false-success.c create mode 100644 test/evaluation/rfc0029/recursive-writer/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/recursive-writer/manifest.json create mode 100644 test/evaluation/rfc0029/recursive-writer/off-by-one.c create mode 100644 test/evaluation/rfc0029/recursive-writer/provenance.md create mode 100644 test/evaluation/rfc0029/recursive-writer/short.c create mode 100644 test/evaluation/rfc0029/recursive-writer/uninitialized.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/api.h create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/direct-build.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/direct-client.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/drop.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/five-build.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/five-client.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/forwarded-build.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/forwarded-client.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/interior-build.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/interior-client.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/lost-build.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/lost-client.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/make.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/manifest.json create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/mark.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/released-build.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/released-client.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/reused-build.c create mode 100644 test/evaluation/rfc0029/repeated-fresh-outputs/reused-client.c create mode 100644 test/evaluation/rfc0029/reverse-byte-writes/README.md create mode 100644 test/evaluation/rfc0029/reverse-byte-writes/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/reverse-byte-writes/good.c create mode 100644 test/evaluation/rfc0029/reverse-byte-writes/library.c create mode 100644 test/evaluation/rfc0029/reverse-byte-writes/manifest.json create mode 100644 test/evaluation/rfc0029/reverse-byte-writes/past-end.c create mode 100644 test/evaluation/rfc0029/reverse-byte-writes/readonly.c create mode 100644 test/evaluation/rfc0029/reverse-byte-writes/short.c create mode 100644 test/evaluation/rfc0029/reverse-initialization/README.md create mode 100644 test/evaluation/rfc0029/reverse-initialization/early.c create mode 100644 test/evaluation/rfc0029/reverse-initialization/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/reverse-initialization/good.c create mode 100644 test/evaluation/rfc0029/reverse-initialization/library.c create mode 100644 test/evaluation/rfc0029/reverse-initialization/local.c create mode 100644 test/evaluation/rfc0029/reverse-initialization/manifest.json create mode 100644 test/evaluation/rfc0029/reverse-initialization/short.c create mode 100644 test/evaluation/rfc0029/reverse-initialization/skipped.c create mode 100644 test/evaluation/rfc0029/reverse-initialization/stride.c create mode 100644 test/evaluation/rfc0029/reverse-initialization/zero.c create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/advanced-manifest.json create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/advanced-read.c create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/advanced-sha256.json create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/audit.md create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/audited-manifest.json create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/audited-sha256.json create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/extended-manifest.json create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/extended-sha256.json create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/heap-unreachable-write.c create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/heap-wrong-base-byte.c create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/manifest.json create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/unreachable-write.c create mode 100644 test/evaluation/rfc0029/scalar-write-offsets/wrong-base-byte.c create mode 100644 test/evaluation/rfc0029/serializer/client.c create mode 100644 test/evaluation/rfc0029/serializer/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/serializer/hex.c create mode 100644 test/evaluation/rfc0029/serializer/hex.h create mode 100644 test/evaluation/rfc0029/serializer/manifest.json create mode 100644 test/evaluation/rfc0029/serializer/provenance.md create mode 100644 test/evaluation/rfc0029/serializer/short.c create mode 100644 test/evaluation/rfc0029/shifted-pointee-facts/README.md create mode 100644 test/evaluation/rfc0029/shifted-pointee-facts/back.c create mode 100644 test/evaluation/rfc0029/shifted-pointee-facts/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/shifted-pointee-facts/manifest.json create mode 100644 test/evaluation/rfc0029/shifted-pointee-facts/same.c create mode 100644 test/evaluation/rfc0029/shifted-pointee-facts/shift.c create mode 100644 test/evaluation/rfc0029/shifted-pointee-facts/unknown.c create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/api.h create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/client.c create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/direct.c create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/disowned.c create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/drop.c create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/forwarded.c create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/lost.c create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/make.c create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/manifest.json create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/released.c create mode 100644 test/evaluation/rfc0029/singleton-link-ownership/step.c create mode 100644 test/evaluation/rfc0029/span-advances/README.md create mode 100644 test/evaluation/rfc0029/span-advances/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/span-advances/good.c create mode 100644 test/evaluation/rfc0029/span-advances/library.c create mode 100644 test/evaluation/rfc0029/span-advances/manifest.json create mode 100644 test/evaluation/rfc0029/span-advances/once.c create mode 100644 test/evaluation/rfc0029/span-advances/over-count.c create mode 100644 test/evaluation/rfc0029/span-advances/over-step.c create mode 100644 test/evaluation/rfc0029/span-advances/uninitialized.c create mode 100644 test/evaluation/rfc0029/span-count-joins/README.md create mode 100644 test/evaluation/rfc0029/span-count-joins/changed-count.c create mode 100644 test/evaluation/rfc0029/span-count-joins/false-count.c create mode 100644 test/evaluation/rfc0029/span-count-joins/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/span-count-joins/good.c create mode 100644 test/evaluation/rfc0029/span-count-joins/library.c create mode 100644 test/evaluation/rfc0029/span-count-joins/manifest.json create mode 100644 test/evaluation/rfc0029/span-count-joins/short.c create mode 100644 test/evaluation/rfc0029/span-counts/README.md create mode 100644 test/evaluation/rfc0029/span-counts/false-count.c create mode 100644 test/evaluation/rfc0029/span-counts/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/span-counts/good.c create mode 100644 test/evaluation/rfc0029/span-counts/library.c create mode 100644 test/evaluation/rfc0029/span-counts/manifest.json create mode 100644 test/evaluation/rfc0029/span-counts/short.c create mode 100644 test/evaluation/rfc0029/span-counts/uninitialized.c create mode 100644 test/evaluation/rfc0029/span-outputs/README.md create mode 100644 test/evaluation/rfc0029/span-outputs/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/span-outputs/good.c create mode 100644 test/evaluation/rfc0029/span-outputs/library.c create mode 100644 test/evaluation/rfc0029/span-outputs/manifest.json create mode 100644 test/evaluation/rfc0029/span-outputs/overflow.c create mode 100644 test/evaluation/rfc0029/span-outputs/readonly.c create mode 100644 test/evaluation/rfc0029/state-capacity-bad.c create mode 100644 test/evaluation/rfc0029/state-good.c create mode 100644 test/evaluation/rfc0029/state-stale-bad.c create mode 100644 test/evaluation/rfc0029/state-tail-bad.c create mode 100644 test/evaluation/rfc0029/state.h create mode 100644 test/evaluation/rfc0029/string-length-copies/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/string-length-copies/api.h create mode 100644 test/evaluation/rfc0029/string-length-copies/callback.c create mode 100644 test/evaluation/rfc0029/string-length-copies/client.c create mode 100644 test/evaluation/rfc0029/string-length-copies/direct.c create mode 100644 test/evaluation/rfc0029/string-length-copies/forward.c create mode 100644 test/evaluation/rfc0029/string-length-copies/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/string-length-copies/manifest.json create mode 100644 test/evaluation/rfc0029/string-length-copies/missing.c create mode 100644 test/evaluation/rfc0029/string-length-copies/overread.c create mode 100644 test/evaluation/rfc0029/string-length-copies/stale.c create mode 100644 test/evaluation/rfc0029/string-length-copies/tail.c create mode 100644 test/evaluation/rfc0029/temporary-release-frames/PROVENANCE.md create mode 100644 test/evaluation/rfc0029/temporary-release-frames/api.h create mode 100644 test/evaluation/rfc0029/temporary-release-frames/attached.c create mode 100644 test/evaluation/rfc0029/temporary-release-frames/client.c create mode 100644 test/evaluation/rfc0029/temporary-release-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/temporary-release-frames/guarded.c create mode 100644 test/evaluation/rfc0029/temporary-release-frames/helper.c create mode 100644 test/evaluation/rfc0029/temporary-release-frames/interior.c create mode 100644 test/evaluation/rfc0029/temporary-release-frames/lost.c create mode 100644 test/evaluation/rfc0029/temporary-release-frames/manifest.json create mode 100644 test/evaluation/rfc0029/temporary-release-frames/nullable.c create mode 100644 test/evaluation/rfc0029/temporary-release-frames/reader.c create mode 100644 test/evaluation/rfc0029/temporary-release-frames/twice.c create mode 100644 test/evaluation/rfc0029/transport/api.h create mode 100644 test/evaluation/rfc0029/transport/bad.c create mode 100644 test/evaluation/rfc0029/transport/client.c create mode 100644 test/evaluation/rfc0029/transport/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/transport/library.c create mode 100644 test/evaluation/rfc0029/transport/manifest.json create mode 100644 test/evaluation/rfc0029/transport/provenance.md create mode 100644 test/evaluation/rfc0029/traversal/cycle.c create mode 100644 test/evaluation/rfc0029/traversal/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/traversal/interior.c create mode 100644 test/evaluation/rfc0029/traversal/manifest.json create mode 100644 test/evaluation/rfc0029/traversal/mutation.c create mode 100644 test/evaluation/rfc0029/traversal/mutual.c create mode 100644 test/evaluation/rfc0029/traversal/nondecreasing.c create mode 100644 test/evaluation/rfc0029/traversal/provenance.md create mode 100644 test/evaluation/rfc0029/traversal/review.md create mode 100644 test/evaluation/rfc0029/traversal/reviewed-manifest.json create mode 100644 test/evaluation/rfc0029/traversal/reviewed-sha256.json create mode 100644 test/evaluation/rfc0029/upstream-construction/README.md create mode 100644 test/evaluation/rfc0029/upstream-construction/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/upstream-construction/manifest.json create mode 100644 test/evaluation/rfc0029/upstream-construction/nested-serialize.c create mode 100644 test/evaluation/rfc0029/upstream-construction/output-leak.c create mode 100644 test/evaluation/rfc0029/upstream-construction/output-twice.c create mode 100644 test/evaluation/rfc0029/upstream-construction/upstream-identity.json create mode 100644 test/evaluation/rfc0029/upstream-extended/failed-parse.c create mode 100644 test/evaluation/rfc0029/upstream-extended/failed-print.c create mode 100644 test/evaluation/rfc0029/upstream-extended/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/upstream-extended/malformed-delete.c create mode 100644 test/evaluation/rfc0029/upstream-extended/manifest.json create mode 100644 test/evaluation/rfc0029/upstream-extended/nested-print.c create mode 100644 test/evaluation/rfc0029/upstream-extended/upstream-identity.json create mode 100644 test/evaluation/rfc0029/upstream-lifetime-audit/README.md create mode 100644 test/evaluation/rfc0029/upstream-lifetime-audit/failed-parse-stack.c create mode 100644 test/evaluation/rfc0029/upstream-lifetime-audit/failed-parse-static.c create mode 100644 test/evaluation/rfc0029/upstream-lifetime-audit/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/upstream-lifetime-audit/manifest.json create mode 100644 test/evaluation/rfc0029/upstream-lifetime-audit/oracle.c create mode 100644 test/evaluation/rfc0029/upstream-lifetime-audit/upstream-identity.json create mode 100644 test/evaluation/rfc0029/upstream-oracle/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/upstream-oracle/oracle.c create mode 100644 test/evaluation/rfc0029/upstream-oracle/upstream-identity.json create mode 100644 test/evaluation/rfc0029/upstream-static-inputs/README.md create mode 100644 test/evaluation/rfc0029/upstream-static-inputs/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/upstream-static-inputs/malformed-delete.c create mode 100644 test/evaluation/rfc0029/upstream-static-inputs/manifest.json create mode 100644 test/evaluation/rfc0029/upstream-static-inputs/parse-delete.c create mode 100644 test/evaluation/rfc0029/upstream-static-inputs/upstream-identity.json create mode 100644 test/evaluation/rfc0029/upstream/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/upstream/manifest.json create mode 100644 test/evaluation/rfc0029/upstream/parse-delete.c create mode 100644 test/evaluation/rfc0029/upstream/parse-double-bad.c create mode 100644 test/evaluation/rfc0029/upstream/print-delete.c create mode 100644 test/evaluation/rfc0029/upstream/upstream-identity.json create mode 100644 test/evaluation/rfc0029/writer-forwarding/audit.md create mode 100644 test/evaluation/rfc0029/writer-forwarding/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/writer-forwarding/manifest.json create mode 100644 test/evaluation/rfc0029/writer-forwarding/reviewed-manifest.json create mode 100644 test/evaluation/rfc0029/writer-forwarding/reviewed-sha256.json create mode 100644 test/evaluation/rfc0029/writer-forwarding/reviewed-skip.c create mode 100644 test/evaluation/rfc0029/writer-forwarding/reviewed-write.c create mode 100644 test/evaluation/rfc0029/writer-forwarding/reviewed-wrong-byte.c create mode 100644 test/evaluation/rfc0029/writer-forwarding/skip.c create mode 100644 test/evaluation/rfc0029/writer-forwarding/write.c create mode 100644 test/evaluation/rfc0029/writer-forwarding/wrong-byte.c create mode 100644 test/evaluation/rfc0029/writer-position-bounds/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/writer-position-bounds/manifest.json create mode 100644 test/evaluation/rfc0029/writer-position-bounds/skip.c create mode 100644 test/evaluation/rfc0029/writer-position-bounds/write.c create mode 100644 test/evaluation/rfc0029/writer-position-bounds/wrong-byte.c create mode 100644 test/evaluation/rfc0029/writer-return-aliases/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/writer-return-aliases/manifest.json create mode 100644 test/evaluation/rfc0029/writer-return-aliases/skip.c create mode 100644 test/evaluation/rfc0029/writer-return-aliases/write.c create mode 100644 test/evaluation/rfc0029/writer-return-aliases/wrong-byte.c create mode 100644 test/evaluation/rfc0029/writer-return-expressions/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/writer-return-expressions/manifest.json create mode 100644 test/evaluation/rfc0029/writer-return-expressions/skip.c create mode 100644 test/evaluation/rfc0029/writer-return-expressions/write.c create mode 100644 test/evaluation/rfc0029/writer-return-expressions/wrong-byte.c create mode 100644 test/evaluation/rfc0029/writer-transport/api.h create mode 100644 test/evaluation/rfc0029/writer-transport/client.c create mode 100644 test/evaluation/rfc0029/writer-transport/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/writer-transport/library.c create mode 100644 test/evaluation/rfc0029/writer-transport/manifest.json create mode 100644 test/evaluation/rfc0029/writer-transport/provenance.md create mode 100644 test/evaluation/rfc0029/writer-transport/short.c create mode 100644 test/evaluation/rfc0029/writer-transport/uninitialized.c create mode 100644 test/evaluation/rfc0029/zero-counters/README.md create mode 100644 test/evaluation/rfc0029/zero-counters/array.c create mode 100644 test/evaluation/rfc0029/zero-counters/audit.md create mode 100644 test/evaluation/rfc0029/zero-counters/audited-manifest.json create mode 100644 test/evaluation/rfc0029/zero-counters/audited-sha256.json create mode 100644 test/evaluation/rfc0029/zero-counters/cases.c create mode 100644 test/evaluation/rfc0029/zero-counters/cells.c create mode 100644 test/evaluation/rfc0029/zero-counters/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/zero-counters/helper.c create mode 100644 test/evaluation/rfc0029/zero-counters/manifest.json create mode 100644 test/evaluation/rfc0029/zero-counters/partial.c create mode 100644 test/evaluation/rfc0029/zero-counters/plain.c create mode 100644 test/evaluation/rfc0029/zero-counters/replaced.c create mode 100644 test/evaluation/rfc0029/zero-frames/README.md create mode 100644 test/evaluation/rfc0029/zero-frames/alias.c create mode 100644 test/evaluation/rfc0029/zero-frames/frozen-sha256.json create mode 100644 test/evaluation/rfc0029/zero-frames/manifest.json create mode 100644 test/evaluation/rfc0029/zero-frames/overwrite.c create mode 100644 test/evaluation/rfc0029/zero-frames/writer.c create mode 100644 unittests/Analysis/RecursiveContractsTest.cpp create mode 100644 unittests/Core/InductionTest.cpp diff --git a/README.md b/README.md index d85ceb0c..b478db44 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,14 @@ for the supported boundary and required evidence, and the [validation record](docs/validation-rfc0028.md) for closed-client proofs, remaining limits and measured cost. +[RFC 0029](docs/rfcs/0029-compositional-recursive-workflows.md) adds explicit +allocator/releaser callback requirements, group validation for supported recursive +cleanup, traversal and fresh construction, and buffer role discovery through +extra state fields and separate-source interfaces. Closed workflow checks cover +partial-tree failure cleanup, a streaming serializer, compiler objects and +validated cache reuse. The broader recursive parser/serializer milestone is +still in progress; see the [validation record](docs/validation-rfc0029.md). + Checked helpers can be rechecked under established input cases, including read-only helpers and forwarded callbacks ([RFC 0025](docs/rfcs/0025-case-sensitive-checked-contracts.md)). Named unions @@ -150,7 +158,7 @@ remain coverage gaps; general nonlinear and loop reasoning are outside the model. Early-exit and other unsupported loops do not produce inferred must-requirements on callers. Existing annotations remain trusted contracts. There is no runtime instrumentation or whole-program verification certificate. -Core summary format is **23**; sidecar format **24** requires rebuilding objects carrying older +Core summary format is **25**; sidecar format **26** requires rebuilding objects carrying older sidecars. The [validation report](docs/validation-rfc0017.md) records **900/900 tests passing**, including under ASan/UBSan, **44/44 original bugs detected and 32/32 clean cases**, plus twelve separate bug/clean regression diff --git a/docs/architecture.md b/docs/architecture.md index 3d0408b9..a62e654c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -51,7 +51,7 @@ dispatch. `DataflowRuntime.cpp` checks memory and stream preconditions, They use the existing checked call, callback, output and summary machinery. Source definitions retain priority over library spellings. Ordinary summaries alone never authorize a checked runtime contract. Portable records use checked -encoding 9, summary format 23 and sidecar format 24 (RFC 0028). +encoding 12, summary format 26 and sidecar format 27 (RFC 0029). | Header | Purpose | | ------------------ | ---------------------------------------------------------------------------------------------- | @@ -651,7 +651,7 @@ objects for compiler-sidecar validation before replay. Compilation may defer an unavailable external contract; the link pass must resolve it. Checked failure reaches Clang independently of the diagnostic filtering policy. -Summary format 23 and sidecar format 24 carry guarded/outcome-qualified contracts, +Summary format 26 and sidecar format 27 carry guarded/outcome-qualified contracts, numeric outputs and RFC 0021 traversal records. Checked record encoding uses version 9; report JSON uses expanded version 2 or compact version 3. Strict parsing and global remapping reject missing premises. Explanation depth @@ -797,7 +797,7 @@ They describe a subset plus freshly added nodes; they do not promise full input consumption. Fresh outputs require actual allocation evidence. Tail outputs are imported across read-only calls. A terminal output can establish a detached node's null successor. Descriptors, source premises and capability combinations -are validated during decoding. Summary format 23 and sidecar format 24 prevent +are validated during decoding. Summary format 26 and sidecar format 27 prevent older metadata from silently discarding these records. Limits are 32 fields, 64 explicit nodes, 64 active facts and 16 KiB per encoded @@ -835,8 +835,8 @@ returning path must establish complete input consumption before an inferred contract is published. Active recursion alone supplies no output. The ordinary may-effect fixed point continues independently. Mutual recursion remains conservative. Complete preservation, consumption, partition and combination -outputs use checked encoding 9; strict decoding requires their source predicates -and separation premises. Summary format 23 and sidecar format 24 carry these +outputs use checked encoding 12; strict decoding requires their source predicates +and separation premises. Summary format 26 and sidecar format 27 carry these records across program databases, object metadata and validated checkpoints. ### Input cases and overlapping member storage (RFC 0025) @@ -886,13 +886,15 @@ reasoning run only in functions with registered buffer candidates. Checked encoding 9 carries validated `buffer`, `buffer-preserved` and `buffer-appended` requirements and outputs to the existing interface codec. -Summary format 23 and sidecar format 24 reject older encodings. Ordinary +Summary format 26 and sidecar format 27 reject older encodings. Ordinary warnings remain independent from checked completeness. ## Opaque interfaces and private state (RFC 0028) Core's `Interface.h`/`Interface.cpp` define a bounded graph of storage types and -its canonical `it1` codec. Edges represent pointer referents, function arguments, +its canonical `it2` codec. Anonymous record typedef identities remain distinct +from tag names and are reproduced only in internal analysis adapters. Edges +represent pointer referents, function arguments, fixed-array elements and record fields. Validation rejects invalid references, overlapping fields, duplicate names, by-value cycles, malformed numbers and exhausted bounds before Analysis sees a description. Conflicting descriptions @@ -957,3 +959,59 @@ populations and independent regressions. The optional `upstream` and `upstream-objects` populations require the pinned cJSON checkout and verify its commit and file digests. The harness rejects syntax failures, missing reports, crashes and unrelated negative outcomes; it retains full compressed reports. + + +## Compositional workflows (RFC 0029) + +`DataflowBufferDiscovery.cpp` nominates state roles independently of field +count. At imported calls, `registerBuffer` validates a transported role +candidate against the target layout and clears all capability flags before +attempting a fold. Nomination supplies no memory permission. + +`RecursiveContracts.cpp` validates eligible cleanup, traversal and construction +SCCs in a private proof +environment. The ordinary summary store receives results only after every +member and Core's `validInductionProgress` check succeed. The Core check +removes strict edges and tests the remaining graph for cycles; Analysis must +prove that those edges decrease the same finite ownership measure. A failed +candidate cannot publish another member's proposed outputs. Existing bounded +may-effect inference remains separate from the inductive must-proofs. + +Direct cleanup uses the same progress graph, including its specialized +contexts. `DataflowRecursiveConstruction.cpp` proposes a nullable fresh forest +and an explicit initialized input interval for two-parameter byte/count +constructors. Recursive edges use immutable entry counts and exact input +identity. Ordinary call transfer composes completed helpers; final validation +checks that every required premise follows from the candidate, every promised +forest is established, and allocation conservation holds on all exits. +Output-slot constructors additionally verify actual null failure states and +positive fresh-forest outputs. An immediately tested completed helper can +transfer one conditional forest through a slot. When that slot is a parent +node's child, the unchanged parent frame and the new child feed the ordinary +folding and allocation accounting rules. +The callback and memory specialization stores reject active private proof +members so no nested context can publish an unverified hypothesis. + +`DataflowCallbackContracts.cpp` introduces input-only behavioral callback +requirements. Generic helpers use a conditional interface under that explicit +premise. Callers check every actual target and propagate its trusted boundaries. +A complete generic behavioral interface avoids unnecessary concrete callback +specialization; unsupported callback protocols retain the existing mechanisms. +The callback names participate in ordinary checked encoding, path remapping, +strict sidecar validation and executable-bound checkpoint invalidation. + +Core's `InitializedRange::outsideWrite` computes exact constant-byte frames. +Analysis preserves them only within the same represented storage or across +proved concrete object separation. It never uses different pointer spellings +alone as evidence that writes cannot overlap. + +Affine interface projection rejects numeric places written on an incoming path, +matching typed integer-expression projection. A changing cursor can instead +project a proved envelope over an unchanged endpoint. This prevents a loop's +current cursor from being mistaken for its entry value at a caller. + +`FloatingCastSupport.cpp` checks finite conversions using Clang's target types +and LLVM's floating/integer representations. It accepts constant operands or +unchanged scalar inputs under lexically dominating true bounds, after ruling +out address exposure, mutation and bypassing jumps. It publishes no numerical +return relationship. Unknown bounds remain ordinary incomplete obligations. diff --git a/docs/checked-code.md b/docs/checked-code.md index b0e843ac..3e823b41 100644 --- a/docs/checked-code.md +++ b/docs/checked-code.md @@ -405,7 +405,7 @@ RFC 0027 supplies that proof for supported transformations, described below. General graphs, cyclic ownership, volatile/atomic links and concurrent access remain outside the model. -Summary format 23 and sidecar format 24 require rebuilding older compiler +Summary format 26 and sidecar format 27 require rebuilding older compiler objects. Persistent caches validate executable, source, preprocessing and callee dependencies before reusing a container contract. See the [validation report](validation-rfc0023.md) for the frozen acceptance population, @@ -464,7 +464,7 @@ contracts. Cross-unit callers need compatible object evidence for recursive contracts; a forward declaration alone does not supply it. RFC 0028 transports that evidence from verified constructors and preserves supported private hook state across separate translation units, as described below. -Summary format 23, sidecar format 24 and checked encoding 9 reject +Summary format 26, sidecar format 27 and checked encoding 12 reject older metadata; rebuild old objects. Expanded JSON version 2 and compact version 3 retain their existing meanings. See [validation](validation-rfc0027.md) for fixed populations, counterexamples, test results and cost observations. @@ -505,7 +505,7 @@ transfers remain incomplete. The portable `buffer`, `buffer-preserved` and `buffer-appended` records travel through the normal source, object and cache workflows. They add no annotation -spelling or pointer ABI. Rebuild older object sidecars for format 24. +spelling or pointer ABI. Rebuild older object sidecars for format 27. Shape discovery is bounded to 16 descriptors and 64 instances; descriptors are limited to 16 KiB. Exhaustion is reported as incomplete. The initial discovery rule requires one non-function data pointer and two unsigned, non-Boolean count @@ -585,7 +585,7 @@ establish the exact written prefix. If `0 <= n && n < sizeof buffer`, `buffer[n] is the written terminator. If `n >= sizeof buffer`, truncation initializes the capacity and its final NUL. A negative or overwritten result establishes neither. -Runtime records use checked encoding 9, summary format 23 and sidecar format 24. +Runtime records use checked encoding 12, summary format 26 and sidecar format 27. Rebuild objects carrying older sidecars. The cache validates the executable and source dependencies before reusing these records. @@ -649,7 +649,218 @@ outside this milestone. The metadata remains internal to analysis. It neither completes the client's forward declarations nor inserts private names into C lookup. Conflicting -layouts lose evidence. Rebuild older artifacts: summary format 23, sidecar -format 24 and checkpoint format 3 intentionally reject previous artifacts. +layouts lose evidence. Rebuild older artifacts: summary format 26, sidecar +format 27 and checkpoint format 3 intentionally reject previous artifacts. Source/header, preprocessing, target and object-content validation still apply; this does not support source-free checked linking. + + +## Composing recursive and stateful helpers (RFC 0029) + +A mutable output record can have extra counters, flags and nested hook records. +The analyzer uses indexing, loop bounds and pointer arithmetic to nominate its +backing pointer, logical length and capacity. The caller must still establish +live storage, sufficient allocation extent and initialized contents. Changing a +capacity or logical length does not create either storage or initialized bytes. +A helper's transported field roles are checked against the caller's actual +record layout, so a separate-source caller need not repeat its indexing code. +Capacity is a guaranteed accessible range; the allocation may be larger. A +helper can require additional initialized entry bytes while its backing pointer +is unchanged. Its caller must prove those bytes independently of capacity. + +A reader loop that advances a field cursor requires the entire interval it may +visit. After the cursor changes, its current value cannot be exported as the +incoming cursor value. A stable upper bound may instead supply a sufficient +extent and initialization requirement. A caller with only the first cell +initialized, or with less storage than the advertised end, fails that contract. +A separate byte pointer and unsigned count can supply the same explicit input +interval when a helper compares a pointer distance against that count. The +caller must establish live initialized storage and a representable distance; +the comparison itself supplies no storage evidence. + +A helper that advances a byte writer by `strlen` can require an initialized +terminated prefix between its incoming cursor and capacity. The caller must +establish a real zero inside that bound; unused capacity need not be initialized. +The returned length ends at the first zero, which can precede another known zero. +This relationship lets a cursor update retain its initialized prefix and strict +capacity bound. Replacing the backing pointer or changing the relevant bytes +invalidates the associated termination evidence. + +Byte-pointer endpoint helpers can require a live initialized span in one array, +with a distance representable by the target `ptrdiff_t`. Callers must prove +that interval from actual storage; unrelated arrays and uninitialized tails +fail the requirement. An integer-returning helper can also guarantee a +nonnegative count no larger than its incoming span. This guarantee must hold +on every return, including joined branches, and promises no processing or writes. + +An unconditional reverse byte-writing loop with a stable bound can establish +the suffix it visits. Index zero requires its own store. Conditional writes, +skipped iterations and early exits do not establish that whole suffix. +A helper that advances an output pointer can preserve initialization up to its +actual final position, including a zero advance on an early return. A bound on +the largest possible advance alone never initializes those bytes. Conditional +integer arguments retain the range of their actual converted values at call +entry; neither arm is replaced by an assumed exact maximum. + +For eligible direct or mutually recursive cleanup and read-only traversal +functions, WeaveC checks the complete group before publishing any member's +guarantee. Proper-child calls +supply progress; forwarding the unchanged input is permitted only when every +cycle also contains a proper-child step. Base cases, each owned child and the +head release all remain obligations. The initial group rule covers at most 32 +single-parameter functions on one record type, with no hidden global effects or +unverified external helpers. A node's own callback binding cannot stand for its +children's bindings. Shared children and owning cycles do not satisfy the +finite ownership-forest predicate. + +An initial recursive construction rule accepts functions taking a constant +byte pointer and an unsigned remaining count, returning a nullable fresh +initialized forest. The caller supplies the entire readable input interval. +Each recursive call stays inside that interval, and every cycle decreases +the immutable entry count. Failure paths must release all partial allocations; +successful paths must transfer the complete fresh forest. Complete inferred +helpers can perform reads and partial-tree cleanup. Private hypotheses cannot +become cached callback or memory specializations. These contracts compose +through separate source files, ordinary objects and validated checkpoints. +An integer-returning constructor may instead publish through a third, pointer +output parameter: positive returns establish a non-null fresh forest and zero +returns must actually leave the slot null. The slot needs writable storage +separated from the input; its previous value supplies no ownership. Immediate +success tests transfer the whole forest, including a child attached by a helper. +The byte pointer and remaining count can also reside in a reader record, with +unrelated fields. Passing it by value preserves the caller's record. Passing a +pointer to it requires initialized writable reader storage separated from the +input bytes; changing its cursor/count invalidates the caller's old field facts. +The recursive hypothesis supplies no post-call reader bounds. Both forms prove +progress against the original input and account for every allocation. + +A constructor taking an owned node pointer and unsigned count can extend an +existing initialized head whose owned links and payloads are initially null. +Every recursive cycle must decrease the entry count. The head remains live and +unchanged on every return; newly acquired descendants must be attached exactly +once or released, including failure paths. Its `container-extended` output keeps +the original head separate from the fresh acquisition ledger. Broader mutable +reader and partial-consumption interfaces, owned-tree writers, and the mandatory +cJSON parsing and printing goals remain incomplete. + +A helper may also publish an allocated byte payload through a local pointer +or a complete allocating helper. The caller must establish an owned head with +the required empty slots. Successful and failed returns retain its ownership; +each acquired payload must be attached exactly once or released. Interior, +released, duplicated, borrowed and lost allocations do not satisfy this proof. +Payload fields discovered from imported cleanup contracts nominate the shape +only; actual storage, initialization and ownership still need proof. + +The modeled `strtod`, `strtof` and `strtold` boundary requires a terminated, +initialized input. A non-null end-pointer slot must be writable and separate +from that input. The resulting pointer retains the input object's bounds and +lifetime; a null slot is permitted. The model makes no claim that the numeric +result is finite or safe to convert to an integer. + +A complete callee that stores a copied pointer into a confined automatic slot +can preserve the source allocation's ownership. This includes an end pointer +stored by `strtod`. The slot address must remain confined to that represented +call. The copy preserves aliases: freeing the base invalidates the end pointer, +an interior pointer cannot be released, and losing every local holder still +leaks the allocation. + +Generic synchronous allocation and release helpers can now export explicit +`callback-allocate` and `callback-release` requirements: + +```c +void *make(void *(*allocate)(size_t), size_t bytes) { + if (!bytes) return NULL; + unsigned char *p = allocate(bytes); + if (p) p[bytes - 1] = 7; + return p; +} +``` + +The helper's contract requires a non-null callback returning either null or a +fresh `free`-compatible allocation of the requested extent. A closed caller +passing `malloc` discharges that requirement through the modeled C-library +boundary. A callback allocating fewer bytes, an unknown external target, a +nullable binding or an incompatible cast does not discharge it. Complete +inferred wrappers are accepted only when their checked interface and effects +establish the same behavior. This adds no annotation or runtime dispatch. + +`free(p); p = NULL;` preserves the earlier release of the entry allocation. +Assigning a new allocation to `p` cannot discharge the old allocation's cleanup +obligation. Zero-initialized byte ranges outside a proved store are preserved; +unknown overlapping writes discard that evidence. + +Scans can also use up to 64 exact initialized bytes from an ordinary character +array initializer, including an immutable static local array. Known contents +can exclude a branch only after the read's storage, bounds and initialization +are proved. Mutation retires overlapping contents; unchanged slices and proved +separate storage can retain them. Actual byte-pointer call cases transport the +captured contents within the existing context budget. This can weaken a +sufficient generic precondition only after rechecking the callee for that input; +it does not certify arbitrary strings or bypass the generic body. An actual +const-qualified array object can retain its contents across a represented write +to another object. A const-qualified pointer alone supplies no such premise, +and writes to the constant array still require ordinary write-permission checks. + +These records require summary format 26, sidecar format 27 and checked encoding +12. Older object sidecars and checkpoints, including interim RFC 0029 development +artifacts, must be rebuilt. + +Floating-to-integer conversions can be proved locally for finite constants or +unchanged scalar inputs under true finite bounds. The bounds are checked using +the target floating format and integer width, including strict endpoints around +64-bit limits. For example, `x >= INT_MIN && x <= INT_MAX` excludes NaN on its +true branch when those constants are represented exactly. Testing and rejecting +`x < INT_MIN || x > INT_MAX` leaves NaN on the remaining branch and supplies no +such proof. Taken addresses, changes to the guarded value, bypassing jumps and +nonstandard floating modes prevent this inference. + +Run the frozen primary, independent serializer, reader, transport, object and cache +populations with `scripts/checked-workflows.py`. The separate `upstream` +population retains the broader cJSON parse/print acceptance goals. Those goals +are not implied by passing the smaller workflows; consult the RFC 0029 +validation record for outstanding coverage. These changes do not establish +arbitrary recursive construction, general floating-point conversion safety or +a complete cJSON library certificate. + +RFC 0029 also supports a recursive byte writer taking immutable byte input, an +unsigned remaining count and a pointer to a discovered buffer record. A checked +writer requires the full initialized input interval, writable output storage +and separation of input, header and backing. Its verified output describes the +actual initialized prefix on every return, including partial failure. A success +return alone does not imply that all input was copied or that the output is +zero terminated. Clients must use the established current length when reading +that prefix. This does not yet cover the required recursive tree printer. + +Reader records with a const byte pointer, initialized input length and changing +cursor can carry a reusable reader predicate. The cursor remains between zero +and the readable input length, including on a partial failure return. The +entire input interval must be initialized; the predicate supplies no permission +to write it. Copied records and separate-source helpers retain that distinction. +A reader output alone specifies no exact consumed count or parsing result. + +An independently checked input case can avoid a generic recursive limit by +proving the recursive branch unreachable. Exact values captured through live +whole scalar objects may help establish that branch; invalidated storage, +partial objects and incompatible views supply no value. The generic function +still reports its own exhaustion when selected independently. + +Exact record-array cells use the same identity for `a->field`, `a[0].field` +and `(*a).field`, including contracts forwarded to a helper. Complete current +zero-byte intervals can establish zero-valued ordinary integer fields in +automatic records and exact array cells. Partial byte coverage and later writes +cannot supply that fact. A stored string terminator can survive an update to a +separate header only when the separation is proved or exported as an explicit +caller requirement using unchanged entry identities. + +Small fixed-size automatic integer arrays retain values for exact elements. +Writes through possibly overlapping indices discard those values. Advancing a +pointer retains its allocation identity but retires facts about its previous +pointee. Borrow diagnostics can therefore identify an exact element such as +`a[1]` where the earlier diagnostic used the whole-array spelling `a[*]`. + +A local numeric byte prefix can exclude NaN from a modeled `strtod`, `strtof` +or `strtold` result. The checker must establish actual numeric characters +through stores, a validated scan, or a byte-preserving copy. Plain initialized +storage is insufficient. Infinity remains possible, so converting the result +to an integer still requires unchanged, target-representable bounds. A null +end-pointer argument does not export ownership of the input allocation. diff --git a/include/weavec/Analysis/Summaries.h b/include/weavec/Analysis/Summaries.h index 0c17c117..a717fb7c 100644 --- a/include/weavec/Analysis/Summaries.h +++ b/include/weavec/Analysis/Summaries.h @@ -245,6 +245,38 @@ class SummaryStore { [[nodiscard]] core::CallTargets targetsForGlobal(const core::SummaryPath &path) const; std::set incompleteFunctions; + /// Immutable call-graph component identities used only for role nomination. + /// Membership supplies no induction hypothesis or completed contract. + std::map recursiveComponents; + std::set recursiveFunctions; + /// RFC 0029: provisional recursive effects do not nominate scalar cases. + bool checkingRecursiveApproximation = false; + /// RFC 0029: the existing final pass rechecks ordinary value outcomes + /// against converged may-effects; this supplies no checked memory output. + bool refreshingRecursiveValueOutcomes = false; + /// RFC 0029: private hypotheses are visible only while validating their + /// group. Completed groups contain immutable same-TU bodies, never imports. + struct RecursiveContractGroup { + std::set members; + bool releases = true; + bool constructs = false; + bool extendsHead = false; + bool writes = false; + bool mutableReader = false; + const clang::FieldDecl *readerData = nullptr; + const clang::FieldDecl *readerCount = nullptr; + }; + RecursiveContractGroup activeRecursiveContracts; + std::map + verifiedRecursiveContracts; + std::set failedRecursiveProgress; + // Value distinguishes failed writer outputs from construction outputs. + std::map failedRecursiveOutputs; + [[nodiscard]] const RecursiveContractGroup * + recursiveContractGroup(const clang::FunctionDecl &caller) const; + [[nodiscard]] bool + recursiveContractPeer(const clang::FunctionDecl &caller, + const clang::FunctionDecl &callee) const; [[nodiscard]] core::CallTargets staticTargets(const clang::Expr &expr, unsigned depth = 0); [[nodiscard]] const std::map & @@ -303,6 +335,13 @@ class SummaryStore { /// RFC 0027: immutable topology discovery, never a flow-sensitive proof. [[nodiscard]] std::vector recursiveLinks(const clang::RecordDecl &record); + struct ContainerFields { + std::vector payloads; + std::map ownership; + }; + /// RFC 0029: nominations retain imported summary dependencies. + [[nodiscard]] ContainerFields + containerFields(const clang::RecordDecl &record); [[nodiscard]] std::map containerOwnership( const clang::RecordDecl &record, @@ -345,6 +384,7 @@ class SummaryStore { bufferShapeCache.clear(); recursiveLinkCache.clear(); importedRecursiveLinkCache.clear(); + containerPayloadCache.clear(); containerOwnershipCache.clear(); } context = unitContext; @@ -474,6 +514,13 @@ class SummaryStore { std::shared_ptr recursiveLinkGeneration; std::map importedRecursiveLinkCache; + std::shared_ptr containerPayloadGeneration; + struct ImportedContainerFields { + ContainerFields fields; + Dependencies dependencies; + }; + std::map + containerPayloadCache; std::map> containerOwnershipCache; diff --git a/include/weavec/Analysis/TranslationUnitAnalysis.h b/include/weavec/Analysis/TranslationUnitAnalysis.h index a5fe83e5..51ca4ff0 100644 --- a/include/weavec/Analysis/TranslationUnitAnalysis.h +++ b/include/weavec/Analysis/TranslationUnitAnalysis.h @@ -142,6 +142,7 @@ class TranslationUnitAnalyzer { void reportConfirmedSizedFields( llvm::ArrayRef reported, const std::set &alreadyReported); + bool verifyRecursiveContractGroup(const std::vector &component); }; } // namespace weavec::analysis diff --git a/include/weavec/Core/Buffer.h b/include/weavec/Core/Buffer.h index a0502a59..ffe9d5d5 100644 --- a/include/weavec/Core/Buffer.h +++ b/include/weavec/Core/Buffer.h @@ -26,6 +26,9 @@ struct BufferShape { ContainerField capacity; std::uint64_t elementBytes = 1; bool pointerElements = false; + // RFC 0029: length is a cursor into fully initialized capacity. This + // descriptor grants read access only, independently of backing ownership. + bool reader = false; bool terminated = false; bool ownsBacking = false; bool ownsElements = false; diff --git a/include/weavec/Core/CallContext.h b/include/weavec/Core/CallContext.h index 07b71744..caeb4269 100644 --- a/include/weavec/Core/CallContext.h +++ b/include/weavec/Core/CallContext.h @@ -38,10 +38,21 @@ struct CallContext { /// directed facts do not assert an exact displacement or ownership share. std::set> orders; std::map facts; + /// Exact initialized bytes beginning at the input pointer's entry value. + /// Payload bytes consume the existing MaxCallContextFacts budget. + // NOLINTNEXTLINE(readability-redundant-member-init): designated-init default + std::map bytes = {}; + /// The captured bytes belong to an actual const-qualified array object. + // NOLINTNEXTLINE(readability-redundant-member-init): designated-init default + std::set immutableBytes = {}; + /// RFC 0029: actual by-value floating arguments known to exclude NaN. + // NOLINTNEXTLINE(readability-redundant-member-init): designated-init default + std::set nonNan = {}; [[nodiscard]] bool empty() const noexcept { return callbacks.empty() && aliases.empty() && separations.empty() && - orders.empty() && facts.empty(); + orders.empty() && facts.empty() && bytes.empty() && + immutableBytes.empty() && nonNan.empty(); } /// Canonicalizes the pair, without weakening a conflicting existing fact. /// False means the context cannot be represented; callers must not use it. diff --git a/include/weavec/Core/CheckedIO.h b/include/weavec/Core/CheckedIO.h index 37420efb..4131734c 100644 --- a/include/weavec/Core/CheckedIO.h +++ b/include/weavec/Core/CheckedIO.h @@ -13,8 +13,8 @@ namespace weavec::core { -/// Single-token hex encoding of length-delimited fields, checked encoding 9 -/// (summary format 23). The source strings are opaque data; no field is +/// Single-token hex encoding of length-delimited fields, checked encoding 12 +/// (summary format 26). The source strings are opaque data; no field is /// executed or reparsed as code. [[nodiscard]] std::string printCheckedContract(const CheckedContract &contract, const GlobalNamer &names); diff --git a/include/weavec/Core/Container.h b/include/weavec/Core/Container.h index d241a176..7249f88a 100644 --- a/include/weavec/Core/Container.h +++ b/include/weavec/Core/Container.h @@ -82,6 +82,8 @@ struct ContainerShape { [[nodiscard]] bool recursiveLink(std::string_view name) const; [[nodiscard]] bool valid() const; + /// Every owned edge is null, so a live instance owns only its head. + [[nodiscard]] bool singletonHead() const; [[nodiscard]] bool entails(const ContainerShape &required) const; [[nodiscard]] std::string encode() const; [[nodiscard]] static std::optional diff --git a/include/weavec/Core/Induction.h b/include/weavec/Core/Induction.h new file mode 100644 index 00000000..9cb737cd --- /dev/null +++ b/include/weavec/Core/Induction.h @@ -0,0 +1,28 @@ +//===- Induction.h - Recursive proof progress (RFC 0029) -------*- C++ -*-===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#ifndef WEAVEC_CORE_INDUCTION_H +#define WEAVEC_CORE_INDUCTION_H + +#include + +namespace weavec::core { + +struct InductionEdge { + unsigned caller = 0; + unsigned callee = 0; + bool strict = false; +}; + +/// Each edge must preserve one established well-founded measure, and a strict +/// edge must decrease it. Analysis proves those premises. This checks that +/// every possible cycle has a decrease, never just one favorable cycle. +[[nodiscard]] bool validInductionProgress(unsigned members, + std::span edges); + +} // namespace weavec::core +#endif // WEAVEC_CORE_INDUCTION_H diff --git a/include/weavec/Core/IntegerExpression.h b/include/weavec/Core/IntegerExpression.h index 3f3921a3..a6223e25 100644 --- a/include/weavec/Core/IntegerExpression.h +++ b/include/weavec/Core/IntegerExpression.h @@ -143,6 +143,38 @@ class IntegerExpression { return constant(*evaluated.value); } } + // RFC 0029: unsigned addition/subtraction is modular. Cancel the repeated + // operand only when its evaluation is total; a discarded invalid shift or + // division must not disappear from the expression's safety obligations. + if (op == IntegerOp::Add && !lhs.type().isSigned && !lhs.type().isBoolean && + lhs.type() == rhs.type()) { + const auto cancel = [](const IntegerExpression &addend, + const IntegerExpression &difference) + -> std::optional { + if (difference.nodes.back().kind != IntegerNodeKind::Operation || + difference.nodes.back().op != IntegerOp::Subtract || + difference.nodes.size() <= addend.nodes.size() + 1) + return std::nullopt; + const auto end = difference.nodes.end() - 1; + const auto start = + end - static_cast(addend.nodes.size()); + if (!std::equal(start, end, addend.nodes.begin()) || + addend + .evaluate([](const Key &, IntegerType type) { + return IntegerRange::full(type); + }) + .mayBeInvalid) + return std::nullopt; + auto remaining = + checked(std::vector(difference.nodes.begin(), start)); + return remaining && remaining->type() == addend.type() ? remaining + : std::nullopt; + }; + if (auto result = cancel(lhs, rhs)) + return result; + if (auto result = cancel(rhs, lhs)) + return result; + } // Canonicalize only operations whose C evaluation is commutative. This // compares already captured values; it never reorders source side effects. if (!isUnary(op) && diff --git a/include/weavec/Core/Interface.h b/include/weavec/Core/Interface.h index 23cdde3b..01dc2abc 100644 --- a/include/weavec/Core/Interface.h +++ b/include/weavec/Core/Interface.h @@ -48,6 +48,7 @@ struct InterfaceNode { bool prototype = true; std::string name; std::string view; + std::string typedefName; std::vector parameters; std::vector fields; friend bool operator==(const InterfaceNode &, diff --git a/include/weavec/Core/Safety.h b/include/weavec/Core/Safety.h index e9004c82..07a629b3 100644 --- a/include/weavec/Core/Safety.h +++ b/include/weavec/Core/Safety.h @@ -248,8 +248,19 @@ struct InitializedRange { // NOLINTNEXTLINE(readability-redundant-member-init): designated-init default std::optional source = {}; bool zeroed = false; + /// RFC 0029: every byte belongs to the decimal numeric text alphabet. + bool numericText = false; + /// RFC 0029: exact bytes at constant endpoints, bounded by 64 bytes. + // NOLINTNEXTLINE(readability-redundant-member-init): designated-init default + std::string bytes = {}; + /// The bytes come from an actual const-qualified character array object. + bool immutableBytes = false; /// RFC 0024: transport an existential prefix, never a full initialized range. bool terminatedWithin = false; + /// RFC 0029: portions untouched by a write in the same byte coordinates. + /// Unrepresented intervals and existential termination facts yield none. + [[nodiscard]] std::vector + outsideWrite(const Affine &first, const Affine &last) const; friend auto operator<=>(const InitializedRange &, const InitializedRange &) = default; }; @@ -300,16 +311,26 @@ struct ArgumentListState { }; struct SafetyState { + struct PendingAllocationRelease { + PlaceId storage; + PlaceId snapshot; + friend bool operator==(const PendingAllocationRelease &, + const PendingAllocationRelease &) = default; + }; /// RFC 0028: historical must-fact about direct entry pointer parameters. std::set consumedAllocations; BufferFacts buffers; UnionState unions; ContainerFacts containers; FootprintRelations footprints; + /// RFC 0029: release a captured old allocation only on non-null realloc. + std::map pendingAllocationReleases; /// Current head/child decomposition was established before any link update. std::set unfoldedFootprints; std::map argumentLists; std::set initialized; + /// Local must-fact: floating values exclude NaN, but may be infinite. + std::set nonNan; std::set pointers; /// May-fact: these objects require unresolved external effects at link time. std::set deferred; diff --git a/include/weavec/Core/Summary.h b/include/weavec/Core/Summary.h index 2c6604fd..e05733f7 100644 --- a/include/weavec/Core/Summary.h +++ b/include/weavec/Core/Summary.h @@ -300,7 +300,18 @@ enum class CheckedRequirementKind : std::uint8_t { /// RFC 0027: output path is the union of entry other and entry begin.path. ContainerCombined, /// RFC 0028: the entry parameter's single allocation was definitely freed. - AllocationConsumed + AllocationConsumed, + /// RFC 0029: synchronous allocator/releaser behavior of an entry callback. + CallbackAllocate, + CallbackRelease, + /// RFC 0029: live initialized byte interval [path, other). + InitializedSpan, + /// RFC 0029: nonnegative integer result bounded by an entry byte span. + CountWithinSpan, + /// RFC 0029: initialized bytes from entry other to the final path position. + InitializedAdvance, + /// RFC 0029: unchanged entry head plus a disjoint fresh allocation region. + ContainerExtended }; struct CheckedRequirement { CheckedRequirementKind kind = CheckedRequirementKind::Valid; diff --git a/include/weavec/Core/SummaryIO.h b/include/weavec/Core/SummaryIO.h index e0579ce4..71b53b39 100644 --- a/include/weavec/Core/SummaryIO.h +++ b/include/weavec/Core/SummaryIO.h @@ -98,7 +98,7 @@ namespace weavec::core { /// version cannot be read by the previous one. // Version 18 (RFC 0023) adds inductive container predicates to checked // contracts. -inline constexpr unsigned SummaryFormatVersion = 23; +inline constexpr unsigned SummaryFormatVersion = 26; /// The name to print for a global root id. using GlobalNamer = std::function; diff --git a/include/weavec/Frontend/Sidecar.h b/include/weavec/Frontend/Sidecar.h index 99777bd8..eb8810b3 100644 --- a/include/weavec/Frontend/Sidecar.h +++ b/include/weavec/Frontend/Sidecar.h @@ -59,7 +59,7 @@ namespace weavec::frontend { /// Version 14 (RFC 0018): safety contracts and checked build input bindings. /// Version 16 (RFC 0020): effective preprocessing identity for object binding. /// Version 19 (RFC 0023): inductive container contracts (summary format 18). -inline constexpr unsigned SidecarFormatVersion = 24; +inline constexpr unsigned SidecarFormatVersion = 27; /// RFC 0028: accumulated demand is independent of computed-context limits. inline constexpr std::size_t MaxSidecarContextRequests = 65536; diff --git a/lib/Analysis/Builtins.cpp b/lib/Analysis/Builtins.cpp index 972bb041..2b02b680 100644 --- a/lib/Analysis/Builtins.cpp +++ b/lib/Analysis/Builtins.cpp @@ -791,9 +791,12 @@ static llvm::StringMap buildTable() { llvm::StringLiteral("strtod"), llvm::StringLiteral("strtof"), llvm::StringLiteral("strtold"), llvm::StringLiteral("strtoimax"), llvm::StringLiteral("strtoumax")}) { - table[name].addStore(core::Store{ - .dest = core::SummaryPath::param(1).deref(), - .value = core::ValueSource::interiorCopy(core::SummaryPath::param(0))}); + auto value = core::ValueSource::interiorCopy(core::SummaryPath::param(0)); + value.when.require(core::SummaryPath::param(1), + core::ValueFact::of(core::Outcome::NonNull)); + table[name].addStore( + core::Store{.dest = core::SummaryPath::param(1).deref(), + .value = std::move(value)}); } // Out-parameters that receive a fresh allocation the caller must release diff --git a/lib/Analysis/CMakeLists.txt b/lib/Analysis/CMakeLists.txt index 5d04e865..1e028cf7 100644 --- a/lib/Analysis/CMakeLists.txt +++ b/lib/Analysis/CMakeLists.txt @@ -15,8 +15,14 @@ weavec_add_library( DataflowContainerTransfer.cpp DataflowContainerContracts.cpp DataflowFootprints.cpp + DataflowFloating.cpp DataflowSafety.cpp + FloatingCastSupport.cpp DataflowBuffers.cpp + DataflowBufferDiscovery.cpp + DataflowRecursiveConstruction.cpp + DataflowRecursiveExtension.cpp + DataflowRecursiveWriting.cpp DataflowBufferContracts.cpp DataflowCases.cpp DataflowUnions.cpp @@ -29,7 +35,9 @@ weavec_add_library( DataflowPointerOperations.cpp DataflowTraversalRelations.cpp DataflowCursors.cpp + DataflowSpans.cpp DataflowStringTraversal.cpp + DataflowByteContents.cpp DataflowArrays.cpp DataflowArrayMemory.cpp DataflowArrayRanges.cpp @@ -47,6 +55,7 @@ weavec_add_library( DataflowMemory.cpp DataflowViews.cpp DataflowCallbacks.cpp + DataflowCallbackContracts.cpp CallbackSummaries.cpp CallContextSummaries.cpp DataflowCallContext.cpp @@ -59,6 +68,7 @@ weavec_add_library( Summaries.cpp SummaryDependencies.cpp TranslationUnitAnalysis.cpp + RecursiveContracts.cpp DataflowLoopRequirements.cpp DataflowGuardCompleteness.cpp PUBLIC_DEPS weavec::Core diff --git a/lib/Analysis/CallContextSummaries.cpp b/lib/Analysis/CallContextSummaries.cpp index e72c9918..9f8de432 100644 --- a/lib/Analysis/CallContextSummaries.cpp +++ b/lib/Analysis/CallContextSummaries.cpp @@ -10,13 +10,16 @@ #include "llvm/ADT/ScopeExit.h" +#include + namespace weavec::analysis { std::optional SummaryStore::specializeMemory( std::string_view symbol, const core::CallContext &bindings, const AnalysisOptions &options, core::DiagnosticSink *sink) { - const bool checkedCase = options.stats != nullptr && options.checkContracts && - !bindings.facts.empty(); + const bool checkedCase = + options.stats != nullptr && options.checkContracts && + (!bindings.facts.empty() || !bindings.nonNan.empty()); if (checkedCase) options.stats->add("checked_case_requests"); const auto decline = [&]() -> std::optional { @@ -24,8 +27,92 @@ std::optional SummaryStore::specializeMemory( options.stats->add("checked_case_declines"); return std::nullopt; }; + const auto *function = callable(symbol); + // Private induction premises belong to the enclosing proof transaction. + // A nested context must not publish a member before that group verifies. + if (function != nullptr && + activeRecursiveContracts.members.contains(function->getCanonicalDecl())) + return decline(); if (!bindings.valid()) return decline(); + if (options.checkContracts && checkingRecursiveApproximation && + activeMemoryContexts.empty() && bindings.aliases.empty() && + bindings.orders.empty()) { + if (options.stats) + options.stats->add("recursive_provisional_context_declines"); + return decline(); + } + if (options.checkContracts && function && + recursiveFunctions.contains(function->getCanonicalDecl()) && + !verifiedRecursiveContracts.contains(function->getCanonicalDecl()) && + bindings.aliases.empty() && bindings.orders.empty()) { + auto base = lookup(*function); + if (base) { + std::set readOnlyRecords; + for (unsigned i = 0; i < function->getNumParams(); ++i) { + const auto type = function->getParamDecl(i)->getType(); + if (!type->isPointerType() || !type->getPointeeType()->isRecordType()) + continue; + bool read = false; + bool changed = false; + for (const auto &[path, effect] : base->summary->effects) { + if (!path.isParam() || path.index != i) + continue; + read |= path.hasDeref() && effect.read; + changed |= effect.written || effect.replaced || effect.consumed(); + } + if (read && !changed) + readOnlyRecords.insert(i); + } + const auto overwritten = [&](const core::SummaryPath &path) { + return path.hasDeref() && + std::ranges::any_of( + base->summary->effects, [&](const auto &effect) { + return (effect.second.written || effect.second.replaced) && + (effect.first == path || + effect.first.isProperPrefixOf(path)); + }); + }; + const bool refinesInput = + std::ranges::any_of(bindings.facts, [&](const auto &entry) { + const auto &[path, fact] = entry; + const bool exact = + fact.constant || (fact.integer && fact.integer->constant()) || + (fact.isPointer() && + fact.implies(core::ValueFact::of(core::Outcome::Null))); + if (!path.isParam() || !exact) + return false; + if (!readOnlyRecords.empty() && path.hasDeref() && + !readOnlyRecords.contains(path.index)) + return false; + return !overwritten(path); + }); + if (!refinesInput) { + if (options.stats) + options.stats->add("recursive_context_nomination_declines"); + if (base->summary->checked.complete()) + return base; + return decline(); + } + const auto stableInputs = [&](core::CallContext input) { + std::erase_if(input.facts, [&](const auto &fact) { + return overwritten(fact.first); + }); + return input; + }; + const auto stable = stableInputs(bindings); + if (std::ranges::any_of(activeMemoryContexts, [&](const auto &active) { + return active.first == symbol && active.second != bindings && + stableInputs(active.second) == stable; + })) { + if (options.stats) + options.stats->add("recursive_output_context_declines"); + if (base->summary->checked.complete()) + return base; + return decline(); + } + } + } const MemoryContextKey key{std::string(symbol), bindings}; noteDependency(symbol); auto &requests = memoryRequests[key.first]; @@ -33,7 +120,6 @@ std::optional SummaryStore::specializeMemory( requests.size() >= core::MaxMemoryContexts) return decline(); requests.insert(bindings); - const auto *function = callable(symbol); const auto *definition = function ? function->getDefinition() : nullptr; if (!context) return decline(); diff --git a/lib/Analysis/CallbackSummaries.cpp b/lib/Analysis/CallbackSummaries.cpp index b67ce2a3..cb1f0ecb 100644 --- a/lib/Analysis/CallbackSummaries.cpp +++ b/lib/Analysis/CallbackSummaries.cpp @@ -368,6 +368,8 @@ std::optional SummaryStore::lookupCall(const CallExpr &call) { std::optional SummaryStore::specialize( const FunctionDecl &function, const core::CallbackBindings &bindings, const AnalysisOptions &options, core::DiagnosticSink *sink) { + if (activeRecursiveContracts.members.contains(function.getCanonicalDecl())) + return std::nullopt; if (bindings.empty()) return lookup(function); const std::string symbol = callableSymbol(function); diff --git a/lib/Analysis/Dataflow.cpp b/lib/Analysis/Dataflow.cpp index 55b9a61a..a902d996 100644 --- a/lib/Analysis/Dataflow.cpp +++ b/lib/Analysis/Dataflow.cpp @@ -46,6 +46,7 @@ #include "clang/AST/ExprCXX.h" #include "clang/AST/OperationKinds.h" +#include "clang/AST/RecordLayout.h" #include "clang/Analysis/FlowSensitive/DataflowWorklist.h" #include "clang/Lex/Lexer.h" @@ -514,10 +515,37 @@ core::AnalysisState FunctionDataflow::initialState() { state.safety->initialized.insert(place); if (param->getType()->isPointerType()) state.safety->pointers.insert(place); + // RFC 0018: by-value arguments provide initialized field values. Keep + // the intervals separate so that this supplies neither initialized + // padding nor evidence about a pointer field's referent. + if (const auto *record = param->getType()->getAsRecordDecl(); + record && record->isCompleteDefinition() && !record->isUnion()) { + const auto &layout = context.getASTRecordLayout(record); + for (const auto *field : record->fields()) { + if (field->isBitField() || !field->getType()->isScalarType()) + continue; + const auto bytes = byteSizeOf(field->getType(), context); + const auto bits = layout.getFieldOffset(field->getFieldIndex()); + if (!bytes || bits % context.getCharWidth() != 0) + continue; + const auto offset = + static_cast(bits / context.getCharWidth()); + state.safety->initialized.insert(builder.fieldPlace(place, *field)); + state.safety->initialize( + place, {.begin = core::Affine::ofConstant(offset), + .end = core::Affine::ofConstant(offset + *bytes)}); + } + } } if (!param->getType()->isPointerType()) { const auto type = integerTypeOf(param->getType(), context); - if (type && paramReassigned[param->getFunctionScopeIndex()]) { + const auto *recursiveGroup = summaries.recursiveContractGroup(function); + const bool progressInput = + options.checkContracts && recursiveGroup != nullptr && + (recursiveGroup->constructs || recursiveGroup->writes) && + param->getFunctionScopeIndex() == 1; + if (type && + (paramReassigned[param->getFunctionScopeIndex()] || progressInput)) { const auto saved = places.create("entry(" + nameOf(place) + ")"); numericEntryValues.emplace(place, saved); state.scalars.set( @@ -627,6 +655,10 @@ core::AnalysisState FunctionDataflow::initialState() { initializeContainers(state); if (state.safety) initializeBuffers(state); + if (state.safety) + initializeRecursiveInput(state); + if (state.safety) + initializeCheckedSpans(state); for (const auto *param : function.parameters()) if (param->getType()->isVariablyModifiedType()) captureVariableArray(builder.placeForVar(*param), *param, state); @@ -1466,10 +1498,24 @@ bool FunctionDataflow::isStorageOfVariable(core::PlaceId place) const { } /// RFC 0011: the pointer's own value moves, carrying its spatial/alias facts. -static void stepPointer(core::PlaceId place, const core::PointerOffset &step, - core::AnalysisState &state) { +void FunctionDataflow::stepPointer(core::PlaceId place, + const core::PointerOffset &step, + core::AnalysisState &state, const Expr &at) { if (step.isZero()) return; + // The pointer retains its allocation, but its pointee names a different + // cell. Retire only facts below this holder: its unchanged aliases still + // name their original cells, and no memory was written by the advance. + for (const auto cell : places.descendants(place)) { + if (!tracksScalar(cell)) + continue; + snapshotIntegerDependencies(cell, &at, state); + snapshotScalar(cell, &at, state); + state.dropGuardsOn(cell); + state.relations.forget(cell); + state.numericValues.erase(cell); + state.scalars.forget(cell); + } // A place with no record so far stood at the start of what it points into // (RFC 0008's convention for parameters); now it is `step` further. const core::SpatialRecord record = @@ -1513,7 +1559,11 @@ FunctionDataflow::summaryAffineOf(const std::optional &affine) { : std::nullopt; } const auto path = stableSummaryPathOf(*affine->place); - if (path) + // RFC 0029: a field spelling is not its immutable entry value after a + // numeric write. In particular, a loop cursor must project an envelope + // covering every iteration, not just its initial cell. + if (path && + (!currentState || !currentState->numericWrites.contains(*affine->place))) return core::PathAffine::ofPath(*path, affine->scale, affine->constant); if (options.checkContracts && currentState) { const auto folded = foldAffine(affine, *currentState); @@ -1529,7 +1579,7 @@ FunctionDataflow::summaryAffineOf(const std::optional &affine) { pair.first == *affine->place ? pair.second : pair.first; const auto input = stableSummaryPathOf(other); std::int64_t constant = 0; - if (oriented && input && + if (oriented && input && !currentState->numericWrites.contains(other) && !__builtin_mul_overflow(oriented->offset, affine->scale, &constant) && !__builtin_add_overflow(constant, affine->constant, &constant)) return core::PathAffine::ofPath(*input, affine->scale, constant); @@ -1809,6 +1859,36 @@ void FunctionDataflow::run() { return; } + // Specialized direct cleanup uses the same private group and progress + // checker as a TU component. Its callback bindings belong only to this + // analysis; the hypothesis must never enter the generic summary store. + bool singletonRecursiveGroup = false; + if (options.checkContracts && function.getNumParams() == 1 && + function.getReturnType()->isVoidType() && + function.getParamDecl(0)->getType()->isPointerType() && + function.getParamDecl(0) + ->getType() + ->getPointeeType() + ->getAsRecordDecl() != nullptr && + !summaries.recursiveContractGroup(function) && + summaries.activeRecursiveContracts.members.empty()) { + for (const auto *block : *cfg) + for (const auto &element : *block) + if (const auto statement = element.getAs()) + if (const auto *call = dyn_cast(statement->getStmt())) + if (const auto *callee = call->getDirectCallee(); + callee && + callee->getCanonicalDecl() == function.getCanonicalDecl()) + singletonRecursiveGroup = true; + if (singletonRecursiveGroup) + summaries.activeRecursiveContracts = { + .members = {function.getCanonicalDecl()}, .releases = true}; + } + const auto finishRecursiveGroup = llvm::scope_exit([&] { + if (singletonRecursiveGroup) + summaries.activeRecursiveContracts = {}; + }); + classifyStmt(body); if (options.checkContracts) initializeChecked(); @@ -1866,18 +1946,34 @@ void FunctionDataflow::run() { continue; const CFGBlock *header = nullptr; const ForStmt *loop = nullptr; + const Stmt *loopStatement = nullptr; + const bool byteContext = !memoryContext.bytes.empty(); + bool byteSwitch = false; bool eligible = true; std::set members; for (const auto *block : *component) { cyclicBlocks[block->getBlockID()] = true; members.insert(block->getBlockID()); const auto *terminator = block->getTerminatorStmt(); + if (const auto *dispatch = dyn_cast_or_null(terminator)) { + const auto *value = dispatch->getCond()->IgnoreParenImpCasts(); + byteSwitch |= value->getType()->isCharType() && + !value->getType().isVolatileQualified() && + (isa(value) || + (isa(value) && + cast(value)->getOpcode() == UO_Deref)); + } if (const auto *candidate = dyn_cast_or_null(terminator)) { - eligible &= loop == nullptr; + eligible &= loopStatement == nullptr || loopStatement == candidate; + loopStatement = candidate; loop = candidate; header = block; - } else if (isa_and_nonnull(terminator)) { + } else if (isa_and_nonnull(terminator)) { + eligible &= byteContext && + (loopStatement == nullptr || loopStatement == terminator); + loopStatement = terminator; + } else if (isa_and_nonnull(terminator) || + (!byteContext && isa_and_nonnull(terminator))) { eligible = false; } } @@ -1897,12 +1993,35 @@ void FunctionDataflow::run() { increment ? dyn_cast( increment->getSubExpr()->IgnoreParenImpCasts()) : nullptr; - eligible &= header != nullptr && condition != nullptr && - condition->getOpcode() == BO_LT && index != nullptr && - increment != nullptr && increment->isIncrementOp() && - stepped != nullptr && - index->getDecl() == stepped->getDecl() && - !condition->getRHS()->HasSideEffects(context); + const bool byteCase = + byteContext && + (isa_and_nonnull(loopStatement) || + (loop != nullptr && byteSwitch && increment != nullptr && + increment->isIncrementOp() && stepped != nullptr && + stepped->getType()->isIntegerType() && + !stepped->getType().isVolatileQualified())); + if (byteCase) { + // RFC 0029: partition actual byte-specialized loop executions under + // the existing traversal bound. The last layer still joins and + // widens every later backedge; no trip-count assumption is made. + header = nullptr; + eligible &= loopStatement != nullptr; + for (const auto *block : *component) + for (const auto &edge : block->preds()) + if (const auto *pred = edge.getReachableBlock(); + pred && !members.contains(pred->getBlockID())) { + eligible &= header == nullptr || header == block; + header = block; + } + eligible &= header != nullptr; + } else { + eligible &= header != nullptr && condition != nullptr && + condition->getOpcode() == BO_LT && index != nullptr && + increment != nullptr && increment->isIncrementOp() && + stepped != nullptr && + index->getDecl() == stepped->getDecl() && + !condition->getRHS()->HasSideEffects(context); + } if (!eligible) continue; // Every external entry must cross the header. Removing the back edges @@ -1931,7 +2050,11 @@ void FunctionDataflow::run() { expansionFor[id] = expansions.size(); expansions.push_back({.header = header, .condition = condition, - .blocks = std::move(members)}); + .blocks = std::move(members), + .decided = byteCase, + .active = byteCase}); + if (byteCase && options.stats) + options.stats->add("checked_loop_expansions"); } using ExpandedKey = std::pair; std::map expandedEntries; @@ -2094,8 +2217,34 @@ void FunctionDataflow::run() { : expandedVisits[{succ.getBlockID(), nextLayer}] > 0)) || (nextLayer == core::MaxTraversalIterations && layer == nextLayer))); - const bool changed = - target->join(edgeState, &places, widen) || premises; + const auto boundaries = + options.checkContracts && widen + ? checkedJoinBoundaries(*target, edgeState) + : std::vector>{}; + bool changed = target->join(edgeState, &places, widen) || premises; + for (const auto &[coordinate, endpoint] : boundaries) { + const auto represented = [&](core::PlaceId cursor) { + return std::ranges::any_of( + target->safety->positions, [&](const auto &entry) { + return entry.second.offset == core::Affine::ofPlace(cursor); + }); + }; + if (!represented(coordinate) || + (endpoint.place && + std::ranges::any_of(checkedCoordinates, + [&](const auto &entry) { + return entry.second == *endpoint.place; + }) && + !represented(*endpoint.place))) + continue; + const auto before = target->relations; + if (!endpoint.place) + target->relations.learnAtMost(coordinate, endpoint.constant); + else + target->relations.learn(coordinate, core::Relation::LessEqual, + *endpoint.place, endpoint.constant); + changed |= before != target->relations; + } if (changed) enqueue(&succ, nextLayer); }; @@ -2167,7 +2316,10 @@ void FunctionDataflow::run() { for (const auto &range : ranges) stream << " initialized " << nameOf(storage) << " [" << range.begin.toString() << "," << range.end.toString() - << ")\n"; + << ")" << (range.numericText ? " numeric-text" : "") + << (!range.bytes.empty() ? " exact-bytes" : "") << "\n"; + for (const auto place : state.safety->nonNan) + stream << " non-NaN " << nameOf(place) << "\n"; for (const auto &[holder, position] : state.safety->positions) stream << " position " << nameOf(holder) << " = " << nameOf(position.storage) << " + " @@ -2247,6 +2399,14 @@ void FunctionDataflow::transfer(const CFGBlock &block, lastCall.reset(); retireHeapInputs(state); blockTerminated = false; + std::optional relocation; + const auto discardRelocation = [&] { + if (!relocation) + return; + state.safety->footprints.forget(relocation->snapshot.first); + state.safety->footprints.forget(relocation->snapshot.second); + relocation.reset(); + }; for (std::size_t index = 0; index < block.size() && !blockTerminated; ++index) { const CFGElement &element = block[index]; @@ -2256,6 +2416,10 @@ void FunctionDataflow::transfer(const CFGBlock &block, const Stmt *stmt = stmtElement->getStmt(); if (stmt == nullptr) continue; + if (relocation && !relocation->evaluation.contains(stmt)) + discardRelocation(); + if (options.checkContracts && !relocation) + relocation = capturePayloadRelocation(*stmt, state); inUnsafe = unsafeBody || unsafeStmts.contains(stmt); if (options.checkContracts) checkedBefore(*stmt, state); @@ -2267,9 +2431,14 @@ void FunctionDataflow::transfer(const CFGBlock &block, handleReturn(*ret, state); if (options.checkContracts) checkedAfter(*stmt, state); + if (relocation && stmt == relocation->clear) { + applyPayloadRelocation(*relocation, state); + discardRelocation(); + } inUnsafe = unsafeBody; continue; } + discardRelocation(); if (const auto lifetimeEnd = element.getAs()) { // Clang <= 22 also ends parameter lifetimes at every `return` (Clang 23 // gates this behind `AddParameterLifetimes`). Parameters live for the @@ -2280,6 +2449,7 @@ void FunctionDataflow::transfer(const CFGBlock &block, handleLifetimeEnd(*var, locateElement(block, index), state); } } + discardRelocation(); retireHeapInputs(state); } @@ -2479,6 +2649,9 @@ void FunctionDataflow::applyEdge(const CFGBlock &from, unsigned succIndex, return; // Successor 0 is the edge taken when the condition holds. + if (options.checkContracts) + if (const auto *loop = dyn_cast_or_null(from.getTerminatorStmt())) + checkedReaderLoop(*loop, state); applyCondition(*condition, succIndex == 0, /*wrapped=*/false, state); if (options.checkContracts && succIndex == 1 && !edgeInfeasible) if (const auto *loop = dyn_cast_or_null(from.getTerminatorStmt())) @@ -2933,7 +3106,12 @@ void FunctionDataflow::applyOutcomeStores(core::PendingOutcome &narrowed, // RFC 0010, *Per-outcome integer facts*: what the callee wrote holds on // every class still possible, and refutes the guards it contradicts. for (const auto &[place, fact] : narrowed.factsInAll()) { - if (!tracksScalar(place)) + const bool cursor = + state.safety && + std::ranges::any_of(checkedCoordinates, [&](const auto &entry) { + return entry.second == place; + }); + if (!tracksScalar(place) && !cursor) continue; state.scalars.set(place, fact); learnFact(place, fact, state); @@ -2946,6 +3124,7 @@ void FunctionDataflow::applyOutcomeTest(const Expr &operand, std::optional constant) { if (selected.empty()) return; + auto feasible = selected; if (operand.getType()->isIntegerType()) { if (const auto actual = scalarFactOf(operand, state)) { core::ValueFact wanted; @@ -2960,6 +3139,16 @@ void FunctionDataflow::applyOutcomeTest(const Expr &operand, edgeInfeasible = true; return; } + // RFCs 0017/0029: the current numeric value can exclude a sign class + // retained by a conservative call-effect approximation. Apply the same + // trust boundary as branch refutation; the conversion checks below + // still have to succeed before these classes reach pending effects. + const auto read = builder.scalarOperand(operand); + if (!read.place || !places.innermostDeref(read.place->place) || + !memoryContext.empty()) + std::erase_if(feasible, [&](core::Outcome outcome) { + return !actual->classes.contains(outcome); + }); } const auto safeRead = builder.scalarOperand(operand); const Expr *tested = operand.IgnoreParens(); @@ -3039,6 +3228,9 @@ void FunctionDataflow::applyOutcomeTest(const Expr &operand, }; if (const auto *call = dyn_cast(e)) { + // What the call itself already knew about its result (RFC 0029). + const std::optional priorOutcome = + state.safety ? scalarFactOf(*call, state) : std::nullopt; if (state.safety) if (const auto result = numericCallResult(*call)) { core::ValueFact fact; @@ -3055,10 +3247,32 @@ void FunctionDataflow::applyOutcomeTest(const Expr &operand, if (!lastCall || lastCall->call != call) return; core::PendingOutcome narrowed = lastCall->pending; - reinstate(narrowed.select(selected)); + reinstate(narrowed.select(feasible)); applyOutcomeGuards(narrowed, state, reinstate); markNullOutcomes(narrowed, state); applyOutcomeStores(narrowed, state); + // The call is tested directly in this CFG block. Its output-slot + // constructor has no intervening writes or other mutating effects, so + // this edge can materialize the already captured success guarantee. + // Saved integer results need separately invalidated pending evidence. + if (state.safety && freshFootprintSlots.contains(call)) { + if (const auto result = numericCallResult(*call)) { + core::ValueFact possible; + for (const auto &[outcome, targets] : narrowed.consumedBy) { + (void)targets; + possible.classes.insert(outcome); + } + if (!possible.classes.empty()) + (void)state.scalars.narrow(*result, possible); + } + applyContainerPosts(*call, state); + applyFootprintPosts(*call, state, std::nullopt); + } else if (state.safety && options.checkContracts) { + // RFC 0029: this edge immediately tests the call's own result. Install + // only the outcome-specific outputs the call could not yet select. + applyContainerPosts(*call, state, std::nullopt, &priorOutcome); + applyFootprintPosts(*call, state, std::nullopt, &priorOutcome); + } return; } if (!PlaceBuilder::isPlaceExpr(*e)) @@ -3141,7 +3355,7 @@ void FunctionDataflow::applyOutcomeTest(const Expr &operand, // The narrowed entry stays even once nothing more can be retracted: it // records which classes the result can still be in, which a later // `return` of it needs. It goes when the result is reassigned. - const std::vector reinstated = entry->second.select(selected); + const std::vector reinstated = entry->second.select(feasible); reinstate(reinstated); applyOutcomeGuards(entry->second, state, reinstate); markNullOutcomes(entry->second, state); @@ -3245,6 +3459,32 @@ void FunctionDataflow::assignScalar(core::PlaceId place, const Expr *value, if (!llvm::is_contained(cells, image)) cells.push_back(image); } + // May aliases identify values to retire, not cells that receive this + // value. In particular *p and *(p + 1) are different scalar cells even + // though ownership effects deliberately share their element summary. + const auto exact = mirrors(place, state, true); + auto written = checkedScalarMemory(place, state); + const auto *assignment = dyn_cast_or_null(at); + if (state.safety && assignment && assignment->getOpcode() == BO_Assign) + written = checkedLvalue(*assignment->getLHS(), state); + std::set assigned; + std::set disjoint; + for (const auto cell : cells) { + const auto memory = checkedScalarMemory(cell, state); + if (written && memory && written->storage == memory->storage) { + if (checkedAtMost(written->begin, memory->begin, state) && + checkedAtMost(memory->begin, written->begin, state) && + checkedAtMost(written->end, memory->end, state) && + checkedAtMost(memory->end, written->end, state)) + assigned.insert(cell); + else if (checkedAtMost(written->end, memory->begin, state) || + checkedAtMost(memory->end, written->begin, state)) + disjoint.insert(cell); + } else if (llvm::is_contained(exact, cell) && + !(assignment && assignment->getLHS()->HasSideEffects(context))) { + assigned.insert(cell); + } + } std::optional fact; std::optional numeric; if (value != nullptr && tracksScalar(place)) { @@ -3252,6 +3492,8 @@ void FunctionDataflow::assignScalar(core::PlaceId place, const Expr *value, numeric = integerExpressionOf(*value, state); const auto *decl = dyn_cast_or_null(builder.declFor(place)); auto storage = decl ? integerTypeOf(*decl, context) : std::nullopt; + if (!storage && assignment && assignment->getOpcode() == BO_Assign) + storage = integerTypeOf(assignment->getLHS()->getType(), context); if (!storage && places.isElement(place)) if (const auto array = arrayTypes.find(*places.parent(place)); array != arrayTypes.end()) @@ -3290,6 +3532,53 @@ void FunctionDataflow::assignScalar(core::PlaceId place, const Expr *value, same = input; sameOffset = 0; } + if (state.safety && numeric) + if (const auto linear = linearIntegerExpression(*numeric, state); + linear && linear->place && linear->scale == 1 && + !llvm::is_contained(cells, *linear->place)) { + same = linear->place; + sameOffset = linear->constant; + } + // RFC 0029: equal constants seed counter relations; the ordinary CFG join + // and nonwrapping adjustment rules must maintain them on every path. + if (!same && state.safety && fact && numeric && + checkedLoopCounters.contains(place)) + if (const auto exactValue = fact->inType(numeric->type()).constant()) + for (const auto other : checkedLoopCounters) { + if (llvm::is_contained(cells, other)) + continue; + const auto *otherDecl = + dyn_cast_or_null(builder.declFor(other)); + const auto known = state.scalars.factOf(other); + if (known && otherDecl && + integerTypeOf(*otherDecl, context) == numeric->type() && + known->inType(numeric->type()).constant() == exactValue) { + same = other; + break; + } + } + // RFC 0029: resetting an index does not change the range already proved + // for an equal, unchanged count. Capture it before retiring the equality. + if (state.safety && fact && fact->constant && + checkedLoopCounters.contains(place)) { + std::vector> retained; + for (const auto &[pair, edge] : state.relations.all()) { + if (edge.relation != core::Relation::Equal || + (pair.first != place && pair.second != place)) + continue; + const auto other = pair.first == place ? pair.second : pair.first; + if (!checkedLoopCounters.contains(other) || + llvm::is_contained(cells, other)) + continue; + const auto *decl = dyn_cast_or_null(builder.declFor(other)); + const auto type = decl ? integerTypeOf(*decl, context) : std::nullopt; + if (type) + retained.emplace_back(other, core::ValueFact::ofInteger( + integerRangeAt(other, *type, state))); + } + for (const auto &[other, range] : retained) + state.scalars.set(other, range); + } // RFC 0028: a symbolic private-array write may replace a known element. // Capture the RHS first, then discard expressions that still name an old // overlapping cell. Only established disjoint selectors keep their values. @@ -3302,23 +3591,33 @@ void FunctionDataflow::assignScalar(core::PlaceId place, const Expr *value, assignScalar(other, nullptr, state, at); } for (const core::PlaceId cell : cells) { + if (disjoint.contains(cell)) + continue; snapshotArrayIndex(cell, at, state); snapshotIntegerDependencies(cell, at, state); snapshotScalar(cell, at, state); state.dropGuardsOn(cell); state.relations.forget(cell); - if (numeric && !numeric->dependsOn(cell) && tracksScalar(cell)) + state.numericValues.erase(cell); + if (assigned.contains(cell) && numeric && !numeric->dependsOn(cell) && + tracksScalar(cell)) state.numericValues.insert_or_assign(cell, *numeric); - if (fact && tracksScalar(cell)) + if (assigned.contains(cell) && fact && tracksScalar(cell)) state.scalars.set(cell, *fact); else state.scalars.forget(cell); - if (same && tracksScalar(cell)) + if (assigned.contains(cell) && fact && fact->constant && + checkedLoopCounters.contains(cell)) { + state.relations.learnAtLeast(cell, *fact->constant); + state.relations.learnAtMost(cell, *fact->constant); + } + if (assigned.contains(cell) && same && tracksScalar(cell)) state.relations.learn(cell, core::Relation::Equal, *same, sameOffset); state.numericWrites.insert(cell); if (const auto path = builder.summaryPathOf(cell)) writtenScalarPaths.insert(*path); } + checkedSpanCountBounds(state); // RFC 0012, *Sized fields*: a count written beside a pointer field. for (const core::PlaceId cell : cells) noteFieldScalarWrite(cell, at, state); @@ -3491,10 +3790,13 @@ bool FunctionDataflow::pruneGuard(core::PlaceGuard &guard, // Most exported guards are already decided by a scalar fact. RFC 0024's // additional range/alias refinement is needed only for the remainder; // avoid rebuilding ranges and scanning relations on this common path. - if (const auto known = state.factOf(it->first)) { - if (known->disjointFrom(it->second)) + auto actualFact = state.factOf(it->first); + if (!actualFact) + actualFact = containerValueFact(it->first, state); + if (actualFact) { + if (actualFact->disjointFrom(it->second)) return false; - if (known->implies(it->second)) { + if (actualFact->implies(it->second)) { it = guard.conditions.erase(it); continue; } @@ -3639,7 +3941,7 @@ void FunctionDataflow::handleExpr(const Expr &expr, stepPointer(ref->place, step == pointerSteps.end() ? core::PointerOffset::unknown() : step->second, - state); + state, expr); } // `n++`, `n += k`: whatever was known of `n` is gone (RFC 0009), // unless the adjustment itself updates it (RFC 0010). @@ -4733,7 +5035,7 @@ void FunctionDataflow::handleCall(const CallExpr &call, "checked integer output initialization is unresolved"); return; } - if (options.checkContracts && handleRecursiveCleanup(call, state)) + if (options.checkContracts && handleRecursiveContract(call, state)) return; retireHeapInputs(state); // RFC 0012, *`WEAVEC_ASSUME`*: the argument holds from here on, as on the @@ -4763,6 +5065,31 @@ void FunctionDataflow::handleCall(const CallExpr &call, checkDereference(pointer->place, call, state); } const auto effects = classifyCall(call, summaries); + if (options.checkContracts && recording() && !memoryContext.empty()) { + const auto *callee = call.getDirectCallee(); + const auto own = + summaries.recursiveComponents.find(function.getCanonicalDecl()); + const auto peer = + callee ? summaries.recursiveComponents.find(callee->getCanonicalDecl()) + : summaries.recursiveComponents.end(); + const bool recursiveEdge = + callee == nullptr || + callee->getCanonicalDecl() == function.getCanonicalDecl() || + (own != summaries.recursiveComponents.end() && + peer != summaries.recursiveComponents.end() && + own->second == peer->second); + if (recursiveEdge) { + // An actual input case may terminate at a peer's base case. Require its + // separately completed contract; a generic recursive approximation, + // including the fixed point's optimistic initial summary, cannot + // discharge this case's exhaustion marker (RFC 0029). + const auto generic = callee ? summaries.lookup(*callee) : std::nullopt; + const bool completeCase = effects && generic && + effects->summary->checked.complete() && + effects->summary != generic->summary; + checkedCaseReachesRecursion |= !completeCase; + } + } if (!effects) { prepareNumericCall(call, core::FunctionSummary{}, state); if (options.checkContracts) @@ -4873,6 +5200,65 @@ void FunctionDataflow::applySummary(const CallExpr &call, continue; escapeValue(builder.classifyValue(arg), /*deep=*/true, state); } + const auto confinedCopyDestination = [&](const core::SummaryPath &dest) { + if (!state.safety || (!library && !summary.checked.complete()) || + !dest.isParam() || dest.index >= call.getNumArgs() || + dest.steps.size() != 1 || + dest.steps.front().step != core::PathStep::Deref) + return false; + const auto *argument = call.getArg(dest.index)->IgnoreParenCasts(); + const auto *address = dyn_cast(argument); + const auto *reference = + address && address->getOpcode() == UO_AddrOf + ? dyn_cast( + address->getSubExpr()->IgnoreParenImpCasts()) + : nullptr; + const auto *variable = + reference ? dyn_cast(reference->getDecl()) : nullptr; + if (!variable || !variable->hasLocalStorage() || + !variable->getType()->isPointerType() || + variable->getType().isVolatileQualified() || + variable->getType()->isAtomicType()) + return false; + const auto parameter = core::SummaryPath::param(dest.index); + const auto effect = summary.effectOf(parameter); + if (effect.escaped || effect.consumed() || effect.replaced || + std::ranges::any_of( + summary.heap, + [&](const auto &entry) { + return entry.first != dest || entry.second.incomplete || + std::ranges::any_of( + entry.second.fields, [&](const auto &field) { + return field.dest != core::SummaryPath::result() || + field.value.path == parameter; + }); + }) || + std::ranges::any_of( + summary.returns, + [&](const auto &value) { return value.path == parameter; }) || + std::ranges::any_of(summary.stores, [&](const auto &store) { + return store.dest != dest || store.value.path == parameter; + })) + return false; + // RFC 0029: this address is confined to this represented output call. + // Another address use keeps the ordinary conservative escape rule. + std::vector syntax{function.getBody()}; + for (std::size_t i = 0; i < syntax.size(); ++i) { + const auto *statement = syntax[i]; + if (const auto *other = dyn_cast(statement); + other && other != address && other->getOpcode() == UO_AddrOf && + addressedLocal(*other->getSubExpr()) == variable) + return false; + for (const auto *child : statement->children()) { + if (!child) + continue; + if (syntax.size() == 65536) + return false; + syntax.push_back(child); + } + } + return true; + }; // RFC 0010, *Per-outcome stores*: a store the callee performs on some // classes only may be retracted by an outcome test, which then undoes // the source's escape; what it was before the call is remembered here. @@ -4880,11 +5266,15 @@ void FunctionDataflow::applySummary(const CallExpr &call, for (const core::Store &store : summary.stores) { if (store.value.kind != core::ValueSource::Kind::Copy || !store.value.path) continue; + auto guard = builder.translateGuard(store.value.when, call); + if (!guard || !pruneGuard(*guard, state)) + continue; if (const auto ref = builder.resolveSummaryPath(*store.value.path, call)) { if (!summary.storesOn.empty()) copySources.try_emplace(store.dest, ref->place, state.resources.isEscaped(ref->place)); - escape(ref->place, state); + if (!confinedCopyDestination(store.dest)) + escape(ref->place, state); } } @@ -6147,13 +6537,12 @@ void FunctionDataflow::mirrorSubtree(core::PlaceId src, core::PlaceId dest, // every fact recorded about the former must hold for the latter too. After // `dest = &src->f` (RFC 0011, *Mirrors translate field offsets*) what is // below `(*src).f` is below `*dest`; after `dest = container_of(src, T, - // f)` what is below `*src` is below `(*dest).f`. Element and unknown - // offsets stay within the element summary. A copy somewhere `Inside` the - // object points at another sub-object: nothing below `*src` is below - // `*dest` (the alias edge alone carries the shared object). + // f)` what is below `*src` is below `(*dest).f`. Nonzero element and + // unknown offsets do not identify the same pointee cell. Their ordinary + // alias edge retains possible shared storage, without copying must-facts. if (state.incompleteHeap.contains(src)) state.incompleteHeap.insert(dest); - if (offset.isInside()) + if (!offset.isZero() && !offset.isField()) return; core::PlaceId from = places.deref(src); core::PlaceId to = places.deref(dest); @@ -6686,7 +7075,7 @@ void FunctionDataflow::applyPointerAssign(core::PlaceId dest, // Except that after `p = p + 1` the place points one element further // into what it owns (RFC 0008, *Invalid releases*; RFC 0011). if (element.isWhole()) - stepPointer(dest, arms[0].origin->offset, state); + stepPointer(dest, arms[0].origin->offset, state, at); return; } @@ -7202,8 +7591,35 @@ void FunctionDataflow::checkTemporaryBorrow(const PlaceRef &borrowed, // -- Queries ------------------------------------------------------------------ FunctionDataflow::MirrorPlaces -FunctionDataflow::mirrors(core::PlaceId place, - const core::AnalysisState &state) { +FunctionDataflow::scalarMirrors(core::PlaceId place, + const core::AnalysisState &state) { + auto result = mirrors(place, state, true); + const auto memory = checkedScalarMemory(place, state); + for (const auto image : borrowedImages(place, state)) { + if (llvm::is_contained(result, image)) + continue; + const auto other = checkedScalarMemory(image, state); + bool same = memory && other && memory->storage == other->storage && + checkedAtMost(memory->begin, other->begin, state) && + checkedAtMost(other->begin, memory->begin, state) && + checkedAtMost(memory->end, other->end, state) && + checkedAtMost(other->end, memory->end, state); + if (!state.safety) + if (const auto deref = places.innermostDeref(place)) { + const auto holder = *places.parent(*deref); + const auto spatial = spatialRecordAt(holder, state); + same = state.loans.heldBy(holder).size() == 1 && + (!spatial || spatial->offset.isZero()); + } + if (same) + result.push_back(image); + } + return result; +} + +FunctionDataflow::MirrorPlaces +FunctionDataflow::mirrors(core::PlaceId place, const core::AnalysisState &state, + bool definite) { const auto parent = places.parent(place); if (!parent) return {place}; @@ -7214,13 +7630,14 @@ FunctionDataflow::mirrors(core::PlaceId place, result.push_back(id); }; const core::PathStep step = places.step(place); - auto parents = mirrors(*parent, state); + auto parents = mirrors(*parent, state, definite); + const auto &aliases = definite ? state.definiteAliases : state.aliases; // Reuse the existing path and vector when this level has no expansion. // Only dereference steps consult aliases; field/index steps on the same // parent already have their interned identity and declaration (RFC 0020). if (parents.size() == 1 && parents.front() == *parent && (step != core::PathStep::Deref || - state.aliases.viewEdgesFrom(*parent).empty())) { + aliases.viewEdgesFrom(*parent).empty())) { parents.front() = place; return parents; } @@ -7235,8 +7652,7 @@ FunctionDataflow::mirrors(core::PlaceId place, // the mirror deeper than the original and the expansion unbounded, so // they are skipped along with anything past the depth limit. add(parentMirror == *parent ? place : places.deref(parentMirror)); - for (const auto &[alias, edge] : - state.aliases.viewEdgesFrom(parentMirror)) { + for (const auto &[alias, edge] : aliases.viewEdgesFrom(parentMirror)) { if (places.isDescendantOf(alias, parentMirror) || places.depth(alias) >= MaxPlaceDepth) continue; @@ -7249,6 +7665,8 @@ FunctionDataflow::mirrors(core::PlaceId place, // it points at is another sub-object, whose fields are not `*p`'s. if (edge.offset.isInside()) continue; + if (definite && !edge.offset.isZero() && !edge.offset.isField()) + continue; if (edge.offset.isField()) { if (!edge.offset.negative) continue; @@ -8118,7 +8536,8 @@ FunctionDataflow::accessOf(const Expr &lvalue) { return std::nullopt; std::optional inner; const Expr &base = PlaceBuilder::stripTransparent(*member->getBase()); - if (!member->isArrow()) { + if (!member->isArrow() || base.getType()->isArrayType()) { + // Array decay selects element zero: `a->f` is `a[0].f`. inner = accessOf(base); } else if (const auto *addr = dyn_cast(&base); addr != nullptr && addr->getOpcode() == UO_AddrOf) { @@ -10567,7 +10986,9 @@ void FunctionDataflow::finalizeSummary(const core::AnalysisState *exitState) { summaryTimer.emplace(options.stats, "summary:" + functionWorkKey(function)); const auto captureViews = llvm::scope_exit([&] { inferred.objectViews = builder.objectViews; }); - if (summaries.incompleteFunctions.contains(function.getCanonicalDecl())) + if (summaries.incompleteFunctions.contains(function.getCanonicalDecl()) && + (!options.checkContracts || memoryContext.empty() || + !validMemoryContext || checkedCaseReachesRecursion)) reportIncomplete("summary iteration limit reached", *function.getBody()); if (convergenceFailed) reportIncomplete("function dataflow iteration limit reached", diff --git a/lib/Analysis/Dataflow.h b/lib/Analysis/Dataflow.h index 7e26197a..966869b2 100644 --- a/lib/Analysis/Dataflow.h +++ b/lib/Analysis/Dataflow.h @@ -70,12 +70,14 @@ class FunctionDataflow { /// (if enabled) and computes the summary. void run(); core::CallbackBindings callbackBindings; + std::set> recursiveContractCalls; core::CallContext memoryContext; bool validMemoryContext = true; bool checkedOutputSeen = false; bool provingBufferBound = false; // Monotone implementation hint: no proof depends on this flag. bool hasBufferPositions = false; + bool checkedCaseReachesRecursion = false; std::map, core::CheckedRequirements> checkedOutputClasses; std::map, std::set> @@ -88,8 +90,9 @@ class FunctionDataflow { [[nodiscard]] core::FunctionSummary summary() && noexcept { return std::move(inferred); } - [[nodiscard]] bool verifiedRecursiveCleanup() const { - return !recursiveCleanupPremises.empty() && inferred.checked.complete(); + [[nodiscard]] bool verifiedRecursiveContracts() const { + return (!recursiveContractPremises.empty() || recursiveInputUsed) && + inferred.checked.complete(); } private: @@ -115,6 +118,27 @@ class FunctionDataflow { std::map footprintContributions; void initializeContainers(core::AnalysisState &state); std::map containerShapes; + std::set containerPayloadWriters; + std::set containerLinkWriters; + std::map + payloadRelocationClears; + std::map> + payloadRelocationSnapshots; + struct PayloadRelocation { + const clang::BinaryOperator *clear; + std::set evaluation; + core::PlaceId holder; + std::string source; + std::string destination; + std::pair snapshot; + std::map aliases; + std::map separated; + }; + std::optional + capturePayloadRelocation(const clang::Stmt &stmt, core::AnalysisState &state); + void applyPayloadRelocation(const PayloadRelocation &relocation, + core::AnalysisState &state); std::map> opaqueContainerParameters; std::map containerRecords; @@ -132,6 +156,9 @@ class FunctionDataflow { std::map containerOutputPaths; [[nodiscard]] const core::ContainerShape * containerShape(clang::QualType type) const; + [[nodiscard]] std::optional + containerValueFact(core::PlaceId cell, + const core::AnalysisState &state) const; [[nodiscard]] std::optional captureContainer(core::PlaceId dest, const ValueOrigin &origin, core::AnalysisState &state); @@ -160,15 +187,41 @@ class FunctionDataflow { const clang::Stmt &at, core::AnalysisState &state); void invalidateContainers(core::PlaceId holder, bool release, bool keepTail, core::AnalysisState &state); + bool preserveFreshContainersAcrossWrite(core::PlaceId cell, + core::AnalysisState &state); + bool preserveInputContainersAcrossLocalWrite(core::PlaceId storage, + core::AnalysisState &state); void checkedContainersAfterCall(const clang::CallExpr &call, const CallEffects *effects, core::AnalysisState &state); void captureContainerPosts(const clang::CallExpr &call, const core::CheckedContract &contract, core::AnalysisState &state); - void applyContainerPosts(const clang::CallExpr &call, - core::AnalysisState &state, - std::optional result = std::nullopt); + // `prior`: install only outcome-specific outputs that this earlier result + // fact had not yet selected (RFC 0029, immediate result tests). + void + applyContainerPosts(const clang::CallExpr &call, core::AnalysisState &state, + std::optional result = std::nullopt, + const std::optional *prior = nullptr); + struct ContainerPrefix { + core::PlaceId ancestor; + core::ContainerFact fact; + std::pair snapshot; + std::map separated; + }; + std::map>> + containerPrefixPosts; + std::map, + std::pair>> + containerPrefixSnapshots; + void captureContainerPrefixes(const clang::CallExpr &call, + const core::CheckedRequirement &post, + core::AnalysisState &state); + void applyContainerPrefixes(const clang::CallExpr &call, + const core::CheckedRequirement &post, + core::PlaceId holder, core::AnalysisState &state); void containerOutputs(core::CheckedContract &outputs, const core::AnalysisState &state, std::optional returned, @@ -178,6 +231,7 @@ class FunctionDataflow { core::ContainerFact fact; std::optional on; bool fresh = false; + std::map separated; bool operator==(const ContainerPost &) const = default; }; std::map> containerPosts; @@ -191,6 +245,7 @@ class FunctionDataflow { containerArguments; std::map containerReleases; std::map containerPayloadReleases; + std::set containerLocalReleases; std::map containerCallObjects; [[nodiscard]] std::optional captureCallContext(const clang::CallExpr &call, @@ -380,10 +435,15 @@ class FunctionDataflow { std::optional storage; std::optional position; std::optional> footprint; + std::optional pendingAllocationRelease; }; // RFC 0027: formal allocation identities, separate from structural facts. std::map footprintHeads; std::map footprintAtoms; + std::map reallocationFootprints; + void prepareReallocationFootprint(const clang::CallExpr &call, + core::AnalysisState &state); + void refineAllocationFootprints(core::AnalysisState &state); std::map> footprintSnapshots; struct FootprintEntry { @@ -394,15 +454,48 @@ class FunctionDataflow { std::map footprintEntries; std::optional footprintReleased; std::optional footprintAllocated; - std::optional recursiveCleanupCandidate; - std::set recursiveCleanupPremises; - bool handleRecursiveCleanup(const clang::CallExpr &call, - core::AnalysisState &state); - void verifyRecursiveCleanup(); + struct FootprintTransfers { + std::optional outcome; + std::vector> alternatives; + bool operator==(const FootprintTransfers &) const = default; + }; + std::vector footprintTransfers; + void verifyFootprintTransfers(); + std::optional recursiveContractCandidate; + bool recursiveInputUsed = false; + void initializeRecursiveInput(core::AnalysisState &state); + std::shared_ptr + recursiveInputCandidate(const clang::FunctionDecl &callee); + std::shared_ptr + recursiveInputCall(const clang::CallExpr &call, core::AnalysisState &state); + void verifyRecursiveConstruction(); + std::shared_ptr + recursiveExtensionCandidate(const clang::FunctionDecl &callee); + void initializeRecursiveExtension(core::AnalysisState &state); + std::shared_ptr + recursiveExtensionCall(const clang::CallExpr &call, + core::AnalysisState &state); + void verifyRecursiveExtension(); + [[nodiscard]] std::shared_ptr + recursiveWriterCandidate(const clang::FunctionDecl &callee); + void initializeRecursiveWriter(core::AnalysisState &state); + void verifyRecursiveWriter(); + [[nodiscard]] bool + recursiveInputRequirementsCovered(const core::FunctionSummary &candidate); + std::set recursiveContractPremises; + bool handleRecursiveContract(const clang::CallExpr &call, + core::AnalysisState &state); + void verifyRecursiveContract(); std::map> footprintCallInputs; std::map> footprintPosts; + std::map, core::PlaceId> + footprintExtensions; + std::map freshFootprintSlots; + std::map> + freshFootprintSlotParents; core::PlaceId footprintHead(core::PlaceId holder); core::PlaceId footprintAtom(core::PlaceId storage); void initializeFootprint(core::PlaceId holder, const core::SummaryPath &path, @@ -423,9 +516,10 @@ class FunctionDataflow { void captureFootprintPosts(const clang::CallExpr &call, const core::CheckedContract &contract, core::AnalysisState &state); - void applyFootprintPosts(const clang::CallExpr &call, - core::AnalysisState &state, - std::optional result); + void + applyFootprintPosts(const clang::CallExpr &call, core::AnalysisState &state, + std::optional result, + const std::optional *prior = nullptr); [[nodiscard]] std::optional establishContainer(const CheckedMemory &memory, const core::ContainerShape &shape, @@ -463,7 +557,8 @@ class FunctionDataflow { std::optional discoverBufferShape(const clang::RecordDecl &record); void discoverBuffers(); - void registerBuffer(core::PlaceId object, const clang::RecordDecl &record); + void registerBuffer(core::PlaceId object, const clang::RecordDecl &record, + const core::BufferShape *transported = nullptr); void initializeBuffers(core::AnalysisState &state); void normalizeBuffers(core::AnalysisState &state); void materializeBuffers(core::AnalysisState &state); @@ -490,6 +585,15 @@ class FunctionDataflow { std::optional outcome, std::optional returned); void initializeChecked(); + void initializeCheckedSpans(core::AnalysisState &state); + void checkedSpanCountBounds(core::AnalysisState &state); + void checkedSpanOutputs(core::CheckedContract &outputs, + const core::AnalysisState &state, + const clang::Expr *value, + std::optional outcome); + std::map, core::PlaceId> checkedSpans; + bool checkedSpanCall(const core::CheckedRequirement &requirement, + const clang::CallExpr &call, core::AnalysisState &state); void discoverCheckedCases(); [[nodiscard]] std::string checkedUnionMember(const clang::FieldDecl &field); [[nodiscard]] core::PlaceId @@ -609,12 +713,24 @@ class FunctionDataflow { std::map checkedCoordinates; std::map checkedPointerResults; std::set checkedCallAssignedPointers; + /// RFC 0029: pointer variables this body may change, from one bounded + /// syntactic scan. A null entry means the budget was exhausted, which + /// conservatively disqualifies every variable. + std::optional> changedPointerVariables; + [[nodiscard]] bool unchangedPointerVariable(core::PlaceId place); [[nodiscard]] std::optional checkedMemory(const clang::Expr &pointer, const core::Affine &begin, const core::Affine &end, const core::AnalysisState &state); [[nodiscard]] std::optional checkedMemoryAt(core::PlaceId holder, const core::Affine &begin, const core::Affine &end, const core::AnalysisState &state); + [[nodiscard]] bool checkedZeroInteger(core::PlaceId place, + const core::AnalysisState &state); + [[nodiscard]] std::optional + checkedScalarMemory(core::PlaceId place, const core::AnalysisState &state); + [[nodiscard]] std::optional + checkedSeparationInput(const CheckedMemory &memory, + const core::AnalysisState &state); [[nodiscard]] std::optional checkedPathMemory(const core::SummaryPath &path, const clang::CallExpr &call, const core::Affine &begin, const core::Affine &end, @@ -645,10 +761,21 @@ class FunctionDataflow { core::AnalysisState &state); void checkedStringUse(const CheckedMemory &memory, const clang::Stmt &at, core::AnalysisState &state); + void checkedStringLength(const clang::CallExpr &call, + core::AnalysisState &state); void checkedStringWrite(const std::optional &memory, - bool zeroed, core::AnalysisState &state); + bool zeroed, core::AnalysisState &state, + bool numericText = false); [[nodiscard]] std::optional checkedWitness(const CheckedMemory &memory, const core::AnalysisState &state); + [[nodiscard]] std::optional> + checkedByteContents(const CheckedMemory &memory, + const core::AnalysisState &state); + [[nodiscard]] std::optional + checkedByteRange(const clang::Expr &expr, const core::AnalysisState &state); + [[nodiscard]] std::vector> + checkedJoinBoundaries(const core::AnalysisState &first, + const core::AnalysisState &second); [[nodiscard]] std::optional checkedTerminatorQuantity(const core::PathAffine &value, const clang::CallExpr &call, @@ -656,6 +783,7 @@ class FunctionDataflow { std::set checkedStringInputs; std::map checkedTerminatorInputs; std::map checkedWitnessMinimum; + std::map checkedFirstZeros; [[nodiscard]] std::optional checkedLvalue(const clang::Expr &expr, const core::AnalysisState &state); [[nodiscard]] bool checkedInterval(const core::Affine &begin, @@ -686,6 +814,15 @@ class FunctionDataflow { core::AnalysisState &state); [[nodiscard]] bool checkedInitialized(const CheckedMemory &memory, const core::AnalysisState &state); + bool checkedNumericByte(const clang::Expr &value, + const core::AnalysisState &state); + bool checkedNumericText(const CheckedMemory &memory, + const core::AnalysisState &state); + bool checkedFloatingValue(const clang::Expr &value, + const core::AnalysisState &state); + void checkedFloatingAfter(const clang::Stmt &stmt, + core::AnalysisState &state); + std::map checkedFloatingResults; bool checkedRequire(core::CheckedRequirementKind kind, const CheckedMemory &memory, const clang::Stmt &at, core::AnalysisState &state, std::string family = {}); @@ -693,7 +830,19 @@ class FunctionDataflow { checkedLoopRequirement(const core::Affine &need, const clang::Stmt &at, core::AnalysisState &state); void checkedLoopExit(const clang::ForStmt &loop, core::AnalysisState &state); + void checkedReaderLoop(const clang::Stmt &at, core::AnalysisState &state); std::map checkedLoops; + std::set checkedLoopConditions; + std::set checkedLoopCounters; + struct CheckedReaderLoop { + const clang::Expr *index = nullptr; + const clang::Expr *position = nullptr; + const clang::Expr *capacity = nullptr; + core::PlaceId backing; + const clang::Expr *numericInput = nullptr; + }; + std::map> + checkedReaderLoops; // Lazy caller-entry guard shared by one conditional requirement group. // The group restores this pointer before its local cache leaves scope. std::optional *checkedRequirementGuard = nullptr; @@ -716,6 +865,7 @@ class FunctionDataflow { // Default preserves existing designated initializers. // NOLINTNEXTLINE(readability-redundant-member-init) std::string unionMember = {}; + bool throughPosition = false; friend bool operator==(const CheckedPost &, const CheckedPost &) = default; }; std::map> checkedPosts; @@ -830,6 +980,9 @@ class FunctionDataflow { std::optional> integerBounds(core::PlaceId place, const core::AnalysisState &state); using NumericExpression = core::IntegerExpression; + std::map>> + checkedSpanPosts; [[nodiscard]] std::optional checkedTraversalSum(const NumericExpression &expression, const core::AnalysisState &state); @@ -901,6 +1054,13 @@ class FunctionDataflow { originTargets(const ValueOrigin &origin, const core::AnalysisState &state); [[nodiscard]] std::optional resolveCall(const clang::CallExpr &call); + [[nodiscard]] std::shared_ptr + requiredCallback(const clang::CallExpr &call, core::AnalysisState &state); + [[nodiscard]] std::optional + callbackEntry(core::PlaceId holder, const core::AnalysisState &state); + void checkedCallbackRequirement(const core::CheckedRequirement &requirement, + const clang::CallExpr &call, + core::AnalysisState &state); [[nodiscard]] static std::string objectEvidenceView(core::PlaceId holder, const core::AnalysisState &state); [[nodiscard]] bool validateObjectPath(const core::SummaryPath &path, @@ -1305,7 +1465,7 @@ class FunctionDataflow { unsigned depth = 0); [[nodiscard]] core::IntegerRange integerRangeAt(core::PlaceId place, core::IntegerType type, - const core::AnalysisState &state); + const core::AnalysisState &state, unsigned equalityDepth = 0); [[nodiscard]] bool preservesInteger(const clang::Expr &expr, const core::AnalysisState &state); void checkIntegerOperation(const clang::Expr &expr, @@ -1319,6 +1479,8 @@ class FunctionDataflow { /// storage of a local or parameter, or memory behind a pointer; not a /// global (any callee may write it) or an array element. [[nodiscard]] bool tracksScalar(core::PlaceId place) const; + void stepPointer(core::PlaceId place, const core::PointerOffset &step, + core::AnalysisState &state, const clang::Expr &at); /// The storage a write to `place` lands in when `place` is below a /// pointer that borrows a local (`q->n` with `q = &s` is `s.n`). [[nodiscard]] std::vector @@ -2102,7 +2264,10 @@ class FunctionDataflow { core::ElementWitness element, const core::AnalysisState &state); [[nodiscard]] MirrorPlaces mirrors(core::PlaceId place, - const core::AnalysisState &state); + const core::AnalysisState &state, + bool definite = false); + [[nodiscard]] MirrorPlaces scalarMirrors(core::PlaceId place, + const core::AnalysisState &state); /// `place` together with its ancestors and descendants. [[nodiscard]] std::vector related(core::PlaceId place); diff --git a/lib/Analysis/DataflowArrays.cpp b/lib/Analysis/DataflowArrays.cpp index 48d1699f..43b03912 100644 --- a/lib/Analysis/DataflowArrays.cpp +++ b/lib/Analysis/DataflowArrays.cpp @@ -8,6 +8,7 @@ #include "AffineSupport.h" #include "Dataflow.h" +#include "IntegerSupport.h" #include "weavec/Core/Array.h" #include "clang/AST/Type.h" @@ -101,11 +102,19 @@ PlaceRef FunctionDataflow::selectArrayElement(PlaceRef storage, std::optional index, QualType type, const Expr &at) { if (!hasPointerCells(type)) { - // RFC 0028: exact cells of private integer arrays use the same bounded - // selectors as pointer cells. General numeric array inference is separate. + // RFCs 0028/0029: private integer arrays and small concrete automatic + // arrays use exact cells. This does not enumerate a runtime-sized array. const auto *root = builder.varForPlace(places.root(storage.place)); - if (type.isNull() || !type->isIntegerType() || !root || - !root->hasGlobalStorage() || root->isExternallyVisible() || + const auto *array = + root ? context.getAsConstantArrayType(root->getType()) : nullptr; + const bool automatic = + root != nullptr && root->hasLocalStorage() && array != nullptr && + array->getSize().getLimitedValue(core::MaxArrayCells + 1) <= + core::MaxArrayCells; + if (type.isNull() || !type->isIntegerType() || type.isVolatileQualified() || + type->isAtomicType() || !root || + (!automatic && + (!root->hasGlobalStorage() || root->isExternallyVisible())) || places.innermostDeref(storage.place) || !tracksScalar(places.root(storage.place))) return storage; @@ -390,17 +399,76 @@ void FunctionDataflow::initializeArray(core::PlaceId storage, QualType type, core::AnalysisState &state, bool zeroInitialize) { const auto *array = context.getAsConstantArrayType(type); - if (!array || !hasPointerCells(array->getElementType())) + if (!array) + return; + if (state.safety && + (decl.hasLocalStorage() || + (decl.isStaticLocal() && array->getElementType().isConstQualified())) && + init && array->getElementType()->isCharType() && + !array->getElementType().isVolatileQualified() && + context.getCharWidth() == 8 && + array->getSize().getLimitedValue(65) <= 64) { + const auto size = array->getSize().getZExtValue(); + const auto *literal = dyn_cast(init->IgnoreParenImpCasts()); + const auto *values = dyn_cast(init->IgnoreParenImpCasts()); + std::string bytes; + bool known = literal != nullptr || values != nullptr; + for (std::uint64_t i = 0; known && i < size; ++i) { + std::uint64_t byte = 0; + if (literal && i < literal->getLength()) { + byte = literal->getCodeUnit(static_cast(i)); + } else if (values && i < values->getNumInits()) { + const auto value = + integerRangeOf(*values->getInit(static_cast(i)), state); + const auto exact = value && !value->mayBeInvalid + ? value->values.constant() + : std::nullopt; + known = exact.has_value(); + if (exact) + byte = exact->bits; + } + bytes.push_back(static_cast(byte & 255U)); + } + if (known && !bytes.empty()) + state.safety->initialize( + storage, + {.begin = {}, + .end = core::Affine::ofConstant(static_cast(size)), + .bytes = std::move(bytes), + .immutableBytes = array->getElementType().isConstQualified()}); + } + const bool scalar = + decl.hasLocalStorage() && array->getElementType()->isIntegerType() && + !array->getElementType().isVolatileQualified() && + !array->getElementType()->isAtomicType() && + array->getSize().getLimitedValue(core::MaxArrayCells + 1) <= + core::MaxArrayCells; + if (!scalar && !hasPointerCells(array->getElementType())) return; const auto count = array->getSize().getLimitedValue(core::MaxArrayCells + 1); const auto summary = places.index(storage); arrayTypes[summary] = array->getElementType(); const auto *list = init ? dyn_cast(init->IgnoreParenImpCasts()) : nullptr; + const auto *literal = + scalar && init ? dyn_cast(init->IgnoreParenImpCasts()) + : nullptr; const auto limit = std::min(count, static_cast(core::MaxArrayCells)); for (std::uint64_t i = 0; i < limit; ++i) { const auto cell = places.element(summary, std::to_string(i)); + if (literal) { + const auto integer = integerTypeOf(array->getElementType(), context); + if (integer) + state.scalars.set( + cell, core::ValueFact::ofInteger( + core::IntegerRange::singleton(core::IntegerValue::ofBits( + *integer, + i < literal->getLength() + ? literal->getCodeUnit(static_cast(i)) + : 0)))); + continue; + } const Expr *value = list && i < list->getNumInits() ? list->getInit(static_cast(i)) : nullptr; diff --git a/lib/Analysis/DataflowBufferContracts.cpp b/lib/Analysis/DataflowBufferContracts.cpp index 60e5378a..e6ea18a2 100644 --- a/lib/Analysis/DataflowBufferContracts.cpp +++ b/lib/Analysis/DataflowBufferContracts.cpp @@ -14,17 +14,25 @@ FunctionDataflow::bufferArgument(const core::CheckedRequirement &requirement, const CallExpr &call, core::AnalysisState &state) { const auto shape = core::BufferShape::decode(requirement.family); - const auto object = builder.resolveSummaryPath(requirement.path, call, true); + const auto object = builder.resolveSummaryPath(requirement.path, call); if (!shape || !object || !object->element.isWhole()) return std::nullopt; + const bool registered = bufferObjects.contains(object->place); if (const auto *decl = dyn_cast_or_null(builder.declFor(object->place))) { auto type = decl->getType(); if (type->isPointerType()) type = type->getPointeeType(); if (const auto *record = type->getAsRecordDecl()) - registerBuffer(object->place, *record); + registerBuffer(object->place, *record, &*shape); } + if (places.isElement(object->place)) + if (const auto storage = places.parent(object->place)) { + const auto type = arrayElementType(*storage); + if (!type.isNull()) + if (const auto *record = type->getAsRecordDecl()) + registerBuffer(object->place, *record, &*shape); + } if (!bufferObjects.contains(object->place) && places.step(object->place) == core::PathStep::Deref) if (const auto pointer = places.parent(object->place)) { @@ -33,8 +41,7 @@ FunctionDataflow::bufferArgument(const core::CheckedRequirement &requirement, const auto type = summaries.interfaceType(evidence); if (!type.isNull()) if (const auto *record = type->getAsRecordDecl()) { - registerBuffer(object->place, *record); - normalizeBuffers(state); + registerBuffer(object->place, *record, &*shape); } } } @@ -42,6 +49,8 @@ FunctionDataflow::bufferArgument(const core::CheckedRequirement &requirement, if (found == bufferObjects.end() || !found->second.sameLayoutAs(*shape)) { return std::nullopt; } + if (!registered) + normalizeBuffers(state); return object->place; } @@ -248,7 +257,7 @@ void FunctionDataflow::applyBufferPosts(const CallExpr &call, const auto type = summaries.interfaceType(shape.object.identity); if (!object || type.isNull() || !type->isRecordType()) continue; - registerBuffer(*object, *type->getAsRecordDecl()); + registerBuffer(*object, *type->getAsRecordDecl(), &shape); const auto layout = bufferObjects.find(*object); if (layout == bufferObjects.end() || !layout->second.sameLayoutAs(shape)) continue; @@ -258,8 +267,14 @@ void FunctionDataflow::applyBufferPosts(const CallExpr &call, if (!object) continue; const auto data = places.field(*object, shape.data.name); - if (const auto *previous = bufferFact(data, state)) + std::optional entryBacking; + if (const auto *previous = bufferFact(data, state)) { shape.ownsBacking |= previous->shape.ownsBacking; + entryBacking = previous->entryBacking; + } else if (const auto backing = state.safety->buffers.storage.find(data); + backing != state.safety->buffers.storage.end()) { + entryBacking = backing->second.entryBacking; + } const auto fact = scalarFactOf(call, state); const bool active = !post.on || (fact && fact->implies(core::ValueFact::of(*post.on))); @@ -294,7 +309,8 @@ void FunctionDataflow::applyBufferPosts(const CallExpr &call, .object = *object, .length = places.field(*object, shape.length.name), .capacity = places.field(*object, shape.capacity.name), - .initialized = true}); + .initialized = true, + .entryBacking = entryBacking}); } else if (post.on) { if (const auto selector = result ? result : numericCallResult(call)) { core::BufferPost guarantee{ @@ -302,7 +318,8 @@ void FunctionDataflow::applyBufferPosts(const CallExpr &call, .object = *object, .length = places.field(*object, shape.length.name), .capacity = places.field(*object, shape.capacity.name), - .initialized = true}, + .initialized = true, + .entryBacking = entryBacking}, .when = {}}; guarantee.when.require(*selector, core::ValueFact::of(*post.on)); auto &entries = state.safety->buffers.pending[data]; diff --git a/lib/Analysis/DataflowBufferDiscovery.cpp b/lib/Analysis/DataflowBufferDiscovery.cpp new file mode 100644 index 00000000..32d7505a --- /dev/null +++ b/lib/Analysis/DataflowBufferDiscovery.cpp @@ -0,0 +1,269 @@ +//===- DataflowBufferDiscovery.cpp - State roles (RFC 0029) --------------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "AffineSupport.h" +#include "Dataflow.h" +#include "IntegerSupport.h" + +#include "clang/AST/RecordLayout.h" +#include "clang/Basic/Version.h" + +#include +#include + +using namespace clang; +namespace weavec::analysis { +std::optional +FunctionDataflow::discoverBufferShape(const RecordDecl &record) { + return summaries.bufferShape( + record, [&]() -> std::optional { + if (!record.isCompleteDefinition() || record.isUnion()) + return std::nullopt; + std::vector pointers; + std::vector counts; + for (const auto *field : record.fields()) { + const auto type = field->getType(); + if (field->isBitField() || field->getName().empty() || + type.isVolatileQualified() || type->isAtomicType()) + return std::nullopt; + if (type->isPointerType() && !type->isFunctionPointerType()) { + const auto element = type->getPointeeType(); + if (element->isScalarType() && !element.isVolatileQualified() && + !element->isAtomicType() && + (!element.isConstQualified() || element->isCharType())) + pointers.push_back(field); + } else if (type->isUnsignedIntegerType() && !type->isBooleanType()) { + counts.push_back(field); + } + } + if (pointers.empty() || pointers.size() > 16 || counts.size() < 2 || + counts.size() > 16) + return std::nullopt; + std::vector usage; + for (const auto *decl : context.getTranslationUnitDecl()->decls()) + if (const auto *definition = dyn_cast(decl); + definition && definition->doesThisDeclarationHaveABody()) + usage.push_back(definition->getBody()); + for (std::size_t i = 0; i < usage.size(); ++i) { + if (usage.size() > 65536) + return std::nullopt; + if (usage[i]) + for (const auto *child : usage[i]->children()) { + if (usage.size() == 65536) + return std::nullopt; + usage.push_back(child); + } + } + const FieldDecl *data = pointers.front(); + if (pointers.size() > 1) { + std::map uses; + for (const auto *statement : usage) { + const Expr *base = nullptr; + if (const auto *index = + dyn_cast_or_null(statement)) + base = index->getBase(); + if (const auto *operation = + dyn_cast_or_null(statement); + operation && operation->getOpcode() == BO_Add && + operation->getLHS()->getType()->isPointerType()) + base = operation->getLHS(); + const auto *member = + base ? dyn_cast(base->IgnoreParenImpCasts()) + : nullptr; + if (member) + for (const auto *pointer : pointers) + if (member->getMemberDecl() == pointer) + ++uses[pointer]; + } + unsigned score = 0; + data = nullptr; + for (const auto &[pointer, count] : uses) + if (count > score) { + data = pointer; + score = count; + } else if (count == score) { + data = nullptr; + } + if (!data) + return std::nullopt; + } + const auto element = data->getType()->getPointeeType(); + const auto unit = byteSizeOf(element, context); + if (!unit || *unit <= 0 || element.isVolatileQualified() || + element->isAtomicType() || + (element.isConstQualified() && !element->isCharType()) || + !element->isScalarType() || element->isFunctionType()) + return std::nullopt; +#if CLANG_VERSION_MAJOR >= 23 + const auto type = context.getCanonicalTagType(&record); +#else + const auto type = context.getRecordType(&record); +#endif + const auto objectType = + core::ObjectType::parse(checkedObjectType(type)); + if (!objectType) + return std::nullopt; + const auto field = [&](const FieldDecl &decl) { + return core::ContainerField{ + .name = decl.getNameAsString(), + .offset = context.getASTRecordLayout(&record).getFieldOffset( + decl.getFieldIndex()) / + context.getCharWidth(), + .bytes = static_cast( + context.getTypeSizeInChars(decl.getType()).getQuantity())}; + }; + // Look for the count used to select cells of this record's data field. + // Discovery is shared across the TU's definitions so constructors and + // reserve helpers agree with indexing helpers. This nominates a + // relation; callers still have to establish its physical and + // initialized storage. + std::vector indexed(counts.size()); + std::vector capacityUses(counts.size()); + std::set> compared; + std::set localIndices; + std::set> indexBounds; + bool indexedData = false; + const auto counter = [&](const Expr *expr) -> std::optional { + const auto *member = + expr ? dyn_cast(expr->IgnoreParenImpCasts()) + : nullptr; + if (!member) + return std::nullopt; + for (unsigned c = 0; c < counts.size(); ++c) + if (member->getMemberDecl() == counts[c]) + return c; + return std::nullopt; + }; + const auto comparedCounter = [&](const Expr *expr) { + if (const auto direct = counter(expr)) + return direct; + const auto *sum = + dyn_cast(expr->IgnoreParenImpCasts()); + if (!sum || + (sum->getOpcode() != BO_Add && sum->getOpcode() != BO_Sub)) + return std::optional{}; + const auto left = counter(sum->getLHS()); + const auto right = counter(sum->getRHS()); + // An indexed cursor plus a local index or constant still nominates + // the extent it is compared with. Two field counters are ambiguous. + if (left.has_value() == right.has_value()) + return std::optional{}; + return left ? left : right; + }; + const auto indexCounter = [&](const Expr *expr) { + if (const auto *unary = + dyn_cast(expr->IgnoreParenImpCasts())) + expr = unary->getSubExpr(); + if (const auto c = counter(expr)) { + ++indexed[*c]; + return; + } + if (const auto *index = + dyn_cast(expr->IgnoreParenImpCasts())) + localIndices.insert(index->getDecl()); + if (const auto *operation = + dyn_cast(expr->IgnoreParenImpCasts()); + operation && (operation->getOpcode() == BO_Add || + operation->getOpcode() == BO_Sub)) { + if (const auto c = counter(operation->getLHS())) + ++indexed[*c]; + if (const auto c = counter(operation->getRHS())) + ++indexed[*c]; + } + }; + for (std::size_t i = 0; i < usage.size() && usage.size() <= 65536; + ++i) { + if (!usage[i]) + continue; + if (const auto *index = dyn_cast(usage[i])) { + const auto *base = + dyn_cast(index->getBase()->IgnoreParenImpCasts()); + if (base && base->getMemberDecl() == data) { + indexedData = true; + indexCounter(index->getIdx()); + } + } + if (const auto *operation = dyn_cast(usage[i])) { + const auto *base = dyn_cast( + operation->getLHS()->IgnoreParenImpCasts()); + if (operation->getOpcode() == BO_Add && base && + base->getMemberDecl() == data) { + indexedData = true; + indexCounter(operation->getRHS()); + } + if (operation->isComparisonOp()) { + const auto left = comparedCounter(operation->getLHS()); + const auto right = comparedCounter(operation->getRHS()); + if (left || right) { + const auto *bound = dyn_cast( + (left ? operation->getRHS() : operation->getLHS()) + ->IgnoreParenImpCasts()); + if (bound) + indexBounds.emplace(bound->getDecl(), + left.value_or(right.value_or(0))); + } + if (left && right && *left != *right) { + compared.emplace(*left, *right); + compared.emplace(*right, *left); + } else if (left || right) { + ++capacityUses[left.value_or(right.value_or(0))]; + } + } + } + } + if (usage.size() > 65536) + return std::nullopt; + for (const auto &[index, count] : indexBounds) + if (localIndices.contains(index)) + ++indexed[count]; + // Discovery only proposes a sufficient predicate. Its relation still + // has to hold at every caller and after every mutation. An unrelated + // depth/generation field is not a capacity merely because it follows + // the logical length in the declaration. + const auto uniqueMaximum = + [](const std::vector &scores) -> std::optional { + const auto best = std::ranges::max_element(scores); + if (best == scores.end() || *best == 0 || + std::ranges::count(scores, *best) != 1) + return std::nullopt; + return static_cast(best - scores.begin()); + }; + auto length = uniqueMaximum(indexed); + std::optional capacity; + if (length) { + std::vector candidates(counts.size()); + for (unsigned c = 0; c < counts.size(); ++c) + if (c != *length && compared.contains({*length, c})) + candidates[c] = 1 + capacityUses[c]; + capacity = uniqueMaximum(candidates); + if (!capacity && counts.size() == 2 && !element.isConstQualified()) + capacity = 1 - *length; + } else if (counts.size() == 2 && indexedData && + !element.isConstQualified()) { + // With a backing access but no distinguishing counter operation, + // retain one deterministic two-counter candidate. A scalar payload + // beside unrelated counters does not nominate a buffer at all. + // The caller must still establish the candidate's actual bounds + // and initialized prefix. + length = 0; + capacity = 1; + } + if (!length || !capacity) + return std::nullopt; + core::BufferShape shape{.object = *objectType, + .data = field(*data), + .length = field(*counts[*length]), + .capacity = field(*counts[*capacity]), + .elementBytes = + static_cast(*unit), + .pointerElements = element->isPointerType(), + .reader = element.isConstQualified()}; + return shape.valid() ? std::optional(shape) : std::nullopt; + }); +} + +} // namespace weavec::analysis diff --git a/lib/Analysis/DataflowBuffers.cpp b/lib/Analysis/DataflowBuffers.cpp index a4e92dc8..183ad2b9 100644 --- a/lib/Analysis/DataflowBuffers.cpp +++ b/lib/Analysis/DataflowBuffers.cpp @@ -18,98 +18,9 @@ using namespace clang; namespace weavec::analysis { -std::optional -FunctionDataflow::discoverBufferShape(const RecordDecl &record) { - return summaries.bufferShape( - record, [&]() -> std::optional { - if (!record.isCompleteDefinition() || record.isUnion()) - return std::nullopt; - const FieldDecl *data = nullptr; - std::vector counts; - for (const auto *field : record.fields()) { - const auto type = field->getType(); - if (field->isBitField() || field->getName().empty() || - type.isVolatileQualified() || type->isAtomicType()) - return std::nullopt; - if (type->isPointerType() && !type->isFunctionPointerType()) { - if (data) - return std::nullopt; - data = field; - } else if (type->isUnsignedIntegerType() && !type->isBooleanType()) { - counts.push_back(field); - } - } - if (!data || counts.size() != 2) - return std::nullopt; - const auto element = data->getType()->getPointeeType(); - const auto unit = byteSizeOf(element, context); - if (!unit || *unit <= 0 || !element->isScalarType() || - element->isFunctionType()) - return std::nullopt; -#if CLANG_VERSION_MAJOR >= 23 - const auto type = context.getCanonicalTagType(&record); -#else - const auto type = context.getRecordType(&record); -#endif - const auto objectType = - core::ObjectType::parse(checkedObjectType(type)); - if (!objectType) - return std::nullopt; - const auto field = [&](const FieldDecl &decl) { - return core::ContainerField{ - .name = decl.getNameAsString(), - .offset = context.getASTRecordLayout(&record).getFieldOffset( - decl.getFieldIndex()) / - context.getCharWidth(), - .bytes = static_cast( - context.getTypeSizeInChars(decl.getType()).getQuantity())}; - }; - // Look for the count used to select cells of this record's data field. - // Discovery is shared across the TU's definitions so constructors and - // reserve helpers agree with indexing helpers. This nominates a - // relation; callers still have to establish its physical and - // initialized storage. - std::array indexed{}; - std::vector usage; - for (const auto *decl : context.getTranslationUnitDecl()->decls()) - if (const auto *definition = dyn_cast(decl); - definition && definition->doesThisDeclarationHaveABody()) - usage.push_back(definition->getBody()); - for (std::size_t i = 0; i < usage.size() && usage.size() <= 65536; - ++i) { - if (!usage[i]) - continue; - if (const auto *index = dyn_cast(usage[i])) { - const auto *base = - dyn_cast(index->getBase()->IgnoreParenImpCasts()); - if (base && base->getMemberDecl() == data) { - const Expr *selector = index->getIdx()->IgnoreParenImpCasts(); - if (const auto *adjustment = dyn_cast(selector)) - selector = adjustment->getSubExpr()->IgnoreParenImpCasts(); - if (const auto *member = dyn_cast(selector)) - for (unsigned c = 0; c < 2; ++c) - if (member->getMemberDecl() == counts[c]) - ++indexed.at(c); - } - } - for (const auto *child : usage[i]->children()) - usage.push_back(child); - } - if (indexed[1] > indexed[0]) - std::swap(counts[0], counts[1]); - core::BufferShape shape{.object = *objectType, - .data = field(*data), - .length = field(*counts[0]), - .capacity = field(*counts[1]), - .elementBytes = - static_cast(*unit), - .pointerElements = element->isPointerType()}; - return shape.valid() ? std::optional(shape) : std::nullopt; - }); -} - void FunctionDataflow::registerBuffer(core::PlaceId object, - const RecordDecl &record) { + const RecordDecl &record, + const core::BufferShape *transported) { if (bufferObjects.contains(object)) return; if (!record.isCompleteDefinition() || record.isUnion()) @@ -126,7 +37,62 @@ void FunctionDataflow::registerBuffer(core::PlaceId object, inferred.incomplete.insert("buffer shape limit reached"); return; } - const auto shape = discoverBufferShape(record); + auto shape = discoverBufferShape(record); + // RFC 0029: a separate-source caller need not repeat the callee's indexing + // operations to discover field roles. Import only its layout candidate, + // validate it against the actual C type, and prove current values below. + if (!shape && transported && transported->valid()) { +#if CLANG_VERSION_MAJOR >= 23 + const auto type = context.getCanonicalTagType(&record); +#else + const auto type = context.getRecordType(&record); +#endif + bool valid = checkedObjectType(type) == transported->object.toString(); + unsigned matched = 0; + for (const auto *field : record.fields()) { + const core::ContainerField *descriptor = nullptr; + for (const auto *candidate : + {&transported->data, &transported->length, &transported->capacity}) + if (field->getName() == candidate->name) + descriptor = candidate; + if (!descriptor) + continue; + ++matched; + const auto fieldType = field->getType(); + valid &= !field->isBitField() && !fieldType.isVolatileQualified() && + !fieldType->isAtomicType() && + context.getASTRecordLayout(&record).getFieldOffset( + field->getFieldIndex()) / + context.getCharWidth() == + descriptor->offset && + context.getTypeSizeInChars(fieldType).getQuantity() == + static_cast(descriptor->bytes); + if (descriptor == &transported->data) { + valid &= + fieldType->isPointerType() && !fieldType->isFunctionPointerType(); + if (fieldType->isPointerType()) { + const auto element = fieldType->getPointeeType(); + valid &= element->isScalarType() && + !element.isVolatileQualified() && + !element->isAtomicType() && + element.isConstQualified() == transported->reader && + (!transported->reader || element->isCharType()) && + byteSizeOf(element, context) == + static_cast(transported->elementBytes) && + element->isPointerType() == transported->pointerElements; + } + } else { + valid &= + fieldType->isUnsignedIntegerType() && !fieldType->isBooleanType(); + } + } + if (valid && matched == 3) { + shape = *transported; + shape->ownsBacking = false; + shape->ownsElements = false; + shape->terminated = false; + } + } if (!shape) return; found = bufferShapes.emplace(&record, *shape).first; @@ -189,6 +155,40 @@ void FunctionDataflow::discoverBuffers() { for (const auto *child : work[i]->children()) work.push_back(child); } + // A separate-source forwarding helper may contain no indexing itself. + // Import a callee's layout candidate before entry initialization so + // its sufficient predicate can be forwarded through the helper. Actual + // C layout validation still happens in registerBuffer; this grants no + // facts about a local object or any post-call state. Generic incompleteness + // does not invalidate a layout candidate or supply a proof of the call. + for (const auto *statement : work) { + const auto *call = dyn_cast_or_null(statement); + const auto *callee = call ? call->getDirectCallee() : nullptr; + const auto summary = callee ? summaries.lookup(*callee) : std::nullopt; + if (!summary) + continue; + for (const auto &requirement : summary->summary->checked.requirements) { + if (requirement.kind != core::CheckedRequirementKind::Buffer) + continue; + const auto shape = core::BufferShape::decode(requirement.family); + const auto ref = + builder.resolveSummaryPath(requirement.path, *call, true); + if (!shape || !ref || !ref->element.isWhole()) + continue; + const auto *decl = + dyn_cast_or_null(builder.declFor(ref->place)); + if (!decl && places.step(ref->place) == core::PathStep::Deref) + if (const auto pointer = places.parent(ref->place)) + decl = dyn_cast_or_null(builder.declFor(*pointer)); + if (!decl) + continue; + const auto type = decl->getType()->isPointerType() + ? decl->getType()->getPointeeType() + : decl->getType(); + if (const auto *record = type->getAsRecordDecl()) + registerBuffer(ref->place, *record, &*shape); + } + } } const core::BufferFact * @@ -214,6 +214,15 @@ void FunctionDataflow::initializeBuffers(core::AnalysisState &state) { const auto data = places.field(object, shape.data.name); const auto length = places.field(object, shape.length.name); const auto capacity = places.field(object, shape.capacity.name); + // A concrete exhausted/escaped cursor can take an early-return branch + // without touching the input at all. Do not impose the generic readable + // buffer candidate on that case. Any actual read still needs the ordinary + // validity, extent and initialization evidence, and outputs are folded + // only from the actual final state. + if (shape.reader && !memoryContext.empty() && + checkedAtMost(core::Affine::ofPlace(capacity), + core::Affine::ofPlace(length), state)) + continue; // Do not demand an initialized input container from a constructor. This // is only a candidate filter: declining an entry premise grants no facts. std::optional firstUse; @@ -233,17 +242,16 @@ void FunctionDataflow::initializeBuffers(core::AnalysisState &state) { self(self, child); }; visit(visit, function.getBody()); - // Nominate a forwarded premise when its callee has a proved contract. - // Carrying predicates through already-incomplete calls adds work without - // making those calls complete. Direct count accesses nominate - // independently. + // A forwarded requirement nominates a sufficient entry premise even if + // the generic callee is incomplete. Its actual checked call still needs + // a complete contract (possibly for an independently verified input case). std::optional forwarded; const auto forwards = [&](auto &&self, const Stmt *statement) -> void { if (!statement) return; if (const auto *call = dyn_cast(statement)) if (const auto effects = classifyCall(*call, summaries); - effects && effects->summary && effects->summary->checked.complete()) + effects && effects->summary) for (const auto &requirement : effects->summary->checked.requirements) { if (requirement.kind != core::CheckedRequirementKind::Buffer) @@ -527,12 +535,23 @@ void FunctionDataflow::materializeBuffers(core::AnalysisState &state) { const auto storage = state.safety->objects.contains(data) ? state.safety->objects.at(data) : places.deref(data); + // A provisional logical capacity (often zero before its field is set) + // must not shrink independently established allocation-time storage. + // Retain that extent if a later header write retires the buffer predicate. + auto accessible = extent; + if (const auto physical = spatialRecordAt(data, state); + physical && physical->extent && physical->offset.isZero() && + checkedAtMost(extent, *physical->extent, state)) + accessible = *physical->extent; if (!state.safety->positions.contains(data)) { - state.safety->positions[data] = {.storage = storage, - .offset = {}, - .extent = extent, - .input = {}, - .validWhenNonempty = true}; + state.safety->positions[data] = { + .storage = storage, + .offset = {}, + .extent = accessible, + .input = {}, + .validWhenNonempty = + accessible == extent || fact.nonNull || + checkedAtMost(core::Affine::ofConstant(1), extent, state)}; } else { auto &position = state.safety->positions.at(data); if (position.extent && checkedAtMost(*position.extent, extent, state) && @@ -540,7 +559,8 @@ void FunctionDataflow::materializeBuffers(core::AnalysisState &state) { position.validWhenNonempty = true; } if (fact.initialized) - state.safety->initialize(storage, {.begin = {}, .end = bytes}); + state.safety->initialize( + storage, {.begin = {}, .end = fact.shape.reader ? extent : bytes}); if (fact.shape.terminated) { state.relations.learn(fact.length, core::Relation::Less, fact.capacity); const auto through = core::Affine::ofPlace(fact.length, 1, 1); @@ -561,14 +581,9 @@ void FunctionDataflow::materializeBuffers(core::AnalysisState &state) { .location = {}, .reason = core::NullReason::Declared}); } - auto spatial = state.spatial.recordOf(data).value_or(core::SpatialRecord{}); - // Keep stronger allocation-time bounds. Between `data = replacement` - // and `capacity = new_capacity`, the old advertised capacity can be - // smaller than the new allocation; folding must not erase its size. - if (!spatial.extent) { - spatial.extent = extent; - state.spatial.set(data, spatial); - } + // The predicate proves at least capacity accessible bytes (RFC 0029). + // Keep that bound in checked memory; ordinary spatial extents describe + // the actual object size and must not be invented from this lower bound. } } @@ -587,8 +602,12 @@ void FunctionDataflow::normalizeBuffers(core::AnalysisState &state) { } const auto length = places.field(object, shape.length.name); const auto capacity = places.field(object, shape.capacity.name); - const auto len = foldAffine(core::Affine::ofPlace(length), state); - const auto cap = foldAffine(core::Affine::ofPlace(capacity), state); + const auto len = checkedZeroInteger(length, state) + ? core::Affine::ofConstant(0) + : foldAffine(core::Affine::ofPlace(length), state); + const auto cap = checkedZeroInteger(capacity, state) + ? core::Affine::ofConstant(0) + : foldAffine(core::Affine::ofPlace(capacity), state); if (!checkedAtMost({}, len, state) || !checkedAtMost(len, cap, state)) { continue; } @@ -609,6 +628,7 @@ void FunctionDataflow::normalizeBuffers(core::AnalysisState &state) { const auto lengthBytes = len.times(unit); if (!capacityBytes || !lengthBytes) continue; + const auto initializedBytes = shape.reader ? *capacityBytes : *lengthBytes; const auto memory = checkedMemoryAt(data, {}, *capacityBytes, state); const auto backing = state.safety->buffers.storage.find(data); if (backing != state.safety->buffers.storage.end()) { @@ -616,10 +636,10 @@ void FunctionDataflow::normalizeBuffers(core::AnalysisState &state) { established.shape.ownsElements = shape.pointerElements && len.isConstant() && len.constant == 0; established.initialized = - lengthBytes->isConstant() && lengthBytes->constant == 0; + initializedBytes.isConstant() && initializedBytes.constant == 0; if (memory) { auto prefix = *memory; - prefix.end = *lengthBytes; + prefix.end = initializedBytes; established.initialized |= checkedInitialized(prefix, state); } state.safety->buffers.set(data, established); @@ -628,11 +648,11 @@ void FunctionDataflow::normalizeBuffers(core::AnalysisState &state) { if (!memory || memory->begin != core::Affine::ofConstant(0) || !memory->extent || !checkedValid(*memory, state) || !checkedInterval({}, *capacityBytes, *memory->extent, state) || - checkedWritePermission(*memory, state) != true) { + (!shape.reader && checkedWritePermission(*memory, state) != true)) { continue; } auto prefix = *memory; - prefix.end = *lengthBytes; + prefix.end = initializedBytes; auto established = shape; const auto resource = state.resources.recordOf(data); established.ownsBacking = diff --git a/lib/Analysis/DataflowByteContents.cpp b/lib/Analysis/DataflowByteContents.cpp new file mode 100644 index 00000000..6ce327c0 --- /dev/null +++ b/lib/Analysis/DataflowByteContents.cpp @@ -0,0 +1,90 @@ +//===- DataflowByteContents.cpp - Bounded byte values (RFC 0029) ---------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Dataflow.h" +#include "IntegerSupport.h" + +using namespace clang; + +namespace weavec::analysis { + +std::optional> +FunctionDataflow::checkedByteContents(const CheckedMemory &memory, + const core::AnalysisState &state) { + if (!state.safety || context.getCharWidth() != 8 || !memory.extent) + return std::nullopt; + const auto found = state.safety->memory.find(memory.storage); + if (found == state.safety->memory.end() || + std::ranges::none_of( + found->second, + [](const auto &range) { return !range.bytes.empty(); }) || + !checkedValid(memory, state) || + !checkedInterval(memory.begin, memory.end, *memory.extent, state) || + !checkedInitialized(memory, state)) + return std::nullopt; + auto first = foldAffine(memory.begin, state); + std::optional low = first.constant; + std::optional high = first.constant; + if (first.place) { + if (first.scale != 1) + return std::nullopt; + std::tie(low, high) = integerBounds(*first.place, state); + const auto relations = checkedRelations(state); + const auto upper = relations.bound(*first.place, std::nullopt); + const auto negativeLower = relations.bound(std::nullopt, *first.place); + if (relations.limited()) { + inferred.checked.limited = true; + inferred.incomplete.insert("traversal relational limit reached"); + return std::nullopt; + } + if (upper) + high = high ? std::min(*high, *upper) : *upper; + if (negativeLower && *negativeLower != INT64_MIN) + low = low ? std::max(*low, -*negativeLower) : -*negativeLower; + if (!low || !high || __builtin_add_overflow(*low, first.constant, &*low) || + __builtin_add_overflow(*high, first.constant, &*high)) + return std::nullopt; + } + if (*low < 0 || *high < *low || *high - *low >= 64) + return std::nullopt; + for (const auto &range : found->second) { + if (range.bytes.empty() || range.source || !range.begin.isConstant() || + !range.end.isConstant() || range.begin.constant > *low || + *high >= range.end.constant) + continue; + auto when = range.when; + if (!pruneGuard(when, state) || !when.trivial()) + continue; + return std::pair{*low, + range.bytes.substr( + static_cast(*low - range.begin.constant), + static_cast(*high - *low + 1))}; + } + return std::nullopt; +} + +std::optional +FunctionDataflow::checkedByteRange(const Expr &expr, + const core::AnalysisState &state) { + if (!state.safety || !expr.getType()->isCharType() || + expr.getType().isVolatileQualified() || expr.getType()->isAtomicType()) + return std::nullopt; + const auto type = integerTypeOf(expr.getType(), context); + const auto memory = checkedLvalue(expr, state); + const auto bytes = + memory ? checkedByteContents(*memory, state) : std::nullopt; + if (!type || !bytes) + return std::nullopt; + auto result = core::IntegerRange(*type); + for (const char byte : bytes->second) + result = result.united(core::IntegerRange::singleton( + core::IntegerValue::ofBits(*type, static_cast(byte)))); + return result; +} + +} // namespace weavec::analysis diff --git a/lib/Analysis/DataflowCallContext.cpp b/lib/Analysis/DataflowCallContext.cpp index 0c688a73..128086e7 100644 --- a/lib/Analysis/DataflowCallContext.cpp +++ b/lib/Analysis/DataflowCallContext.cpp @@ -45,6 +45,7 @@ FunctionDataflow::contextPlace(const core::SummaryPath &path, core::PlaceId place = builder.placeForVar(*root); QualType type = root->getType(); for (const auto &step : path.steps) { + const auto parentType = type; type = contextStepType(type, step); if (type.isNull()) return std::nullopt; @@ -53,7 +54,11 @@ FunctionDataflow::contextPlace(const core::SummaryPath &path, place = places.deref(place); break; case core::PathStep::Field: - place = places.field(place, step.field); + for (const auto *field : parentType->getAsRecordDecl()->fields()) + if (field->getName() == step.field) { + place = builder.fieldPlace(place, *field); + break; + } break; case core::PathStep::Index: if (step.field.empty()) { @@ -119,6 +124,42 @@ void FunctionDataflow::initializeCallContext(core::AnalysisState &state) { for (const auto &[path, fact] : memoryContext.facts) if (!path.isRoot() || fact.isPointer()) installFact(path, fact); + for (const auto &path : memoryContext.nonNan) { + const auto input = contextPlace(path, state); + if (!state.safety || !input || !input->second->isRealFloatingType() || + input->second.isVolatileQualified() || input->second->isAtomicType()) { + valid = false; + continue; + } + state.safety->nonNan.insert(input->first); + } + for (const auto &[path, bytes] : memoryContext.bytes) { + const auto input = contextPlace(path, state); + if (!state.safety || !input || !input->second->isPointerType() || + !input->second->getPointeeType()->isCharType() || + input->second->getPointeeType().isVolatileQualified() || + context.getCharWidth() != 8) { + valid = false; + continue; + } + const auto storage = places.deref(input->first); + const auto end = + core::Affine::ofConstant(static_cast(bytes.size())); + // RFC 0029: capturing the payload independently proved this live readable + // interval. It supplies a minimum accessible extent, not write permission + // or an exact physical allocation size. + state.safety->pointers.insert(input->first); + state.nulls.set(input->first, {.state = core::Nullness::NonNull, + .location = {}, + .reason = core::NullReason::Declared}); + state.safety->accessible[storage] = end; + state.safety->initialize( + storage, + {.begin = {}, + .end = end, + .bytes = bytes, + .immutableBytes = memoryContext.immutableBytes.contains(path)}); + } std::map inputs; for (const auto &alias : memoryContext.aliases) { const auto a = contextPlace(alias.first, state); @@ -185,7 +226,7 @@ FunctionDataflow::captureCallContext(const CallExpr &call, summary.numericOutputs, [](const auto &entry) { return !entry.first.isResult(); }) || std::ranges::any_of(summary.effects, [](const auto &entry) { - return entry.second.consumed(); + return entry.second.consumed() || entry.second.written; }); // A constructor case can refine head fields (for example an empty child // slot for a singleton), while its general contract remains inductive. @@ -205,13 +246,26 @@ FunctionDataflow::captureCallContext(const CallExpr &call, post.kind == core::CheckedRequirementKind::ContainerDerived) && post.path.isResult(); }); - const bool checkedCase = - !changesMemory && options.checkContracts && summary.checked.computed && - (!summary.checked.complete() || constructorCase || projectionCase) && - std::ranges::none_of(summary.effects, [](const auto &entry) { - return entry.second.written; + bool checkedCase = + options.checkContracts && summary.checked.computed && + (!summary.checked.complete() || constructorCase || projectionCase); + const auto *inductiveCallee = call.getDirectCallee(); + const bool completeInduction = inductiveCallee != nullptr && + summary.checked.complete() && + summaries.verifiedRecursiveContracts.contains( + inductiveCallee->getCanonicalDecl()); + // Complete induction already covers the input bytes. Keep its established + // contract instead of nominating extra pointee selectors merely from a + // payload. Existing constructor, alias and scalar cases remain available. + const bool byteCandidate = + !completeInduction && options.checkContracts && + summary.checked.computed && state.safety && + std::ranges::any_of(state.safety->memory, [](const auto &entry) { + return std::ranges::any_of(entry.second, [](const auto &range) { + return !range.bytes.empty(); + }); }); - if (!changesMemory && !checkedCase) + if (!changesMemory && !checkedCase && !byteCandidate) return std::nullopt; const auto owner = callSummaries.find(&call); assert(owner != callSummaries.end() && owner->second.get() == &summary && @@ -222,17 +276,35 @@ FunctionDataflow::captureCallContext(const CallExpr &call, return std::nullopt; } auto footprint = *prepared; - if (checkedCase) + if (checkedCase || byteCandidate) footprint.insert(summary.checked.caseInputs.begin(), summary.checked.caseInputs.end()); if (options.checkContracts && summary.checked.computed) for (const auto &[path, effect] : summary.effects) if (effect.written && !path.isResult()) footprint.insert(path); + if (state.safety && checkedCase) + for (const auto &pre : summary.checked.requirements) { + if (pre.kind != core::CheckedRequirementKind::Container || + !pre.path.isParam() || !pre.path.isRoot()) + continue; + const auto actual = builder.resolveSummaryPath(pre.path, call); + const auto shape = core::ContainerShape::decode(pre.family); + if (!actual || !shape || state.safety->containers.find(actual->place)) + continue; + if (const auto memory = checkedMemoryAt(actual->place, {}, {}, state)) + if (const auto fact = establishContainer(*memory, *shape, state)) + state.safety->containers.set(actual->place, *fact); + } if (state.safety) for (unsigned i = 0; i < call.getNumArgs(); ++i) if (const auto ref = - builder.resolveSummaryPath(core::SummaryPath::param(i), call)) + builder.resolveSummaryPath(core::SummaryPath::param(i), call)) { + if (checkedCase && !state.safety->containers.find(ref->place)) + if (const auto *shape = containerShape(call.getArg(i)->getType())) + if (const auto memory = checkedMemoryAt(ref->place, {}, {}, state)) + if (const auto fact = establishContainer(*memory, *shape, state)) + state.safety->containers.set(ref->place, *fact); if (const auto *fact = state.safety->containers.find(ref->place)) { // A terminal opaque object can specialize a helper that releases // only its head. These are actual null fields, not new ownership. @@ -260,7 +332,10 @@ FunctionDataflow::captureCallContext(const CallExpr &call, footprint.insert(core::SummaryPath::param(i).deref().field( condition.field.name)); } + for (const auto &name : fact->shape.emptyPayloads) + footprint.insert(core::SummaryPath::param(i).deref().field(name)); } + } if (footprint.size() > core::MaxCallContextFacts) { reportIncomplete("call context input path limit reached", call); return std::nullopt; @@ -519,6 +594,9 @@ FunctionDataflow::captureCallContext(const CallExpr &call, // domain and conversion assumptions are the same as ordinary CFG checking. for (unsigned i = 0; i < call.getNumArgs(); ++i) { const auto *arg = call.getArg(i); + if (options.checkContracts && arg->getType()->isRealFloatingType() && + checkedFloatingValue(*arg, state)) + result.nonNan.insert(core::SummaryPath::param(i)); if (!arg->getType()->isIntegerType()) continue; if (const auto fact = scalarFactOf(*arg, state); @@ -536,41 +614,85 @@ FunctionDataflow::captureCallContext(const CallExpr &call, fact && !fact->trivial()) result.facts[core::SummaryPath::param(i)] = *fact; } + if (byteCandidate && context.getCharWidth() == 8) + for (const auto &input : inputs) { + if (!input.bytePointer) + continue; + const auto memory = checkedPathMemory(input.path, call, {}, {}, state); + if (!memory || !memory->extent || !checkedValid(*memory, state)) + continue; + const auto start = foldAffine(memory->begin, state); + const auto found = state.safety->memory.find(memory->storage); + if (!start.isConstant() || found == state.safety->memory.end()) + continue; + for (const auto &range : found->second) { + if (range.bytes.empty() || range.source || !range.begin.isConstant() || + !range.end.isConstant() || range.begin.constant > start.constant || + start.constant >= range.end.constant) + continue; + auto when = range.when; + auto slice = *memory; + slice.end = range.end; + if (!pruneGuard(when, state) || !when.trivial() || + !checkedInterval(slice.begin, slice.end, *slice.extent, state) || + !checkedInitialized(slice, state)) + continue; + result.bytes[input.path] = range.bytes.substr( + static_cast(start.constant - range.begin.constant)); + if (range.immutableBytes) + result.immutableBytes.insert(input.path); + break; + } + } + checkedCase |= !result.bytes.empty(); + if (!changesMemory && !checkedCase) + return std::nullopt; for (const auto &path : footprint) { - if (const auto ref = builder.resolveSummaryPath(path, call)) + if (const auto ref = builder.resolveSummaryPath(path, call)) { if (const auto fact = state.factOf(ref->place); fact && !fact->trivial()) result.facts[path] = *fact; - } - // A recursive predicate can establish an empty head slot even when no - // ordinary pointer-cell fact was exported by its constructor. - if (state.safety) - for (const auto &path : footprint) { - if (path.steps.size() < 2 || - path.steps.back().step != core::PathStep::Field || - path.steps[path.steps.size() - 2].step != core::PathStep::Deref) - continue; - auto parent = path; - const auto field = parent.steps.back().field; - parent.steps.truncate(parent.steps.size() - 2); - if (const auto ref = builder.resolveSummaryPath(parent, call)) - if (const auto *fact = state.safety->containers.find(ref->place); - fact && state.nulls.isNonNull(ref->place)) { - if (fact->shape.recursiveLink(field) && - (fact->shape.terminal || fact->shape.emptyLinks.contains(field))) - result.facts[path] = core::ValueFact::of(core::Outcome::Null); - if (const auto known = fact->shape.headValues.find(field); - known != fact->shape.headValues.end()) - if (const auto record = - containerRecords.find(fact->shape.object.toString()); - record != containerRecords.end()) - for (const auto *selector : record->second->fields()) - if (selector->getName() == field) - if (const auto type = integerTypeOf(*selector, context)) - result.facts[path] = core::ValueFact::ofInteger( - core::IntegerRange::singleton( - core::IntegerValue::ofBits(*type, known->second))); - } + if (checkedZeroInteger(ref->place, state)) + result.facts[path] = core::ValueFact::ofConstant(0); } + // A copied pointer field may identify an initialized local scalar without + // a separately materialized scalar fact under the field's dereference. + // Capture only an exact whole scalar of the same target C type. Merely + // sharing an allocation or a byte representation does not give its value. + if (!options.checkContracts || result.facts.contains(path) || + path.steps.empty() || path.steps.back().step != core::PathStep::Deref || + !path.isParam() || path.index >= call.getNumArgs()) + continue; + auto type = call.getArg(path.index)->getType(); + for (const auto &step : path.steps) + type = contextStepType(type, step); + if (type.isNull() || !type->isIntegerType() || type.isVolatileQualified() || + type->isAtomicType()) + continue; + auto pointer = path; + pointer.steps.truncate(pointer.steps.size() - 1); + const auto bytes = core::Affine::ofConstant( + context.getTypeSizeInChars(type).getQuantity()); + const auto memory = checkedPathMemory(pointer, call, {}, bytes, state); + if (!memory || memory->begin != core::Affine::ofConstant(0) || + memory->end != bytes || !memory->extent || + !checkedInterval(memory->begin, memory->end, *memory->extent, state) || + !checkedValid(*memory, state) || !checkedInitialized(*memory, state)) + continue; + const auto *decl = + dyn_cast_or_null(builder.declFor(memory->storage)); + if (!decl || decl->getType().isVolatileQualified() || + decl->getType()->isAtomicType() || + !ASTContext::hasSameUnqualifiedType(type, decl->getType())) + continue; + if (const auto fact = state.scalars.factOf(memory->storage); + fact && !fact->trivial()) + result.facts[path] = *fact; + } + // RFC 0029: capture and guard checking use the same current head evidence. + for (const auto &path : footprint) + if (const auto ref = builder.resolveSummaryPath(path, call)) + if (const auto fact = containerValueFact(ref->place, state)) + result.facts[path] = *fact; const bool completeConsumption = summary.checked.complete() && std::ranges::any_of(summary.checked.establishes, [](const auto &post) { @@ -584,12 +706,13 @@ FunctionDataflow::captureCallContext(const CallExpr &call, const bool checkedScalars = !completeConsumption && options.checkContracts && summary.checked.computed && - std::ranges::any_of(result.facts, [&](const auto &entry) { - return checkedCase || (recursiveContext && !entry.first.isRoot()) - ? !entry.second.trivial() - : !entry.second.isPointer() && - entry.second.constant.has_value(); - }); + (!result.bytes.empty() || !result.nonNan.empty() || + std::ranges::any_of(result.facts, [&](const auto &entry) { + return checkedCase || (recursiveContext && !entry.first.isRoot()) + ? !entry.second.trivial() + : !entry.second.isPointer() && + entry.second.constant.has_value(); + })); if (result.aliases.empty() && !checkedScalars && (!selectedInputs || inputs.size() < 2 || unresolved || unrepresentable)) return std::nullopt; diff --git a/lib/Analysis/DataflowCallbackContracts.cpp b/lib/Analysis/DataflowCallbackContracts.cpp new file mode 100644 index 00000000..83634901 --- /dev/null +++ b/lib/Analysis/DataflowCallbackContracts.cpp @@ -0,0 +1,211 @@ +//===- DataflowCallbackContracts.cpp - Callback premises (RFC 0029) ------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Dataflow.h" + +#include + +using namespace clang; +namespace weavec::analysis { + +static std::optional +callbackProtocol(QualType type, const ASTContext &context) { + if (type->isPointerType()) + type = type->getPointeeType(); + const auto *prototype = type->getAs(); + if (!prototype || prototype->isVariadic() || prototype->getNumParams() != 1) + return std::nullopt; + if (ASTContext::hasSameType(prototype->getReturnType(), context.VoidPtrTy) && + ASTContext::hasSameUnqualifiedType(prototype->getParamType(0), + context.getSizeType())) + return core::CheckedRequirementKind::CallbackAllocate; + if (prototype->getReturnType()->isVoidType() && + ASTContext::hasSameUnqualifiedType(prototype->getParamType(0), + context.VoidPtrTy)) + return core::CheckedRequirementKind::CallbackRelease; + return std::nullopt; +} + +std::optional +FunctionDataflow::callbackEntry(core::PlaceId holder, + const core::AnalysisState &state) { + if (state.safety->havoc || state.safety->invalidatedPointers.contains(holder)) + return std::nullopt; + ValueOrigin input; + input.kind = ValueOrigin::Kind::Copy; + input.place = PlaceRef{.place = holder, .derefs = {}, .element = {}}; + const auto source = sourceValueOf(input, state, true); + if (!source.path || !source.path->isParam() || + state.isOverwritten(*source.path)) + return std::nullopt; + // A symbolic copy must still denote its entry version. In particular a + // function-wide spelling cannot recover a callback after replacement. + const auto direct = builder.summaryPathOf(holder); + if (direct == source.path && state.safety->replacedPointers.contains(holder)) + return std::nullopt; + return source.path; +} + +std::shared_ptr +FunctionDataflow::requiredCallback(const CallExpr &call, + core::AnalysisState &state) { + if (!options.checkContracts || call.getNumArgs() != 1) + return {}; + // Do not erase an explicit function pointer cast before checking identity. + const auto *expression = call.getCallee()->IgnoreParens(); + while (const auto *cast = dyn_cast(expression)) + expression = cast->getSubExpr()->IgnoreParens(); + if (isa(expression)) + return {}; + const auto kind = callbackProtocol(expression->getType(), context); + const auto ref = builder.resolvePointerValue(*expression); + const auto path = ref ? callbackEntry(ref->place, state) : std::nullopt; + if (!kind || !path) + return {}; + if (recording()) { + inferred.callbackInputs.insert(*path); + inferred.checked.require({.kind = *kind, + .path = *path, + .other = {}, + .family = "free", + .when = summaryGuardOf(guardHere(state))}); + } + safetyObligation(core::SafetyProperty::Call, core::SafetyOutcome::Required, + call, "callback", "entry callback behavior is required"); + auto summary = std::make_shared(); + summary->checked.computed = true; + summary->checked.signature = + functionTypeKey(call.getCallee()->getType(), context); + if (*kind == core::CheckedRequirementKind::CallbackAllocate) { + summary->addReturn(core::ValueSource::freshAt( + "free", core::PointerOffset::zero(), + core::PathAffine::ofPath(core::SummaryPath::param(0)))); + summary->addReturn(core::ValueSource::null()); + } else { + summary->addEffect(core::SummaryPath::param(0), + core::PlaceEffect{.freed = true, .family = "free"}); + summary->checked.require({.kind = core::CheckedRequirementKind::Release, + .path = core::SummaryPath::param(0), + .other = {}, + .family = "free"}); + summary->checked.establish( + {.kind = core::CheckedRequirementKind::AllocationConsumed, + .path = core::SummaryPath::param(0), + .other = {}, + .family = "free"}); + } + return summary; +} + +static bool callbackSatisfies(const core::FunctionSummary &summary, + core::CheckedRequirementKind kind) { + if (!summary.checked.complete() || !summary.incomplete.empty() || + summary.neverReturns || !summary.stores.empty() || + !summary.heap.empty() || !summary.callbackInputs.empty() || + !summary.arrayCopies.empty() || !summary.arrayReleases.empty() || + !summary.arrayFills.empty() || !summary.numericOutputs.empty() || + !summary.increments.empty() || !summary.decrements.empty() || + !summary.requiresNonNull.empty() || !summary.requiresExtent.empty()) + return false; + const auto input = core::SummaryPath::param(0); + if (kind == core::CheckedRequirementKind::CallbackAllocate) { + if (!summary.effects.empty() || !summary.checked.requirements.empty() || + summary.returns.empty()) + return false; + return std::ranges::all_of(summary.returns, [&](const auto &value) { + return value.kind == core::ValueSource::Kind::Null || + (value.isFresh() && value.family == "free" && + value.offset.isZero() && + value.extent == core::PathAffine::ofPath(input)); + }); + } + if (std::ranges::any_of(summary.effects, [&](const auto &entry) { + const auto &effect = entry.second; + return entry.first != input || effect.read || effect.written || + effect.moved || effect.escaped || effect.replaced || + effect.element || effect.share || !effect.freed || + effect.family != "free" || !effect.at.isZero(); + })) + return false; + if (std::ranges::any_of(summary.checked.requirements, [&](const auto &pre) { + return pre.kind != core::CheckedRequirementKind::Release || + pre.path != input || pre.family != "free" || + pre.begin != core::PathAffine::ofConstant(0) || + pre.end != core::PathAffine::ofConstant(0); + })) + return false; + return std::ranges::any_of( + summary.checked.establishes, [&](const auto &post) { + return post.kind == core::CheckedRequirementKind::AllocationConsumed && + post.path == input && post.family == "free" && + post.when.trivial() && !post.on && !post.ifNonNull; + }); +} + +void FunctionDataflow::checkedCallbackRequirement( + const core::CheckedRequirement &requirement, const CallExpr &call, + core::AnalysisState &state) { + core::CallTargets targets = core::CallTargets::any(); + const auto ref = builder.resolveSummaryPath(requirement.path, call); + if (requirement.path.isParam() && requirement.path.isRoot() && + requirement.path.index < call.getNumArgs()) + targets = functionTargets(*call.getArg(requirement.path.index), state); + else if (ref) + if (const auto found = state.callTargets.find(ref->place); + found != state.callTargets.end()) + targets = found->second; + if (ref && state.nulls.isNonNull(ref->place)) + targets.null = false; + bool satisfied = + !targets.unknown && !targets.null && !targets.functions.empty(); + for (const auto &symbol : targets.functions) { + const auto actual = summaries.lookupSymbol(symbol); + const auto *declaration = summaries.callable(symbol); + const auto protocol = + declaration ? callbackProtocol(declaration->getType(), context) + : std::nullopt; + bool matches = actual && protocol == requirement.kind; + if (matches && actual->source == SummarySource::Builtin) { + matches = + symbol == + (requirement.kind == core::CheckedRequirementKind::CallbackAllocate + ? "malloc" + : "free"); + } else if (matches) { + matches = actual->summary->checked.signature == + functionTypeKey(declaration->getType(), context) && + callbackSatisfies(*actual->summary, requirement.kind); + } + if (matches && actual->source == SummarySource::Builtin) + safetyObligation(core::SafetyProperty::Call, core::SafetyOutcome::Trusted, + call, symbol, "modeled C library contract"); + else if (matches && recording()) + inferred.checked.obligations.addCalls( + actual->summary->checked.obligations, true, locate(call), + function.getNameAsString(), symbol, inUnsafe); + satisfied &= matches; + } + bool required = false; + if (!satisfied && targets.functions.empty() && targets.unknown && + !targets.null && ref) + if (const auto path = callbackEntry(ref->place, state)) { + required = true; + if (recording()) { + auto forwarded = requirement; + forwarded.path = *path; + forwarded.when = summaryGuardOf(guardHere(state)); + inferred.checked.require(std::move(forwarded)); + inferred.callbackInputs.insert(*path); + } + } + safetyObligation(core::SafetyProperty::Call, + core::safetyOutcome(satisfied, required), call, "callback", + "callee requires compatible synchronous callback behavior"); +} + +} // namespace weavec::analysis diff --git a/lib/Analysis/DataflowCallbacks.cpp b/lib/Analysis/DataflowCallbacks.cpp index 5dca8fe3..072c8a17 100644 --- a/lib/Analysis/DataflowCallbacks.cpp +++ b/lib/Analysis/DataflowCallbacks.cpp @@ -165,6 +165,12 @@ FunctionDataflow::resolveCall(const CallExpr &call) { if (!currentState) return direct ? summaries.lookup(*direct) : summaries.lookupIndirect(call); core::AnalysisState &state = *currentState; + if (auto hypothesis = recursiveInputCall(call, state)) { + callSummaries[&call] = hypothesis; + callSources[&call] = SummarySource::Inferred; + return ResolvedSummary{.summary = std::move(hypothesis), + .source = SummarySource::Inferred}; + } std::shared_ptr result; SummarySource source = SummarySource::Inferred; const auto captureCallbacks = [&](const core::FunctionSummary &summary) { @@ -202,12 +208,14 @@ FunctionDataflow::resolveCall(const CallExpr &call) { }; const auto contextualize = [&](std::string_view symbol, - std::shared_ptr base) { + std::shared_ptr base, + std::optional prepared = std::nullopt) { // Path resolution validates this target's object views before using // its footprint. The final contextual result replaces this below. auto &snapshot = callSummaries[&call]; snapshot = std::move(base); - auto bindings = captureCallContext(call, *snapshot, state); + auto bindings = prepared ? std::move(prepared) + : captureCallContext(call, *snapshot, state); if (!bindings) return snapshot; if (const auto callbacks = callbackContexts.find(&call); @@ -218,6 +226,13 @@ FunctionDataflow::resolveCall(const CallExpr &call) { const auto specialized = summaries.specializeMemory( symbol, *bindings, options, recording() && emitDiagnostics && !inUnsafe ? &collected : nullptr); + // RFC 0029: a failed precision attempt cannot replace a complete + // generic proof. Its unchanged requirements still apply at this call. + if (options.checkContracts && snapshot->checked.complete() && + (!specialized || !specialized->summary->checked.complete())) { + memoryContexts.erase(&call); + return snapshot; + } for (auto diagnostic : collected.diagnostics()) { diagnostic.addNote("called here with related pointer arguments", locate(call)); @@ -233,8 +248,43 @@ FunctionDataflow::resolveCall(const CallExpr &call) { if (const auto base = summaries.lookup(*direct)) { result = summaries.retainSummary(*base); source = base->source; - if (!result->callbackInputs.empty()) { - auto bindings = captureCallbacks(*result); + std::optional combinedInputs; + auto bindings = captureCallbacks(*result); + const bool behavioral = + result->checked.complete() && !result->callbackInputs.empty() && + std::ranges::all_of(result->callbackInputs, [&](const auto &path) { + if (!path.isParam() || !path.isRoot()) + return false; + const auto binding = bindings.find(path); + if (binding == bindings.end() || binding->second.null) + return false; + return std::ranges::any_of( + result->checked.requirements, [&](const auto &requirement) { + if (requirement.path != path || + !(requirement.kind == + core::CheckedRequirementKind::CallbackAllocate || + requirement.kind == + core::CheckedRequirementKind::CallbackRelease)) + return false; + return std::ranges::all_of( + binding->second.functions, [&](const auto &symbol) { + const auto actual = summaries.lookupSymbol(symbol); + const auto expected = + requirement.kind == core::CheckedRequirementKind:: + CallbackAllocate + ? "malloc" + : "free"; + return actual && + actual->source == SummarySource::Builtin && + symbol == expected; + }); + }); + }); + if (!result->callbackInputs.empty() && !behavioral) { + // A behavioral contract is one sufficient interface. A known target + // with another protocol (e.g. a void(void*) writer) still uses its + // actual body and memory effects, rather than acquiring release + // semantics from its C prototype. const bool known = !bindings.empty() && std::ranges::any_of(bindings, [](const auto &binding) { @@ -242,12 +292,26 @@ FunctionDataflow::resolveCall(const CallExpr &call) { }); if (known) { callbackContexts[&call] = bindings; - if (const auto specialized = - summaries.specialize(*direct, bindings, options, nullptr)) { - result = summaries.retainSummary(*specialized); - } else { - reportIncomplete("callback context unavailable or limit reached", - call); + if (options.checkContracts) { + // RFC 0029: both sets of actual entry premises belong to one + // body check. A callback-only preliminary run can otherwise + // populate nested cases for selectors the caller already knows. + callSummaries[&call] = result; + combinedInputs = captureCallContext(call, *result, state); + if (combinedInputs) { + combinedInputs->callbacks = bindings; + if (options.stats) + options.stats->add("combined_callback_case_requests"); + } + } + if (!combinedInputs) { + if (const auto specialized = + summaries.specialize(*direct, bindings, options, nullptr)) { + result = summaries.retainSummary(*specialized); + } else { + reportIncomplete("callback context unavailable or limit reached", + call); + } } } } @@ -255,11 +319,12 @@ FunctionDataflow::resolveCall(const CallExpr &call) { direct->getDefinition() != nullptr || (summaries.programDatabase() != nullptr && summaries.programDatabase()->defines(direct->getName())); - if (source == SummarySource::Inferred || - source == SummarySource::Program || - (source == SummarySource::Annotation && knownBody)) { + if (!behavioral && (source == SummarySource::Inferred || + source == SummarySource::Program || + (source == SummarySource::Annotation && knownBody))) { summaries.registerCallable(*direct); - result = contextualize(callableSymbol(*direct), std::move(result)); + result = contextualize(callableSymbol(*direct), std::move(result), + std::move(combinedInputs)); } if (!memoryContexts.contains(&call) && callbackContexts.contains(&call) && recording() && emitDiagnostics && !inUnsafe && @@ -278,6 +343,14 @@ FunctionDataflow::resolveCall(const CallExpr &call) { if (const auto contract = summaries.lookupIndirect(call)) { result = summaries.retainSummary(*contract); source = contract->source; + } else if (const auto required = + targets.functions.empty() && targets.unknown && !targets.null + ? requiredCallback(call, state) + : nullptr; + required) { + result = required; + // RFC 0029: this is a sufficient entry requirement, never a trusted + // target or a replacement for checking a known callback implementation. } else { bool returns = false; std::optional singleSource; diff --git a/lib/Analysis/DataflowCheckedIntegers.cpp b/lib/Analysis/DataflowCheckedIntegers.cpp index adb660d4..1637c20a 100644 --- a/lib/Analysis/DataflowCheckedIntegers.cpp +++ b/lib/Analysis/DataflowCheckedIntegers.cpp @@ -77,9 +77,7 @@ bool FunctionDataflow::handleCheckedIntegerCall(const CallExpr &call, if (stored) state.numericValues.insert_or_assign(*saved, *stored); assignScalar(pointee->place, nullptr, state, &call); - auto cells = mirrors(pointee->place, state); - cells.push_back(pointee->place); - llvm::append_range(cells, borrowedImages(pointee->place, state)); + const auto cells = scalarMirrors(pointee->place, state); for (const auto cell : cells) { state.scalars.set(cell, core::ValueFact::ofInteger(values.values)); if (const auto frozen = state.numericValues.find(*saved); diff --git a/lib/Analysis/DataflowContainerContracts.cpp b/lib/Analysis/DataflowContainerContracts.cpp index 22fc7b81..749f3a12 100644 --- a/lib/Analysis/DataflowContainerContracts.cpp +++ b/lib/Analysis/DataflowContainerContracts.cpp @@ -15,6 +15,19 @@ using namespace clang; namespace weavec::analysis { +static bool unchangedContainerRegion(const core::ContainerFact &before, + const core::ContainerFact &after) { + if (!std::ranges::includes(before.ancestors, after.ancestors) || + (after.tailOf && after.tailOf != before.tailOf) || + (!after.tailField.empty() && after.tailField != before.tailField)) + return false; + auto previous = before; + previous.ancestors = after.ancestors; + previous.tailOf = after.tailOf; + previous.tailField = after.tailField; + return previous == after; +} + void FunctionDataflow::snapshotContainerOutput(core::PlaceId holder, core::AnalysisState &state) { const bool hasFact = state.safety->containers.find(holder) != nullptr; @@ -176,7 +189,8 @@ void FunctionDataflow::captureContainerPosts( } if (post.kind != core::CheckedRequirementKind::Container && post.kind != core::CheckedRequirementKind::ContainerDerived && - post.kind != core::CheckedRequirementKind::ContainerFresh) + post.kind != core::CheckedRequirementKind::ContainerFresh && + post.kind != core::CheckedRequirementKind::ContainerExtended) continue; const auto shape = core::ContainerShape::decode(post.family); const auto guard = checkedGuard(post.when, call, state); @@ -193,7 +207,8 @@ void FunctionDataflow::captureContainerPosts( if (post.kind == core::CheckedRequirementKind::ContainerFresh) { fact.allocationCompatible = true; fact.localAllocation = true; - } else if (post.kind == core::CheckedRequirementKind::ContainerDerived && + } else if ((post.kind == core::CheckedRequirementKind::ContainerDerived || + post.kind == core::CheckedRequirementKind::ContainerExtended) && arguments != containerArguments.end()) { std::vector sources{post.other}; if (post.begin.path) @@ -254,24 +269,81 @@ void FunctionDataflow::captureContainerPosts( if (inserted) object->second = places.create("container call region"); fact.members.insert(object->second); + std::map separated; + if (post.kind == core::CheckedRequirementKind::ContainerFresh) + for (const auto &[other, current] : state.safety->containers.all()) { + const bool output = std::ranges::any_of( + contract.establishes, [&](const auto &candidate) { + if (candidate.path.isResult()) + return false; + const auto actual = + builder.resolveSummaryPath(candidate.path, call); + return actual && (actual->place == other || + places.isDescendantOf(other, actual->place)); + }); + if (!output && !state.safety->invalidatedPointers.contains(other) && + !state.moves.recordOf(other)) + separated.emplace(other, current); + } + if (post.kind == core::CheckedRequirementKind::ContainerExtended || + post.kind == core::CheckedRequirementKind::ContainerDerived) { + std::vector sources{post.other}; + if (post.begin.path) + sources.push_back(*post.begin.path); + if (post.end.path) + sources.push_back(*post.end.path); + for (const auto &[other, current] : state.safety->containers.all()) + if (std::ranges::all_of(sources, [&](const auto &path) { + const auto source = builder.resolveSummaryPath(path, call); + return source && + state.safety->containers.separated(source->place, other); + })) + separated.emplace(other, current); + } if (fact.valid()) posts.push_back( {.path = post.path, .fact = std::move(fact), .on = post.on, - .fresh = post.kind == core::CheckedRequirementKind::ContainerFresh}); + .fresh = post.kind == core::CheckedRequirementKind::ContainerFresh, + .separated = std::move(separated)}); } } void FunctionDataflow::applyContainerPosts( const CallExpr &call, core::AnalysisState &state, - std::optional result) { + std::optional result, + const std::optional *prior) { const auto found = containerPosts.find(&call); if (found == containerPosts.end()) return; for (const auto &post : found->second) { if (post.path.isResult() != result.has_value()) continue; + // RFC 0029: an immediate result test installs a verified transfer and + // only the structural outputs on that transfer's own paths. + if (prior) { + if (!post.on || + (*prior && (*prior)->implies(core::ValueFact::of(*post.on)))) + continue; + const auto selected = scalarFactOf(call, state); + const auto posts = footprintPosts.find(&call); + if (!selected || posts == footprintPosts.end()) + continue; + if (std::ranges::none_of(posts->second, [&](const auto &transfer) { + const bool region = + transfer.kind == + core::CheckedRequirementKind::ContainerExtended || + (transfer.kind == + core::CheckedRequirementKind::ContainerCombined && + transfer.end == core::PathAffine::ofConstant(1)); + return region && transfer.on && + selected->implies(core::ValueFact::of(*transfer.on)) && + (transfer.path == post.path || + transfer.path.isProperPrefixOf(post.path)); + })) + continue; + } if (post.on && !(result && post.on == core::Outcome::NonNull)) { const auto outcome = scalarFactOf(call, state); bool allOutcomes = false; @@ -299,23 +371,43 @@ void FunctionDataflow::applyContainerPosts( } if (!holder) continue; - std::vector previous; - if (post.fresh) - for (const auto &[other, fact] : state.safety->containers.all()) { - // Outputs of one invocation may denote the same fresh allocation. - // A reused loop call-region identity also supplies no separation. - const auto region = containerCallObjects.find(&call); - if (region != containerCallObjects.end() && - fact.members.contains(region->second)) - continue; - previous.push_back(other); - } state.safety->containers.set(*holder, post.fact); state.safety->invalidatedPointers.erase(*holder); - for (const auto other : previous) - state.safety->containers.separate(*holder, other); + // RFC 0029: only the original singleton and fresh descendants compose + // this output. Preserve separation from a surviving unchanged forest. + for (const auto &[other, before] : post.separated) { + const auto alias = state.definiteAliases.offsetOf(*holder, other); + if (other == *holder || (alias && alias->isZero()) || + state.safety->invalidatedPointers.contains(other) || + state.moves.recordOf(other)) + continue; + if (const auto *current = state.safety->containers.find(other); + current && unchangedContainerRegion(before, *current)) + state.safety->containers.separate(*holder, other); + } state.safety->pointers.insert(*holder); snapshotContainerOutput(*holder, state); + // RFC 0029: the output describes this actual head, including every + // unchanged definite alias. Old descriptors are not restored. + const auto memory = checkedMemoryAt(*holder, {}, {}, state); + if (memory) + for (const auto &[alias, edge] : + state.definiteAliases.edgesFrom(*holder)) { + if (alias == *holder || !edge.exact() || + !state.definiteAliases.sameShare(*holder, alias)) + continue; + const auto other = checkedMemoryAt(alias, {}, {}, state); + if (!other || other->storage != memory->storage || + other->begin != memory->begin) + continue; + state.safety->containers.set(alias, post.fact); + for (const auto separate : + state.safety->containers.separatedFrom(*holder)) + state.safety->containers.separate(alias, separate); + state.safety->invalidatedPointers.erase(alias); + state.safety->pointers.insert(alias); + snapshotContainerOutput(alias, state); + } if (post.fact.shape.terminal || !post.fact.shape.emptyLinks.empty() || !post.fact.shape.emptyPayloads.empty()) { QualType type; diff --git a/lib/Analysis/DataflowContainerTransfer.cpp b/lib/Analysis/DataflowContainerTransfer.cpp index e0009b4f..97192f2e 100644 --- a/lib/Analysis/DataflowContainerTransfer.cpp +++ b/lib/Analysis/DataflowContainerTransfer.cpp @@ -8,6 +8,7 @@ #include "Dataflow.h" #include "IntegerSupport.h" +#include "RuntimeModels.h" #include #include @@ -16,6 +17,161 @@ using namespace clang; namespace weavec::analysis { +std::optional +FunctionDataflow::capturePayloadRelocation(const Stmt &stmt, + core::AnalysisState &state) { + const auto *copy = dyn_cast(&stmt); + const auto found = payloadRelocationClears.find(copy); + if (!copy || found == payloadRelocationClears.end() || unsafeBody || + unsafeStmts.contains(copy) || unsafeStmts.contains(found->second)) + return std::nullopt; + const auto *clear = found->second; + const auto *destination = + dyn_cast(copy->getLHS()->IgnoreParenImpCasts()); + const auto *source = + dyn_cast(copy->getRHS()->IgnoreParenImpCasts()); + const auto *cleared = + dyn_cast(clear->getLHS()->IgnoreParenImpCasts()); + if (!destination || !source || !cleared || !destination->isArrow() || + !source->isArrow() || !cleared->isArrow() || + !clear->getRHS()->isNullPointerConstant( + context, Expr::NPC_ValueDependentIsNotNull)) + return std::nullopt; + const auto base = [](const MemberExpr &member) -> const VarDecl * { + const auto *ref = + dyn_cast(member.getBase()->IgnoreParenImpCasts()); + return ref ? dyn_cast(ref->getDecl()) : nullptr; + }; + const auto *variable = base(*destination); + const auto *from = dyn_cast(source->getMemberDecl()); + const auto *to = dyn_cast(destination->getMemberDecl()); + if (!variable || base(*source) != variable || base(*cleared) != variable || + !from || !to || from == to || cleared->getMemberDecl() != from || + from->getParent() != to->getParent() || + variable->getType().isVolatileQualified() || + !from->getType()->isPointerType() || + !ASTContext::hasSameType(from->getType(), to->getType()) || + from->getType().isVolatileQualified() || from->getType()->isAtomicType()) + return std::nullopt; + const auto holder = builder.placeForVar(*variable); + const auto *before = state.safety->containers.find(holder); + const auto memory = checkedMemoryAt(holder, {}, {}, state); + if (!before || before->empty || !before->allocationCompatible || + before->shape.access != core::ContainerAccess::Release || + !before->releasedChildren.empty() || !before->releasedPayloads.empty() || + !state.nulls.isNonNull(holder) || !memory || + memory->begin != core::Affine{} || !checkedValid(*memory, state)) + return std::nullopt; + const auto payload = [&](const FieldDecl &field) { + return std::ranges::find_if(before->shape.payloads, [&](const auto &entry) { + return entry.field.name == field.getName(); + }); + }; + const auto original = payload(*from); + const auto target = payload(*to); + if (original == before->shape.payloads.end() || + target == before->shape.payloads.end() || + original->family != target->family || + containerOwns(holder, original->field.name, before->shape, state) != + true || + containerOwns(holder, target->field.name, before->shape, state) != true) + return std::nullopt; + const auto empty = [&](const core::ContainerField &field) { + return before->shape.emptyPayloads.contains(field.name) || + state.nulls.stateOf(places.field( + places.deref(holder), field.name)) == core::Nullness::Null || + containerZeroField(holder, field, state); + }; + if (!empty(target->field)) + return std::nullopt; + auto after = *before; + after.shape.emptyPayloads.insert(original->field.name); + if (empty(original->field)) + after.shape.emptyPayloads.insert(target->field.name); + else + after.shape.emptyPayloads.erase(target->field.name); + if (!requireContainer(*before, stmt, state)) + return std::nullopt; + unfoldFootprint(holder, *before, state); + const auto [saved, inserted] = payloadRelocationSnapshots.try_emplace(copy); + if (inserted) + saved->second = {places.create("relocated forest footprint"), + places.create("relocated payload footprint")}; + state.safety->footprints.assign(saved->second.first, {{holder, 1}}); + state.safety->footprints.assign( + saved->second.second, + {{places.field(places.deref(holder), original->field.name), 1}}); + PayloadRelocation result{.clear = clear, + .evaluation = {}, + .holder = holder, + .source = original->field.name, + .destination = target->field.name, + .snapshot = saved->second, + .aliases = {{holder, after}}, + .separated = {}}; + std::vector work{clear}; + while (!work.empty()) { + const auto *current = work.back(); + work.pop_back(); + if (!current) + continue; + result.evaluation.insert(current); + for (const auto *child : current->children()) + work.push_back(child); + } + for (const auto &[other, fact] : state.safety->containers.all()) { + const auto offset = state.definiteAliases.offsetOf(other, holder); + const auto otherMemory = checkedMemoryAt(other, {}, {}, state); + if (other != holder && offset && offset->isZero() && + fact.shape == before->shape && fact.empty == before->empty && + fact.allocationCompatible && fact.releasedChildren.empty() && + fact.releasedPayloads.empty() && otherMemory && + otherMemory->storage == memory->storage && + otherMemory->begin == memory->begin && + checkedValid(*otherMemory, state)) { + auto updated = fact; + updated.shape = after.shape; + result.aliases.emplace(other, std::move(updated)); + } else if (state.safety->containers.separated(holder, other)) { + result.separated.emplace(other, fact); + } + } + return result; +} + +void FunctionDataflow::applyPayloadRelocation( + const PayloadRelocation &relocation, core::AnalysisState &state) { + const auto source = + places.field(places.deref(relocation.holder), relocation.source); + const auto memory = checkedMemoryAt(relocation.holder, {}, {}, state); + if (state.nulls.stateOf(source) != core::Nullness::Null || !memory || + memory->begin != core::Affine{} || !checkedValid(*memory, state)) + return; + // These are the same allocations before and after two adjacent pure stores. + // The temporary duplicate never supplies a container predicate. + for (const auto &[holder, fact] : relocation.aliases) { + state.safety->containers.set(holder, fact); + state.safety->footprints.assign(holder, {{relocation.snapshot.first, 1}}); + state.safety->footprints.assign( + places.field(places.deref(holder), relocation.source), {}); + const auto destination = + places.field(places.deref(holder), relocation.destination); + state.safety->footprints.assign(destination, + {{relocation.snapshot.second, 1}}); + state.safety->footprints.assign(footprintHead(destination), + {{relocation.snapshot.second, 1}}); + for (const auto &field : {relocation.source, relocation.destination}) + if (fact.shape.ownership.contains(field)) + state.safety->footprints.forget(footprintContribution(holder, field)); + state.safety->unfoldedFootprints.erase(holder); + for (const auto &[other, before] : relocation.separated) + if (const auto *current = state.safety->containers.find(other); + current && *current == before) + state.safety->containers.separate(holder, other); + snapshotContainerOutput(holder, state); + } +} + bool FunctionDataflow::separateContainers(core::PlaceId first, core::PlaceId second, const Stmt &at, core::AnalysisState &state) { @@ -46,9 +202,17 @@ bool FunctionDataflow::separateContainers(core::PlaceId first, // proof contains an inductively summarized allocation or input region. const auto am = checkedMemoryAt(first, {}, {}, state); const auto bm = checkedMemoryAt(second, {}, {}, state); - if (am && bm && a->inputs.empty() && b->inputs.empty() && !a->suffix && - !b->suffix && builder.declFor(am->storage) && - builder.declFor(bm->storage)) { + const auto concrete = [&](const core::ContainerFact &fact) { + return std::ranges::all_of(fact.members, [&](const auto member) { + return builder.declFor(member) || + std::ranges::any_of(checkedObjects, [&](const auto &entry) { + return entry.second == member; + }); + }); + }; + if (am && bm && checkedValid(*am, state) && checkedValid(*bm, state) && + a->inputs.empty() && b->inputs.empty() && !a->suffix && !b->suffix && + concrete(*a) && concrete(*b)) { bool overlap = false; for (const auto member : a->members) overlap |= b->members.contains(member); @@ -123,6 +287,125 @@ static bool containerEffectCovered(const core::SummaryPath &input, return true; } +void FunctionDataflow::captureContainerPrefixes( + const CallExpr &call, const core::CheckedRequirement &post, + core::AnalysisState &state) { + if ((post.kind != core::CheckedRequirementKind::ContainerPreserved && + post.kind != core::CheckedRequirementKind::ContainerExtended) || + post.path != post.other || !post.path.isParam() || !post.path.isRoot()) + return; + const auto actual = builder.resolveSummaryPath(post.other, call); + const auto summary = callSummaries.find(&call); + if (!actual || summary == callSummaries.end() || !summary->second) + return; + const auto *before = state.safety->containers.find(actual->place); + if (!before || before->empty) + return; + std::set ancestors = before->ancestors; + if (before->tailOf && !before->tailField.empty()) + ancestors.insert(*before->tailOf); + for (const auto ancestor : ancestors) { + const auto *prefix = state.safety->containers.find(ancestor); + const auto alias = state.definiteAliases.offsetOf(ancestor, actual->place); + if (!prefix || prefix->empty || ancestor == actual->place || + (alias && alias->isZero()) || state.moves.recordOf(ancestor) || + state.safety->invalidatedPointers.contains(ancestor)) + continue; + bool confined = true; + for (const auto &[path, effect] : summary->second->effects) { + if (effect.escaped) { + confined = false; + break; + } + if (!effect.written && !effect.consumed()) + continue; + if (containerEffectCovered(post.other, before->shape, path, effect)) + continue; + const auto destination = builder.resolveSummaryPath(path, call); + if (effect.consumed() || !destination || + !destination->element.isWhole()) { + confined = false; + break; + } + auto framed = state; + bool separate = + preserveInputContainersAcrossLocalWrite(destination->place, framed); + if (!separate) + separate = + preserveFreshContainersAcrossWrite(destination->place, framed); + const auto *surviving = framed.safety->containers.find(ancestor); + if (!separate || !surviving || *surviving != *prefix) { + confined = false; + break; + } + } + if (!confined) + continue; + const auto [saved, inserted] = + containerPrefixSnapshots[&call].try_emplace({post.path, ancestor}); + if (inserted) + saved->second = {places.create("call enclosing prefix footprint"), + places.create("call enclosing head footprint")}; + const auto snapshot = saved->second; + state.safety->footprints.assign(snapshot.first, + {{ancestor, 1}, {actual->place, -1}}); + state.safety->footprints.assign(snapshot.second, + {{footprintHead(ancestor), 1}}); + ContainerPrefix frame{.ancestor = ancestor, + .fact = *prefix, + .snapshot = snapshot, + .separated = {}}; + for (const auto neighbor : state.safety->containers.separatedFrom(ancestor)) + if (const auto *fact = state.safety->containers.find(neighbor)) + frame.separated.emplace(neighbor, *fact); + containerPrefixPosts[&call][post].push_back(std::move(frame)); + } +} + +void FunctionDataflow::applyContainerPrefixes( + const CallExpr &call, const core::CheckedRequirement &post, + core::PlaceId holder, core::AnalysisState &state) { + const auto found = containerPrefixPosts.find(&call); + if (found == containerPrefixPosts.end()) + return; + const auto frames = found->second.find(post); + auto &facts = state.safety->containers; + const auto *current = facts.find(holder); + if (frames == found->second.end() || !current || current->empty) + return; + auto output = *current; + for (const auto &frame : frames->second) { + auto required = frame.fact.shape; + required.terminal = false; + required.emptyLinks.clear(); + required.headValues.clear(); + required.emptyPayloads.clear(); + if (!output.entails(required)) + continue; + auto prefix = frame.fact; + prefix.members.insert(output.members.begin(), output.members.end()); + prefix.inputs.insert(output.inputs.begin(), output.inputs.end()); + prefix.allocationCompatible &= output.allocationCompatible; + prefix.localAllocation &= output.localAllocation; + facts.set(frame.ancestor, std::move(prefix)); + state.safety->pointers.insert(frame.ancestor); + state.safety->invalidatedPointers.erase(frame.ancestor); + state.safety->footprints.assign(frame.ancestor, + {{frame.snapshot.first, 1}, {holder, 1}}); + state.safety->footprints.assign(footprintHead(frame.ancestor), + {{frame.snapshot.second, 1}}); + state.safety->unfoldedFootprints.erase(frame.ancestor); + for (const auto &[neighbor, before] : frame.separated) + if (const auto *surviving = facts.find(neighbor); + surviving && *surviving == before && + facts.separated(holder, neighbor)) + facts.separate(frame.ancestor, neighbor); + output.ancestors.insert(frame.ancestor); + snapshotContainerOutput(frame.ancestor, state); + } + facts.set(holder, std::move(output)); +} + void FunctionDataflow::invalidateContainers(core::PlaceId holder, bool release, bool keepTail, core::AnalysisState &state) { @@ -253,18 +536,158 @@ void FunctionDataflow::invalidateContainers(core::PlaceId holder, bool release, } } +bool FunctionDataflow::preserveInputContainersAcrossLocalWrite( + core::PlaceId storage, core::AnalysisState &state) { + const auto root = places.root(storage); + const auto *var = builder.varForPlace(root); + const bool automatic = var != nullptr && var->hasLocalStorage() && + !isa(var) && + !places.innermostDeref(storage); + const bool fresh = + std::ranges::any_of(checkedObjects, [&](const auto &entry) { + return entry.second == storage; + }); + if (!automatic && !fresh) + return false; + // RFC 0029: a live entry forest predates this local object. Every + // current member must still have entry provenance; root separation alone + // cannot frame an attached local node or an unknown callee-produced region. + // A forest made only of this invocation's heap allocations is likewise + // disjoint from an automatic object; a fresh destination could be a member. + std::vector retired; + for (const auto &[holder, fact] : state.safety->containers.all()) { + const bool named = holder == root || places.isDescendantOf(holder, root); + const bool allocated = + automatic && fact.localAllocation && fact.inputs.empty(); + if (named || (!allocated && + (fact.inputs.empty() || + !std::ranges::all_of(fact.members, [&](const auto member) { + return containerInputs.contains(member); + })))) + retired.push_back(holder); + } + for (const auto holder : retired) { + state.safety->containers.block(holder); + state.safety->footprints.forget(holder); + state.safety->unfoldedFootprints.erase(holder); + } + return true; +} + +bool FunctionDataflow::preserveFreshContainersAcrossWrite( + core::PlaceId cell, core::AnalysisState &state) { + if (places.step(cell) != core::PathStep::Deref && + (places.step(cell) != core::PathStep::Field || + !isa_and_nonnull(builder.declFor(cell)))) + return false; + const auto object = places.innermostDeref(cell); + const auto holder = object ? places.parent(*object) : std::nullopt; + if (!holder) + return false; + const auto memory = checkedMemoryAt(*holder, {}, {}, state); + if (!memory || !memory->input || !checkedValid(*memory, state) || + state.resources.isEscaped(*holder)) + return false; + // RFC 0029: an unchanged entry object predates all fresh members. Incoming + // singleton heads additionally need explicit separation from that object; + // distinct parameter names alone cannot preserve an ownership selector. + std::vector retired; + for (const auto &[other, fact] : state.safety->containers.all()) { + bool separate = fact.localAllocation && fact.inputs.empty(); + std::set entries; + if (!separate && fact.allocationCompatible && !fact.inputs.empty()) { + // A derived predicate's non-fresh members belong to these explicit + // inputs. Its synthetic call region can also contain proved fresh + // descendants; it is not an additional unknown incoming region. + separate = std::ranges::all_of(fact.inputs, [&](const auto member) { + if (const auto input = containerInputs.find(member); + input != containerInputs.end()) { + const auto shape = containerInputShapes.find(member); + if (input->second == *memory->input || + shape == containerInputShapes.end() || + !shape->second.singletonHead()) + return false; + entries.insert(member); + return true; + } + return false; + }); + } + if (!separate || other == cell || places.isDescendantOf(other, cell)) { + retired.push_back(other); + } else if (recording()) { + for (const auto entry : entries) { + inferred.checked.require( + {.kind = core::CheckedRequirementKind::Container, + .path = containerInputs.at(entry), + .other = {}, + .family = containerInputShapes.at(entry).encode()}); + inferred.checked.require( + {.kind = core::CheckedRequirementKind::Separated, + .path = *memory->input, + .other = containerInputs.at(entry), + .family = {}}); + } + } + } + for (const auto other : retired) { + state.safety->containers.block(other); + state.safety->footprints.forget(other); + state.safety->unfoldedFootprints.erase(other); + } + return true; +} + void FunctionDataflow::checkedContainerStore(const Stmt &stmt, core::AnalysisState &state) { + const auto localValueWrite = [&](const Expr &value) { + const auto ref = builder.resolve(value); + if (!ref || !ref->element.isWhole()) { + const auto memory = checkedLvalue(value, state); + return memory && + preserveInputContainersAcrossLocalWrite(memory->storage, state); + } + if (!ref->derefs.empty() || !isLocalStorage(ref->place)) { + if (const auto memory = checkedLvalue(value, state); + memory && + preserveInputContainersAcrossLocalWrite(memory->storage, state)) + return true; + return preserveFreshContainersAcrossWrite(ref->place, state); + } + const auto *var = builder.varForPlace(places.root(ref->place)); + if (!var || addressTaken.contains(var->getCanonicalDecl())) { + const auto memory = checkedLvalue(value, state); + return memory && + preserveInputContainersAcrossLocalWrite(memory->storage, state); + } + // Changing an unexposed automatic pointer/counter cell cannot change a + // separate heap object's fields. Retire names reached through that cell; + // an old exact-node predicate does not survive pointer arithmetic. + std::vector retired{ref->place}; + for (const auto &[holder, fact] : state.safety->containers.all()) { + (void)fact; + if (holder != ref->place && places.isDescendantOf(holder, ref->place)) + retired.push_back(holder); + } + for (const auto holder : retired) { + state.safety->containers.replace(holder); + state.safety->footprints.forget(holder); + state.safety->unfoldedFootprints.erase(holder); + } + return true; + }; if (const auto *increment = dyn_cast(&stmt); increment && increment->isIncrementDecrementOp() && increment->getType()->isPointerType()) { - state.safety->containers.clear(); + if (!localValueWrite(*increment->getSubExpr())) + state.safety->containers.clear(); return; } const auto *assignment = dyn_cast(&stmt); if (assignment && assignment->isCompoundAssignmentOp() && assignment->getLHS()->getType()->isPointerType()) { - state.safety->containers.clear(); + if (!localValueWrite(*assignment->getLHS())) + state.safety->containers.clear(); return; } if (!assignment || !assignment->isAssignmentOp()) @@ -285,16 +708,27 @@ void FunctionDataflow::checkedContainerStore(const Stmt &stmt, // Arbitrary byte and indirect stores can corrupt a previously folded // object. Direct holder replacement is handled by installCheckedPointer. if (!isa(assignment->getLHS()->IgnoreParenImpCasts()) && - !assignment->getLHS()->getType()->isPointerType()) + !assignment->getLHS()->getType()->isPointerType() && + !localValueWrite(*assignment->getLHS())) state.safety->containers.clear(); return; } const auto *field = dyn_cast(member->getMemberDecl()); const auto holder = builder.resolvePointerValue(*member->getBase()); if (!field || !holder || !holder->element.isWhole()) { + if (const auto memory = checkedLvalue(*assignment->getLHS(), state); + memory && + preserveInputContainersAcrossLocalWrite(memory->storage, state)) + return; state.safety->containers.clear(); return; } + if (!containerShapes.contains(field->getParent()) && + !state.safety->containers.find(holder->place)) + if (const auto memory = checkedLvalue(*assignment->getLHS(), state); + memory && + preserveInputContainersAcrossLocalWrite(memory->storage, state)) + return; foldContainerStores(holder->place, *field->getParent(), field, stmt, state); } @@ -308,8 +742,28 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, const auto *candidate = discovered == containerShapes.end() ? nullptr : &discovered->second; const auto *old = facts.find(holder); - if (!candidate && !old) + if (old && field && old->shape.access != core::ContainerAccess::Release && + std::ranges::any_of(old->shape.payloads, [&](const auto &payload) { + return field->getName() == payload.field.name; + })) { + auto owned = old->shape; + owned.access = core::ContainerAccess::Release; + owned.family = "free"; + if (const auto strengthened = strengthenContainer(*old, owned)) { + facts.set(holder, *strengthened); + old = facts.find(holder); + } + } + if (!candidate && !old) { + if (const auto memory = checkedMemoryAt(holder, {}, {}, state); + memory && + preserveInputContainersAcrossLocalWrite(memory->storage, state)) + return; + if (!field || !preserveFreshContainersAcrossWrite( + builder.fieldPlace(places.deref(holder), *field), state)) + facts.clear(); return; + } auto shape = old ? old->shape : *candidate; const auto previous = old && field ? std::optional(*old) : std::nullopt; const bool linkWrite = @@ -337,6 +791,16 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, return; const auto memory = checkedMemoryAt(holder, {}, {}, state); + // RFC 0029: an attachment can be the first operation that exposes a + // concrete ownership forest. Prove that entire graph before resorting to + // a compositional fold, which needs an already established parent frame. + // The graph checks initialized links, allocation bases and unique ownership + // for every reachable member, including zero-initialized calloc fields. + if (!previous && (linkWrite || payloadWrite) && memory) + if (const auto concrete = establishContainer(*memory, shape, state)) { + facts.set(holder, *concrete); + return; + } if (payloadWrite && field && memory && previous && previous->localAllocation && previous->releasedChildren.empty() && previous->releasedPayloads.empty()) { @@ -414,6 +878,10 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, if (!shape.recursiveLink(link->getNameAsString())) continue; const auto cell = builder.fieldPlace(places.deref(holder), *link); + const auto childField = std::ranges::find( + shape.children, link->getNameAsString(), &core::ContainerField::name); + const auto &descriptor = + childField == shape.children.end() ? shape.link : *childField; auto required = shape; required.terminal = false; required.emptyLinks.clear(); @@ -471,7 +939,8 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, head->shape.family = stored->shape.family; } } - } else if (state.nulls.stateOf(cell) == core::Nullness::Null) { + } else if (state.nulls.stateOf(cell) == core::Nullness::Null || + containerZeroField(holder, descriptor, state)) { tail = core::ContainerFact{ .shape = required, .members = {}, .inputs = {}, .empty = true}; } else if (!linkWrite || link != field) { @@ -497,13 +966,25 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, } std::vector> prefixes; std::map prefixFrames; + std::map> prefixSeparation; std::vector savedTails; std::vector preservedSeparation; if (complete) { for (const auto &[other, fact] : facts.all()) { if (fact.tailOf == holder) savedTails.push_back(other); - if (previous && previous->ancestors.contains(other) && + auto required = fact.shape; + required.terminal = false; + required.emptyLinks.clear(); + required.headValues.clear(); + required.emptyPayloads.clear(); + const auto alias = state.definiteAliases.offsetOf(holder, other); + const bool sameHead = other == holder || (alias && alias->isZero()); + if (previous && head && !sameHead && + !state.safety->invalidatedPointers.contains(other) && + !state.moves.recordOf(other) && head->shape.entails(required) && + (previous->ancestors.contains(other) || + (previous->tailOf == other && !previous->tailField.empty())) && std::ranges::all_of(children, [&](const auto &child) { return child.fact.empty || child.fact.tailOf == holder || separateContainers(other, child.cell, stmt, state); @@ -516,6 +997,12 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, const auto frame = saved->second.first; state.safety->footprints.assign(frame, {{other, 1}, {holder, -1}}); prefixFrames.emplace(other, frame); + for (const auto neighbor : facts.separatedFrom(other)) + if (std::ranges::all_of(children, [&](const auto &child) { + return child.fact.empty || + facts.separated(child.cell, neighbor); + })) + prefixSeparation[other].insert(neighbor); } if (facts.separated(holder, other) && std::ranges::all_of(children, [&](const auto &child) { @@ -590,8 +1077,17 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, if (!active) return; const bool empty = state.nulls.stateOf(cell) == core::Nullness::Null; + bool unchangedOwnership = false; + if (previous && conditionWrite) + if (const auto condition = shape.ownership.find(payload.field.name); + condition != shape.ownership.end()) + if (const auto value = + previous->shape.headValues.find(condition->second.field.name); + value != previous->shape.headValues.end()) + unchangedOwnership = ((value->second & condition->second.mask) == + condition->second.value) == *active; if (*active && !empty && - (!previous || conditionWrite || + (!previous || (conditionWrite && !unchangedOwnership) || (field && field->getName() == payload.field.name) || previous->releasedPayloads.contains(payload.field.name))) { const auto *assignment = dyn_cast(&stmt); @@ -602,11 +1098,18 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, origin.call ? resolvedLibraryName(*origin.call) : std::string{}; const auto resource = state.resources.recordOf(cell); const auto bytes = checkedMemoryAt(cell, {}, {}, state); - // A fresh nullable allocation is either empty or a separate allocation - // base. A copied pointer, borrowed object or stale field supplies no such - // separation from the existing inductive object and its other payloads. + // A direct fresh nullable allocation is empty or a separate base. A + // local copy needs independent live acquisition and storage evidence; + // its spelling alone cannot fold borrowed or duplicated ownership. + const bool directAllocation = + allocation == "malloc" || allocation == "calloc"; + const bool copiedAllocation = origin.kind == ValueOrigin::Kind::Copy && + origin.place && origin.offset.isZero() && + bytes && + !head->members.contains(bytes->storage) && + checkedValid(*bytes, state); if (!previous || !field || field->getName() != payload.field.name || - (allocation != "malloc" && allocation != "calloc") || !resource || + (!directAllocation && !copiedAllocation) || !resource || resource->origin != core::ResourceOrigin::Allocated || resource->escaped || resource->family != payload.family || !bytes || bytes->begin != core::Affine::ofConstant(0) || @@ -638,12 +1141,40 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, head->tailField.clear(); head->releasedChildren.clear(); head->ancestors.clear(); + for (const auto &[ancestor, fact] : prefixes) { + (void)fact; + head->ancestors.insert(ancestor); + } + if (previous && previous->tailOf && + head->ancestors.contains(*previous->tailOf)) { + head->tailOf = previous->tailOf; + head->tailField = previous->tailField; + } head->empty = false; head->suffix = true; // A fold can represent unbounded allocation instances. facts.set(holder, *head); facts.publish(holder); for (const auto other : preservedSeparation) facts.separate(holder, other); + std::vector headAliases{holder}; + for (const auto &[alias, edge] : state.definiteAliases.edgesFrom(holder)) { + if (alias == holder || !edge.exact() || + !state.definiteAliases.sameShare(holder, alias) || + state.moves.recordOf(alias) || + state.safety->invalidatedPointers.contains(alias)) + continue; + headAliases.push_back(alias); + facts.set(alias, *head); + facts.publish(alias); + state.safety->footprints.assign(alias, {{holder, 1}}); + state.safety->footprints.assign(footprintHead(alias), + {{footprintHead(holder), 1}}); + state.safety->unfoldedFootprints.erase(alias); + state.safety->pointers.insert(alias); + for (const auto separate : facts.separatedFrom(holder)) + facts.separate(alias, separate); + snapshotContainerOutput(alias, state); + } std::vector> descendants; for (const auto &child : children) { if (!child.owned || child.fact.empty) @@ -654,7 +1185,15 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, const auto alias = state.definiteAliases.offsetOf(other, child.cell); if (other == child.cell || (alias && alias->isZero())) { auto descendant = fact; - descendant.ancestors.insert(holder); + descendant.ancestors.insert(headAliases.begin(), headAliases.end()); + descendant.ancestors.insert(head->ancestors.begin(), + head->ancestors.end()); + // The completed fold proves this exact edge. Retain its proper-child + // identity when another child slot is subsequently attached; the + // existing child now overlaps its parent and is no longer a separate + // forest. Sibling uniqueness was checked before publishing the fold. + descendant.tailOf = holder; + descendant.tailField = std::string(places.fieldName(child.cell)); descendants.emplace_back(other, std::move(descendant)); } } @@ -682,6 +1221,9 @@ void FunctionDataflow::foldContainerStores(core::PlaceId holder, const auto frame = prefixFrames.at(other); state.safety->footprints.assign(other, {{frame, 1}, {holder, 1}}); state.safety->footprints.forget(frame); + for (const auto neighbor : prefixSeparation[other]) + if (facts.find(neighbor)) + facts.separate(other, neighbor); } } @@ -702,6 +1244,8 @@ void FunctionDataflow::checkedContainersAfterCall(const CallExpr &call, invalidateContainers(release->second, true, true, state); return; } + if (containerLocalReleases.contains(&call)) + return; if (!effects || !effects->summary) { state.safety->containers.clear(); return; @@ -711,6 +1255,24 @@ void FunctionDataflow::checkedContainersAfterCall(const CallExpr &call, if (name == "malloc" || name == "calloc" || name == "strlen" || name == "strnlen") return; + if (const auto *model = runtimeModel(name); + model && runtimeSignature(*model, call, context)) { + if (model->family == RuntimeFamily::Compare || + model->family == RuntimeFamily::Numeric) + return; + if (model->family == RuntimeFamily::ParseNumeric) { + const auto output = builder.classifyValue(*call.getArg(1)); + if (output.kind == ValueOrigin::Kind::Null || + (output.kind == ValueOrigin::Kind::Copy && output.place && + output.offset.isZero() && + state.nulls.stateOf(output.place->place) == core::Nullness::Null)) + return; + if (const auto memory = checkedMemory(*call.getArg(1), {}, {}, state); + memory && + preserveInputContainersAcrossLocalWrite(memory->storage, state)) + return; + } + } if (name == "free" && call.getNumArgs() == 1) { const auto origin = builder.classifyValue(*call.getArg(0)); if (origin.kind == ValueOrigin::Kind::Null || @@ -718,7 +1280,19 @@ void FunctionDataflow::checkedContainersAfterCall(const CallExpr &call, state.nulls.stateOf(origin.place->place) == core::Nullness::Null)) return; } - state.safety->containers.clear(); + bool framed = false; + if ((name == "memset" || name == "__builtin_memset" || + name == "__builtin___memset_chk" || name == "memcpy" || + name == "__builtin_memcpy" || name == "__builtin___memcpy_chk" || + name == "memmove" || name == "__builtin_memmove" || + name == "__builtin___memmove_chk") && + call.getNumArgs() > 0) + if (const auto destination = + checkedMemory(*call.getArg(0), {}, {}, state)) + framed = preserveInputContainersAcrossLocalWrite(destination->storage, + state); + if (!framed) + state.safety->containers.clear(); if ((name == "memset" || name == "__builtin_memset" || name == "__builtin___memset_chk") && call.getNumArgs() > 0) @@ -748,10 +1322,38 @@ void FunctionDataflow::checkedContainersAfterCall(const CallExpr &call, if (!shape || shape->access == core::ContainerAccess::Read) continue; handled.emplace_back(requirement.path, *shape); - if (const auto actual = builder.resolveSummaryPath(requirement.path, call)) + const bool extends = std::ranges::any_of( + effects->summary->checked.establishes, [&](const auto &post) { + return post.kind == core::CheckedRequirementKind::ContainerExtended && + post.path == requirement.path && post.other == post.path && + !post.on && post.when.trivial(); + }); + if (const auto actual = + builder.resolveSummaryPath(requirement.path, call)) { + const bool selectorWritten = + std::ranges::any_of(shape->ownership, [&](const auto &entry) { + const auto selector = + requirement.path.deref().field(entry.second.field.name); + return std::ranges::any_of( + effects->summary->effects, [&](const auto &operation) { + return operation.second.written && + (operation.first == selector || + operation.first.isProperPrefixOf(selector)); + }); + }); + if (selectorWritten) + for (const auto &[holder, fact] : state.safety->containers.all()) + if (!state.safety->containers.separated(actual->place, holder)) + for (const auto &[name, condition] : fact.shape.ownership) { + (void)condition; + state.safety->footprints.forget( + footprintContribution(holder, name)); + } invalidateContainers(actual->place, - shape->access == core::ContainerAccess::Release, + shape->access == core::ContainerAccess::Release && + !extends, false, state); + } } for (const auto &[path, effect] : effects->summary->effects) if ((effect.written || effect.consumed()) && @@ -767,6 +1369,20 @@ void FunctionDataflow::checkedContainersAfterCall(const CallExpr &call, if (const auto actual = contextPlace(path, state); actual && actual->second->isArithmeticType()) continue; + if (effect.written && !effect.consumed()) + if (const auto actual = builder.resolveSummaryPath(path, call); + actual && actual->element.isWhole()) { + if (preserveInputContainersAcrossLocalWrite(actual->place, state)) + continue; + if (const auto object = places.innermostDeref(actual->place)) + if (const auto holder = places.parent(*object)) + if (const auto memory = checkedMemoryAt(*holder, {}, {}, state); + memory && preserveInputContainersAcrossLocalWrite( + memory->storage, state)) + continue; + if (preserveFreshContainersAcrossWrite(actual->place, state)) + continue; + } state.safety->containers.clear(); break; } diff --git a/lib/Analysis/DataflowContainers.cpp b/lib/Analysis/DataflowContainers.cpp index eecedece..d286a869 100644 --- a/lib/Analysis/DataflowContainers.cpp +++ b/lib/Analysis/DataflowContainers.cpp @@ -20,6 +20,43 @@ using namespace clang; namespace weavec::analysis { +std::optional +FunctionDataflow::containerValueFact(core::PlaceId cell, + const core::AnalysisState &state) const { + if (!state.safety || places.isBase(cell) || + places.step(cell) != core::PathStep::Field) + return std::nullopt; + const auto object = places.parent(cell); + if (!object || places.isBase(*object) || + places.step(*object) != core::PathStep::Deref) + return std::nullopt; + const auto holder = places.parent(*object); + if (!holder || !state.nulls.isNonNull(*holder) || + state.moves.recordOf(*holder) || + state.safety->invalidatedPointers.contains(*holder)) + return std::nullopt; + const auto *fact = state.safety->containers.find(*holder); + if (!fact || fact->empty) + return std::nullopt; + const std::string field(places.fieldName(cell)); + if (fact->shape.emptyPayloads.contains(field) || + (fact->shape.recursiveLink(field) && + (fact->shape.terminal || fact->shape.emptyLinks.contains(field)))) + return core::ValueFact::of(core::Outcome::Null); + const auto known = fact->shape.headValues.find(field); + const auto record = containerRecords.find(fact->shape.object.toString()); + if (known == fact->shape.headValues.end() || record == containerRecords.end()) + return std::nullopt; + for (const auto *selector : record->second->fields()) + if (selector->getName() == field && !selector->isBitField() && + !selector->getType().isVolatileQualified() && + !selector->getType()->isAtomicType()) + if (const auto type = integerTypeOf(*selector, context)) + return core::ValueFact::ofInteger(core::IntegerRange::singleton( + core::IntegerValue::ofBits(*type, known->second))); + return std::nullopt; +} + static QualType containerRecordType(const RecordDecl *record, const ASTContext &ctx) { #if CLANG_VERSION_MAJOR >= 23 @@ -162,8 +199,10 @@ FunctionDataflow::containerShape(QualType type) const { void FunctionDataflow::discoverContainers() { std::map candidates; std::set localRecords; + std::set producedRecords; std::set releases; std::set writes; + std::set payloadStores; std::map> payloads; const auto note = [&](const Expr *expr, unsigned weight) { const auto *member = @@ -180,6 +219,17 @@ void FunctionDataflow::discoverContainers() { const auto *stmt = workItems[i]; if (!stmt) continue; + if (const auto *compound = dyn_cast(stmt)) { + const BinaryOperator *previous = nullptr; + for (const auto *child : compound->body()) { + const auto *assignment = dyn_cast(child); + if (previous && assignment && assignment->getOpcode() == BO_Assign) + payloadRelocationClears.emplace(previous, assignment); + previous = assignment && assignment->getOpcode() == BO_Assign + ? assignment + : nullptr; + } + } if (const auto *call = dyn_cast(stmt)) if (const auto *callee = call->getDirectCallee()) if (const auto summary = summaries.lookup(*callee)) { @@ -191,7 +241,7 @@ void FunctionDataflow::discoverContainers() { container) || std::ranges::any_of(summary->summary->checked.establishes, container); - // RFC 0028: forwarding an opaque parameter may infer a sufficient + // RFC 0028/0029: forwarding a parameter may infer a sufficient // entry predicate. Nomination supplies no fact about a local value: // every closed caller still has to establish this input contract. for (const auto &requirement : @@ -208,11 +258,15 @@ void FunctionDataflow::discoverContainers() { !parameter->getType()->isPointerType()) continue; const auto pointee = parameter->getType()->getPointeeType(); - if (!pointee->isRecordType() || !pointee->isIncompleteType()) + if (!pointee->isRecordType()) continue; const auto shape = core::ContainerShape::decode(requirement.family); if (!shape || - summaries.interfaceType(shape->object.identity).isNull()) + (pointee->isIncompleteType() && + summaries.interfaceType(shape->object.identity).isNull())) + continue; + if (!pointee->isIncompleteType() && + checkedObjectType(pointee) != shape->object.toString()) continue; const auto place = builder.placeForVar(*parameter); const auto [found, inserted] = @@ -226,16 +280,23 @@ void FunctionDataflow::discoverContainers() { } } if (const auto *call = dyn_cast(stmt)) - if (const auto *record = containerRecord(call->getType())) + if (const auto *record = containerRecord(call->getType())) { localRecords.insert(record); + producedRecords.insert(record); + } if (const auto *assignment = dyn_cast(stmt); assignment && assignment->getOpcode() == BO_Assign) { note(assignment->getRHS(), 2); note(assignment->getLHS(), 1); if (const auto *member = dyn_cast(assignment->getLHS()->IgnoreParenImpCasts())) - if (const auto *field = dyn_cast(member->getMemberDecl())) + if (const auto *field = dyn_cast(member->getMemberDecl())) { writes.insert(field->getParent()); + if (field->getType()->isPointerType() && + !assignment->getRHS()->isNullPointerConstant( + context, Expr::NPC_ValueDependentIsNotNull)) + payloadStores.insert(field); + } if (const auto *rhs = dyn_cast(assignment->getRHS()->IgnoreParenImpCasts())) if (const auto *field = dyn_cast(rhs->getMemberDecl()); @@ -290,10 +351,21 @@ void FunctionDataflow::discoverContainers() { // RFC 0027: all recursive functions on a record nominate the same topology. // This includes constructors whose local stores do not choose one cursor. for (const auto *param : function.parameters()) - if (const auto *record = containerRecord(param->getType())) + if (const auto *record = containerRecord(param->getType())) { chosen.try_emplace(record); + if (const auto *group = summaries.recursiveContractGroup(function); + group && group->releases) + releases.insert(record); + } for (const auto *record : localRecords) chosen.try_emplace(record); + if (const auto *group = summaries.recursiveContractGroup(function); + group && group->constructs) + if (const auto *record = containerRecord( + function.getNumParams() == 3 + ? function.getParamDecl(2)->getType()->getPointeeType() + : function.getReturnType())) + chosen.try_emplace(record); for (auto &[record, selected] : chosen) { const auto links = summaries.recursiveLinks(*record); if (!links.empty()) { @@ -348,18 +420,34 @@ void FunctionDataflow::discoverContainers() { if (releases.contains(record)) { shape.access = core::ContainerAccess::Release; shape.family = "free"; - for (const auto *field : payloads[record]) { - const auto descriptor = containerField(*field, context); - if (!descriptor || shape.recursiveLink(field->getNameAsString())) { - valid = false; - break; - } - shape.payloads.push_back({.field = *descriptor, .family = "free"}); + } + const auto imported = summaries.containerFields(*record); + for (const auto *field : imported.payloads) + payloads[record].insert(field); + for (const auto *field : payloads[record]) { + const auto descriptor = containerField(*field, context); + if (!descriptor || shape.recursiveLink(field->getNameAsString())) { + valid = false; + break; } + shape.payloads.push_back({.field = *descriptor, .family = "free"}); } shape.ownership = summaries.containerOwnership(*record, [&] { return containerOwnershipCandidates(*record, context); }); + for (const auto &[name, condition] : imported.ownership) { + const bool matches = + std::ranges::any_of(record->fields(), [&](const auto *field) { + return field->getName() == condition.field.name && + field->getType()->isIntegerType() && + containerField(*field, context) == condition.field; + }); + if (!matches) + continue; + const auto [found, inserted] = shape.ownership.emplace(name, condition); + if (!inserted && found->second != condition) + shape.ownership.erase(found); + } for (const auto &[name, condition] : shape.ownership) { (void)condition; if (shape.recursiveLink(name) || @@ -374,6 +462,22 @@ void FunctionDataflow::discoverContainers() { } std::ranges::sort(shape.initialized); std::ranges::sort(shape.payloads); + if (producedRecords.contains(record) && + std::ranges::any_of(payloadStores, [&](const auto *field) { + return field->getParent() == record && + shape.recursiveLink(field->getNameAsString()); + })) + containerLinkWriters.insert(record); + if (std::ranges::any_of(payloadStores, [&](const auto *field) { + return field->getParent() == record && + std::ranges::any_of(shape.payloads, [&](const auto &payload) { + return field->getName() == payload.field.name; + }); + })) { + shape.access = core::ContainerAccess::Release; + shape.family = "free"; + containerPayloadWriters.insert(record); + } if (valid && shape.valid()) { containerShapes.emplace(record, shape); containerRecords.emplace(shape.object.toString(), record); @@ -399,6 +503,7 @@ FunctionDataflow::containerInput(core::PlaceId holder, } void FunctionDataflow::refineContainers(core::AnalysisState &state) { + refineAllocationFootprints(state); for (const auto &[holder, head] : footprintHeads) if (state.nulls.stateOf(holder) == core::Nullness::Null) { state.safety->footprints.constrain({{holder, 1}}); @@ -475,8 +580,10 @@ void FunctionDataflow::initializeContainers(core::AnalysisState &state) { if (const auto opaque = opaqueContainerParameters.find(place); opaque != opaqueContainerParameters.end() && opaque->second) { shape = &*opaque->second; - const auto type = summaries.interfaceType(shape->object.identity); - record = type.isNull() ? nullptr : type->getAsRecordDecl(); + if (!record) { + const auto type = summaries.interfaceType(shape->object.identity); + record = type.isNull() ? nullptr : type->getAsRecordDecl(); + } if (!record) continue; for (const auto *field : record->fields()) @@ -517,9 +624,41 @@ void FunctionDataflow::initializeContainers(core::AnalysisState &state) { fact.shape.terminal = true; fact.shape.emptyLinks.clear(); } + if (containerLinkWriters.contains(record) && fact.shape.singletonHead()) { + fact.shape.access = core::ContainerAccess::Release; + fact.shape.family = "free"; + } + if ((containerPayloadWriters.contains(record) || + (fact.shape.singletonHead() && + (shape->access != core::ContainerAccess::Release || + containerLinkWriters.contains(record)))) && + fact.shape != *shape) + fact = containerInput( + place, core::SummaryPath::param(param->getFunctionScopeIndex()), + fact.shape); } + // RFC 0029: the payload writer's nominated ownership is an entry + // premise on every path, including an early allocation failure. Install + // it before final CFG visitation so return order cannot lose that frame. + if (containerPayloadWriters.contains(record) || + (containerLinkWriters.contains(record) && fact.shape.singletonHead())) + inferred.checked.require( + {.kind = core::CheckedRequirementKind::Container, + .path = core::SummaryPath::param(param->getFunctionScopeIndex()), + .other = {}, + .family = fact.shape.encode()}); state.safety->containers.set(place, std::move(fact)); } + // RFC 0029: a complete singleton is exactly its head object, so an explicit + // distinct-object case premise separates two such incoming footprints. + for (const auto &[first, second] : state.distinctObjects) { + const auto *a = state.safety->containers.find(first); + const auto *b = state.safety->containers.find(second); + if (a && b && !a->empty && !b->empty && a->shape.singletonHead() && + b->shape.singletonHead() && a->inputs.size() == 1 && + b->inputs.size() == 1 && a->inputs != b->inputs) + state.safety->containers.separate(first, second); + } // Definite entry aliases of a proper child carry that child's structural // witness and allocation identity, rather than a second independent input. for (const auto &alias : memoryContext.aliases) { @@ -599,11 +738,35 @@ FunctionDataflow::containerAt(core::PlaceId holder, .shape = shape, .members = {}, .inputs = {}, .empty = true}; if (state.moves.recordOf(holder) || state.raw.isRaw(holder)) return std::nullopt; - if (const auto *fact = state.safety->containers.find(holder)) - if (const auto sufficient = strengthenContainer(*fact, shape)) { + if (const auto *fact = state.safety->containers.find(holder)) { + auto current = *fact; + if (!current.empty && state.nulls.isNonNull(holder) && + !state.safety->invalidatedPointers.contains(holder)) { + const auto nullField = [&](const std::string &name) { + return state.nulls.stateOf(places.field(places.deref(holder), name)) == + core::Nullness::Null; + }; + for (const auto &payload : current.shape.payloads) + if (nullField(payload.field.name)) + current.shape.emptyPayloads.insert(payload.field.name); + if (!current.shape.terminal) { + if (nullField(current.shape.link.name)) + current.shape.emptyLinks.insert(current.shape.link.name); + for (const auto &child : current.shape.children) + if (nullField(child.name)) + current.shape.emptyLinks.insert(child.name); + if (current.shape.emptyLinks.size() == + current.shape.children.size() + 1) { + current.shape.terminal = true; + current.shape.emptyLinks.clear(); + } + } + } + if (const auto sufficient = strengthenContainer(current, shape)) { state.safety->containers.set(holder, *sufficient); return sufficient; } + } if (places.step(holder) == core::PathStep::Field) { const auto parent = places.parent(holder); const auto pointer = parent && places.step(*parent) == core::PathStep::Deref @@ -750,6 +913,8 @@ bool FunctionDataflow::checkedContainerAccess(const Expr &expr, bool read, return false; if (!requireContainer(*fact, expr, state)) return false; + if (write) + unfoldFootprint(holder->place, *fact, state); const auto outcome = core::safetyOutcome(fact->inputs.empty(), !fact->inputs.empty()); safetyObligation(core::SafetyProperty::Bounds, outcome, expr, @@ -1121,11 +1286,20 @@ bool FunctionDataflow::checkedContainerRelease(const CallExpr &call, if (!fact && footprintHeads.contains(holder)) { const auto resource = state.resources.recordOf(holder); const auto memory = checkedMemoryAt(holder, {}, {}, state); + auto when = resource ? resource->guard : core::PlaceGuard{}; + auto nonNull = state; + nonNull.nulls.set(holder, {.state = core::Nullness::NonNull, + .location = {}, + .reason = core::NullReason::Declared}); if (resource && resource->origin == core::ResourceOrigin::Allocated && resource->family == "free" && !resource->escaped && memory && memory->begin == core::Affine::ofConstant(0) && - checkedValid(*memory, state)) + checkedValid(*memory, nonNull) && pruneGuard(when, nonNull) && + when.trivial()) { releaseFootprint(holder, false, state); + if (preserveInputContainersAcrossLocalWrite(memory->storage, state)) + containerLocalReleases.insert(&call); + } } if (!fact || fact->shape.access != core::ContainerAccess::Release || fact->shape.family != "free" || state.moves.recordOf(holder)) diff --git a/lib/Analysis/DataflowCursors.cpp b/lib/Analysis/DataflowCursors.cpp index d78a07f3..ae474dbf 100644 --- a/lib/Analysis/DataflowCursors.cpp +++ b/lib/Analysis/DataflowCursors.cpp @@ -8,6 +8,7 @@ #include "AffineSupport.h" #include "Dataflow.h" +#include "IntegerSupport.h" using namespace clang; @@ -38,7 +39,40 @@ bool FunctionDataflow::checkedPointerComparable(const BinaryOperator &expr, state); return live && bounds; }; - return position(*a) && position(*b); + if (!position(*a) || !position(*b)) + return false; + if (expr.getOpcode() == BO_Sub && aType->getPointeeType()->isCharType() && + a->inputPlace && bufferFact(*a->inputPlace, state) && a->extent && + a->extent == b->extent && a->extent->place && a->extent->scale == 1 && + a->extent->constant == 0) { + const auto type = integerTypeOf(expr.getType(), context); + const auto maximum = + type ? core::IntegerRange::full(*type).maximum() : std::nullopt; + const auto limit = maximum ? maximum->signedValue() : std::nullopt; + if (limit && + !checkedAtMost(*a->extent, core::Affine::ofConstant(*limit), state)) + if (const auto extent = summaryAffineOf(a->extent)) { + // RFC 0029: an explicit sufficient entry bound proves ptrdiff_t + // representability, independently of provenance and cursor bounds. + const auto slack = + NumericExpression::constant(core::IntegerValue::ofBits( + CursorType, + CursorType.mask() - static_cast(*limit))); + const auto projected = summaryIntegerExpression(slack); + if (projected) { + if (recording()) + inferred.checked.require( + {.kind = core::CheckedRequirementKind::SumFits, + .path = {}, + .other = {}, + .begin = *extent, + .end = core::PathAffine::ofExpression(*projected), + .family = {}}); + state.relations.learnAtMost(*a->extent->place, *limit); + } + } + } + return true; } void FunctionDataflow::checkedPointerCondition(const BinaryOperator &expr, @@ -85,6 +119,34 @@ void FunctionDataflow::checkedPointerCondition(const BinaryOperator &expr, } if (!relation || lhs.scale != 1 || rhs.scale != 1) return; + const auto strictlyLess = [&](const core::Affine &a, const core::Affine &b) { + const auto next = a.shifted(1); + return next && checkedAtMost(*next, b, state); + }; + bool impossible = false; + switch (*relation) { + case core::Relation::Less: + // The reversed bound refutes this branch's strict comparison. + // NOLINTNEXTLINE(readability-suspicious-call-argument) + impossible = checkedAtMost(rhs, lhs, state); + break; + case core::Relation::LessEqual: + impossible = strictlyLess(rhs, lhs); + break; + case core::Relation::Equal: + impossible = strictlyLess(lhs, rhs) || strictlyLess(rhs, lhs); + break; + case core::Relation::GreaterEqual: + impossible = strictlyLess(lhs, rhs); + break; + case core::Relation::Greater: + impossible = checkedAtMost(lhs, rhs, state); + break; + } + if (impossible) { + edgeInfeasible = true; + return; + } std::int64_t offset = 0; if (__builtin_sub_overflow(rhs.constant, lhs.constant, &offset)) return; @@ -137,10 +199,42 @@ void FunctionDataflow::installCheckedPosition(core::PlaceId dest, return; } const auto expression = checkedByteExpression(position.offset, state); + const bool selfDependent = expression && expression->dependsOn(coordinate); + bool bounded = false; + if (position.extent && position.extent->place != coordinate) { + const auto extentExpression = + position.extent->place + ? numericExpressions.find(*position.extent->place) + : numericExpressions.end(); + if (extentExpression == numericExpressions.end() || + !extentExpression->second.dependsOn(coordinate)) + bounded = checkedAtMost(sourceOffset, *position.extent, state); + } + auto upperBound = position.extent; + if (bounded) { + const CheckedMemory memory{.storage = position.storage, + .begin = sourceOffset, + .end = sourceOffset, + .extent = position.extent, + .input = {}, + .pointer = nullptr}; + if (const auto witness = checkedWitness(memory, state); + witness && witness->zero.isConstant() && + checkedAtMost(witness->zero, *upperBound, state)) { + const auto zero = witness->zero.place + ? numericExpressions.find(*witness->zero.place) + : numericExpressions.end(); + if (zero == numericExpressions.end() || + !zero->second.dependsOn(coordinate)) + upperBound = witness->zero; + } + } const auto range = expression ? evaluateNumericExpression(*expression, state) : core::IntegerRangeEvaluation{ .values = core::IntegerRange::full(CursorType)}; + if (selfDependent) + snapshotIntegerDependencies(coordinate, nullptr, state); snapshotScalar(coordinate, nullptr, state); state.dropGuardsOn(coordinate); state.relations.forget(coordinate); @@ -148,12 +242,34 @@ void FunctionDataflow::installCheckedPosition(core::PlaceId dest, range.mayBeInvalid ? core::IntegerRange::full(CursorType) : range.values.converted(CursorType))); + if (!range.mayBeInvalid) + if (const auto value = range.values.converted(CursorType).constant()) + for (const auto &[otherHolder, other] : checkedCoordinates) { + const auto current = state.safety->positions.find(otherHolder); + if (other == coordinate || current == state.safety->positions.end() || + current->second.offset != core::Affine::ofPlace(other)) + continue; + const auto known = state.scalars.factOf(other); + if (known && known->inType(CursorType).constant() == value) { + // Equal byte counts need no shared object. This edge is established + // by actual assignments, then updated by ordinary cursor transfers. + state.relations.learn(coordinate, core::Relation::Equal, other); + break; + } + } if (sourceOffset.place && sourceOffset.scale == 1 && - sourceOffset.place != coordinate) + sourceOffset.place != coordinate && !selfDependent) state.relations.learn(coordinate, core::Relation::Equal, *sourceOffset.place, sourceOffset.constant); if (nonnegative) state.relations.learnAtLeast(coordinate, 0); + if (bounded) { + if (upperBound->isConstant()) + state.relations.learnAtMost(coordinate, upperBound->constant); + else if (upperBound->scale == 1) + state.relations.learn(coordinate, core::Relation::LessEqual, + *upperBound->place, upperBound->constant); + } position.offset = core::Affine::ofPlace(coordinate); state.safety->positions[dest] = position; } @@ -184,8 +300,10 @@ void FunctionDataflow::checkedAdvancePointer(const Expr &expr, const auto ref = builder.resolve(*operand); if (!ref || !ref->element.isWhole()) return; - const auto holder = ref->place; + auto holder = ref->place; const auto old = checkedMemoryAt(holder, {}, {}, state); + if (old && old->holder) + holder = *old->holder; if (!old || !shift) { state.safety->positions.erase(holder); return; @@ -216,6 +334,16 @@ void FunctionDataflow::checkedAdvancePointer(const Expr &expr, } return; } + const auto advancedOffset = position.offset.shifted(delta.constant); + const auto extentExpression = + position.extent && position.extent->place + ? numericExpressions.find(*position.extent->place) + : numericExpressions.end(); + const bool retainsExtent = + advancedOffset && position.extent && position.extent->place != id && + (extentExpression == numericExpressions.end() || + !extentExpression->second.dependsOn(id)) && + checkedAtMost(*advancedOffset, *position.extent, state); const auto previous = integerRangeAt(id, CursorType, state); const bool positive = delta.constant >= 0; const auto magnitude = @@ -234,10 +362,32 @@ void FunctionDataflow::checkedAdvancePointer(const Expr &expr, // target-typed maximum, before retaining mathematical update relations. if (!preserves && positive) { const auto bounds = checkedRelations(state); - for (const auto &[other, fact] : state.scalars.all()) { - if (other == id || !fact.integer || fact.integer->empty()) + if (const auto upper = bounds.bound(id, std::nullopt); upper && *upper >= 0) + preserves = + static_cast(*upper) <= CursorType.mask() - magnitude; + std::map inputs; + for (const auto &[other, fact] : state.scalars.all()) + if (fact.integer) + inputs.emplace(other, fact.integer->type); + for (const auto &[otherHolder, other] : checkedCoordinates) + if (state.safety->positions.contains(otherHolder)) + inputs.emplace(other, CursorType); + for (const auto &[pair, edge] : state.relations.all()) { + (void)edge; + for (const auto other : {pair.first, pair.second}) + if (const auto *decl = + dyn_cast_or_null(builder.declFor(other))) + if (const auto type = integerTypeOf(*decl, context)) + inputs.emplace(other, *type); + } + for (const auto &[other, type] : inputs) { + if (preserves) + break; + if (other == id) continue; - const auto upper = fact.integer->maximum(); + // A full target range still bounds nonwrapping arithmetic. Widening + // may omit that redundant scalar fact while retaining q < p or q < n. + const auto upper = integerRangeAt(other, type, state).maximum(); const auto distance = bounds.bound(id, other); inferred.checked.limited |= bounds.limited(); if (bounds.limited()) @@ -359,6 +509,14 @@ void FunctionDataflow::checkedAdvancePointer(const Expr &expr, } for (const auto &[storage, range] : advanced) state.safety->initialize(storage, range); + if (retainsExtent) { + if (position.extent->isConstant()) + state.relations.learnAtMost(id, position.extent->constant); + else if (position.extent->scale == 1) + state.relations.learn(id, core::Relation::LessEqual, + *position.extent->place, + position.extent->constant); + } } if (!postfix) { state.safety->positions[saved] = position; diff --git a/lib/Analysis/DataflowFloating.cpp b/lib/Analysis/DataflowFloating.cpp new file mode 100644 index 00000000..72215903 --- /dev/null +++ b/lib/Analysis/DataflowFloating.cpp @@ -0,0 +1,157 @@ +//===- DataflowFloating.cpp - Numeric input facts (RFC 0029) -------------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "AffineSupport.h" +#include "Dataflow.h" +#include "IntegerSupport.h" + +#include + +using namespace clang; +namespace weavec::analysis { + +static bool numericTextByte(std::uint64_t value) { + return value == 0 || (value >= '0' && value <= '9') || value == '+' || + value == '-' || value == '.' || value == 'e' || value == 'E'; +} + +bool FunctionDataflow::checkedNumericByte(const Expr &value, + const core::AnalysisState &state) { + if (context.getCharWidth() != 8) + return false; + const auto range = integerRangeOf(value, state); + if (!range || range->mayBeInvalid || range->values.empty()) + return false; + const core::IntegerType byte{.width = 8, .isSigned = false}; + const auto actual = range->values.converted(byte); + return std::ranges::all_of(actual.all(), [](const auto &interval) { + return (interval.lower == interval.upper && + numericTextByte(interval.lower)) || + (interval.lower >= '0' && interval.upper <= '9') || + (interval.lower >= '-' && interval.upper <= '.'); + }); +} + +bool FunctionDataflow::checkedNumericText(const CheckedMemory &memory, + const core::AnalysisState &state) { + if (!state.safety || state.safety->havoc || context.getCharWidth() != 8) + return false; + // An unrepresented existential string endpoint is not an empty string. + if (checkedAtMost(memory.end, memory.begin, state)) + return false; + const auto ranges = state.safety->memory.find(memory.storage); + if (ranges == state.safety->memory.end()) + return false; + auto through = foldAffine(memory.begin, state); + for (std::size_t step = 0; step <= ranges->second.size(); ++step) { + if (checkedAtMost(memory.end, through, state)) + return true; + bool advanced = false; + for (const auto &range : ranges->second) { + auto when = range.when; + if ((!range.numericText && !range.zeroed) || range.source || + !pruneGuard(when, state) || !when.trivial() || + !checkedAtMost(range.begin, through, state) || + !checkedAtMost(through, range.end, state) || + checkedAtMost(range.end, through, state)) + continue; + through = range.end; + advanced = true; + break; + } + if (!advanced) + break; + } + return false; +} + +bool FunctionDataflow::checkedFloatingValue(const Expr &value, + const core::AnalysisState &state) { + if (!state.safety || state.safety->havoc || + !value.getType()->isRealFloatingType()) + return false; + llvm::APFloat constant(0.0); + if (value.EvaluateAsFloat(constant, context)) + return !constant.isNaN(); + const auto *expr = value.IgnoreParenImpCasts(); + if (const auto *call = dyn_cast(expr)) { + const auto result = checkedFloatingResults.find(call); + return result != checkedFloatingResults.end() && + state.safety->nonNan.contains(result->second); + } + const auto *reference = dyn_cast(expr); + const auto *variable = + reference ? dyn_cast(reference->getDecl()) : nullptr; + return variable != nullptr && variable->hasLocalStorage() && + !variable->getType().isVolatileQualified() && + !variable->getType()->isAtomicType() && + !addressTaken.contains(variable->getCanonicalDecl()) && + state.safety->nonNan.contains(builder.placeForVar(*variable)); +} + +void FunctionDataflow::checkedFloatingAfter(const Stmt &stmt, + core::AnalysisState &state) { + const auto assign = [&](const VarDecl &variable, const Expr *value) { + if (!variable.getType()->isRealFloatingType()) + return; + const auto place = builder.placeForVar(variable); + const bool nonNan = value && checkedFloatingValue(*value, state); + state.safety->nonNan.erase(place); + if (nonNan && variable.hasLocalStorage() && + !variable.getType().isVolatileQualified() && + !variable.getType()->isAtomicType() && + !addressTaken.contains(variable.getCanonicalDecl())) + state.safety->nonNan.insert(place); + }; + if (const auto *declarations = dyn_cast(&stmt)) { + for (const auto *declaration : declarations->decls()) { + const auto *variable = dyn_cast(declaration); + if (!variable) + continue; + assign(*variable, variable->getInit()); + const auto *array = context.getAsConstantArrayType(variable->getType()); + const auto *initial = variable->getInit(); + if (!array || !array->getElementType()->isCharType() || !initial || + variable->getType().isVolatileQualified() || + context.getCharWidth() != 8) + continue; + bool numeric = false; + if (const auto *text = dyn_cast(initial->IgnoreImpCasts())) + numeric = text->isOrdinary() && + std::ranges::all_of(text->getBytes(), [](unsigned char c) { + return numericTextByte(c); + }); + else if (const auto *list = dyn_cast(initial)) + numeric = std::ranges::all_of(list->inits(), [&](const Expr *value) { + return checkedNumericByte(*value, state); + }); + if (numeric) + if (const auto bytes = byteSizeOf(variable->getType(), context)) + state.safety->initialize(builder.placeForVar(*variable), + {.begin = {}, + .end = core::Affine::ofConstant(*bytes), + .numericText = true}); + } + } + const Expr *written = nullptr; + const Expr *value = nullptr; + if (const auto *assignment = dyn_cast(&stmt); + assignment && assignment->isAssignmentOp()) { + written = assignment->getLHS(); + if (assignment->getOpcode() == BO_Assign) + value = assignment->getRHS(); + } + if (const auto *unary = dyn_cast(&stmt); + unary && unary->isIncrementDecrementOp()) + written = unary->getSubExpr(); + const auto *reference = + written ? dyn_cast(written->IgnoreParenImpCasts()) : nullptr; + if (const auto *variable = + reference ? dyn_cast(reference->getDecl()) : nullptr) + assign(*variable, value); +} +} // namespace weavec::analysis diff --git a/lib/Analysis/DataflowFootprints.cpp b/lib/Analysis/DataflowFootprints.cpp index f51757f0..ab16d735 100644 --- a/lib/Analysis/DataflowFootprints.cpp +++ b/lib/Analysis/DataflowFootprints.cpp @@ -8,6 +8,7 @@ #include "Dataflow.h" #include "IntegerSupport.h" +#include "weavec/Core/Induction.h" #include "llvm/Support/raw_ostream.h" @@ -17,6 +18,97 @@ using namespace clang; namespace weavec::analysis { +static bool freshFootprintSlot(const core::FunctionSummary &summary, + const core::CheckedContract &contract, + const core::CheckedRequirement &post) { + if (!summary.returns.empty() || summary.outcomes.size() != 2 || + !summary.outcomes.contains(core::Outcome::Positive) || + !summary.outcomes.contains(core::Outcome::Zero) || + post.kind != core::CheckedRequirementKind::ContainerFresh || + !post.path.isParam() || !post.path.hasDeref() || + post.on != core::Outcome::Positive || post.ifNonNull) + return false; + const auto nulls = summary.nullOn.find(core::Outcome::Zero); + return nulls != summary.nullOn.end() && nulls->second.contains(post.path) && + std::ranges::none_of( + contract.establishes, + [&](const auto &other) { + return other.kind == + core::CheckedRequirementKind::ContainerFresh && + other.path != post.path; + }) && + std::ranges::all_of(summary.stores, + [&](const auto &store) { + return !store.value.isFresh() || + store.dest == post.path; + }) && + std::ranges::all_of(summary.effects, [&](const auto &entry) { + const auto &effect = entry.second; + return !effect.consumed() && !effect.escaped && + (!effect.written || entry.first == post.path); + }); +} + +void FunctionDataflow::verifyFootprintTransfers() { + const auto &contract = inferred.checked; + for (const auto &transfer : footprintTransfers) { + const auto applies = [&](const auto &post) { + return post.when.trivial() && (!post.on || post.on == transfer.outcome); + }; + const bool published = + std::ranges::any_of(transfer.alternatives, [&](const auto &paths) { + if (!std::ranges::all_of(paths, [&](const auto &path) { + // The exit ledger already proved this proper returned forest + // contains the complete transfer. A null result contributes + // no allocation; fresh-result posts describe the non-null arm. + if (path.isResult() && path.isRoot() && + transfer.outcome == core::Outcome::Null) + return true; + return std::ranges::any_of( + contract.establishes, [&](const auto &post) { + if (!applies(post)) + return false; + if (post.kind == + core::CheckedRequirementKind::ContainerPartition) + return post.path == path || post.other == path; + if (post.path != path) + return false; + if (post.kind == + core::CheckedRequirementKind::ContainerFresh) + return (path.isResult() && path.isRoot()) || + freshFootprintSlot(inferred, contract, post); + return post.kind == core::CheckedRequirementKind:: + ContainerPreserved || + post.kind == core::CheckedRequirementKind:: + ContainerCombined || + (post.kind == core::CheckedRequirementKind:: + ContainerExtended && + !post.on); + }); + })) + return false; + return paths.size() < 2 || + std::ranges::any_of( + contract.establishes, + [&](const auto &post) { + return applies(post) && + post.kind == core::CheckedRequirementKind:: + ContainerSeparated && + paths.contains(post.path) && + paths.contains(post.other) && + post.path != post.other; + }); + }); + if (!published) { + safetyObligation( + core::SafetyProperty::Semantics, core::SafetyOutcome::Unresolved, + *function.getBody(), "container footprint", + "allocation transfer has no surviving portable output guarantee"); + return; + } + } +} + void FunctionDataflow::recordAllocationConsumed(core::PlaceId holder, core::AnalysisState &state) { const auto path = builder.summaryPathOf(holder); @@ -131,6 +223,42 @@ core::PlaceId FunctionDataflow::footprintAtom(core::PlaceId storage) { return entry->second; } +bool FunctionDataflow::unchangedPointerVariable(core::PlaceId place) { + if (!changedPointerVariables) { + auto &changed = changedPointerVariables.emplace(); + const auto note = [&](const Expr *expr) { + if (const auto *ref = dyn_cast_or_null( + expr ? expr->IgnoreParenImpCasts() : nullptr)) + if (const auto *var = dyn_cast(ref->getDecl())) + changed.insert(var->getCanonicalDecl()); + }; + std::vector work{function.getBody()}; + for (std::size_t i = 0; i < work.size(); ++i) { + const auto *stmt = work[i]; + if (!stmt) + continue; + if (work.size() > 65536) { + // An exhausted scan proves nothing about any variable. + changed.insert(nullptr); + break; + } + if (const auto *assignment = dyn_cast(stmt); + assignment && assignment->isAssignmentOp()) + note(assignment->getLHS()); + if (const auto *unary = dyn_cast(stmt); + unary && + (unary->isIncrementDecrementOp() || unary->getOpcode() == UO_AddrOf)) + note(unary->getSubExpr()); + for (const auto *child : stmt->children()) + work.push_back(child); + } + } + const auto *var = builder.varForPlace(place); + return var != nullptr && places.isBase(place) && + !changedPointerVariables->contains(nullptr) && + !changedPointerVariables->contains(var->getCanonicalDecl()); +} + void FunctionDataflow::initializeFootprint(core::PlaceId holder, const core::SummaryPath &path, const core::ContainerShape &shape, @@ -209,6 +337,56 @@ void FunctionDataflow::unfoldFootprint(core::PlaceId holder, state.safety->unfoldedFootprints.insert(holder); } +void FunctionDataflow::prepareReallocationFootprint( + const CallExpr &call, core::AnalysisState &state) { + if (!footprintAllocated || call.getNumArgs() == 0) + return; + const auto [saved, inserted] = reallocationFootprints.try_emplace(&call); + if (inserted) + saved->second = places.create("reallocation input footprint"); + // A loop can revisit the same call before an older result was refined. + // That result must not release the new generation's saved input head. + state.safety->forget(saved->second); + auto &relations = state.safety->footprints; + const auto origin = builder.classifyValue(*call.getArg(0)); + if (origin.kind == ValueOrigin::Kind::Null || + (origin.place && + state.nulls.stateOf(origin.place->place) == core::Nullness::Null)) { + relations.assign(saved->second, {}); + } else if (origin.place && origin.offset.isZero() && + footprintHeads.contains(origin.place->place)) { + relations.assign(saved->second, {{footprintHead(origin.place->place), 1}}); + } +} + +void FunctionDataflow::refineAllocationFootprints(core::AnalysisState &state) { + auto &pendingReleases = state.safety->pendingAllocationReleases; + if (state.safety->havoc) { + pendingReleases.clear(); + return; + } + for (auto entry = pendingReleases.begin(); entry != pendingReleases.end();) { + const auto [holder, release] = *entry; + const auto object = state.safety->objects.find(holder); + if (!footprintReleased || object == state.safety->objects.end() || + object->second != release.storage || + state.safety->invalidatedPointers.contains(holder)) { + entry = pendingReleases.erase(entry); + continue; + } + const auto nullness = state.nulls.stateOf(holder); + if (nullness == core::Nullness::NonNull) { + state.safety->footprints.assign( + *footprintReleased, {{*footprintReleased, 1}, {release.snapshot, 1}}); + entry = pendingReleases.erase(entry); + } else if (nullness == core::Nullness::Null) { + entry = pendingReleases.erase(entry); + } else { + ++entry; + } + } +} + void FunctionDataflow::captureFootprint(core::PlaceId dest, const ValueOrigin &origin, CheckedPointer &pointer, @@ -218,8 +396,23 @@ void FunctionDataflow::captureFootprint(core::PlaceId dest, containerShape(decl->getType()) != nullptr; const auto library = origin.call ? resolvedLibraryName(*origin.call) : std::string{}; + const bool reallocation = + (library == "realloc" || library == "reallocarray") && + reallocationFootprints.contains(origin.call); + const auto callee = + origin.call ? callSummaries.find(origin.call) : callSummaries.end(); + const bool byteHelper = + origin.call != nullptr && origin.call->getType()->isPointerType() && + origin.call->getType()->getPointeeType()->isCharType() && + origin.family == "free" && callee != callSummaries.end() && + callee->second && callee->second->checked.complete() && + std::ranges::none_of( + callee->second->checked.establishes, [](const auto &post) { + return post.kind == core::CheckedRequirementKind::ContainerFresh; + }); const bool freshAllocation = pointer.fresh && footprintAllocated && - (library == "malloc" || library == "calloc"); + (library == "malloc" || library == "calloc" || + reallocation || byteHelper); if (!pointer.container && !freshContainer && !freshAllocation && !footprintHeads.contains(dest) && !(origin.place && footprintHeads.contains(origin.place->place))) @@ -286,6 +479,9 @@ void FunctionDataflow::captureFootprint(core::PlaceId dest, relations.assign(whole, {{atom, 1}}); relations.assign(*footprintAllocated, {{*footprintAllocated, 1}, {atom, 1}}); + if (const auto previous = reallocationFootprints.find(origin.call); + reallocation && previous != reallocationFootprints.end()) + pointer.pendingAllocationRelease = previous->second; } } else if (origin.place && origin.offset.isZero()) { const auto source = origin.place->place; @@ -301,6 +497,17 @@ void FunctionDataflow::installFootprint(core::PlaceId dest, const CheckedPointer &pointer, core::AnalysisState &state) { auto &relations = state.safety->footprints; + state.safety->pendingAllocationReleases.erase(dest); + if (pointer.pendingAllocationRelease && pointer.storage) { + if (state.safety->pendingAllocationReleases.size() < + core::MaxFootprintVariables) + state.safety->pendingAllocationReleases.emplace( + dest, core::SafetyState::PendingAllocationRelease{ + .storage = *pointer.storage, + .snapshot = *pointer.pendingAllocationRelease}); + else + inferred.checked.limited = true; + } // Head identities below a replaced pointer name belong to its old object. // Ghost places are not ordinary descendants retired by reinitialization. for (const auto &[holder, head] : footprintHeads) @@ -419,6 +626,45 @@ void FunctionDataflow::footprintOutputs(core::CheckedContract &outputs, .on = outcome}); } for (auto first = visible.begin(); first != visible.end(); ++first) { + // RFC 0029: the live unchanged singleton head remains the complete + // entry footprint. Every other output member must come from this + // invocation, and the allocation ledger must account for all of it. + if (first->first == entry.holder && first->second == path && + shape.access == core::ContainerAccess::Release && + shape.singletonHead() && footprintAllocated && + !state.safety->replacedPointers.contains(entry.holder)) { + const auto *fact = state.safety->containers.find(first->first); + const auto memory = checkedMemoryAt(entry.holder, {}, {}, state); + const bool onlyEntry = + fact != nullptr && fact->inputs.size() == 1 && + containerInputs.contains(*fact->inputs.begin()) && + containerInputs.at(*fact->inputs.begin()) == path; + core::FootprintSum complete{{entry.identity, 1}, + {*footprintAllocated, 1}, + {*footprintReleased, -1}, + {first->first, -1}}; + if (onlyEntry && fact->allocationCompatible && memory && + memory->input == path && memory->begin == core::Affine{} && + checkedValid(*memory, state) && relations.entails(complete)) { + auto extended = fact->shape; + if (extended.access == core::ContainerAccess::Release) { + // A case may know non-nullness from its captured values. Keep + // the explicit live-entry premise required by this portable + // ownership relation as well. + inferred.checked.require( + {.kind = core::CheckedRequirementKind::Valid, + .path = path, + .other = {}, + .family = {}}); + outputs.establish( + {.kind = core::CheckedRequirementKind::ContainerExtended, + .path = path, + .other = path, + .family = extended.encode(), + .on = outcome}); + } + } + } if (relations.equal(first->first, entry.identity)) outputs.establish( {.kind = core::CheckedRequirementKind::ContainerPreserved, @@ -497,6 +743,59 @@ void FunctionDataflow::footprintOutputs(core::CheckedContract &outputs, .family = combinedShape.encode(), .on = outcome}); } + // RFC 0029: an attaching helper may also publish a payload acquired by + // this call. The unchanged live head then owns both complete entry + // footprints and exactly this invocation's outstanding acquisitions. + if (!footprintAllocated || + a->second.access != core::ContainerAccess::Release || + b->second.access != core::ContainerAccess::Release || + a->second.family != b->second.family || + a->second.payloads != b->second.payloads) + continue; + for (const auto &[head, tail] : + {std::pair{first, second}, std::pair{second, first}}) { + const auto holder = head->second.holder; + const auto output = visible.find(holder); + const auto *fact = state.safety->containers.find(holder); + const auto memory = checkedMemoryAt(holder, {}, {}, state); + if (output == visible.end() || output->second != head->first || !fact || + !fact->allocationCompatible || !memory || + memory->begin != core::Affine{} || + !unchangedPointerVariable(holder) || + !checkedValid(*memory, state) || + relations.entails({{holder, 1}, + {head->second.identity, -1}, + {tail->second.identity, -1}}) || + !relations.entails({{holder, 1}, + {head->second.identity, -1}, + {tail->second.identity, -1}, + {*footprintAllocated, -1}, + {*footprintReleased, 1}}) || + !std::ranges::all_of(fact->inputs, [&](const auto input) { + const auto source = containerInputs.find(input); + return source != containerInputs.end() && + (source->second == head->first || + source->second == tail->first); + })) + continue; + inferred.checked.require( + {.kind = core::CheckedRequirementKind::ContainerSeparated, + .path = first->first, + .other = second->first, + .family = {}}); + inferred.checked.require({.kind = core::CheckedRequirementKind::Valid, + .path = head->first, + .other = {}, + .family = {}}); + outputs.establish( + {.kind = core::CheckedRequirementKind::ContainerCombined, + .path = head->first, + .other = head->first, + .begin = core::PathAffine::ofPath(tail->first), + .end = core::PathAffine::ofConstant(1), + .family = combinedShape.encode(), + .on = outcome}); + } } // Entry release permission does not transfer the caller's cleanup duty. // Without local acquisitions, a partial helper can be conditionally safe; @@ -505,20 +804,85 @@ void FunctionDataflow::footprintOutputs(core::CheckedContract &outputs, // consumption candidates are independently checked on every exit. bool accounted = relations.entails(balance) || (footprintAllocated && relations.empty(*footprintAllocated)); - for (auto first = visible.begin(); first != visible.end() && !accounted; + // RFC 0029: byte allocations participate in the same acquisition ledger, + // but returning their live base transfers one allocation, not a forest. + if (!accounted && returned && outcome == core::Outcome::NonNull && + footprintHeads.contains(*returned)) { + const auto resource = state.resources.recordOf(*returned); + const auto memory = checkedMemoryAt(*returned, {}, {}, state); + if (resource && resource->origin == core::ResourceOrigin::Allocated && + resource->family == "free" && !resource->escaped && + !state.moves.recordOf(*returned) && memory && + memory->begin == core::Affine::ofConstant(0) && + checkedValid(*memory, state)) { + auto transferred = balance; + --transferred[footprintHead(*returned)]; + accounted = relations.entails(std::move(transferred)); + } + } + // A complete callee's fresh result returned without a local holder has no + // resource record. Its identity is either null or that single allocation. + if (!accounted && returned && footprintHeads.contains(*returned) && + (outcome == core::Outcome::NonNull || outcome == core::Outcome::Null)) + for (const auto &[expression, place] : checkedReturnPlaces) { + const auto *call = dyn_cast(expression); + if (place != *returned || call == nullptr) + continue; + // Every alternative is null or this call's own allocation base. + const std::function single = + [&](const ValueOrigin &origin) { + if (origin.kind == ValueOrigin::Kind::Null) + return true; + if (origin.kind == ValueOrigin::Kind::Conditional) + return !origin.alternatives.empty() && + std::ranges::all_of(origin.alternatives, single); + return origin.kind == ValueOrigin::Kind::Alloc && + origin.call == call && origin.family == "free" && + origin.offset.isZero(); + }; + if (!single(builder.classifyValue(*call)) || + !checkedObjects.contains({call, *returned}) || + state.safety->invalidatedPointers.contains(*returned)) + continue; + auto transferred = balance; + --transferred[footprintHead(*returned)]; + accounted = relations.entails(std::move(transferred)); + } + const bool needsOutput = !accounted; + FootprintTransfers transfers{.outcome = outcome, .alternatives = {}}; + const auto remember = [&](std::set paths) { + accounted = true; + if (std::ranges::find(transfers.alternatives, paths) != + transfers.alternatives.end()) + return; + if (transfers.alternatives.size() < core::MaxContainerFacts) + transfers.alternatives.push_back(std::move(paths)); + else + inferred.checked.limited = true; + }; + for (auto first = visible.begin(); first != visible.end() && needsOutput; ++first) { auto remaining = balance; --remaining[first->first]; - accounted = relations.entails(remaining); - for (auto second = std::next(first); second != visible.end() && !accounted; - ++second) { + if (relations.entails(remaining)) + remember({first->second}); + for (auto second = std::next(first); second != visible.end(); ++second) { if (!state.safety->containers.separated(first->first, second->first)) continue; auto partition = remaining; --partition[second->first]; - accounted = relations.entails(std::move(partition)); + if (relations.entails(std::move(partition))) + remember({first->second, second->second}); } } + if (needsOutput && accounted && recording() && + std::ranges::find(footprintTransfers, transfers) == + footprintTransfers.end()) { + if (footprintTransfers.size() < core::MaxSafetyRequirements) + footprintTransfers.push_back(std::move(transfers)); + else + inferred.checked.limited = true; + } safetyObligation( core::SafetyProperty::Semantics, accounted ? core::SafetyOutcome::Proven : core::SafetyOutcome::Unresolved, @@ -533,18 +897,66 @@ void FunctionDataflow::captureFootprintPosts( core::AnalysisState &state) { auto &posts = footprintPosts[&call]; posts.clear(); + containerPrefixPosts[&call].clear(); if (!contract.complete()) return; const auto arguments = containerArguments.find(&call); auto &inputs = footprintCallInputs[&call]; + freshFootprintSlots.erase(&call); + freshFootprintSlotParents.erase(&call); + // A single success-published output slot carries an entire fresh forest, + // not just the allocation of its head. The other returning class acquires + // no output allocation. Keep this conditional region until the caller + // actually tests the result; an unchecked call cannot discharge its ledger. + const auto resolved = callSummaries.find(&call); + if (call.getDirectCallee() != nullptr && resolved != callSummaries.end() && + resolved->second && resolved->second->returns.empty() && + resolved->second->outcomes.size() == 2 && + resolved->second->outcomes.contains(core::Outcome::Positive) && + resolved->second->outcomes.contains(core::Outcome::Zero)) { + const auto &summary = *resolved->second; + std::set slots; + for (const auto &post : contract.establishes) { + const auto nulls = summary.nullOn.find(core::Outcome::Zero); + const auto guard = checkedGuard(post.when, call, state); + if (post.kind == core::CheckedRequirementKind::ContainerFresh && + post.path.isParam() && post.path.hasDeref() && + post.on == core::Outcome::Positive && !post.ifNonNull && guard && + guard->trivial() && nulls != summary.nullOn.end() && + nulls->second.contains(post.path)) + slots.insert(post.path); + } + if (slots.size() == 1 && + std::ranges::all_of(summary.stores, + [&](const auto &store) { + return !store.value.isFresh() || + store.dest == *slots.begin(); + }) && + std::ranges::all_of(summary.effects, [&](const auto &entry) { + const auto &effect = entry.second; + return !effect.consumed() && !effect.escaped && + (!effect.written || entry.first == *slots.begin()); + })) + freshFootprintSlots.emplace(&call, *slots.begin()); + } + if (const auto slot = freshFootprintSlots.find(&call); + slot != freshFootprintSlots.end()) + if (const auto actual = builder.resolveSummaryPath(slot->second, call); + actual && places.step(actual->place) == core::PathStep::Field) + if (const auto object = places.parent(actual->place); + object && places.step(*object) == core::PathStep::Deref) + if (const auto parent = places.parent(*object)) + if (const auto *fact = state.safety->containers.find(*parent)) + freshFootprintSlotParents.emplace(&call, std::pair{*parent, *fact}); if (footprintAllocated && containerCallObjects.contains(&call) && - std::ranges::any_of(contract.establishes, [&](const auto &post) { - const auto guard = checkedGuard(post.when, call, state); - return post.kind == core::CheckedRequirementKind::ContainerFresh && - post.path.isResult() && post.path.isRoot() && - (!post.on || post.on == core::Outcome::NonNull) && guard && - guard->trivial(); - })) { + (freshFootprintSlots.contains(&call) || + std::ranges::any_of(contract.establishes, [&](const auto &post) { + const auto guard = checkedGuard(post.when, call, state); + return post.kind == core::CheckedRequirementKind::ContainerFresh && + post.path.isResult() && post.path.isRoot() && + (!post.on || post.on == core::Outcome::NonNull) && guard && + guard->trivial(); + }))) { const auto region = containerCallObjects.at(&call); state.safety->footprints.forget(region); state.safety->footprints.assign(*footprintAllocated, @@ -583,15 +995,25 @@ void FunctionDataflow::captureFootprintPosts( state.safety->footprints.assign(saved->second, {{*source, 1}}); return true; }; + std::set extended; for (const auto &post : contract.establishes) { if (post.kind != core::CheckedRequirementKind::ContainerPreserved && post.kind != core::CheckedRequirementKind::ContainerConsumed && post.kind != core::CheckedRequirementKind::ContainerPartition && - post.kind != core::CheckedRequirementKind::ContainerCombined) + post.kind != core::CheckedRequirementKind::ContainerCombined && + post.kind != core::CheckedRequirementKind::ContainerExtended) continue; const auto guard = checkedGuard(post.when, call, state); if (!guard || !guard->trivial()) continue; + if (post.kind == core::CheckedRequirementKind::ContainerExtended && + std::ranges::any_of(contract.establishes, [&](const auto &other) { + return other.kind == + core::CheckedRequirementKind::ContainerPreserved && + other.path == post.path && other.other == post.other && + other.when == post.when && other.on == post.on; + })) + continue; bool captured = false; if (post.kind == core::CheckedRequirementKind::ContainerPartition) captured = post.begin.path && capture(*post.begin.path); @@ -599,17 +1021,77 @@ void FunctionDataflow::captureFootprintPosts( captured = capture(post.other); if (post.kind == core::CheckedRequirementKind::ContainerCombined) captured &= post.begin.path && capture(*post.begin.path); - if (captured) + const bool combinedExtension = + post.kind == core::CheckedRequirementKind::ContainerCombined && + post.end == core::PathAffine::ofConstant(1); + if (captured && combinedExtension && !footprintAllocated) + continue; + if (captured && + (post.kind == core::CheckedRequirementKind::ContainerExtended || + combinedExtension)) { + if ((post.on && !combinedExtension) || !footprintAllocated) + continue; + const auto [extension, inserted] = + footprintExtensions.try_emplace({&call, post.path}); + if (inserted) + extension->second = places.create("fresh extension footprint"); + // Outcome-specific outputs of one path share one fresh region. + if (extended.insert(extension->second).second) { + state.safety->footprints.forget(extension->second); + state.safety->footprints.assign( + *footprintAllocated, + {{*footprintAllocated, 1}, {extension->second, 1}}); + } + } + if (captured) { posts.push_back(post); + captureContainerPrefixes(call, post, state); + } } } void FunctionDataflow::applyFootprintPosts( const CallExpr &call, core::AnalysisState &state, - std::optional result) { + std::optional result, + const std::optional *prior) { const auto region = containerCallObjects.find(&call); const auto structural = containerPosts.find(&call); - if (result && region != containerCallObjects.end() && + if (!prior && !result && region != containerCallObjects.end()) + if (const auto slot = freshFootprintSlots.find(&call); + slot != freshFootprintSlots.end()) { + const auto outcome = scalarFactOf(call, state); + const bool failure = + outcome && outcome->implies(core::ValueFact::of(core::Outcome::Zero)); + const bool success = + outcome && + outcome->implies(core::ValueFact::of(core::Outcome::Positive)); + const auto actual = builder.resolveSummaryPath(slot->second, call); + if (failure) + state.safety->footprints.constrain({{region->second, 1}}); + else if (success) + if (actual && state.safety->containers.find(actual->place)) { + state.safety->footprints.assign(actual->place, {{region->second, 1}}); + snapshotContainerOutput(actual->place, state); + } + // The only possible write of this completed helper is the exact output + // slot. Restore the captured parent frame and fold that one changed + // child using its newly proved output (or its actual null failure). + if (actual && (success || failure)) + if (const auto parent = freshFootprintSlotParents.find(&call); + parent != freshFootprintSlotParents.end()) + if (const auto *field = + dyn_cast_or_null(builder.declFor(actual->place))) { + const auto &[holder, before] = parent->second; + state.safety->containers.set(holder, before); + if (before.localAllocation) + state.safety->containers.markFresh(holder); + if (success) + state.safety->containers.separate(holder, actual->place); + foldContainerStores(holder, *field->getParent(), field, call, + state); + } + } + if (!prior && result && region != containerCallObjects.end() && structural != containerPosts.end() && std::ranges::any_of(structural->second, [](const auto &post) { return post.fresh && post.path.isResult() && post.path.isRoot(); @@ -643,6 +1125,19 @@ void FunctionDataflow::applyFootprintPosts( hasResult && !result && region != containerCallObjects.end(); if (!nested && hasResult != result.has_value()) continue; + // An immediate result test installs only what the call could not, and + // never replays consumption. + // On an immediate result test, install only the transfers that add a + // guarantee. Replaying a preserved or consumed output could replace + // current evidence with the weaker captured entry footprint. + if (prior && + (!post.on || + post.kind == core::CheckedRequirementKind::ContainerConsumed || + (*prior && (*prior)->implies(core::ValueFact::of(*post.on))))) + continue; + const bool combinedExtension = + post.kind == core::CheckedRequirementKind::ContainerCombined && + post.end == core::PathAffine::ofConstant(1); if (post.on) { const auto outcome = scalarFactOf(call, state); const bool resultMatches = @@ -652,8 +1147,22 @@ void FunctionDataflow::applyFootprintPosts( (post.on == core::Outcome::Null && state.nulls.stateOf(*result) == core::Nullness::Null)); if (!resultMatches && - (!outcome || !outcome->implies(core::ValueFact::of(*post.on)))) + (!outcome || !outcome->implies(core::ValueFact::of(*post.on)))) { + // The fresh region exists only on a covered outcome. Once every + // covering output of this path is excluded, it is empty. + const auto extension = footprintExtensions.find({&call, post.path}); + if (combinedExtension && outcome && + extension != footprintExtensions.end() && + std::ranges::none_of(found->second, [&](const auto &other) { + return other.kind == + core::CheckedRequirementKind::ContainerCombined && + other.end == core::PathAffine::ofConstant(1) && + other.path == post.path && + (!other.on || outcome->classes.contains(*other.on)); + })) + state.safety->footprints.constrain({{extension->second, 1}}); continue; + } } const auto &inputs = footprintCallInputs.at(&call); if (post.kind == core::CheckedRequirementKind::ContainerConsumed) { @@ -679,10 +1188,47 @@ void FunctionDataflow::applyFootprintPosts( if (post.kind == core::CheckedRequirementKind::ContainerCombined && post.begin.path) ++footprint[inputs.at(*post.begin.path)]; + if (combinedExtension) { + const auto extension = footprintExtensions.find({&call, post.path}); + if (extension == footprintExtensions.end()) + continue; + ++footprint[extension->second]; + } + if (post.kind == core::CheckedRequirementKind::ContainerExtended) { + const auto extension = footprintExtensions.find({&call, post.path}); + if (extension == footprintExtensions.end()) + continue; + ++footprint[extension->second]; + state.safety->footprints.assign(footprintHead(*holder), + {{inputs.at(post.other), 1}}); + } + // RFC 0029: this output states the final footprint is exactly its + // incoming regions, so the same contract's fresh region is empty. + if (post.kind != core::CheckedRequirementKind::ContainerExtended && + !combinedExtension) + if (const auto extension = footprintExtensions.find({&call, post.path}); + extension != footprintExtensions.end()) + state.safety->footprints.constrain({{extension->second, 1}}); state.safety->footprints.assign(*holder, std::move(footprint)); + const auto *fact = state.safety->containers.find(*holder); + const auto memory = checkedMemoryAt(*holder, {}, {}, state); + if (fact && memory) + for (const auto &[alias, edge] : + state.definiteAliases.edgesFrom(*holder)) { + const auto *otherFact = state.safety->containers.find(alias); + if (alias == *holder || !edge.exact() || !otherFact || + *otherFact != *fact || + !state.definiteAliases.sameShare(*holder, alias)) + continue; + const auto other = checkedMemoryAt(alias, {}, {}, state); + if (other && other->storage == memory->storage && + other->begin == memory->begin) + state.safety->footprints.assign(alias, {{*holder, 1}}); + } } else { continue; } + applyContainerPrefixes(call, post, *holder, state); // RFC 0028: a verified partition or preservation transfers the cleanup // duty with the complete footprint, including an unassigned call result. // The function's allocation balance still rejects losing that result. @@ -708,17 +1254,19 @@ void FunctionDataflow::applyFootprintPosts( } } -bool FunctionDataflow::handleRecursiveCleanup(const CallExpr &call, - core::AnalysisState &state) { +bool FunctionDataflow::handleRecursiveContract(const CallExpr &call, + core::AnalysisState &state) { const auto *callee = call.getDirectCallee(); - if (!callee || callee->getCanonicalDecl() != function.getCanonicalDecl() || - function.getNumParams() != 1 || call.getNumArgs() != 1 || - !function.getReturnType()->isVoidType()) + const auto *group = summaries.recursiveContractGroup(function); + if (!callee || !summaries.recursiveContractPeer(function, *callee) || + !group || function.getNumParams() != 1 || call.getNumArgs() != 1) return false; + const bool releases = group->releases; const auto *shape = containerShape(function.getParamDecl(0)->getType()); - if (!shape || shape->access != core::ContainerAccess::Release) + if (!shape || shape->access != (releases ? core::ContainerAccess::Release + : core::ContainerAccess::Read)) return false; - if (!recursiveCleanupCandidate) { + if (!recursiveContractCandidate) { // The initial induction rule has no hidden global effects or outputs. // Additional callbacks/mutators need their ordinary verified contracts. bool candidate = true; @@ -736,16 +1284,18 @@ bool FunctionDataflow::handleRecursiveCleanup(const CallExpr &call, const auto binding = path ? callbackBindings.find(*path) : callbackBindings.end(); const bool actualFree = - binding != callbackBindings.end() && !binding->second.unknown && - !binding->second.null && + path && path->isGlobal() && binding != callbackBindings.end() && + !binding->second.unknown && !binding->second.null && binding->second.functions == std::set{"free"} && operation->getNumArgs() == 1; - candidate &= - actualFree || - (target != nullptr && - (target->getCanonicalDecl() == function.getCanonicalDecl() || - (target->getName() == "free" && !target->hasBody() && - operation->getNumArgs() == 1))); + // A callback stored in the current node need not be the callback in + // its children. The structural predicate carries no per-node callback + // behavior; a root specialization cannot authorize the induction. + candidate &= (releases && actualFree) || + (target != nullptr && + (summaries.recursiveContractPeer(function, *target) || + (releases && target->getName() == "free" && + !target->hasBody() && operation->getNumArgs() == 1))); } if (const auto *assignment = dyn_cast(stmt); assignment && assignment->isAssignmentOp()) { @@ -755,8 +1305,10 @@ bool FunctionDataflow::handleRecursiveCleanup(const CallExpr &call, reference ? dyn_cast(reference->getDecl()) : nullptr; const auto *member = dyn_cast(assignment->getLHS()->IgnoreParenImpCasts()); - const bool localCursor = var != nullptr && var->hasLocalStorage() && - var->getType()->isPointerType(); + const bool localCursor = + var != nullptr && var->hasLocalStorage() && + (var->getType()->isPointerType() || + (!releases && var->getType()->isIntegerType())); const bool clearedPayload = member != nullptr && builder.classifyValue(*assignment->getRHS()).kind == @@ -765,27 +1317,53 @@ bool FunctionDataflow::handleRecursiveCleanup(const CallExpr &call, return member->getMemberNameInfo().getAsString() == payload.field.name; }); - candidate &= assignment->getOpcode() == BO_Assign && - (localCursor || clearedPayload); + candidate &= (assignment->getOpcode() == BO_Assign || !releases) && + (localCursor || (releases && clearedPayload)); + } + if (const auto *unary = dyn_cast(stmt); + unary && unary->isIncrementDecrementOp()) { + const auto *reference = + dyn_cast(unary->getSubExpr()->IgnoreParenImpCasts()); + const auto *var = + reference ? dyn_cast(reference->getDecl()) : nullptr; + candidate &= !releases && var != nullptr && var->hasLocalStorage() && + var->getType()->isIntegerType(); } - if (const auto *unary = dyn_cast(stmt)) - candidate &= !unary->isIncrementDecrementOp(); for (const auto *child : stmt->children()) work.push_back(child); } - recursiveCleanupCandidate = candidate && work.size() <= 65536; + recursiveContractCandidate = candidate && work.size() <= 65536; } - if (!*recursiveCleanupCandidate) + if (!*recursiveContractCandidate) + return false; + // RFC 0029: ownership resolution intentionally strips pointer arithmetic. + // An induction premise instead needs the exact node, not an interior or + // one-past address into the same allocation. + const auto origin = builder.classifyValue(*call.getArg(0)); + const auto actual = origin.place; + if (origin.kind != ValueOrigin::Kind::Copy || !actual || + !actual->element.isWhole() || !origin.offset.isZero() || + (origin.boundsOffset && !origin.boundsOffset->isZero()) || + std::ranges::any_of(origin.spatialSteps, + [](const auto &step) { return !step.isZero(); })) return false; - const auto actual = builder.resolvePointerValue(*call.getArg(0)); - if (!actual) + if (const auto spatial = spatialRecordAt(actual->place, state); + spatial && !spatial->offset.isZero()) return false; const auto fact = containerAt(actual->place, *shape, state); - if (!fact || !fact->tailOf || fact->tailField.empty() || - !shape->recursiveLink(fact->tailField) || - state.nulls.stateOf(*fact->tailOf) != core::Nullness::NonNull) + if (!fact) return false; - auto source = stableSummaryPathOf(*fact->tailOf); + const bool strict = + fact->tailOf && !fact->tailField.empty() && + shape->recursiveLink(fact->tailField) && + state.nulls.stateOf(*fact->tailOf) == core::Nullness::NonNull; + const bool forward = + actual->place == builder.placeForVar(*function.getParamDecl(0)) && + !state.safety->replacedPointers.contains(actual->place) && + fact->releasedChildren.empty() && fact->releasedPayloads.empty(); + if (!strict && !forward) + return false; + auto source = stableSummaryPathOf(strict ? *fact->tailOf : actual->place); if (!source && fact->inputs.size() == 1) if (const auto entry = containerInputs.find(*fact->inputs.begin()); entry != containerInputs.end()) @@ -794,34 +1372,69 @@ bool FunctionDataflow::handleRecursiveCleanup(const CallExpr &call, return false; if (!requireContainer(*fact, call, state)) return false; - recursiveCleanupPremises.insert(*source); + recursiveContractPremises.insert(*source); + recursiveContractCalls.emplace(callee->getCanonicalDecl(), strict); state.safety->containers.set(actual->place, *fact); - releaseFootprint(actual->place, true, state); - invalidateContainers(actual->place, true, false, state); - state.moves.markMoved(actual->place, core::MoveReason::Freed, locate(call)); - state.resources.clear(actual->place); + if (releases) { + releaseFootprint(actual->place, true, state); + invalidateContainers(actual->place, true, false, state); + state.moves.markMoved(actual->place, core::MoveReason::Freed, locate(call)); + state.resources.clear(actual->place); + } safetyObligation( core::SafetyProperty::Call, core::SafetyOutcome::Required, call, - "proper recursive child", - "recursive cleanup uses a proper child induction hypothesis"); + strict ? "proper recursive child" : "recursive forwarding", + strict ? "recursive call uses a proper child induction hypothesis" + : "recursive forwarding requires progress on every group cycle"); return true; } -void FunctionDataflow::verifyRecursiveCleanup() { - for (const auto &path : recursiveCleanupPremises) { +void FunctionDataflow::verifyRecursiveContract() { + const auto *group = summaries.recursiveContractGroup(function); + if (!group) + return; + if (group->constructs) + verifyRecursiveConstruction(); + if (group->writes) + verifyRecursiveWriter(); + if (group->members.size() == 1 && !recursiveContractCalls.empty()) { + std::vector edges; + for (const auto &[callee, strict] : recursiveContractCalls) { + (void)callee; + edges.push_back({.caller = 0, .callee = 0, .strict = strict}); + } + if (!core::validInductionProgress(1, edges)) { + summaries.failedRecursiveProgress.insert(function.getCanonicalDecl()); + safetyObligation(core::SafetyProperty::Semantics, + core::SafetyOutcome::Unresolved, *function.getBody(), + "recursive progress", + "recursive proof cycle has no strict progress"); + } + } + for (const auto &path : recursiveContractPremises) { const bool verified = std::ranges::any_of( inferred.checked.establishes, [&](const auto &post) { - return post.kind == core::CheckedRequirementKind::ContainerConsumed && + return post.kind == + (group->releases + ? core::CheckedRequirementKind::ContainerConsumed + : core::CheckedRequirementKind::ContainerPreserved) && post.path == path && post.when.trivial() && !post.on; }); + const auto *success = group->releases + ? "recursive cleanup conserves and releases the " + "complete input footprint" + : "recursive traversal preserves the complete " + "input footprint"; + const auto *failure = + group->releases ? "recursive cleanup does not establish complete " + "input footprint consumption" + : "recursive traversal does not establish complete " + "input footprint preservation"; safetyObligation(core::SafetyProperty::Semantics, verified ? core::SafetyOutcome::Proven : core::SafetyOutcome::Unresolved, *function.getBody(), "recursive footprint", - verified ? "recursive cleanup conserves and releases the " - "complete input footprint" - : "recursive cleanup does not establish complete " - "input footprint consumption"); + verified ? success : failure); } } diff --git a/lib/Analysis/DataflowIntegerExpressions.cpp b/lib/Analysis/DataflowIntegerExpressions.cpp index db8336ff..48add6f4 100644 --- a/lib/Analysis/DataflowIntegerExpressions.cpp +++ b/lib/Analysis/DataflowIntegerExpressions.cpp @@ -105,8 +105,44 @@ FunctionDataflow::integerExpressionOf(const Expr &expr, trait->getTypeOfArgument()->isVariablyModifiedType()) return variableArraySize(trait->getTypeOfArgument(), state); if (const auto *binary = dyn_cast(e)) { - if (const auto pointer = checkedPointerOperation(*binary, state)) - return NumericExpression::constant(*pointer); + if (const auto range = checkedPointerRange(*binary, state)) { + if (const auto pointer = range->constant()) + return NumericExpression::constant(*pointer); + if (binary->getOpcode() == BO_Sub && type->isSigned && + binary->getLHS()->getType()->getPointeeType()->isCharType()) { + const auto left = checkedMemory(*binary->getLHS(), {}, {}, state); + const auto right = checkedMemory(*binary->getRHS(), {}, {}, state); + const auto maximum = + core::IntegerRange::full(*type).maximum()->signedValue(); + const auto coordinate = + [&](const core::Affine &value) -> std::optional { + const auto offset = foldAffine(value, state); + if (!maximum || offset.scale != 1 || + !checkedAtMost({}, offset, state) || + !checkedAtMost(offset, core::Affine::ofConstant(*maximum), state)) + return std::nullopt; + if (!offset.place) + return NumericExpression::constant(core::IntegerValue::ofBits( + *type, static_cast(offset.constant))); + const auto input = core::Affine::ofPlace(*offset.place); + if (!checkedAtMost({}, input, state) || + !checkedAtMost(input, core::Affine::ofConstant(*maximum), state)) + return std::nullopt; + auto result = NumericExpression::input(*offset.place, *type); + if (offset.constant == 0) + return result; + return NumericExpression::operation( + core::IntegerOp::Add, result, + NumericExpression::constant(core::IntegerValue::ofBits( + *type, static_cast(offset.constant)))); + }; + const auto lhs = left ? coordinate(left->begin) : std::nullopt; + const auto rhs = right ? coordinate(right->begin) : std::nullopt; + if (lhs && rhs) + return NumericExpression::operation(core::IntegerOp::Subtract, *lhs, + *rhs); + } + } if (binary->getOpcode() == BO_Assign || binary->getOpcode() == BO_Comma) return child(*binary->getRHS()); if (binary->isCompoundAssignmentOp()) @@ -217,8 +253,13 @@ FunctionDataflow::integerExpressionOf(const Expr &expr, } if (weavec::analysis::PlaceBuilder::strlenArgumentOf(*e)) { const auto length = builder.legacyAffineOf(*e); - if (length && length->place && length->scale == 1 && length->constant == 0) + if (length && length->place && length->scale == 1 && + length->constant == 0) { + if (const auto stored = state.numericValues.find(*length->place); + stored != state.numericValues.end()) + return stored->second.converted(*type); return NumericExpression::input(*length->place, *type); + } } if (const auto *call = dyn_cast(e)) if (const auto result = numericCallResult(*call)) { @@ -292,6 +333,18 @@ FunctionDataflow::integerAffineOf(const Expr &expr, if (const auto *cast = dyn_cast(e); cast && preservesInteger(*cast, state)) return integerAffineOf(*cast->getSubExpr(), state); + if (state.safety) + if (const auto *conditional = dyn_cast(e); + conditional && !conditional->HasSideEffects(context)) { + // A selected arm or a recognized minimum/maximum keeps its relation to + // the operands; the evaluation-site union is only the weaker fallback. + if (const auto expression = integerExpressionOf(*conditional, state)) + return internIntegerExpression(*expression, state); + if (const auto saved = integerStatementResults.find(conditional); + saved != integerStatementResults.end() && saved->second && + state.scalars.factOf(*saved->second)) + return core::Affine::ofPlace(*saved->second); + } if (PlaceBuilder::isPlaceExpr(*e)) if (const auto place = builder.resolve(*e); place && numericEntryValues.contains(place->place)) @@ -313,13 +366,20 @@ std::optional FunctionDataflow::instantiateIntegerExpression(const core::PathAffine &value, const CallExpr &call, core::AnalysisState &state) { - if (!value.expression) - return std::nullopt; - const auto substituted = value.expression->substitute( - [&](const core::SummaryPath &path, - core::IntegerType type) -> std::optional { - return numericInput(call, path, type, state); - }); + std::optional substituted; + if (value.expression) { + substituted = value.expression->substitute( + [&](const core::SummaryPath &path, + core::IntegerType type) -> std::optional { + return numericInput(call, path, type, state); + }); + } else if (value.path && value.path->isParam() && value.path->isRoot() && + value.path->index < call.getNumArgs() && + value.quantity == core::AffineQuantity::Integer) { + if (const auto type = + integerTypeOf(call.getArg(value.path->index)->getType(), context)) + substituted = numericInput(call, *value.path, *type, state); + } if (!substituted) return std::nullopt; const auto scaled = diff --git a/lib/Analysis/DataflowIntegerStatements.cpp b/lib/Analysis/DataflowIntegerStatements.cpp index 13e91acf..63332d70 100644 --- a/lib/Analysis/DataflowIntegerStatements.cpp +++ b/lib/Analysis/DataflowIntegerStatements.cpp @@ -97,9 +97,7 @@ void FunctionDataflow::handleIntegerCompound(const CompoundAssignOperator &expr, if (expression) state.numericValues.insert_or_assign(*outputs, *expression); assignScalar(ref->place, nullptr, state, &expr); - auto cells = mirrors(ref->place, state); - cells.push_back(ref->place); - llvm::append_range(cells, borrowedImages(ref->place, state)); + const auto cells = scalarMirrors(ref->place, state); for (const auto cell : cells) { if (!tracksScalar(cell)) continue; @@ -107,8 +105,12 @@ void FunctionDataflow::handleIntegerCompound(const CompoundAssignOperator &expr, state.scalars.set( cell, core::ValueFact::ofInteger(result.values.converted(*storage))); if (const auto frozen = state.numericValues.find(*outputs); - frozen != state.numericValues.end() && !frozen->second.dependsOn(cell)) + frozen != state.numericValues.end() && + !frozen->second.dependsOn(cell)) { state.numericValues.insert_or_assign(cell, frozen->second); + if (const auto input = frozen->second.inputKey()) + state.relations.learn(cell, core::Relation::Equal, *input); + } } state.numericValues.erase(*outputs); // The right hand side was evaluated before overwriting the logical count. diff --git a/lib/Analysis/DataflowIntegers.cpp b/lib/Analysis/DataflowIntegers.cpp index 28323b6e..756300dc 100644 --- a/lib/Analysis/DataflowIntegers.cpp +++ b/lib/Analysis/DataflowIntegers.cpp @@ -18,7 +18,8 @@ namespace weavec::analysis { core::IntegerRange FunctionDataflow::integerRangeAt(core::PlaceId place, core::IntegerType type, - const core::AnalysisState &state) { + const core::AnalysisState &state, + unsigned equalityDepth) { auto range = core::IntegerRange::full(type); if (builder.isLengthPlace(place)) { const auto sizeType = integerTypeOf(context.getSizeType(), context); @@ -33,6 +34,9 @@ FunctionDataflow::integerRangeAt(core::PlaceId place, core::IntegerType type, range = core::IntegerRange::full(*storage).converted(type); if (const auto fact = state.scalars.factOf(place)) range = range.intersect(fact->inType(type)); + if (checkedZeroInteger(place, state)) + range = range.intersect( + core::IntegerRange::singleton(core::IntegerValue::ofBits(type, 0))); const auto restrict = [&](std::optional bound, core::IntegerOp op) { if (!bound) @@ -64,6 +68,12 @@ FunctionDataflow::integerRangeAt(core::PlaceId place, core::IntegerType type, auto otherRange = core::IntegerRange::full(*otherType); if (const auto fact = state.scalars.factOf(other)) otherRange = otherRange.intersect(fact->inType(*otherType)); + if (equalityDepth == 0 && state.safety && + edge->relation == core::Relation::Equal && + checkedLoopCounters.contains(place) && + checkedLoopCounters.contains(other)) + otherRange = otherRange.intersect( + integerRangeAt(other, *otherType, state, equalityDepth + 1)); if (otherRange.empty()) continue; // RFC 0026: size_t's upper half is not representable by signedValue(). @@ -288,6 +298,8 @@ std::optional FunctionDataflow::integerRangeOf( .mayBeInvalid = a->mayBeInvalid || b->mayBeInvalid}; } if (PlaceBuilder::isPlaceExpr(*e)) { + if (const auto bytes = checkedByteRange(*e, state)) + return core::IntegerRangeEvaluation{.values = bytes->converted(*type)}; if (const auto *ref = dyn_cast(e); ref && isa(ref->getDecl())) { const auto &value = cast(ref->getDecl())->getInitVal(); @@ -427,8 +439,21 @@ bool FunctionDataflow::refineIntegerComparison(const Expr &lhs, const auto left = builder.scalarOperand(lhs); const auto right = builder.scalarOperand(rhs); const auto trusted = [&](const PlaceBuilder::ScalarOperand &read) { - return !read.place || !places.innermostDeref(read.place->place) || - !memoryContext.empty(); + if (!read.place || !places.innermostDeref(read.place->place) || + !memoryContext.empty()) + return true; + const auto memory = checkedScalarMemory(read.place->place, state); + if (!memory || !memory->extent || !checkedValid(*memory, state) || + !checkedInitialized(*memory, state) || + !checkedInterval(memory->begin, memory->end, *memory->extent, state)) + return false; + const auto *local = builder.varForPlace(places.root(memory->storage)); + if (local && local->hasLocalStorage() && + !places.innermostDeref(memory->storage)) + return true; + return std::ranges::any_of(checkedObjects, [&](const auto &entry) { + return entry.second == memory->storage; + }); }; if (narrowed.empty()) { if (trusted(left) && trusted(right)) diff --git a/lib/Analysis/DataflowNumericInputs.cpp b/lib/Analysis/DataflowNumericInputs.cpp index 6fc39778..9748664f 100644 --- a/lib/Analysis/DataflowNumericInputs.cpp +++ b/lib/Analysis/DataflowNumericInputs.cpp @@ -85,6 +85,14 @@ void FunctionDataflow::captureNumericInputs( const auto affine = [&](const core::PathAffine &value) { if (value.expression) expression(*value.expression); + else if (options.checkContracts && value.path && value.path->isParam() && + value.path->isRoot() && value.path->index < call.getNumArgs() && + value.quantity == core::AffineQuantity::Integer && + isa( + call.getArg(value.path->index)->IgnoreParenCasts())) + if (const auto type = + integerTypeOf(call.getArg(value.path->index)->getType(), context)) + dependencies.emplace(*value.path, *type); }; const auto source = [&](const core::ValueSource &value) { guard(value.when); @@ -156,6 +164,7 @@ void FunctionDataflow::captureNumericInputs( state.dropGuardsOn(saved); state.scalars.forget(saved); state.relations.forget(saved); + state.numericValues.erase(saved); numericSnapshotExpressions.erase(saved); } for (const auto &[path, type] : dependencies) { @@ -172,8 +181,20 @@ void FunctionDataflow::captureNumericInputs( // never take (including a null argument). Capture unknown here; eagerly // diagnosing every possible dependency would add warnings even when its // guarded consumer is refuted or the null access is already diagnosed. - if (!value) + if (!value) { + if (options.checkContracts && path.isParam() && path.isRoot() && + path.index < call.getNumArgs()) { + const auto *argument = call.getArg(path.index); + if (!argument->HasSideEffects(context) && + isa(argument->IgnoreParenCasts())) + if (const auto range = integerRangeOf(*argument, state); + range && !range->mayBeInvalid) + state.scalars.set( + saved->second, + core::ValueFact::ofInteger(range->values.converted(type))); + } continue; + } const auto evaluated = evaluateNumericExpression(*value, state); if (!evaluated.mayBeInvalid) state.scalars.set(saved->second, diff --git a/lib/Analysis/DataflowNumericOutputs.cpp b/lib/Analysis/DataflowNumericOutputs.cpp index e7102f0c..bad400af 100644 --- a/lib/Analysis/DataflowNumericOutputs.cpp +++ b/lib/Analysis/DataflowNumericOutputs.cpp @@ -15,6 +15,34 @@ using namespace clang; namespace weavec::analysis { +static bool numericPathKeepsActualCell(const core::SummaryPath &path, + const CallExpr &call, + PlaceBuilder &builder) { + if (!path.isParam() || path.steps.empty() || + path.steps.front().step != core::PathStep::Deref) + return true; + if (path.index >= call.getNumArgs()) + return false; + const auto &argument = *call.getArg(path.index); + if (builder.addressedPlace(argument)) + return true; + const auto origin = builder.classifyValue(argument); + std::vector pending{&origin}; + while (!pending.empty()) { + const auto *value = pending.back(); + pending.pop_back(); + if (value->kind == ValueOrigin::Kind::Conditional) { + for (const auto &alternative : value->alternatives) + pending.push_back(&alternative); + } else if (!value->offset.isZero()) { + // A may-effect can name the pointee summary of p + n. A numeric + // must-output cannot silently assign that value to *p (RFC 0029). + return false; + } + } + return true; +} + void FunctionDataflow::recordNumericOutputs(const Expr *value, const core::AnalysisState &state) { if (!recording()) @@ -176,6 +204,8 @@ void FunctionDataflow::prepareNumericCall(const CallExpr &call, for (const auto &[path, outputs] : summary.numericOutputs) { if (path.isResult() && !path.isRoot()) continue; + if (!numericPathKeepsActualCell(path, call, builder)) + continue; std::optional type; if (path.isResult()) { type = integerTypeOf(call.getType(), context); @@ -304,19 +334,14 @@ void FunctionDataflow::finishNumericCall(const CallExpr &call, if (path.isResult()) continue; const auto dest = builder.resolveSummaryPath(path, call); - if (!dest) + if (!dest || !dest->element.isWhole()) continue; const auto fact = state.scalars.factOf(saved); const auto expression = state.numericValues.find(saved); const auto value = expression == state.numericValues.end() ? std::nullopt : std::optional(expression->second); - auto cells = mirrors(dest->place, state); - if (!llvm::is_contained(cells, dest->place)) - cells.push_back(dest->place); - for (const auto cell : borrowedImages(dest->place, state)) - if (!llvm::is_contained(cells, cell)) - cells.push_back(cell); + const auto cells = scalarMirrors(dest->place, state); for (const auto cell : cells) { auto [entry, inserted] = outputs.try_emplace(cell, Output{.fact = fact, .value = value}); diff --git a/lib/Analysis/DataflowRecursiveConstruction.cpp b/lib/Analysis/DataflowRecursiveConstruction.cpp new file mode 100644 index 00000000..4d235737 --- /dev/null +++ b/lib/Analysis/DataflowRecursiveConstruction.cpp @@ -0,0 +1,423 @@ +//===- DataflowRecursiveConstruction.cpp - Fresh recursive outputs -------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Dataflow.h" +#include "IntegerSupport.h" + +#include +#include + +using namespace clang; +namespace weavec::analysis { + +static core::SummaryPath +constructionDataPath(const SummaryStore::RecursiveContractGroup &group) { + const auto root = group.mutableReader ? core::SummaryPath::param(0).deref() + : core::SummaryPath::param(0); + return group.readerData ? root.field(group.readerData->getNameAsString()) + : root; +} + +static core::SummaryPath +constructionCountPath(const SummaryStore::RecursiveContractGroup &group) { + const auto root = group.mutableReader ? core::SummaryPath::param(0).deref() + : core::SummaryPath::param(0); + return group.readerCount ? root.field(group.readerCount->getNameAsString()) + : core::SummaryPath::param(1); +} + +std::shared_ptr +FunctionDataflow::recursiveInputCandidate(const FunctionDecl &callee) { + const auto *group = summaries.recursiveContractGroup(function); + if (!group || (!group->constructs && !group->writes)) + return {}; + if (group->writes) + return recursiveWriterCandidate(callee); + if (group->extendsHead) + return recursiveExtensionCandidate(callee); + const bool outputSlot = callee.getNumParams() == 3; + const auto type = outputSlot + ? callee.getParamDecl(2)->getType()->getPointeeType() + : callee.getReturnType(); + const auto *discovered = containerShape(type); + if (!discovered) + return {}; + auto shape = *discovered; + shape.access = core::ContainerAccess::Release; + shape.family = "free"; + shape.terminal = false; + shape.emptyLinks.clear(); + shape.emptyPayloads.clear(); + shape.headValues.clear(); + if (!shape.valid()) + return {}; + auto result = std::make_shared(); + result->checked.computed = true; + result->checked.signature = functionTypeKey(callee.getType(), context); + const auto input = constructionDataPath(*group); + const auto length = core::PathAffine::ofPath(constructionCountPath(*group)); + result->checked.require({.kind = core::CheckedRequirementKind::Valid, + .path = input, + .other = {}, + .family = {}}); + for (const auto kind : {core::CheckedRequirementKind::Extent, + core::CheckedRequirementKind::Initialized}) + result->checked.require({.kind = kind, + .path = input, + .other = {}, + .end = length, + .family = {}}); + result->addEffect(input.deref(), {.read = true}); + if (group->mutableReader) { + const auto reader = core::SummaryPath::param(0); + const auto bytes = + context + .getTypeSizeInChars( + callee.getParamDecl(0)->getType()->getPointeeType()) + .getQuantity(); + result->checked.require({.kind = core::CheckedRequirementKind::Valid, + .path = reader, + .other = {}, + .family = {}}); + for (const auto kind : {core::CheckedRequirementKind::Extent, + core::CheckedRequirementKind::Initialized, + core::CheckedRequirementKind::Writable}) + result->checked.require({.kind = kind, + .path = reader, + .other = {}, + .end = core::PathAffine::ofConstant(bytes), + .family = {}}); + result->checked.require({.kind = core::CheckedRequirementKind::Separated, + .path = reader, + .other = input, + .family = {}}); + result->addEffect(input, {.written = true}); + result->addEffect(constructionCountPath(*group), {.written = true}); + } + const auto fresh = core::ValueSource::freshAt( + "free", core::PointerOffset::zero(), + core::PathAffine::ofConstant( + static_cast(shape.object.bytes))); + const auto output = outputSlot ? core::SummaryPath::param(2).deref() + : core::SummaryPath::result(); + const auto success = + outputSlot ? core::Outcome::Positive : core::Outcome::NonNull; + if (outputSlot) { + const auto slot = core::SummaryPath::param(2); + const auto bytes = context.getTypeSizeInChars(type).getQuantity(); + result->checked.require({.kind = core::CheckedRequirementKind::Valid, + .path = slot, + .other = {}, + .family = {}}); + for (const auto kind : {core::CheckedRequirementKind::Extent, + core::CheckedRequirementKind::Writable}) + result->checked.require({.kind = kind, + .path = slot, + .other = {}, + .end = core::PathAffine::ofConstant(bytes), + .family = {}}); + result->checked.require({.kind = core::CheckedRequirementKind::Separated, + .path = input, + .other = slot, + .family = {}}); + result->addEffect(output, {.written = true}); + result->addStore({.dest = output, .value = fresh}); + result->addStore({.dest = output, .value = core::ValueSource::null()}); + result->outcomes[core::Outcome::Zero] = {}; + result->outcomes[success] = {}; + result->nullOn[core::Outcome::Zero].insert(output); + result->nonNullOn[success].insert(output); + } else { + result->addReturn(fresh); + result->addReturn(core::ValueSource::null()); + } + for (const auto kind : {core::CheckedRequirementKind::Container, + core::CheckedRequirementKind::ContainerFresh}) + result->checked.establish({.kind = kind, + .path = output, + .other = {}, + .family = shape.encode(), + .on = success}); + return result; +} + +void FunctionDataflow::initializeRecursiveInput(core::AnalysisState &state) { + const auto candidate = recursiveInputCandidate(function); + if (!candidate) + return; + const auto &group = *summaries.recursiveContractGroup(function); + if (group.extendsHead) { + initializeRecursiveExtension(state); + return; + } + const auto parameter = builder.placeForVar(*function.getParamDecl(0)); + const auto root = group.mutableReader ? places.deref(parameter) : parameter; + const auto holder = + group.readerData ? builder.fieldPlace(root, *group.readerData) : root; + const auto count = group.readerCount + ? builder.fieldPlace(root, *group.readerCount) + : builder.placeForVar(*function.getParamDecl(1)); + if (group.readerCount && !numericEntryValues.contains(count)) { + const auto type = integerTypeOf(*group.readerCount, context); + if (!type) + return; + const auto saved = places.create("entry(" + nameOf(count) + ")"); + numericEntryValues.emplace(count, saved); + state.scalars.set( + saved, core::ValueFact::ofInteger(core::IntegerRange::full(*type))); + snapshotPlaces.insert(saved); + numericSnapshotExpressions.emplace( + saved, core::IntegerExpression::input( + constructionCountPath(group), *type)); + state.numericValues.insert_or_assign( + count, NumericExpression::input(saved, *type)); + state.relations.learn(count, core::Relation::Equal, saved); + } + const auto saved = numericEntryValues.find(count); + const auto length = core::Affine::ofPlace( + saved == numericEntryValues.end() ? count : saved->second); + if (group.readerData) + state.safety->positions.insert_or_assign( + holder, core::PointerPosition{.storage = places.deref(holder), + .offset = {}, + .extent = length, + .input = holder}); + const auto memory = checkedMemoryAt(holder, {}, length, state); + if (!memory || memory->input != constructionDataPath(group) || + memory->begin != core::Affine::ofConstant(0)) + return; + // This is an explicit sufficient entry premise, never an output. Normal + // stores, calls and alias invalidation retire the dependent memory facts. + for (const auto &requirement : candidate->checked.requirements) + inferred.checked.require(requirement); + state.nulls.set(holder, {.state = core::Nullness::NonNull, + .location = {}, + .reason = core::NullReason::Declared}); + state.safety->pointers.insert(holder); + state.safety->accessible[memory->storage] = length; + if (const auto position = state.safety->positions.find(holder); + position != state.safety->positions.end()) + position->second.extent = length; + state.safety->initialize( + memory->storage, {.begin = {}, .end = length, .when = {}, .source = {}}); + if (group.writes) + initializeRecursiveWriter(state); + if (group.mutableReader) { + const auto bytes = + context + .getTypeSizeInChars( + function.getParamDecl(0)->getType()->getPointeeType()) + .getQuantity(); + state.nulls.set(parameter, {.state = core::Nullness::NonNull, + .location = {}, + .reason = core::NullReason::Declared}); + state.safety->pointers.insert(parameter); + state.safety->accessible[root] = core::Affine::ofConstant(bytes); + state.safety->initialize( + root, {.begin = {}, .end = core::Affine::ofConstant(bytes)}); + } +} + +std::shared_ptr +FunctionDataflow::recursiveInputCall(const CallExpr &call, + core::AnalysisState &state) { + const auto *callee = call.getDirectCallee(); + const auto *group = summaries.recursiveContractGroup(function); + if (!group || (!group->constructs && !group->writes) || !callee || + call.getNumArgs() != function.getNumParams() || + !summaries.recursiveContractPeer(function, *callee)) + return {}; + if (group->extendsHead) + return recursiveExtensionCall(call, state); + auto candidate = recursiveInputCandidate(*callee); + auto length = + group->readerCount + ? builder.affineFromPath( + core::PathAffine::ofPath(constructionCountPath(*group)), call) + : integerAffineOf(*call.getArg(1), state); + if (length) + length = foldAffine(*length, state); + const auto root = builder.placeForVar(*function.getParamDecl(0)); + const auto count = + group->readerCount + ? builder.fieldPlace(group->mutableReader ? places.deref(root) : root, + *group->readerCount) + : builder.placeForVar(*function.getParamDecl(1)); + const auto saved = numericEntryValues.find(count); + const auto entry = core::Affine::ofPlace( + saved == numericEntryValues.end() ? count : saved->second); + const auto memory = length ? checkedPathMemory(constructionDataPath(*group), + call, {}, *length, state) + : std::nullopt; + if (!candidate || !length || !memory || + memory->input != constructionDataPath(*group) || + !checkedAtMost({}, *length, state) || + !checkedAtMost({}, memory->begin, state) || + !checkedAtMost(memory->end, entry, state) || + !checkedAtMost(*length, entry, state)) + return {}; + const auto successor = length->shifted(1); + const bool strict = successor && checkedAtMost(*successor, entry, state); + recursiveInputUsed = true; + recursiveContractCalls.emplace(callee->getCanonicalDecl(), strict); + const char *reason = + "recursive forwarding requires progress on every group cycle"; + if (strict) + reason = + group->writes + ? "recursive writer decreases the initialized input interval" + : "recursive construction decreases the initialized input interval"; + safetyObligation(core::SafetyProperty::Call, core::SafetyOutcome::Required, + call, "recursive input interval", reason); + // resolveCall retains this privately for the ordinary precondition and + // output transfer machinery. No candidate is installed in SummaryStore. + return candidate; +} + +bool FunctionDataflow::recursiveInputRequirementsCovered( + const core::FunctionSummary &candidate) { + const auto &group = *summaries.recursiveContractGroup(function); + const auto inputPath = constructionDataPath(group); + const auto countPath = constructionCountPath(group); + bool verified = true; + const auto countType = + group.readerCount + ? integerTypeOf(*group.readerCount, context) + : integerTypeOf(function.getParamDecl(1)->getType(), context); + for (const auto &pre : inferred.checked.requirements) { + auto unguarded = pre; + unguarded.when.clear(); + if (candidate.checked.requirements.contains(unguarded)) + continue; + const unsigned header = group.writes ? 2 : 0; + if ((group.mutableReader || group.writes) && + pre.path == core::SummaryPath::param(header) && + (pre.kind == core::CheckedRequirementKind::Extent || + pre.kind == core::CheckedRequirementKind::Initialized || + pre.kind == core::CheckedRequirementKind::Writable)) { + const auto bytes = + context + .getTypeSizeInChars( + function.getParamDecl(header)->getType()->getPointeeType()) + .getQuantity(); + if (pre.begin.isConstant() && pre.end.isConstant() && + pre.begin.constant >= 0 && pre.begin.constant <= pre.end.constant && + pre.end.constant <= bytes) + continue; + } + bool covered = pre.path == inputPath && + (pre.kind == core::CheckedRequirementKind::Valid || + pre.kind == core::CheckedRequirementKind::Extent || + pre.kind == core::CheckedRequirementKind::Initialized); + if (covered && pre.kind != core::CheckedRequirementKind::Valid) { + std::uint64_t minimum = 0; + if (const auto condition = pre.when.conditions.find(countPath); + condition != pre.when.conditions.end() && countType) + if (const auto lower = condition->second.inType(*countType).minimum()) + minimum = lower->bits; + for (const auto &condition : pre.when.integers) + if (countType && condition.range && + condition.lhs.type() == *countType && + condition.lhs.inputKey() == countPath && + condition.range->type == *countType) + if (const auto lower = condition.range->minimum()) + minimum = std::max(minimum, lower->bits); + const auto coveredEndpoint = [&](const core::PathAffine &bound, + bool upper) { + if (bound.quantity != core::AffineQuantity::Integer) + return false; + auto path = bound.path; + if (bound.expression) { + if (!countType || bound.expression->type() != *countType || + bound.expression->inputKey() != countPath) + return false; + path = countPath; + } + if (!path) + return bound.constant >= 0 && + (!upper || std::cmp_less_equal(bound.constant, minimum)); + return path == countPath && bound.scale == 1 && bound.constant <= 0 && + std::uint64_t{0} - static_cast(bound.constant) <= + minimum; + }; + covered &= + coveredEndpoint(pre.begin, false) && coveredEndpoint(pre.end, true); + } + verified &= covered; + } + return verified; +} + +void FunctionDataflow::verifyRecursiveConstruction() { + if (const auto *group = summaries.recursiveContractGroup(function); + group && group->extendsHead) { + verifyRecursiveExtension(); + return; + } + if (!recursiveInputUsed) + return; + const auto candidate = recursiveInputCandidate(function); + const auto &group = *summaries.recursiveContractGroup(function); + const auto inputPath = constructionDataPath(group); + const auto countPath = constructionCountPath(group); + const bool outputSlot = function.getNumParams() == 3; + bool verified = candidate != nullptr; + if (outputSlot) { + const auto output = core::SummaryPath::param(2).deref(); + for (const auto &[outcome, effects] : inferred.outcomes) { + (void)effects; + if (outcome == core::Outcome::Zero) { + const auto nulls = checkedNullOutputClasses.find(outcome); + verified &= nulls != checkedNullOutputClasses.end() && + nulls->second.contains(output); + } else if (outcome == core::Outcome::Positive) { + const auto nonnull = inferred.nonNullOn.find(outcome); + verified &= nonnull != inferred.nonNullOn.end() && + nonnull->second.contains(output); + } else { + verified = false; + } + } + } else { + verified &= + inferred.returnsOnlyFresh() && inferred.freshReturnFamily() == "free"; + } + if (candidate) { + verified &= recursiveInputRequirementsCovered(*candidate); + for (const auto &[path, effect] : inferred.effects) + verified &= + !path.isGlobal() && + (!effect.written || + (group.mutableReader && (path == inputPath || path == countPath)) || + (outputSlot && path == core::SummaryPath::param(2).deref())) && + !effect.consumed() && !effect.escaped && !effect.replaced; + for (const auto &expected : candidate->checked.establishes) { + const auto needed = core::ContainerShape::decode(expected.family); + verified &= + needed && std::ranges::any_of( + inferred.checked.establishes, [&](const auto &post) { + const auto actual = + core::ContainerShape::decode(post.family); + return post.kind == expected.kind && + post.path == expected.path && + post.when.trivial() && !post.ifNonNull && + (!post.on || post.on == expected.on) && + actual && actual->entails(*needed); + }); + } + } + safetyObligation( + core::SafetyProperty::Semantics, + verified ? core::SafetyOutcome::Proven : core::SafetyOutcome::Unresolved, + *function.getBody(), "recursive construction output", + verified ? "recursive construction establishes a fresh initialized forest" + : "recursive construction does not establish its complete " + "output contract"); +} + +} // namespace weavec::analysis diff --git a/lib/Analysis/DataflowRecursiveExtension.cpp b/lib/Analysis/DataflowRecursiveExtension.cpp new file mode 100644 index 00000000..eba98a8d --- /dev/null +++ b/lib/Analysis/DataflowRecursiveExtension.cpp @@ -0,0 +1,204 @@ +//===- DataflowRecursiveExtension.cpp - Owned head construction ---------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Dataflow.h" + +#include +#include + +using namespace clang; + +namespace weavec::analysis { + +std::shared_ptr +FunctionDataflow::recursiveExtensionCandidate(const FunctionDecl &callee) { + const auto type = callee.getParamDecl(0)->getType(); + const auto *discovered = containerShape(type); + const auto *record = type->getPointeeType()->getAsRecordDecl(); + if (!discovered || !record) + return {}; + auto input = *discovered; + input.access = core::ContainerAccess::Release; + input.family = "free"; + input.terminal = true; + input.emptyLinks.clear(); + input.headValues.clear(); + for (const auto &payload : input.payloads) + input.emptyPayloads.insert(payload.field.name); + if (!input.singletonHead()) + return {}; + auto output = input; + output.terminal = false; + output.emptyPayloads.clear(); + const auto root = core::SummaryPath::param(0); + auto result = std::make_shared(); + result->checked.computed = true; + result->checked.signature = functionTypeKey(callee.getType(), context); + result->checked.require({.kind = core::CheckedRequirementKind::Valid, + .path = root, + .other = {}, + .family = {}}); + result->checked.require({.kind = core::CheckedRequirementKind::Container, + .path = root, + .other = {}, + .family = input.encode()}); + result->addEffect(root.deref(), {.read = true, .written = true}); + for (const auto *field : record->fields()) { + const auto path = root.deref().field(field->getNameAsString()); + result->addEffect(path, {.written = true}); + if (field->getType()->isPointerType()) + result->addStore({.dest = path, .value = core::ValueSource::unknown()}); + } + result->checked.establish( + {.kind = core::CheckedRequirementKind::ContainerExtended, + .path = root, + .other = root, + .family = output.encode()}); + return result; +} + +void FunctionDataflow::initializeRecursiveExtension( + core::AnalysisState &state) { + const auto candidate = recursiveExtensionCandidate(function); + if (!candidate) + return; + const auto root = core::SummaryPath::param(0); + const auto holder = builder.placeForVar(*function.getParamDecl(0)); + for (const auto &requirement : candidate->checked.requirements) { + inferred.checked.require(requirement); + if (requirement.kind != core::CheckedRequirementKind::Container) + continue; + const auto shape = core::ContainerShape::decode(requirement.family); + if (!shape) + continue; + initializeFootprint(holder, root, *shape, state); + footprintEntries.at(root).shape = *shape; + state.safety->containers.set(holder, containerInput(holder, root, *shape)); + state.nulls.set(holder, {.state = core::Nullness::NonNull, + .location = {}, + .reason = core::NullReason::Declared}); + state.safety->pointers.insert(holder); + const auto object = places.deref(holder); + state.safety->accessible[object] = core::Affine::ofConstant( + static_cast(shape->object.bytes)); + for (const auto &field : shape->initialized) + state.safety->initialize( + object, {.begin = core::Affine::ofConstant( + static_cast(field.offset)), + .end = core::Affine::ofConstant( + static_cast(field.offset + field.bytes))}); + const auto nullField = [&](const core::ContainerField &field) { + const auto cell = places.field(object, field.name); + state.nulls.set(cell, {.state = core::Nullness::Null, + .location = {}, + .reason = core::NullReason::Declared}); + state.resources.markNull(cell); + state.safety->footprints.constrain({{cell, 1}}); + }; + nullField(shape->link); + for (const auto &child : shape->children) + nullField(child); + for (const auto &payload : shape->payloads) + nullField(payload.field); + state.safety->footprints.constrain( + {{footprintHead(holder), 1}, {footprintEntries.at(root).identity, -1}}); + } +} + +std::shared_ptr +FunctionDataflow::recursiveExtensionCall(const CallExpr &call, + core::AnalysisState &state) { + const auto *callee = call.getDirectCallee(); + if (!callee || call.getNumArgs() != 2) + return {}; + auto candidate = recursiveExtensionCandidate(*callee); + auto count = integerAffineOf(*call.getArg(1), state); + if (!candidate || !count) + return {}; + count = foldAffine(*count, state); + const auto parameter = builder.placeForVar(*function.getParamDecl(1)); + const auto saved = numericEntryValues.find(parameter); + const auto entry = core::Affine::ofPlace( + saved == numericEntryValues.end() ? parameter : saved->second); + if (!checkedAtMost({}, *count, state) || !checkedAtMost(*count, entry, state)) + return {}; + const auto next = count->shifted(1); + const bool strict = next && checkedAtMost(*next, entry, state); + recursiveInputUsed = true; + recursiveContractCalls.emplace(callee->getCanonicalDecl(), strict); + safetyObligation( + core::SafetyProperty::Call, core::SafetyOutcome::Required, call, + "recursive construction count", + strict ? "recursive construction decreases its immutable entry count" + : "recursive forwarding requires progress on every group cycle"); + return candidate; +} + +void FunctionDataflow::verifyRecursiveExtension() { + if (!recursiveInputUsed) + return; + const auto candidate = recursiveExtensionCandidate(function); + bool verified = candidate != nullptr; + if (candidate) { + const auto root = core::SummaryPath::param(0); + const auto input = std::ranges::find_if( + candidate->checked.requirements, [](const auto &pre) { + return pre.kind == core::CheckedRequirementKind::Container; + }); + const auto shape = core::ContainerShape::decode(input->family).value(); + for (auto pre : inferred.checked.requirements) { + pre.when.clear(); + if (candidate->checked.requirements.contains(pre)) + continue; + bool covered = false; + if (pre.path == root && + pre.kind == core::CheckedRequirementKind::Container) + if (const auto needed = core::ContainerShape::decode(pre.family)) + covered = shape.entails(*needed); + if (pre.path == root && pre.begin.isConstant() && pre.end.isConstant() && + pre.begin.constant >= 0 && pre.end.constant >= pre.begin.constant && + std::cmp_less_equal(pre.end.constant, shape.object.bytes)) { + covered |= pre.kind == core::CheckedRequirementKind::Extent || + pre.kind == core::CheckedRequirementKind::Writable; + if (pre.kind == core::CheckedRequirementKind::Initialized) + covered |= + std::ranges::any_of(shape.initialized, [&](const auto &field) { + return std::cmp_less_equal(field.offset, pre.begin.constant) && + std::cmp_less_equal(pre.end.constant, + field.offset + field.bytes); + }); + } + verified &= covered; + } + for (const auto &[path, effect] : inferred.effects) + verified &= !path.isGlobal() && !effect.consumed() && !effect.escaped && + !effect.replaced && + (!effect.written || + (path.isParam() && path.index == 0 && path.hasDeref())); + for (const auto &expected : candidate->checked.establishes) + verified &= + std::ranges::any_of(inferred.checked.establishes, [&](auto actual) { + const auto established = + core::ContainerShape::decode(actual.family); + const auto required = core::ContainerShape::decode(expected.family); + actual.family = expected.family; + return actual == expected && established && required && + established->entails(*required); + }); + } + safetyObligation( + core::SafetyProperty::Semantics, + verified ? core::SafetyOutcome::Proven : core::SafetyOutcome::Unresolved, + *function.getBody(), "recursive construction output", + verified ? "recursive construction preserves its head and accounts for " + "every fresh descendant" + : "recursive construction does not establish its complete " + "output contract"); +} + +} // namespace weavec::analysis diff --git a/lib/Analysis/DataflowRecursiveWriting.cpp b/lib/Analysis/DataflowRecursiveWriting.cpp new file mode 100644 index 00000000..a95eac73 --- /dev/null +++ b/lib/Analysis/DataflowRecursiveWriting.cpp @@ -0,0 +1,160 @@ +//===- DataflowRecursiveWriting.cpp - Recursive buffer outputs ----------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Dataflow.h" + +#include + +using namespace clang; +namespace weavec::analysis { + +std::shared_ptr +FunctionDataflow::recursiveWriterCandidate(const FunctionDecl &callee) { + const auto type = callee.getParamDecl(2)->getType()->getPointeeType(); + const auto *record = type->getAsRecordDecl(); + const auto shape = record ? discoverBufferShape(*record) : std::nullopt; + if (!shape || shape->elementBytes != 1 || shape->pointerElements || + shape->reader || type.isConstQualified() || type.isVolatileQualified()) + return {}; + auto result = std::make_shared(); + result->checked.computed = true; + result->checked.signature = functionTypeKey(callee.getType(), context); + const auto input = core::SummaryPath::param(0); + const auto length = core::PathAffine::ofPath(core::SummaryPath::param(1)); + const auto header = core::SummaryPath::param(2); + const auto object = header.deref(); + const auto data = object.field(shape->data.name); + result->checked.require({.kind = core::CheckedRequirementKind::Valid, + .path = input, + .other = {}, + .family = {}}); + for (const auto kind : {core::CheckedRequirementKind::Extent, + core::CheckedRequirementKind::Initialized}) + result->checked.require({.kind = kind, + .path = input, + .other = {}, + .end = length, + .family = {}}); + result->checked.require({.kind = core::CheckedRequirementKind::Valid, + .path = header, + .other = {}, + .family = {}}); + for (const auto kind : {core::CheckedRequirementKind::Extent, + core::CheckedRequirementKind::Initialized, + core::CheckedRequirementKind::Writable}) + result->checked.require( + {.kind = kind, + .path = header, + .other = {}, + .end = core::PathAffine::ofConstant( + context.getTypeSizeInChars(type).getQuantity()), + .family = {}}); + for (const auto &other : {header, data}) + result->checked.require({.kind = core::CheckedRequirementKind::Separated, + .path = input, + .other = other, + .family = {}}); + result->checked.require({.kind = core::CheckedRequirementKind::Separated, + .path = header, + .other = data, + .family = {}}); + result->checked.require( + {.kind = core::CheckedRequirementKind::Writable, + .path = data, + .other = {}, + .end = core::PathAffine::ofPath(object.field(shape->capacity.name)), + .family = {}}); + result->checked.require({.kind = core::CheckedRequirementKind::Buffer, + .path = object, + .other = {}, + .family = shape->encode()}); + result->checked.establish({.kind = core::CheckedRequirementKind::Buffer, + .path = object, + .other = {}, + .family = shape->encode()}); + result->addEffect(input.deref(), {.read = true}); + result->addEffect(data.deref(), {.written = true}); + result->addEffect(object.field(shape->length.name), {.written = true}); + result->outcomes[core::Outcome::Zero] = {}; + result->outcomes[core::Outcome::Positive] = {}; + return result; +} + +void FunctionDataflow::initializeRecursiveWriter(core::AnalysisState &state) { + const auto candidate = recursiveWriterCandidate(function); + if (!candidate) + return; + const auto header = builder.placeForVar(*function.getParamDecl(2)); + const auto object = places.deref(header); + const auto type = function.getParamDecl(2)->getType()->getPointeeType(); + registerBuffer(object, *type->getAsRecordDecl()); + const auto found = bufferObjects.find(object); + if (found == bufferObjects.end()) + return; + const auto &shape = found->second; + const auto data = places.field(object, shape.data.name); + // Forwarding members may not themselves read a field. Their candidate still + // has the same explicit buffer entry requirement as every other member. + checkedInputObjects[data] = places.deref(data); + bufferEntryBackings.insert(data); + state.safety->buffers.set( + data, {.shape = shape, + .object = object, + .length = places.field(object, shape.length.name), + .capacity = places.field(object, shape.capacity.name), + .initialized = true, + .entryBacking = data}); + state.nulls.set(header, {.state = core::Nullness::NonNull, + .location = {}, + .reason = core::NullReason::Declared}); + state.safety->pointers.insert(header); + const auto bytes = + core::Affine::ofConstant(context.getTypeSizeInChars(type).getQuantity()); + state.safety->accessible[object] = bytes; + state.safety->initialize(object, {.begin = {}, .end = bytes}); + materializeBuffers(state); +} + +void FunctionDataflow::verifyRecursiveWriter() { + if (!recursiveInputUsed) + return; + const auto candidate = recursiveWriterCandidate(function); + bool verified = candidate && recursiveInputRequirementsCovered(*candidate); + if (candidate) { + const auto &expected = *candidate->checked.establishes.begin(); + const auto shape = core::BufferShape::decode(expected.family); + verified &= std::ranges::any_of( + inferred.checked.establishes, [&](const auto &post) { + const auto actual = core::BufferShape::decode(post.family); + return post.kind == expected.kind && post.path == expected.path && + post.when.trivial() && !post.on && !post.ifNonNull && actual && + actual->entails(*shape); + }); + for (const auto &[path, effect] : inferred.effects) { + const auto allowed = candidate->effects.find(path); + verified &= !path.isGlobal() && !effect.consumed() && !effect.escaped && + !effect.replaced && + (!effect.written || (allowed != candidate->effects.end() && + allowed->second.written)); + } + for (const auto &[outcome, effects] : inferred.outcomes) { + (void)effects; + verified &= + outcome == core::Outcome::Zero || outcome == core::Outcome::Positive; + } + } + safetyObligation( + core::SafetyProperty::Semantics, + verified ? core::SafetyOutcome::Proven : core::SafetyOutcome::Unresolved, + *function.getBody(), "recursive writer output", + verified ? "recursive writer establishes its initialized buffer prefix" + : "recursive writer does not establish its complete output " + "contract"); +} + +} // namespace weavec::analysis diff --git a/lib/Analysis/DataflowRuntime.cpp b/lib/Analysis/DataflowRuntime.cpp index 699cd3e0..e0279a3c 100644 --- a/lib/Analysis/DataflowRuntime.cpp +++ b/lib/Analysis/DataflowRuntime.cpp @@ -13,6 +13,7 @@ #include #include +#include using namespace clang; namespace weavec::analysis { @@ -223,6 +224,14 @@ bool FunctionDataflow::runtimeIntrinsic(const CallExpr &call, return false; if (runtimeListIntrinsic(call, state)) return true; + llvm::APFloat constant(0.0); + if (call.getType()->isRealFloatingType() && !call.HasSideEffects(context) && + call.EvaluateAsFloat(constant, context)) { + safetyObligation(core::SafetyProperty::Call, core::SafetyOutcome::Proven, + call, callee->getNameAsString(), + "target-evaluated floating constant intrinsic"); + return true; + } const auto name = callee->getName(); const bool expect = name == "__builtin_expect" || name == "__builtin_expect_with_probability"; @@ -274,16 +283,59 @@ bool FunctionDataflow::checkedRuntimeCall(const CallExpr &call, }; if (model->family == RuntimeFamily::Numeric) return true; + if (model->family == RuntimeFamily::ParseNumeric) { + const auto input = runtimeString(argument(0), std::nullopt, call, state); + auto [floating, inserted] = checkedFloatingResults.try_emplace(&call); + if (inserted) + floating->second = places.create("numeric floating result"); + state.safety->nonNan.erase(floating->second); + if (input && checkedNumericText(*input, state)) + state.safety->nonNan.insert(floating->second); + const auto slot = builder.classifyValue(argument(1)); + const bool nullSlot = + slot.kind == ValueOrigin::Kind::Null || + (slot.kind == ValueOrigin::Kind::Copy && slot.place && + slot.offset.isZero() && + state.nulls.stateOf(slot.place->place) == core::Nullness::Null); + if (nullSlot) + return true; + const auto bytes = + context.getTypeSizeInChars(argument(1).getType()->getPointeeType()) + .getQuantity(); + const auto output = runtimeInterval( + argument(1), core::Affine::ofConstant(bytes), false, true, call, state); + if (!input || !output || !runtimeSeparate(*input, *output, call, state)) + return true; + checkedWrites[&call].push_back(*output); + // The end pointer can identify the terminator itself, never a later byte. + // This establishes memory provenance only, including failed conversions; + // the floating result may still be NaN or infinite. + if (const auto end = input->end.shifted(-1); + end && checkedAtMost(input->begin, *end, state)) + checkedPositionPosts[&call].push_back( + {.path = core::SummaryPath::param(1).deref(), + .position = {.storage = input->storage, + .offset = input->begin, + .extent = input->extent, + .input = input->inputPlace}, + .upper = end, + .when = {}, + .on = {}, + .nonNull = true}); + return true; + } if (model->family == RuntimeFamily::Compare || model->family == RuntimeFamily::Search || model->family == RuntimeFamily::Span) { std::optional first; + std::optional second; if (name == "memcmp" || name == "memchr") { const auto bytes = count(2); if (bytes) { first = runtimeInterval(argument(0), *bytes, true, false, call, state); if (name == "memcmp") - (void)runtimeInterval(argument(1), *bytes, true, false, call, state); + second = + runtimeInterval(argument(1), *bytes, true, false, call, state); } else { safetyObligation( core::SafetyProperty::Bounds, core::SafetyOutcome::Unresolved, call, @@ -299,7 +351,68 @@ bool FunctionDataflow::checkedRuntimeCall(const CallExpr &call, } first = runtimeString(argument(0), limit, call, state); if (model->parameters[1] == 's') - (void)runtimeString(argument(1), limit, call, state); + second = runtimeString(argument(1), limit, call, state); + } + if (first && second && context.getCharWidth() == 8 && + model->family == RuntimeFamily::Compare) { + // RFC 0029: contents can establish the sign, never the implementation's + // chosen nonzero magnitude. Each byte still needs independent bounds, + // lifetime and initialization evidence. + const auto byte = + [&](const Expr &pointer, + std::int64_t index) -> std::optional { + if (const auto *literal = + dyn_cast(pointer.IgnoreParenCasts())) { + if (!literal->isOrdinary() || + std::cmp_greater(index, literal->getLength())) + return std::nullopt; + return std::cmp_equal(index, literal->getLength()) + ? 0 + : literal->getCodeUnit(static_cast(index)); + } + const auto memory = + checkedMemory(pointer, core::Affine::ofConstant(index), + core::Affine::ofConstant(index + 1), state); + const auto contents = + memory ? checkedByteContents(*memory, state) : std::nullopt; + if (!contents || contents->second.size() != 1) + return std::nullopt; + return static_cast(contents->second.front()); + }; + std::optional length; + if (name == "memcmp" || name == "strncmp") + if (const auto range = integerRangeOf(argument(2), state); + range && !range->mayBeInvalid) + if (const auto value = range->values.constant()) + length = value->signedValue(); + const bool bounded = name != "strcmp"; + if (!bounded || (length && *length >= 0)) { + std::optional result; + for (std::int64_t index = 0; index <= 64; ++index) { + if (bounded && index == *length) { + result = core::ValueFact::ofConstant(0); + break; + } + if (index == 64) + break; + const auto left = byte(argument(0), index); + const auto right = byte(argument(1), index); + if (!left || !right) + break; + if (*left != *right) { + result = + core::ValueFact::of(*left < *right ? core::Outcome::Negative + : core::Outcome::Positive); + break; + } + if (name != "memcmp" && *left == 0) { + result = core::ValueFact::ofConstant(0); + break; + } + } + if (const auto place = numericCallResult(call); place && result) + state.scalars.set(*place, *result); + } } if (first && model->family == RuntimeFamily::Search) if (const auto end = first->end.shifted(-1); diff --git a/lib/Analysis/DataflowSafety.cpp b/lib/Analysis/DataflowSafety.cpp index 1b8fa75f..e6d228f1 100644 --- a/lib/Analysis/DataflowSafety.cpp +++ b/lib/Analysis/DataflowSafety.cpp @@ -8,6 +8,7 @@ #include "AffineSupport.h" #include "Dataflow.h" +#include "FloatingCastSupport.h" #include "IntegerSupport.h" #include "weavec/Analysis/ProgramDatabase.h" #include "weavec/Core/ObjectType.h" @@ -15,6 +16,8 @@ #include "clang/AST/Attr.h" #include "clang/Basic/SourceManager.h" +#include "llvm/ADT/ScopeExit.h" + using namespace clang; namespace weavec::analysis { @@ -78,6 +81,25 @@ static bool checkedAllocationOrigin(const ValueOrigin &origin, }); } +static bool checkedCharacterPointerView(QualType from, QualType to, + const ASTContext &context) { + if (!from->isPointerType() || !to->isPointerType() || + from.isVolatileQualified() || to.isVolatileQualified() || + !from->getPointeeType()->isAnyCharacterType() || + !to->getPointeeType()->isAnyCharacterType() || + from->getPointeeType().isVolatileQualified() || + to->getPointeeType().isVolatileQualified() || + from->getPointeeType().getAddressSpace() != + to->getPointeeType().getAddressSpace()) + return false; + // RFC 0029: Clang's character-pointer alias class shares the actual target + // representation. A compatible view does not make a const slot writable. + return from->getPointeeType()->isCharType() && + to->getPointeeType()->isCharType() && + context.getTypeSize(from) == context.getTypeSize(to) && + context.getTypeAlign(from) == context.getTypeAlign(to); +} + static bool checkedTypeUnsupported(QualType type, unsigned depth = 0) { if (type.isNull() || depth > core::MaxHeapPathDepth) return true; @@ -149,6 +171,34 @@ void FunctionDataflow::initializeChecked() { checkedUnsupported.insert(function.getBody()); if (getAnnotations(function).invalid) checkedUnsupported.insert(function.getBody()); + const auto nominateCounter = [&](const Stmt *statement) { + const auto *adjustment = dyn_cast_or_null(statement); + const auto *returned = dyn_cast_or_null(statement); + const Expr *operand = returned ? returned->getRetValue() : nullptr; + if (adjustment && adjustment->isIncrementDecrementOp()) + operand = adjustment->getSubExpr(); + if (!operand) + return; + const auto *reference = + dyn_cast(operand->IgnoreParenImpCasts()); + const auto *variable = + reference ? dyn_cast(reference->getDecl()) : nullptr; + if (!variable || !variable->hasLocalStorage() || + !variable->getType()->isIntegerType() || + variable->getType()->isBooleanType() || + variable->getType().isVolatileQualified() || + variable->getType()->isAtomicType() || + addressTaken.contains(variable->getCanonicalDecl())) + return; + const auto place = builder.placeForVar(*variable); + if (checkedLoopCounters.size() < core::MaxTraversalVariables || + checkedLoopCounters.contains(place)) { + checkedLoopCounters.insert(place); + } else { + inferred.checked.limited = true; + inferred.incomplete.insert("traversal variable limit reached"); + } + }; std::vector pendingStmts{function.getBody()}; for (std::size_t i = 0; i < pendingStmts.size(); ++i) { const Stmt *stmt = pendingStmts[i]; @@ -158,12 +208,15 @@ void FunctionDataflow::initializeChecked() { inferred.checked.limited = true; break; } + if (isa(stmt)) + nominateCounter(stmt); if (isa( stmt)) checkedUnsupported.insert(stmt); if (const auto *expr = dyn_cast(stmt)) { if (const auto *cast = dyn_cast(expr); - cast && cast->getCastKind() == CK_FloatingToIntegral) + cast && cast->getCastKind() == CK_FloatingToIntegral && + !finiteFloatingCast(*cast, function, context)) checkedUnsupported.insert(stmt); if (const auto *cast = dyn_cast(expr); cast && cast->getCastKind() == CK_BitCast && @@ -174,6 +227,7 @@ void FunctionDataflow::initializeChecked() { if (!to->isVoidType() && !to->isCharType() && !clang::ASTContext::hasSameUnqualifiedType(from, to) && !from->isVoidType() && !from->isCharType() && + !checkedCharacterPointerView(from, to, context) && !checkedAllocationOrigin(builder.classifyValue(*cast))) checkedUnsupported.insert(stmt); } @@ -197,11 +251,33 @@ void FunctionDataflow::initializeChecked() { getAnnotations(*var).checked)) checkedUnsupported.insert(stmt); if (const auto *loop = dyn_cast(stmt)) { + nominateCounter(loop->getInc()); + checkedLoops[loop] = loop; + if (loop->getCond()) { + std::vector conditions{loop->getCond()}; + for (std::size_t j = 0; + j < conditions.size() && conditions.size() <= 64; ++j) + for (const auto *child : conditions[j]->children()) + if (child) + conditions.push_back(child); + if (conditions.size() <= 64) { + for (const auto *condition : conditions) { + checkedLoops[condition] = loop; + checkedLoopConditions.insert(condition); + } + } else { + checkedLoops[loop->getCond()] = loop; + checkedLoopConditions.insert(loop->getCond()); + } + } + if (loop->getInc()) + checkedLoops[loop->getInc()] = loop; std::vector body{loop->getBody()}; for (std::size_t j = 0; j < body.size() && body.size() < 65536; ++j) { if (!body[j]) continue; checkedLoops[body[j]] = loop; + nominateCounter(body[j]); for (const auto *child : body[j]->children()) body.push_back(child); } @@ -221,12 +297,25 @@ void FunctionDataflow::initializeChecked() { void FunctionDataflow::checkedBefore(const Stmt &stmt, core::AnalysisState &state) { materializeBuffers(state); - if (checkedUnsupported.contains(&stmt)) + checkedReaderLoop(stmt, state); + const auto *floatingCast = dyn_cast(&stmt); + const bool provedFloating = + floatingCast != nullptr && + floatingCast->getCastKind() == CK_FloatingToIntegral && + finiteFloatingCast( + *floatingCast, function, context, + checkedFloatingValue(*floatingCast->getSubExpr(), state)); + if (checkedUnsupported.contains(&stmt) && !provedFloating) safetyObligation(core::SafetyProperty::Semantics, core::SafetyOutcome::Unresolved, stmt, "unsupported", "unsupported checked C construct or storage type"); - if (const auto *cast = dyn_cast(&stmt)) + if (const auto *cast = dyn_cast(&stmt)) { + if (cast->getCastKind() == CK_FloatingToIntegral && provedFloating) + safetyObligation(core::SafetyProperty::Semantics, + core::SafetyOutcome::Proven, stmt, "conversion", + "floating conversion has a finite representable range"); checkedObjectCast(*cast, state); + } if (const auto *declarations = dyn_cast(&stmt)) for (const auto *decl : declarations->decls()) if (const auto *var = dyn_cast(decl); @@ -353,6 +442,26 @@ void FunctionDataflow::checkedBefore(const Stmt &stmt, void FunctionDataflow::checkedAfter(const Stmt &stmt, core::AnalysisState &state) { + if (const auto *conditional = dyn_cast(&stmt); + conditional && conditional->getType()->isIntegerType() && + !conditional->HasSideEffects(context)) { + const auto range = integerRangeOf(*conditional, state); + auto &saved = integerStatementResults[conditional]; + if (!saved) { + saved = places.create("conditional-value@" + + std::to_string(locate(stmt).line)); + snapshotPlaces.insert(*saved); + } + snapshotIntegerDependencies(*saved, conditional, state); + snapshotScalar(*saved, conditional, state); + state.dropGuardsOn(*saved); + state.relations.forget(*saved); + state.numericValues.erase(*saved); + state.scalars.forget(*saved); + if (range && !range->mayBeInvalid && !range->values.empty()) + state.scalars.set(*saved, core::ValueFact::ofInteger(range->values)); + } + checkedFloatingAfter(stmt, state); if (const auto *expr = dyn_cast(&stmt)) checkedAdvancePointer(*expr, state); if (inUnsafe && checkedUnsupported.contains(&stmt)) { @@ -368,6 +477,7 @@ void FunctionDataflow::checkedAfter(const Stmt &stmt, state.safety->buffers.storage.clear(); state.safety->containers.clear(); state.safety->initialized.clear(); + state.safety->nonNan.clear(); state.safety->pointers.clear(); state.safety->memory.clear(); state.safety->positions.clear(); @@ -409,7 +519,11 @@ void FunctionDataflow::checkedAfter(const Stmt &stmt, assignment->getOpcode() == BO_Assign && byteSizeOf(written->getType(), context) == 1 && integerConstant(*assignment->getRHS(), context) == 0; - checkedStringWrite(writtenMemory, zeroed, state); + const bool numericText = assignment != nullptr && + assignment->getOpcode() == BO_Assign && + byteSizeOf(written->getType(), context) == 1 && + checkedNumericByte(*assignment->getRHS(), state); + checkedStringWrite(writtenMemory, zeroed, state, numericText); checkedUnionWrite(*written, writtenMemory, state); if (const auto ref = builder.resolve(*written)) if (ref->element.isWhole()) @@ -418,7 +532,8 @@ void FunctionDataflow::checkedAfter(const Stmt &stmt, state.safety->initialize(memory->storage, {.begin = foldAffine(memory->begin, state), .end = foldAffine(memory->end, state), - .zeroed = zeroed}); + .zeroed = zeroed, + .numericText = numericText}); // Keep a symbolic right endpoint as well as the folded interval. A // subsequent value-preserving advance can carry this must-write fact // around a CFG back edge without pretending that a visit was a store. @@ -426,7 +541,8 @@ void FunctionDataflow::checkedAfter(const Stmt &stmt, {.begin = checkedStableAffine( foldAffine(memory->begin, state), state), .end = checkedStableAffine(memory->end, state), - .zeroed = zeroed}); + .zeroed = zeroed, + .numericText = numericText}); if (const auto *subscript = dyn_cast(written->IgnoreParenImpCasts())) { const Expr *indexExpr = subscript->getIdx()->IgnoreParenImpCasts(); @@ -454,12 +570,14 @@ void FunctionDataflow::checkedAfter(const Stmt &stmt, symbolic->storage, {.begin = foldAffine(symbolic->begin, state), .end = checkedStableAffine(symbolic->end, state), - .zeroed = zeroed}); + .zeroed = zeroed, + .numericText = numericText}); state.safety->initialize( symbolic->storage, {.begin = symbolic->begin, .end = checkedStableAffine(symbolic->end, state), - .zeroed = zeroed}); + .zeroed = zeroed, + .numericText = numericText}); } } } @@ -676,6 +794,34 @@ void FunctionDataflow::installCheckedPointer(core::PlaceId dest, void FunctionDataflow::checkedOutputs(const core::AnalysisState &incoming, const Expr *value) { + if (value && value->getType()->isPointerType()) + if (const auto *conditional = + dyn_cast(value->IgnoreParenImpCasts()); + conditional && !conditional->HasSideEffects(context) && + !isa( + conditional->getTrueExpr()->IgnoreParenImpCasts()) && + !isa( + conditional->getFalseExpr()->IgnoreParenImpCasts())) { + // RFC 0029: pure conditional returns have the same output alternatives + // as explicit returns on the two CFG edges. Refine the computed whole + // condition, not just the final operand of a short-circuit expression. + const bool previousInfeasible = edgeInfeasible; + const auto previousCall = lastCall; + const auto restore = llvm::scope_exit([&] { + edgeInfeasible = previousInfeasible; + lastCall = previousCall; + }); + for (const bool holds : {true, false}) { + auto branch = incoming; + edgeInfeasible = false; + lastCall = previousCall; + applyCondition(*conditional->getCond(), holds, true, branch); + if (!edgeInfeasible) + checkedOutputs(branch, holds ? conditional->getTrueExpr() + : conditional->getFalseExpr()); + } + return; + } std::optional materialized; std::optional returned; if (value && value->getType()->isPointerType()) { @@ -790,7 +936,7 @@ void FunctionDataflow::checkedOutputs(const core::AnalysisState &incoming, }); if (!indirect) if (const auto path = builder.summaryPathOf(storage); - path && (path->isParam() || path->isGlobal())) + path && ((path->isParam() && !path->isRoot()) || path->isGlobal())) paths[storage].insert(*path); } for (const auto &[holder, storage] : checkedInputObjects) @@ -815,8 +961,30 @@ void FunctionDataflow::checkedOutputs(const core::AnalysisState &incoming, nullOutputs.size() < core::MaxSafetyRequirements) nullOutputs.insert(*path); } + // RFC 0029: `return helper(...);` forwards the callee's outcome-specific + // structural and footprint outputs into each of this function's outcomes. + const auto *forwarded = value && value->getType()->isIntegerType() && + options.checkContracts && state.safety && + returnedIdentity + ? dyn_cast(value->IgnoreParenCasts()) + : nullptr; + if (forwarded && (!footprintPosts.contains(forwarded) || + numericCallResult(*forwarded) != returnedIdentity)) + forwarded = nullptr; for (const auto outcome : classes) { core::CheckedContract outputs; + std::optional selectedOutcome; + if (forwarded && outcome) { + selectedOutcome = state; + const auto prior = scalarFactOf(*forwarded, *selectedOutcome); + const auto fact = core::ValueFact::of(*outcome); + (void)selectedOutcome->learn(*returnedIdentity, fact); + (void)selectedOutcome->scalars.narrow(*returnedIdentity, fact); + applyContainerPosts(*forwarded, *selectedOutcome, std::nullopt, &prior); + applyFootprintPosts(*forwarded, *selectedOutcome, std::nullopt, &prior); + } + const core::AnalysisState &outcomeState = + selectedOutcome ? *selectedOutcome : state; auto consumed = state.safety->consumedAllocations; // RFC 0028: a null entry pointer has no allocation left to release. // Reassignment cannot make this true of an earlier non-null input. @@ -836,9 +1004,10 @@ void FunctionDataflow::checkedOutputs(const core::AnalysisState &incoming, .other = {}, .family = "free", .on = outcome}); + checkedSpanOutputs(outputs, state, value, outcome); checkedUnionOutputs(outputs, value, outcome, state); - containerOutputs(outputs, state, returned, outcome); - footprintOutputs(outputs, state, returned, outcome); + containerOutputs(outputs, outcomeState, returned, outcome); + footprintOutputs(outputs, outcomeState, returned, outcome); if (outcome && returnedIdentity && !state.safety->buffers.pending.empty()) { auto selected = state; (void)selected.learn(*returnedIdentity, core::ValueFact::of(*outcome)); @@ -869,13 +1038,42 @@ void FunctionDataflow::checkedOutputs(const core::AnalysisState &incoming, .end = last, .family = {}, .on = outcome}); + const CheckedMemory prefix{.storage = position.storage, + .begin = {}, + .end = position.offset, + .extent = position.extent, + .input = origin}; + if (!destination->isResult() && + checkedAtMost({}, position.offset, state) && + checkedInitialized(prefix, state)) + outputs.establish( + {.kind = core::CheckedRequirementKind::InitializedAdvance, + .path = *destination, + .other = *origin, + .family = {}, + .on = outcome}); }; if (const auto offset = summaryAffineOf(position.offset)) establish(*offset, *offset); if (checkedAtMost({}, position.offset, state)) if (const auto bound = - checkedRequirementEnvelope(position.offset, state)) - establish(core::PathAffine::ofConstant(0), *bound); + checkedRequirementEnvelope(position.offset, state)) { + std::int64_t first = 0; + if (position.offset.place && position.offset.scale > 0) + if (const auto lower = + integerBounds(*position.offset.place, state).first) { + std::int64_t scaled = 0; + if (!__builtin_mul_overflow(*lower, position.offset.scale, + &scaled) && + !__builtin_add_overflow(scaled, position.offset.constant, + &scaled) && + scaled > 0 && + checkedAtMost(core::Affine::ofConstant(scaled), + position.offset, state)) + first = scaled; + } + establish(core::PathAffine::ofConstant(first), *bound); + } // Common entry bounds survive alternative return sites, including a // zero-length early return. Each bound is proved on this return edge. if (checkedAtMost({}, position.offset, state)) @@ -1079,7 +1277,10 @@ void FunctionDataflow::checkedOutputs(const core::AnalysisState &incoming, (post.ifNonNull && nullOutputs.contains(post.path))) joined.insert(post); for (const auto &first : existing->second) - if (first.kind == core::CheckedRequirementKind::ContainerDerived) + if (first.kind == core::CheckedRequirementKind::ContainerDerived || + first.kind == core::CheckedRequirementKind::ContainerExtended || + first.kind == core::CheckedRequirementKind::Container || + first.kind == core::CheckedRequirementKind::ContainerFresh) for (const auto &second : outputs.establishes) if (const auto generalized = core::joinContainerOutput(first, second)) @@ -1111,17 +1312,115 @@ void FunctionDataflow::checkedOutputs(const core::AnalysisState &incoming, for (const auto &[outcome, outputs] : checkedOutputClasses) { (void)outcome; for (auto post : outputs) { + if (post.kind == core::CheckedRequirementKind::ContainerExtended || + post.kind == core::CheckedRequirementKind::ContainerDerived || + post.kind == core::CheckedRequirementKind::Container || + post.kind == core::CheckedRequirementKind::ContainerFresh) { + std::optional common = post; + for (const auto &[otherOutcome, otherOutputs] : checkedOutputClasses) { + (void)otherOutcome; + std::optional joined; + for (auto other : otherOutputs) { + other.on = post.on; + if (const auto candidate = + core::joinContainerOutput(*common, other)) { + joined = candidate; + break; + } + } + common = joined; + if (!common) + break; + } + if (common) { + common->on.reset(); + inferred.checked.establish(*common); + } + if (post.kind != core::CheckedRequirementKind::ContainerExtended) { + auto unconditional = post; + unconditional.on.reset(); + if (!common || unconditional != *common) + inferred.checked.establish(std::move(post)); + } + continue; + } const bool everyOutcome = std::ranges::all_of(checkedOutputClasses, [&](const auto &entry) { auto selected = post; selected.on = entry.first; return entry.second.contains(selected); }); + if (post.kind == core::CheckedRequirementKind::CountWithinSpan && + !everyOutcome) + continue; if (everyOutcome) post.on.reset(); inferred.checked.establish(std::move(post)); } } + // Every outcome contributes independently proved bounds. A common enclosing + // interval preserves the actual cursor identity even when early returns + // advance by zero and successful returns advance by a variable count. + if (checkedOutputClasses.size() > 1) + for (const auto &candidate : checkedOutputClasses.begin()->second) { + if (candidate.kind != core::CheckedRequirementKind::Position || + !candidate.when.trivial() || !candidate.begin.isConstant() || + !candidate.end.isConstant()) + continue; + auto first = candidate.begin.constant; + auto last = candidate.end.constant; + bool covered = true; + for (const auto &[outcome, outputs] : checkedOutputClasses) { + (void)outcome; + std::optional> interval; + for (const auto &post : outputs) { + if (post.kind != core::CheckedRequirementKind::Position || + post.path != candidate.path || post.other != candidate.other || + !post.when.trivial() || !post.begin.isConstant() || + !post.end.isConstant()) + continue; + if (!interval) { + interval = {post.begin.constant, post.end.constant}; + } else { + interval->first = std::max(interval->first, post.begin.constant); + interval->second = std::min(interval->second, post.end.constant); + } + } + if (!interval || interval->first > interval->second) { + covered = false; + break; + } + first = std::min(first, interval->first); + last = std::max(last, interval->second); + } + if (covered) { + auto joined = candidate; + joined.begin = core::PathAffine::ofConstant(first); + joined.end = core::PathAffine::ofConstant(last); + joined.on.reset(); + inferred.checked.establish(std::move(joined)); + } + } + if (std::ranges::any_of(inferred.checked.establishes, [](const auto &post) { + return post.kind == core::CheckedRequirementKind::InitializedAdvance; + })) { + core::CheckedRequirements::Set supported( + inferred.checked.establishes.begin(), + inferred.checked.establishes.end()); + std::erase_if(supported, [&](const auto &post) { + return post.kind == core::CheckedRequirementKind::InitializedAdvance && + std::ranges::none_of( + inferred.checked.establishes, [&](const auto &position) { + return position.kind == + core::CheckedRequirementKind::Position && + position.path == post.path && + position.other == post.other && + position.when.trivial() && + (!position.on || position.on == post.on); + }); + }); + inferred.checked.establishes.assign(std::move(supported)); + } } void FunctionDataflow::checkedFinish(const core::AnalysisState *exitState) { @@ -1129,7 +1428,27 @@ void FunctionDataflow::checkedFinish(const core::AnalysisState *exitState) { runtimeListReturns(*function.getBody(), *exitState); checkedOutputs(*exitState); } - verifyRecursiveCleanup(); + verifyRecursiveContract(); + verifyFootprintTransfers(); + if (const auto failed = + summaries.failedRecursiveOutputs.find(function.getCanonicalDecl()); + failed != summaries.failedRecursiveOutputs.end() && + !summaries.activeRecursiveContracts.members.contains( + function.getCanonicalDecl())) + safetyObligation( + core::SafetyProperty::Semantics, core::SafetyOutcome::Unresolved, + *function.getBody(), + failed->second ? "recursive writer output" + : "recursive construction output", + failed->second + ? "recursive writer does not establish its complete output contract" + : "recursive construction does not establish its complete output " + "contract"); + if (summaries.failedRecursiveProgress.contains(function.getCanonicalDecl())) + safetyObligation(core::SafetyProperty::Semantics, + core::SafetyOutcome::Unresolved, *function.getBody(), + "recursive progress", + "recursive proof cycle has no strict progress"); for (const Stmt *stmt : checkedUnsupported) { // RFC 0025: evaluated exclusions belong to their reachable proof case. // Unmapped exclusions remain unconditional; absence is not reachability. @@ -1170,7 +1489,26 @@ void FunctionDataflow::checkedFinish(const core::AnalysisState *exitState) { if (function.getParamDecl(i)->getType()->isPointerType() && !function.getParamDecl(i)->getType()->isFunctionPointerType() && function.getParamDecl(j)->getType()->isPointerType() && - !function.getParamDecl(j)->getType()->isFunctionPointerType()) + !function.getParamDecl(j)->getType()->isFunctionPointerType()) { + const auto first = core::SummaryPath::param(i); + const auto second = core::SummaryPath::param(j); + const bool readOnly = + std::ranges::none_of(inferred.effects, [&](const auto &entry) { + const auto &[path, effect] = entry; + return path.isParam() && (path.index == i || path.index == j) && + (effect.written || effect.consumed() || effect.escaped); + }); + const bool span = + readOnly && + std::ranges::any_of( + inferred.checked.requirements, [&](const auto &pre) { + return pre.kind == + core::CheckedRequirementKind::InitializedSpan && + ((pre.path == first && pre.other == second) || + (pre.path == second && pre.other == first)); + }); + if (span) + continue; inferred.checked.require( {.kind = core::CheckedRequirementKind::Separated, .path = core::SummaryPath::param(i), @@ -1178,6 +1516,7 @@ void FunctionDataflow::checkedFinish(const core::AnalysisState *exitState) { .begin = {}, .end = {}, .family = {}}); + } } inferred.checked.discardUnrepresentedContainerOutputs(); const auto annotations = getAnnotations(function); diff --git a/lib/Analysis/DataflowSafetyCalls.cpp b/lib/Analysis/DataflowSafetyCalls.cpp index 05817b20..43ce5ac4 100644 --- a/lib/Analysis/DataflowSafetyCalls.cpp +++ b/lib/Analysis/DataflowSafetyCalls.cpp @@ -30,12 +30,16 @@ void FunctionDataflow::checkedCall(const CallExpr &call, containerReadOnlyCalls.erase(&call); containerReleases.erase(&call); containerPayloadReleases.erase(&call); + containerLocalReleases.erase(&call); footprintPosts.erase(&call); + freshFootprintSlots.erase(&call); + freshFootprintSlotParents.erase(&call); checkedCallAssignedPointers.clear(); checkedWrites.erase(&call); checkedPosts.erase(&call); checkedPositionPosts.erase(&call); checkedProgressPosts.erase(&call); + checkedSpanPosts.erase(&call); bufferAllocationSequences.erase(&call); const auto *callee = call.getDirectCallee(); std::string name = callee ? callee->getNameAsString() : "indirect call"; @@ -115,6 +119,12 @@ void FunctionDataflow::checkedCall(const CallExpr &call, if ((freshObject(left.storage) && (right.input || right.inputPlace)) || (freshObject(right.storage) && (left.input || left.inputPlace))) return true; + // RFC 0029: an allocation identity acquired by this invocation remains + // separate from its automatic objects after attachment retires the + // allocation's resource record. Validity stays an independent obligation. + if ((localA && freshObject(right.storage)) || + (localB && freshObject(left.storage))) + return true; return liveAllocation(a) && liveAllocation(b) && a->location != b->location; }; const auto valid = [&](const CheckedMemory &memory) { @@ -205,7 +215,10 @@ void FunctionDataflow::checkedCall(const CallExpr &call, "checked reallocation requires a positive size"); const auto origin = builder.classifyValue(*call.getArg(0)); const auto memory = checkedMemory(*call.getArg(0), {}, {}, state); - bool releasable = origin.kind == ValueOrigin::Kind::Null; + bool releasable = + origin.kind == ValueOrigin::Kind::Null || + (origin.place && + state.nulls.stateOf(origin.place->place) == core::Nullness::Null); if (memory) { const auto holder = memory->holder.value_or(memory->storage); const auto resource = state.resources.recordOf(holder); @@ -221,6 +234,8 @@ void FunctionDataflow::checkedCall(const CallExpr &call, *memory, call, state, "free"); obligation(core::SafetyProperty::Release, releasable, required, "reallocation requires a live allocation base"); + if (positive && (releasable || required)) + prepareReallocationFootprint(call, state); if (positive && releasable) if (const auto ref = builder.resolve(*call.getArg(0))) { const auto *buffer = bufferFact(ref->place, state); @@ -258,6 +273,17 @@ void FunctionDataflow::checkedCall(const CallExpr &call, const unsigned source = copy ? 1U : 0U; if (call.getNumArgs() <= source) return; + if (name == "strlen") { + core::CheckedContract input; + input.require({.kind = core::CheckedRequirementKind::Terminated, + .path = core::SummaryPath::param(0), + .other = {}, + .begin = {}, + .end = {}, + .family = {}}); + prepareCheckedStringInputs(call, input, state); + checkedStringLength(call, state); + } const auto length = stringLengthOf(*call.getArg(source), state); const auto throughNul = length ? length->shifted(1) : std::nullopt; std::optional count; @@ -410,25 +436,33 @@ void FunctionDataflow::checkedCall(const CallExpr &call, if (name != "memset") source = interval(1, *bytes, true, false); interval(0, *bytes, false, source); + if (source && name != "memset") { + const auto input = checkedMemory(*call.getArg(1), {}, *bytes, state); + if (input && checkedNumericText(*input, state)) { + // Capture both the byte contents and every endpoint before the copy's + // effects, including an aliased length cell. This local primitive + // contract uses the normal snapshot and postcondition machinery. + core::CheckedContract copy; + copy.computed = true; + copy.establish( + {.kind = core::CheckedRequirementKind::Copied, + .path = core::SummaryPath::param(0), + .other = core::SummaryPath::param(1), + .end = core::PathAffine::ofPath(core::SummaryPath::param(2)), + .family = {}}); + captureCheckedPosts(call, copy, state); + } + } if (name == "memcpy") { const auto left = checkedMemory(*call.getArg(0), {}, *bytes, state); const auto right = checkedMemory(*call.getArg(1), {}, *bytes, state); const bool separated = left && right && separate(*left, *right); - const auto identity = [&](const std::optional &memory) { - if (!memory) - return std::optional{}; - if (memory->holder) - if (const auto *buffer = bufferFact(*memory->holder, state); - buffer && buffer->entryBacking) - return builder.summaryPathOf(*buffer->entryBacking); - if (memory->input) - return memory->input; - return memory->inputPlace ? stableSummaryPathOf(*memory->inputPlace) - : std::nullopt; - }; - const auto leftInput = identity(left); - const auto rightInput = identity(right); - const bool required = !separated && leftInput && rightInput; + const auto leftInput = + left ? checkedSeparationInput(*left, state) : std::nullopt; + const auto rightInput = + right ? checkedSeparationInput(*right, state) : std::nullopt; + const bool required = + !separated && leftInput && rightInput && leftInput != rightInput; if (required && recording()) inferred.checked.require( {.kind = core::CheckedRequirementKind::Separated, @@ -633,6 +667,11 @@ void FunctionDataflow::checkedCall(const CallExpr &call, }); for (const auto *entry : requirements) { const auto &requirement = *entry; + if (requirement.kind == core::CheckedRequirementKind::CallbackAllocate || + requirement.kind == core::CheckedRequirementKind::CallbackRelease) { + checkedCallbackRequirement(requirement, call, state); + continue; + } if (requirement.kind == core::CheckedRequirementKind::Buffer) { checkedBufferCall(requirement, call, state); continue; @@ -661,6 +700,12 @@ void FunctionDataflow::checkedCall(const CallExpr &call, continue; } } + if (requirement.kind == core::CheckedRequirementKind::InitializedSpan) { + obligation(core::SafetyProperty::Bounds, + checkedSpanCall(requirement, call, state), false, + "callee requires a live initialized same-array byte span"); + continue; + } if (requirement.kind == core::CheckedRequirementKind::SumFits) { const auto first = builder.affineFromPath(requirement.begin, call); const auto last = builder.affineFromPath(requirement.end, call); @@ -706,6 +751,12 @@ void FunctionDataflow::checkedCall(const CallExpr &call, "free permits a null pointer"); continue; } + if (requirement.kind == core::CheckedRequirementKind::Valid && pointer && + builder.classifyValue(*pointer).kind == ValueOrigin::Kind::Null) { + obligation(core::SafetyProperty::Validity, false, false, + "call requires live non-null storage"); + continue; + } const auto first = builder.affineFromPath(requirement.begin, call); const auto last = builder.affineFromPath(requirement.end, call); const auto memory = @@ -738,6 +789,14 @@ void FunctionDataflow::checkedCall(const CallExpr &call, } else if (kind == core::CheckedRequirementKind::Initialized) { property = core::SafetyProperty::Initialization; proved = checkedInitialized(*memory, state); + } else if (kind == core::CheckedRequirementKind::TerminatedWithin) { + property = core::SafetyProperty::Initialization; + auto bounded = *memory; + bounded.extent = memory->end; + proved = memory->extent && + checkedInterval(memory->begin, memory->end, *memory->extent, + state) && + checkedTerminated(bounded, state); } else if (kind == core::CheckedRequirementKind::Terminated) { property = core::SafetyProperty::Initialization; const auto base = @@ -805,11 +864,13 @@ void FunctionDataflow::checkedCall(const CallExpr &call, checkedPathMemory(requirement.other, call, {}, {}, state); if (other && other->storage != memory->storage) { proved = separate(*memory, *other); - if (!proved && memory->input && other->input) { + const auto leftInput = checkedSeparationInput(*memory, state); + const auto rightInput = checkedSeparationInput(*other, state); + if (!proved && leftInput && rightInput && leftInput != rightInput) { if (recording()) { auto exported = requirement; - exported.path = *memory->input; - exported.other = *other->input; + exported.path = *leftInput; + exported.other = *rightInput; inferred.checked.require(std::move(exported)); } obligation(property, false, true, @@ -899,11 +960,87 @@ void FunctionDataflow::checkedCallAfter(const CallExpr &call, std::ranges::any_of(effects->summary->effects, [](const auto &entry) { return entry.second.written; }))) { + // RFC 0029: a complete helper may change addressed automatic cells while + // leaving separate byte objects intact. Resolve every actual destination; + // unknown writes and escaping or consuming effects supply no frame. + bool localWrites = effects != nullptr && effects->summary && + effects->summary->checked.complete(); + std::set writtenRoots; + if (localWrites) { + for (const auto &[path, effect] : effects->summary->effects) { + if (effect.consumed() || effect.escaped || effect.replaced) + localWrites = false; + if (!effect.written) + continue; + const auto actual = builder.resolveSummaryPath(path, call); + if (!actual || !isLocalStorage(actual->place)) { + localWrites = false; + break; + } + writtenRoots.insert(places.root(actual->place)); + } + if (writes != checkedWrites.end()) + for (const auto &memory : writes->second) { + localWrites &= isLocalStorage(memory.storage); + writtenRoots.insert(places.root(memory.storage)); + } + } + std::vector> retained; + bool representedWrites = effects != nullptr && effects->summary && + effects->summary->checked.complete(); + std::set writtenObjects; + if (representedWrites) { + for (const auto &[path, effect] : effects->summary->effects) { + if (!effect.written) + continue; + const auto actual = builder.resolveSummaryPath(path, call); + if (!actual || !actual->element.isWhole()) { + representedWrites = false; + break; + } + auto storage = places.root(actual->place); + if (const auto object = places.innermostDeref(actual->place)) { + const auto holder = places.parent(*object); + const auto memory = + holder ? checkedMemoryAt(*holder, {}, {}, state) : std::nullopt; + if (!memory) { + representedWrites = false; + break; + } + storage = memory->storage; + } + writtenObjects.insert(storage); + } + if (writes != checkedWrites.end()) + for (const auto &memory : writes->second) + writtenObjects.insert(memory.storage); + } + if (representedWrites || (localWrites && !writtenRoots.empty())) + for (const auto &[storage, ranges] : state.safety->memory) { + bool separate = localWrites && isLocalStorage(storage) && + !writtenRoots.contains(places.root(storage)); + if (localWrites && places.step(storage) == core::PathStep::Deref) + if (const auto holder = places.parent(storage)) + if (const auto memory = checkedMemoryAt(*holder, {}, {}, state); + memory && memory->storage == storage && memory->inputPlace && + storage == places.deref(*memory->inputPlace) && + !state.safety->replacedPointers.contains(*memory->inputPlace) && + checkedSeparationInput(*memory, state) && + checkedValid(*memory, state)) + separate = true; + for (const auto &range : ranges) + if (!range.bytes.empty() && !range.source && + (separate || (representedWrites && range.immutableBytes && + !writtenObjects.contains(storage)))) + retained.emplace_back(storage, range); + } for (auto &[data, fact] : state.safety->buffers.values) { (void)data; fact.shape.terminated = false; } state.forgetZeroedMemory(); + for (auto &[storage, range] : retained) + state.safety->initialize(storage, std::move(range)); } if (writes != checkedWrites.end()) { const auto *callee = call.getDirectCallee(); diff --git a/lib/Analysis/DataflowSafetyContracts.cpp b/lib/Analysis/DataflowSafetyContracts.cpp index 1513fd74..ae205e74 100644 --- a/lib/Analysis/DataflowSafetyContracts.cpp +++ b/lib/Analysis/DataflowSafetyContracts.cpp @@ -75,6 +75,17 @@ std::vector FunctionDataflow::checkedCopyRanges( range.end = foldAffine(range.end, state); const auto first = foldAffine(begin, state); const auto last = foldAffine(end, state); + if ((range.numericText || range.zeroed) && + checkedAtMost(range.begin, source.begin, state) && + checkedAtMost(source.end, range.end, state)) { + // Source ranges use storage coordinates; copied output ranges use + // argument-relative coordinates. Full coverage needs no subtraction + // of two independently represented symbolic origins. + range.begin = first; + range.end = last; + result.push_back(std::move(range)); + continue; + } if (range.begin.isConstant() && range.end.isConstant() && first.isConstant() && last.isConstant()) { range.begin.constant = std::max(range.begin.constant, first.constant); @@ -104,6 +115,8 @@ void FunctionDataflow::captureCheckedPosts( positions.clear(); auto &progress = checkedProgressPosts[&call]; progress.clear(); + auto &spanCounts = checkedSpanPosts[&call]; + spanCounts.clear(); if (!contract.complete()) return; for (const auto &post : contract.establishes) { @@ -132,6 +145,23 @@ void FunctionDataflow::captureCheckedPosts( state.safety->containers.set(holder, *owned); releaseFootprint(holder, false, state); } + } else if (footprintHeads.contains(holder)) { + // RFC 0029: the helper consumes this ordinary allocation exactly as a + // modeled nullable release does; null contributes the empty footprint. + const auto resource = state.resources.recordOf(holder); + const auto memory = checkedMemoryAt(holder, {}, {}, state); + auto when = resource ? resource->guard : core::PlaceGuard{}; + auto nonNull = state; + nonNull.nulls.set(holder, {.state = core::Nullness::NonNull, + .location = {}, + .reason = core::NullReason::Declared}); + if (resource && resource->origin == core::ResourceOrigin::Allocated && + resource->family == "free" && !resource->escaped && + !state.moves.recordOf(holder) && memory && + memory->begin == core::Affine::ofConstant(0) && + checkedValid(*memory, nonNull) && pruneGuard(when, nonNull) && + when.trivial()) + releaseFootprint(holder, false, state); } } auto &snapshots = checkedSnapshots[&call]; @@ -260,6 +290,7 @@ void FunctionDataflow::captureCheckedPosts( range.begin = freeze(range.begin); range.end = freeze(range.end); range.zeroed &= preservesBytes; + range.numericText &= preservesBytes; posts.push_back({.path = core::SummaryPath::result(), .range = range, .on = {}, @@ -317,6 +348,64 @@ void FunctionDataflow::captureCheckedPosts( .objectType = post.family}); continue; } + if (post.kind == core::CheckedRequirementKind::InitializedAdvance) { + const auto origin = checkedPathMemory(post.other, call, {}, {}, state); + if (origin && !post.path.isResult() && post.when.trivial()) + posts.push_back({.path = post.path, + .range = {.begin = freeze(origin->begin)}, + .on = post.on, + .storage = origin->storage, + .objectType = {}, + .throughPosition = true}); + continue; + } + if (post.kind == core::CheckedRequirementKind::CountWithinSpan) { + const auto result = numericCallResult(call); + const auto type = integerTypeOf(call.getType(), context); + const auto a = checkedPathMemory(post.path, call, {}, {}, state); + const auto b = checkedPathMemory(post.other, call, {}, {}, state); + if (!result || !type || !a || !b || a->storage != b->storage || + !checkedAtMost({}, a->begin, state) || + !checkedAtMost(a->begin, b->begin, state)) + continue; + const auto capture = [&](const core::Affine &coordinate) { + auto expression = checkedByteExpression(coordinate, state); + if (coordinate.place && coordinate.scale == 1 && + coordinate.constant == 0) + if (const auto *decl = dyn_cast_or_null( + builder.declFor(*coordinate.place)); + decl && !decl->getType().isVolatileQualified() && + !decl->getType()->isAtomicType()) + if (const auto cellType = integerTypeOf(*decl, context); + cellType && cellType->width <= 64) + if (const auto minimum = + integerRangeAt(*coordinate.place, *cellType, state) + .minimum(); + minimum && !minimum->negative()) + expression = + NumericExpression::input(*coordinate.place, *cellType) + .converted({.width = 64, .isSigned = false}); + return expression + ? expression->substitute( + [&](core::PlaceId key, core::IntegerType keyType) { + return std::optional(NumericExpression::input( + snapshot(key), keyType)); + }) + : std::nullopt; + }; + const auto first = capture(a->begin); + const auto last = capture(b->begin); + const core::IntegerType bytes{.width = 64, .isSigned = false}; + const auto count = + NumericExpression::input(*result, *type).converted(bytes); + const auto length = first && last + ? NumericExpression::operation( + core::IntegerOp::Subtract, *last, *first) + : std::nullopt; + if (count && length) + spanCounts.emplace_back(*count, *length); + continue; + } if (post.kind == core::CheckedRequirementKind::Progress) { const auto guard = checkedGuard(post.when, call, state); const auto a = checkedPathMemory(post.path, call, {}, {}, state); @@ -393,10 +482,21 @@ void FunctionDataflow::captureCheckedPosts( checkedPathMemory(post.other, call, *first, *last, state); if (source) ranges = checkedCopyRanges(*source, *first, *last, state); - // The relational contract preserves initialization, not byte values. - // A callee may have overwritten initialized input with nonzero data. - for (auto &range : ranges) - range.zeroed = false; + // Only a recognized byte-copy primitive also preserves contents. + // A general initialized-output contract may have overwritten the input. + auto name = resolvedLibraryName(call); + if (const auto *callee = call.getDirectCallee(); + callee && callee->getBuiltinID()) { + if (name.starts_with("__builtin___") && name.ends_with("_chk")) + name = name.substr(12, name.size() - 16); + else if (name.starts_with("__builtin_")) + name = name.substr(10); + } + const bool byteCopy = name == "memcpy" || name == "memmove"; + for (auto &range : ranges) { + range.zeroed &= byteCopy; + range.numericText &= byteCopy; + } } else if (post.kind == core::CheckedRequirementKind::Terminated) { const auto through = last->shifted(1); if (!through) @@ -499,12 +599,22 @@ void FunctionDataflow::applyCheckedPositions( existing && existing->storage == post.position.storage && existing->extent && checkedInterval(existing->begin, existing->end, *existing->extent, - state) && - (!post.position.extent || - !checkedInterval(post.position.offset, - post.upper.value_or(post.position.offset), - *post.position.extent, state))) - continue; + state)) { + // Position outputs are simultaneous guarantees. A wider envelope + // must not replace an already established exact (or narrower) value. + const auto upper = post.upper.value_or(post.position.offset); + const bool sameExtent = + post.position.extent && + checkedAtMost(*existing->extent, *post.position.extent, state) && + checkedAtMost(*post.position.extent, *existing->extent, state); + if (!post.position.extent || + !checkedInterval(post.position.offset, upper, *post.position.extent, + state) || + (sameExtent && + checkedAtMost(post.position.offset, existing->begin, state) && + checkedAtMost(existing->end, upper, state))) + continue; + } auto position = post.position; if (post.upper && foldAffine(*post.upper, state) != foldAffine(position.offset, state)) { @@ -558,6 +668,13 @@ void FunctionDataflow::applyCheckedPositions( spatial.offset = core::PointerOffset::ofElements(offset.constant / *unit); spatial.boundsOffset = spatial.offset; state.spatial.set(*dest, spatial); + } else { + auto spatial = + state.spatial.recordOf(*dest).value_or(core::SpatialRecord{}); + spatial.extent = post.position.extent; + spatial.offset = core::PointerOffset::unknown(); + spatial.boundsOffset = spatial.offset; + state.spatial.set(*dest, spatial); } if (post.nonNull && (!post.on || !result)) { state.safety->pointers.insert(*dest); @@ -566,6 +683,49 @@ void FunctionDataflow::applyCheckedPositions( .reason = core::NullReason::Declared}); } } + if (!result) + for (const auto &post : found->second) { + if (!post.on || !post.when.trivial() || + !post.position.offset.isConstant() || !post.upper || + !post.upper->isConstant() || post.position.offset.constant < 0 || + post.position.offset.constant > post.upper->constant) + continue; + const auto dest = builder.resolveSummaryPath(post.path, call); + if (!dest || !installed.contains(dest->place)) + continue; + const auto actual = state.safety->positions.find(dest->place); + const auto coordinate = checkedCoordinates.find(dest->place); + if (actual == state.safety->positions.end() || + coordinate == checkedCoordinates.end() || + actual->second.storage != post.position.storage || + actual->second.offset != core::Affine::ofPlace(coordinate->second)) + continue; + if (!lastCall || lastCall->call != &call) { + core::PendingOutcome outcome; + outcome.callee = calleeName(call); + outcome.location = locate(call); + if (call.getType()->isPointerType()) { + outcome.consumedBy.try_emplace(core::Outcome::Null); + outcome.consumedBy.try_emplace(core::Outcome::NonNull); + } else if (call.getType()->isIntegerType()) { + outcome.consumedBy.try_emplace(core::Outcome::Zero); + outcome.consumedBy.try_emplace(core::Outcome::Positive); + if (call.getType()->isSignedIntegerType()) + outcome.consumedBy.try_emplace(core::Outcome::Negative); + } else { + continue; + } + lastCall = CallOutcome{.call = &call, .pending = std::move(outcome)}; + } + const core::IntegerType bytes{.width = 64, .isSigned = false}; + const auto range = core::IntegerRange::between( + core::IntegerValue::ofBits( + bytes, static_cast(post.position.offset.constant)), + core::IntegerValue::ofBits( + bytes, static_cast(post.upper->constant))); + lastCall->pending.factOn[*post.on].emplace_back( + coordinate->second, core::ValueFact::ofInteger(range)); + } if (!result) if (const auto progress = checkedProgressPosts.find(&call); progress != checkedProgressPosts.end()) @@ -646,6 +806,26 @@ void FunctionDataflow::applyCheckedResult(core::PlaceId dest, void FunctionDataflow::applyCheckedPosts(const CallExpr &call, const core::FunctionSummary &summary, core::AnalysisState &state) { + if (const auto counts = checkedSpanPosts.find(&call); + counts != checkedSpanPosts.end()) + for (const auto &[count, length] : counts->second) { + if (const auto result = numericCallResult(call)) { + state.relations.learnAtLeast(*result, 0); + if (const auto bound = linearIntegerExpression(length, state)) { + if (bound->isConstant()) + state.relations.learnAtMost(*result, bound->constant); + else if (bound->place && bound->scale == 1) + state.relations.learn(*result, core::Relation::LessEqual, + *bound->place, bound->constant); + } + } + if (!state.numericConditions.requireInteger( + {.lhs = count, + .op = core::IntegerOp::LessEqual, + .rhs = length}) && + state.numericConditions.size() >= core::MaxGuardConjuncts) + state.numericConditionsIncomplete = true; + } applyCheckedPositions(call, state); applyContainerPosts(call, state); applyFootprintPosts(call, state, std::nullopt); @@ -692,6 +872,15 @@ void FunctionDataflow::applyCheckedPosts(const CallExpr &call, } auto storage = post.storage; auto range = post.range; + if (post.throughPosition) { + const auto output = checkedPathMemory(post.path, call, {}, {}, state); + if (!storage || !output || output->storage != *storage || + !checkedAtMost(range.begin, output->begin, state) || + !checkedValid(*output, state) || !output->extent || + !checkedInterval(range.begin, output->begin, *output->extent, state)) + continue; + range.end = output->begin; + } if (!storage) { const auto memory = checkedPathMemory(post.path, call, range.begin, range.end, state); diff --git a/lib/Analysis/DataflowSafetyLoops.cpp b/lib/Analysis/DataflowSafetyLoops.cpp index e21f039f..4cbe5b0c 100644 --- a/lib/Analysis/DataflowSafetyLoops.cpp +++ b/lib/Analysis/DataflowSafetyLoops.cpp @@ -11,6 +11,8 @@ #include "Dataflow.h" #include "IntegerSupport.h" +#include "clang/AST/ParentMapContext.h" + using namespace clang; namespace weavec::analysis { @@ -61,6 +63,217 @@ static const VarDecl *loopRequirementIndex(const ForStmt &loop, return loopRequirementVariable(assignment->getLHS()); } +void FunctionDataflow::checkedReaderLoop(const Stmt &at, + core::AnalysisState &state) { + if (bufferObjects.empty()) + return; + const auto found = checkedLoops.find(&at); + if (found == checkedLoops.end()) + return; + const auto &loop = *found->second; + auto [saved, inserted] = checkedReaderLoops.try_emplace(&loop); + if (inserted) { + const Expr *initial = nullptr; + const auto *index = loopRequirementIndex(loop, initial); + const auto type = index ? integerTypeOf(*index, context) : std::nullopt; + const auto *increment = + loop.getInc() + ? dyn_cast(loop.getInc()->IgnoreParenImpCasts()) + : nullptr; + if (!index || !type || type->isSigned || type->isBoolean || + !index->hasLocalStorage() || addressTaken.contains(index) || + index->getType().isVolatileQualified() || + index->getType()->isAtomicType() || !initial || + integerConstant(*initial, context) != 0 || + initial->HasSideEffects(context) || !increment || + !increment->isIncrementOp() || + loopRequirementVariable(increment->getSubExpr()) != index || + !loop.getCond() || loop.getCond()->HasSideEffects(context)) + return; + // A surrounding switch could jump directly into a case in the loop. + const Stmt *ancestor = &loop; + bool reachedBody = false; + for (unsigned depth = 0; depth < 128; ++depth) { + if (ancestor == function.getBody()) { + reachedBody = true; + break; + } + const auto parents = context.getParents(*ancestor); + if (parents.size() != 1) + return; + ancestor = parents[0].get(); + if (!ancestor || isa(ancestor)) + return; + } + if (!reachedBody) + return; + std::vector body; + if (!collectLoopRequirementStatements(loop.getBody(), body)) + return; + for (const auto *statement : body) { + if (isa(statement)) + return; + const Expr *written = nullptr; + if (const auto *assignment = dyn_cast(statement); + assignment && assignment->isAssignmentOp()) + written = assignment->getLHS(); + if (const auto *unary = dyn_cast(statement); + unary && unary->isIncrementDecrementOp()) + written = unary->getSubExpr(); + if (written) { + const auto *variable = loopRequirementVariable(written); + if (!variable || variable == index || !variable->hasLocalStorage() || + !variable->getType()->isArithmeticType()) + return; + } + } + std::vector conditions{loop.getCond()}; + for (std::size_t i = 0; i < conditions.size(); ++i) { + if (conditions.size() > MaxLoopRequirementConditionNodes) + return; + const auto *comparison = + dyn_cast(conditions[i]->IgnoreParenImpCasts()); + if (!comparison) + continue; + if (comparison->getOpcode() == BO_LAnd) { + conditions.push_back(comparison->getLHS()); + conditions.push_back(comparison->getRHS()); + continue; + } + if (comparison->getOpcode() != BO_LT) + continue; + const auto *sum = + dyn_cast(comparison->getLHS()->IgnoreParenImpCasts()); + if (!sum || sum->getOpcode() != BO_Add || + integerTypeOf(sum->getType(), context) != type || + integerTypeOf(comparison->getRHS()->getType(), context) != type) + continue; + const Expr *indexed = sum->getLHS(); + const Expr *position = sum->getRHS(); + if (loopRequirementVariable(indexed) != index) + std::swap(indexed, position); + if (loopRequirementVariable(indexed) != index || + integerTypeOf(indexed->getType(), context) != type || + integerTypeOf(position->getType(), context) != type || + !isa(position->IgnoreParenImpCasts()) || + !isa(comparison->getRHS()->IgnoreParenImpCasts())) + continue; + const auto cursor = builder.resolve(*position); + const auto capacity = builder.resolve(*comparison->getRHS()); + if (!cursor || !capacity) + continue; + const auto *cursorDecl = + dyn_cast_or_null(builder.declFor(cursor->place)); + const auto *capacityDecl = + dyn_cast_or_null(builder.declFor(capacity->place)); + if (!cursorDecl || !capacityDecl || + integerTypeOf(*cursorDecl, context) != type || + integerTypeOf(*capacityDecl, context) != type) + continue; + for (const auto &[object, shape] : bufferObjects) + if (shape.reader && + places.field(object, shape.length.name) == cursor->place && + places.field(object, shape.capacity.name) == capacity->place) { + saved->second = CheckedReaderLoop{ + .index = indexed, + .position = position, + .capacity = comparison->getRHS(), + .backing = places.field(object, shape.data.name)}; + const auto *compound = dyn_cast(loop.getBody()); + const auto *selection = + compound && compound->size() == 1 + ? dyn_cast(*compound->body_begin()) + : dyn_cast(loop.getBody()); + const auto *byte = + selection ? dyn_cast( + selection->getCond()->IgnoreParenImpCasts()) + : nullptr; + bool numeric = byte != nullptr && + byteSizeOf(byte->getType(), context) == 1 && + !byte->getType().isVolatileQualified() && + !byte->getType()->isAtomicType() && + loopRequirementVariable(byte->getIdx()) == index; + bool hasDefault = false; + unsigned labels = 0; + for (const auto *label = selection ? selection->getSwitchCaseList() + : nullptr; + label && numeric; label = label->getNextSwitchCase()) { + if (++labels > 256) { + numeric = false; + break; + } + if (const auto *branch = dyn_cast(label)) { + numeric &= + branch->getRHS() == nullptr && + integerConstant(*branch->getLHS(), context).has_value() && + checkedNumericByte(*branch->getLHS(), state); + } else { + const auto *otherwise = cast(label); + const auto *jump = dyn_cast(otherwise->getSubStmt()); + hasDefault = true; + numeric &= jump != nullptr && + std::ranges::find(body, jump->getLabel()->getStmt()) == + body.end(); + } + } + if (numeric && hasDefault && labels > 1) + saved->second->numericInput = byte->getBase(); + break; + } + } + } + if (!saved->second) + return; + const auto &candidate = *saved->second; + const auto *buffer = bufferFact(candidate.backing, state); + if (!buffer || !buffer->shape.reader || !buffer->initialized) + return; + const auto index = integerExpressionOf(*candidate.index, state); + const auto cursor = integerExpressionOf(*candidate.position, state); + const auto capacity = integerExpressionOf(*candidate.capacity, state); + if (!index || !cursor || !capacity || + !checkedAtMost(core::Affine::ofPlace(buffer->length), + core::Affine::ofPlace(buffer->capacity), state)) + return; + const auto room = NumericExpression::operation(core::IntegerOp::Subtract, + *capacity, *cursor); + if (!room) + return; + // The initial index is zero; stable counters and the strict successful + // test leave room for the next unit step. This is an arithmetic induction + // premise, independently of the storage/initialization obligations. + state.numericConditions.requireInteger( + {.lhs = *index, .op = core::IntegerOp::LessEqual, .rhs = *room}); + const bool strict = &at != &loop && !checkedLoopConditions.contains(&at); + if (strict) + state.numericConditions.requireInteger( + {.lhs = *index, .op = core::IntegerOp::Less, .rhs = *room}); + const auto local = builder.resolve(*candidate.index); + if (local) { + const auto type = index->type(); + auto range = core::IntegerRange::full(type); + const auto available = evaluateNumericExpression(*room, state); + if (!available.mayBeInvalid) + range = range.satisfying(strict ? core::IntegerOp::Less + : core::IntegerOp::LessEqual, + available.values); + state.scalars.set( + local->place, + core::ValueFact::ofInteger( + integerRangeAt(local->place, type, state).intersect(range))); + if (candidate.numericInput && &at != &loop) { + const auto memory = + checkedMemory(*candidate.numericInput, {}, + core::Affine::ofPlace(local->place), state); + if (memory && checkedAtMost(memory->begin, memory->end, state)) + state.safety->initialize( + memory->storage, + {.begin = memory->begin, .end = memory->end, .numericText = true}); + } + } +} + // Only scalar locals and parameters can be stable without tracking heap writes. // Explicit minimum expressions are supported; arithmetic bounds are left to a // later extension rather than assuming that their evaluation cannot overflow. @@ -220,6 +433,120 @@ void FunctionDataflow::checkedLoopExit(const ForStmt &loop, const auto *condition = loop.getCond() ? dyn_cast(loop.getCond()->IgnoreParens()) : nullptr; + if (index && condition && condition->getOpcode() == BO_GT && + loopRequirementVariable(condition->getLHS()) == index && + integerConstant(*condition->getRHS(), context) == 0) { + const auto type = integerTypeOf(*index, context); + const auto *decrement = + loop.getInc() + ? dyn_cast(loop.getInc()->IgnoreParenImpCasts()) + : nullptr; + if (!type || type->isBoolean || !index->hasLocalStorage() || + addressTaken.contains(index) || + index->getType().isVolatileQualified() || + index->getType()->isAtomicType() || !initial || + initial->HasSideEffects(context) || !decrement || + !decrement->isDecrementOp() || + loopRequirementVariable(decrement->getSubExpr()) != index) + return; + std::set inputs{index}; + std::vector initialNodes; + if (!collectLoopRequirementStatements(initial, initialNodes)) + return; + for (const auto *node : initialNodes) + if (const auto *reference = dyn_cast(node)) { + const auto *variable = dyn_cast(reference->getDecl()); + if (!variable || !variable->hasLocalStorage() || + !variable->getType()->isIntegerType() || + variable->getType().isVolatileQualified() || + variable->getType()->isAtomicType() || + addressTaken.contains(variable->getCanonicalDecl())) + return; + inputs.insert(variable->getCanonicalDecl()); + } + std::vector statements; + if (!collectLoopRequirementStatements(loop.getBody(), statements)) + return; + const ArraySubscriptExpr *written = nullptr; + for (const auto *statement : statements) { + if (isa(statement)) + return; + const Expr *target = nullptr; + if (const auto *unary = dyn_cast(statement); + unary && + (unary->isIncrementDecrementOp() || unary->getOpcode() == UO_AddrOf)) + return; + if (const auto *binary = dyn_cast(statement)) { + if (binary->isLogicalOp()) + return; + if (binary->isAssignmentOp()) { + target = binary->getLHS(); + if (const auto *subscript = + dyn_cast(target->IgnoreParenImpCasts())) { + if (written || binary->getOpcode() != BO_Assign || + loopRequirementVariable(subscript->getIdx()) != index || + !subscript->getType()->isCharType() || + subscript->getType().isVolatileQualified()) + return; + written = subscript; + target = nullptr; + } + } + } + if (target) { + const auto *variable = loopRequirementVariable(target); + if (!variable || !variable->hasLocalStorage() || + !variable->getType()->isIntegerType() || + inputs.contains(variable) || + variable->getType().isVolatileQualified() || + variable->getType()->isAtomicType() || + addressTaken.contains(variable)) + return; + } + } + if (!written || written->getBase()->HasSideEffects(context)) + return; + const auto *base = written->getBase()->IgnoreParenImpCasts(); + const auto *slot = dyn_cast(base); + const auto *baseVariable = loopRequirementVariable(base); + if (slot && slot->getOpcode() == UO_Deref) + baseVariable = loopRequirementVariable(slot->getSubExpr()); + else + slot = nullptr; + if (!baseVariable || !baseVariable->hasLocalStorage() || + (!baseVariable->getType()->isPointerType() && + !baseVariable->getType()->isArrayType()) || + baseVariable->getType().isVolatileQualified() || + baseVariable->getType()->isAtomicType() || + (baseVariable->getType()->isPointerType() && + addressTaken.contains(baseVariable))) + return; + const auto expression = integerExpressionOf(*initial, state); + const auto first = + expression ? linearIntegerExpression(*expression, state) : std::nullopt; + const auto end = first ? first->shifted(1) : std::nullopt; + if (!first || !end || !checkedAtMost({}, *first, state)) + return; + const auto memory = checkedMemory(*written->getBase(), + core::Affine::ofConstant(1), *end, state); + if (!memory) + return; + if (slot) { + const auto bytes = byteSizeOf(slot->getType(), context); + const auto header = + bytes ? checkedMemory(*slot->getSubExpr(), {}, + core::Affine::ofConstant(*bytes), state) + : std::nullopt; + if (!header || !runtimeSeparate(*header, *memory, loop, state)) + return; + } + state.safety->initialize(memory->storage, + {.begin = memory->begin, .end = memory->end}); + return; + } if (!index || !condition || condition->getOpcode() != BO_LT || loopRequirementVariable(condition->getLHS()) != index) return; diff --git a/lib/Analysis/DataflowSafetyMemory.cpp b/lib/Analysis/DataflowSafetyMemory.cpp index b0bfcbeb..287c04b4 100644 --- a/lib/Analysis/DataflowSafetyMemory.cpp +++ b/lib/Analysis/DataflowSafetyMemory.cpp @@ -16,6 +16,182 @@ using namespace clang; namespace weavec::analysis { +std::optional +FunctionDataflow::checkedSeparationInput(const CheckedMemory &memory, + const core::AnalysisState &state) { + // An immutable backing identity supports separation after a cursor update, + // but supplies neither current validity nor current capacity (RFC 0029). + if (memory.holder) { + if (state.moves.recordOf(*memory.holder) || + state.safety->invalidatedPointers.contains(*memory.holder)) + return std::nullopt; + if (const auto *buffer = bufferFact(*memory.holder, state); + buffer && buffer->entryBacking) + return builder.summaryPathOf(*buffer->entryBacking); + // Changing the logical prefix retires the full buffer predicate while + // preserving the backing identity. Storage facts are independently + // invalidated if that pointer, header or capacity is replaced. + if (const auto backing = state.safety->buffers.storage.find(*memory.holder); + backing != state.safety->buffers.storage.end() && + backing->second.entryBacking && + memory.storage == places.deref(*backing->second.entryBacking)) + return builder.summaryPathOf(*backing->second.entryBacking); + } + if (memory.input) + return memory.input; + return memory.inputPlace ? stableSummaryPathOf(*memory.inputPlace) + : std::nullopt; +} + +std::optional +FunctionDataflow::checkedScalarMemory(core::PlaceId place, + const core::AnalysisState &state) { + if (!state.safety || state.safety->havoc) + return std::nullopt; + const auto *decl = dyn_cast_or_null(builder.declFor(place)); + QualType type = decl ? decl->getType() : QualType{}; + if (!places.isBase(place) && places.step(place) == core::PathStep::Deref) { + const auto *pointer = + dyn_cast_or_null(builder.declFor(*places.parent(place))); + type = pointer && pointer->getType()->isPointerType() + ? pointer->getType()->getPointeeType() + : QualType{}; + } else if (places.isElement(place)) { + type = arrayElementType(*places.parent(place)); + } + if (type.isNull() || !type->isIntegerType() || type.isVolatileQualified() || + type->isAtomicType()) + return std::nullopt; + const auto bytes = byteSizeOf(type, context); + if (!bytes) + return std::nullopt; + auto storage = place; + std::int64_t offset = 0; + bool selected = false; + unsigned depth = 0; + while (!places.isBase(storage)) { + if (++depth > core::MaxHeapPathDepth) + return std::nullopt; + const auto parent = places.parent(storage); + if (!parent) + return std::nullopt; + std::int64_t shift = 0; + if (places.step(storage) == core::PathStep::Deref) { + std::int64_t end = 0; + if (__builtin_add_overflow(offset, *bytes, &end)) + return std::nullopt; + return checkedMemoryAt(*parent, core::Affine::ofConstant(offset), + core::Affine::ofConstant(end), state); + } + if (places.step(storage) == core::PathStep::Field) { + const auto *field = dyn_cast_or_null(builder.declFor(storage)); + if (!field || field->isBitField() || field->getParent()->isUnion()) + return std::nullopt; + shift = static_cast(context.getFieldOffset(field) / + context.getCharWidth()); + } else if (places.isElement(storage)) { + const auto index = core::ArrayIndex::parse(places.fieldName(storage)); + const auto element = arrayElementType(*parent); + const auto unit = + element.isNull() ? std::nullopt : byteSizeOf(element, context); + if (!index || index->symbol || !unit || + __builtin_mul_overflow(index->offset, *unit, &shift)) + return std::nullopt; + selected = true; + } else if (places.step(storage) == core::PathStep::Index && selected) { + selected = false; + } else { + return std::nullopt; + } + if (__builtin_add_overflow(offset, shift, &offset)) + return std::nullopt; + storage = *parent; + } + std::int64_t end = 0; + if (__builtin_add_overflow(offset, *bytes, &end)) + return std::nullopt; + return CheckedMemory{.storage = storage, + .begin = core::Affine::ofConstant(offset), + .end = core::Affine::ofConstant(end), + .extent = {}, + .input = {}, + .pointer = nullptr}; +} + +bool FunctionDataflow::checkedZeroInteger(core::PlaceId place, + const core::AnalysisState &state) { + if (!state.safety || state.safety->havoc || state.safety->memory.empty()) + return false; + const auto root = places.root(place); + const auto ranges = state.safety->memory.find(root); + if (ranges == state.safety->memory.end() || + std::ranges::none_of(ranges->second, [](const auto &range) { + return range.zeroed && !range.source && range.when.trivial(); + })) + return false; + const auto *decl = dyn_cast_or_null(builder.declFor(place)); + if (!decl || !decl->getType()->isIntegerType() || + decl->getType().isVolatileQualified() || decl->getType()->isAtomicType()) + return false; + const auto bytes = byteSizeOf(decl->getType(), context); + if (!bytes) + return false; + // RFC 0029: use current byte evidence, never a remembered memset event. + // This bounded layout walk covers concrete automatic cells only. It does + // not dereference a pointer or assume that a wildcard denotes element zero. + auto storage = place; + std::int64_t offset = 0; + bool selected = false; + unsigned depth = 0; + while (!places.isBase(storage)) { + if (++depth > core::MaxHeapPathDepth) + return false; + const auto parent = places.parent(storage); + if (!parent) + return false; + std::int64_t shift = 0; + if (places.step(storage) == core::PathStep::Field) { + const auto *field = dyn_cast_or_null(builder.declFor(storage)); + if (!field || field->isBitField() || field->getParent()->isUnion() || + field->getType().isVolatileQualified() || + field->getType()->isAtomicType()) + return false; + shift = static_cast(context.getFieldOffset(field) / + context.getCharWidth()); + } else if (places.isElement(storage)) { + const auto index = core::ArrayIndex::parse(places.fieldName(storage)); + const auto type = arrayElementType(*parent); + const auto unit = + type.isNull() ? std::nullopt : byteSizeOf(type, context); + if (!index || index->symbol || index->offset < 0 || !unit || + __builtin_mul_overflow(index->offset, *unit, &shift)) + return false; + selected = true; + } else if (places.step(storage) == core::PathStep::Index && selected) { + selected = false; + } else { + return false; + } + if (__builtin_add_overflow(offset, shift, &offset)) + return false; + storage = *parent; + } + const auto *var = builder.varForPlace(storage); + if (!var || !var->hasLocalStorage() || var->getType().isVolatileQualified() || + var->getType()->isAtomicType()) + return false; + const auto extent = byteSizeOf(var->getType(), context); + std::int64_t end = 0; + if (!extent || __builtin_add_overflow(offset, *bytes, &end) || offset < 0 || + end > *extent) + return false; + return std::ranges::any_of(ranges->second, [&](const auto &range) { + return range.zeroed && !range.source && range.when.trivial() && + range.begin.isConstant() && range.end.isConstant() && + range.begin.constant <= offset && end <= range.end.constant; + }); +} + std::optional FunctionDataflow::checkedByteExpression(const core::Affine &value, const core::AnalysisState &state) { @@ -42,6 +218,9 @@ FunctionDataflow::checkedByteExpression(const core::Affine &value, type = fact->integer->type; if (!type && (checkedTerminatorInputs.contains(*value.place) || + std::ranges::any_of( + checkedSpans, + [&](const auto &entry) { return entry.second == *value.place; }) || std::ranges::any_of(checkedCoordinates, [&](const auto &entry) { return entry.second == *value.place; }))) @@ -114,8 +293,18 @@ std::optional FunctionDataflow::checkedByteSum( return exact; if (const auto exact = cancel(rhs, lhs)) return exact; - const auto a = checkedByteExpression(lhs, state); - const auto b = checkedByteExpression(rhs, state); + // Byte endpoints carry mathematical displacements separately from their + // evaluated C values. Keep that normal form when two symbolic bases are + // combined, so a strict bound on a+b also covers the endpoint a+b+1. + std::int64_t displacement = 0; + if (__builtin_add_overflow(lhs.constant, rhs.constant, &displacement)) + return std::nullopt; + auto left = lhs; + auto right = rhs; + left.constant = 0; + right.constant = 0; + const auto a = checkedByteExpression(left, state); + const auto b = checkedByteExpression(right, state); if (!a || !b) return std::nullopt; const auto sum = NumericExpression::operation(core::IntegerOp::Add, *a, *b); @@ -125,8 +314,8 @@ std::optional FunctionDataflow::checkedByteSum( operationDoesNotOverflow(core::IntegerOp::Add, *a, *b, a->type(), state); bool required = false; if (!proved && options.checkContracts) { - const auto first = summaryAffineOf(lhs); - const auto last = summaryAffineOf(rhs); + const auto first = summaryAffineOf(left); + const auto last = summaryAffineOf(right); required = first && last; if (required && recording()) inferred.checked.require({.kind = core::CheckedRequirementKind::SumFits, @@ -147,7 +336,7 @@ std::optional FunctionDataflow::checkedByteSum( saved = expressionPlaces.emplace(*sum, place).first; numericExpressions.emplace(place, *sum); } - return core::Affine::ofPlace(saved->second); + return core::Affine::ofPlace(saved->second, 1, displacement); } std::optional @@ -155,6 +344,33 @@ FunctionDataflow::checkedMemoryAt(core::PlaceId holder, const core::Affine &begin, const core::Affine &end, const core::AnalysisState &state) { + // RFC 0029: *slot can name a local pointer cell, not a second cursor. + // Resolve only an exact whole-cell access with independently proved + // validity, initialization and the same ordinary pointer representation. + if (places.step(holder) == core::PathStep::Deref) + if (const auto parent = places.parent(holder)) + if (const auto *pointer = + dyn_cast_or_null(builder.declFor(*parent)); + pointer && pointer->getType()->isPointerType()) { + const auto type = pointer->getType()->getPointeeType(); + const auto bytes = byteSizeOf(type, context); + if (type->isPointerType() && !type.isVolatileQualified() && bytes) { + const auto cell = checkedMemoryAt( + *parent, {}, core::Affine::ofConstant(*bytes), state); + const auto *variable = + cell ? dyn_cast_or_null(builder.declFor(cell->storage)) + : nullptr; + if (variable && variable->hasLocalStorage() && + places.isBase(cell->storage) && + !variable->getType().isVolatileQualified() && + ASTContext::hasSameUnqualifiedType(type, variable->getType()) && + foldAffine(cell->begin, state) == core::Affine::ofConstant(0) && + cell->extent && checkedValid(*cell, state) && + checkedInitialized(*cell, state) && + checkedInterval(cell->begin, cell->end, *cell->extent, state)) + holder = cell->storage; + } + } // A borrowed union object's member is the same holder under either name. // Canonicalize only definite, bounded storage images, never may-alias values. if (!state.safety->unions.members.empty()) { @@ -209,10 +425,8 @@ FunctionDataflow::checkedMemoryAt(core::PlaceId holder, .begin = begin, .end = end, .extent = extent, - // The relational premise describes current storage. - // Keep entry identity for separation, but do not turn - // derived current bounds/validity into scalar input - // requirements about a possibly replaced pointer. + // The relational premise describes current storage. Its retained + // entry identity is generally usable for separation alone. .input = {}, .pointer = nullptr, .holder = holder, @@ -260,7 +474,15 @@ FunctionDataflow::checkedMemoryAt(core::PlaceId holder, .inputPlace = position.input, .validWhenNonempty = position.validWhenNonempty}; } - const auto inputPath = stableSummaryPathOf(holder); + auto inputPath = stableSummaryPathOf(holder); + // RFC 0029: a later parameter assignment does not change the value at this + // operation. Recover the entry root only while the flow-sensitive domain + // proves that it has not been replaced on any incoming edge. + if (!inputPath && !state.safety->replacedPointers.contains(holder)) { + const auto path = builder.summaryPathOf(holder); + if (path && path->isParam() && path->isRoot()) + inputPath = path; + } CheckedMemory result{.storage = holder, .begin = begin, .end = end, @@ -380,9 +602,22 @@ void FunctionDataflow::checkedPointerFormation( "pointer formation", "formed pointer must remain within its object or one past it"); const bool valid = memory && checkedValid(*memory, state); + auto validity = memory; + if (!valid && validity && !validity->input && validity->holder) { + const auto holder = *validity->holder; + // An empty buffer need not promise a live backing. Pointer arithmetic + // still needs one, even for an offset of zero. Export that extra premise + // only for the exact unchanged entry pointer; a replaced backing cannot + // recover validity from its old allocation's identity. + if (const auto *fact = bufferFact(holder, state); + fact && fact->entryBacking == holder && + validity->storage == places.deref(holder) && + !state.safety->replacedPointers.contains(holder)) + validity->input = stableSummaryPathOf(holder); + } const bool input = - memory && memory->input && - checkedRequire(core::CheckedRequirementKind::Valid, *memory, at, state); + validity && validity->input && + checkedRequire(core::CheckedRequirementKind::Valid, *validity, at, state); safetyObligation(core::SafetyProperty::Validity, core::safetyOutcome(valid, input), at, "pointer formation", "pointer arithmetic requires live non-null storage"); @@ -426,15 +661,31 @@ std::optional FunctionDataflow::checkedMemory(const Expr &pointer, const core::Affine &begin, const core::Affine &end, const core::AnalysisState &state) { - const Expr *value = pointer.IgnoreParenImpCasts(); + // Pointer-to-pointer casts preserve storage identity (RFC 0004). Inspect + // arithmetic inside an explicit cast too, retaining the operand's original + // element size before expressing its offset in mathematical byte units. + const Expr *value = &PlaceBuilder::stripTransparent(pointer); if (const auto *address = dyn_cast(value); address && address->getOpcode() == UO_AddrOf) { const auto *target = address->getSubExpr()->IgnoreParenImpCasts(); const bool indirect = - isa(target) || + isa(target) || isa(target) || (isa(target) && cast(target)->getOpcode() == UO_Deref); - if (indirect) { + // Keep a const subobject's own identity so a cast cannot turn its mutable + // enclosing record into write permission for that member (RFC 0018). + if (indirect && !target->getType().isConstQualified()) { + // A member address retains its enclosing storage identity, but typed + // pointer arithmetic is still confined to that member subobject. Its + // layout alone supplies no evidence that the enclosing storage is live + // or large enough; checkedLvalue retains those separate obligations. + if (isa(target)) { + const auto bytes = byteSizeOf(target->getType(), context); + if (!bytes || !checkedAtMost({}, begin, state) || + !checkedAtMost(begin, end, state) || + !checkedAtMost(end, core::Affine::ofConstant(*bytes), state)) + return std::nullopt; + } auto location = checkedLvalue(*target, state); if (!location) return std::nullopt; @@ -460,12 +711,13 @@ FunctionDataflow::checkedMemory(const Expr &pointer, const core::Affine &begin, // can also carry an ordinary derived offset for an indirect holder; adding // it again would double-count a previous pointer-to-pointer increment. if (value->getType()->isPointerType() && PlaceBuilder::isPlaceExpr(*value)) - if (const auto holder = builder.resolvePointerValue(*value); - holder && state.safety->positions.contains(holder->place)) { + if (const auto holder = builder.resolvePointerValue(*value)) { auto result = checkedMemoryAt(holder->place, begin, end, state); - if (result) + if (result && result->holder && + state.safety->positions.contains(*result->holder)) { result->pointer = &pointer; - return result; + return result; + } } // Preserve the evaluated C index, then scale in mathematical byte units. if (const auto *binary = dyn_cast(value); @@ -616,8 +868,10 @@ FunctionDataflow::checkedWritePermission(const CheckedMemory &memory, const core::AnalysisState &state) { if (foldAffine(memory.begin, state) == foldAffine(memory.end, state)) return true; - if (memory.holder && bufferFact(*memory.holder, state)) - return true; + if (memory.holder) + if (const auto *fact = bufferFact(*memory.holder, state); + fact && !fact->shape.reader) + return true; if (memory.input) return std::nullopt; if (builder.isLiteralPlace(places.root(memory.storage))) @@ -639,7 +893,14 @@ FunctionDataflow::checkedWritePermission(const CheckedMemory &memory, type = array->getElementType(); if (type.isConstQualified()) return false; - if (isa(decl) && !type->isPointerType()) + // RFC 0018: an identified pointer variable is a writable cell too. + // Its pointee still needs separate permission. A direct lvalue or a + // different holder identifying this object denotes the cell itself. + if (isa(decl) && + (!type->isPointerType() || + (place == memory.storage && + (!memory.holder || *memory.holder != memory.storage) && + !places.innermostDeref(place)))) mutableObject = true; if (place == memory.storage && memory.pointer && !memory.holder && builder.classifyValue(*memory.pointer).kind == @@ -662,6 +923,20 @@ FunctionDataflow::checkedWritePermission(const CheckedMemory &memory, if (resource && resource->origin == core::ResourceOrigin::Allocated && resource->family == "free") return true; + for (const auto &[holder, storage] : state.safety->objects) { + if (storage != memory.storage) + continue; + const auto owner = state.resources.recordOf(holder); + if (!owner || owner->origin != core::ResourceOrigin::Allocated || + owner->family != "free" || owner->escaped) + continue; + auto guard = owner->guard; + const auto allocation = checkedMemoryAt(holder, {}, {}, state); + if (pruneGuard(guard, state) && guard.trivial() && allocation && + allocation->storage == memory.storage && + checkedValid(*allocation, state)) + return true; + } return std::nullopt; } @@ -696,8 +971,9 @@ bool FunctionDataflow::checkedInitialized(const CheckedMemory &memory, fact && fact->initialized && checkedInterval( memory.begin, memory.end, - core::Affine::ofPlace(fact->length, static_cast( - fact->shape.elementBytes)), + core::Affine::ofPlace( + fact->shape.reader ? fact->capacity : fact->length, + static_cast(fact->shape.elementBytes)), state)) return true; if (builder.isLiteralPlace(memory.storage) && memory.extent) @@ -765,8 +1041,11 @@ bool FunctionDataflow::checkedTerminated(const CheckedMemory &memory, const core::AnalysisState &state) { if (!memory.extent || !checkedValid(memory, state)) return false; - if (checkedWitness(memory, state)) - return true; + if (const auto witness = checkedWitness(memory, state)) + if (const auto through = witness->zero.shifted(1); + through && + checkedInterval(memory.begin, *through, *memory.extent, state)) + return true; if (const auto bounded = state.safety->boundedTermination.find(memory.storage); bounded != state.safety->boundedTermination.end()) @@ -814,8 +1093,23 @@ bool FunctionDataflow::checkedRequire(core::CheckedRequirementKind kind, inferred.checked.deferred = true; return true; } - if (!memory.input) + auto input = memory.input; + if (!input && memory.holder && + (kind == core::CheckedRequirementKind::Extent || + kind == core::CheckedRequirementKind::Initialized) && + bufferFact(*memory.holder, state) && + memory.storage == places.deref(*memory.holder) && + !state.safety->replacedPointers.contains(*memory.holder)) + input = stableSummaryPathOf(*memory.holder); + if (!input) return false; + if (kind == core::CheckedRequirementKind::Extent && memory.inputPlace && + bufferFact(*memory.inputPlace, state)) { + const auto backing = checkedMemoryAt(*memory.inputPlace, {}, {}, state); + if (backing && backing->storage == memory.storage && backing->extent && + checkedInterval(memory.begin, memory.end, *backing->extent, state)) + return true; + } core::PathGuard condition; if (recording()) { if (checkedRequirementGuard) { @@ -843,6 +1137,11 @@ bool FunctionDataflow::checkedRequire(core::CheckedRequirementKind kind, if (!checkedAtMost({}, memory.begin, state)) return false; end = core::PathAffine::ofConstant(0); + } else if (kind == core::CheckedRequirementKind::TerminatedWithin) { + // Both bounds name entry values. Widening or resetting the start of a + // terminated prefix could include an earlier, uninitialized interval. + if (!begin || !end || !checkedAtMost({}, memory.begin, state)) + return false; } else if (!end) { end = checkedRequirementEnvelope(memory.end, state); if (!end) @@ -871,7 +1170,7 @@ bool FunctionDataflow::checkedRequire(core::CheckedRequirementKind kind, } if (recording()) inferred.checked.require({.kind = kind, - .path = *memory.input, + .path = *input, .other = {}, .begin = *begin, .end = *end, diff --git a/lib/Analysis/DataflowSpans.cpp b/lib/Analysis/DataflowSpans.cpp new file mode 100644 index 00000000..76290bba --- /dev/null +++ b/lib/Analysis/DataflowSpans.cpp @@ -0,0 +1,297 @@ +//===- DataflowSpans.cpp - Entry byte spans (RFC 0029) --------------------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "Dataflow.h" +#include "IntegerSupport.h" + +#include + +using namespace clang; +namespace weavec::analysis { + +static const VarDecl *spanVariable(const Expr *expression) { + const auto *reference = + dyn_cast(expression->IgnoreParenImpCasts()); + return reference ? dyn_cast(reference->getDecl()) : nullptr; +} + +static const ParmVarDecl * +spanParameter(const Expr *expression, const std::set &changed, + unsigned depth = 0) { + const auto *variable = spanVariable(expression); + if (!variable || depth == 12 || changed.contains(variable) || + variable->getType().isVolatileQualified() || + variable->getType()->isAtomicType()) + return nullptr; + if (const auto *parameter = dyn_cast(variable)) + return parameter; + return variable->hasLocalStorage() && variable->getInit() + ? spanParameter(variable->getInit(), changed, depth + 1) + : nullptr; +} + +void FunctionDataflow::initializeCheckedSpans(core::AnalysisState &state) { + const auto bytePointer = [](QualType type) { + return type->isPointerType() && type->getPointeeType()->isCharType() && + !type.isVolatileQualified() && + !type->getPointeeType().isVolatileQualified(); + }; + unsigned endpoints = 0; + for (const auto *parameter : function.parameters()) + endpoints += bytePointer(parameter->getType()) ? 1U : 0U; + if (endpoints == 0) + return; + std::vector syntax{function.getBody()}; + std::set changed; + std::vector differences; + std::vector comparisons; + for (std::size_t i = 0; i < syntax.size(); ++i) { + const auto *statement = syntax[i]; + if (const auto *unary = dyn_cast(statement); + unary && + (unary->isIncrementDecrementOp() || unary->getOpcode() == UO_AddrOf)) + if (const auto *variable = spanVariable(unary->getSubExpr())) + changed.insert(variable); + if (const auto *binary = dyn_cast(statement)) { + if (binary->getOpcode() == BO_LT) + comparisons.push_back(binary); + if (binary->isAssignmentOp()) + if (const auto *variable = spanVariable(binary->getLHS())) + changed.insert(variable); + const auto left = binary->getLHS()->getType(); + const auto right = binary->getRHS()->getType(); + if (binary->getOpcode() == BO_Sub && left->isPointerType() && + right->isPointerType() && left->getPointeeType()->isCharType() && + !left->getPointeeType().isVolatileQualified() && + !right->getPointeeType().isVolatileQualified() && + ASTContext::hasSameUnqualifiedType(left->getPointeeType(), + right->getPointeeType())) + differences.push_back(binary); + } + if (const auto *trait = dyn_cast(statement); + trait && + (trait->isArgumentType() || + !trait->getArgumentExpr()->getType()->isVariablyModifiedType())) + continue; + for (const auto *child : statement->children()) { + if (!child) + continue; + if (syntax.size() == 65536) + return; + syntax.push_back(child); + } + } + using Pair = std::pair; + std::set candidates; + std::map uses; + std::set intervals; + std::map intervalUses; + for (const auto *comparison : comparisons) { + const auto *difference = + dyn_cast(comparison->getLHS()->IgnoreParenCasts()); + if (!difference || difference->getOpcode() != BO_Sub || + !bytePointer(difference->getLHS()->getType()) || + !bytePointer(difference->getRHS()->getType()) || + !ASTContext::hasSameUnqualifiedType( + difference->getLHS()->getType()->getPointeeType(), + difference->getRHS()->getType()->getPointeeType())) + continue; + const auto *base = spanParameter(difference->getRHS(), changed); + const auto *count = spanParameter(comparison->getRHS(), changed); + if (!base || !count || base == count || + base->getDeclContext() != &function || + count->getDeclContext() != &function || + !count->getType()->isUnsignedIntegerType() || + count->getType()->isBooleanType()) + continue; + if (intervals.emplace(base, count).second) { + ++intervalUses[base]; + ++intervalUses[count]; + } + if (intervals.size() > 16) + return; + } + for (const auto *difference : differences) { + const auto *first = spanParameter(difference->getRHS(), changed); + const auto *last = spanParameter(difference->getLHS(), changed); + if (!first || !last || first == last || !bytePointer(first->getType()) || + !bytePointer(last->getType()) || first->getDeclContext() != &function || + last->getDeclContext() != &function) + continue; + if (candidates.emplace(first, last).second) { + ++uses[first]; + ++uses[last]; + } + if (candidates.size() > 16) + return; + } + const auto type = integerTypeOf(context.getPointerDiffType(), context); + const auto maximum = + type ? core::IntegerRange::full(*type).maximum() : std::nullopt; + const auto limit = maximum ? maximum->signedValue() : std::nullopt; + if (!type || !limit || *limit <= 0) + return; + for (const auto &[base, count] : intervals) { + const auto countType = integerTypeOf(*count, context); + if (!countType || intervalUses[base] != 1 || intervalUses[count] != 1) + continue; + const auto holder = builder.placeForVar(*base); + const auto size = builder.placeForVar(*count); + const auto storage = places.deref(holder); + const auto length = core::Affine::ofPlace(size); + const auto path = core::SummaryPath::param(base->getFunctionScopeIndex()); + const auto countPath = + core::SummaryPath::param(count->getFunctionScopeIndex()); + const auto largest = core::IntegerRange::full(*countType).maximum()->bits; + const auto bound = std::min(largest, static_cast(*limit)); + const auto range = core::IntegerRange::between( + core::IntegerValue::ofBits(*countType, 0), + core::IntegerValue::ofBits(*countType, bound)); + state.scalars.set( + size, core::ValueFact::ofInteger( + integerRangeAt(size, *countType, state).intersect(range))); + state.relations.learnAtLeast(size, 0); + state.relations.learnAtMost(size, static_cast(bound)); + checkedInputObjects[holder] = storage; + state.safety->pointers.insert(holder); + state.nulls.set(holder, {.state = core::Nullness::NonNull, + .location = {}, + .reason = core::NullReason::Declared}); + state.safety->accessible[storage] = length; + state.safety->positions[holder] = { + .storage = storage, .offset = {}, .extent = length, .input = holder}; + state.safety->initialize(storage, {.begin = {}, .end = length}); + inferred.checked.require({.kind = core::CheckedRequirementKind::Valid, + .path = path, + .other = {}, + .family = {}}); + for (const auto kind : {core::CheckedRequirementKind::Extent, + core::CheckedRequirementKind::Initialized}) + inferred.checked.require({.kind = kind, + .path = path, + .other = {}, + .end = core::PathAffine::ofPath(countPath), + .family = {}}); + if (largest > bound) { + const auto slack = core::IntegerExpression::constant( + core::IntegerValue::ofBits(*countType, largest - bound)); + inferred.checked.require({.kind = core::CheckedRequirementKind::SumFits, + .path = {}, + .other = {}, + .begin = core::PathAffine::ofPath(countPath), + .end = core::PathAffine::ofExpression(slack), + .family = {}}); + } + } + for (const auto &[first, last] : candidates) { + if (uses[first] != 1 || uses[last] != 1) + continue; + const auto begin = builder.placeForVar(*first); + const auto end = builder.placeForVar(*last); + // This sufficient entry condition is installed only in initialState. + // Flow-sensitive invalidation cannot recreate it at a later subtraction. + const auto distance = + places.create("span(" + nameOf(begin) + "," + nameOf(end) + ")"); + checkedSpans[{begin, end}] = distance; + const auto length = core::Affine::ofPlace(distance); + const auto storage = places.deref(begin); + state.scalars.set(distance, + core::ValueFact::ofInteger(core::IntegerRange::between( + core::IntegerValue::ofBits(*type, 0), *maximum))); + state.relations.learnAtLeast(distance, 0); + state.relations.learnAtMost(distance, *limit); + state.safety->accessible[storage] = length; + state.safety->initialize( + storage, {.begin = {}, .end = length, .when = {}, .source = {}}); + for (const auto holder : {begin, end}) { + checkedInputObjects[holder] = storage; + state.safety->pointers.insert(holder); + state.nulls.set(holder, {.state = core::Nullness::NonNull, + .location = {}, + .reason = core::NullReason::Declared}); + state.safety->positions[holder] = { + .storage = storage, + .offset = holder == begin ? core::Affine::ofConstant(0) : length, + .extent = length}; + } + inferred.checked.require( + {.kind = core::CheckedRequirementKind::InitializedSpan, + .path = core::SummaryPath::param(first->getFunctionScopeIndex()), + .other = core::SummaryPath::param(last->getFunctionScopeIndex()), + .end = core::PathAffine::ofConstant(*limit), + .family = {}}); + } +} + +bool FunctionDataflow::checkedSpanCall( + const core::CheckedRequirement &requirement, const CallExpr &call, + core::AnalysisState &state) { + const auto first = checkedPathMemory(requirement.path, call, {}, {}, state); + const auto last = checkedPathMemory(requirement.other, call, {}, {}, state); + if (!first || !last || first->storage != last->storage || !first->extent || + !last->extent || !checkedValid(*first, state) || + !checkedValid(*last, state) || + !checkedInterval(first->begin, first->end, *first->extent, state) || + !checkedInterval(last->begin, last->end, *last->extent, state) || + !checkedAtMost(first->begin, last->begin, state)) + return false; + const auto maximum = first->begin.shifted(requirement.end.constant); + // Both coordinates are already nonnegative. Bounding the final coordinate + // also bounds their difference, without overflowing first + PTRDIFF_MAX. + if ((!maximum || !checkedAtMost(last->begin, *maximum, state)) && + !checkedAtMost(last->begin, + core::Affine::ofConstant(requirement.end.constant), state)) + return false; + auto interval = *first; + interval.end = last->begin; + return checkedInitialized(interval, state); +} + +void FunctionDataflow::checkedSpanCountBounds(core::AnalysisState &state) { + if (!state.safety || checkedSpans.empty()) + return; + for (const auto &[endpoints, distance] : checkedSpans) { + (void)endpoints; + const auto lower = state.relations.atLeast(distance); + if (!lower) + continue; + for (const auto counter : checkedLoopCounters) { + const auto fact = state.scalars.factOf(counter); + if (fact && fact->constant && *fact->constant >= 0 && + *fact->constant <= *lower) + state.relations.learn(counter, core::Relation::LessEqual, distance); + } + } +} + +void FunctionDataflow::checkedSpanOutputs( + core::CheckedContract &outputs, const core::AnalysisState &state, + const Expr *value, std::optional outcome) { + if (checkedSpans.empty() || !value || !value->getType()->isIntegerType()) + return; + auto current = state; + const auto range = integerRangeOf(*value, current); + const auto count = integerAffineOf(*value, current); + if (!range || range->mayBeInvalid || !count || + !checkedAtMost({}, *count, current)) + return; + for (const auto &[endpoints, distance] : checkedSpans) { + const auto first = builder.summaryPathOf(endpoints.first); + const auto last = builder.summaryPathOf(endpoints.second); + if (!first || !last || + !checkedAtMost(*count, core::Affine::ofPlace(distance), current)) + continue; + outputs.establish({.kind = core::CheckedRequirementKind::CountWithinSpan, + .path = *first, + .other = *last, + .family = {}, + .on = outcome}); + } +} + +} // namespace weavec::analysis diff --git a/lib/Analysis/DataflowStringTraversal.cpp b/lib/Analysis/DataflowStringTraversal.cpp index 1627b1c4..ab41472d 100644 --- a/lib/Analysis/DataflowStringTraversal.cpp +++ b/lib/Analysis/DataflowStringTraversal.cpp @@ -16,6 +16,124 @@ using namespace clang; namespace weavec::analysis { +void FunctionDataflow::checkedStringLength(const CallExpr &call, + core::AnalysisState &state) { + if (call.getNumArgs() != 1 || state.safety->havoc) + return; + const auto result = builder.legacyAffineOf(call); + if (!result || !result->place) + return; + // A later call may observe changed bytes. Saved results keep their old + // value, while this call's result cannot reuse a previous first-zero proof. + snapshotIntegerDependencies(*result->place, &call, state); + snapshotScalar(*result->place, &call, state); + state.dropGuardsOn(*result->place); + state.numericValues.erase(*result->place); + if (stringLengthOf(*call.getArg(0), state)) + return; + auto memory = checkedMemory(*call.getArg(0), {}, {}, state); + const auto type = integerTypeOf(call.getType(), context); + if (!memory || !memory->extent || !result || !result->place || !type) + return; + std::optional upper; + if (const auto witness = checkedWitness(*memory, state)) + upper = witness->zero.shifted(1); + if (!upper) + if (const auto found = + state.safety->boundedTermination.find(memory->storage); + found != state.safety->boundedTermination.end()) + for (const auto &fact : found->second) { + auto when = fact.when; + if (pruneGuard(when, state) && when.trivial() && + checkedAtMost(fact.begin, memory->begin, state) && + checkedAtMost(memory->begin, fact.begin, state)) { + upper = fact.end; + break; + } + } + bool required = false; + if (!upper && !state.safety->writtenStorage.contains(memory->storage)) { + // A buffer's capacity nominates an explicit bounded-string premise; it + // supplies no initialized contents or zero byte by itself (RFC 0029). + for (const auto &[data, buffer] : state.safety->buffers.values) { + if (buffer.shape.reader || buffer.shape.pointerElements || + buffer.shape.elementBytes != 1 || !buffer.entryBacking || + memory->storage != places.deref(*buffer.entryBacking) || + state.safety->replacedPointers.contains(data)) + continue; + auto input = *memory; + input.input = stableSummaryPathOf(*buffer.entryBacking); + input.end = core::Affine::ofPlace(buffer.capacity); + if (input.input && + checkedRequire(core::CheckedRequirementKind::TerminatedWithin, input, + call, state)) { + upper = input.end; + required = true; + break; + } + } + } + if (!upper || !checkedValid(*memory, state) || + (!required && + !checkedInterval(memory->begin, *upper, *memory->extent, state))) + return; + const auto begin = checkedByteExpression(memory->begin, state); + if (!begin) + return; + auto first = checkedFirstZeros.find(&call); + if (first == checkedFirstZeros.end()) { + if (checkedFirstZeros.size() >= core::MaxTraversalVariables) + return; + first = checkedFirstZeros.emplace(&call, places.create("first zero")).first; + } + const auto zero = first->second; + if (begin->dependsOn(zero) || upper->place == zero) + return; + snapshotIntegerDependencies(zero, &call, state); + snapshotScalar(zero, &call, state); + state.dropGuardsOn(zero); + state.relations.forget(zero); + state.numericValues.erase(zero); + state.scalars.set(zero, + core::ValueFact::ofInteger(core::IntegerRange::between( + core::IntegerValue::ofBits(*type, 0), + core::IntegerValue::ofBits(*type, type->mask() - 1)))); + const auto bound = [&](const core::Affine &value, bool lower) { + const auto folded = foldAffine(value, state); + if (folded.isConstant()) { + if (lower) + state.relations.learnAtLeast(zero, folded.constant); + else if (folded.constant != INT64_MIN) + state.relations.learnAtMost(zero, folded.constant - 1); + } else if (folded.scale == 1) { + state.relations.learn( + zero, lower ? core::Relation::GreaterEqual : core::Relation::Less, + *folded.place, folded.constant); + } + }; + bound(memory->begin, true); + bound(*upper, false); + const auto start = begin->converted(*type); + const auto length = start ? NumericExpression::operation( + core::IntegerOp::Subtract, + NumericExpression::input(zero, *type), *start) + : std::nullopt; + if (!length) + return; + state.numericValues.insert_or_assign(*result->place, *length); + const auto through = core::Affine::ofPlace(zero, 1, 1); + state.safety->initialize(memory->storage, + {.begin = memory->begin, .end = through}); + state.safety->initialize( + memory->storage, + {.begin = core::Affine::ofPlace(zero), .end = through, .zeroed = true}); + safetyObligation( + core::SafetyProperty::Initialization, + required ? core::SafetyOutcome::Required : core::SafetyOutcome::Proven, + call, "bounded string", + "string length ends at the first initialized zero within its bound"); +} + void FunctionDataflow::collectCheckedStrings(const Stmt &stmt) { std::vector pendingNodes{&stmt}; for (std::size_t i = 0; @@ -118,6 +236,16 @@ FunctionDataflow::checkedWitness(const CheckedMemory &memory, // Existing exact string facts and explicit zero stores can introduce a // witness, but only after the entire prefix is proved initialized. std::vector candidates; + if (memory.holder) + if (const auto spatial = spatialRecordAt(*memory.holder, state); + spatial && spatial->offset.isZero() && spatial->string && + !spatial->string->unterminated && spatial->string->length) { + const auto origin = checkedMemoryAt(*memory.holder, {}, {}, state); + if (origin && origin->storage == memory.storage && + checkedAtMost({}, origin->begin, state) && + checkedAtMost(origin->begin, {}, state)) + candidates.push_back(*spatial->string->length); + } if (memory.pointer) if (const auto length = stringLengthOf(*memory.pointer, state)) { const auto base = checkedMemory(*memory.pointer, {}, {}, state); @@ -273,11 +401,60 @@ void FunctionDataflow::checkedStringCondition(const Expr &expr, const Expr *other, bool holds, core::AnalysisState &state) { const auto *value = expr.IgnoreParenImpCasts(); - if (!value->getType()->isCharType() || !PlaceBuilder::isPlaceExpr(*value)) + if (!value->getType()->isCharType() || + value->getType().isVolatileQualified() || + value->getType()->isAtomicType() || !PlaceBuilder::isPlaceExpr(*value)) return; const auto memory = checkedLvalue(*value, state); if (!memory || !checkedValid(*memory, state)) return; + const auto byteType = integerTypeOf(value->getType(), context); + const auto comparedType = integerTypeOf(expr.getType(), context); + const auto operation = integerOpOf(op); + const auto comparedValue = + other ? integerRangeOf(*other, state) + : std::optional(core::IntegerRangeEvaluation{ + .values = + core::IntegerRange::singleton(core::IntegerValue::ofBits( + comparedType.value_or(core::BooleanType), 0))}); + const auto contents = checkedByteContents(*memory, state); + if (contents && byteType && comparedType && operation && comparedValue && + !comparedValue->mayBeInvalid && comparedValue->values.constant()) { + std::optional first; + std::optional last; + for (std::size_t i = 0; i < contents->second.size(); ++i) { + const auto byte = + core::IntegerValue::ofBits( + *byteType, static_cast(contents->second[i])) + .converted(*comparedType); + const auto test = core::evaluateInteger( + *operation, byte, *comparedValue->values.constant()); + if (!test.value) + return; + if ((test.value->bits != 0) != holds) + continue; + const auto index = contents->first + static_cast(i); + if (!first) + first = index; + last = index; + } + if (!first) { + edgeInfeasible = true; + return; + } + if (memory->begin.place && memory->begin.scale == 1) { + std::int64_t lower = 0; + std::int64_t upper = 0; + if (!__builtin_sub_overflow(*first, memory->begin.constant, &lower) && + !__builtin_sub_overflow(*last, memory->begin.constant, &upper)) { + if (*first > contents->first) + state.relations.learnAtLeast(*memory->begin.place, lower); + if (*last < contents->first + + static_cast(contents->second.size()) - 1) + state.relations.learnAtMost(*memory->begin.place, upper); + } + } + } const auto witness = checkedWitness(*memory, state); if (!witness) return; @@ -297,6 +474,13 @@ void FunctionDataflow::checkedStringCondition(const Expr &expr, return; const auto coordinate = memory->begin; const auto zero = witness->zero; + if (checkedAtMost(zero, coordinate, state)) { + // checkedWitness already proved coordinate <= zero. Reading a nonzero + // byte at that same current zero is impossible, not a negative offset + // that should flow into loop widening (RFC 0029). + edgeInfeasible = true; + return; + } if (coordinate.place && zero.place && coordinate.scale == 1 && zero.scale == 1) { std::int64_t shift = 0; @@ -318,7 +502,7 @@ void FunctionDataflow::checkedStringCondition(const Expr &expr, void FunctionDataflow::checkedStringWrite( const std::optional &memory, bool zeroed, - core::AnalysisState &state) { + core::AnalysisState &state, bool numericText) { for (auto &[data, fact] : state.safety->buffers.values) { (void)data; fact.shape.terminated = false; @@ -331,7 +515,86 @@ void FunctionDataflow::checkedStringWrite( post.fact.shape.terminated = false; } auto witnesses = std::move(state.safety->termination); + // RFC 0029: preserve only proved frames of zero-initialized storage. A + // concrete field store must not erase calloc's untouched sibling fields. + std::vector> zeros; + if (memory) { + const auto concrete = [&](core::PlaceId storage) { + return isLocalStorage(storage) || + std::ranges::any_of(checkedObjects, [&](const auto &entry) { + return entry.second == storage; + }); + }; + const auto *writtenVariable = + builder.varForPlace(places.root(memory->storage)); + const bool privateScalar = + isLocalStorage(memory->storage) && writtenVariable != nullptr && + writtenVariable->hasLocalStorage() && + writtenVariable->getType()->isScalarType() && + !addressTaken.contains(writtenVariable->getCanonicalDecl()); + const auto writtenInput = checkedSeparationInput(*memory, state); + for (const auto &[storage, ranges] : state.safety->memory) { + std::optional preservedInput; + bool liveEntry = false; + if (storage != memory->storage && + places.step(storage) == core::PathStep::Deref) + if (const auto holder = places.parent(storage)) + if (const auto input = checkedMemoryAt(*holder, {}, {}, state); + input && input->storage == storage) { + preservedInput = checkedSeparationInput(*input, state); + liveEntry = + preservedInput && input->inputPlace && + storage == places.deref(*input->inputPlace) && + !state.safety->replacedPointers.contains(*input->inputPlace) && + checkedValid(*input, state); + } + for (const auto &range : ranges) { + if ((!range.zeroed && !range.numericText && range.bytes.empty()) || + range.source) + continue; + if (storage == memory->storage) { + if (numericText && (range.numericText || range.zeroed)) { + auto content = range; + content.zeroed = false; + content.numericText = true; + content.bytes.clear(); + content.immutableBytes = false; + zeros.emplace_back(storage, std::move(content)); + } + if (checkedAtMost(memory->end, range.begin, state) || + checkedAtMost(range.end, memory->begin, state)) { + zeros.emplace_back(storage, range); + } else { + for (auto part : + range.outsideWrite(foldAffine(memory->begin, state), + foldAffine(memory->end, state))) + zeros.emplace_back(storage, std::move(part)); + } + } else if (range.immutableBytes || privateScalar || + (liveEntry && concrete(memory->storage) && + checkedValid(*memory, state)) || + (concrete(storage) && concrete(memory->storage) && + places.root(storage) != places.root(memory->storage))) { + zeros.emplace_back(storage, range); + } else if (writtenInput && preservedInput && + writtenInput != preservedInput) { + // Conditional proof, not separation inferred from different names: + // each caller must establish that the unchanged entry objects are + // disjoint before this retained zero can be used. + if (recording()) + inferred.checked.require( + {.kind = core::CheckedRequirementKind::Separated, + .path = *writtenInput, + .other = *preservedInput, + .family = {}}); + zeros.emplace_back(storage, range); + } + } + } + } state.forgetZeroedMemory(); + for (auto &[storage, range] : zeros) + state.safety->initialize(storage, std::move(range)); if (!memory) { for (const auto &[storage, entries] : witnesses) { (void)entries; diff --git a/lib/Analysis/DataflowStrings.cpp b/lib/Analysis/DataflowStrings.cpp index 78ac60f1..314e3793 100644 --- a/lib/Analysis/DataflowStrings.cpp +++ b/lib/Analysis/DataflowStrings.cpp @@ -104,9 +104,9 @@ static std::optional formatIndexOf(const StringCallee &callee) { return std::nullopt; } -/// The string literal `expr` is, through the array decay and parentheses. +/// The string literal behind ordinary storage-preserving pointer casts. static const StringLiteral *literalOf(const Expr &expr) { - return dyn_cast(expr.IgnoreParenImpCasts()); + return dyn_cast(&PlaceBuilder::stripTransparent(expr)); } /// A byte offset in elements of `unit` bytes, when the tracker follows it: diff --git a/lib/Analysis/DataflowTraversalRelations.cpp b/lib/Analysis/DataflowTraversalRelations.cpp index 8a1f6826..1b7e21d9 100644 --- a/lib/Analysis/DataflowTraversalRelations.cpp +++ b/lib/Analysis/DataflowTraversalRelations.cpp @@ -97,7 +97,8 @@ static std::optional traversalSumBound( const std::map> &expressions, const core::AnalysisState &state, - const std::function &atMost) { + const std::function + &atMost) { if (!value.place || value.scale <= 0) return std::nullopt; const auto stored = expressions.find(*value.place); @@ -111,6 +112,28 @@ static std::optional traversalSumBound( const auto operands = sum.operands(); if (operands.size() != 2) return std::nullopt; + const auto endpoint = + [&](const auto &expression) -> std::optional { + if (const auto constant = expression.constantValue()) { + const auto value = constant->signedValue(); + return value && *value >= 0 + ? std::optional(core::Affine::ofConstant(*value)) + : std::nullopt; + } + const auto input = expression.inputKey(); + return input && !expressions.contains(*input) + ? std::optional(core::Affine::ofPlace(*input)) + : std::nullopt; + }; + const auto same = [&](const auto &a, const auto &b) { + if (a == b) + return true; + const auto x = a.inputKey(); + const auto y = b.inputKey(); + return a.type() == b.type() && x && y && !expressions.contains(*x) && + !expressions.contains(*y) && + state.relations.between(*x, *y) == core::Relation::Equal; + }; for (const auto &predicate : state.numericConditions.integers) { if (predicate.range) continue; @@ -133,12 +156,13 @@ static std::optional traversalSumBound( if (parts.size() != 2 || parts.front().type() != sum.type() || parts.back().type() != sum.type()) continue; - const auto length = parts.front().inputKey(); - const auto index = parts.back().inputKey(); - if (!length || !index || expressions.contains(*length) || - expressions.contains(*index) || !atMost(*index, *length) || - !((operands.front() == count && operands.back() == parts.back()) || - (operands.back() == count && operands.front() == parts.back()))) + const auto length = endpoint(parts.front()); + const auto index = endpoint(parts.back()); + if (!length || !index || !atMost(*index, *length) || + !((same(operands.front(), count) && + same(operands.back(), parts.back())) || + (same(operands.back(), count) && + same(operands.front(), parts.back())))) continue; const auto offset = relation == core::IntegerOp::Less ? -1 : 0; std::int64_t displacement = 0; @@ -146,7 +170,8 @@ static std::optional traversalSumBound( &displacement) || __builtin_add_overflow(displacement, value.constant, &displacement)) continue; - return core::Affine::ofPlace(*length, value.scale, displacement); + const auto scaled = length->times(value.scale); + return scaled ? scaled->shifted(displacement) : std::nullopt; } return std::nullopt; } @@ -155,6 +180,93 @@ void FunctionDataflow::checkedDifferenceCondition(const Expr &lhs, BinaryOperatorKind op, const Expr &rhs, bool holds, core::AnalysisState &state) { + // RFC 0029: retain the coordinate tested by (size_t)(cursor-base), rather + // than only the range known on this particular visit to the loop header. + const Expr *value = lhs.IgnoreParens(); + std::vector conversions; + while (const auto *cast = dyn_cast(value)) { + if (conversions.size() == 12) + return; + conversions.push_back(cast); + value = cast->getSubExpr()->IgnoreParens(); + } + if (const auto *subtraction = dyn_cast(value); + subtraction && subtraction->getOpcode() == BO_Sub && + subtraction->getLHS()->getType()->isPointerType()) { + if (lhs.HasSideEffects(context) || rhs.HasSideEffects(context) || + !subtraction->getLHS()->getType()->getPointeeType()->isCharType()) + return; + auto range = checkedPointerRange(*subtraction, state); + if (!range) + return; + for (const auto *cast : llvm::reverse(conversions)) { + const auto type = integerTypeOf(cast->getType(), context); + if (!type || !conversionPreserves(*range, *type)) + return; + range = range->converted(*type); + } + const auto cursor = checkedMemory(*subtraction->getLHS(), {}, {}, state); + const auto base = checkedMemory(*subtraction->getRHS(), {}, {}, state); + auto limit = integerAffineOf(rhs, state); + // Keep the live bound cell even when this case knows its current value. + // Its relation survives ordinary CFG widening more precisely than a + // succession of constant cursor hulls. + if (const auto bound = builder.resolve(*rhs.IgnoreParenImpCasts())) + if (const auto *decl = + dyn_cast_or_null(builder.declFor(bound->place)); + decl && bound->element.isWhole() && + integerTypeOf(*decl, context) == + integerTypeOf(rhs.getType(), context)) + limit = core::Affine::ofPlace(bound->place); + if (!cursor || !base || !limit || !base->begin.isConstant() || + !cursor->begin.place || cursor->begin.scale != 1 || limit->scale != 1) + return; + std::optional relation; + switch (op) { + case BO_LT: + relation = holds ? core::Relation::Less : core::Relation::GreaterEqual; + break; + case BO_LE: + relation = holds ? core::Relation::LessEqual : core::Relation::Greater; + break; + case BO_GT: + relation = holds ? core::Relation::Greater : core::Relation::LessEqual; + break; + case BO_GE: + relation = holds ? core::Relation::GreaterEqual : core::Relation::Less; + break; + case BO_EQ: + case BO_NE: + if ((op == BO_EQ) == holds) + relation = core::Relation::Equal; + break; + default: + break; + } + std::int64_t offset = 0; + if (!relation || + __builtin_add_overflow(limit->constant, base->begin.constant, + &offset) || + __builtin_sub_overflow(offset, cursor->begin.constant, &offset)) + return; + const auto coordinate = *cursor->begin.place; + if (limit->place) { + state.relations.learn(coordinate, *relation, *limit->place, offset); + } else { + if (*relation == core::Relation::Less && offset != INT64_MIN) + state.relations.learnAtMost(coordinate, offset - 1); + if (*relation == core::Relation::Greater && offset != INT64_MAX) + state.relations.learnAtLeast(coordinate, offset + 1); + if (*relation == core::Relation::LessEqual || + *relation == core::Relation::Equal) + state.relations.learnAtMost(coordinate, offset); + if (*relation == core::Relation::GreaterEqual || + *relation == core::Relation::Equal) + state.relations.learnAtLeast(coordinate, offset); + } + checkedSpanCountBounds(state); + return; + } const auto *difference = dyn_cast(lhs.IgnoreParenImpCasts()); const auto constant = integerConstant(rhs, context); if (!difference || difference->getOpcode() != BO_Sub || !constant || @@ -235,6 +347,91 @@ FunctionDataflow::checkedStableAffine(const core::Affine &value, return value; } +std::vector> +FunctionDataflow::checkedJoinBoundaries(const core::AnalysisState &first, + const core::AnalysisState &second) { + std::vector> result; + if (!first.safety || !second.safety) + return result; + std::size_t candidates = 0; + std::optional firstBounds; + std::optional secondBounds; + for (const auto &[holder, coordinate] : checkedCoordinates) { + const auto a = first.safety->positions.find(holder); + const auto b = second.safety->positions.find(holder); + if (a == first.safety->positions.end() || + b == second.safety->positions.end() || a->second != b->second || + a->second.offset != core::Affine::ofPlace(coordinate)) + continue; + std::vector endpoints; + if (a->second.extent) + endpoints.push_back(*a->second.extent); + if (const auto found = first.safety->termination.find(a->second.storage); + found != first.safety->termination.end()) + for (const auto &witness : found->second) + endpoints.push_back(witness.zero); + for (const auto &[otherHolder, position] : first.safety->positions) { + if (otherHolder == holder || position.storage != a->second.storage || + position.offset.place == coordinate || + checkedCoordinates.contains(otherHolder)) + continue; + const auto other = second.safety->positions.find(otherHolder); + if (other != second.safety->positions.end() && other->second == position) + endpoints.push_back(position.offset); + } + for (const auto &endpoint : endpoints) { + if (++candidates > core::MaxTraversalIterations) { + inferred.checked.limited = true; + inferred.incomplete.insert( + "traversal invariant candidate limit reached"); + return result; + } + if (endpoint.place == coordinate || endpoint.scale != 1 || + !checkedAtMost(a->second.offset, endpoint, first) || + !checkedAtMost(b->second.offset, endpoint, second)) + continue; + if (endpoint.place) + if (const auto expression = numericExpressions.find(*endpoint.place); + expression != numericExpressions.end() && + expression->second.dependsOn(coordinate)) + continue; + result.emplace_back(coordinate, endpoint); + } + for (const auto &[otherHolder, other] : checkedCoordinates) { + if (coordinate == other) + continue; + const auto x = first.safety->positions.find(otherHolder); + const auto y = second.safety->positions.find(otherHolder); + if (x == first.safety->positions.end() || + y == second.safety->positions.end() || x->second != y->second || + x->second.offset != core::Affine::ofPlace(other)) + continue; + if (++candidates > core::MaxTraversalIterations) { + inferred.checked.limited = true; + inferred.incomplete.insert( + "traversal invariant candidate limit reached"); + return result; + } + if (!firstBounds) { + firstBounds = checkedRelations(first); + secondBounds = checkedRelations(second); + } + const auto left = firstBounds->bound(coordinate, other); + const auto right = secondBounds->bound(coordinate, other); + if (firstBounds->limited() || secondBounds->limited()) { + inferred.checked.limited = true; + inferred.incomplete.insert("traversal relational limit reached"); + continue; + } + if (left && right && *left <= 0 && *right <= 0) + result.emplace_back( + coordinate, + core::Affine::ofPlace(other, 1, *left < 0 && *right < 0 ? -1 : 0)); + } + } + return result; +} + bool FunctionDataflow::checkedJoinPremises(core::AnalysisState &target, core::AnalysisState &incoming) { if (!target.safety || !incoming.safety) @@ -256,15 +453,18 @@ bool FunctionDataflow::checkedJoinPremises(core::AnalysisState &target, normalize(incoming); bool cursorPremises = false; std::size_t candidates = 0; + std::optional targetBounds; + std::optional incomingBounds; for (const auto &[holder, position] : target.safety->positions) { + (void)position; const auto coordinate = checkedCoordinates.find(holder); if (coordinate == checkedCoordinates.end() || !incoming.safety->positions.contains(holder)) continue; for (const auto &[other, otherPosition] : target.safety->positions) { + (void)otherPosition; const auto otherCoordinate = checkedCoordinates.find(other); - if (!(holder < other) || position.storage != otherPosition.storage || - otherCoordinate == checkedCoordinates.end() || + if (!(holder < other) || otherCoordinate == checkedCoordinates.end() || !incoming.safety->positions.contains(other)) continue; if (++candidates > core::MaxTraversalIterations) { @@ -276,16 +476,29 @@ bool FunctionDataflow::checkedJoinPremises(core::AnalysisState &target, for (const bool reverse : {false, true}) { const auto a = reverse ? otherCoordinate->second : coordinate->second; const auto b = reverse ? coordinate->second : otherCoordinate->second; - // Both edges must prove the candidate independently, including an - // edge where the order is implicit through a saved call input. - if (!checkedAtMost(core::Affine::ofPlace(a), core::Affine::ofPlace(b), - target) || - !checkedAtMost(core::Affine::ofPlace(a), core::Affine::ofPlace(b), - incoming)) + // Both edges must prove the numeric candidate independently, including + // an edge where the order is implicit through a saved call input. + // Offsets in different objects may be related; this establishes no + // common pointer provenance (RFC 0029). + if (!targetBounds) { + targetBounds = checkedRelations(target); + incomingBounds = checkedRelations(incoming); + } + const auto left = targetBounds->bound(a, b); + const auto right = incomingBounds->bound(a, b); + if (targetBounds->limited() || incomingBounds->limited()) { + inferred.checked.limited = true; + inferred.incomplete.insert("traversal relational limit reached"); continue; + } + if (!left || !right) + continue; + // Do not tighten an established order to an incidental first-trip + // distance: widening that distance can erase a strict loop guard. + const auto displacement = std::max({*left, *right, std::int64_t{0}}); const auto previous = target.relations; - target.relations.learn(a, core::Relation::LessEqual, b); - incoming.relations.learn(a, core::Relation::LessEqual, b); + target.relations.learn(a, core::Relation::LessEqual, b, displacement); + incoming.relations.learn(a, core::Relation::LessEqual, b, displacement); cursorPremises |= previous != target.relations; } } @@ -497,52 +710,53 @@ FunctionDataflow::checkedRelations(const core::AnalysisState &state) { if (operands.size() != 2 || operands.front().type() != root.type || operands.back().type() != root.type) continue; - const auto a = operands.front().inputKey(); - const auto b = operands.back().inputKey(); - if (!a || !b || !result.implies(b, a, 0)) - continue; - std::optional relation; - switch (operation) { - case core::IntegerOp::Less: - relation = core::Relation::Less; - break; - case core::IntegerOp::LessEqual: - relation = core::Relation::LessEqual; - break; - case core::IntegerOp::Equal: - relation = core::Relation::Equal; - break; - case core::IntegerOp::GreaterEqual: - relation = core::Relation::GreaterEqual; - break; - case core::IntegerOp::Greater: - relation = core::Relation::Greater; - break; - default: - break; - } - if (!relation) + const auto endpoint = + [&](NumericExpression value) -> std::optional { + while (value.all().back().kind == core::IntegerNodeKind::Convert) { + const auto operand = value.operands().front(); + const auto range = + operand.evaluate([&](core::PlaceId place, core::IntegerType type) { + return integerRangeAt(place, type, state); + }); + if (range.mayBeInvalid || + !conversionPreserves(range.values, value.type())) + return std::nullopt; + value = operand; + } + if (const auto input = value.inputKey()) + return core::Affine::ofPlace(*input); + if (const auto constant = value.constantValue()) + if (const auto number = constant->signedValue()) + return core::Affine::ofConstant(*number); + return std::nullopt; + }; + const auto a = endpoint(operands.front()); + const auto b = endpoint(operands.back()); + std::int64_t displacement = 0; + if (!a || !b || + __builtin_sub_overflow(a->constant, b->constant, &displacement) || + !result.implies(b->place, a->place, displacement)) continue; const auto lower = evaluated.values.minimum()->signedValue(); const auto upper = evaluated.values.maximum()->signedValue(); - if (lower && (*relation == core::Relation::Greater || - *relation == core::Relation::GreaterEqual || - *relation == core::Relation::Equal)) - result.learn(*a, - {.relation = *relation == core::Relation::Equal - ? core::Relation::GreaterEqual - : *relation, - .offset = *lower}, - *b); - if (upper && (*relation == core::Relation::Less || - *relation == core::Relation::LessEqual || - *relation == core::Relation::Equal)) - result.learn(*a, - {.relation = *relation == core::Relation::Equal - ? core::Relation::LessEqual - : *relation, - .offset = *upper}, - *b); + if (lower && (operation == core::IntegerOp::Greater || + operation == core::IntegerOp::GreaterEqual || + operation == core::IntegerOp::Equal)) { + std::int64_t bound = 0; + if (!__builtin_sub_overflow(displacement, *lower, &bound) && + (operation != core::IntegerOp::Greater || + !__builtin_sub_overflow(bound, std::int64_t{1}, &bound))) + result.constrain(b->place, a->place, bound); + } + if (upper && (operation == core::IntegerOp::Less || + operation == core::IntegerOp::LessEqual || + operation == core::IntegerOp::Equal)) { + std::int64_t bound = 0; + if (!__builtin_sub_overflow(*upper, displacement, &bound) && + (operation != core::IntegerOp::Less || + !__builtin_sub_overflow(bound, std::int64_t{1}, &bound))) + result.constrain(a->place, b->place, bound); + } } inferred.checked.limited |= result.limited(); if (result.limited()) @@ -584,9 +798,8 @@ bool FunctionDataflow::checkedAtMost(const core::Affine &lhs, } if (const auto sum = traversalSumBound( a, numericExpressions, state, - [&](core::PlaceId index, core::PlaceId length) { - return checkedAtMost(core::Affine::ofPlace(index), - core::Affine::ofPlace(length), state); + [&](const core::Affine &index, const core::Affine &length) { + return checkedAtMost(index, length, state); }); sum && checkedAtMost(*sum, b, state)) return true; @@ -758,9 +971,8 @@ FunctionDataflow::checkedRequirementEnvelope(const core::Affine &need, } if (const auto sum = traversalSumBound( need, numericExpressions, state, - [&](core::PlaceId index, core::PlaceId length) { - return checkedAtMost(core::Affine::ofPlace(index), - core::Affine::ofPlace(length), state); + [&](const core::Affine &index, const core::Affine &length) { + return checkedAtMost(index, length, state); })) if (const auto projected = summaryAffineOf(sum)) return projected; @@ -790,8 +1002,25 @@ FunctionDataflow::checkedRequirementEnvelope(const core::Affine &need, return projected; } // A type maximum is not a useful inferred traversal capacity. Project a - // constant only when control flow explicitly established that boundary. - const auto upper = state.relations.atMost(*need.place); + // constant only from an explicit boundary or an actual narrowed value fact. + auto upper = state.relations.atMost(*need.place); + std::optional type; + if (const auto *decl = + dyn_cast_or_null(builder.declFor(*need.place))) + type = integerTypeOf(*decl, context); + if (const auto fact = state.scalars.factOf(*need.place)) { + if (!type && fact->integer) + type = fact->integer->type; + if (type) { + const auto range = fact->inType(*type); + const auto maximum = range.maximum(); + const auto bound = maximum ? maximum->signedValue() : std::nullopt; + const auto typeMaximum = core::IntegerRange::full(*type).maximum(); + if (bound && (fact->constant || + (typeMaximum && maximum->bits != typeMaximum->bits))) + upper = upper ? std::min(*upper, *bound) : bound; + } + } std::int64_t last = 0; if (upper && !__builtin_mul_overflow(*upper, need.scale, &last) && !__builtin_add_overflow(last, need.constant, &last) && last >= 0) diff --git a/lib/Analysis/FloatingCastSupport.cpp b/lib/Analysis/FloatingCastSupport.cpp new file mode 100644 index 00000000..f4cf8922 --- /dev/null +++ b/lib/Analysis/FloatingCastSupport.cpp @@ -0,0 +1,209 @@ +//===- FloatingCastSupport.cpp - Finite conversions (RFC 0029) ------------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "FloatingCastSupport.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ParentMapContext.h" + +#include "llvm/ADT/APSInt.h" + +#include + +using namespace clang; +namespace weavec::analysis { + +static bool standardFloatingMode(const Expr &expression, + const ASTContext &context) { + const auto options = expression.getFPFeaturesInEffect(context.getLangOpts()); + return !options.isFPConstrained() && !options.getNoHonorNaNs() && + !options.getNoHonorInfs() && !options.getAllowFPReassociate() && + options.getFPEvalMethod() == LangOptions::FEM_Source; +} + +static const VarDecl *floatingVariable(const Expr *expression) { + const auto *reference = + dyn_cast(expression->IgnoreParenImpCasts()); + return reference ? dyn_cast(reference->getDecl()) : nullptr; +} + +static bool unchangedFloatingVariable(const VarDecl &variable, + const FunctionDecl &function, + bool allowAssignments) { + if (!variable.hasLocalStorage() || variable.getType().isVolatileQualified() || + !variable.getType()->isRealFloatingType()) + return false; + std::vector pending{function.getBody()}; + for (std::size_t i = 0; i < pending.size(); ++i) { + const auto *statement = pending[i]; + // Lexical true branches are not dominance proofs in the presence of a + // jump into their bodies, including switch labels nested inside an if. + if (isa(statement) || + (!allowAssignments && isa(statement))) + return false; + if (const auto *unary = dyn_cast(statement); + unary && + (unary->isIncrementDecrementOp() || unary->getOpcode() == UO_AddrOf) && + floatingVariable(unary->getSubExpr()) == &variable) + return false; + if (const auto *binary = dyn_cast(statement); + binary && binary->isAssignmentOp() && + floatingVariable(binary->getLHS()) == &variable && !allowAssignments) + return false; + for (const auto *child : statement->children()) { + if (!child) + continue; + if (pending.size() == 65536) + return false; + pending.push_back(child); + } + } + return true; +} + +static bool unchangedFloatingBranch(const Stmt &branch, + const VarDecl &variable) { + std::vector pending{&branch}; + for (std::size_t i = 0; i < pending.size(); ++i) { + const auto *statement = pending[i]; + if (isa(statement)) + return false; + if (const auto *binary = dyn_cast(statement); + binary && binary->isAssignmentOp() && + floatingVariable(binary->getLHS()) == &variable) + return false; + if (const auto *unary = dyn_cast(statement); + unary && unary->isIncrementDecrementOp() && + floatingVariable(unary->getSubExpr()) == &variable) + return false; + for (const auto *child : statement->children()) { + if (!child) + continue; + if (pending.size() == 65536) + return false; + pending.push_back(child); + } + } + return true; +} + +static bool representableFloatingEndpoint(const llvm::APFloat &value, + QualType target, + const ASTContext &context) { + if (!value.isFinite()) + return false; + llvm::APSInt integer(context.getIntWidth(target), + !target->isSignedIntegerType()); + bool exact = false; + const auto status = + value.convertToInteger(integer, llvm::APFloat::rmTowardZero, &exact); + // Discarding a fractional part is the defined C conversion. Invalid or + // overflowing endpoints cannot justify any conversion in the interval. + return status == llvm::APFloat::opOK || status == llvm::APFloat::opInexact; +} + +static void floatingBounds(const Expr &condition, const VarDecl &variable, + QualType target, ASTContext &context, bool &lower, + bool &upper, unsigned &remaining, bool conditionTrue, + bool excludesNan) { + if (remaining == 0) + return; + --remaining; + const auto *binary = + dyn_cast(condition.IgnoreParenImpCasts()); + if (!binary || !standardFloatingMode(*binary, context)) + return; + if ((conditionTrue && binary->getOpcode() == BO_LAnd) || + (!conditionTrue && excludesNan && binary->getOpcode() == BO_LOr)) { + floatingBounds(*binary->getLHS(), variable, target, context, lower, upper, + remaining, conditionTrue, excludesNan); + floatingBounds(*binary->getRHS(), variable, target, context, lower, upper, + remaining, conditionTrue, excludesNan); + return; + } + if (!binary->isComparisonOp() || (!conditionTrue && !excludesNan)) + return; + auto operation = binary->getOpcode(); + if (!conditionTrue) + operation = BinaryOperator::negateComparisonOp(operation); + const Expr *constant = binary->getRHS(); + if (floatingVariable(binary->getLHS()) != &variable) { + if (floatingVariable(binary->getRHS()) != &variable) + return; + constant = binary->getLHS(); + operation = BinaryOperator::reverseComparisonOp(operation); + } + llvm::APFloat endpoint(0.0); + if (!constant->EvaluateAsFloat(endpoint, context) || !endpoint.isFinite()) + return; + if (operation == BO_LT) + endpoint.next(true); + if (operation == BO_GT) + endpoint.next(false); + if (!representableFloatingEndpoint(endpoint, target, context)) + return; + lower |= operation == BO_GT || operation == BO_GE || operation == BO_EQ; + upper |= operation == BO_LT || operation == BO_LE || operation == BO_EQ; +} + +bool finiteFloatingCast(const CastExpr &cast, const FunctionDecl &function, + ASTContext &context, bool excludesNan) { + if (cast.getCastKind() != CK_FloatingToIntegral || + !cast.getType()->isIntegerType() || context.getLangOpts().FastMath || + context.getLangOpts().RoundingMath || + !standardFloatingMode(cast, context)) + return false; + llvm::APFloat constant(0.0); + if (cast.getSubExpr()->EvaluateAsFloat(constant, context)) + return representableFloatingEndpoint(constant, cast.getType(), context); + const auto *variable = floatingVariable(cast.getSubExpr()); + if (!variable || + !ASTContext::hasSameUnqualifiedType(variable->getType(), + cast.getSubExpr()->getType()) || + !unchangedFloatingVariable(*variable, function, excludesNan)) + return false; + bool lower = false; + bool upper = false; + const bool earlyReturns = + unchangedFloatingVariable(*variable, function, false); + const Stmt *child = &cast; + unsigned remaining = 256; + for (unsigned depth = 0; depth < 128; ++depth) { + const auto parents = context.getParents(*child); + if (parents.size() != 1) + break; + const auto *parent = parents[0].get(); + if (!parent) + break; + if (const auto *compound = dyn_cast(parent); + compound && earlyReturns) + for (const auto *sibling : compound->body()) { + if (sibling == child) + break; + const auto *branch = dyn_cast(sibling); + if (!branch || branch->getElse()) + continue; + const Stmt *arm = branch->getThen(); + if (const auto *body = dyn_cast(arm); + body && body->size() == 1) + arm = *body->body_begin(); + if (isa(arm)) + floatingBounds(*branch->getCond(), *variable, cast.getType(), context, + lower, upper, remaining, false, excludesNan); + } + if (const auto *branch = dyn_cast(parent); + branch && (branch->getThen() == child || branch->getElse() == child) && + unchangedFloatingBranch(*child, *variable)) + floatingBounds(*branch->getCond(), *variable, cast.getType(), context, + lower, upper, remaining, branch->getThen() == child, + excludesNan); + child = parent; + } + return lower && upper && remaining != 0; +} +} // namespace weavec::analysis diff --git a/lib/Analysis/FloatingCastSupport.h b/lib/Analysis/FloatingCastSupport.h new file mode 100644 index 00000000..c9ebf57f --- /dev/null +++ b/lib/Analysis/FloatingCastSupport.h @@ -0,0 +1,25 @@ +//===- FloatingCastSupport.h - Target conversion proof (RFC 0029) -*- C++ +//-*-===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#ifndef WEAVEC_ANALYSIS_FLOATINGCASTSUPPORT_H +#define WEAVEC_ANALYSIS_FLOATINGCASTSUPPORT_H + +namespace clang { +class ASTContext; +class CastExpr; +class FunctionDecl; +} // namespace clang + +namespace weavec::analysis { +/// A local sufficient proof, never a floating arithmetic or return summary. +[[nodiscard]] bool finiteFloatingCast(const clang::CastExpr &cast, + const clang::FunctionDecl &function, + clang::ASTContext &context, + bool excludesNan = false); +} // namespace weavec::analysis +#endif diff --git a/lib/Analysis/FunctionAnalysis.cpp b/lib/Analysis/FunctionAnalysis.cpp index cacbdc8e..31b7c370 100644 --- a/lib/Analysis/FunctionAnalysis.cpp +++ b/lib/Analysis/FunctionAnalysis.cpp @@ -54,7 +54,7 @@ bool FunctionAnalyzer::analyze(const FunctionDecl &function, if (!options.checkContracts) return summaries.setInferred(function, std::move(dataflow).summary(), widenSummary); - const bool verifiedInduction = dataflow.verifiedRecursiveCleanup(); + const bool verifiedInduction = dataflow.verifiedRecursiveContracts(); auto summary = std::move(dataflow).summary(); if (options.checkContracts) for (const auto &diagnostic : validation.diagnostics()) diff --git a/lib/Analysis/InterfaceTypes.cpp b/lib/Analysis/InterfaceTypes.cpp index dfb0583a..83d92834 100644 --- a/lib/Analysis/InterfaceTypes.cpp +++ b/lib/Analysis/InterfaceTypes.cpp @@ -132,6 +132,9 @@ describeInterfaceType(QualType root, const ASTContext &context) { valid = false; } else { node.name = record->getNameAsString(); + if (node.name.empty()) + if (const auto *alias = record->getTypedefNameForAnonDecl()) + node.typedefName = alias->getNameAsString(); if (record->isCompleteDefinition()) { node.view = recordLayoutKey(type, context); const auto &layout = context.getASTRecordLayout(record); @@ -176,6 +179,15 @@ QualType materializeInterfaceType(const core::InterfaceType &description, context, TagTypeKind::Struct, context.getTranslationUnitDecl(), {}, {}, node.name.empty() ? nullptr : &context.Idents.get(node.name)); records[i]->setImplicit(); + if (!node.typedefName.empty()) { + auto *alias = TypedefDecl::Create( + context, context.getTranslationUnitDecl(), {}, {}, + &context.Idents.get(node.typedefName), + context.getTrivialTypeSourceInfo( + context.getCanonicalTypeDeclType(records[i]))); + alias->setImplicit(); + records[i]->setTypedefNameForAnonDecl(alias); + } Qualifiers qualifiers; if (node.qualifiers & 1U) qualifiers.addConst(); diff --git a/lib/Analysis/PlaceBuilder.cpp b/lib/Analysis/PlaceBuilder.cpp index d47132c3..de71e682 100644 --- a/lib/Analysis/PlaceBuilder.cpp +++ b/lib/Analysis/PlaceBuilder.cpp @@ -366,7 +366,10 @@ PlaceBuilder::resolveSummaryPath(const core::SummaryPath &path, // A scalar pointee contract applied to a decayed array names element // zero. Explicit selected paths and range roots already name storage. - if (!arrayStorage && firstStep == 1 && argExpr && selectArray && + if ((!arrayStorage || + (firstStep < path.steps.size() && + path.steps[firstStep].step == core::PathStep::Field)) && + firstStep == 1 && argExpr && selectArray && (firstStep == path.steps.size() || path.steps[firstStep].step != core::PathStep::Index)) { const auto *array = @@ -1226,6 +1229,10 @@ std::optional PlaceBuilder::resolve(const Expr &expr) { return std::nullopt; ref->place = places.index(ref->place); setWitness(*ref, core::ElementWitness::ofConstant(0)); + if (selectArray) + *ref = selectArray( + *ref, core::Affine::ofConstant(0), + base.getType()->getAsArrayTypeUnsafe()->getElementType(), e); ref->place = fieldPlace(ref->place, field); return ref; } @@ -1970,6 +1977,8 @@ PlaceBuilder::affineFromPath(const core::PathAffine &affine, if (affine.path->index >= call.getNumArgs()) return std::nullopt; base = affineOf(*call.getArg(affine.path->index)); + if (!base && expressionFromPath) + return expressionFromPath(affine, call); } else if (const auto ref = resolveSummaryPath(*affine.path, call)) { base = core::Affine::ofPlace(ref->place); } diff --git a/lib/Analysis/RecursiveContracts.cpp b/lib/Analysis/RecursiveContracts.cpp new file mode 100644 index 00000000..64aaa748 --- /dev/null +++ b/lib/Analysis/RecursiveContracts.cpp @@ -0,0 +1,312 @@ +//===- RecursiveContracts.cpp - Atomic recursive proofs (RFC 0029) ------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "Dataflow.h" +#include "weavec/Analysis/TranslationUnitAnalysis.h" +#include "weavec/Core/Induction.h" + +#include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/raw_ostream.h" + +#include + +using namespace clang; +namespace weavec::analysis { + +static std::pair +copiedReaderFields(const RecordDecl &record, + const std::vector &members) { + std::set data; + std::set counts; + const auto memberField = [&](const Expr *expression) { + const auto *member = + expression ? dyn_cast(expression->IgnoreParenImpCasts()) + : nullptr; + const auto *field = + member ? dyn_cast(member->getMemberDecl()) : nullptr; + return field && field->getParent() == &record ? field : nullptr; + }; + for (const auto *member : members) { + std::vector work{member->getBody()}; + for (std::size_t i = 0; i < work.size(); ++i) { + if (work.size() > 65536 || data.size() > 16 || counts.size() > 16) + return {}; + const auto *statement = work[i]; + if (!statement) + continue; + const Expr *read = nullptr; + const Expr *decrement = nullptr; + if (const auto *index = dyn_cast(statement)) + read = index->getBase(); + if (const auto *unary = dyn_cast(statement)) { + if (unary->getOpcode() == UO_Deref) + read = unary->getSubExpr(); + if (unary->isDecrementOp()) + decrement = unary->getSubExpr(); + } + if (const auto *binary = dyn_cast(statement); + binary && binary->getOpcode() == BO_SubAssign) + decrement = binary->getLHS(); + if (const auto *field = memberField(read); + field && !field->isBitField() && field->getType()->isPointerType() && + !field->getType().isVolatileQualified() && + field->getType()->getPointeeType().isConstQualified() && + !field->getType()->getPointeeType().isVolatileQualified() && + field->getType()->getPointeeType()->isCharType()) + data.insert(field); + if (const auto *field = memberField(decrement); + field && !field->isBitField() && + field->getType()->isUnsignedIntegerType() && + !field->getType()->isBooleanType() && + !field->getType().isVolatileQualified() && + !field->getType()->isAtomicType()) + counts.insert(field); + for (const auto *child : statement->children()) { + if (work.size() >= 65536) + return {}; + work.push_back(child); + } + } + } + return data.size() == 1 && counts.size() == 1 + ? std::pair{*data.begin(), *counts.begin()} + : std::pair{}; +} + +bool TranslationUnitAnalyzer::verifyRecursiveContractGroup( + const std::vector &component) { + if (component.empty() || component.size() > 32 || + !store.activeRecursiveContracts.members.empty()) + return false; + std::map members; + QualType pointerType; + const auto *first = definitions[component.front()]; + const bool extendsHead = + first->getNumParams() == 2 && first->getReturnType()->isIntegerType() && + first->getParamDecl(0)->getType()->isPointerType() && + first->getParamDecl(0)->getType()->getPointeeType()->isRecordType() && + first->getParamDecl(1)->getType()->isUnsignedIntegerType() && + !first->getParamDecl(1)->getType()->isBooleanType(); + const bool mutableReader = first->getNumParams() == 1 && + first->getParamDecl(0)->getType()->isPointerType(); + QualType readerType; + if (first->getNumParams() == 1) + readerType = mutableReader + ? first->getParamDecl(0)->getType()->getPointeeType() + : first->getParamDecl(0)->getType(); + const auto *reader = + readerType.isNull() ? nullptr : readerType->getAsRecordDecl(); + const bool copiedReader = + reader != nullptr && !reader->isUnion() && + first->getReturnType()->isPointerType() && + first->getReturnType()->getPointeeType()->getAsRecordDecl() != nullptr; + const bool outputSlot = + first->getNumParams() == 3 && first->getReturnType()->isIntegerType() && + first->getParamDecl(2)->getType()->isPointerType() && + first->getParamDecl(2)->getType()->getPointeeType()->isPointerType(); + const bool writes = + !outputSlot && first->getNumParams() == 3 && + first->getReturnType()->isIntegerType() && + first->getParamDecl(2)->getType()->isPointerType() && + first->getParamDecl(2)->getType()->getPointeeType()->getAsRecordDecl() != + nullptr; + const bool constructs = + extendsHead || outputSlot || copiedReader || + (first->getNumParams() == 2 && first->getReturnType()->isPointerType() && + first->getReturnType()->getPointeeType()->getAsRecordDecl() != nullptr); + for (const unsigned index : component) { + const auto *definition = definitions[index]; + if (extendsHead || copiedReader) { + if (!ASTContext::hasSameType(first->getType(), definition->getType())) + return false; + } else if (constructs || writes) { + if (!ASTContext::hasSameType(first->getType(), definition->getType()) || + !definition->getParamDecl(0)->getType()->isPointerType() || + !definition->getParamDecl(0) + ->getType() + ->getPointeeType() + ->isCharType() || + !definition->getParamDecl(0) + ->getType() + ->getPointeeType() + .isConstQualified() || + !definition->getParamDecl(1)->getType()->isUnsignedIntegerType() || + definition->getParamDecl(1)->getType()->isBooleanType()) + return false; + } else if (definition->getNumParams() != 1 || + !(definition->getReturnType()->isVoidType() || + definition->getReturnType()->isIntegerType())) { + return false; + } + auto type = constructs ? definition->getReturnType() + : definition->getParamDecl(0)->getType(); + if (extendsHead) + type = definition->getParamDecl(0)->getType(); + if (writes) + type = definition->getParamDecl(2)->getType(); + if (outputSlot) + type = definition->getParamDecl(2)->getType()->getPointeeType(); + if (!type->isPointerType() || !type->getPointeeType()->getAsRecordDecl()) + return false; + if (pointerType.isNull()) + pointerType = type; + else if (!ASTContext::hasSameType(pointerType, type)) + return false; + members.emplace(definition->getCanonicalDecl(), + static_cast(members.size())); + } + std::pair readerFields; + if (copiedReader) { + std::vector definitionsInGroup; + definitionsInGroup.reserve(component.size()); + for (const auto index : component) + definitionsInGroup.push_back(definitions[index]); + readerFields = copiedReaderFields(*reader, definitionsInGroup); + if (!readerFields.first || !readerFields.second) { + for (const auto &[member, memberIndex] : members) { + (void)memberIndex; + store.failedRecursiveOutputs.insert_or_assign(member, writes); + } + return false; + } + } + // Keep the group rule closed under effects. Construction may compose a + // completed helper: the normal call transfer checks its requirements and + // effects, and construction validation covers every resulting premise and + // output. An unavailable helper or private global cannot disappear behind + // the induction hypothesis. + bool releases = false; + for (const unsigned index : component) { + std::vector pending{definitions[index]->getBody()}; + for (std::size_t i = 0; i < pending.size(); ++i) { + if (pending.size() > 65536) + return false; + const auto *statement = pending[i]; + if (!statement) + continue; + if (const auto *reference = dyn_cast(statement)) + if (const auto *var = dyn_cast(reference->getDecl()); + var && var->hasGlobalStorage()) + return false; + if (const auto *call = dyn_cast(statement)) { + const auto *callee = call->getDirectCallee(); + releases |= callee != nullptr && callee->getName() == "free" && + !callee->hasBody(); + bool allowed = callee != nullptr && + (members.contains(callee->getCanonicalDecl()) || + (callee->getName() == "free" && !callee->hasBody() && + call->getNumArgs() == 1) || + (constructs && !callee->hasBody() && + (callee->getName() == "malloc" || + callee->getName() == "calloc"))); + if (!allowed && (constructs || writes) && callee) + if (const auto helper = store.lookup(*callee); + helper && helper->summary->checked.complete()) + allowed = true; + if (!allowed) + return false; + } + for (const auto *child : statement->children()) + pending.push_back(child); + } + } + if (!constructs && releases && + std::ranges::any_of(component, [&](unsigned index) { + return !definitions[index]->getReturnType()->isVoidType(); + })) + return false; + store.activeRecursiveContracts.releases = releases && !constructs; + store.activeRecursiveContracts.constructs = constructs; + store.activeRecursiveContracts.extendsHead = extendsHead; + store.activeRecursiveContracts.writes = writes; + store.activeRecursiveContracts.mutableReader = copiedReader && mutableReader; + store.activeRecursiveContracts.readerData = readerFields.first; + store.activeRecursiveContracts.readerCount = readerFields.second; + for (const auto &[function, index] : members) { + (void)index; + store.activeRecursiveContracts.members.insert(function); + } + const auto clear = + llvm::scope_exit([&] { store.activeRecursiveContracts = {}; }); + std::map candidates; + std::vector edges; + for (const unsigned index : component) { + const auto &definition = *definitions[index]; + core::DiagnosticCollector collected; + FunctionAnalyzer validator(context, collected, options); + validator.validate(definition); + if (std::ranges::any_of(collected.diagnostics(), [](const auto &entry) { + return entry.severity == core::Severity::Error || + entry.id == core::diag::InvalidAnnotation; + })) + return false; + FunctionDataflow analysis(context, definition, collected, options, store, + false); + analysis.run(); + if (!analysis.verifiedRecursiveContracts()) { + if ((constructs || writes) && !analysis.recursiveContractCalls.empty()) + for (const auto &[member, memberIndex] : members) { + (void)memberIndex; + store.failedRecursiveOutputs.insert_or_assign(member, writes); + } + if (options.dumpStream) { + *options.dumpStream << " rejected recursive contract for " + << definition.getNameAsString() << ":\n"; + for (const auto &[identity, obligation] : + analysis.summary().checked.obligations.entries()) { + (void)identity; + if (obligation.outcome >= core::SafetyOutcome::Unresolved) + *options.dumpStream << " " << obligation.reason << "\n"; + } + } + return false; + } + for (const auto &[callee, strict] : analysis.recursiveContractCalls) { + const auto target = members.find(callee); + if (target == members.end()) + return false; + edges.push_back({.caller = members.at(definition.getCanonicalDecl()), + .callee = target->second, + .strict = strict}); + } + auto summary = std::move(analysis).summary(); + if (std::ranges::any_of(summary.effects, [](const auto &entry) { + return entry.first.isGlobal(); + })) + return false; + candidates.emplace(definition.getCanonicalDecl(), std::move(summary)); + } + if (!core::validInductionProgress(static_cast(members.size()), + edges)) { + for (const auto &[function, index] : members) { + (void)index; + store.failedRecursiveProgress.insert(function); + } + return false; + } + // No candidate has been published or used as a completed summary. All + // members proved their local operations and complete outputs; publication + // below performs no intervening analysis or callback into another member. + for (auto &[function, summary] : candidates) { + store.failedRecursiveOutputs.erase(function); + store.verifiedRecursiveContracts[function] = store.activeRecursiveContracts; + store.setInferred(*function, std::move(summary), false, true); + } + if (options.stats) { + const char *counter = "recursive_traversal_groups_verified"; + if (writes) + counter = "recursive_writer_groups_verified"; + else if (constructs) + counter = "recursive_construction_groups_verified"; + else if (releases) + counter = "recursive_cleanup_groups_verified"; + options.stats->add(counter); + } + return true; +} + +} // namespace weavec::analysis diff --git a/lib/Analysis/RuntimeModels.cpp b/lib/Analysis/RuntimeModels.cpp index cde002e0..3fa2c95a 100644 --- a/lib/Analysis/RuntimeModels.cpp +++ b/lib/Analysis/RuntimeModels.cpp @@ -12,6 +12,18 @@ #include namespace weavec::analysis { static constexpr auto Models = std::to_array({ + {.name = "strtod", + .family = RuntimeFamily::ParseNumeric, + .parameters = "te", + .result = 'r'}, + {.name = "strtof", + .family = RuntimeFamily::ParseNumeric, + .parameters = "te", + .result = 'r'}, + {.name = "strtold", + .family = RuntimeFamily::ParseNumeric, + .parameters = "te", + .result = 'r'}, {.name = "fabs", .family = RuntimeFamily::Numeric, .parameters = "r", @@ -220,6 +232,14 @@ bool runtimeSignature(const RuntimeModel &model, const clang::CallExpr &call, return false; const auto matches = [&](char role, clang::QualType type) { switch (role) { + case 't': + return type->isPointerType() && + ASTContext::hasSameType(type->getPointeeType(), + context.CharTy.withConst()); + case 'e': + return type->isPointerType() && + ASTContext::hasSameType(type->getPointeeType(), + context.getPointerType(context.CharTy)); case 's': return type->isPointerType() && type->getPointeeType()->isCharType(); case 'p': @@ -236,7 +256,8 @@ bool runtimeSignature(const RuntimeModel &model, const clang::CallExpr &call, return ASTContext::hasSameUnqualifiedType(type, context.IntTy); case 'r': { const bool base = model.name == "fabs" || model.name == "floor" || - model.name == "ceil" || model.name == "trunc"; + model.name == "ceil" || model.name == "trunc" || + model.name == "strtod"; auto expected = context.DoubleTy; if (!base) expected = diff --git a/lib/Analysis/RuntimeModels.h b/lib/Analysis/RuntimeModels.h index 0f265362..89292814 100644 --- a/lib/Analysis/RuntimeModels.h +++ b/lib/Analysis/RuntimeModels.h @@ -14,6 +14,7 @@ namespace weavec::analysis { enum class RuntimeFamily : std::uint8_t { Numeric, + ParseNumeric, Compare, Search, Span, @@ -27,7 +28,7 @@ struct RuntimeModel { std::string_view name; RuntimeFamily family; // s: narrow string, p: memory, f: stream, z: size_t, i: int, d: fd, - // r: real scalar, a: va_list. Result: + // r: real scalar, a: va_list, t: const char input, e: char ** output. Result: // i:int,z:size_t,n:ssize_t,p:pointer,r:real. std::string_view parameters; char result; diff --git a/lib/Analysis/Summaries.cpp b/lib/Analysis/Summaries.cpp index 062b9ca1..7cb80744 100644 --- a/lib/Analysis/Summaries.cpp +++ b/lib/Analysis/Summaries.cpp @@ -126,9 +126,17 @@ SummaryStore::recursiveLinks(const RecordDecl &record) { const auto *stmt = work[i]; if (!stmt) continue; + // Only actual recursive peers nominate a mutual recursive edge. + // An unrelated child destructor can otherwise hide the full topology + // supplied by an imported contract in a separate-source client. if (const auto *call = dyn_cast(stmt)) if (const auto *callee = call->getDirectCallee(); - callee && callee->getCanonicalDecl() == fn->getCanonicalDecl()) + callee && + (callee->getCanonicalDecl() == fn->getCanonicalDecl() || + (recursiveComponents.contains(fn->getCanonicalDecl()) && + recursiveComponents.contains(callee->getCanonicalDecl()) && + recursiveComponents.at(fn->getCanonicalDecl()) == + recursiveComponents.at(callee->getCanonicalDecl())))) for (const auto *argument : call->arguments()) if (const auto *field = fieldOf(argument)) children.insert(field); @@ -161,6 +169,99 @@ SummaryStore::recursiveLinks(const RecordDecl &record) { return result.empty() ? importedLinks() : result; } +SummaryStore::ContainerFields +SummaryStore::containerFields(const RecordDecl &record) { + if (!context) + return {}; + const auto generation = database ? database->importGeneration() : nullptr; + if (containerPayloadGeneration != generation) { + containerPayloadCache.clear(); + containerPayloadGeneration = generation; + } + if (const auto found = containerPayloadCache.find(&record); + found != containerPayloadCache.end()) { + inheritDependencies(found->second.dependencies); + return found->second.fields; + } + std::set fields; + std::map ownership; + std::set conflicting; + Dependencies dependencies; + beginDependencies(dependencies); + for (const auto *decl : context->getTranslationUnitDecl()->decls()) { + const auto *fn = dyn_cast(decl); + if (!fn || std::ranges::none_of(fn->parameters(), [&](const auto *param) { + return param->getType()->isPointerType() && + param->getType()->getPointeeType()->getAsRecordDecl() == + &record; + })) + continue; + if (fn->doesThisDeclarationHaveABody()) { + std::vector work{fn->getBody()}; + for (std::size_t i = 0; i < work.size() && work.size() <= 65536; ++i) { + const auto *stmt = work[i]; + if (!stmt) + continue; + if (const auto *call = dyn_cast(stmt); + call && call->getNumArgs() == 1) + if (const auto *callee = call->getDirectCallee(); + callee && callee->getName() == "free" && !callee->hasBody()) + if (const auto *member = dyn_cast( + call->getArg(0)->IgnoreParenImpCasts())) + if (const auto *field = + dyn_cast(member->getMemberDecl()); + field && field->getParent() == &record && + field->getType()->isPointerType() && + field->getType()->getPointeeType()->getAsRecordDecl() != + &record) + fields.insert(field); + for (const auto *child : stmt->children()) + work.push_back(child); + } + continue; + } + if (fn->getDefinition()) + continue; + const auto imported = lookup(*fn); + if (!imported || imported->source != SummarySource::Program) + continue; + for (const auto &requirement : imported->summary->checked.requirements) { + if (requirement.kind != core::CheckedRequirementKind::Container) + continue; + const auto shape = core::ContainerShape::decode(requirement.family); + if (!shape) + continue; + for (const auto &[name, condition] : shape->ownership) { + const auto [found, inserted] = ownership.emplace(name, condition); + if (!inserted && found->second != condition) + conflicting.insert(name); + } + for (const auto &payload : shape->payloads) + if (payload.family == "free") + for (const auto *field : record.fields()) + if (field->getType()->isPointerType() && + field->getName() == payload.field.name && + field->getType()->getPointeeType()->getAsRecordDecl() != + &record) + fields.insert(field); + } + } + endDependencies(); + std::vector result(fields.begin(), fields.end()); + std::ranges::sort(result, {}, + [](const auto *field) { return field->getFieldIndex(); }); + for (const auto &name : conflicting) + ownership.erase(name); + ContainerFields candidates{.payloads = std::move(result), + .ownership = std::move(ownership)}; + if (containerPayloadCache.size() < 256) + containerPayloadCache.emplace( + &record, + ImportedContainerFields{.fields = candidates, + .dependencies = std::move(dependencies)}); + return candidates; +} + std::optional SummaryStore::bufferShape( const RecordDecl &record, const std::function()> &discover) { @@ -174,6 +275,21 @@ std::optional SummaryStore::bufferShape( return result; } +const SummaryStore::RecursiveContractGroup * +SummaryStore::recursiveContractGroup(const FunctionDecl &caller) const { + const auto *from = caller.getCanonicalDecl(); + if (activeRecursiveContracts.members.contains(from)) + return &activeRecursiveContracts; + const auto group = verifiedRecursiveContracts.find(from); + return group == verifiedRecursiveContracts.end() ? nullptr : &group->second; +} + +bool SummaryStore::recursiveContractPeer(const FunctionDecl &caller, + const FunctionDecl &callee) const { + const auto *group = recursiveContractGroup(caller); + return group != nullptr && group->members.contains(callee.getCanonicalDecl()); +} + std::map SummaryStore::containerOwnership( const RecordDecl &record, @@ -905,6 +1021,21 @@ bool SummaryStore::setInferred(const FunctionDecl &function, // Replacement can cycle as numeric and temporal guards project together. auto joined = *previous->second; joined.join(summary); + if (refreshingRecursiveValueOutcomes) { + // Earlier SCC approximations are not additional returning executions. + // This body was rechecked against settled conservative effects. Keep + // only its actual value guarantees; checked memory outputs and + // every may-effect still use their existing widening (RFC 0029). + if (!joined.outcomes.empty()) { + joined.nullOn = summary.nullOn; + joined.nonNullOn = summary.nonNullOn; + } + const auto result = core::SummaryPath::result(); + joined.numericOutputs.erase(result); + if (const auto values = summary.numericOutputs.find(result); + values != summary.numericOutputs.end()) + joined.numericOutputs.emplace(result, values->second); + } // RFC 0027: these outputs were verified against private proper-child // induction hypotheses. They do not come from the optimistic SCC seed. // Continue widening ordinary may-effects and retaining every obligation. diff --git a/lib/Analysis/TranslationUnitAnalysis.cpp b/lib/Analysis/TranslationUnitAnalysis.cpp index 41b5e8c8..cd840479 100644 --- a/lib/Analysis/TranslationUnitAnalysis.cpp +++ b/lib/Analysis/TranslationUnitAnalysis.cpp @@ -19,6 +19,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/Support/FormatVariadic.h" #include @@ -419,6 +420,17 @@ void TranslationUnitAnalyzer::run( const std::vector> adjacency = buildCallGraph(); const std::vector> components = core::stronglyConnectedComponents(adjacency); + if (options.checkContracts) + for (unsigned index = 0; index < components.size(); ++index) + for (const auto member : components[index]) { + store.recursiveComponents[definitions[member]->getCanonicalDecl()] = + index; + if (components[index].size() > 1 || + std::ranges::find(adjacency[member], member) != + adjacency[member].end()) + store.recursiveFunctions.insert( + definitions[member]->getCanonicalDecl()); + } // RFC 0012, *Sized fields*, "Two passes in a unit": the first pass takes // inferred sized fields from the program database only; every report is @@ -714,6 +726,15 @@ void TranslationUnitAnalyzer::analyzeComponent( if (recursive) { for (const unsigned member : component) recursiveFunctions.insert(definitions[member]->getCanonicalDecl()); + } + const bool verifiedGroup = recursive && options.checkContracts && + verifyRecursiveContractGroup(component); + const bool previousApproximation = store.checkingRecursiveApproximation; + store.checkingRecursiveApproximation = recursive && !verifiedGroup; + const auto restoreApproximation = llvm::scope_exit( + [&] { store.checkingRecursiveApproximation = previousApproximation; }); + bool settled = false; + if (recursive && !verifiedGroup) { // Start every member at the bottom summary and iterate silently until // nothing changes; the final, reporting run then sees the fixpoint. for (const unsigned member : component) { @@ -736,17 +757,26 @@ void TranslationUnitAnalyzer::analyzeComponent( changed = analyzeSilently(*definitions[member], analyzer, true) || changed; } - if (!changed) + if (!changed) { + settled = true; break; + } if (round + 1 == MaxFixpointRounds) for (const unsigned member : component) store.markIncomplete(*definitions[member]); } } + const bool previousRefresh = store.refreshingRecursiveValueOutcomes; + store.refreshingRecursiveValueOutcomes = + recursive && settled && !verifiedGroup; + const auto restoreRefresh = llvm::scope_exit( + [&] { store.refreshingRecursiveValueOutcomes = previousRefresh; }); for (const unsigned member : component) { const FunctionDecl &function = *definitions[member]; const bool report = shouldReport(function); + if (store.refreshingRecursiveValueOutcomes) + silentAnalyses.erase(function.getCanonicalDecl()); if (report) analyzer.analyze(function, store, true, /*widenSummary=*/recursive); else diff --git a/lib/Core/AnalysisState.cpp b/lib/Core/AnalysisState.cpp index a902f4cd..35c6aa05 100644 --- a/lib/Core/AnalysisState.cpp +++ b/lib/Core/AnalysisState.cpp @@ -725,7 +725,8 @@ void AnalysisState::forgetZeroedMemory() { for (auto &[cls, facts] : outcome.initializedOn) { (void)cls; std::erase_if(facts, [](const auto &fact) { - return fact.second.zeroed || fact.second.terminatedWithin; + return fact.second.zeroed || fact.second.numericText || + fact.second.terminatedWithin; }); } } diff --git a/lib/Core/Buffer.cpp b/lib/Core/Buffer.cpp index d09fbf83..89ebd0e7 100644 --- a/lib/Core/Buffer.cpp +++ b/lib/Core/Buffer.cpp @@ -16,6 +16,7 @@ bool BufferShape::valid() const { if (!object.valid() || elementBytes == 0 || elementBytes > static_cast(INT64_MAX) || (terminated && (elementBytes != 1 || pointerElements)) || + (reader && (elementBytes != 1 || pointerElements)) || (ownsElements && !pointerElements)) return false; const auto validField = [&](const ContainerField &field) { @@ -62,7 +63,8 @@ std::string BufferShape::encode() const { const auto field = [](const std::string &name) { return std::to_string(name.size()) + ':' + name; }; - return "buffer1:" + std::to_string(elementBytes) + ':' + + return std::string(reader ? "reader1:" : "buffer1:") + + std::to_string(elementBytes) + ':' + std::to_string(static_cast(pointerElements)) + ':' + std::to_string(static_cast(terminated)) + ':' + std::to_string(static_cast(ownsBacking)) + ':' + @@ -70,7 +72,8 @@ std::string BufferShape::encode() const { field(length.name) + field(capacity.name) + carrier.encode(); } std::optional BufferShape::decode(std::string_view text) { - if (!text.starts_with("buffer1:") || text.size() > 16384) + if ((!text.starts_with("buffer1:") && !text.starts_with("reader1:")) || + text.size() > 16384) return std::nullopt; const auto original = text; text.remove_prefix(8); @@ -96,6 +99,7 @@ std::optional BufferShape::decode(std::string_view text) { return true; }; BufferShape result; + result.reader = original.starts_with("reader1:"); std::uint64_t pointers = 0; std::uint64_t terminated = 0; std::uint64_t backing = 0; diff --git a/lib/Core/CMakeLists.txt b/lib/Core/CMakeLists.txt index 508069c6..eff1e667 100644 --- a/lib/Core/CMakeLists.txt +++ b/lib/Core/CMakeLists.txt @@ -16,6 +16,7 @@ weavec_add_library( Format.cpp Diagnostic.cpp Integer.cpp + Induction.cpp Interface.cpp CheckedInteger.cpp Lifetime.cpp diff --git a/lib/Core/CallContext.cpp b/lib/Core/CallContext.cpp index 2f0a4e3a..c3353925 100644 --- a/lib/Core/CallContext.cpp +++ b/lib/Core/CallContext.cpp @@ -13,6 +13,7 @@ #include #include +#include namespace weavec::core { @@ -54,7 +55,8 @@ bool CallContext::addAlias(ContextAlias alias) { for (const auto &existing : aliases) if (existing.first == alias.first && existing.second == alias.second) return existing == alias; - if (aliases.size() + facts.size() + separations.size() + orders.size() >= + if (aliases.size() + facts.size() + separations.size() + orders.size() + + nonNan.size() >= MaxCallContextFacts) return false; const auto [it, inserted] = aliases.insert(std::move(alias)); @@ -66,8 +68,19 @@ bool CallContext::addAlias(ContextAlias alias) { } bool CallContext::valid() const { + for (const auto &path : immutableBytes) + if (!bytes.contains(path)) + return false; + std::size_t byteCount = 0; + for (const auto &[path, value] : bytes) { + if (!validContextPath(path) || value.empty() || + value.size() > MaxCallContextFacts - byteCount) + return false; + byteCount += value.size(); + } if (empty() || - aliases.size() + facts.size() + separations.size() + orders.size() > + aliases.size() + facts.size() + separations.size() + orders.size() + + byteCount + immutableBytes.size() + nonNan.size() > MaxCallContextFacts || callbacks.size() > MaxCallbackContexts) return false; @@ -163,6 +176,34 @@ bool CallContext::valid() const { fact.disjointFrom(otherFact)) return false; } + for (const auto &[path, value] : bytes) { + (void)value; + paths.insert(path); + if (const auto fact = facts.find(path); + fact != facts.end() && (!fact->second.isPointer() || + fact->second.classes.contains(Outcome::Null))) + return false; + for (const auto &[other, contents] : bytes) { + if (!(path < other)) + continue; + const auto offset = definite.offsetOf(idOf(other), idOf(path)); + if (!offset || (!offset->isZero() && !offset->isElements())) + continue; + for (std::size_t i = 0; i < value.size(); ++i) { + std::int64_t index = 0; + if (!__builtin_add_overflow(static_cast(i), + offset->elements, &index) && + index >= 0 && std::cmp_less(index, contents.size()) && + value[i] != contents[static_cast(index)]) + return false; + } + } + } + for (const auto &path : nonNan) + if (!validContextPath(path) || !path.isParam() || !path.isRoot() || + facts.contains(path) || paths.contains(path) || + callbacks.contains(path)) + return false; return paths.size() <= MaxCallContextPaths; } @@ -230,6 +271,21 @@ std::optional remapCallContext(const CallContext &context, if (!mapped || !result.facts.emplace(*mapped, fact).second) return std::nullopt; } + for (const auto &[path, value] : context.bytes) { + const auto mapped = pathOf(path); + if (!mapped || !result.bytes.emplace(*mapped, value).second) + return std::nullopt; + } + for (const auto &path : context.immutableBytes) { + const auto mapped = pathOf(path); + if (!mapped || !result.immutableBytes.insert(*mapped).second) + return std::nullopt; + } + for (const auto &path : context.nonNan) { + const auto mapped = pathOf(path); + if (!mapped || !result.nonNan.insert(*mapped).second) + return std::nullopt; + } for (const auto &[a, b] : context.orders) { const auto first = pathOf(a); const auto second = pathOf(b); @@ -367,7 +423,8 @@ static std::string encodeContextText(std::string_view text) { return encoded; } -static std::optional decodeContextText(std::string_view text) { +static std::optional decodeContextText(std::string_view text, + bool binary = false) { if (text.empty() || text.size() % 2 != 0 || text.size() > 32768) return std::nullopt; const auto digit = [](char c) -> int { @@ -382,7 +439,7 @@ static std::optional decodeContextText(std::string_view text) { if (hi < 0 || lo < 0) return std::nullopt; const char c = static_cast((hi * 16) + lo); - if (c == '\0' || c == '\n' || c == '\r') + if (!binary && (c == '\0' || c == '\n' || c == '\r')) return std::nullopt; result += c; } @@ -414,6 +471,12 @@ std::string printCallContext(const CallContext &context, append("o:" + path(a) + ':' + path(b)); for (const auto &[p, fact] : context.facts) append("v:" + path(p) + ':' + encodeContextText(fact.toString())); + for (const auto &[p, value] : context.bytes) + append("b:" + path(p) + ':' + encodeContextText(value)); + for (const auto &p : context.immutableBytes) + append("k:" + path(p)); + for (const auto &p : context.nonNan) + append("n:" + path(p)); return result; } @@ -489,6 +552,24 @@ std::optional parseCallContext(std::string_view text, const auto fact = decoded ? ValueFact::parse(*decoded) : std::nullopt; if (!path || !fact || !result.facts.emplace(*path, *fact).second) return std::nullopt; + } else if (fields[0] == "b" && fields.size() == 3) { + if (fields[2].size() > 2 * MaxCallContextFacts) + return std::nullopt; + const auto path = pathOf(fields[1]); + const auto value = decodeContextText(fields[2], true); + if (!path || !value || value->empty() || + value->size() > MaxCallContextFacts || + encodeContextText(*value) != fields[2] || + !result.bytes.emplace(*path, *value).second) + return std::nullopt; + } else if (fields[0] == "k" && fields.size() == 2) { + const auto path = pathOf(fields[1]); + if (!path || !result.immutableBytes.insert(*path).second) + return std::nullopt; + } else if (fields[0] == "n" && fields.size() == 2) { + const auto path = pathOf(fields[1]); + if (!path || !result.nonNan.insert(*path).second) + return std::nullopt; } else { return std::nullopt; } diff --git a/lib/Core/CheckedContract.cpp b/lib/Core/CheckedContract.cpp index 7b9244e5..18ea1d67 100644 --- a/lib/Core/CheckedContract.cpp +++ b/lib/Core/CheckedContract.cpp @@ -16,12 +16,33 @@ namespace weavec::core { std::optional joinContainerOutput(const CheckedRequirement &first, const CheckedRequirement &second) { - if (first.kind != CheckedRequirementKind::ContainerDerived || - second.kind != CheckedRequirementKind::ContainerDerived || - first.path != second.path || first.family != second.family || + const bool derived = first.kind == CheckedRequirementKind::ContainerDerived; + if ((!derived && first.kind != CheckedRequirementKind::Container && + first.kind != CheckedRequirementKind::ContainerFresh && + first.kind != CheckedRequirementKind::ContainerExtended) || + second.kind != first.kind || first.path != second.path || first.on != second.on || first.when != second.when || first.ifNonNull || - second.ifNonNull) + second.ifNonNull || + (!derived && (first.other != second.other || + first.begin != second.begin || first.end != second.end))) return std::nullopt; + const auto a = ContainerShape::decode(first.family); + const auto b = ContainerShape::decode(second.family); + if (!a || !b) + return std::nullopt; + ContainerFacts left; + ContainerFacts right; + const PlaceId holder{0}; + left.set(holder, {.shape = *a, .members = {}, .inputs = {}}); + right.set(holder, {.shape = *b, .members = {}, .inputs = {}}); + left.join(right); + const auto *common = left.find(holder); + if (!common) + return std::nullopt; + auto result = first; + result.family = common->shape.encode(); + if (!derived) + return result; std::set sources{first.other, second.other}; for (const auto *post : {&first, &second}) { if (post->begin.path) @@ -29,7 +50,6 @@ joinContainerOutput(const CheckedRequirement &first, if (post->end.path) sources.insert(*post->end.path); } - auto result = first; result.begin = result.end = {}; result.other = {}; if (sources.size() > 3) { @@ -90,7 +110,10 @@ void CheckedRequirements::intersect(const CheckedRequirements &other) { } Set generalized; for (const auto &first : entries()) - if (first.kind == CheckedRequirementKind::ContainerDerived) + if (first.kind == CheckedRequirementKind::ContainerDerived || + first.kind == CheckedRequirementKind::ContainerExtended || + first.kind == CheckedRequirementKind::Container || + first.kind == CheckedRequirementKind::ContainerFresh) for (const auto &second : other.entries()) if (const auto joined = joinContainerOutput(first, second)) generalized.insert(*joined); @@ -105,7 +128,7 @@ void CheckedRequirements::intersect(const CheckedRequirements &other) { insert(entry); } -static constexpr std::array Kinds{ +static constexpr std::array Kinds{ "valid", "extent", "initialized", @@ -137,7 +160,13 @@ static constexpr std::array Kinds{ "container-consumed", "container-partition", "container-combined", - "allocation-consumed"}; + "allocation-consumed", + "callback-allocate", + "callback-release", + "initialized-span", + "count-within-span", + "initialized-advance", + "container-extended"}; std::string_view toString(CheckedRequirementKind value) noexcept { const auto index = static_cast(value); @@ -171,10 +200,19 @@ void CheckedContract::establish(CheckedRequirement requirement) { } bool CheckedContract::hasContainerOutputPremises( const CheckedRequirement &post) const { + if (post.kind == CheckedRequirementKind::ContainerExtended && + (post.on || !post.when.trivial() || post.ifNonNull || + post.path != post.other || !post.path.isParam() || !post.path.isRoot() || + std::ranges::none_of(requirements, [&](const auto &pre) { + return pre.kind == CheckedRequirementKind::Valid && + pre.path == post.other && pre.when.trivial(); + }))) + return false; if (post.kind == CheckedRequirementKind::ContainerDerived || post.kind == CheckedRequirementKind::ContainerTail || post.kind == CheckedRequirementKind::ContainerPreserved || - post.kind == CheckedRequirementKind::ContainerConsumed) { + post.kind == CheckedRequirementKind::ContainerConsumed || + post.kind == CheckedRequirementKind::ContainerExtended) { const auto shape = ContainerShape::decode(post.family); if (!shape) return false; @@ -188,6 +226,12 @@ bool CheckedContract::hasContainerOutputPremises( input->link == shape->link && input->children == shape->children && input->ownership == shape->ownership && + (post.kind != CheckedRequirementKind::ContainerExtended || + (input->singletonHead() && + input->access == ContainerAccess::Release && + shape->access == ContainerAccess::Release && + input->family == shape->family && + input->payloads == shape->payloads)) && ((post.kind != CheckedRequirementKind::ContainerConsumed && post.kind != CheckedRequirementKind::ContainerPreserved) || input->entails(*shape)); @@ -228,6 +272,29 @@ bool CheckedContract::hasContainerOutputPremises( if (!premise(post.other) || !separated(requirements, post.other, *post.begin.path, false)) return false; + // RFC 0029: a fresh region needs two owned inputs and a live head. + if (post.end == PathAffine::ofConstant(1)) { + const auto owned = [&](const SummaryPath &path) { + return std::ranges::any_of(requirements, [&](const auto &entry) { + if (entry.kind != CheckedRequirementKind::Container || + entry.path != path || !entry.when.trivial()) + return false; + const auto input = ContainerShape::decode(entry.family); + return input && input->access == ContainerAccess::Release && + input->family == "free"; + }); + }; + if (post.path != post.other || !post.path.isParam() || + !post.path.isRoot() || shape->access != ContainerAccess::Release || + !owned(post.other) || !owned(*post.begin.path) || + std::ranges::none_of(requirements, [&](const auto &pre) { + return pre.kind == CheckedRequirementKind::Valid && + pre.path == post.other && pre.when.trivial(); + })) + return false; + } else if (post.end != PathAffine::ofConstant(0)) { + return false; + } } else if (!separated(establishes, post.path, post.other, true)) { return false; } diff --git a/lib/Core/CheckedIO.cpp b/lib/Core/CheckedIO.cpp index e29e5b9b..9e60bfdd 100644 --- a/lib/Core/CheckedIO.cpp +++ b/lib/Core/CheckedIO.cpp @@ -261,8 +261,20 @@ class CheckedReader { result.other == SummaryPath{} && result.family == "free" && result.begin == PathAffine::ofConstant(0) && result.end == PathAffine::ofConstant(0); - if (result.kind == CheckedRequirementKind::TerminatedWithin) + if (result.kind == CheckedRequirementKind::CountWithinSpan) + valid &= result.path != result.other && result.family.empty() && + result.begin == PathAffine::ofConstant(0) && + result.end == PathAffine::ofConstant(0); + if (result.kind == CheckedRequirementKind::InitializedAdvance) valid &= result.family.empty() && + result.begin == PathAffine::ofConstant(0) && + result.end == PathAffine::ofConstant(0); + if (result.kind == CheckedRequirementKind::InitializedSpan) + valid &= result.path != result.other && result.family.empty() && + result.begin == PathAffine::ofConstant(0) && + result.end.isConstant() && result.end.constant > 0; + if (result.kind == CheckedRequirementKind::TerminatedWithin) + valid &= result.family.empty() && result.other == SummaryPath{} && (!result.begin.isConstant() || result.begin.constant >= 0) && (!result.begin.isConstant() || !result.end.isConstant() || result.begin.constant < result.end.constant); @@ -291,7 +303,8 @@ class CheckedReader { result.kind == CheckedRequirementKind::ContainerFresh || result.kind == CheckedRequirementKind::ContainerTail || result.kind == CheckedRequirementKind::ContainerPreserved || - result.kind == CheckedRequirementKind::ContainerConsumed) + result.kind == CheckedRequirementKind::ContainerConsumed || + result.kind == CheckedRequirementKind::ContainerExtended) valid &= ContainerShape::decode(result.family).has_value() && result.begin == PathAffine::ofConstant(0) && result.end == PathAffine::ofConstant(0); @@ -318,7 +331,11 @@ class CheckedReader { result.begin.scale == 1 && result.begin.constant == 0 && !result.begin.expression && result.begin.quantity == PathAffine{}.quantity && - result.end == PathAffine::ofConstant(0) && + (result.end == PathAffine::ofConstant(0) || + (result.kind == CheckedRequirementKind::ContainerCombined && + result.end == PathAffine::ofConstant(1) && + result.path == result.other && result.path.isParam() && + result.path.isRoot())) && (result.kind != CheckedRequirementKind::ContainerPartition || result.path != result.other) && (result.kind != CheckedRequirementKind::ContainerCombined || @@ -334,8 +351,18 @@ class CheckedReader { valid &= result.on.has_value(); } result.ifNonNull = flag(); + if (result.kind == CheckedRequirementKind::CallbackAllocate || + result.kind == CheckedRequirementKind::CallbackRelease) + valid &= result.path.isParam() && result.other == SummaryPath{} && + result.begin == PathAffine::ofConstant(0) && + result.end == PathAffine::ofConstant(0) && + result.family == "free" && !result.on && !result.ifNonNull; if (result.kind == CheckedRequirementKind::AllocationConsumed) valid &= result.when.trivial() && !result.ifNonNull; + if (result.kind == CheckedRequirementKind::ContainerExtended) + valid &= result.when.trivial() && !result.on && !result.ifNonNull && + result.path == result.other && result.path.isParam() && + result.path.isRoot(); if (result.kind == CheckedRequirementKind::Buffer || result.kind == CheckedRequirementKind::BufferPreserved || result.kind == CheckedRequirementKind::BufferAppended) @@ -348,7 +375,8 @@ class CheckedReader { result.kind == CheckedRequirementKind::ContainerFresh || result.kind == CheckedRequirementKind::ContainerTail || result.kind == CheckedRequirementKind::ContainerPreserved || - result.kind == CheckedRequirementKind::ContainerConsumed) + result.kind == CheckedRequirementKind::ContainerConsumed || + result.kind == CheckedRequirementKind::ContainerExtended) valid &= result.begin == result.end && !result.ifNonNull; if (result.kind == CheckedRequirementKind::ContainerDerived || result.kind == CheckedRequirementKind::ContainerPartition || @@ -401,7 +429,7 @@ class CheckedReader { std::string printCheckedContract(const CheckedContract &contract, const GlobalNamer &names) { CheckedWriter out; - out.text("9"); + out.text("12"); out.text(contract.signature); out.number(contract.computed); out.number(contract.selected); @@ -443,7 +471,7 @@ std::string printCheckedContract(const CheckedContract &contract, std::optional parseCheckedContract(std::string_view record, const GlobalResolver &resolve) { CheckedReader in(record); - if (in.text() != "9") + if (in.text() != "12") return std::nullopt; CheckedContract result; result.signature = in.text(); @@ -478,6 +506,8 @@ parseCheckedContract(std::string_view record, const GlobalResolver &resolve) { if (requirement.path.isResult() || requirement.other.isResult() || requirement.on || requirement.ifNonNull || hasResult(requirement.begin) || hasResult(requirement.end) || + requirement.kind == CheckedRequirementKind::CountWithinSpan || + requirement.kind == CheckedRequirementKind::InitializedAdvance || requirement.kind == CheckedRequirementKind::Copied || requirement.kind == CheckedRequirementKind::Zeroed || requirement.kind == CheckedRequirementKind::Position || @@ -491,6 +521,7 @@ parseCheckedContract(std::string_view record, const GlobalResolver &resolve) { requirement.kind == CheckedRequirementKind::ContainerConsumed || requirement.kind == CheckedRequirementKind::ContainerPartition || requirement.kind == CheckedRequirementKind::ContainerCombined || + requirement.kind == CheckedRequirementKind::ContainerExtended || requirement.kind == CheckedRequirementKind::AllocationConsumed) return std::nullopt; for (const auto &requirement : result.requirements) @@ -498,8 +529,7 @@ parseCheckedContract(std::string_view record, const GlobalResolver &resolve) { requirement.end != PathAffine::ofConstant(0)) return std::nullopt; for (const auto &requirement : result.requirements) - if (requirement.kind == CheckedRequirementKind::ArgumentListConsumed || - requirement.kind == CheckedRequirementKind::TerminatedWithin) + if (requirement.kind == CheckedRequirementKind::ArgumentListConsumed) return std::nullopt; for (const auto &requirement : result.requirements) if (requirement.kind == CheckedRequirementKind::Terminated && @@ -508,7 +538,30 @@ parseCheckedContract(std::string_view record, const GlobalResolver &resolve) { (requirement.begin.isConstant() && requirement.begin.constant < 0))) return std::nullopt; for (const auto &post : result.establishes) { - if (post.kind == CheckedRequirementKind::StandardStream || + if (post.kind == CheckedRequirementKind::InitializedAdvance && + (post.path.isResult() || post.other.isResult() || + (post.path.isParam() && post.path.isRoot()) || post.ifNonNull || + !post.when.trivial() || + std::ranges::none_of(result.establishes, [&](const auto &position) { + return position.kind == CheckedRequirementKind::Position && + position.path == post.path && position.other == post.other && + position.when.trivial() && + (!position.on || position.on == post.on); + }))) + return std::nullopt; + if (post.kind == CheckedRequirementKind::CountWithinSpan && + (post.path.isResult() || post.other.isResult() || post.on || + post.ifNonNull || !post.when.trivial() || + std::ranges::none_of(result.requirements, [&](const auto &pre) { + return pre.kind == CheckedRequirementKind::InitializedSpan && + pre.path == post.path && pre.other == post.other && + pre.when.trivial(); + }))) + return std::nullopt; + if (post.kind == CheckedRequirementKind::InitializedSpan || + post.kind == CheckedRequirementKind::StandardStream || + post.kind == CheckedRequirementKind::CallbackAllocate || + post.kind == CheckedRequirementKind::CallbackRelease || post.kind == CheckedRequirementKind::ArgumentList || post.kind == CheckedRequirementKind::FormatArguments || (post.kind == CheckedRequirementKind::ArgumentListConsumed && @@ -521,7 +574,8 @@ parseCheckedContract(std::string_view record, const GlobalResolver &resolve) { post.other.isResult()) return std::nullopt; if ((post.kind == CheckedRequirementKind::ContainerPreserved || - post.kind == CheckedRequirementKind::ContainerConsumed) && + post.kind == CheckedRequirementKind::ContainerConsumed || + post.kind == CheckedRequirementKind::ContainerExtended) && post.other.isResult()) return std::nullopt; if (post.kind == CheckedRequirementKind::ContainerConsumed && diff --git a/lib/Core/Container.cpp b/lib/Core/Container.cpp index c1f86b87..90392815 100644 --- a/lib/Core/Container.cpp +++ b/lib/Core/Container.cpp @@ -105,6 +105,19 @@ bool ContainerShape::recursiveLink(std::string_view name) const { children, [&](const auto &child) { return child.name == name; }); } +bool ContainerShape::singletonHead() const { + return valid() && + (terminal || + (emptyLinks.contains(link.name) && + std::ranges::all_of(children, + [&](const auto &child) { + return emptyLinks.contains(child.name); + }))) && + std::ranges::all_of(payloads, [&](const auto &payload) { + return emptyPayloads.contains(payload.field.name); + }); +} + bool ContainerShape::entails(const ContainerShape &required) const { return valid() && required.valid() && object == required.object && link == required.link && children == required.children && diff --git a/lib/Core/Induction.cpp b/lib/Core/Induction.cpp new file mode 100644 index 00000000..da9f180f --- /dev/null +++ b/lib/Core/Induction.cpp @@ -0,0 +1,39 @@ +//===- Induction.cpp - Recursive proof progress (RFC 0029) ---------------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "weavec/Core/Induction.h" + +#include + +namespace weavec::core { + +bool validInductionProgress(unsigned members, + std::span edges) { + if (members == 0 || members > 32 || edges.size() > 256) + return false; + std::vector incoming(members); + std::vector> next(members); + for (const auto &edge : edges) { + if (edge.caller >= members || edge.callee >= members) + return false; + if (!edge.strict) { + next[edge.caller].push_back(edge.callee); + ++incoming[edge.callee]; + } + } + std::vector ready; + for (unsigned i = 0; i < members; ++i) + if (incoming[i] == 0) + ready.push_back(i); + for (unsigned i = 0; i < ready.size(); ++i) + for (const auto callee : next[ready[i]]) + if (--incoming[callee] == 0) + ready.push_back(callee); + return ready.size() == members; +} + +} // namespace weavec::core diff --git a/lib/Core/Interface.cpp b/lib/Core/Interface.cpp index a754712d..820fd7f7 100644 --- a/lib/Core/Interface.cpp +++ b/lib/Core/Interface.cpp @@ -41,12 +41,17 @@ bool InterfaceType::valid() const { for (const auto &node : nodes) { if (node.kind > InterfaceKind::Array || node.qualifiers > 7 || !interfaceText(node.name) || !interfaceText(node.view) || + !interfaceText(node.typedefName) || node.fields.size() > MaxInterfaceFields || node.parameters.size() > MaxInterfaceFields || node.bytes > std::numeric_limits::max()) return false; if ((node.qualifiers & 4U) && node.kind != InterfaceKind::Pointer) return false; + if (!node.typedefName.empty() && + (node.kind != InterfaceKind::Record || !node.name.empty() || + !node.bytes || !interfaceIdentifier(node.typedefName))) + return false; if (node.kind == InterfaceKind::Record) { if ((!node.name.empty() && !interfaceIdentifier(node.name)) || (node.bytes && node.view.empty()) || @@ -59,7 +64,7 @@ bool InterfaceType::valid() const { node.kind != InterfaceKind::Integer && node.kind != InterfaceKind::Floating && !node.name.empty()) return false; - textBytes += node.name.size() + node.view.size(); + textBytes += node.name.size() + node.view.size() + node.typedefName.size(); if (textBytes > MaxInterfaceBytes) return false; const bool incomplete = @@ -149,7 +154,7 @@ bool InterfaceType::valid() const { std::string InterfaceType::encode() const { if (!valid()) return {}; - std::string result = "it1;"; + std::string result = "it2;"; const auto number = [&](std::uint64_t value) { result += std::to_string(value); result += ';'; @@ -170,6 +175,7 @@ std::string InterfaceType::encode() const { number(node.prototype ? 1U : 0U); text(node.name); text(node.view); + text(node.typedefName); number(node.parameters.size()); for (const auto parameter : node.parameters) number(parameter); @@ -184,7 +190,7 @@ std::string InterfaceType::encode() const { } std::optional InterfaceType::decode(std::string_view input) { - if (input.size() > MaxInterfaceBytes || !input.starts_with("it1;")) + if (input.size() > MaxInterfaceBytes || !input.starts_with("it2;")) return std::nullopt; const auto original = input; input.remove_prefix(4); @@ -230,6 +236,7 @@ std::optional InterfaceType::decode(std::string_view input) { node.prototype = number(1) != 0; node.name = text(); node.view = text(); + node.typedefName = text(); const auto parameters = number(MaxInterfaceFields); for (std::uint64_t j = 0; ok && j < parameters; ++j) node.parameters.push_back( diff --git a/lib/Core/Relation.cpp b/lib/Core/Relation.cpp index 810dd77a..5b71e5d9 100644 --- a/lib/Core/Relation.cpp +++ b/lib/Core/Relation.cpp @@ -480,7 +480,12 @@ bool RelationTracker::join(const RelationTracker &other, continue; } if (theirs->second > it->second) { - it->second = theirs->second; + if (keepDifferenceBound && widenDifferences && theirs->second > 0) { + it = upper.erase(it); + changed = true; + continue; + } + it->second = keepDifferenceBound && widenDifferences ? 0 : theirs->second; changed = true; } ++it; @@ -493,7 +498,12 @@ bool RelationTracker::join(const RelationTracker &other, continue; } if (theirs->second < it->second) { - it->second = theirs->second; + if (keepDifferenceBound && widenDifferences && theirs->second < 0) { + it = lower.erase(it); + changed = true; + continue; + } + it->second = keepDifferenceBound && widenDifferences ? 0 : theirs->second; changed = true; } ++it; diff --git a/lib/Core/Safety.cpp b/lib/Core/Safety.cpp index b86f22e6..eda9d904 100644 --- a/lib/Core/Safety.cpp +++ b/lib/Core/Safety.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace weavec::core { @@ -656,7 +657,9 @@ static bool mergeInitializedConditions(std::vector &ranges) { auto &a = ranges[i]; const auto &b = ranges[j]; if (a.begin != b.begin || a.end != b.end || a.source != b.source || - a.zeroed != b.zeroed || a.when.pointers != b.when.pointers || + a.zeroed != b.zeroed || a.numericText != b.numericText || + a.bytes != b.bytes || a.immutableBytes != b.immutableBytes || + a.when.pointers != b.when.pointers || a.when.integers != b.when.integers) { ++j; continue; @@ -711,7 +714,46 @@ static bool mergeInitializedConditions(std::vector &ranges) { return changed; } +std::vector +InitializedRange::outsideWrite(const Affine &first, const Affine &last) const { + if (terminatedWithin || !begin.isConstant() || !end.isConstant() || + !first.isConstant() || !last.isConstant() || begin.constant < 0 || + first.constant < 0 || begin.constant > end.constant || + first.constant > last.constant) + return {}; + if (first == last || last.constant <= begin.constant || + first.constant >= end.constant) + return {*this}; + std::vector result; + if (begin.constant < first.constant) { + auto left = *this; + left.end = first; + if (!left.bytes.empty()) + left.bytes.resize( + static_cast(first.constant - begin.constant)); + result.push_back(std::move(left)); + } + if (last.constant < end.constant) { + auto right = *this; + right.begin = last; + if (!right.bytes.empty()) + right.bytes.erase( + 0, static_cast(last.constant - begin.constant)); + result.push_back(std::move(right)); + } + return result; +} + void SafetyState::initialize(PlaceId storage, InitializedRange range) { + if (range.immutableBytes && range.bytes.empty()) + return; + if (!range.bytes.empty() && + (!range.begin.isConstant() || !range.end.isConstant() || + range.begin.constant < 0 || range.end.constant < range.begin.constant || + std::cmp_not_equal(range.end.constant - range.begin.constant, + range.bytes.size()) || + range.bytes.size() > 64 || range.source || range.terminatedWithin)) + return; if (range.terminatedWithin) { auto &facts = boundedTermination[storage]; if (facts.size() < MaxInitializedRanges && @@ -722,9 +764,12 @@ void SafetyState::initialize(PlaceId storage, InitializedRange range) { } return; } - if (range.zeroed) { + if (range.zeroed || range.numericText || !range.bytes.empty()) { auto plain = range; plain.zeroed = false; + plain.numericText = false; + plain.bytes.clear(); + plain.immutableBytes = false; initialize(storage, std::move(plain)); } if (range.begin == range.end) @@ -734,11 +779,12 @@ void SafetyState::initialize(PlaceId storage, InitializedRange range) { return; // Exact adjacency is valid for symbolic endpoints as well as constants. // Every constituent interval is a must-fact; no gap is filled here. - bool extended = true; + bool extended = range.bytes.empty(); while (extended) { extended = std::erase_if(ranges, [&](const InitializedRange &old) { if (old.source != range.source || old.zeroed != range.zeroed || - old.when != range.when) + old.numericText != range.numericText || + old.when != range.when || !old.bytes.empty()) return false; if (old.end == range.begin) { range.begin = old.begin; @@ -751,13 +797,15 @@ void SafetyState::initialize(PlaceId storage, InitializedRange range) { return false; }) != 0; } - if (range.begin.isConstant() && range.end.isConstant()) { + if (range.bytes.empty() && range.begin.isConstant() && + range.end.isConstant()) { if (range.begin.constant > range.end.constant) return; // Coalesce only known adjacent/overlapping intervals, never gaps. std::erase_if(ranges, [&](const InitializedRange &old) { if (old.source != range.source || old.zeroed != range.zeroed || - old.when != range.when || !old.begin.isConstant() || + old.numericText != range.numericText || old.when != range.when || + !old.bytes.empty() || !old.begin.isConstant() || !old.end.isConstant() || old.end.constant < range.begin.constant || range.end.constant < old.begin.constant) return false; @@ -779,7 +827,9 @@ void SafetyState::forgetZeros() { boundedTermination.clear(); for (auto &[storage, ranges] : memory) { (void)storage; - std::erase_if(ranges, [](const auto &range) { return range.zeroed; }); + std::erase_if(ranges, [](const auto &range) { + return range.zeroed || range.numericText || !range.bytes.empty(); + }); } } void SafetyState::copyMemory(PlaceId source, PlaceId destination) { @@ -815,6 +865,10 @@ void SafetyState::forget(PlaceId place) { unions.invalidate(place); containers.erase(place); footprints.forget(place); + std::erase_if(pendingAllocationReleases, [&](const auto &entry) { + return entry.first == place || entry.second.storage == place || + entry.second.snapshot == place; + }); unfoldedFootprints.erase(place); objectTypes.erase(place); writtenStorage.erase(place); @@ -837,6 +891,7 @@ void SafetyState::forget(PlaceId place) { } void SafetyState::forgetDependency(PlaceId place) { + nonNan.erase(place); buffers.forget(place); unions.forgetDependency(place); for (auto &[storage, facts] : boundedTermination) { @@ -924,11 +979,20 @@ bool SafetyState::join(const SafetyState &other, const PlaceGuard &left, other.paths.empty() ? std::vector{right} : other.paths); changed |= containers.join(other.containers); changed |= footprints.join(other.footprints); + changed |= std::erase_if(pendingAllocationReleases, [&](const auto &entry) { + const auto found = + other.pendingAllocationReleases.find(entry.first); + return found == other.pendingAllocationReleases.end() || + found->second != entry.second; + }) != 0; const auto unfoldedBefore = unfoldedFootprints.size(); std::erase_if(unfoldedFootprints, [&](PlaceId holder) { return !other.unfoldedFootprints.contains(holder); }); changed |= unfoldedBefore != unfoldedFootprints.size(); + changed |= std::erase_if(nonNan, [&](PlaceId place) { + return !other.nonNan.contains(place); + }) != 0; changed |= buffers.join(other.buffers); for (auto &[place, list] : argumentLists) { const auto found = other.argumentLists.find(place); @@ -1058,12 +1122,15 @@ bool SafetyState::join(const SafetyState &other, const PlaceGuard &left, std::vector common; for (const auto &a : aRanges) { for (const auto &b : bRanges) { - if (a.source != b.source || a.zeroed != b.zeroed || a.when != b.when) + if (a.source != b.source || a.zeroed != b.zeroed || + a.numericText != b.numericText || a.bytes != b.bytes || + a.immutableBytes != b.immutableBytes || a.when != b.when) continue; if (a == b) { common.push_back(a); - } else if (a.begin.isConstant() && a.end.isConstant() && - b.begin.isConstant() && b.end.isConstant()) { + } else if (a.bytes.empty() && a.begin.isConstant() && + a.end.isConstant() && b.begin.isConstant() && + b.end.isConstant()) { const auto first = std::max(a.begin.constant, b.begin.constant); const auto last = std::min(a.end.constant, b.end.constant); if (first < last) @@ -1071,7 +1138,8 @@ bool SafetyState::join(const SafetyState &other, const PlaceGuard &left, .end = Affine::ofConstant(last), .when = a.when, .source = a.source, - .zeroed = a.zeroed}); + .zeroed = a.zeroed, + .numericText = a.numericText}); } } } diff --git a/scripts/checked-containers.py b/scripts/checked-containers.py index 2fef7820..e174bf3c 100644 --- a/scripts/checked-containers.py +++ b/scripts/checked-containers.py @@ -259,8 +259,8 @@ def link(label): (work / names[1]).write_text(original) metadata = work / '1.o.weavec' contents = metadata.read_text() - assert contents.startswith('weavec-summaries 24\n') - metadata.write_text(contents.replace('weavec-summaries 24\n', 'weavec-summaries 20\n', 1)) + assert contents.startswith('weavec-summaries 27\n') + metadata.write_text(contents.replace('weavec-summaries 27\n', 'weavec-summaries 20\n', 1)) process, _ = link('sidecar-old-format') assert process.returncode == 1 and 'unsupported format 20' in process.stderr, process.stderr results.append(dict(name='old-container-sidecar-format', passed=True)) diff --git a/scripts/checked-workflows.py b/scripts/checked-workflows.py new file mode 100644 index 00000000..c38ca9b0 --- /dev/null +++ b/scripts/checked-workflows.py @@ -0,0 +1,810 @@ +#!/usr/bin/env python3 +"""RFC 0029 frozen workflow, object and checkpoint acceptance checks.""" +import argparse +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import re +import signal +import subprocess +import tempfile +import time + +ROOT = Path(__file__).resolve().parent.parent +FIXTURES = ROOT / 'test/evaluation/rfc0029' +OBJECT_POPULATIONS = ( + 'transport', + 'serializer', + 'readers', + 'construction', + 'mutual-construction', + 'construction-helpers', + 'output-construction', + 'reader-construction', + 'mutable-reader-construction', + 'numeric-input', + 'cursor-readers', + 'recursive-contexts', + 'recursive-state-cases', + 'recursive-output-cases', + 'record-arrays', + 'writer-return-aliases', + 'writer-return-expressions', + 'writer-position-bounds', + 'writer-forwarding', + 'container-local-frames', + 'container-call-frames', + 'mixed-allocation-ledger', + 'reallocation-ledger-failures', + 'scalar-write-offsets', + 'bounded-string-cursor', + 'cast-reader-intervals', + 'reader-index-loops', + 'paired-reader-counters', + 'character-pointer-slots', + 'pointer-reader-offsets', + 'initialized-spans', + 'span-outputs', + 'reverse-byte-writes', + 'span-counts', + 'span-count-joins', + 'local-callee-copies', + 'reverse-initialization', + 'conditional-count-arguments', + 'initialized-advance', + 'advance-outcomes', + 'counter-reset-ranges-reviewed', + 'guarded-advance', + 'guarded-cursor-bounds', + 'span-advances', + 'paired-cursor-loops', + 'independent-cursors', + 'fixed-span-steps', + 'helper-cursor-pairs', + 'byte-cursor-content', + 'byte-cursor-static', + 'byte-cursor-forwarding', + 'byte-cursor-frames', + 'byte-global-frames', + 'byte-helper-frames', + 'byte-comparisons', + 'byte-loop-partitions', + 'byte-switch-partitions-reviewed', + 'byte-callee-frames', + 'comparison-container-frames-reviewed', + 'numeric-container-frames', + 'temporary-release-frames', + 'container-outcome-frames', + 'string-length-copies', + 'container-alias-outputs', + 'floating-call-premises', + 'singleton-link-ownership', + 'pointer-difference-sizes', + 'payload-publication', + 'payload-write-frames', + 'payload-early-exits', + 'payload-reader-returns', + 'payload-relocation', + 'attached-payload-transfer', + 'numeric-text', + 'numeric-scans', + 'numeric-scan-locals', + 'numeric-short-circuit', + 'cursor-envelope-joins-reviewed', + 'pointer-count-readers', + 'shifted-pointee-facts', + 'buffer-entry-intervals-reviewed', + 'in-place-extension', + 'anonymous-private-state', + 'container-value-guards', + 'zero-counters', + 'zero-frames', + 'combined-callback-cases', + 'mutual-cases', + 'recursive-cases', + 'recursive-writer-reviewed', + 'writer-transport', + 'corpus-regressions', + 'recursive-transport', + 'output-transport', +) + + +def load_module(name): + spec = importlib.util.spec_from_file_location(name.replace('-', '_'), ROOT / 'scripts' / (name + '.py')) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +EVALUATION = load_module('checked-evaluation') +REPORT = load_module('checked-report') + + +def digest(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def document(path): + return json.loads(path.read_text()) if path.is_file() else {} + + +def verify(directory): + inventory = document(directory / 'frozen-sha256.json') + if not inventory: + raise ValueError('missing frozen inventory: ' + str(directory)) + for name, expected in inventory.items(): + if digest(directory / name) != expected: + raise ValueError('frozen input changed: ' + str(directory / name)) + return inventory + + +def invoke(args, label, command, cwd=None, timeout=None): + for binary, expected in args.identities.items(): + if digest(Path(binary)) != expected: + raise ValueError('executable changed during validation: ' + binary) + for argument in command: + if argument.startswith(('--checked-report=', '-fweavec-checked-report=', '--analysis-stats=')): + Path(argument.split('=', 1)[1]).unlink(missing_ok=True) + start = time.monotonic() + process = subprocess.Popen(command, cwd=cwd, text=True, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, start_new_session=True) + try: + stdout, stderr = process.communicate(timeout=args.timeout if timeout is None else timeout) + except subprocess.TimeoutExpired: + if os.name == 'posix': + os.killpg(process.pid, signal.SIGKILL) + else: + process.kill() + stdout, stderr = process.communicate() + (args.output / (label + '.log')).write_text(stdout + stderr) + raise + run = subprocess.CompletedProcess(command, process.returncode, stdout, stderr) + (args.output / (label + '.log')).write_text(run.stdout + run.stderr) + return run, time.monotonic() - start + + +def population_manifest(directory): + verify(directory) + manifest = directory / 'manifest.json' + variant = {'readers': 'reviewed', 'cursor-readers': 'audited', 'zero-counters': 'audited', 'traversal': 'reviewed', 'writer-forwarding': 'reviewed', 'scalar-write-offsets': 'audited', + 'container-value-guards': 'reviewed', 'byte-cursor-frames': 'reviewed', 'corpus-regressions': 'discovered'}.get(directory.name) + if variant: + inventory = document(directory / (variant + '-sha256.json')) + if not inventory: + raise ValueError('missing reviewed inventory: ' + str(directory)) + for name, expected in inventory.items(): + if digest(directory / name) != expected: + raise ValueError('reviewed input changed: ' + name) + manifest = directory / (variant + '-manifest.json') + return manifest + + +def verify_upstream(directory): + if (directory / 'upstream-identity.json').is_file(): + identity = document(directory / 'upstream-identity.json') + upstream = ROOT / 'build/corpus/cJSON-program' + revision = subprocess.check_output(['git', '-C', str(upstream), 'rev-parse', 'HEAD'], text=True).strip() + if revision != identity['commit']: + raise ValueError('upstream revision differs') + for name, expected in identity['files'].items(): + if digest(upstream / name) != expected: + raise ValueError('upstream source differs: ' + name) + + +def source(args, directory): + manifest = population_manifest(directory) + verify_upstream(directory) + path = args.output / 'source.json' + path.unlink(missing_ok=True) + # The inner runner gives each syntax check and checker its own deadline. + # Do not impose that same single-process deadline on the whole population. + population_timeout = len(document(manifest)['cases']) * (2 * args.timeout + 30) + run, seconds = invoke(args, 'source', [args.python, str(ROOT / 'scripts/checked-evaluation.py'), + '--weavec', str(args.weavec), '--clang', str(args.clang), + '--manifest', str(manifest), '--json', str(path), '--timeout', str(args.timeout)], + timeout=population_timeout) + result = document(path) + if not result.get('cases'): + raise ValueError('source evaluation did not produce cases') + if run.returncode not in (0, 1): + raise ValueError('source runner failed abnormally') + for case in result['cases']: + yield dict(name=case['name'], passed=case['passed'], reason=case.get('reason', ''), + seconds=seconds, report=str(path)) + + +def objects(args): + for population in ([args.object_population] if getattr(args, 'object_population', None) + else OBJECT_POPULATIONS): + directory = FIXTURES / population + for case in document(population_manifest(directory))['cases']: + case = dict(case, functions=case.get('functions', [case.get('function')]), + sources=case.get('sources', [case.get('source')])) + if 'main' not in case['functions'] and population != 'zero-counters': + # Only executable clients belong to this population. The + # source population independently checks generic interfaces. + if case['name'] != 'runtime-hex' and population not in ( + 'construction', 'mutual-construction', 'construction-helpers', + 'corpus-regressions', 'output-construction', 'reader-construction', 'mutable-reader-construction', 'recursive-writer-reviewed'): + continue + if population != 'recursive-writer-reviewed': + case = dict(case, functions=['main']) + with tempfile.TemporaryDirectory(prefix='weavec-workflow-objects-') as temporary: + work = Path(temporary) + for path in directory.glob('*'): + if path.suffix in ('.c', '.h'): + (work / path.name).write_bytes(path.read_bytes()) + if population == 'zero-counters': + # These frozen cases select named closed functions rather + # than main. Supply an unused entry point for the ordinary + # executable link; keep every original selected function. + (work / 'weavec-test-entry.c').write_text('int main(void) { return 0; }\n') + case = dict(case, sources=[*case['sources'], 'weavec-test-entry.c']) + stages = [] + for index, name in enumerate(case['sources']): + label = population + '-' + case['name'] + '-compile-' + str(index) + # Ordinary diagnostics may already detect a frozen mutant. + # Produce its object anyway so checked linking independently + # verifies the selected obligation; warning severity never + # changes whether a checked contract is complete. + run, seconds = invoke(args, label, [str(args.cc), '-std=c11', '-Wno-error=weavec', '-c', name, + '-o', str(index) + '.o'], work) + stages.append(dict(stage=label, seconds=seconds, returncode=run.returncode)) + if run.returncode != 0 or not (work / (str(index) + '.o.weavec')).is_file(): + raise ValueError('ordinary object compilation failed: ' + label) + report = args.output / (population + '-' + case['name'] + '.json') + run, seconds = invoke(args, population + '-' + case['name'] + '-link', + [str(args.cc), *['-fweavec-checked-function=' + name for name in case['functions']], + '-fweavec-checked-report=' + str(report), + *[str(i) + '.o' for i in range(len(case['sources']))], '-o', 'client'], work) + passed, reason = EVALUATION.assess(case, run.returncode, document(report)) + if passed and case['expect'] == 'accepted' and not (work / 'client').is_file(): + passed, reason = False, 'checked link produced no executable' + yield dict(name=population + '-' + case['name'], passed=passed, reason=reason, + stages=stages, seconds=seconds, report=str(report)) + + +def upstream_cases(): + for population in ('upstream', 'upstream-extended', 'upstream-construction', 'upstream-static-inputs', 'upstream-lifetime-audit'): + directory = FIXTURES / population + manifest = population_manifest(directory) + verify_upstream(directory) + for case in document(manifest)['cases']: + yield population + '-' + case['name'], directory, case + + +def upstream_objects(args): + with tempfile.TemporaryDirectory(prefix='weavec-upstream-objects-') as temporary: + work = Path(temporary) + compiled = {} + for label, directory, case in upstream_cases(): + objects = [] + stages = [] + for name in case['sources']: + path = (directory / name).resolve() + if path not in compiled: + target = work / (str(len(compiled)) + '.o') + run, seconds = invoke(args, label + '-compile-' + str(len(compiled)), + [str(args.cc), '-std=c11', '-Wno-error=weavec', '-c', str(path), + '-o', str(target)], work) + if run.returncode or not Path(str(target) + '.weavec').is_file(): + raise ValueError('upstream ordinary object compilation failed: ' + label) + compiled[path] = target + stages.append(dict(source=str(path), seconds=seconds, + object_bytes=target.stat().st_size, + sidecar_bytes=Path(str(target) + '.weavec').stat().st_size)) + objects.append(str(compiled[path])) + report = args.output / (label + '.json') + binary = work / label + run, seconds = invoke(args, label + '-link', + [str(args.cc), *['-fweavec-checked-function=' + name for name in case['functions']], + '-fweavec-checked-report=' + str(report), *objects, '-lm', '-o', str(binary)], work) + passed, reason = EVALUATION.assess(case, run.returncode, document(report)) + if passed and case['expect'] == 'accepted' and not binary.is_file(): + passed, reason = False, 'checked link produced no executable' + yield dict(name=label, passed=passed, reason=reason, stages=stages, + seconds=seconds, report=str(report)) + + +def upstream_checkpoints(args): + for label, directory, case in upstream_cases(): + with tempfile.TemporaryDirectory(prefix='weavec-upstream-cache-') as temporary: + cache = Path(temporary) / 'cache' + observations = {} + for phase in ('uncached', 'cold', 'warm', 'compact'): + report = args.output / (label + '-' + phase + '.json') + stats = args.output / (label + '-' + phase + '.stats.json') + command = [str(args.weavec), '--whole-program', + *['--checked-function=' + name for name in case['functions']], + '--checked-report=' + str(report), '--analysis-stats=' + str(stats)] + if phase != 'uncached': + command.append('--analysis-cache=' + str(cache)) + if phase == 'compact': + command.append('--checked-report-format=compact') + command += [*[str((directory / name).resolve()) for name in case['sources']], + '--', '-std=c11'] + run, seconds = invoke(args, label + '-' + phase, command) + doc = document(report) + if run.returncode not in (0, 1) or not doc: + raise ValueError('upstream checkpoint analysis failed: ' + label + '-' + phase) + expanded = REPORT.expand_report(doc) + counters = document(stats).get('counters', {}) + observations[phase] = dict(returncode=run.returncode, report=expanded, + stderr=run.stderr, counters=counters, seconds=seconds, + report_bytes=report.stat().st_size, + checkpoint_bytes=sum(p.stat().st_size for p in cache.glob('*.wcache'))) + first = observations['uncached'] + passed, reason = EVALUATION.assess(case, first['returncode'], first['report']) + if any((entry['returncode'], entry['report'], entry['stderr']) != + (first['returncode'], first['report'], first['stderr']) + for entry in observations.values()): + passed, reason = False, 'canonical upstream cached report differs' + if any(observations[phase]['counters'].get('cache_hits', 0) < len(case['sources']) or + observations[phase]['counters'].get('function_analyses', 0) != 0 + for phase in ('warm', 'compact')): + passed, reason = False, 'unchanged upstream workflow did not fully reuse checkpoints' + for entry in observations.values(): + del entry['report'], entry['stderr'] + yield dict(name=label, passed=passed, reason=reason, observations=observations) + + +def upstream_oracle(args): + directory = FIXTURES / 'upstream-oracle' + verify(directory) + verify_upstream(directory) + with tempfile.TemporaryDirectory(prefix='weavec-upstream-oracle-') as temporary: + binary = Path(temporary) / 'oracle' + run, _ = invoke(args, 'compile', [str(args.clang), '-std=c11', '-g', '-O1', + '-fsanitize=address,undefined', '-fno-omit-frame-pointer', + str(directory / 'oracle.c'), str(ROOT / 'build/corpus/cJSON-program/cJSON.c'), + '-lm', '-o', str(binary)]) + if run.returncode != 0: + raise ValueError('upstream oracle compilation failed') + run, seconds = invoke(args, 'oracle', ['env', 'ASAN_OPTIONS=detect_leaks=0', + 'UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1', str(binary)]) + if run.returncode != 0: + raise ValueError('upstream allocation ledger or sanitizer failed') + observation = json.loads(run.stdout) + if observation != dict(documents=8, runs=68, injected_failures=52): + raise ValueError('upstream oracle population changed') + yield dict(name='upstream-allocation-failure-oracle', passed=True, + seconds=seconds, observation=observation) + yield from upstream_lifetime_oracle(args) + + +def upstream_lifetime_oracle(args): + directory = FIXTURES / 'upstream-lifetime-audit' + verify(directory) + verify_upstream(directory) + with tempfile.TemporaryDirectory(prefix='weavec-upstream-lifetime-') as temporary: + for storage in ('stack', 'static'): + binary = Path(temporary) / storage + command = [str(args.clang), '-std=c11', '-g', '-O1', + '-fsanitize=address,undefined', '-fno-omit-frame-pointer'] + if storage == 'static': + command.append('-DSTATIC_INPUT') + command += [str(directory / 'oracle.c'), + str(ROOT / 'build/corpus/cJSON-program/cJSON.c'), + '-lm', '-o', str(binary)] + run, _ = invoke(args, 'lifetime-' + storage + '-compile', command) + if run.returncode: + raise ValueError('upstream lifetime oracle compilation failed') + run, seconds = invoke(args, 'lifetime-' + storage, + ['env', 'ASAN_OPTIONS=detect_leaks=0:detect_stack_use_after_return=1', + 'UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1', str(binary)]) + passed = (run.returncode == 0 if storage == 'static' else + run.returncode != 0 and 'stack-use-after-' in run.stderr) + yield dict(name='upstream-lifetime-' + storage, passed=passed, + seconds=seconds, returncode=run.returncode) + + +def construction_oracle(args): + """Execute the frozen finite clients with every allocation failure point. + + This concrete ledger is independent of abstract footprints and progress + graphs. It corroborates only these clients, not arbitrary runtime lengths. + """ + for population, positive in (('construction', 'build'), + ('mutual-construction', 'forwarding'), + ('construction-helpers', 'build'), + ('output-construction', 'build'), + ('reader-construction', 'build'), + ('mutable-reader-construction', 'build')): + directory = FIXTURES / population + verify(directory) + variants = [(positive, 0), ('leak', 74)] + if (directory / 'double.c').is_file(): + variants.append(('double', 73)) + for variant, code in variants: + with tempfile.TemporaryDirectory(prefix='weavec-construction-oracle-') as temporary: + work = Path(temporary) + source = r''' +#include +#include +static void *live[128]; +static size_t calls, fail_at; +static void *oracle_calloc(size_t n, size_t size) { + if (++calls == fail_at) return NULL; + void *p = calloc(n, size); + if (!p) { fputs("unexpected host allocation failure\n", stderr); exit(75); } + for (size_t i = 0; i < 128; ++i) { + if (!live[i]) { live[i] = p; return p; } + } + exit(76); +} +static void oracle_free(void *p) { + if (!p) return; + for (size_t i = 0; i < 128; ++i) { + if (live[i] == p) { live[i] = NULL; free(p); return; } + } + fputs("release is not live\n", stderr); + for (size_t i = 0; i < 128; ++i) free(live[i]); + exit(73); +} +#define calloc oracle_calloc +#define free oracle_free +#define main frozen_client +''' + source += '#include ' + json.dumps(str(directory / (variant + '.c'))) + '\n' + source += r''' +#undef main +#undef free +#undef calloc +int main(void) { + size_t count = 0; + for (fail_at = 0; fail_at <= count; ++fail_at) { + calls = 0; + if (frozen_client() != 0) return 77; + if (fail_at == 0) count = calls; + for (size_t i = 0; i < 128; ++i) { + if (live[i]) { + fputs("live allocations remain\n", stderr); + for (size_t j = 0; j < 128; ++j) free(live[j]); + return 74; + } + } + } + printf("checked %zu allocation failure points\n", count); + return 0; +} +''' + (work / 'oracle.c').write_text(source) + label = population + '-' + variant + '-oracle' + compiled, _ = invoke(args, label + '-compile', + [str(args.clang), '-std=c11', '-fsanitize=address,undefined', + '-fno-sanitize-recover=all', 'oracle.c', '-o', 'oracle'], work) + if compiled.returncode: + raise ValueError('concrete oracle did not compile: ' + label) + run, seconds = invoke(args, label, [str(work / 'oracle')], work) + marker = {0: 'allocation failure points', 73: 'release is not live', + 74: 'live allocations remain'}[code] + passed = run.returncode == code and marker in run.stdout + run.stderr + # Repeating recursive cleanup first reads the freed child's + # links, before it reaches the ledger's second release. + if population == 'construction-helpers' and variant == 'double': + passed |= run.returncode != 0 and \ + 'ERROR: AddressSanitizer: heap-use-after-free' in run.stderr + count = re.search(r'checked (\d+) allocation failure points', run.stdout) + yield dict(name=label, passed=passed, returncode=run.returncode, + seconds=seconds, + allocation_failure_points=int(count[1]) if count else None, + reason='' if passed else 'concrete ledger differs') + + + +def reader_index_oracle(args): + """Exhaust the eight-bit arithmetic analogue, independently of the checker.""" + verify(FIXTURES / 'reader-index-loops') + cases = 0 + accesses = 0 + for capacity in range(256): + for position in range(capacity + 1): + index = 0 + while ((position + index) & 255) < capacity: + if not (index < capacity - position and position + index < 256): + raise ValueError('strict unit-stride induction differs from concrete arithmetic') + accesses += 1 + index = (index + 1) & 255 + if index == 0: + raise ValueError('reader index unexpectedly wrapped') + if index != capacity - position: + raise ValueError('reader index exited at the wrong boundary') + cases += 1 + yield dict(name='reader-index-eight-bit-oracle', passed=True, + cases=cases, accesses=accesses, reason='') + + +def writer_oracle(args): + """Check finite prefix contents and capacity canaries independently.""" + directory = FIXTURES / 'recursive-writer-reviewed' + verify(directory) + for variant, expected in (('emit', 0), ('false-prefix', 74)): + with tempfile.TemporaryDirectory(prefix='weavec-writer-oracle-') as temporary: + work = Path(temporary) + (work / 'fixture.c').write_bytes((directory / (variant + '.c')).read_bytes()) + (work / 'oracle.c').write_text(r''' +#include +#include +#define main fixture_main +#include "fixture.c" +#undef main +int main(void) { + unsigned char input[33], output[33]; + size_t cases = 0; + for (size_t i = 0; i < sizeof input; ++i) input[i] = (unsigned char)(i + 1); + for (size_t n = 0; n <= 32; ++n) + for (size_t capacity = 0; capacity <= 32; ++capacity) + for (size_t initial = 0; initial <= capacity; ++initial) { + memset(output, 0xa5, sizeof output); + struct writer w = {7, output, initial, capacity}; + int result = emit(input, n, &w); + size_t copied = n < capacity - initial ? n : capacity - initial; + if (w.used != initial + copied || w.capacity != capacity || + w.data != output || w.flags != 7 || result != (copied == n)) { + puts("prefix or capacity differs"); return 74; + } + for (size_t i = 0; i < sizeof output; ++i) { + unsigned char expected = i >= initial && i < initial + copied + ? input[i - initial] : 0xa5; + if (output[i] != expected) { + puts("prefix or capacity differs"); return 74; + } + } + ++cases; + } + printf("checked %zu finite prefix cases\n", cases); + return 0; +} +''') + label = 'writer-oracle-' + variant + compiled, _ = invoke(args, label + '-compile', + [str(args.clang), '-std=c11', '-fsanitize=address,undefined', + '-fno-sanitize-recover=all', 'oracle.c', '-o', 'oracle'], work) + if compiled.returncode: + raise ValueError('writer oracle did not compile') + run, seconds = invoke(args, label, [str(work / 'oracle')], work) + count = re.search(r'checked (\d+) finite prefix cases', run.stdout) + passed = run.returncode == expected and ( + count is not None if expected == 0 else + 'prefix or capacity differs' in run.stdout) + yield dict(name=label, passed=passed, returncode=run.returncode, + seconds=seconds, finite_cases=int(count[1]) if count else None, + reason='' if passed else 'concrete prefix oracle differs') + + +def reader_oracle(args): + directory = FIXTURES / 'cursor-readers' + population_manifest(directory) + for variant, expected in [('library', 0), ('partial', 0), ('escape', 74)]: + with tempfile.TemporaryDirectory(prefix='weavec-reader-oracle-') as temporary: + work = Path(temporary) + for name in ['reader.h', variant + '.c']: + (work / name).write_bytes((directory / name).read_bytes()) + (work / 'oracle.c').write_text(r''' +#include "reader.h" +#include +int main(void) { + unsigned char input[33]; + size_t cases=0; + for (unsigned first=0; first<2; ++first) { + for (size_t i=0; itext=p;', 'n->text=p;n->name=p;') + if payloads else + original.replace("p[i]=='\\\\'", "p[i]=='b'") + if population == 'byte-cursor-content' else + original.replace('int value=r->data[r->pos];', 'int value=r->data[r->limit+1];') + if population == 'cursor-readers' else + original.replace('w->data[w->used]=input[0];', '(void)input[0];') + if population == 'writer-transport' else + original.replace('destroy(p->next);', '(void)p->next;') + if population != 'transport' + else original.replace('destroy_even(p->right);', '(void)p->right;')) + if mutation == original: + raise ValueError('contract mutation did not modify source') + path.write_text(mutation) + changed, fresh = analyze('changed'), analyze('changed-uncached', False) + if changed[0] != fresh[0] or changed[0][0] != 1 or not changed[1].get('function_analyses'): + raise ValueError('changed recursive member reused a stale proof') + yield dict(name='changed-group-invalidates-proof', passed=True, counters=changed[1]) + path.write_text(original) + if payloads: + destructor = work / 'drop.c' + original_cleanup = destructor.read_text() + changed_cleanup = original_cleanup.replace('free(n->text)', '(void)n->text') + if changed_cleanup == original_cleanup: + raise ValueError('cleanup mutation did not modify source') + destructor.write_text(changed_cleanup) + changed, fresh = analyze('changed-cleanup'), analyze('changed-cleanup-uncached', False) + if changed[0] != fresh[0] or changed[0][0] != 1 or not changed[1].get('function_analyses'): + raise ValueError('changed imported ownership reused a stale proof') + yield dict(name='changed-ownership-invalidates-proof', passed=True, counters=changed[1]) + destructor.write_text(original_cleanup) + if population == 'byte-cursor-content': + client_path = work / 'client.c' + good = client_path.read_text() + client_path.write_text(good.replace("'b'", "'\\\\'")) + changed, fresh = analyze('changed-bytes'), analyze('changed-bytes-uncached', False) + if changed[0] != fresh[0] or changed[0][0] != 1 or not changed[1].get('function_analyses'): + raise ValueError('changed input bytes reused a stale proof') + yield dict(name='changed-bytes-invalidates-proof', passed=True, counters=changed[1]) + client_path.write_text(good) + for checkpoint in (work / 'cache').glob('*.wcache'): + checkpoint.write_bytes(checkpoint.read_bytes()[:91]) + corrupt = analyze('corrupt') + if corrupt[0] != uncached[0] or corrupt[1].get('cache_hits', 0): + raise ValueError('corrupt checkpoint was not recomputed') + yield dict(name='corrupt-checkpoint-recomputed', passed=True) + for index, name in enumerate(sources): + run, _ = invoke(args, 'compile-' + str(index), + [str(args.cc), '-std=c11', '-c', name, '-o', str(index) + '.o'], work) + if run.returncode: + raise ValueError('object compilation failed') + metadata = work / '1.o.weavec' + text = metadata.read_text() + version = re.match(r'weavec-summaries (\d+)\n', text) + if not version: + raise ValueError('missing sidecar version') + metadata.write_text(text.replace(version[0], 'weavec-summaries ' + str(int(version[1])-1) + '\n', 1)) + run, _ = invoke(args, 'old-sidecar', [str(args.cc), '-fweavec-checked-function=main', + *[str(index) + '.o' for index in range(len(sources))], + '-o', 'old'], work) + if run.returncode != 1 or 'unsupported format' not in run.stderr: + raise ValueError('old sidecar was accepted') + yield dict(name='old-sidecar-rejected', passed=True) + + +def main(): + import sys + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--weavec', required=True, type=Path) + parser.add_argument('--cc', required=True, type=Path) + parser.add_argument('--clang', default='clang', type=Path) + parser.add_argument('--output', required=True, type=Path) + parser.add_argument('--population', choices=['source', 'transport', 'serializer', 'readers', 'offsets', 'traversal', 'construction', 'mutual-construction', 'construction-helpers', 'output-construction', 'reader-construction', 'mutable-reader-construction', 'numeric-input', 'cursor-readers', 'recursive-contexts', 'recursive-state-cases', 'recursive-output-cases', 'record-arrays', 'writer-return-aliases', 'writer-return-expressions', 'writer-position-bounds', 'writer-forwarding', 'container-local-frames', 'container-call-frames', 'mixed-allocation-ledger', 'reallocation-ledger-failures', 'scalar-write-offsets', 'bounded-string-cursor', 'cast-reader-intervals', 'reader-index-loops', 'paired-reader-counters', 'character-pointer-slots', 'pointer-reader-offsets', 'initialized-spans', 'span-outputs', 'reverse-byte-writes', 'span-counts', 'span-count-joins', 'local-callee-copies', 'reverse-initialization', 'conditional-count-arguments', 'initialized-advance', 'advance-outcomes', 'counter-reset-ranges-reviewed', 'guarded-advance', 'guarded-cursor-bounds', 'span-advances', 'paired-cursor-loops', 'independent-cursors', 'fixed-span-steps', 'helper-cursor-pairs', 'byte-cursor-content', 'byte-cursor-static', 'byte-cursor-forwarding', 'byte-cursor-frames', 'byte-global-frames', 'byte-helper-frames', 'byte-comparisons', 'byte-loop-partitions', 'byte-switch-partitions-reviewed', 'byte-callee-frames', 'comparison-container-frames-reviewed', 'numeric-container-frames', 'temporary-release-frames', 'container-outcome-frames', 'string-length-copies', 'container-alias-outputs', 'floating-call-premises', 'singleton-link-ownership', 'pointer-difference-sizes', 'payload-publication', 'payload-write-frames', 'payload-early-exits', 'payload-reader-returns', 'payload-relocation', 'attached-payload-transfer', 'payload-cache', 'byte-cache', 'numeric-text', 'numeric-scans', 'numeric-scan-locals', 'numeric-short-circuit', 'cursor-envelope-joins-reviewed', 'pointer-count-readers', 'shifted-pointee-facts', 'buffer-entry-intervals-reviewed', 'in-place-extension', 'anonymous-private-state', 'container-value-guards', 'zero-counters', 'zero-frames', 'combined-callback-cases', 'mutual-cases', 'recursive-cases', 'recursive-writer-reviewed', 'writer-transport', 'writer-cache', 'cursor-cache', 'cursor-oracle', 'reader-index-oracle', 'writer-oracle', 'construction-oracle', 'corpus-regressions', 'recursive-transport', 'recursive-cache', 'output-transport', 'output-cache', 'upstream', 'upstream-extended', 'upstream-construction', 'upstream-static-inputs', 'upstream-lifetime-audit', 'upstream-lifetime-oracle', 'upstream-oracle', 'upstream-objects', 'upstream-cache', 'objects', 'cache'], required=True) + parser.add_argument('--object-population', choices=OBJECT_POPULATIONS, + help='Run one object population; omission runs all frozen object cases.') + parser.add_argument('--timeout', default=600, type=float) + args = parser.parse_args() + args.python = sys.executable + args.weavec, args.cc, args.output = args.weavec.resolve(), args.cc.resolve(), args.output.resolve() + args.output.mkdir(parents=True, exist_ok=True) + args.identities = {str(binary): digest(binary) for binary in (args.weavec, args.cc)} + results = [] + try: + if args.population == 'objects': + cases = objects(args) + elif args.population == 'cache': + cases = checkpoints(args) + elif args.population == 'construction-oracle': + cases = construction_oracle(args) + elif args.population == 'upstream-objects': + cases = upstream_objects(args) + elif args.population == 'upstream-cache': + cases = upstream_checkpoints(args) + elif args.population == 'upstream-lifetime-oracle': + cases = upstream_lifetime_oracle(args) + elif args.population == 'upstream-oracle': + cases = upstream_oracle(args) + elif args.population == 'recursive-cache': + cases = checkpoints(args, 'recursive-transport') + elif args.population == 'output-cache': + cases = checkpoints(args, 'output-transport') + elif args.population == 'reader-index-oracle': + cases = reader_index_oracle(args) + elif args.population == 'cursor-oracle': + cases = reader_oracle(args) + elif args.population == 'payload-cache': + cases = checkpoints(args, 'payload-write-frames') + elif args.population == 'byte-cache': + cases = checkpoints(args, 'byte-cursor-content') + elif args.population == 'cursor-cache': + cases = checkpoints(args, 'cursor-readers') + elif args.population == 'writer-cache': + cases = checkpoints(args, 'writer-transport') + elif args.population == 'writer-oracle': + cases = writer_oracle(args) + else: + directory = FIXTURES if args.population == 'source' else FIXTURES / args.population + cases = source(args, directory) + for result in cases: + results.append(result) + print(('PASS ' if result['passed'] else 'FAIL ') + result['name'] + ': ' + result.get('reason', ''), flush=True) + except (OSError, ValueError, KeyError, subprocess.TimeoutExpired, subprocess.CalledProcessError) as error: + results.append(dict(name=args.population, passed=False, reason=str(error))) + print('FAIL ' + args.population + ': ' + str(error), flush=True) + (args.output / 'results.json').write_text(json.dumps(dict(version=1, binaries=args.identities, cases=results), indent=2) + '\n') + return 0 if results and all(result['passed'] for result in results) else 1 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/test/Analysis/rfc0002-borrows.c b/test/Analysis/rfc0002-borrows.c index 3aa9d6ac..d863c84c 100644 --- a/test/Analysis/rfc0002-borrows.c +++ b/test/Analysis/rfc0002-borrows.c @@ -84,7 +84,7 @@ void temporary_borrows(void) { void array_decay(void) { int a[4]; int *p = a; - // EXCL: rfc0002-borrows.c:[[@LINE+1]]:12: error: cannot borrow 'a[*]' as mutable because it is already borrowed [weavec::conflicting-borrow] + // EXCL: rfc0002-borrows.c:[[@LINE+1]]:12: error: cannot borrow 'a[1]' as mutable because it is already borrowed [weavec::conflicting-borrow] int *q = &a[1]; use(p); use(q); diff --git a/test/Analysis/rfc0029-callback-contracts.c b/test/Analysis/rfc0029-callback-contracts.c new file mode 100644 index 00000000..e2872f1e --- /dev/null +++ b/test/Analysis/rfc0029-callback-contracts.c @@ -0,0 +1,13 @@ +// RUN: %weavec --checked-function=create --checked-report=%t.json %s -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: FileCheck %s --check-prefix=REPORT < %t.json +// CLEAN-NOT: error: +// REPORT: "kind":"callback-allocate" +// RFC 0029: a generic helper exports a behavioral premise; it does not trust +// an unbound allocator's implementation. +#include +void *create(void *(*allocate)(size_t), size_t n) { + if (!n) return 0; + unsigned char *p = allocate(n); + if (p) p[n - 1] = 7; + return p; +} diff --git a/test/Analysis/rfc0029-character-pointer-slots.c b/test/Analysis/rfc0029-character-pointer-slots.c new file mode 100644 index 00000000..016d6802 --- /dev/null +++ b/test/Analysis/rfc0029-character-pointer-slots.c @@ -0,0 +1,4 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/character-pointer-slots/end.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/character-pointer-slots/unrelated.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: unsupported checked C construct or storage type [weavec::checking-incomplete] +// RFC 0029: compatible character-pointer views retain the original slot rules. diff --git a/test/Analysis/rfc0029-container-value-guards.c b/test/Analysis/rfc0029-container-value-guards.c new file mode 100644 index 00000000..b2e21f52 --- /dev/null +++ b/test/Analysis/rfc0029-container-value-guards.c @@ -0,0 +1,6 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/container-value-guards/null-result.c -- +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/container-value-guards/replaced-head.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/container-value-guards/changed-alias.c -- 2>&1 | FileCheck %s +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/container-value-guards/replaced-live-head.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: access interval must fit its object [weavec::checking-incomplete] +// RFC 0029: a current head predicate supplies a value, and mutation retires it. diff --git a/test/Analysis/rfc0029-floating-conversions.c b/test/Analysis/rfc0029-floating-conversions.c new file mode 100644 index 00000000..22456491 --- /dev/null +++ b/test/Analysis/rfc0029-floating-conversions.c @@ -0,0 +1,20 @@ +// RUN: %weavec --checked-function=bounded --checked-function=wide %s -- 2>&1 | FileCheck %s --check-prefix=GOOD --allow-empty +// RUN: not %weavec --checked-function=unordered %s -- 2>&1 | FileCheck %s --check-prefix=BAD +// GOOD-NOT: error: +// BAD: cannot establish checked safety: unsupported checked C construct or storage type [weavec::checking-incomplete] +// RFC 0029: true finite bounds justify conversion; false comparisons admit NaN. +int bounded(double value) { + if (value >= -2147483648.0 && value <= 2147483647.0) + return (int)value; + return 0; +} +long long wide(double value) { + if (value >= -9223372036854775808.0 && value < 9223372036854775808.0) + return (long long)value; + return 0; +} +int unordered(double value) { + if (value < -2147483648.0 || value > 2147483647.0) + return 0; + return (int)value; +} diff --git a/test/Analysis/rfc0029-numeric-input.c b/test/Analysis/rfc0029-numeric-input.c new file mode 100644 index 00000000..7776a8e5 --- /dev/null +++ b/test/Analysis/rfc0029-numeric-input.c @@ -0,0 +1,7 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-input/end.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-input/null-end.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-input/readonly-slot.c -- 2>&1 | FileCheck %s --check-prefix=READONLY +// CLEAN-NOT: error: +// READONLY: checked safety failed: cannot write to read-only storage [weavec::checking-failed] +// RFC 0029: an end pointer keeps the input object's bounds and lifetime; +// no finite floating-point result is inferred from the library call. diff --git a/test/Analysis/rfc0029-record-arrays.c b/test/Analysis/rfc0029-record-arrays.c new file mode 100644 index 00000000..fff9c58a --- /dev/null +++ b/test/Analysis/rfc0029-record-arrays.c @@ -0,0 +1,11 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/record-arrays/spellings.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/record-arrays/forward.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --checked-function=array %S/../evaluation/rfc0029/zero-counters/array.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/record-arrays/alias-bad.c -- 2>&1 | FileCheck %s --check-prefix=ALIAS +// RUN: not %weavec --checked-function=partial %S/../evaluation/rfc0029/zero-counters/partial.c -- 2>&1 | FileCheck %s --check-prefix=PARTIAL +// CLEAN-NOT: error: +// ALIAS: [weavec::double-free] +// PARTIAL: cannot establish checked safety: access interval must fit its object [weavec::checking-incomplete] +// RFCs 0015/0029: exact record-array spellings agree. Current complete zero +// representations can establish counters; partial bytes and aliased releases +// retain their ordinary proof obligations. diff --git a/test/Analysis/rfc0029-recursive-cases.c b/test/Analysis/rfc0029-recursive-cases.c new file mode 100644 index 00000000..dfda009a --- /dev/null +++ b/test/Analysis/rfc0029-recursive-cases.c @@ -0,0 +1,7 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/recursive-cases/zero.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/recursive-cases/live.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --checked-function=walk %S/../evaluation/rfc0029/recursive-cases/generic.c -- 2>&1 | FileCheck %s --check-prefix=LIMIT +// CLEAN-NOT: error: +// LIMIT: summary iteration limit +// RFC 0029: a proved unreachable recursive branch does not exhaust the case; +// the independently selected generic recursive definition retains its limit. diff --git a/test/Analysis/rfc0029-recursive-traversal.c b/test/Analysis/rfc0029-recursive-traversal.c new file mode 100644 index 00000000..1c4e4bab --- /dev/null +++ b/test/Analysis/rfc0029-recursive-traversal.c @@ -0,0 +1,6 @@ +// RUN: %weavec --checked-function=visit_even --checked-function=visit_odd --checked-function=main %S/../evaluation/rfc0029/traversal/mutual.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --checked-function=visit_even --checked-function=visit_odd --checked-function=main %S/../evaluation/rfc0029/traversal/nondecreasing.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --checked-function=visit_even %S/../evaluation/rfc0029/traversal/cycle.c -- 2>&1 | FileCheck %s --check-prefix=CYCLE +// CLEAN-NOT: error: +// CYCLE: cannot establish checked safety: recursive proof cycle has no strict progress [weavec::checking-incomplete] +// RFC 0029: every cycle needs a strict edge; a forwarding member is allowed. diff --git a/test/Analysis/rfc0029-scalar-cell-identity.c b/test/Analysis/rfc0029-scalar-cell-identity.c new file mode 100644 index 00000000..f9a1cd8b --- /dev/null +++ b/test/Analysis/rfc0029-scalar-cell-identity.c @@ -0,0 +1,4 @@ +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/scalar-write-offsets/advanced-read.c -- 2>&1 | FileCheck %s +// CHECK: checked safety failed: 'bytes[2]' is out of bounds: index 2 of an object of 2 bytes [weavec::checking-failed] +// RFCs 0017/0021/0029: moving a pointer retains its allocation identity, +// but scalar facts about the old pointee cannot describe the next cell. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 91cbc281..c8c070e7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -274,6 +274,236 @@ if(Python3_Interpreter_FOUND) set_tests_properties(checked-${population}-rfc0028 PROPERTIES LABELS "integration" TIMEOUT 600) endforeach() + foreach( + population + source + transport + serializer + readers + offsets + traversal + construction + mutual-construction + construction-helpers + output-construction + reader-construction + mutable-reader-construction + numeric-input + cursor-readers + cursor-cache + byte-cache + payload-cache + cursor-oracle + recursive-contexts + recursive-state-cases + recursive-output-cases + mutual-cases + record-arrays + writer-return-aliases + writer-return-expressions + writer-position-bounds + writer-forwarding + container-local-frames + container-call-frames + mixed-allocation-ledger + reallocation-ledger-failures + scalar-write-offsets + bounded-string-cursor + cast-reader-intervals + reader-index-loops + paired-reader-counters + character-pointer-slots + pointer-reader-offsets + initialized-spans + span-outputs + reverse-byte-writes + span-counts + span-count-joins + local-callee-copies + reverse-initialization + conditional-count-arguments + initialized-advance + advance-outcomes + counter-reset-ranges-reviewed + guarded-advance + guarded-cursor-bounds + span-advances + paired-cursor-loops + independent-cursors + fixed-span-steps + helper-cursor-pairs + byte-cursor-content + byte-cursor-static + byte-cursor-forwarding + byte-cursor-frames + byte-global-frames + byte-helper-frames + byte-comparisons + byte-loop-partitions + byte-switch-partitions-reviewed + byte-callee-frames + comparison-container-frames-reviewed + numeric-container-frames + temporary-release-frames + container-outcome-frames + string-length-copies + container-alias-outputs + floating-call-premises + singleton-link-ownership + pointer-difference-sizes + payload-publication + payload-write-frames + payload-early-exits + payload-reader-returns + payload-relocation + numeric-text + numeric-scans + numeric-scan-locals + numeric-short-circuit + cursor-envelope-joins-reviewed + pointer-count-readers + shifted-pointee-facts + buffer-entry-intervals-reviewed + in-place-extension + anonymous-private-state + container-value-guards + reader-index-oracle + zero-counters + zero-frames + combined-callback-cases + recursive-cases + recursive-writer-reviewed + writer-transport + writer-cache + writer-oracle + output-transport + output-cache + construction-oracle + corpus-regressions + recursive-transport + recursive-cache + cache) + add_test( + NAME checked-${population}-rfc0029 + COMMAND + "${Python3_EXECUTABLE}" + "${PROJECT_SOURCE_DIR}/scripts/checked-workflows.py" --population + "${population}" --weavec "$" --cc + "$" --clang "${WEAVEC_CLANG_EXECUTABLE}" + --output "${CMAKE_CURRENT_BINARY_DIR}/rfc0029-${population}") + set_tests_properties(checked-${population}-rfc0029 + PROPERTIES LABELS "integration" TIMEOUT 600) + endforeach() + # Each immutable object population keeps the same 600-second deadline. + # Combining every RFC 0029 population into one test exceeded that deadline + # even when all individual checked links completed successfully. + foreach( + population + transport + serializer + readers + construction + mutual-construction + construction-helpers + output-construction + reader-construction + mutable-reader-construction + numeric-input + cursor-readers + recursive-contexts + recursive-state-cases + recursive-output-cases + record-arrays + writer-return-aliases + writer-return-expressions + writer-position-bounds + writer-forwarding + container-local-frames + container-call-frames + mixed-allocation-ledger + reallocation-ledger-failures + scalar-write-offsets + bounded-string-cursor + cast-reader-intervals + reader-index-loops + paired-reader-counters + character-pointer-slots + pointer-reader-offsets + initialized-spans + span-outputs + reverse-byte-writes + span-counts + span-count-joins + local-callee-copies + reverse-initialization + conditional-count-arguments + initialized-advance + advance-outcomes + counter-reset-ranges-reviewed + guarded-advance + guarded-cursor-bounds + span-advances + paired-cursor-loops + independent-cursors + fixed-span-steps + helper-cursor-pairs + byte-cursor-content + byte-cursor-static + byte-cursor-forwarding + byte-cursor-frames + byte-global-frames + byte-helper-frames + byte-comparisons + byte-loop-partitions + byte-switch-partitions-reviewed + byte-callee-frames + comparison-container-frames-reviewed + numeric-container-frames + temporary-release-frames + container-outcome-frames + string-length-copies + container-alias-outputs + floating-call-premises + singleton-link-ownership + pointer-difference-sizes + payload-publication + payload-write-frames + payload-early-exits + payload-reader-returns + payload-relocation + numeric-text + numeric-scans + numeric-scan-locals + numeric-short-circuit + cursor-envelope-joins-reviewed + pointer-count-readers + shifted-pointee-facts + buffer-entry-intervals-reviewed + in-place-extension + anonymous-private-state + container-value-guards + zero-counters + zero-frames + combined-callback-cases + mutual-cases + recursive-cases + recursive-writer-reviewed + writer-transport + corpus-regressions + recursive-transport + output-transport) + add_test( + NAME checked-objects-${population}-rfc0029 + COMMAND + "${Python3_EXECUTABLE}" + "${PROJECT_SOURCE_DIR}/scripts/checked-workflows.py" --population + objects --object-population "${population}" --weavec + "$" --cc "$" --clang + "${WEAVEC_CLANG_EXECUTABLE}" --output + "${CMAKE_CURRENT_BINARY_DIR}/rfc0029-objects-${population}") + set_tests_properties(checked-objects-${population}-rfc0029 + PROPERTIES LABELS "integration" TIMEOUT 600) + endforeach() add_test(NAME recall-harness COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/scripts/test_recall.py") diff --git a/test/Driver/rfc0005-weavec-cc.c b/test/Driver/rfc0005-weavec-cc.c index 426ad5e3..f63cd5ed 100644 --- a/test/Driver/rfc0005-weavec-cc.c +++ b/test/Driver/rfc0005-weavec-cc.c @@ -35,7 +35,7 @@ #include "../Inputs/prelude.h" #include "node.h" -// SIDECAR: weavec-summaries 24 +// SIDECAR: weavec-summaries 27 // SIDECAR: source {{.*}}node.c // SIDECAR: cwd {{.+}} // SIDECAR: arg -triple diff --git a/test/Driver/rfc0014-callback-link.c b/test/Driver/rfc0014-callback-link.c index 961397ce..b40faca6 100644 --- a/test/Driver/rfc0014-callback-link.c +++ b/test/Driver/rfc0014-callback-link.c @@ -6,7 +6,7 @@ // RUN: not %weavec_cc %t.dir/helper.o %t.dir/client.o -o %t.dir/program 2>&1 | FileCheck %s --check-prefix=LINK // RUN: not %weavec --whole-program %S/Inputs/rfc0014-callback-client.c %S/Inputs/rfc0014-callback-helper.c -- 2>&1 | FileCheck %s --check-prefix=LINK // RFC 0014: the same callback bug is checked through the CLI and sidecars. -// SIDECAR: weavec-summaries 24 +// SIDECAR: weavec-summaries 27 // SIDECAR: function invoke external plain // SIDECAR: accepts-callbacks // SIDECAR: callback-input param 0 diff --git a/test/Driver/rfc0016-composition-link.c b/test/Driver/rfc0016-composition-link.c index 900f25b8..cedb1333 100644 --- a/test/Driver/rfc0016-composition-link.c +++ b/test/Driver/rfc0016-composition-link.c @@ -11,7 +11,7 @@ int main(void) { char *p = malloc(4); if (p) release_then_write(p, p); return 0; } -// FORMAT: weavec-summaries 24 +// FORMAT: weavec-summaries 27 // FORMAT: accepts-memory-contexts // LINK: rfc0016-callee.c:4:4: error: use of 'b' after it was freed [weavec::use-after-free] // LINK: 1 error generated. diff --git a/test/Driver/rfc0017-numeric-link.c b/test/Driver/rfc0017-numeric-link.c index fdc842f9..5ca58fc4 100644 --- a/test/Driver/rfc0017-numeric-link.c +++ b/test/Driver/rfc0017-numeric-link.c @@ -30,7 +30,7 @@ int main(void) { p[0] = 1; free(p); return 0; } -// FORMAT: weavec-summaries 24 +// FORMAT: weavec-summaries 27 // FORMAT-DAG: numeric result value // FORMAT-DAG: requires-extent 0 param 1 scale 1 plus 1 start param 1 scale 1 plus 0 // LINK-DAG: error: 'put_at' requires 'a' before its start [weavec::out-of-bounds] diff --git a/test/WholeProgram/rfc0011-extents.c b/test/WholeProgram/rfc0011-extents.c index bccdf547..e02954f2 100644 --- a/test/WholeProgram/rfc0011-extents.c +++ b/test/WholeProgram/rfc0011-extents.c @@ -23,7 +23,7 @@ // DUMP: function 'buffer_put8': param 0 *: written; stores{} returns{} requires{param 0} requires-extent{param 0: 8} // DUMP: function 'wrapped_release': param 0: freed(free),at(-struct~wrapped.payload); stores{} returns{} -// SIDECAR: weavec-summaries 24 +// SIDECAR: weavec-summaries 27 // SIDECAR: function buffer_fill // SIDECAR: requires-extent 0 param 1 scale 1 plus 0 when param 1 positive|negative // SIDECAR: function buffer_new diff --git a/test/WholeProgram/rfc0012-sized-fields.c b/test/WholeProgram/rfc0012-sized-fields.c index 1f2d7b48..16962007 100644 --- a/test/WholeProgram/rfc0012-sized-fields.c +++ b/test/WholeProgram/rfc0012-sized-fields.c @@ -25,7 +25,7 @@ // DUMP-NEXT: sized-field 'struct view.raw' by 'struct view.len' * 4 in u64 // DUMP-NEXT: unsized-field 'struct view.raw' -// SIDECAR: weavec-summaries 24 +// SIDECAR: weavec-summaries 27 // SIDECAR-DAG: sized-field struct~vec.items struct~vec.cap 4 u64 // SIDECAR-DAG: sized-field struct~view.raw struct~view.len 4 u64 // SIDECAR-DAG: unsized-field struct~view.raw diff --git a/test/WholeProgram/rfc0013-heap.c b/test/WholeProgram/rfc0013-heap.c index bd2bd3d0..c8afeb35 100644 --- a/test/WholeProgram/rfc0013-heap.c +++ b/test/WholeProgram/rfc0013-heap.c @@ -8,7 +8,7 @@ #include "../Inputs/prelude.h" #include "Inputs/heap13.h" -// SIDECAR: weavec-summaries 24 +// SIDECAR: weavec-summaries 27 // SIDECAR: heap result complete // SIDECAR-NEXT: heap-field result at result *.data fresh(free) extent 4 // SIDECAR: heap-field result at result *.data copy param 0 diff --git a/test/WholeProgram/rfc0015-arrays.c b/test/WholeProgram/rfc0015-arrays.c index 8d416874..2c0befaa 100644 --- a/test/WholeProgram/rfc0015-arrays.c +++ b/test/WholeProgram/rfc0015-arrays.c @@ -7,7 +7,7 @@ // RUN: not %weavec_cc %t/library.o %t/caller.o -o %t/program 2>&1 | FileCheck %s #include "Inputs/array15.h" -// SIDECAR: weavec-summaries 24 +// SIDECAR: weavec-summaries 27 // RFC 0017: memcpy's element count is the wrapped byte product divided by 8. // SIDECAR-DAG: array-copy param 0 * from param 1 * dest-begin 0 source-begin 0 count expr u64,c,8;u64,v,706172616d2032;u64,mul;u64,c,8;u64,div scale 1 plus 0 bytes 8 view pointer definite when cmp u64,c,8;u64,v,706172616d2032;u64,mul;u64,c,8;u64,div in u64:0-2305843009213693951 // SIDECAR-DAG: array-copy result * from param 0 * dest-begin 0 source-begin 0 count expr u64,c,8;u64,v,706172616d2031;u64,mul;u64,c,8;u64,div scale 1 plus 0 bytes 8 view pointer definite when cmp u64,c,8;u64,v,706172616d2031;u64,mul;u64,c,8;u64,div in u64:0-2305843009213693951 diff --git a/test/WholeProgram/rfc0029-advance-outcomes.c b/test/WholeProgram/rfc0029-advance-outcomes.c new file mode 100644 index 00000000..bca736a2 --- /dev/null +++ b/test/WholeProgram/rfc0029-advance-outcomes.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/advance-outcomes/good.c %S/../evaluation/rfc0029/advance-outcomes/library.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/advance-outcomes/bad-failure.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: every return outcome must justify its advanced bytes. diff --git a/test/WholeProgram/rfc0029-anonymous-private-state.c b/test/WholeProgram/rfc0029-anonymous-private-state.c new file mode 100644 index 00000000..f3c53351 --- /dev/null +++ b/test/WholeProgram/rfc0029-anonymous-private-state.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/anonymous-private-state/state.c %S/../evaluation/rfc0029/anonymous-private-state/client.c -- 2>&1 | FileCheck %s --allow-empty +// CHECK-NOT: error: +// RFC 0029: anonymous typedefs retain private numeric outputs across units. diff --git a/test/WholeProgram/rfc0029-attached-payload-transfer.c b/test/WholeProgram/rfc0029-attached-payload-transfer.c new file mode 100644 index 00000000..1ef1e521 --- /dev/null +++ b/test/WholeProgram/rfc0029-attached-payload-transfer.c @@ -0,0 +1,6 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/attached-payload-transfer/good.c %S/../evaluation/rfc0029/attached-payload-transfer/library.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/attached-payload-transfer/leak.c %S/../evaluation/rfc0029/attached-payload-transfer/library.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: container operation loses part of the owned allocation footprint +// RFC 0029: a successful attach transfers both owned inputs plus its own +// fresh payload; the failure path keeps them separate, so losing the item +// on that path remains a rejection. diff --git a/test/WholeProgram/rfc0029-bounded-string-cursor.c b/test/WholeProgram/rfc0029-bounded-string-cursor.c new file mode 100644 index 00000000..625d5294 --- /dev/null +++ b/test/WholeProgram/rfc0029-bounded-string-cursor.c @@ -0,0 +1,6 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/bounded-string-cursor/finish.c %S/../evaluation/rfc0029/bounded-string-cursor/bounded.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/bounded-string-cursor/finish.c %S/../evaluation/rfc0029/bounded-string-cursor/earlier-zero.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/bounded-string-cursor/finish.c %S/../evaluation/rfc0029/bounded-string-cursor/uninitialized.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: callee terminated-within safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: strlen finds the first zero in an initialized bounded prefix. +// Neither a capacity field nor an isolated later zero initializes that prefix. diff --git a/test/WholeProgram/rfc0029-buffer-entry-intervals.c b/test/WholeProgram/rfc0029-buffer-entry-intervals.c new file mode 100644 index 00000000..9f973491 --- /dev/null +++ b/test/WholeProgram/rfc0029-buffer-entry-intervals.c @@ -0,0 +1,7 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/buffer-entry-intervals-reviewed/spare-physical.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/buffer-entry-intervals-reviewed/short-physical.c -- 2>&1 | FileCheck %s --check-prefix=SHORT +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/buffer-entry-intervals-reviewed/uninitialized-tail.c -- 2>&1 | FileCheck %s --check-prefix=UNINITIALIZED +// CLEAN-NOT: error: +// SHORT: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// UNINITIALIZED: cannot establish checked safety: callee initialized safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: capacity is a lower bound. Additional entry bytes must be proved. diff --git a/test/WholeProgram/rfc0029-byte-callee-frames.c b/test/WholeProgram/rfc0029-byte-callee-frames.c new file mode 100644 index 00000000..5004bc6c --- /dev/null +++ b/test/WholeProgram/rfc0029-byte-callee-frames.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-callee-frames/static.c %S/../evaluation/rfc0029/byte-callee-frames/library.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-callee-frames/wrong.c %S/../evaluation/rfc0029/byte-callee-frames/library.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-byte-comparisons.c b/test/WholeProgram/rfc0029-byte-comparisons.c new file mode 100644 index 00000000..1ae63575 --- /dev/null +++ b/test/WholeProgram/rfc0029-byte-comparisons.c @@ -0,0 +1,6 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-comparisons/bom-absent.c %S/../evaluation/rfc0029/byte-comparisons/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-comparisons/embedded-zero-memory.c %S/../evaluation/rfc0029/byte-comparisons/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-comparisons/unsigned-order.c %S/../evaluation/rfc0029/byte-comparisons/library.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-comparisons/bom-present.c %S/../evaluation/rfc0029/byte-comparisons/library.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: actual initialized bytes establish only the C comparison result's sign. diff --git a/test/WholeProgram/rfc0029-byte-cursor-content.c b/test/WholeProgram/rfc0029-byte-cursor-content.c new file mode 100644 index 00000000..d5bcec3d --- /dev/null +++ b/test/WholeProgram/rfc0029-byte-cursor-content.c @@ -0,0 +1,6 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-content/good.c %S/../evaluation/rfc0029/byte-cursor-content/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-content/offset.c %S/../evaluation/rfc0029/byte-cursor-content/library.c -- +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/byte-cursor-content/local.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-content/changed.c %S/../evaluation/rfc0029/byte-cursor-content/library.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: actual byte contents refine scans only while their initialized storage and contents remain current. diff --git a/test/WholeProgram/rfc0029-byte-cursor-forwarding.c b/test/WholeProgram/rfc0029-byte-cursor-forwarding.c new file mode 100644 index 00000000..b16df8c1 --- /dev/null +++ b/test/WholeProgram/rfc0029-byte-cursor-forwarding.c @@ -0,0 +1,6 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-forwarding/good.c %S/../evaluation/rfc0029/byte-cursor-forwarding/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-forwarding/offset.c %S/../evaluation/rfc0029/byte-cursor-forwarding/library.c -- +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/byte-cursor-forwarding/local.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-forwarding/changed.c %S/../evaluation/rfc0029/byte-cursor-forwarding/library.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: a read-only wrapper transports actual initialized byte contents and their proved readable interval to the checked scan. diff --git a/test/WholeProgram/rfc0029-byte-cursor-frames.c b/test/WholeProgram/rfc0029-byte-cursor-frames.c new file mode 100644 index 00000000..5ed4bbcd --- /dev/null +++ b/test/WholeProgram/rfc0029-byte-cursor-frames.c @@ -0,0 +1,6 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-frames/good.c %S/../evaluation/rfc0029/byte-cursor-frames/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-frames/offset.c %S/../evaluation/rfc0029/byte-cursor-frames/library.c -- +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/byte-cursor-frames/local.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-frames/changed.c %S/../evaluation/rfc0029/byte-cursor-frames/library.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: writes to a separate allocation and an exact local pointer cell preserve the input byte evidence; actual input mutation retires it. diff --git a/test/WholeProgram/rfc0029-byte-cursor-static.c b/test/WholeProgram/rfc0029-byte-cursor-static.c new file mode 100644 index 00000000..d13e9f04 --- /dev/null +++ b/test/WholeProgram/rfc0029-byte-cursor-static.c @@ -0,0 +1,6 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-static/good.c %S/../evaluation/rfc0029/byte-cursor-static/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-static/offset.c %S/../evaluation/rfc0029/byte-cursor-static/library.c -- +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/byte-cursor-static/local.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-cursor-static/changed.c %S/../evaluation/rfc0029/byte-cursor-static/library.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: immutable static local initializers establish contents; mutable static storage does not regain stale contents. diff --git a/test/WholeProgram/rfc0029-byte-global-frames.c b/test/WholeProgram/rfc0029-byte-global-frames.c new file mode 100644 index 00000000..ef3e6fd8 --- /dev/null +++ b/test/WholeProgram/rfc0029-byte-global-frames.c @@ -0,0 +1,6 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-global-frames/good.c %S/../evaluation/rfc0029/byte-global-frames/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-global-frames/offset.c %S/../evaluation/rfc0029/byte-global-frames/library.c -- +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/byte-global-frames/local.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-global-frames/changed.c %S/../evaluation/rfc0029/byte-global-frames/library.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: actual constant array bytes survive separate global writes; const pointer spelling supplies no immutable object premise. diff --git a/test/WholeProgram/rfc0029-byte-helper-frames.c b/test/WholeProgram/rfc0029-byte-helper-frames.c new file mode 100644 index 00000000..6b042597 --- /dev/null +++ b/test/WholeProgram/rfc0029-byte-helper-frames.c @@ -0,0 +1,6 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-helper-frames/good.c %S/../evaluation/rfc0029/byte-helper-frames/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-helper-frames/offset.c %S/../evaluation/rfc0029/byte-helper-frames/library.c -- +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/byte-helper-frames/local.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-helper-frames/changed.c %S/../evaluation/rfc0029/byte-helper-frames/library.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: complete helper writes to a confined automatic object preserve independently live separate byte inputs. diff --git a/test/WholeProgram/rfc0029-byte-loop-partitions.c b/test/WholeProgram/rfc0029-byte-loop-partitions.c new file mode 100644 index 00000000..f41c7861 --- /dev/null +++ b/test/WholeProgram/rfc0029-byte-loop-partitions.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-loop-partitions/good.c %S/../evaluation/rfc0029/byte-loop-partitions/while.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-loop-partitions/good.c %S/../evaluation/rfc0029/byte-loop-partitions/do.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-loop-partitions/good.c %S/../evaluation/rfc0029/byte-loop-partitions/skip.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: access interval must fit its object diff --git a/test/WholeProgram/rfc0029-byte-switch-partitions.c b/test/WholeProgram/rfc0029-byte-switch-partitions.c new file mode 100644 index 00000000..3b221790 --- /dev/null +++ b/test/WholeProgram/rfc0029-byte-switch-partitions.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-switch-partitions-reviewed/direct.c %S/../evaluation/rfc0029/byte-switch-partitions-reviewed/scanner.c %S/../evaluation/rfc0029/byte-switch-partitions-reviewed/wrapper.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/byte-switch-partitions-reviewed/beyond-bound.c %S/../evaluation/rfc0029/byte-switch-partitions-reviewed/scanner.c %S/../evaluation/rfc0029/byte-switch-partitions-reviewed/wrapper.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-cast-reader-intervals.c b/test/WholeProgram/rfc0029-cast-reader-intervals.c new file mode 100644 index 00000000..97b106b4 --- /dev/null +++ b/test/WholeProgram/rfc0029-cast-reader-intervals.c @@ -0,0 +1,5 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/cast-reader-intervals/library.c %S/../evaluation/rfc0029/cast-reader-intervals/good.c -- +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/cast-reader-intervals/scaled-good.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/cast-reader-intervals/scaled-bad.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: runtime access interval must fit its object [weavec::checking-incomplete] +// RFCs 0004/0029: pointer casts preserve the original scaled byte offset. diff --git a/test/WholeProgram/rfc0029-comparison-container-frames.c b/test/WholeProgram/rfc0029-comparison-container-frames.c new file mode 100644 index 00000000..593f4fe3 --- /dev/null +++ b/test/WholeProgram/rfc0029-comparison-container-frames.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/comparison-container-frames-reviewed/bounded.c %S/../evaluation/rfc0029/comparison-container-frames-reviewed/bounded-inspect.c %S/../evaluation/rfc0029/comparison-container-frames-reviewed/reader.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/comparison-container-frames-reviewed/uninitialized.c %S/../evaluation/rfc0029/comparison-container-frames-reviewed/uninitialized-inspect.c %S/../evaluation/rfc0029/comparison-container-frames-reviewed/reader.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-conditional-count-arguments.c b/test/WholeProgram/rfc0029-conditional-count-arguments.c new file mode 100644 index 00000000..bf371e41 --- /dev/null +++ b/test/WholeProgram/rfc0029-conditional-count-arguments.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/conditional-count-arguments/good.c %S/../evaluation/rfc0029/conditional-count-arguments/library.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/conditional-count-arguments/changed.c %S/../evaluation/rfc0029/conditional-count-arguments/library.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: each initialized output byte needs proof on every return. diff --git a/test/WholeProgram/rfc0029-container-alias-outputs.c b/test/WholeProgram/rfc0029-container-alias-outputs.c new file mode 100644 index 00000000..8fcb6848 --- /dev/null +++ b/test/WholeProgram/rfc0029-container-alias-outputs.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/container-alias-outputs/forwarded.c %S/../evaluation/rfc0029/container-alias-outputs/update.c %S/../evaluation/rfc0029/container-alias-outputs/forward.c %S/../evaluation/rfc0029/container-alias-outputs/drop.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/container-alias-outputs/disowned.c %S/../evaluation/rfc0029/container-alias-outputs/disown.c %S/../evaluation/rfc0029/container-alias-outputs/forward.c %S/../evaluation/rfc0029/container-alias-outputs/drop.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-container-outcome-frames.c b/test/WholeProgram/rfc0029-container-outcome-frames.c new file mode 100644 index 00000000..e5f4a2b9 --- /dev/null +++ b/test/WholeProgram/rfc0029-container-outcome-frames.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/container-outcome-frames/helper.c %S/../evaluation/rfc0029/container-outcome-frames/helper-inspect.c %S/../evaluation/rfc0029/container-outcome-frames/forward.c %S/../evaluation/rfc0029/container-outcome-frames/drop.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/container-outcome-frames/disowned.c %S/../evaluation/rfc0029/container-outcome-frames/disowned-inspect.c %S/../evaluation/rfc0029/container-outcome-frames/forward.c %S/../evaluation/rfc0029/container-outcome-frames/drop.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-counter-reset-ranges.c b/test/WholeProgram/rfc0029-counter-reset-ranges.c new file mode 100644 index 00000000..152b9f3b --- /dev/null +++ b/test/WholeProgram/rfc0029-counter-reset-ranges.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/counter-reset-ranges-reviewed/good.c %S/../evaluation/rfc0029/counter-reset-ranges-reviewed/library.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/counter-reset-ranges-reviewed/released.c -- 2>&1 | FileCheck %s +// CHECK: after it was freed +// RFC 0029: a reset index cannot destroy the unchanged count's proved range. diff --git a/test/WholeProgram/rfc0029-fixed-span-steps.c b/test/WholeProgram/rfc0029-fixed-span-steps.c new file mode 100644 index 00000000..5e471f33 --- /dev/null +++ b/test/WholeProgram/rfc0029-fixed-span-steps.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/fixed-span-steps/runtime.c %S/../evaluation/rfc0029/fixed-span-steps/library.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/fixed-span-steps/over-count.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: formed pointer must remain within its object or one past it [weavec::checking-incomplete] +// RFC 0029: constant steps retain a verified span bound, never an overstated count. diff --git a/test/WholeProgram/rfc0029-floating-call-premises.c b/test/WholeProgram/rfc0029-floating-call-premises.c new file mode 100644 index 00000000..98527991 --- /dev/null +++ b/test/WholeProgram/rfc0029-floating-call-premises.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/floating-call-premises/forwarded.c %S/../evaluation/rfc0029/floating-call-premises/clamp.c %S/../evaluation/rfc0029/floating-call-premises/forward.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/floating-call-premises/nan.c %S/../evaluation/rfc0029/floating-call-premises/clamp.c %S/../evaluation/rfc0029/floating-call-premises/forward.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: unsupported checked C construct or storage type diff --git a/test/WholeProgram/rfc0029-guarded-advance.c b/test/WholeProgram/rfc0029-guarded-advance.c new file mode 100644 index 00000000..09432dbf --- /dev/null +++ b/test/WholeProgram/rfc0029-guarded-advance.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/guarded-advance/good.c %S/../evaluation/rfc0029/guarded-advance/library.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/guarded-advance/changed.c %S/../evaluation/rfc0029/guarded-advance/library.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: the actual unmodified result must establish the cursor guarantee. diff --git a/test/WholeProgram/rfc0029-guarded-cursor-bounds.c b/test/WholeProgram/rfc0029-guarded-cursor-bounds.c new file mode 100644 index 00000000..adde19ce --- /dev/null +++ b/test/WholeProgram/rfc0029-guarded-cursor-bounds.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/guarded-cursor-bounds/good.c %S/../evaluation/rfc0029/guarded-cursor-bounds/library.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/guarded-cursor-bounds/changed.c %S/../evaluation/rfc0029/guarded-cursor-bounds/library.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: the actual unmodified result must establish the cursor guarantee. diff --git a/test/WholeProgram/rfc0029-helper-cursor-pairs.c b/test/WholeProgram/rfc0029-helper-cursor-pairs.c new file mode 100644 index 00000000..e93324c0 --- /dev/null +++ b/test/WholeProgram/rfc0029-helper-cursor-pairs.c @@ -0,0 +1,6 @@ +// RUN: %weavec --checked-function=decode %S/../evaluation/rfc0029/helper-cursor-pairs/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/helper-cursor-pairs/good.c %S/../evaluation/rfc0029/helper-cursor-pairs/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/helper-cursor-pairs/mixed.c %S/../evaluation/rfc0029/helper-cursor-pairs/library.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/helper-cursor-pairs/short.c %S/../evaluation/rfc0029/helper-cursor-pairs/library.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: helper cursor offsets must survive every loop path; zero bytes refute only actual nonzero reads. diff --git a/test/WholeProgram/rfc0029-in-place-extension.c b/test/WholeProgram/rfc0029-in-place-extension.c new file mode 100644 index 00000000..9194a5c3 --- /dev/null +++ b/test/WholeProgram/rfc0029-in-place-extension.c @@ -0,0 +1,7 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/in-place-extension/recursive.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/in-place-extension/lost-child.c -- 2>&1 | FileCheck %s --check-prefix=LOST +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/in-place-extension/nondecreasing.c -- 2>&1 | FileCheck %s --check-prefix=PROGRESS +// CLEAN-NOT: error: +// LOST: 'c' is leaked [weavec::leak] +// PROGRESS: recursive proof cycle has no strict progress +// RFC 0029: the caller retains its head; every fresh descendant needs cleanup. diff --git a/test/WholeProgram/rfc0029-independent-cursors.c b/test/WholeProgram/rfc0029-independent-cursors.c new file mode 100644 index 00000000..034f0426 --- /dev/null +++ b/test/WholeProgram/rfc0029-independent-cursors.c @@ -0,0 +1,5 @@ +// RUN: %weavec --checked-function=copy_bytes %S/../evaluation/rfc0029/independent-cursors/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/independent-cursors/good.c %S/../evaluation/rfc0029/independent-cursors/library.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/independent-cursors/extra-step.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: access interval must fit its object [weavec::checking-incomplete] +// RFC 0029: independently proved numeric cursor relations grant no pointer identity. diff --git a/test/WholeProgram/rfc0029-initialized-advance.c b/test/WholeProgram/rfc0029-initialized-advance.c new file mode 100644 index 00000000..2ff9df7b --- /dev/null +++ b/test/WholeProgram/rfc0029-initialized-advance.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/initialized-advance/good.c %S/../evaluation/rfc0029/initialized-advance/library.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/initialized-advance/over-advance.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: each initialized output byte needs proof on every return. diff --git a/test/WholeProgram/rfc0029-initialized-spans.c b/test/WholeProgram/rfc0029-initialized-spans.c new file mode 100644 index 00000000..6d9043ca --- /dev/null +++ b/test/WholeProgram/rfc0029-initialized-spans.c @@ -0,0 +1,5 @@ +// RUN: %weavec --checked-function=pair %S/../evaluation/rfc0029/initialized-spans/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/initialized-spans/library.c %S/../evaluation/rfc0029/initialized-spans/good.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/initialized-spans/library.c %S/../evaluation/rfc0029/initialized-spans/unrelated.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: callee requires a live initialized same-array byte span [weavec::checking-incomplete] +// RFC 0029: a span is an explicit caller premise, never pointer-name evidence. diff --git a/test/WholeProgram/rfc0029-local-callee-copies.c b/test/WholeProgram/rfc0029-local-callee-copies.c new file mode 100644 index 00000000..b0eb489c --- /dev/null +++ b/test/WholeProgram/rfc0029-local-callee-copies.c @@ -0,0 +1,5 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/local-callee-copies/numeric.c -- +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/local-callee-copies/copy.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/local-callee-copies/freed-alias.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: a confined local output keeps ownership, including cleanup duty. diff --git a/test/WholeProgram/rfc0029-numeric-container-frames.c b/test/WholeProgram/rfc0029-numeric-container-frames.c new file mode 100644 index 00000000..c763ca0f --- /dev/null +++ b/test/WholeProgram/rfc0029-numeric-container-frames.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/numeric-container-frames/local-end.c %S/../evaluation/rfc0029/numeric-container-frames/local-end-inspect.c %S/../evaluation/rfc0029/numeric-container-frames/reader.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/numeric-container-frames/owned-end.c %S/../evaluation/rfc0029/numeric-container-frames/owned-end-inspect.c %S/../evaluation/rfc0029/numeric-container-frames/reader.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-numeric-scan-locals.c b/test/WholeProgram/rfc0029-numeric-scan-locals.c new file mode 100644 index 00000000..1ad853ef --- /dev/null +++ b/test/WholeProgram/rfc0029-numeric-scan-locals.c @@ -0,0 +1,4 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-scan-locals/assignment.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-scan-locals/source-write.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: unsupported checked C construct or storage type [weavec::checking-incomplete] +// RFC 0029: private scalar writes preserve byte facts; source writes retire them. diff --git a/test/WholeProgram/rfc0029-numeric-scans.c b/test/WholeProgram/rfc0029-numeric-scans.c new file mode 100644 index 00000000..62e90bb0 --- /dev/null +++ b/test/WholeProgram/rfc0029-numeric-scans.c @@ -0,0 +1,4 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-scans/good.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-scans/permissive.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: unsupported checked C construct or storage type [weavec::checking-incomplete] +// RFC 0029: numeric bytes exclude NaN only while actual contents remain proved. diff --git a/test/WholeProgram/rfc0029-numeric-short-circuit.c b/test/WholeProgram/rfc0029-numeric-short-circuit.c new file mode 100644 index 00000000..6c36b658 --- /dev/null +++ b/test/WholeProgram/rfc0029-numeric-short-circuit.c @@ -0,0 +1,4 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-short-circuit/left.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-short-circuit/bypass.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: unsupported checked C construct or storage type [weavec::checking-incomplete] +// RFC 0029: short-circuit tests retain only the already-visited numeric prefix. diff --git a/test/WholeProgram/rfc0029-numeric-text.c b/test/WholeProgram/rfc0029-numeric-text.c new file mode 100644 index 00000000..0cb6b794 --- /dev/null +++ b/test/WholeProgram/rfc0029-numeric-text.c @@ -0,0 +1,4 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-text/stores.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/numeric-text/nan.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: unsupported checked C construct or storage type [weavec::checking-incomplete] +// RFC 0029: numeric bytes exclude NaN only while actual contents remain proved. diff --git a/test/WholeProgram/rfc0029-output-construction.c b/test/WholeProgram/rfc0029-output-construction.c new file mode 100644 index 00000000..703fc3dd --- /dev/null +++ b/test/WholeProgram/rfc0029-output-construction.c @@ -0,0 +1,7 @@ +// RUN: %weavec --checked-function=build --checked-function=main %S/../evaluation/rfc0029/output-construction/build.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/output-transport/client.c %S/../evaluation/rfc0029/output-transport/library.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --checked-function=build %S/../evaluation/rfc0029/output-construction/uncleared-failure.c -- 2>&1 | FileCheck %s --check-prefix=OUTPUT +// CLEAN-NOT: error: +// OUTPUT: cannot establish checked safety: recursive construction does not establish its complete output contract [weavec::checking-incomplete] +// RFC 0029: an output-slot candidate needs actual failure nullness and a +// complete fresh forest on every successful return. diff --git a/test/WholeProgram/rfc0029-paired-cursor-loops.c b/test/WholeProgram/rfc0029-paired-cursor-loops.c new file mode 100644 index 00000000..9466be4f --- /dev/null +++ b/test/WholeProgram/rfc0029-paired-cursor-loops.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/paired-cursor-loops/good.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/paired-cursor-loops/over-step.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: an earlier scan bounds each unit step of the following cursor. diff --git a/test/WholeProgram/rfc0029-paired-reader-counters.c b/test/WholeProgram/rfc0029-paired-reader-counters.c new file mode 100644 index 00000000..202a488b --- /dev/null +++ b/test/WholeProgram/rfc0029-paired-reader-counters.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/paired-reader-counters/library.c %S/../evaluation/rfc0029/paired-reader-counters/good.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/paired-reader-counters/too-many.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: count equality needs actual equal initial values and preserved steps. diff --git a/test/WholeProgram/rfc0029-payload-early-exits.c b/test/WholeProgram/rfc0029-payload-early-exits.c new file mode 100644 index 00000000..727d48da --- /dev/null +++ b/test/WholeProgram/rfc0029-payload-early-exits.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-early-exits/client.c %S/../evaluation/rfc0029/payload-early-exits/goto.c %S/../evaluation/rfc0029/payload-early-exits/drop.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-early-exits/client.c %S/../evaluation/rfc0029/payload-early-exits/early.c %S/../evaluation/rfc0029/payload-early-exits/drop.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-early-exits/client.c %S/../evaluation/rfc0029/payload-early-exits/released-head.c %S/../evaluation/rfc0029/payload-early-exits/drop.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-payload-publication.c b/test/WholeProgram/rfc0029-payload-publication.c new file mode 100644 index 00000000..e45ab56f --- /dev/null +++ b/test/WholeProgram/rfc0029-payload-publication.c @@ -0,0 +1,5 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-publication/client.c %S/../evaluation/rfc0029/payload-publication/good.c %S/../evaluation/rfc0029/payload-publication/drop.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-publication/client.c %S/../evaluation/rfc0029/payload-publication/helper.c %S/../evaluation/rfc0029/payload-publication/drop.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-publication/client.c %S/../evaluation/rfc0029/payload-publication/duplicate.c %S/../evaluation/rfc0029/payload-publication/drop.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: container operation loses part of the owned allocation footprint [weavec::checking-incomplete] +// RFC 0029: publication transfers a proved live allocation exactly once. diff --git a/test/WholeProgram/rfc0029-payload-reader-returns.c b/test/WholeProgram/rfc0029-payload-reader-returns.c new file mode 100644 index 00000000..724df2ad --- /dev/null +++ b/test/WholeProgram/rfc0029-payload-reader-returns.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-reader-returns/client.c %S/../evaluation/rfc0029/payload-reader-returns/wrap.c %S/../evaluation/rfc0029/payload-reader-returns/good.c %S/../evaluation/rfc0029/payload-reader-returns/drop.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-reader-returns/client.c %S/../evaluation/rfc0029/payload-reader-returns/wrap.c %S/../evaluation/rfc0029/payload-reader-returns/leak.c %S/../evaluation/rfc0029/payload-reader-returns/drop.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-payload-relocation.c b/test/WholeProgram/rfc0029-payload-relocation.c new file mode 100644 index 00000000..540ad5d1 --- /dev/null +++ b/test/WholeProgram/rfc0029-payload-relocation.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-relocation/helper.c %S/../evaluation/rfc0029/payload-relocation/fill.c %S/../evaluation/rfc0029/payload-relocation/drop.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-relocation/duplicate.c %S/../evaluation/rfc0029/payload-relocation/fill.c %S/../evaluation/rfc0029/payload-relocation/drop.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-payload-write-frames.c b/test/WholeProgram/rfc0029-payload-write-frames.c new file mode 100644 index 00000000..f8f90d3c --- /dev/null +++ b/test/WholeProgram/rfc0029-payload-write-frames.c @@ -0,0 +1,5 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-write-frames/client.c %S/../evaluation/rfc0029/payload-write-frames/good.c %S/../evaluation/rfc0029/payload-write-frames/drop.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-write-frames/client.c %S/../evaluation/rfc0029/payload-write-frames/helper.c %S/../evaluation/rfc0029/payload-write-frames/drop.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/payload-write-frames/aliased-client.c %S/../evaluation/rfc0029/payload-write-frames/good.c %S/../evaluation/rfc0029/payload-write-frames/drop.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: callee requires separated input objects [weavec::checking-incomplete] +// RFC 0029: a represented cursor store must be separate from the incoming head. diff --git a/test/WholeProgram/rfc0029-pointer-count-readers.c b/test/WholeProgram/rfc0029-pointer-count-readers.c new file mode 100644 index 00000000..3421692f --- /dev/null +++ b/test/WholeProgram/rfc0029-pointer-count-readers.c @@ -0,0 +1,4 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/pointer-count-readers/good.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/pointer-count-readers/short.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: inferred pointer/count input intervals remain caller obligations. diff --git a/test/WholeProgram/rfc0029-pointer-difference-sizes.c b/test/WholeProgram/rfc0029-pointer-difference-sizes.c new file mode 100644 index 00000000..84937661 --- /dev/null +++ b/test/WholeProgram/rfc0029-pointer-difference-sizes.c @@ -0,0 +1,5 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/pointer-difference-sizes/good.c -- +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/pointer-difference-sizes/offset.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/pointer-difference-sizes/undersized.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: access interval must fit its object [weavec::checking-incomplete] +// RFC 0029: pointer differences retain their validated coordinate and allocation-time identity. diff --git a/test/WholeProgram/rfc0029-pointer-reader-offsets.c b/test/WholeProgram/rfc0029-pointer-reader-offsets.c new file mode 100644 index 00000000..4e271f95 --- /dev/null +++ b/test/WholeProgram/rfc0029-pointer-reader-offsets.c @@ -0,0 +1,5 @@ +// RUN: %weavec --checked-function=scan %S/../evaluation/rfc0029/pointer-reader-offsets/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/pointer-reader-offsets/library.c %S/../evaluation/rfc0029/pointer-reader-offsets/good.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/pointer-reader-offsets/unrelated.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: pointer difference or ordering needs shared-object evidence [weavec::checking-incomplete] +// RFC 0029: pointer-difference guard projection requires established operands. diff --git a/test/WholeProgram/rfc0029-reader-construction.c b/test/WholeProgram/rfc0029-reader-construction.c new file mode 100644 index 00000000..bedc94a5 --- /dev/null +++ b/test/WholeProgram/rfc0029-reader-construction.c @@ -0,0 +1,9 @@ +// RUN: %weavec --checked-function=build --checked-function=main %S/../evaluation/rfc0029/reader-construction/build.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --checked-function=build --checked-function=main %S/../evaluation/rfc0029/mutable-reader-construction/build.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --checked-function=build %S/../evaluation/rfc0029/reader-construction/cycle.c -- 2>&1 | FileCheck %s --check-prefix=PROGRESS +// RUN: not %weavec --checked-function=build %S/../evaluation/rfc0029/mutable-reader-construction/leak.c -- 2>&1 | FileCheck %s --check-prefix=CLEANUP +// CLEAN-NOT: error: +// PROGRESS: recursive construction does not establish its complete output contract [weavec::checking-incomplete] +// CLEANUP: container operation loses part of the owned allocation footprint [weavec::checking-incomplete] +// RFC 0029: record layout nominates roles; the initialized interval, progress +// and complete allocation footprint still require independent evidence. diff --git a/test/WholeProgram/rfc0029-reader-index-loops.c b/test/WholeProgram/rfc0029-reader-index-loops.c new file mode 100644 index 00000000..643e647b --- /dev/null +++ b/test/WholeProgram/rfc0029-reader-index-loops.c @@ -0,0 +1,5 @@ +// RUN: %weavec --checked-function=scan %S/../evaluation/rfc0029/reader-index-loops/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/reader-index-loops/library.c %S/../evaluation/rfc0029/reader-index-loops/good.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/reader-index-loops/body-index.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: callee extent safety precondition must hold [weavec::checking-incomplete] +// RFC 0029: strict unit-stride induction requires an unchanged index and reader. diff --git a/test/WholeProgram/rfc0029-reader-projection.c b/test/WholeProgram/rfc0029-reader-projection.c new file mode 100644 index 00000000..4ac919b7 --- /dev/null +++ b/test/WholeProgram/rfc0029-reader-projection.c @@ -0,0 +1,7 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/readers/client.c %S/../evaluation/rfc0029/readers/cursor.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/readers/short.c %S/../evaluation/rfc0029/readers/cursor.c -- 2>&1 | FileCheck %s --check-prefix=SHORT +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/readers/uninitialized.c %S/../evaluation/rfc0029/readers/cursor.c -- 2>&1 | FileCheck %s --check-prefix=TAIL +// CLEAN-NOT: error: +// SHORT: cannot establish checked safety: callee buffer allocation extent and initialized-prefix precondition must hold [weavec::checking-incomplete] +// TAIL: cannot establish checked safety: callee buffer allocation extent and initialized-prefix precondition must hold [weavec::checking-incomplete] +// RFC 0029: a changing field cursor requires the whole iteration envelope. diff --git a/test/WholeProgram/rfc0029-recursive-construction.c b/test/WholeProgram/rfc0029-recursive-construction.c new file mode 100644 index 00000000..9f08f94e --- /dev/null +++ b/test/WholeProgram/rfc0029-recursive-construction.c @@ -0,0 +1,9 @@ +// RUN: %weavec --checked-function=build --checked-function=main %S/../evaluation/rfc0029/construction/build.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/recursive-transport/client.c %S/../evaluation/rfc0029/recursive-transport/library.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --checked-function=build %S/../evaluation/rfc0029/construction/cycle.c -- 2>&1 | FileCheck %s --check-prefix=CYCLE +// RUN: not %weavec --checked-function=build %S/../evaluation/rfc0029/construction/leak.c -- 2>&1 | FileCheck %s --check-prefix=LEAK +// CLEAN-NOT: error: +// CYCLE: cannot establish checked safety: recursive proof cycle has no strict progress [weavec::checking-incomplete] +// LEAK: cannot establish checked safety: container operation loses part of the owned allocation footprint [weavec::checking-incomplete] +// RFC 0029: fresh recursive outputs require decreasing initialized inputs and +// complete allocation accounting on success and failure paths. diff --git a/test/WholeProgram/rfc0029-recursive-writer.c b/test/WholeProgram/rfc0029-recursive-writer.c new file mode 100644 index 00000000..e0632953 --- /dev/null +++ b/test/WholeProgram/rfc0029-recursive-writer.c @@ -0,0 +1,6 @@ +// RUN: %weavec --checked-function=emit --checked-function=main %S/../evaluation/rfc0029/recursive-writer-reviewed/prefix.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --checked-function=emit --checked-function=other --checked-function=main %S/../evaluation/rfc0029/recursive-writer-reviewed/mutual.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --checked-function=emit %S/../evaluation/rfc0029/recursive-writer-reviewed/false-prefix.c -- 2>&1 | FileCheck %s --check-prefix=OUTPUT +// CLEAN-NOT: error: +// OUTPUT: recursive writer does not establish its complete output contract [weavec::checking-incomplete] +// RFC 0029: a returning writer must prove its actual initialized prefix. diff --git a/test/WholeProgram/rfc0029-reverse-byte-writes.c b/test/WholeProgram/rfc0029-reverse-byte-writes.c new file mode 100644 index 00000000..20e3f77a --- /dev/null +++ b/test/WholeProgram/rfc0029-reverse-byte-writes.c @@ -0,0 +1,5 @@ +// RUN: %weavec --checked-function=encode %S/../evaluation/rfc0029/reverse-byte-writes/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/reverse-byte-writes/library.c %S/../evaluation/rfc0029/reverse-byte-writes/good.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/reverse-byte-writes/library.c %S/../evaluation/rfc0029/reverse-byte-writes/short.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: sufficient scalar envelopes retain actual caller bounds checks. diff --git a/test/WholeProgram/rfc0029-reverse-initialization.c b/test/WholeProgram/rfc0029-reverse-initialization.c new file mode 100644 index 00000000..0e648fe3 --- /dev/null +++ b/test/WholeProgram/rfc0029-reverse-initialization.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/reverse-initialization/good.c %S/../evaluation/rfc0029/reverse-initialization/library.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/reverse-initialization/skipped.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: each initialized output byte needs proof on every return. diff --git a/test/WholeProgram/rfc0029-shifted-pointee-facts.c b/test/WholeProgram/rfc0029-shifted-pointee-facts.c new file mode 100644 index 00000000..3c6a029c --- /dev/null +++ b/test/WholeProgram/rfc0029-shifted-pointee-facts.c @@ -0,0 +1,4 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/shifted-pointee-facts/same.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/shifted-pointee-facts/shift.c -- 2>&1 | FileCheck %s +// CHECK: access interval must fit its object +// RFC 0029: an element displacement must not copy the original pointee value. diff --git a/test/WholeProgram/rfc0029-singleton-link-ownership.c b/test/WholeProgram/rfc0029-singleton-link-ownership.c new file mode 100644 index 00000000..bb1d2c32 --- /dev/null +++ b/test/WholeProgram/rfc0029-singleton-link-ownership.c @@ -0,0 +1,5 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/singleton-link-ownership/client.c %S/../evaluation/rfc0029/singleton-link-ownership/direct.c %S/../evaluation/rfc0029/singleton-link-ownership/make.c %S/../evaluation/rfc0029/singleton-link-ownership/drop.c %S/../evaluation/rfc0029/singleton-link-ownership/step.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/singleton-link-ownership/client.c %S/../evaluation/rfc0029/singleton-link-ownership/forwarded.c %S/../evaluation/rfc0029/singleton-link-ownership/make.c %S/../evaluation/rfc0029/singleton-link-ownership/drop.c %S/../evaluation/rfc0029/singleton-link-ownership/step.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/singleton-link-ownership/client.c %S/../evaluation/rfc0029/singleton-link-ownership/disowned.c %S/../evaluation/rfc0029/singleton-link-ownership/make.c %S/../evaluation/rfc0029/singleton-link-ownership/drop.c %S/../evaluation/rfc0029/singleton-link-ownership/step.c -- 2>&1 | FileCheck %s +// CHECK: error: cannot establish checked safety: allocation transfer has no surviving portable output guarantee [weavec::checking-incomplete] +// RFC 0029: every accounted acquisition needs a surviving caller-side carrier. diff --git a/test/WholeProgram/rfc0029-span-advances.c b/test/WholeProgram/rfc0029-span-advances.c new file mode 100644 index 00000000..63b62f3c --- /dev/null +++ b/test/WholeProgram/rfc0029-span-advances.c @@ -0,0 +1,4 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/span-advances/good.c %S/../evaluation/rfc0029/span-advances/library.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/span-advances/over-step.c %S/../evaluation/rfc0029/span-advances/library.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: each returned step must fit the actual captured remaining span. diff --git a/test/WholeProgram/rfc0029-span-count-joins.c b/test/WholeProgram/rfc0029-span-count-joins.c new file mode 100644 index 00000000..61fecf4c --- /dev/null +++ b/test/WholeProgram/rfc0029-span-count-joins.c @@ -0,0 +1,5 @@ +// RUN: %weavec --checked-function=probe %S/../evaluation/rfc0029/span-count-joins/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/span-count-joins/library.c %S/../evaluation/rfc0029/span-count-joins/good.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/span-count-joins/changed-count.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: assignment/guard order cannot replace all-path count evidence. diff --git a/test/WholeProgram/rfc0029-span-counts.c b/test/WholeProgram/rfc0029-span-counts.c new file mode 100644 index 00000000..7a54deac --- /dev/null +++ b/test/WholeProgram/rfc0029-span-counts.c @@ -0,0 +1,5 @@ +// RUN: %weavec --checked-function=probe %S/../evaluation/rfc0029/span-counts/library.c -- +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/span-counts/library.c %S/../evaluation/rfc0029/span-counts/good.c -- +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/span-counts/false-count.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: +// RFC 0029: count bounds are proved at every return against entry endpoints. diff --git a/test/WholeProgram/rfc0029-string-length-copies.c b/test/WholeProgram/rfc0029-string-length-copies.c new file mode 100644 index 00000000..965c35a4 --- /dev/null +++ b/test/WholeProgram/rfc0029-string-length-copies.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/string-length-copies/client.c %S/../evaluation/rfc0029/string-length-copies/callback.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/string-length-copies/client.c %S/../evaluation/rfc0029/string-length-copies/overread.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-temporary-release-frames.c b/test/WholeProgram/rfc0029-temporary-release-frames.c new file mode 100644 index 00000000..9a9ee5e9 --- /dev/null +++ b/test/WholeProgram/rfc0029-temporary-release-frames.c @@ -0,0 +1,3 @@ +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/temporary-release-frames/client.c %S/../evaluation/rfc0029/temporary-release-frames/guarded.c %S/../evaluation/rfc0029/temporary-release-frames/reader.c -- +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/temporary-release-frames/client.c %S/../evaluation/rfc0029/temporary-release-frames/attached.c %S/../evaluation/rfc0029/temporary-release-frames/reader.c -- 2>&1 | FileCheck %s +// CHECK: cannot establish checked safety: diff --git a/test/WholeProgram/rfc0029-workflows.c b/test/WholeProgram/rfc0029-workflows.c new file mode 100644 index 00000000..9830612a --- /dev/null +++ b/test/WholeProgram/rfc0029-workflows.c @@ -0,0 +1,9 @@ +// RUN: %weavec --checked-function=main %S/../evaluation/rfc0029/mutual-cleanup.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/transport/client.c %S/../evaluation/rfc0029/transport/library.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: %weavec --whole-program --checked-function=main --checked-function=encode_client %S/../evaluation/rfc0029/serializer/client.c %S/../evaluation/rfc0029/serializer/hex.c -- 2>&1 | FileCheck %s --check-prefix=CLEAN --allow-empty +// RUN: not %weavec --whole-program --checked-function=main %S/../evaluation/rfc0029/transport/bad.c %S/../evaluation/rfc0029/transport/library.c -- 2>&1 | FileCheck %s --check-prefix=BAD +// RUN: not %weavec --checked-function=main %S/../evaluation/rfc0029/offsets/forward.c -- 2>&1 | FileCheck %s --check-prefix=OFFSET +// CLEAN-NOT: error: +// BAD: cannot establish checked safety: access interval must fit its object [weavec::checking-incomplete] +// OFFSET: checked safety failed: 'p' is released but points 1 element past the start of its allocation [weavec::checking-failed] +// RFC 0029: recursive group proof, imported state roles and actual callbacks. diff --git a/test/evaluation/rfc0029/README.md b/test/evaluation/rfc0029/README.md new file mode 100644 index 00000000..470ddc9e --- /dev/null +++ b/test/evaluation/rfc0029/README.md @@ -0,0 +1,112 @@ +# RFC 0029 workflow populations + +`manifest.json` and the C inputs were frozen against the immutable baseline +binary before checker changes. `frozen-sha256.json` binds those files; never +rewrite it to accommodate an outcome. The `upstream` population independently +binds unchanged pinned cJSON sources. Its parse/delete and print/delete cases +remain mandatory RFC goals even when the primary population passes. + +`serializer` is an independent streaming hexadecimal encoder with reordered +state fields, runtime input, repeated allocation growth, failure cleanup and a +short-input counterpart. `transport` combines mutually recursive cleanup with +allocator and releaser callbacks across source units. Each later population +has its own freeze and provenance, preceding its first analyzer run. + +Run each population with: + +```sh +python3 scripts/checked-workflows.py --weavec build/dev/bin/weavec \ + --cc build/dev/bin/weavec-cc --clang clang \ + --population source --output build/rfc0029-source +``` + +Other populations: `transport`, `serializer`, `readers`, `offsets`, `traversal`, +`construction`, `mutual-construction`, `construction-helpers`, +`output-construction`, `output-transport`, `output-cache`, +`construction-oracle`, `corpus-regressions`, `recursive-transport`, +`recursive-cache`, `objects`, `cache`, `upstream`. +All except upstream run in CTest. The optional upstream runner requires the pinned +checkout under `build/corpus/cJSON-program`; it retains failing observations. + +The later `readers` population preserves its original probe manifest and uses +an independently frozen reviewed manifest. Its audit documents why a guarded +out-of-range cursor is safe and replaces that invalid negative with an actual +escaped access. Truncated and partially initialized input exposed a pre-existing +false proof: only the incoming cursor cell had been required for an entire loop. +Both remain mandatory rejections through source and ordinary-object transport. + +The `offsets` population was frozen during the candidate-17 proof-boundary +review. It requires exact recursive arguments: an interior or one-past pointer +cannot borrow the induction hypothesis for the allocation's base node. + +`traversal` checks read-only recursive forests and includes a separately frozen +review of a safe forwarding cycle: an unchanged edge is permitted when every +cycle also contains a strict child edge. The original negative expectation and +failed development observation remain intact. A cycle with no strict edge is +a required rejection. + +`construction` checks runtime-length fresh chains and failure allocation +accounting. `mutual-construction` audits atomic publication and unchanged +forwarding between constructors. `construction-helpers` adds a binary tree +whose second recursive allocation may fail after its left subtree is built; +a complete inferred helper must release that subtree. `recursive-transport` +composes construction, traversal and destruction across source units, and +`recursive-cache` compares their full cold/warm/uncached/compact reports and +dependency invalidation. Object generation lowers ordinary WeaveC errors to +warnings so checked linking independently exercises negative fixtures. + +`output-construction` freezes success-returning constructors that publish an +owned forest through a pointer slot. Its failures must actually leave that +slot null, and its success must publish the whole forest. False success, +unchanged failure slots, lost or repeated cleanup, null slots, short input and +nondecreasing recursion have separate cases. `output-transport` splits the +constructor, traversal and cleanup from their client; `output-cache` checks +equivalent reports, reuse and invalidation. The concrete allocation oracle +also covers all three allocation failure points of this output-slot client. + +`construction-oracle` instruments allocation/release at runtime, independently +of the abstract checker. It runs each frozen finite positive with failure at +every allocation point, and rejects its leak and repeated-cleanup mutants. +ASan/UBSan also checks the executed paths. This finite ledger is corroborating +evidence, not a proof for arbitrary runtime inputs. + +`reader-construction` and `mutable-reader-construction` each freeze seven +recursive constructor cases with an extra reader field, runtime input lengths, +and their truncated, uninitialized, nondecreasing, escaped-interval and cleanup +counterparts. Both run through source, ordinary objects and the concrete +allocation-failure oracle. Copied readers preserve their caller record; mutable +reader hypotheses supply no cursor/count postcondition. These populations do +not replace the unchanged mandatory cJSON parser and serializer clients. + +`corpus-regressions` preserves reductions of two lost baseline-complete cJSON +helpers. An external child destructor must not mask imported recursive links, +and buffer discovery must not hide a valid unchanged-entry pointer premise. +Its initial cursor probe lacked enough usage to nominate buffer roles, so the +separate `discovered` inventory adds a role-nominating helper without rewriting +the original population. All four cases and their object clients run in CTest. + +The harness verifies fixture and executable identities, rejects parse/tool +failures as evidence, tests intended negative properties, and compares complete +expanded reports across cold, warm, uncached and compact execution. Cache +mutation removes a child's release and must invalidate the entire dependent +proof. Corrupt checkpoints must recompute; older sidecars must be rebuilt. + +Core tests independently enumerate progress graphs and concrete byte masks. +Analysis tests cover hidden global effects, nondecreasing cycles, shared and +cyclic child ownership, per-node callbacks, stale/reassigned inputs and forged +callback behavior. These finite tests do not establish arbitrary-C soundness. + +The separately reviewed `recursive-writer-reviewed` population checks recursive +byte writers and partial initialized prefixes. `recursive-writer/audit.md` +retains the original expectation audit; complete serialization is still a +separate obligation. `writer-transport` covers separate source, objects and +checkpoint invalidation; `writer-oracle` checks 18,513 finite prefix/capacity +combinations with an independent concrete oracle. + +`cursor-readers` preserves the original expectation audit and the reviewed +reader-cursor population. `cursor-cache` checks canonical reuse and mutation; +`cursor-oracle` runs 1,123 finite cursor/input combinations for both ordinary +and partial-return helpers, and detects cursor escape. `recursive-cases` and +`recursive-contexts` distinguish actual input-case completion from generic +recursive exhaustion without raising any budget. `mutual-cases` adds a finite +call chain inside a syntactically recursive component. diff --git a/test/evaluation/rfc0029/advance-outcomes/README.md b/test/evaluation/rfc0029/advance-outcomes/README.md new file mode 100644 index 00000000..dc00c676 --- /dev/null +++ b/test/evaluation/rfc0029/advance-outcomes/README.md @@ -0,0 +1,4 @@ +# Cursor advances across return outcomes (RFC 0029) + +Frozen before candidate 54f. Success writes its advanced prefix; a zero return +may leave the pointer unchanged. A missing write on either outcome stays rejected. diff --git a/test/evaluation/rfc0029/advance-outcomes/SHA256SUMS b/test/evaluation/rfc0029/advance-outcomes/SHA256SUMS new file mode 100644 index 00000000..67a02e23 --- /dev/null +++ b/test/evaluation/rfc0029/advance-outcomes/SHA256SUMS @@ -0,0 +1,8 @@ +612e9f58e1520b0e5734976f665396e717be1d7633145dab061d4292f59be8fe README.md +b59571e34441b05ffd9836d7d0ba0d746dd65c7b1ffa71fc6f67e00fe5fe57cc bad-failure.c +eb35b93add37cb8040a38a05b48e976e330020e7d0cebfbe26ef1bab453a5da2 good.c +87a6cfd0aeb766146857c6ac027cefe5d7258f49ca1d74ea4b340968bf134029 interior.c +fcac9fa6e65e1c02dff34afecd519cbc40b05e3949dc199ed2391d7fef841032 library.c +b6e1030f97b403f5a343458b84cbb526250f1201528261cb800a8af776b2ba64 manifest.json +3233e07294c27b245956fdec2f1ea1bd18a410ca623f6f6dc40ffe5bbd0b7110 short.c +da57efaf86238266b74d248c3006f6c86bdf5b56632132578a3d6bc429919271 skipped.c diff --git a/test/evaluation/rfc0029/advance-outcomes/bad-failure.c b/test/evaluation/rfc0029/advance-outcomes/bad-failure.c new file mode 100644 index 00000000..50d51432 --- /dev/null +++ b/test/evaluation/rfc0029/advance-outcomes/bad-failure.c @@ -0,0 +1,9 @@ +unsigned encode(unsigned char **out,unsigned code) { + if(code==0){*out+=1;return 0;} + unsigned char n=code>255?4:1; + unsigned char i; + for(i=(unsigned char)(n-1);i>0;i--) (*out)[i]=42; + (*out)[0]=1; *out+=n;return n; +} +unsigned encode(unsigned char**,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[4],*p=out;encode(&p,argc>1?65536:0);if(p>out)return p[-1];return 0;} diff --git a/test/evaluation/rfc0029/advance-outcomes/frozen-sha256.json b/test/evaluation/rfc0029/advance-outcomes/frozen-sha256.json new file mode 100644 index 00000000..d850ecf0 --- /dev/null +++ b/test/evaluation/rfc0029/advance-outcomes/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "README.md": "612e9f58e1520b0e5734976f665396e717be1d7633145dab061d4292f59be8fe", + "bad-failure.c": "b59571e34441b05ffd9836d7d0ba0d746dd65c7b1ffa71fc6f67e00fe5fe57cc", + "good.c": "eb35b93add37cb8040a38a05b48e976e330020e7d0cebfbe26ef1bab453a5da2", + "interior.c": "87a6cfd0aeb766146857c6ac027cefe5d7258f49ca1d74ea4b340968bf134029", + "library.c": "fcac9fa6e65e1c02dff34afecd519cbc40b05e3949dc199ed2391d7fef841032", + "manifest.json": "b6e1030f97b403f5a343458b84cbb526250f1201528261cb800a8af776b2ba64", + "short.c": "3233e07294c27b245956fdec2f1ea1bd18a410ca623f6f6dc40ffe5bbd0b7110", + "skipped.c": "da57efaf86238266b74d248c3006f6c86bdf5b56632132578a3d6bc429919271" +} diff --git a/test/evaluation/rfc0029/advance-outcomes/good.c b/test/evaluation/rfc0029/advance-outcomes/good.c new file mode 100644 index 00000000..f19f22c5 --- /dev/null +++ b/test/evaluation/rfc0029/advance-outcomes/good.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[4],*p=out;encode(&p,argc>1?65536:0);if(p>out)return p[-1];return 0;} diff --git a/test/evaluation/rfc0029/advance-outcomes/interior.c b/test/evaluation/rfc0029/advance-outcomes/interior.c new file mode 100644 index 00000000..67ea66e8 --- /dev/null +++ b/test/evaluation/rfc0029/advance-outcomes/interior.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[5],*p=out+1;encode(&p,argc>1?65536:0);if(p>out+1)return p[-1];return 0;} diff --git a/test/evaluation/rfc0029/advance-outcomes/library.c b/test/evaluation/rfc0029/advance-outcomes/library.c new file mode 100644 index 00000000..9eaca14c --- /dev/null +++ b/test/evaluation/rfc0029/advance-outcomes/library.c @@ -0,0 +1,7 @@ +unsigned encode(unsigned char **out,unsigned code) { + if(code==0)return 0; + unsigned char n=code>255?4:1; + unsigned char i; + for(i=(unsigned char)(n-1);i>0;i--) (*out)[i]=42; + (*out)[0]=1; *out+=n;return n; +} diff --git a/test/evaluation/rfc0029/advance-outcomes/manifest.json b/test/evaluation/rfc0029/advance-outcomes/manifest.json new file mode 100644 index 00000000..4eafe100 --- /dev/null +++ b/test/evaluation/rfc0029/advance-outcomes/manifest.json @@ -0,0 +1,83 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "interior", + "sources": [ + "interior.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + }, + { + "name": "skipped", + "sources": [ + "skipped.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + }, + { + "name": "bad-failure", + "sources": [ + "bad-failure.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + } + ] +} diff --git a/test/evaluation/rfc0029/advance-outcomes/short.c b/test/evaluation/rfc0029/advance-outcomes/short.c new file mode 100644 index 00000000..0bc6fde8 --- /dev/null +++ b/test/evaluation/rfc0029/advance-outcomes/short.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[3],*p=out;encode(&p,argc>1?65536:0);if(p>out)return p[-1];return 0;} diff --git a/test/evaluation/rfc0029/advance-outcomes/skipped.c b/test/evaluation/rfc0029/advance-outcomes/skipped.c new file mode 100644 index 00000000..271f0039 --- /dev/null +++ b/test/evaluation/rfc0029/advance-outcomes/skipped.c @@ -0,0 +1,9 @@ +unsigned encode(unsigned char **out,unsigned code) { + if(code==0)return 0; + unsigned char n=code>255?4:1; + unsigned char i; + for(i=(unsigned char)(n-1);i>0;i--) if(i!=2)(*out)[i]=42; + (*out)[0]=1; *out+=n;return n; +} +unsigned encode(unsigned char**,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[4],*p=out;encode(&p,argc>1?65536:0);if(p>out)return p[-1];return 0;} diff --git a/test/evaluation/rfc0029/anonymous-private-state/README.md b/test/evaluation/rfc0029/anonymous-private-state/README.md new file mode 100644 index 00000000..e6d5f659 --- /dev/null +++ b/test/evaluation/rfc0029/anonymous-private-state/README.md @@ -0,0 +1 @@ +Frozen at candidate69b before anonymous typedef metadata changes. A private scalar output in an anonymous record must retain its storage representation across source boundaries. diff --git a/test/evaluation/rfc0029/anonymous-private-state/client.c b/test/evaluation/rfc0029/anonymous-private-state/client.c new file mode 100644 index 00000000..5e810618 --- /dev/null +++ b/test/evaluation/rfc0029/anonymous-private-state/client.c @@ -0,0 +1,2 @@ +void reset(void); +int main(void) { reset(); return 0; } diff --git a/test/evaluation/rfc0029/anonymous-private-state/frozen-sha256.json b/test/evaluation/rfc0029/anonymous-private-state/frozen-sha256.json new file mode 100644 index 00000000..e0e6f71a --- /dev/null +++ b/test/evaluation/rfc0029/anonymous-private-state/frozen-sha256.json @@ -0,0 +1,6 @@ +{ + "README.md": "cb1790a104e66f1213d6de08bc45bafdd043048a0dd6731e131e1dd345c0e1b9", + "client.c": "d18e4b4ee327ac9e9873ff3e55689011de587f508c59c653999c63931093cd09", + "manifest.json": "651b0c3d13b5fa9c26775cce58ec20608f825ec91cd798164bdd007e3891c65f", + "state.c": "e0bdc231b36b326575965adebc5928a3d0aba0665b79b4ab894d460356b3c877" +} diff --git a/test/evaluation/rfc0029/anonymous-private-state/manifest.json b/test/evaluation/rfc0029/anonymous-private-state/manifest.json new file mode 100644 index 00000000..8f3c3498 --- /dev/null +++ b/test/evaluation/rfc0029/anonymous-private-state/manifest.json @@ -0,0 +1,21 @@ +{ + "version": 1, + "cases": [ + { + "name": "private-typedef", + "sources": [ + "state.c", + "client.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "annotation", + "unsafe" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/anonymous-private-state/state.c b/test/evaluation/rfc0029/anonymous-private-state/state.c new file mode 100644 index 00000000..ae73455c --- /dev/null +++ b/test/evaluation/rfc0029/anonymous-private-state/state.c @@ -0,0 +1,3 @@ +typedef struct { const unsigned char *text; unsigned long offset; } state; +static state last; +void reset(void) { last.text = 0; last.offset = 0; } diff --git a/test/evaluation/rfc0029/attached-payload-transfer/PROVENANCE.md b/test/evaluation/rfc0029/attached-payload-transfer/PROVENANCE.md new file mode 100644 index 00000000..8f06981c --- /dev/null +++ b/test/evaluation/rfc0029/attached-payload-transfer/PROVENANCE.md @@ -0,0 +1,8 @@ +RFC 0029 attached-payload transfer population, frozen against candidate 102 +while adding the fresh-region container-combined output, its forwarding +through a direct return, and its installation on an immediate result test. +A complete attach publishes the disjoint union of both owned inputs and the +key it allocates itself on success, and preserves both inputs separately on +allocation failure. The client tests the forwarded result. Losing the item +on the failure path, releasing it twice after a successful transfer, and +ignoring the result all remain required rejections. diff --git a/test/evaluation/rfc0029/attached-payload-transfer/api.h b/test/evaluation/rfc0029/attached-payload-transfer/api.h new file mode 100644 index 00000000..dca8bc5a --- /dev/null +++ b/test/evaluation/rfc0029/attached-payload-transfer/api.h @@ -0,0 +1,5 @@ +struct node{struct node *next,*prev,*child;int flags;char *text,*key;}; +struct node *create(void); +void destroy(struct node *); +int attach_wrapper(struct node *,struct node *); +void reset_hooks(void); diff --git a/test/evaluation/rfc0029/attached-payload-transfer/frozen-sha256.json b/test/evaluation/rfc0029/attached-payload-transfer/frozen-sha256.json new file mode 100644 index 00000000..50c8aede --- /dev/null +++ b/test/evaluation/rfc0029/attached-payload-transfer/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "PROVENANCE.md": "ecb88df3ca8aa2546f097656bdcf9d589a513cf745cab4e88c3ca09c8c4ba811", + "api.h": "f0adbb7a9fde5b28e2f4031edac235d2de563952a42b4819ae48d7bbc2a11566", + "good.c": "dbd50ed7b005b4a31a5dd3cb7e3f9980161cc9dff8bf124016327e431b192976", + "ignored.c": "f0495c45905ecc2cf6d632af437d6d33a086e9b8fe0750836d6e85feba78fb12", + "leak.c": "3977ba14724889ef66619e7d5f0dba29e6db42498398c05b3eaceea06e385ee6", + "library.c": "ce66461521423b5013243b46f470a62699bd2690f18d415bffad4a1996df0092", + "manifest.json": "5267b76af08fd332a741da8ff4d75181196fa107376d136dfcde6a5d931cd1a3", + "twice.c": "0327188ef01b0f96ea2c8be09bf5b5b4432151ae081ab8098c1d8c9ecfa59c75" +} diff --git a/test/evaluation/rfc0029/attached-payload-transfer/good.c b/test/evaluation/rfc0029/attached-payload-transfer/good.c new file mode 100644 index 00000000..2612e826 --- /dev/null +++ b/test/evaluation/rfc0029/attached-payload-transfer/good.c @@ -0,0 +1,4 @@ +#include "api.h" +int main(void){reset_hooks();struct node *o=create();if(!o)return 0;struct node *i=create();if(!i){destroy(o);return 0;} +if(!attach_wrapper(o,i)){destroy(i);destroy(o);return 0;} +destroy(o);return 0;} diff --git a/test/evaluation/rfc0029/attached-payload-transfer/ignored.c b/test/evaluation/rfc0029/attached-payload-transfer/ignored.c new file mode 100644 index 00000000..da5823ef --- /dev/null +++ b/test/evaluation/rfc0029/attached-payload-transfer/ignored.c @@ -0,0 +1,4 @@ +#include "api.h" +int main(void){reset_hooks();struct node *o=create();if(!o)return 0;struct node *i=create();if(!i){destroy(o);return 0;} +attach_wrapper(o,i); +destroy(o);return 0;} diff --git a/test/evaluation/rfc0029/attached-payload-transfer/leak.c b/test/evaluation/rfc0029/attached-payload-transfer/leak.c new file mode 100644 index 00000000..58947969 --- /dev/null +++ b/test/evaluation/rfc0029/attached-payload-transfer/leak.c @@ -0,0 +1,4 @@ +#include "api.h" +int main(void){reset_hooks();struct node *o=create();if(!o)return 0;struct node *i=create();if(!i){destroy(o);return 0;} +if(!attach_wrapper(o,i)){destroy(o);return 0;} +destroy(o);return 0;} diff --git a/test/evaluation/rfc0029/attached-payload-transfer/library.c b/test/evaluation/rfc0029/attached-payload-transfer/library.c new file mode 100644 index 00000000..01afd2de --- /dev/null +++ b/test/evaluation/rfc0029/attached-payload-transfer/library.c @@ -0,0 +1,18 @@ + +#define NULL ((void *)0) +typedef __SIZE_TYPE__ size_t; +void *malloc(size_t); +void free(void *); +void *memset(void *, int, size_t); +struct hooks { void *(*allocate)(size_t); void (*deallocate)(void*); }; +static struct hooks global_hooks = {malloc,free}; +struct node { struct node *next,*prev,*child; int flags; char *text,*key; }; +static void reset(struct hooks *h){if(!h){global_hooks.allocate=malloc;global_hooks.deallocate=free;return;}global_hooks=*h;} +static struct node *new_item(const struct hooks *h){struct node *p=(struct node*)h->allocate(sizeof(struct node));if(p)memset(p,0,sizeof *p);return p;} +struct node *create(void){struct node *p=new_item(&global_hooks);if(p)p->flags=1;return p;} +void destroy(struct node *p){struct node *next=0;while(p){next=p->next;if(!(p->flags&256)&&p->child)destroy(p->child);if(!(p->flags&256)&&p->text){global_hooks.deallocate(p->text);p->text=0;}if(!(p->flags&512)&&p->key){global_hooks.deallocate(p->key);p->key=0;}global_hooks.deallocate(p);p=next;}} +static void suffix(struct node *last,struct node *item){last->next=item;item->prev=last;} +static int add(struct node *array,struct node *item){struct node *child=0;if(!item||!array||array==item)return 0;child=array->child;if(!child){array->child=item;item->prev=item;item->next=0;}else{if(child->prev){suffix(child->prev,item);array->child->prev=item;}}return 1;} +int attach(struct node *object,struct node *item){char *key=0;if(!object||!item||object==item)return 0;key=(char*)malloc(4);if(!key)return 0;key[0]=0;item->key=key;return add(object,item);} +int attach_wrapper(struct node *o,struct node *i){return attach(o,i);} +void reset_hooks(void){reset(0);} diff --git a/test/evaluation/rfc0029/attached-payload-transfer/manifest.json b/test/evaluation/rfc0029/attached-payload-transfer/manifest.json new file mode 100644 index 00000000..323408fa --- /dev/null +++ b/test/evaluation/rfc0029/attached-payload-transfer/manifest.json @@ -0,0 +1,37 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": ["good.c", "library.c"], + "functions": ["main"], + "expect": "accepted", + "forbidden_trust": ["unsafe", "annotation"], + "entry_requirements": 0 + }, + { + "name": "leak", + "sources": ["leak.c", "library.c"], + "functions": ["main"], + "expect": "rejected", + "forbidden_trust": ["unsafe", "annotation"], + "reason": "leak|footprint|ownership" + }, + { + "name": "twice", + "sources": ["twice.c", "library.c"], + "functions": ["main"], + "expect": "rejected", + "forbidden_trust": ["unsafe", "annotation"], + "reason": "freed twice|release|ownership|footprint" + }, + { + "name": "ignored", + "sources": ["ignored.c", "library.c"], + "functions": ["main"], + "expect": "rejected", + "forbidden_trust": ["unsafe", "annotation"], + "reason": "leak|footprint|ownership" + } + ] +} diff --git a/test/evaluation/rfc0029/attached-payload-transfer/twice.c b/test/evaluation/rfc0029/attached-payload-transfer/twice.c new file mode 100644 index 00000000..01220ddd --- /dev/null +++ b/test/evaluation/rfc0029/attached-payload-transfer/twice.c @@ -0,0 +1,4 @@ +#include "api.h" +int main(void){reset_hooks();struct node *o=create();if(!o)return 0;struct node *i=create();if(!i){destroy(o);return 0;} +if(!attach_wrapper(o,i)){destroy(i);destroy(o);return 0;} +destroy(o);destroy(i);return 0;} diff --git a/test/evaluation/rfc0029/bounded-string-cursor/bounded.c b/test/evaluation/rfc0029/bounded-string-cursor/bounded.c new file mode 100644 index 00000000..81885d97 --- /dev/null +++ b/test/evaluation/rfc0029/bounded-string-cursor/bounded.c @@ -0,0 +1,14 @@ +#include "writer.h" +#include +#include +int main(void) { + unsigned char *data = malloc(4); + if (!data) return 0; + memcpy(data, "abc", 4); + struct writer w = {data, 4, 1, 0}; + finish(&w); + if (w.length >= w.capacity) data[4] = 1; + else data[w.length] = 0; + free(data); + return 0; +} diff --git a/test/evaluation/rfc0029/bounded-string-cursor/earlier-zero.c b/test/evaluation/rfc0029/bounded-string-cursor/earlier-zero.c new file mode 100644 index 00000000..1249ec7f --- /dev/null +++ b/test/evaluation/rfc0029/bounded-string-cursor/earlier-zero.c @@ -0,0 +1,14 @@ +#include "writer.h" +#include +#include +int main(void) { + unsigned char *data = malloc(4); + if (!data) return 0; + data[0]=0; data[1]=42; data[2]=0; data[3]=0; + struct writer w = {data, 4, 1, 0}; + finish(&w); + if (w.length >= w.capacity) data[4] = 1; + else data[w.length] = 0; + free(data); + return 0; +} diff --git a/test/evaluation/rfc0029/bounded-string-cursor/escaped-cursor.c b/test/evaluation/rfc0029/bounded-string-cursor/escaped-cursor.c new file mode 100644 index 00000000..4fba8541 --- /dev/null +++ b/test/evaluation/rfc0029/bounded-string-cursor/escaped-cursor.c @@ -0,0 +1,14 @@ +#include "writer.h" +#include +#include +int main(void) { + unsigned char *data = malloc(4); + if (!data) return 0; + memcpy(data, "abc", 4); + struct writer w = {data, 4, 5, 0}; + finish(&w); + if (w.length >= w.capacity) data[4] = 1; + else data[w.length] = 0; + free(data); + return 0; +} diff --git a/test/evaluation/rfc0029/bounded-string-cursor/finish.c b/test/evaluation/rfc0029/bounded-string-cursor/finish.c new file mode 100644 index 00000000..06805d6d --- /dev/null +++ b/test/evaluation/rfc0029/bounded-string-cursor/finish.c @@ -0,0 +1,7 @@ +#include "writer.h" +#include +void finish(struct writer *w) { + if (!w || !w->data) return; + const unsigned char *next = w->data + w->length; + w->length += strlen((const char *)next); +} diff --git a/test/evaluation/rfc0029/bounded-string-cursor/forged-capacity.c b/test/evaluation/rfc0029/bounded-string-cursor/forged-capacity.c new file mode 100644 index 00000000..772a0898 --- /dev/null +++ b/test/evaluation/rfc0029/bounded-string-cursor/forged-capacity.c @@ -0,0 +1,14 @@ +#include "writer.h" +#include +#include +int main(void) { + unsigned char *data = malloc(4); + if (!data) return 0; + memcpy(data, "abc", 4); + struct writer w = {data, 8, 4, 0}; + finish(&w); + if (w.length >= w.capacity) data[4] = 1; + else data[w.length] = 0; + free(data); + return 0; +} diff --git a/test/evaluation/rfc0029/bounded-string-cursor/frozen-sha256.json b/test/evaluation/rfc0029/bounded-string-cursor/frozen-sha256.json new file mode 100644 index 00000000..230f029f --- /dev/null +++ b/test/evaluation/rfc0029/bounded-string-cursor/frozen-sha256.json @@ -0,0 +1,11 @@ +{ + "bounded.c": "5dcb528cacefe136410fec85a11ee2ebbca2e8dbacada1834516cc5cb5f742e2", + "earlier-zero.c": "4f10bec7dbede32ef171b7a95da79fef651e7f393a3ffd2699971ad7a349897b", + "escaped-cursor.c": "43664a36ea494448287dbad8ae6e2b7beb2ec462226c835cc9e008936b85106a", + "finish.c": "1f26f216e30bde9ce3f8572212c49fe2f20cba30a68711cb0c494cf847ead760", + "forged-capacity.c": "e75e03e8fd408d79a3c5f1227466cc2f1fe0c5416dfaac9e667ed158bc91a5e9", + "manifest.json": "6d3d096fbe63fe087d2237e027a94d5232a9950f3a59e8a0b8868aa77ccebe58", + "uninitialized.c": "3f399532abeb8dc8f0d78dde54f7e2b64363daeeb671046a58569547afa40baf", + "unterminated.c": "65835f1c0f55b362f2a09a1e3a8bef7262fe34588c211a5ec1d36d2b4bbb0762", + "writer.h": "03bed685a3b3bd403e54da9ddd3f397dde76fed585a89b8ac64e321528f823c4" +} diff --git a/test/evaluation/rfc0029/bounded-string-cursor/manifest.json b/test/evaluation/rfc0029/bounded-string-cursor/manifest.json new file mode 100644 index 00000000..4f9d4e18 --- /dev/null +++ b/test/evaluation/rfc0029/bounded-string-cursor/manifest.json @@ -0,0 +1,99 @@ +{ + "version": 1, + "cases": [ + { + "name": "bounded", + "sources": [ + "finish.c", + "bounded.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "earlier-zero", + "sources": [ + "finish.c", + "earlier-zero.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "forged-capacity", + "sources": [ + "finish.c", + "forged-capacity.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval|terminat|initializ" + }, + { + "name": "unterminated", + "sources": [ + "finish.c", + "unterminated.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval|terminat|initializ" + }, + { + "name": "uninitialized", + "sources": [ + "finish.c", + "uninitialized.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval|terminat|initializ" + }, + { + "name": "escaped-cursor", + "sources": [ + "finish.c", + "escaped-cursor.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval|terminat|initializ" + } + ] +} diff --git a/test/evaluation/rfc0029/bounded-string-cursor/uninitialized.c b/test/evaluation/rfc0029/bounded-string-cursor/uninitialized.c new file mode 100644 index 00000000..01856288 --- /dev/null +++ b/test/evaluation/rfc0029/bounded-string-cursor/uninitialized.c @@ -0,0 +1,14 @@ +#include "writer.h" +#include +#include +int main(void) { + unsigned char *data = malloc(4); + if (!data) return 0; + data[0]=42; data[3]=0; + struct writer w = {data, 4, 1, 0}; + finish(&w); + if (w.length >= w.capacity) data[4] = 1; + else data[w.length] = 0; + free(data); + return 0; +} diff --git a/test/evaluation/rfc0029/bounded-string-cursor/unterminated.c b/test/evaluation/rfc0029/bounded-string-cursor/unterminated.c new file mode 100644 index 00000000..78b5f983 --- /dev/null +++ b/test/evaluation/rfc0029/bounded-string-cursor/unterminated.c @@ -0,0 +1,14 @@ +#include "writer.h" +#include +#include +int main(void) { + unsigned char *data = malloc(4); + if (!data) return 0; + memset(data, 42, 4); + struct writer w = {data, 4, 1, 0}; + finish(&w); + if (w.length >= w.capacity) data[4] = 1; + else data[w.length] = 0; + free(data); + return 0; +} diff --git a/test/evaluation/rfc0029/bounded-string-cursor/writer.h b/test/evaluation/rfc0029/bounded-string-cursor/writer.h new file mode 100644 index 00000000..77778012 --- /dev/null +++ b/test/evaluation/rfc0029/bounded-string-cursor/writer.h @@ -0,0 +1,3 @@ +#include +struct writer { unsigned char *data; size_t capacity, length; int format; }; +void finish(struct writer *w); diff --git a/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/README.md b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/README.md new file mode 100644 index 00000000..7e4bb961 --- /dev/null +++ b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/README.md @@ -0,0 +1,15 @@ +# Buffer capacity and additional entry intervals (RFC 0029) + +Candidate 66a falsely diagnosed spare physical storage as too small. Candidate +67a removes that invented exact extent but cannot yet project the extra entry +bytes. Candidate 67b projects the extra intervals and exposes unrelated nullable +buffer regressions; 67c restricts the extra projection to extent and initialized +bytes, preserving the existing validity proof. + +The original frozen `buffer-lower-extents` and `buffer-entry-intervals` +inventories remain under `build/rfc29-validation`. This reviewed inventory keeps +all six source files and acceptance expectations identical. Two original reason +patterns accidentally omitted the word `extent`, although the intended bounds +precondition was reported. The reviewed patterns include that existing message. +Short storage, an out-of-array index, replaced backing and an uninitialized tail +must still reject for their intended properties. No diagnostic flag is changed. diff --git a/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/frozen-sha256.json b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/frozen-sha256.json new file mode 100644 index 00000000..f9b291a8 --- /dev/null +++ b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "full-physical.c": "fcb5789c92529050ee2b77af39eb5e69aca52d89f86f9281c8c985a246103bea", + "manifest.json": "f8cf49287a24d2c22b9ca2463d822822ef5a453ac115f014f8523b00a0c3942e", + "past-physical.c": "9d944c949026a145fcb05885f1165d518dcbcffeb9a9e69cd487faae6bfdac70", + "replaced-short.c": "877dcc638f662f7376d38b8e56247f1d45d0c6c8eb62fe8a63014513bbbabbfc", + "short-physical.c": "fc4ab5653ec94960e8a87feb136262689eda22407496a797d42367a81c46d713", + "spare-physical.c": "b287fa9c12df0bd6ef61585da93bf6d690dfe2d8d1bf6f1683d8a172169b43f4", + "uninitialized-tail.c": "8d44d9d9dc9082e8f7882609b9caa99bbabac9ed2d76565647aa18ce8a32caa5" +} diff --git a/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/full-physical.c b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/full-physical.c new file mode 100644 index 00000000..a295f2b6 --- /dev/null +++ b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/full-physical.c @@ -0,0 +1,8 @@ +#include +#include +struct B {char *data;size_t used,capacity;}; +static void copy(struct B *b,char *out) { + if(b->used>b->capacity)return; + if (b->data[b->used]) memcpy(out,b->data,b->used+1); +} +int main(void){char in[4]={'a','b','c',0},out[4]={0};struct B b={in,2,4};copy(&b,out);return 0;} diff --git a/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/manifest.json b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/manifest.json new file mode 100644 index 00000000..5effc557 --- /dev/null +++ b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/manifest.json @@ -0,0 +1,75 @@ +{ + "version": 1, + "cases": [ + { + "name": "spare-physical", + "source": "spare-physical.c", + "function": "main", + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "full-physical", + "source": "full-physical.c", + "function": "main", + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "short-physical", + "source": "short-physical.c", + "function": "main", + "expect": "rejected", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|interval|object|extent" + }, + { + "name": "past-physical", + "source": "past-physical.c", + "function": "main", + "expect": "rejected", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|interval|object|extent" + }, + { + "name": "replaced-short", + "source": "replaced-short.c", + "function": "main", + "expect": "rejected", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|interval|object" + }, + { + "name": "uninitialized-tail", + "source": "uninitialized-tail.c", + "function": "main", + "expect": "rejected", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + } + ] +} diff --git a/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/past-physical.c b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/past-physical.c new file mode 100644 index 00000000..319763ab --- /dev/null +++ b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/past-physical.c @@ -0,0 +1,8 @@ +#include +#include +struct B {char *data;size_t used,capacity;}; +static void copy(struct B *b,char *out) { + if(b->used>b->capacity)return; + if (b->data[b->used]) memcpy(out,b->data,b->used+1); +} +int main(void){char in[4]={'a','b','c',0},out[4]={0};struct B b={in,4,4};copy(&b,out);return 0;} diff --git a/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/replaced-short.c b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/replaced-short.c new file mode 100644 index 00000000..61ffd147 --- /dev/null +++ b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/replaced-short.c @@ -0,0 +1,8 @@ +#include +#include +struct B {char *data;size_t used,capacity;}; +static void copy(struct B *b,char *out) { + char replacement[1]={'x'}; b->data=replacement; if(b->used>b->capacity)return; + if (b->data[b->used]) memcpy(out,b->data,b->used+1); +} +int main(void){char in[4]={'a','b','c',0},out[4]={0};struct B b={in,2,2};copy(&b,out);return 0;} diff --git a/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/short-physical.c b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/short-physical.c new file mode 100644 index 00000000..51573a40 --- /dev/null +++ b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/short-physical.c @@ -0,0 +1,8 @@ +#include +#include +struct B {char *data;size_t used,capacity;}; +static void copy(struct B *b,char *out) { + if(b->used>b->capacity)return; + if (b->data[b->used]) memcpy(out,b->data,b->used+1); +} +int main(void){char in[2]={'a','b'},out[4]={0};struct B b={in,2,2};copy(&b,out);return 0;} diff --git a/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/spare-physical.c b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/spare-physical.c new file mode 100644 index 00000000..d5225451 --- /dev/null +++ b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/spare-physical.c @@ -0,0 +1,8 @@ +#include +#include +struct B {char *data;size_t used,capacity;}; +static void copy(struct B *b,char *out) { + if(b->used>b->capacity)return; + if (b->data[b->used]) memcpy(out,b->data,b->used+1); +} +int main(void){char in[4]={'a','b','c',0},out[4]={0};struct B b={in,2,2};copy(&b,out);return 0;} diff --git a/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/uninitialized-tail.c b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/uninitialized-tail.c new file mode 100644 index 00000000..12518c8b --- /dev/null +++ b/test/evaluation/rfc0029/buffer-entry-intervals-reviewed/uninitialized-tail.c @@ -0,0 +1,8 @@ +#include +#include +struct B {char *data;size_t used,capacity;}; +static void copy(struct B *b,char *out) { + if(b->used>b->capacity)return; + if (b->data[b->used]) memcpy(out,b->data,b->used+1); +} +int main(void){char in[4],out[4]={0};struct B b={in,2,2};in[0]='a';in[1]='b';copy(&b,out);return 0;} diff --git a/test/evaluation/rfc0029/byte-callee-frames/PROVENANCE.md b/test/evaluation/rfc0029/byte-callee-frames/PROVENANCE.md new file mode 100644 index 00000000..18a6b18f --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate81a before preserving actual constant-array byte contents across complete helpers with represented writes to other input and global objects. A const pointer alone supplies no such premise. Actual content changes, writing a constant object, an uninitialized object, a wrong byte, and an unknown call remain negative. diff --git a/test/evaluation/rfc0029/byte-callee-frames/api.h b/test/evaluation/rfc0029/byte-callee-frames/api.h new file mode 100644 index 00000000..e33fc67a --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/api.h @@ -0,0 +1 @@ +struct state{unsigned n;};int inspect(struct state *,const unsigned char *); diff --git a/test/evaluation/rfc0029/byte-callee-frames/changed-library.c b/test/evaluation/rfc0029/byte-callee-frames/changed-library.c new file mode 100644 index 00000000..b44712a9 --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/changed-library.c @@ -0,0 +1,3 @@ +#include "api.h" +static void change(struct state*s,unsigned char*p){s->n=1;p[0]=98;} +int inspect(struct state*s,const unsigned char*p){change(s,(unsigned char*)p);if(p[0]!=97)return p[8];return 0;} diff --git a/test/evaluation/rfc0029/byte-callee-frames/frozen-sha256.json b/test/evaluation/rfc0029/byte-callee-frames/frozen-sha256.json new file mode 100644 index 00000000..28ffa387 --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/frozen-sha256.json @@ -0,0 +1,13 @@ +{ + "PROVENANCE.md": "93431990043a79022f30493aabe5750053f73f52414eb2d99408c78ff94cf4b5", + "api.h": "9ae6aa48ec0679d8df0ef83eebaa572445fb74f6aa96f21a7c48364a10e6b4f3", + "changed-library.c": "729b63600c2898862b9d5e44f290d9620967ead840e36f9189be848c4f89d4ef", + "library.c": "fc361952bbbd381142cc82360eb5066ebf3b6c3a1850693bec42fb2644773e51", + "local.c": "e1bbaa3755fc30dfd1f6fe020471d8022c0fa0cb4ad15831de39b93b4cf39b7a", + "manifest.json": "a95f8ea2003b30acdf8faa9a475eb0d2dba4b24dd8eb39d315f576dd15ad2250", + "mutable.c": "e386434d0410f62bb4245e6fedf986dbab622b2507ba21f9f6620202a7be4bca", + "partial.c": "c12ad673fa0ad0fa0933d12bed0c564802fca528cf035ce9a40dede736fe6ff1", + "static.c": "2944e5443f35a475077341cf51f981b56d7dd73ba801157cf3707c9ce2ec05db", + "unknown-library.c": "56d3d07a34f78b2b4109e21b6e77572684a81573c277031446667c67487dc862", + "wrong.c": "f76b15ea29eb0407c14ab31d1334a64d0a59f64bc8f389beb7c59b24f28b4a33" +} diff --git a/test/evaluation/rfc0029/byte-callee-frames/library.c b/test/evaluation/rfc0029/byte-callee-frames/library.c new file mode 100644 index 00000000..488cae8d --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/library.c @@ -0,0 +1,4 @@ +#include "api.h" +static unsigned config; +static void change(struct state*s){s->n=1;config=1;} +int inspect(struct state*s,const unsigned char*p){change(s);if(p[0]!=97)return p[8];return 0;} diff --git a/test/evaluation/rfc0029/byte-callee-frames/local.c b/test/evaluation/rfc0029/byte-callee-frames/local.c new file mode 100644 index 00000000..0ccaf8bb --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/local.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct state s={0};const unsigned char p[]={97};return inspect(&s,p);} diff --git a/test/evaluation/rfc0029/byte-callee-frames/manifest.json b/test/evaluation/rfc0029/byte-callee-frames/manifest.json new file mode 100644 index 00000000..ad2b253e --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/manifest.json @@ -0,0 +1,117 @@ +{ + "version": 1, + "cases": [ + { + "name": "static", + "sources": [ + "static.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "local", + "sources": [ + "local.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "wrong", + "sources": [ + "wrong.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bounds|storage|writable|contract|unknown|const" + }, + { + "name": "mutable-changed", + "sources": [ + "mutable.c", + "changed-library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bounds|storage|writable|contract|unknown|const" + }, + { + "name": "const-write", + "sources": [ + "static.c", + "changed-library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bounds|storage|writable|contract|unknown|const" + }, + { + "name": "partial", + "sources": [ + "partial.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bounds|storage|writable|contract|unknown|const" + }, + { + "name": "unknown", + "sources": [ + "static.c", + "unknown-library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bounds|storage|writable|contract|unknown|const" + } + ] +} diff --git a/test/evaluation/rfc0029/byte-callee-frames/mutable.c b/test/evaluation/rfc0029/byte-callee-frames/mutable.c new file mode 100644 index 00000000..5bf3643e --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/mutable.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct state s={0};unsigned char p[]={97};return inspect(&s,p);} diff --git a/test/evaluation/rfc0029/byte-callee-frames/partial.c b/test/evaluation/rfc0029/byte-callee-frames/partial.c new file mode 100644 index 00000000..ccbb5604 --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/partial.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct state s={0};unsigned char p[1];return inspect(&s,p);} diff --git a/test/evaluation/rfc0029/byte-callee-frames/static.c b/test/evaluation/rfc0029/byte-callee-frames/static.c new file mode 100644 index 00000000..1664cb08 --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/static.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct state s={0};static const unsigned char p[]={97};return inspect(&s,p);} diff --git a/test/evaluation/rfc0029/byte-callee-frames/unknown-library.c b/test/evaluation/rfc0029/byte-callee-frames/unknown-library.c new file mode 100644 index 00000000..586fe866 --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/unknown-library.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct state*); +int inspect(struct state*s,const unsigned char*p){unknown(s);if(p[0]!=97)return p[8];return 0;} diff --git a/test/evaluation/rfc0029/byte-callee-frames/wrong.c b/test/evaluation/rfc0029/byte-callee-frames/wrong.c new file mode 100644 index 00000000..14f7a6e6 --- /dev/null +++ b/test/evaluation/rfc0029/byte-callee-frames/wrong.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct state s={0};static const unsigned char p[]={98};return inspect(&s,p);} diff --git a/test/evaluation/rfc0029/byte-comparisons/PROVENANCE.md b/test/evaluation/rfc0029/byte-comparisons/PROVENANCE.md new file mode 100644 index 00000000..0e49c5aa --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate75e before bounded comparison-result inference. Checks actual contents, embedded zeros, unsigned byte ordering, mutation, incomplete initialization and short objects. A nonzero result has only a sign, never a prescribed magnitude. No upstream code is changed. diff --git a/test/evaluation/rfc0029/byte-comparisons/bom-absent.c b/test/evaluation/rfc0029/byte-comparisons/bom-absent.c new file mode 100644 index 00000000..45582796 --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/bom-absent.c @@ -0,0 +1,2 @@ +int inspect(const unsigned char *,unsigned,unsigned); +int main(void){const unsigned char p[]="abc"; return inspect(p,4,0);} diff --git a/test/evaluation/rfc0029/byte-comparisons/bom-present.c b/test/evaluation/rfc0029/byte-comparisons/bom-present.c new file mode 100644 index 00000000..bd995176 --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/bom-present.c @@ -0,0 +1,2 @@ +int inspect(const unsigned char *,unsigned,unsigned); +int main(void){const unsigned char p[]={239,187,191,0}; return inspect(p,4,0);} diff --git a/test/evaluation/rfc0029/byte-comparisons/changed.c b/test/evaluation/rfc0029/byte-comparisons/changed.c new file mode 100644 index 00000000..56aacf1e --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/changed.c @@ -0,0 +1,2 @@ +int inspect(const unsigned char *,unsigned,unsigned); +int main(void){unsigned char p[]="abc"; p[1]=120; return inspect(p,4,4);} diff --git a/test/evaluation/rfc0029/byte-comparisons/embedded-zero-memory.c b/test/evaluation/rfc0029/byte-comparisons/embedded-zero-memory.c new file mode 100644 index 00000000..73da279c --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/embedded-zero-memory.c @@ -0,0 +1,2 @@ +int inspect(const unsigned char *,unsigned,unsigned); +int main(void){const unsigned char p[]={0,120}; return inspect(p,2,1);} diff --git a/test/evaluation/rfc0029/byte-comparisons/embedded-zero-string.c b/test/evaluation/rfc0029/byte-comparisons/embedded-zero-string.c new file mode 100644 index 00000000..f972edc8 --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/embedded-zero-string.c @@ -0,0 +1,2 @@ +int inspect(const unsigned char *,unsigned,unsigned); +int main(void){const unsigned char p[]={0,120}; return inspect(p,2,2);} diff --git a/test/evaluation/rfc0029/byte-comparisons/equal.c b/test/evaluation/rfc0029/byte-comparisons/equal.c new file mode 100644 index 00000000..5b581722 --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/equal.c @@ -0,0 +1,2 @@ +int inspect(const unsigned char *,unsigned,unsigned); +int main(void){const unsigned char p[]="abc"; return inspect(p,4,4);} diff --git a/test/evaluation/rfc0029/byte-comparisons/frozen-sha256.json b/test/evaluation/rfc0029/byte-comparisons/frozen-sha256.json new file mode 100644 index 00000000..a6badc43 --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/frozen-sha256.json @@ -0,0 +1,15 @@ +{ + "PROVENANCE.md": "55c3005de7e520c0ab36ab228037fa162ffc570ece44c75736e2bfbaa6375e8e", + "bom-absent.c": "013ece49f37e64e4b2cc3509b99ecc8953769cca49c193a24d6de0a62f3807a8", + "bom-present.c": "3895d41da91978336dbad3873e1e29fbfb087144946bfd6beaeccc8ceb3bd5d5", + "changed.c": "ecb5c47309241bd499965155ffe64b25638a551a1ff068ad1aef10096d3174b8", + "embedded-zero-memory.c": "a835e0140dfa85dfd08d2e82c3160dd94f528620a11e0af96113e864f52d8580", + "embedded-zero-string.c": "11ebd010317e04b43fefc05122a634ecea12bcc8a839065dfecf6f561465d2be", + "equal.c": "aa63b3e2322a49a27442266481bd6c2a043e6cd280352fb7f772371cd30d67fa", + "library.c": "7265fed5ef56e534bc078fbf320503fb9175ab846b1cf37fb40ea61789414306", + "manifest.json": "57bea82479da010a416b436c4ea08b3e9d59c6168ca98ac975728cf4835e117e", + "partial.c": "385aaa53bc2225e968c3ba5ed5e3f0a4239468a49045eda8302c22eb6d216125", + "short.c": "77f4985dc7f8b5b369d9804914c23248695b3e16217bce84219dac6e9bd5d22c", + "sign-not-magnitude.c": "f0b84642ebd8e20083f426500843c336401af74ef620582d35e19f25dbfe5c67", + "unsigned-order.c": "4bbe7860c534eed6e0a65f9510013c4b5fbdd3e9d92b15e34fe62b4a66fcfcf5" +} diff --git a/test/evaluation/rfc0029/byte-comparisons/library.c b/test/evaluation/rfc0029/byte-comparisons/library.c new file mode 100644 index 00000000..3d48331c --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/library.c @@ -0,0 +1,10 @@ +#include +int inspect(const unsigned char *p, unsigned n, unsigned mode) { + if (mode == 0 && strncmp((const char *)p, "\xef\xbb\xbf", 3) == 0) return p[n]; + if (mode == 1 && memcmp(p, "\0y", 2) >= 0) return p[n]; + if (mode == 2 && strcmp((const char *)p, "\0y") != 0) return p[n]; + if (mode == 3 && memcmp(p, "a", 1) <= 0) return p[n]; + if (mode == 4 && strncmp((const char *)p, "abc", 3) != 0) return p[n]; + if (mode == 5 && memcmp(p, "abc", 3) == 1) return p[n]; + return 0; +} diff --git a/test/evaluation/rfc0029/byte-comparisons/manifest.json b/test/evaluation/rfc0029/byte-comparisons/manifest.json new file mode 100644 index 00000000..1cab05a6 --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/manifest.json @@ -0,0 +1,165 @@ +{ + "version": 1, + "cases": [ + { + "name": "bom-absent", + "sources": [ + "bom-absent.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "bom-present", + "sources": [ + "bom-present.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|extent|bounds|initialized|pointer|contract" + }, + { + "name": "embedded-zero-memory", + "sources": [ + "embedded-zero-memory.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "embedded-zero-string", + "sources": [ + "embedded-zero-string.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "unsigned-order", + "sources": [ + "unsigned-order.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "equal", + "sources": [ + "equal.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "changed", + "sources": [ + "changed.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|extent|bounds|initialized|pointer|contract" + }, + { + "name": "partial", + "sources": [ + "partial.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|extent|bounds|initialized|pointer|contract" + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|extent|bounds|initialized|pointer|contract" + }, + { + "name": "sign-not-magnitude", + "sources": [ + "sign-not-magnitude.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|extent|bounds|initialized|pointer|contract" + } + ] +} diff --git a/test/evaluation/rfc0029/byte-comparisons/partial.c b/test/evaluation/rfc0029/byte-comparisons/partial.c new file mode 100644 index 00000000..5b6ca662 --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/partial.c @@ -0,0 +1,2 @@ +int inspect(const unsigned char *,unsigned,unsigned); +int main(void){unsigned char p[4]; p[0]=97; return inspect(p,4,4);} diff --git a/test/evaluation/rfc0029/byte-comparisons/short.c b/test/evaluation/rfc0029/byte-comparisons/short.c new file mode 100644 index 00000000..a2c249bb --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/short.c @@ -0,0 +1,2 @@ +int inspect(const unsigned char *,unsigned,unsigned); +int main(void){const unsigned char p[]={97}; return inspect(p,1,4);} diff --git a/test/evaluation/rfc0029/byte-comparisons/sign-not-magnitude.c b/test/evaluation/rfc0029/byte-comparisons/sign-not-magnitude.c new file mode 100644 index 00000000..7d08c4f7 --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/sign-not-magnitude.c @@ -0,0 +1,2 @@ +int inspect(const unsigned char *,unsigned,unsigned); +int main(void){const unsigned char p[]="dbc"; return inspect(p,4,5);} diff --git a/test/evaluation/rfc0029/byte-comparisons/unsigned-order.c b/test/evaluation/rfc0029/byte-comparisons/unsigned-order.c new file mode 100644 index 00000000..b27e7a5f --- /dev/null +++ b/test/evaluation/rfc0029/byte-comparisons/unsigned-order.c @@ -0,0 +1,2 @@ +int inspect(const unsigned char *,unsigned,unsigned); +int main(void){const unsigned char p[]={255}; return inspect(p,1,3);} diff --git a/test/evaluation/rfc0029/byte-cursor-content/PROVENANCE.md b/test/evaluation/rfc0029/byte-cursor-content/PROVENANCE.md new file mode 100644 index 00000000..17015543 --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-content/PROVENANCE.md @@ -0,0 +1 @@ +Frozen before byte-content inference changes. Baseline: completed Debug candidate72k. Known bytes may refine branches only while actual initialized storage and its contents survive. Mutation, partial initialization and actual escape-marker bytes retain unsafe accesses. The generic helper has no trusted contract. diff --git a/test/evaluation/rfc0029/byte-cursor-content/api.h b/test/evaluation/rfc0029/byte-cursor-content/api.h new file mode 100644 index 00000000..d9f9fc3e --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-content/api.h @@ -0,0 +1 @@ +int inspect(const unsigned char *, unsigned); diff --git a/test/evaluation/rfc0029/byte-cursor-content/bad.c b/test/evaluation/rfc0029/byte-cursor-content/bad.c new file mode 100644 index 00000000..4610ad7e --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-content/bad.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char text[]={'a','\\','c'};return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-cursor-content/changed.c b/test/evaluation/rfc0029/byte-cursor-content/changed.c new file mode 100644 index 00000000..7e5245ed --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-content/changed.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){unsigned char text[]={'a','b','c'};text[1]='\\';return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-cursor-content/frozen-sha256.json b/test/evaluation/rfc0029/byte-cursor-content/frozen-sha256.json new file mode 100644 index 00000000..791fa529 --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-content/frozen-sha256.json @@ -0,0 +1,14 @@ +{ + "PROVENANCE.md": "b435fe5bec1de9dd417ee10536dab53540b4f73b2769de1be7f4f4d4ba7235a1", + "api.h": "b85b58b60eb2133cb3735a9aee546bbc47e730a7109ed9cd15548b551b22dfdb", + "bad.c": "f4bd90b2d40682885c9258c3d76de018649349421c2d658b75add1b985737627", + "changed.c": "a89ab339ed4e0e7e1c268b96b5f94293855171f9944f26ffb6251689ec3af739", + "good.c": "b90e820d91aaca8224205110e308f2ad8411aaf651bd535c0721aaf929f93e13", + "library.c": "b07cf8e3f465ea3e062b5726c6618c1006b3b8d6f4b72407893adf0929e799b6", + "local-write.c": "948070d3ff49cdef2a97638f688b19c36bb4a09f2770519ebb720894834a81ee", + "local.c": "5a8339fb8feadc4b8580ffc631bbb1f24adec76c15da5d429757a372a115abb8", + "manifest.json": "61435be1d9058e9ba6bbc2039164e59c99abdc587f91673ae157a80af73a5c79", + "offset.c": "7a18205a9bb1f524c2ea248aaea4842d2b704364a820ff4b3ca68f3c2bd3228c", + "uninitialized.c": "c8996a44a2bd7a4912b714f8c87a47be8d66101dec89bfaeda1bb3fa1ec5ea68", + "unknown-write.c": "e8ba51578ba4f73c21ed5f3c543e52457a4807dde2509dfd721277d57915db14" +} diff --git a/test/evaluation/rfc0029/byte-cursor-content/good.c b/test/evaluation/rfc0029/byte-cursor-content/good.c new file mode 100644 index 00000000..65ade3a6 --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-content/good.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char text[]={'a','b','c'};return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-cursor-content/library.c b/test/evaluation/rfc0029/byte-cursor-content/library.c new file mode 100644 index 00000000..e00737a2 --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-content/library.c @@ -0,0 +1 @@ +int inspect(const unsigned char *p,unsigned n){unsigned i=0;while(i0&&argc<3)text[argc]='\\';return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-cursor-forwarding/PROVENANCE.md b/test/evaluation/rfc0029/byte-cursor-forwarding/PROVENANCE.md new file mode 100644 index 00000000..d5d427ec --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-forwarding/PROVENANCE.md @@ -0,0 +1,3 @@ +# Byte-content forwarding + +Frozen before candidate 74e. This preserves the byte-cursor-content inputs and expected outcomes, adding an ordinary read-only forwarding function in front of the unchanged scan. The wrapper must transport actual live initialized bytes to the rechecked callee. Unknown writes, changed bytes and uninitialized tails remain rejected. diff --git a/test/evaluation/rfc0029/byte-cursor-forwarding/api.h b/test/evaluation/rfc0029/byte-cursor-forwarding/api.h new file mode 100644 index 00000000..d9f9fc3e --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-forwarding/api.h @@ -0,0 +1 @@ +int inspect(const unsigned char *, unsigned); diff --git a/test/evaluation/rfc0029/byte-cursor-forwarding/bad.c b/test/evaluation/rfc0029/byte-cursor-forwarding/bad.c new file mode 100644 index 00000000..4610ad7e --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-forwarding/bad.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char text[]={'a','\\','c'};return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-cursor-forwarding/changed.c b/test/evaluation/rfc0029/byte-cursor-forwarding/changed.c new file mode 100644 index 00000000..7e5245ed --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-forwarding/changed.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){unsigned char text[]={'a','b','c'};text[1]='\\';return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-cursor-forwarding/frozen-sha256.json b/test/evaluation/rfc0029/byte-cursor-forwarding/frozen-sha256.json new file mode 100644 index 00000000..884bd4bc --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-forwarding/frozen-sha256.json @@ -0,0 +1,13 @@ +{ + "api.h": "b85b58b60eb2133cb3735a9aee546bbc47e730a7109ed9cd15548b551b22dfdb", + "bad.c": "f4bd90b2d40682885c9258c3d76de018649349421c2d658b75add1b985737627", + "changed.c": "a89ab339ed4e0e7e1c268b96b5f94293855171f9944f26ffb6251689ec3af739", + "good.c": "b90e820d91aaca8224205110e308f2ad8411aaf651bd535c0721aaf929f93e13", + "library.c": "3ef630dddf444e70d8cde9104a8f9fb7060717fc966cfce98568637c3b9023b0", + "local-write.c": "948070d3ff49cdef2a97638f688b19c36bb4a09f2770519ebb720894834a81ee", + "local.c": "5a8339fb8feadc4b8580ffc631bbb1f24adec76c15da5d429757a372a115abb8", + "manifest.json": "61435be1d9058e9ba6bbc2039164e59c99abdc587f91673ae157a80af73a5c79", + "offset.c": "7a18205a9bb1f524c2ea248aaea4842d2b704364a820ff4b3ca68f3c2bd3228c", + "uninitialized.c": "c8996a44a2bd7a4912b714f8c87a47be8d66101dec89bfaeda1bb3fa1ec5ea68", + "unknown-write.c": "e8ba51578ba4f73c21ed5f3c543e52457a4807dde2509dfd721277d57915db14" +} diff --git a/test/evaluation/rfc0029/byte-cursor-forwarding/good.c b/test/evaluation/rfc0029/byte-cursor-forwarding/good.c new file mode 100644 index 00000000..65ade3a6 --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-forwarding/good.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char text[]={'a','b','c'};return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-cursor-forwarding/library.c b/test/evaluation/rfc0029/byte-cursor-forwarding/library.c new file mode 100644 index 00000000..c95c0490 --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-forwarding/library.c @@ -0,0 +1,2 @@ +static int inspect_bytes(const unsigned char *p,unsigned n){unsigned i=0;while(i0&&argc<3)text[argc]='\\';return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-cursor-frames/PROVENANCE.md b/test/evaluation/rfc0029/byte-cursor-frames/PROVENANCE.md new file mode 100644 index 00000000..232df513 --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-frames/PROVENANCE.md @@ -0,0 +1 @@ +Frozen before extending exact-byte frame preservation. Baseline: completed Debug73f semantics, with temporary parse_string-only diagnostics. Tests fresh allocation and address-taken local pointer cells separately from aliased input writes, actual escape bytes, changed contents and partial initialization. No trusted helper contract. diff --git a/test/evaluation/rfc0029/byte-cursor-frames/REVIEW.md b/test/evaluation/rfc0029/byte-cursor-frames/REVIEW.md new file mode 100644 index 00000000..e6ddbc0f --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-frames/REVIEW.md @@ -0,0 +1,3 @@ +# Diagnostic expectation review + +Candidate 74g rejects the alias case for the required separation between the written byte object and readable input. The frozen manifest expected only a bounds or initialization diagnostic. The reviewed manifest adds the actual separation obligation to that case only; every source, acceptance outcome and other expectation is unchanged. The original manifest and failed observations remain retained. diff --git a/test/evaluation/rfc0029/byte-cursor-frames/alias.c b/test/evaluation/rfc0029/byte-cursor-frames/alias.c new file mode 100644 index 00000000..13bd26c1 --- /dev/null +++ b/test/evaluation/rfc0029/byte-cursor-frames/alias.c @@ -0,0 +1,2 @@ +int inspect(unsigned char *p,unsigned char *q,unsigned n){q[1]='\\';for(unsigned i=0;i +int inspect(const unsigned char *p,unsigned n) { + unsigned char *out=malloc(n); if(!out)return 0; + unsigned char *q=out; unsigned char **slot=&q; + for(unsigned i=0;i +int main(void){static const unsigned char text[]={97,98,99};free((void*)text);return 0;} diff --git a/test/evaluation/rfc0029/byte-global-frames/uninitialized.c b/test/evaluation/rfc0029/byte-global-frames/uninitialized.c new file mode 100644 index 00000000..229af1b5 --- /dev/null +++ b/test/evaluation/rfc0029/byte-global-frames/uninitialized.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){unsigned char text[3];text[0]='a';return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-global-frames/unknown-write.c b/test/evaluation/rfc0029/byte-global-frames/unknown-write.c new file mode 100644 index 00000000..cc57ae82 --- /dev/null +++ b/test/evaluation/rfc0029/byte-global-frames/unknown-write.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(int argc,char **argv){unsigned char text[]={'a','b','c'};if(argc>0&&argc<3)text[argc]='\\';return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-helper-frames/PROVENANCE.md b/test/evaluation/rfc0029/byte-helper-frames/PROVENANCE.md new file mode 100644 index 00000000..caca1a48 --- /dev/null +++ b/test/evaluation/rfc0029/byte-helper-frames/PROVENANCE.md @@ -0,0 +1,3 @@ +# Byte contents across local helper writes + +Frozen before candidate75e. The byte-cursor-content inputs and expected outcomes are unchanged. The scan calls an ordinary complete helper that increments an addressed local scalar. Only the callee's actual represented local write can preserve separate byte storage; unknown calls and input mutation cannot. diff --git a/test/evaluation/rfc0029/byte-helper-frames/api.h b/test/evaluation/rfc0029/byte-helper-frames/api.h new file mode 100644 index 00000000..d9f9fc3e --- /dev/null +++ b/test/evaluation/rfc0029/byte-helper-frames/api.h @@ -0,0 +1 @@ +int inspect(const unsigned char *, unsigned); diff --git a/test/evaluation/rfc0029/byte-helper-frames/bad.c b/test/evaluation/rfc0029/byte-helper-frames/bad.c new file mode 100644 index 00000000..4610ad7e --- /dev/null +++ b/test/evaluation/rfc0029/byte-helper-frames/bad.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char text[]={'a','\\','c'};return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-helper-frames/changed.c b/test/evaluation/rfc0029/byte-helper-frames/changed.c new file mode 100644 index 00000000..7e5245ed --- /dev/null +++ b/test/evaluation/rfc0029/byte-helper-frames/changed.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){unsigned char text[]={'a','b','c'};text[1]='\\';return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-helper-frames/frozen-sha256.json b/test/evaluation/rfc0029/byte-helper-frames/frozen-sha256.json new file mode 100644 index 00000000..8ced84bf --- /dev/null +++ b/test/evaluation/rfc0029/byte-helper-frames/frozen-sha256.json @@ -0,0 +1,14 @@ +{ + "PROVENANCE.md": "606f69be73767f33089f31c7d84485d349e94ec56b76a6ad8ac4a6350714d126", + "api.h": "b85b58b60eb2133cb3735a9aee546bbc47e730a7109ed9cd15548b551b22dfdb", + "bad.c": "f4bd90b2d40682885c9258c3d76de018649349421c2d658b75add1b985737627", + "changed.c": "a89ab339ed4e0e7e1c268b96b5f94293855171f9944f26ffb6251689ec3af739", + "good.c": "b90e820d91aaca8224205110e308f2ad8411aaf651bd535c0721aaf929f93e13", + "library.c": "dae5b97d2feb32543a7f557283a0bc1b5f787b97929dd52f9c961693e7839ace", + "local-write.c": "948070d3ff49cdef2a97638f688b19c36bb4a09f2770519ebb720894834a81ee", + "local.c": "5a8339fb8feadc4b8580ffc631bbb1f24adec76c15da5d429757a372a115abb8", + "manifest.json": "61435be1d9058e9ba6bbc2039164e59c99abdc587f91673ae157a80af73a5c79", + "offset.c": "7a18205a9bb1f524c2ea248aaea4842d2b704364a820ff4b3ca68f3c2bd3228c", + "uninitialized.c": "c8996a44a2bd7a4912b714f8c87a47be8d66101dec89bfaeda1bb3fa1ec5ea68", + "unknown-write.c": "e8ba51578ba4f73c21ed5f3c543e52457a4807dde2509dfd721277d57915db14" +} diff --git a/test/evaluation/rfc0029/byte-helper-frames/good.c b/test/evaluation/rfc0029/byte-helper-frames/good.c new file mode 100644 index 00000000..65ade3a6 --- /dev/null +++ b/test/evaluation/rfc0029/byte-helper-frames/good.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char text[]={'a','b','c'};return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-helper-frames/library.c b/test/evaluation/rfc0029/byte-helper-frames/library.c new file mode 100644 index 00000000..f0af9217 --- /dev/null +++ b/test/evaluation/rfc0029/byte-helper-frames/library.c @@ -0,0 +1,2 @@ +static void bump(unsigned *p){++*p;} +int inspect(const unsigned char *p,unsigned n){unsigned calls=0; bump(&calls);unsigned i=0;while(i0&&argc<3)text[argc]='\\';return inspect(text,3);} diff --git a/test/evaluation/rfc0029/byte-loop-partitions/PROVENANCE.md b/test/evaluation/rfc0029/byte-loop-partitions/PROVENANCE.md new file mode 100644 index 00000000..2a19e87f --- /dev/null +++ b/test/evaluation/rfc0029/byte-loop-partitions/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate80e before using the existing bounded loop-partition domain for byte-specialized while/do regions. Repeated delimiters test the first actual loop exit; skipping it, changing it, omitting it, and reading an uninitialized output remain negative. No loop bound or context budget is increased. diff --git a/test/evaluation/rfc0029/byte-loop-partitions/api.h b/test/evaluation/rfc0029/byte-loop-partitions/api.h new file mode 100644 index 00000000..f679b146 --- /dev/null +++ b/test/evaluation/rfc0029/byte-loop-partitions/api.h @@ -0,0 +1 @@ +unsigned scan(const unsigned char *,unsigned); diff --git a/test/evaluation/rfc0029/byte-loop-partitions/changed.c b/test/evaluation/rfc0029/byte-loop-partitions/changed.c new file mode 100644 index 00000000..fef81043 --- /dev/null +++ b/test/evaluation/rfc0029/byte-loop-partitions/changed.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){unsigned char p[]={97,34,98,99,100,34};p[1]=120;char out[2]={1,2};return out[scan(p,sizeof p)];} diff --git a/test/evaluation/rfc0029/byte-loop-partitions/do.c b/test/evaluation/rfc0029/byte-loop-partitions/do.c new file mode 100644 index 00000000..705c0803 --- /dev/null +++ b/test/evaluation/rfc0029/byte-loop-partitions/do.c @@ -0,0 +1,2 @@ +#include "api.h" +unsigned scan(const unsigned char*p,unsigned n){unsigned i=0;if(!n)return 0;do{if(p[i]==34)return i;i++;}while(i +size_t scan(const unsigned char *,size_t,size_t); +size_t forward(const unsigned char *,size_t,size_t); diff --git a/test/evaluation/rfc0029/byte-switch-partitions-reviewed/beyond-bound.c b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/beyond-bound.c new file mode 100644 index 00000000..8314f561 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/beyond-bound.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char p[]="111111111111111111111111111111111111111x";size_t k=scan(p,sizeof p,0);unsigned char out[1]={0};if(k==1)return 0;return out[k+1];} diff --git a/test/evaluation/rfc0029/byte-switch-partitions-reviewed/changed.c b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/changed.c new file mode 100644 index 00000000..2dae9c50 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/changed.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){unsigned char p[]="1x2x";p[1]='1';size_t k=scan(p,sizeof p,0);unsigned char out[1]={0};if(k==1)return 0;return out[k+1];} diff --git a/test/evaluation/rfc0029/byte-switch-partitions-reviewed/direct.c b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/direct.c new file mode 100644 index 00000000..f3e83e17 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/direct.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char p[]="1x2x";size_t k=scan(p,sizeof p,0);unsigned char out[1]={0};if(k==1)return 0;return out[k+1];} diff --git a/test/evaluation/rfc0029/byte-switch-partitions-reviewed/forward.c b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/forward.c new file mode 100644 index 00000000..0fda5ed8 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/forward.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char p[]="1x2x";size_t k=forward(p,sizeof p,0);unsigned char out[1]={0};if(k==1)return 0;return out[k+1];} diff --git a/test/evaluation/rfc0029/byte-switch-partitions-reviewed/frozen-sha256.json b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/frozen-sha256.json new file mode 100644 index 00000000..623e38ad --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/frozen-sha256.json @@ -0,0 +1,16 @@ +{ + "PROVENANCE.md": "6b113ffe7c338679b71737925d7ba591a1b88cee43752947b39a388284bc34cb", + "api.h": "a514f09f44f4812b3f0707e143d2a287166ee0144558a02d1342cad0e09f9b83", + "beyond-bound.c": "c3f049fc67268426e299462ee2c58b148426b6a979fad56a1fc9fbc3fdacbb23", + "changed.c": "17fa1431d29cf6fb4e8c0e58ae9d87fe98d0cffe224c78612cac7fdcde356847", + "direct.c": "a060b94542b514d47988345317004e53024aab7d6af523ddefbba14660cfbbba", + "forward.c": "c0b0d9f41809393e57f2667717bcb7a4026593a12137ad2d6471f02e9cfa5c82", + "manifest.json": "3a5cfc3d566e496185b2ee6d30b50193fba48ff5d51b6a003772f5aca5d983aa", + "missing.c": "56ae414006404f49734adf36719d101e0f4263ec51221cb7240c31e3537a5eba", + "scanner.c": "cf15abee3ced91a7584501a955aa110375e54d84d931380f26e0b85c04e9eeb6", + "shifted.c": "8a7ccd71efa7a84ed2cacd5f35b6a268374c8b81e7d5c5ce18a4061d00be11ea", + "skip-scanner.c": "690b840dc4b7ac2e73dc4d9e721a232c0d81df8940f3f3d896f39dbb62090a34", + "skip.c": "a060b94542b514d47988345317004e53024aab7d6af523ddefbba14660cfbbba", + "uninitialized.c": "e6333cbdb156e11680e0546d098b6b8eea44e2c6284bfeb76b5821b1734005b1", + "wrapper.c": "c4593c29a031ff02a63258e369a86ad2a766d51267eb5f0bdd9288236139269d" +} diff --git a/test/evaluation/rfc0029/byte-switch-partitions-reviewed/manifest.json b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/manifest.json new file mode 100644 index 00000000..8cb9b473 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/manifest.json @@ -0,0 +1,141 @@ +{ + "version": 1, + "cases": [ + { + "name": "direct", + "sources": [ + "direct.c", + "scanner.c", + "wrapper.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "forward", + "sources": [ + "forward.c", + "scanner.c", + "wrapper.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "shifted", + "sources": [ + "shifted.c", + "scanner.c", + "wrapper.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "skip", + "sources": [ + "skip.c", + "skip-scanner.c", + "wrapper.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bound|access|integer|contract" + }, + { + "name": "changed", + "sources": [ + "changed.c", + "scanner.c", + "wrapper.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bound|access|integer|contract" + }, + { + "name": "missing", + "sources": [ + "missing.c", + "scanner.c", + "wrapper.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bound|access|integer|contract" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "scanner.c", + "wrapper.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bound|access|integer|contract" + }, + { + "name": "beyond-bound", + "sources": [ + "beyond-bound.c", + "scanner.c", + "wrapper.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bound|access|integer|contract" + } + ] +} diff --git a/test/evaluation/rfc0029/byte-switch-partitions-reviewed/missing.c b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/missing.c new file mode 100644 index 00000000..c07cc16d --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/missing.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char p[]="1122";size_t k=scan(p,sizeof p,0);unsigned char out[1]={0};if(k==1)return 0;return out[k+1];} diff --git a/test/evaluation/rfc0029/byte-switch-partitions-reviewed/scanner.c b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/scanner.c new file mode 100644 index 00000000..1a4641dd --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions-reviewed/scanner.c @@ -0,0 +1,2 @@ +#include "api.h" +size_t scan(const unsigned char *p,size_t n,size_t start){size_t i;for(i=0;start+i +size_t scan(const unsigned char *,size_t,size_t); +size_t forward(const unsigned char *,size_t,size_t); diff --git a/test/evaluation/rfc0029/byte-switch-partitions/beyond-bound.c b/test/evaluation/rfc0029/byte-switch-partitions/beyond-bound.c new file mode 100644 index 00000000..8314f561 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions/beyond-bound.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char p[]="111111111111111111111111111111111111111x";size_t k=scan(p,sizeof p,0);unsigned char out[1]={0};if(k==1)return 0;return out[k+1];} diff --git a/test/evaluation/rfc0029/byte-switch-partitions/changed.c b/test/evaluation/rfc0029/byte-switch-partitions/changed.c new file mode 100644 index 00000000..2dae9c50 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions/changed.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){unsigned char p[]="1x2x";p[1]='1';size_t k=scan(p,sizeof p,0);unsigned char out[1]={0};if(k==1)return 0;return out[k+1];} diff --git a/test/evaluation/rfc0029/byte-switch-partitions/direct.c b/test/evaluation/rfc0029/byte-switch-partitions/direct.c new file mode 100644 index 00000000..f3e83e17 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions/direct.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char p[]="1x2x";size_t k=scan(p,sizeof p,0);unsigned char out[1]={0};if(k==1)return 0;return out[k+1];} diff --git a/test/evaluation/rfc0029/byte-switch-partitions/forward.c b/test/evaluation/rfc0029/byte-switch-partitions/forward.c new file mode 100644 index 00000000..0fda5ed8 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions/forward.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char p[]="1x2x";size_t k=forward(p,sizeof p,0);unsigned char out[1]={0};if(k==1)return 0;return out[k+1];} diff --git a/test/evaluation/rfc0029/byte-switch-partitions/frozen-sha256.json b/test/evaluation/rfc0029/byte-switch-partitions/frozen-sha256.json new file mode 100644 index 00000000..499f8164 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions/frozen-sha256.json @@ -0,0 +1,14 @@ +{ + "PROVENANCE.md": "dee04e97556afcf051f98e407c2dd3a3713bd67a646d683957fa5833b62909e0", + "api.h": "a514f09f44f4812b3f0707e143d2a287166ee0144558a02d1342cad0e09f9b83", + "beyond-bound.c": "c3f049fc67268426e299462ee2c58b148426b6a979fad56a1fc9fbc3fdacbb23", + "changed.c": "17fa1431d29cf6fb4e8c0e58ae9d87fe98d0cffe224c78612cac7fdcde356847", + "direct.c": "a060b94542b514d47988345317004e53024aab7d6af523ddefbba14660cfbbba", + "forward.c": "c0b0d9f41809393e57f2667717bcb7a4026593a12137ad2d6471f02e9cfa5c82", + "manifest.json": "5846f92a00b50e64da8daacca9d03c407858ffb465b3f0382d0d36c8a11afa09", + "missing.c": "56ae414006404f49734adf36719d101e0f4263ec51221cb7240c31e3537a5eba", + "scanner.c": "cf15abee3ced91a7584501a955aa110375e54d84d931380f26e0b85c04e9eeb6", + "shifted.c": "8a7ccd71efa7a84ed2cacd5f35b6a268374c8b81e7d5c5ce18a4061d00be11ea", + "skip.c": "a060b94542b514d47988345317004e53024aab7d6af523ddefbba14660cfbbba", + "uninitialized.c": "e6333cbdb156e11680e0546d098b6b8eea44e2c6284bfeb76b5821b1734005b1" +} diff --git a/test/evaluation/rfc0029/byte-switch-partitions/manifest.json b/test/evaluation/rfc0029/byte-switch-partitions/manifest.json new file mode 100644 index 00000000..b7d74e14 --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions/manifest.json @@ -0,0 +1,141 @@ +{ + "version": 1, + "cases": [ + { + "name": "direct", + "sources": [ + "direct.c", + "scanner.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "forward", + "sources": [ + "forward.c", + "scanner.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "shifted", + "sources": [ + "shifted.c", + "scanner.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "skip", + "sources": [ + "skip.c", + "skip.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bound|access|integer|contract" + }, + { + "name": "changed", + "sources": [ + "changed.c", + "scanner.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bound|access|integer|contract" + }, + { + "name": "missing", + "sources": [ + "missing.c", + "scanner.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bound|access|integer|contract" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "scanner.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bound|access|integer|contract" + }, + { + "name": "beyond-bound", + "sources": [ + "beyond-bound.c", + "scanner.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|bound|access|integer|contract" + } + ] +} diff --git a/test/evaluation/rfc0029/byte-switch-partitions/missing.c b/test/evaluation/rfc0029/byte-switch-partitions/missing.c new file mode 100644 index 00000000..c07cc16d --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions/missing.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char p[]="1122";size_t k=scan(p,sizeof p,0);unsigned char out[1]={0};if(k==1)return 0;return out[k+1];} diff --git a/test/evaluation/rfc0029/byte-switch-partitions/scanner.c b/test/evaluation/rfc0029/byte-switch-partitions/scanner.c new file mode 100644 index 00000000..1a4641dd --- /dev/null +++ b/test/evaluation/rfc0029/byte-switch-partitions/scanner.c @@ -0,0 +1,2 @@ +#include "api.h" +size_t scan(const unsigned char *p,size_t n,size_t start){size_t i;for(i=0;start+i +static void *create(void *(*allocate)(size_t), size_t n) { + if (!n) return 0; + unsigned char *p = allocate(n); + if (p) p[n - 1] = 7; + return p; +} +int main(void) { + void *p = create(malloc, 8); + free(p); + return 0; +} diff --git a/test/evaluation/rfc0029/callback-short-bad.c b/test/evaluation/rfc0029/callback-short-bad.c new file mode 100644 index 00000000..ee0389b7 --- /dev/null +++ b/test/evaluation/rfc0029/callback-short-bad.c @@ -0,0 +1,13 @@ +#include +static void *short_allocate(size_t n) { (void)n; return malloc(1); } +static void *create(void *(*allocate)(size_t), size_t n) { + if (!n) return 0; + unsigned char *p = allocate(n); + if (p) p[n - 1] = 7; + return p; +} +int main(void) { + void *p = create(short_allocate, 8); + free(p); + return 0; +} diff --git a/test/evaluation/rfc0029/cast-reader-intervals/README.md b/test/evaluation/rfc0029/cast-reader-intervals/README.md new file mode 100644 index 00000000..cc1b1a68 --- /dev/null +++ b/test/evaluation/rfc0029/cast-reader-intervals/README.md @@ -0,0 +1 @@ +RFC 0029 discovered regression: transparent byte-pointer casts around reader cursor arithmetic must retain the evaluated byte offset and original storage extent. Frozen before the projection repair; scaled offsets, forged capacity and uninitialized data remain independently checked. No upstream source is changed. diff --git a/test/evaluation/rfc0029/cast-reader-intervals/forged.c b/test/evaluation/rfc0029/cast-reader-intervals/forged.c new file mode 100644 index 00000000..b78b189e --- /dev/null +++ b/test/evaluation/rfc0029/cast-reader-intervals/forged.c @@ -0,0 +1,6 @@ +#include "reader.h" +int main(void) { + unsigned char input[2] = {'a', 'b'}; + struct reader r = {input, 6, 0, 7}; + return prefix(&r); +} diff --git a/test/evaluation/rfc0029/cast-reader-intervals/frozen-sha256.json b/test/evaluation/rfc0029/cast-reader-intervals/frozen-sha256.json new file mode 100644 index 00000000..296d9c70 --- /dev/null +++ b/test/evaluation/rfc0029/cast-reader-intervals/frozen-sha256.json @@ -0,0 +1,11 @@ +{ + "README.md": "815c0deff7ec559a4ec5e666d8a9d59d69c875d7afc50948f84834670a85367f", + "forged.c": "945acac123fa1d50be5c4c70f7481466fe21bc2d50a8120d412428abb8503b81", + "good.c": "b19f752f7818ac89f820251c92356c3d43a331d5fccfc3eb608885d1f1f218c4", + "library.c": "125bebc44a01565ec579bb5fb3d92705afa615c3f20aef5071371a2f2710f2aa", + "manifest.json": "1accf8e20444b3996f7ff9185ce320a6313d61ad42d363088264b2bfb91b9a83", + "reader.h": "8fe1d59946dcb86689da856a41d0fb0038ac9792c6cc63e73be0fed138d3f90c", + "scaled-bad.c": "35452a77c850622e8be92851b990e6ab60406f2706a8ab3749565ec070ced107", + "scaled-good.c": "72e9a3cfd1c1aa502d86f7c904d5ae939537876f3182781113307cb9e7bb04c4", + "uninitialized.c": "f6eda98b4da3326a64afeddf1a41f14dce786286738dc94073b8bda44d13478f" +} diff --git a/test/evaluation/rfc0029/cast-reader-intervals/good.c b/test/evaluation/rfc0029/cast-reader-intervals/good.c new file mode 100644 index 00000000..bf75db8a --- /dev/null +++ b/test/evaluation/rfc0029/cast-reader-intervals/good.c @@ -0,0 +1,6 @@ +#include "reader.h" +int main(void) { + unsigned char input[6] = {'a', 'b', 'c', 0, 0, 0}; + struct reader r = {input, sizeof input, 0, 7}; + return prefix(&r); +} diff --git a/test/evaluation/rfc0029/cast-reader-intervals/library.c b/test/evaluation/rfc0029/cast-reader-intervals/library.c new file mode 100644 index 00000000..0030a6c8 --- /dev/null +++ b/test/evaluation/rfc0029/cast-reader-intervals/library.c @@ -0,0 +1,11 @@ +#include "reader.h" +#include +int prefix(struct reader *r) { + if (!r || !r->data || r->position != 0) return 0; + if (r->position + 4 < r->capacity && + strncmp((const char *)(r->data + r->position), "abc", 3) == 0) { + r->position += 3; + return 1; + } + return 0; +} diff --git a/test/evaluation/rfc0029/cast-reader-intervals/manifest.json b/test/evaluation/rfc0029/cast-reader-intervals/manifest.json new file mode 100644 index 00000000..63d2d57e --- /dev/null +++ b/test/evaluation/rfc0029/cast-reader-intervals/manifest.json @@ -0,0 +1,83 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "forged", + "sources": [ + "forged.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|interval|initialized|bounds" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|interval|initialized|bounds" + }, + { + "name": "scaled-good", + "sources": [ + "scaled-good.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "scaled-bad", + "sources": [ + "scaled-bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|interval|initialized|bounds" + } + ] +} diff --git a/test/evaluation/rfc0029/cast-reader-intervals/reader.h b/test/evaluation/rfc0029/cast-reader-intervals/reader.h new file mode 100644 index 00000000..207552ff --- /dev/null +++ b/test/evaluation/rfc0029/cast-reader-intervals/reader.h @@ -0,0 +1,3 @@ +#include +struct reader { const unsigned char *data; size_t capacity, position; unsigned depth; }; +int prefix(struct reader *r); diff --git a/test/evaluation/rfc0029/cast-reader-intervals/scaled-bad.c b/test/evaluation/rfc0029/cast-reader-intervals/scaled-bad.c new file mode 100644 index 00000000..12a1a722 --- /dev/null +++ b/test/evaluation/rfc0029/cast-reader-intervals/scaled-bad.c @@ -0,0 +1,5 @@ +#include +int main(void) { + unsigned short input[3] = {0, 0, 0}; + return memcmp((const char *)(input + 3), "xx", 2); +} diff --git a/test/evaluation/rfc0029/cast-reader-intervals/scaled-good.c b/test/evaluation/rfc0029/cast-reader-intervals/scaled-good.c new file mode 100644 index 00000000..31742edf --- /dev/null +++ b/test/evaluation/rfc0029/cast-reader-intervals/scaled-good.c @@ -0,0 +1,5 @@ +#include +int main(void) { + unsigned short input[3] = {0, 0, 0}; + return memcmp((const char *)(input + 1), "xx", 2); +} diff --git a/test/evaluation/rfc0029/cast-reader-intervals/uninitialized.c b/test/evaluation/rfc0029/cast-reader-intervals/uninitialized.c new file mode 100644 index 00000000..b5c7a3ad --- /dev/null +++ b/test/evaluation/rfc0029/cast-reader-intervals/uninitialized.c @@ -0,0 +1,6 @@ +#include "reader.h" +int main(void) { + unsigned char input[6]; input[0] = 'a'; input[2] = 'c'; + struct reader r = {input, sizeof input, 0, 7}; + return prefix(&r); +} diff --git a/test/evaluation/rfc0029/character-pointer-slots/README.md b/test/evaluation/rfc0029/character-pointer-slots/README.md new file mode 100644 index 00000000..70cba7cc --- /dev/null +++ b/test/evaluation/rfc0029/character-pointer-slots/README.md @@ -0,0 +1 @@ +Frozen before RFC 0029 character-pointer-slot support. Clang target alias semantics permit character-pointer views of a pointer cell; actual extent, initialization, mutability and end-pointer provenance remain required. Unrelated pointer element types remain unsupported. diff --git a/test/evaluation/rfc0029/character-pointer-slots/end.c b/test/evaluation/rfc0029/character-pointer-slots/end.c new file mode 100644 index 00000000..e09102f8 --- /dev/null +++ b/test/evaluation/rfc0029/character-pointer-slots/end.c @@ -0,0 +1,2 @@ +#include +int main(void) { unsigned char text[]="12.5x"; unsigned char *end=0; (void)strtod((const char *)text,(char **)&end); return *end==0; } diff --git a/test/evaluation/rfc0029/character-pointer-slots/frozen-sha256.json b/test/evaluation/rfc0029/character-pointer-slots/frozen-sha256.json new file mode 100644 index 00000000..9e206e82 --- /dev/null +++ b/test/evaluation/rfc0029/character-pointer-slots/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "README.md": "313f0abc1e24e3621d0bbd2192f2cef16e403dda8011e45cda60f36e46eef9e7", + "end.c": "d0cadcb0e6d7584123f48772170601e0be00f581431d9ed880d4c75f30093f23", + "manifest.json": "99b2e356077b682d34f0d82ac2d8b396bf7a0a1ae6f996a9cb9babbf78eb5003", + "past-end.c": "b831c6ca74f1c98638b8cc7c378a467db73cc3d8752e2574ccd6b7ce30c97fb4", + "readonly.c": "686882971f698da389edfa9bb4f2057076ebc6b8641490eda867225f77aae141", + "unrelated.c": "843eadad7a94fb55b37e43a2ecad561da81af78701bf6120b0402aa2dcc2e47c", + "write.c": "fc37c1fe0d3cf773400a7e3272381817ce121712ab4f003d3897dd3ecdd75a29" +} diff --git a/test/evaluation/rfc0029/character-pointer-slots/manifest.json b/test/evaluation/rfc0029/character-pointer-slots/manifest.json new file mode 100644 index 00000000..8db2a04c --- /dev/null +++ b/test/evaluation/rfc0029/character-pointer-slots/manifest.json @@ -0,0 +1,80 @@ +{ + "version": 1, + "cases": [ + { + "name": "end", + "sources": [ + "end.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "write", + "sources": [ + "write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "readonly", + "sources": [ + "readonly.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "read-only|writ" + }, + { + "name": "past-end", + "sources": [ + "past-end.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|interval|extent" + }, + { + "name": "unrelated", + "sources": [ + "unrelated.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct" + } + ] +} diff --git a/test/evaluation/rfc0029/character-pointer-slots/past-end.c b/test/evaluation/rfc0029/character-pointer-slots/past-end.c new file mode 100644 index 00000000..708f1c0f --- /dev/null +++ b/test/evaluation/rfc0029/character-pointer-slots/past-end.c @@ -0,0 +1,2 @@ +#include +int main(void) { unsigned char text[]="12"; unsigned char *end=0; (void)strtod((const char *)text,(char **)&end); return end[3]; } diff --git a/test/evaluation/rfc0029/character-pointer-slots/readonly.c b/test/evaluation/rfc0029/character-pointer-slots/readonly.c new file mode 100644 index 00000000..66b5e747 --- /dev/null +++ b/test/evaluation/rfc0029/character-pointer-slots/readonly.c @@ -0,0 +1,2 @@ +#include +int main(void) { unsigned char text[]="12"; unsigned char *const end=0; (void)strtod((const char *)text,(char **)&end); return 0; } diff --git a/test/evaluation/rfc0029/character-pointer-slots/unrelated.c b/test/evaluation/rfc0029/character-pointer-slots/unrelated.c new file mode 100644 index 00000000..8d06d7fe --- /dev/null +++ b/test/evaluation/rfc0029/character-pointer-slots/unrelated.c @@ -0,0 +1,2 @@ +static void set(float **out,float *p) { *out=p; } +int main(void) { float a=0; int *p=0; set((float **)&p,&a); return *p; } diff --git a/test/evaluation/rfc0029/character-pointer-slots/write.c b/test/evaluation/rfc0029/character-pointer-slots/write.c new file mode 100644 index 00000000..91fa2b5b --- /dev/null +++ b/test/evaluation/rfc0029/character-pointer-slots/write.c @@ -0,0 +1,2 @@ +static void set(char **out,char *p) { *out=p; } +int main(void) { unsigned char a[2]={7,0}; unsigned char *p=0; set((char **)&p,(char *)a); return *p; } diff --git a/test/evaluation/rfc0029/combined-callback-cases/README.md b/test/evaluation/rfc0029/combined-callback-cases/README.md new file mode 100644 index 00000000..37969436 --- /dev/null +++ b/test/evaluation/rfc0029/combined-callback-cases/README.md @@ -0,0 +1 @@ +RFC0029 combined callback and scalar input cases. Frozen before scheduling a direct call with both established inputs as one private body analysis. Baseline candidate30g. The same actual callback targets and scalar premises must be retained across separate sources and compiler objects; absent initialization, a live null callback and a possibly missing writer remain rejected. This population preserves existing behavior and does not assert a prior missed positive. diff --git a/test/evaluation/rfc0029/combined-callback-cases/api.c b/test/evaluation/rfc0029/combined-callback-cases/api.c new file mode 100644 index 00000000..cdc0649f --- /dev/null +++ b/test/evaluation/rfc0029/combined-callback-cases/api.c @@ -0,0 +1,4 @@ +#include "api.h" +int invoke(int enabled, writer_fn write, unsigned char *p) { + if(!enabled)return 0; write(p); return p[0]; +} diff --git a/test/evaluation/rfc0029/combined-callback-cases/api.h b/test/evaluation/rfc0029/combined-callback-cases/api.h new file mode 100644 index 00000000..924f0fb7 --- /dev/null +++ b/test/evaluation/rfc0029/combined-callback-cases/api.h @@ -0,0 +1,2 @@ +typedef void (*writer_fn)(unsigned char *); +int invoke(int enabled, writer_fn write, unsigned char *p); diff --git a/test/evaluation/rfc0029/combined-callback-cases/client.c b/test/evaluation/rfc0029/combined-callback-cases/client.c new file mode 100644 index 00000000..33dccc25 --- /dev/null +++ b/test/evaluation/rfc0029/combined-callback-cases/client.c @@ -0,0 +1,3 @@ +#include "api.h" +static void fill(unsigned char *p) {p[0]=7;} +int main(void) {unsigned char p[1];return invoke(1,fill,p)!=7;} diff --git a/test/evaluation/rfc0029/combined-callback-cases/frozen-sha256.json b/test/evaluation/rfc0029/combined-callback-cases/frozen-sha256.json new file mode 100644 index 00000000..78a0d66f --- /dev/null +++ b/test/evaluation/rfc0029/combined-callback-cases/frozen-sha256.json @@ -0,0 +1,11 @@ +{ + "README.md": "48adb254586d9d522a40bd3efe36d1d13f47807340d99a4c480b08debdf07ba5", + "api.c": "17f15bab5b9643b79c08028f1507bebd2eb07940b5495e97473e7ec9032cacaf", + "api.h": "3573d80611941d09e19c2e7c19187e3d8dde10ab437311c28e353d9f38749cfc", + "client.c": "42380918185b29f7f1890b151c11f43d9c76971066bca4f3dd5ee5766564181a", + "inactive.c": "00bd1cf10bd16de41219494f52d518025eed17b5051ef7d3d3d8447f842d1f4a", + "manifest.json": "b2027fa05bd7d710c9e9925e89d49b8c6e1c2a33dff228a0141a372b7022ca03", + "missing.c": "06615a6cfd0928dd5870156978eb751f11783bd2d63f6af295050c48f8e0c64f", + "mixed.c": "c89d388e7f006d93856ff94a04a37d45dd2356c31db22ef916ce32fc515b481e", + "null.c": "e82fd799a48219ff5c5e0d8b4a4f14f1e5374bab383738f72481497c99f6a85b" +} diff --git a/test/evaluation/rfc0029/combined-callback-cases/inactive.c b/test/evaluation/rfc0029/combined-callback-cases/inactive.c new file mode 100644 index 00000000..7dd7eb64 --- /dev/null +++ b/test/evaluation/rfc0029/combined-callback-cases/inactive.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void) {return invoke(0,0,0);} diff --git a/test/evaluation/rfc0029/combined-callback-cases/manifest.json b/test/evaluation/rfc0029/combined-callback-cases/manifest.json new file mode 100644 index 00000000..0f041b1b --- /dev/null +++ b/test/evaluation/rfc0029/combined-callback-cases/manifest.json @@ -0,0 +1,85 @@ +{ + "version": 1, + "cases": [ + { + "name": "combined-callback-client", + "sources": [ + "api.c", + "client.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "combined-callback-inactive", + "sources": [ + "api.c", + "inactive.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "combined-callback-missing", + "sources": [ + "api.c", + "missing.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + }, + { + "name": "combined-callback-null", + "sources": [ + "api.c", + "null.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "null|indirect|callback" + }, + { + "name": "combined-callback-mixed", + "sources": [ + "api.c", + "mixed.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + } + ] +} diff --git a/test/evaluation/rfc0029/combined-callback-cases/missing.c b/test/evaluation/rfc0029/combined-callback-cases/missing.c new file mode 100644 index 00000000..78be2a39 --- /dev/null +++ b/test/evaluation/rfc0029/combined-callback-cases/missing.c @@ -0,0 +1,3 @@ +#include "api.h" +static void skip(unsigned char *p) {(void)p;} +int main(void) {unsigned char p[1];return invoke(1,skip,p);} diff --git a/test/evaluation/rfc0029/combined-callback-cases/mixed.c b/test/evaluation/rfc0029/combined-callback-cases/mixed.c new file mode 100644 index 00000000..8bf7dd0b --- /dev/null +++ b/test/evaluation/rfc0029/combined-callback-cases/mixed.c @@ -0,0 +1,4 @@ +#include "api.h" +static void fill(unsigned char *p) {p[0]=7;} +static void skip(unsigned char *p) {(void)p;} +int main(int argc,char **argv) {(void)argv;unsigned char p[1];return invoke(1,argc>1?fill:skip,p);} diff --git a/test/evaluation/rfc0029/combined-callback-cases/null.c b/test/evaluation/rfc0029/combined-callback-cases/null.c new file mode 100644 index 00000000..600de639 --- /dev/null +++ b/test/evaluation/rfc0029/combined-callback-cases/null.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void) {unsigned char p[1]={0};return invoke(1,0,p);} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/PROVENANCE.md b/test/evaluation/rfc0029/comparison-container-frames-reviewed/PROVENANCE.md new file mode 100644 index 00000000..b56aeba2 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/PROVENANCE.md @@ -0,0 +1 @@ +The original comparison-container-frames population is retained unchanged: its recursive signed sum can overflow, so its three claimed positives are invalid. This separately frozen population uses defined unsigned accumulation and returns a boolean client status. Frozen against candidate84d before validating modeled read-only comparison frames. Four negative memory/unknown-call outcomes remain required. diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/api.h b/test/evaluation/rfc0029/comparison-container-frames-reviewed/api.h new file mode 100644 index 00000000..98834209 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/api.h @@ -0,0 +1,6 @@ +#include +#include +struct node {struct node *next,*child;unsigned value;}; +unsigned inspect(struct node *,const char *); +unsigned read_tree(const struct node *); +void drop(struct node *); diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/bounded-inspect.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/bounded-inspect.c new file mode 100644 index 00000000..069b9edc --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/bounded-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){if(strncmp(s,"ok",2)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/bounded.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/bounded.c new file mode 100644 index 00000000..950b7937 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/bounded.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/bytes-inspect.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/bytes-inspect.c new file mode 100644 index 00000000..72ac6a68 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/bytes-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){if(memcmp(s,"ok",2)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/bytes.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/bytes.c new file mode 100644 index 00000000..950b7937 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/bytes.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/extent-inspect.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/extent-inspect.c new file mode 100644 index 00000000..3b7e85c8 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/extent-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){if(memcmp(s,"okay",4)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/extent.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/extent.c new file mode 100644 index 00000000..950b7937 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/extent.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/frozen-sha256.json b/test/evaluation/rfc0029/comparison-container-frames-reviewed/frozen-sha256.json new file mode 100644 index 00000000..532e2c30 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/frozen-sha256.json @@ -0,0 +1,20 @@ +{ + "PROVENANCE.md": "80214801cd6ad4bbdf19dcd7358eecee00929430d11a53ebb66eee1b43c5d7af", + "api.h": "530abccf783302bf9062ef13195ad4598c601f4d48d967dc7ee6c1e7c29fb149", + "bounded-inspect.c": "d6b300c664d328f2b3754158606cd9fa5084ece3f5931980661a07abdf8deca7", + "bounded.c": "9005cb0a80b6da515ec385b0a68c57d3e48e8f3ef697538ede74a19f69569452", + "bytes-inspect.c": "8751b54c3a47e6fb2f12d6b6c70034e377edf87c4f9b9cf2185b9b32f53a2bef", + "bytes.c": "9005cb0a80b6da515ec385b0a68c57d3e48e8f3ef697538ede74a19f69569452", + "extent-inspect.c": "8b97fd15bfc269eedad647492912822965c0b5516eacaaf5e358f51663dc4a5d", + "extent.c": "9005cb0a80b6da515ec385b0a68c57d3e48e8f3ef697538ede74a19f69569452", + "manifest.json": "4654ce0f8d2686db5b6437075163b1c0f0e69f9339c0bbe92a375efcbc7764fa", + "reader.c": "2c68ca780b10bdd84511d65419c648d3aa6ed29b1dfcd8b1f90ede1b3141e3d6", + "released-inspect.c": "c29bead5c58b02d0635aec24037e72672348bb3c288a6359645e314706200027", + "released.c": "7e4488bd7ded73e4ae5e5aa64efe71473ae84c99321ff2ded9eabb54d9f97039", + "string-inspect.c": "68e4c10a824c64e33d38a80152636f87faaff8db6a86cd06966b558dce89f0cb", + "string.c": "9005cb0a80b6da515ec385b0a68c57d3e48e8f3ef697538ede74a19f69569452", + "uninitialized-inspect.c": "d6b300c664d328f2b3754158606cd9fa5084ece3f5931980661a07abdf8deca7", + "uninitialized.c": "9d9004eebad3fdb7b443376f806c508d0c1e3011dbbcde67f08840660fe95bd9", + "unknown-inspect.c": "295ee610aa33558fbaf492f9b4d49d1a847cc7ab911d228c993ec48cdc9385ef", + "unknown.c": "9005cb0a80b6da515ec385b0a68c57d3e48e8f3ef697538ede74a19f69569452" +} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/manifest.json b/test/evaluation/rfc0029/comparison-container-frames-reviewed/manifest.json new file mode 100644 index 00000000..15573375 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/manifest.json @@ -0,0 +1,124 @@ +{ + "version": 1, + "cases": [ + { + "name": "bounded", + "sources": [ + "bounded.c", + "bounded-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "bytes", + "sources": [ + "bytes.c", + "bytes-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "string", + "sources": [ + "string.c", + "string-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "released", + "sources": [ + "released.c", + "released-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|live|released|unavailable|contract|container|call" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "uninitialized-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|live|released|unavailable|contract|container|call" + }, + { + "name": "extent", + "sources": [ + "extent.c", + "extent-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|live|released|unavailable|contract|container|call" + }, + { + "name": "unknown", + "sources": [ + "unknown.c", + "unknown-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|live|released|unavailable|contract|container|call" + } + ] +} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/reader.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/reader.c new file mode 100644 index 00000000..1e6cb5ea --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/reader.c @@ -0,0 +1,3 @@ +#include "api.h" +unsigned read_tree(const struct node *n){return n?n->value+read_tree(n->child)+read_tree(n->next):0;} +void drop(struct node *n){while(n){struct node *next=n->next;drop(n->child);free(n);n=next;}} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/released-inspect.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/released-inspect.c new file mode 100644 index 00000000..e87ab073 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/released-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){free(n);if(strncmp(s,"ok",2)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/released.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/released.c new file mode 100644 index 00000000..7120dbc4 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/released.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";unsigned r=inspect(n,s);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/string-inspect.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/string-inspect.c new file mode 100644 index 00000000..e9a7978e --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/string-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){if(strcmp(s,"ok")==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/string.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/string.c new file mode 100644 index 00000000..950b7937 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/string.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/uninitialized-inspect.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/uninitialized-inspect.c new file mode 100644 index 00000000..069b9edc --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/uninitialized-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){if(strncmp(s,"ok",2)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/uninitialized.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/uninitialized.c new file mode 100644 index 00000000..2ad2c99c --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/uninitialized.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;char s[3];s[0]='o';unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/unknown-inspect.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/unknown-inspect.c new file mode 100644 index 00000000..172da23f --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/unknown-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){unknown(n);if(strncmp(s,"ok",2)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames-reviewed/unknown.c b/test/evaluation/rfc0029/comparison-container-frames-reviewed/unknown.c new file mode 100644 index 00000000..950b7937 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames-reviewed/unknown.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/comparison-container-frames/PROVENANCE.md b/test/evaluation/rfc0029/comparison-container-frames/PROVENANCE.md new file mode 100644 index 00000000..970a7581 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate84d before modeled read-only comparisons preserve established container predicates. Positives compare an independent initialized input before forwarding the live forest to a separately inferred reader. Negatives retain actual read bounds, initialization, stale allocation and unknown-call obligations. diff --git a/test/evaluation/rfc0029/comparison-container-frames/api.h b/test/evaluation/rfc0029/comparison-container-frames/api.h new file mode 100644 index 00000000..2ec15173 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/api.h @@ -0,0 +1,6 @@ +#include +#include +struct node {struct node *next,*child;int value;}; +int inspect(struct node *,const char *); +int read_tree(const struct node *); +void drop(struct node *); diff --git a/test/evaluation/rfc0029/comparison-container-frames/bounded-inspect.c b/test/evaluation/rfc0029/comparison-container-frames/bounded-inspect.c new file mode 100644 index 00000000..c146f014 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/bounded-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +int inspect(struct node *n,const char *s){if(strncmp(s,"ok",2)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames/bounded.c b/test/evaluation/rfc0029/comparison-container-frames/bounded.c new file mode 100644 index 00000000..61a0f118 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/bounded.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";int r=inspect(n,s);drop(n);return r;} diff --git a/test/evaluation/rfc0029/comparison-container-frames/bytes-inspect.c b/test/evaluation/rfc0029/comparison-container-frames/bytes-inspect.c new file mode 100644 index 00000000..6eb06bd6 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/bytes-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +int inspect(struct node *n,const char *s){if(memcmp(s,"ok",2)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames/bytes.c b/test/evaluation/rfc0029/comparison-container-frames/bytes.c new file mode 100644 index 00000000..61a0f118 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/bytes.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";int r=inspect(n,s);drop(n);return r;} diff --git a/test/evaluation/rfc0029/comparison-container-frames/extent-inspect.c b/test/evaluation/rfc0029/comparison-container-frames/extent-inspect.c new file mode 100644 index 00000000..c6902271 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/extent-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +int inspect(struct node *n,const char *s){if(memcmp(s,"okay",4)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames/extent.c b/test/evaluation/rfc0029/comparison-container-frames/extent.c new file mode 100644 index 00000000..61a0f118 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/extent.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";int r=inspect(n,s);drop(n);return r;} diff --git a/test/evaluation/rfc0029/comparison-container-frames/frozen-sha256.json b/test/evaluation/rfc0029/comparison-container-frames/frozen-sha256.json new file mode 100644 index 00000000..c67c4ffd --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/frozen-sha256.json @@ -0,0 +1,20 @@ +{ + "PROVENANCE.md": "6f3f4a993bae7e4e42cf1161b74144bce0ca2ee48de0016542ac9a4ee2fb04d4", + "api.h": "6147777fa33e040e5a217ba6332a8d8bd1cbc02f8c2e193b6e76eda81d996adc", + "bounded-inspect.c": "83fccfb09cb46ce2da4aa024953bcb5e0467ec3144795224727f261d44bf8e18", + "bounded.c": "f80dd77c7e819239653032cb27be1cfcab1e46ea8bacf704af76009ba3e2560a", + "bytes-inspect.c": "635b6d44544c45be2d6e81fb36bd76023c285290469c2cdde8cb3317c13a326a", + "bytes.c": "f80dd77c7e819239653032cb27be1cfcab1e46ea8bacf704af76009ba3e2560a", + "extent-inspect.c": "b9c3ad672b28a7b6bcb9f9719f2c3a068d4b55d24ed2f4a827a4f80f45d4970a", + "extent.c": "f80dd77c7e819239653032cb27be1cfcab1e46ea8bacf704af76009ba3e2560a", + "manifest.json": "4654ce0f8d2686db5b6437075163b1c0f0e69f9339c0bbe92a375efcbc7764fa", + "reader.c": "94cc0a01f232fde3f13e6f205fcdcb530a0b77c0317d62d5d1fc3cbe3c2dd7a2", + "released-inspect.c": "7571edc86769dcba0f6bc36a9c0634a34221af665adb5b17ff59c07da509bafb", + "released.c": "fe4f586e3311be089167db3dfad4d24d13362ce2794d1902058f1c4ca819b629", + "string-inspect.c": "2a96879962fb20e750faef2c21184fa8a6cf6cda04c2ee938e52c6cbd3dba282", + "string.c": "f80dd77c7e819239653032cb27be1cfcab1e46ea8bacf704af76009ba3e2560a", + "uninitialized-inspect.c": "83fccfb09cb46ce2da4aa024953bcb5e0467ec3144795224727f261d44bf8e18", + "uninitialized.c": "80708e552109e6100c2bdb9d2c0e281650cf3d1433230bccf31ffbd36bdc0842", + "unknown-inspect.c": "6f2d1efc366bce4ac7f5d6de8554a13e21abe9ff998da8b56650508d3fbf685b", + "unknown.c": "f80dd77c7e819239653032cb27be1cfcab1e46ea8bacf704af76009ba3e2560a" +} diff --git a/test/evaluation/rfc0029/comparison-container-frames/manifest.json b/test/evaluation/rfc0029/comparison-container-frames/manifest.json new file mode 100644 index 00000000..15573375 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/manifest.json @@ -0,0 +1,124 @@ +{ + "version": 1, + "cases": [ + { + "name": "bounded", + "sources": [ + "bounded.c", + "bounded-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "bytes", + "sources": [ + "bytes.c", + "bytes-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "string", + "sources": [ + "string.c", + "string-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "released", + "sources": [ + "released.c", + "released-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|live|released|unavailable|contract|container|call" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "uninitialized-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|live|released|unavailable|contract|container|call" + }, + { + "name": "extent", + "sources": [ + "extent.c", + "extent-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|live|released|unavailable|contract|container|call" + }, + { + "name": "unknown", + "sources": [ + "unknown.c", + "unknown-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|live|released|unavailable|contract|container|call" + } + ] +} diff --git a/test/evaluation/rfc0029/comparison-container-frames/reader.c b/test/evaluation/rfc0029/comparison-container-frames/reader.c new file mode 100644 index 00000000..8dc7bd1f --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/reader.c @@ -0,0 +1,3 @@ +#include "api.h" +int read_tree(const struct node *n){return n?n->value+read_tree(n->child)+read_tree(n->next):0;} +void drop(struct node *n){while(n){struct node *next=n->next;drop(n->child);free(n);n=next;}} diff --git a/test/evaluation/rfc0029/comparison-container-frames/released-inspect.c b/test/evaluation/rfc0029/comparison-container-frames/released-inspect.c new file mode 100644 index 00000000..0b0a3321 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/released-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +int inspect(struct node *n,const char *s){free(n);if(strncmp(s,"ok",2)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames/released.c b/test/evaluation/rfc0029/comparison-container-frames/released.c new file mode 100644 index 00000000..ab1546ef --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/released.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";int r=inspect(n,s);return r;} diff --git a/test/evaluation/rfc0029/comparison-container-frames/string-inspect.c b/test/evaluation/rfc0029/comparison-container-frames/string-inspect.c new file mode 100644 index 00000000..8f334081 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/string-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +int inspect(struct node *n,const char *s){if(strcmp(s,"ok")==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames/string.c b/test/evaluation/rfc0029/comparison-container-frames/string.c new file mode 100644 index 00000000..61a0f118 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/string.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";int r=inspect(n,s);drop(n);return r;} diff --git a/test/evaluation/rfc0029/comparison-container-frames/uninitialized-inspect.c b/test/evaluation/rfc0029/comparison-container-frames/uninitialized-inspect.c new file mode 100644 index 00000000..c146f014 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/uninitialized-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +int inspect(struct node *n,const char *s){if(strncmp(s,"ok",2)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames/uninitialized.c b/test/evaluation/rfc0029/comparison-container-frames/uninitialized.c new file mode 100644 index 00000000..083291dd --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/uninitialized.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;char s[3];s[0]='o';int r=inspect(n,s);drop(n);return r;} diff --git a/test/evaluation/rfc0029/comparison-container-frames/unknown-inspect.c b/test/evaluation/rfc0029/comparison-container-frames/unknown-inspect.c new file mode 100644 index 00000000..6bb2bf6a --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/unknown-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +int inspect(struct node *n,const char *s){unknown(n);if(strncmp(s,"ok",2)==0)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/comparison-container-frames/unknown.c b/test/evaluation/rfc0029/comparison-container-frames/unknown.c new file mode 100644 index 00000000..61a0f118 --- /dev/null +++ b/test/evaluation/rfc0029/comparison-container-frames/unknown.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="ok";int r=inspect(n,s);drop(n);return r;} diff --git a/test/evaluation/rfc0029/conditional-count-arguments/README.md b/test/evaluation/rfc0029/conditional-count-arguments/README.md new file mode 100644 index 00000000..aaae6465 --- /dev/null +++ b/test/evaluation/rfc0029/conditional-count-arguments/README.md @@ -0,0 +1,6 @@ +# Conditional scalar call arguments (RFC 0029) + +Frozen in the validation directory before candidate 53 while the prior full +suite runs; move unchanged into the evaluation inventory after that run. +Conditional values retain their actual converted range at call entry. Both +arms contribute, and another loop invocation cannot reuse an earlier value. diff --git a/test/evaluation/rfc0029/conditional-count-arguments/changed.c b/test/evaluation/rfc0029/conditional-count-arguments/changed.c new file mode 100644 index 00000000..25e1023e --- /dev/null +++ b/test/evaluation/rfc0029/conditional-count-arguments/changed.c @@ -0,0 +1,2 @@ +void touch(unsigned char*,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[4];for(unsigned i=0;i<2;i++)touch(out,i?8:2);return argc;} diff --git a/test/evaluation/rfc0029/conditional-count-arguments/converted.c b/test/evaluation/rfc0029/conditional-count-arguments/converted.c new file mode 100644 index 00000000..39abc5e2 --- /dev/null +++ b/test/evaluation/rfc0029/conditional-count-arguments/converted.c @@ -0,0 +1,2 @@ +void touch(unsigned char*,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[4];touch(out,(unsigned char)(argc>1?258:260));return 0;} diff --git a/test/evaluation/rfc0029/conditional-count-arguments/frozen-sha256.json b/test/evaluation/rfc0029/conditional-count-arguments/frozen-sha256.json new file mode 100644 index 00000000..7a0d792a --- /dev/null +++ b/test/evaluation/rfc0029/conditional-count-arguments/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "README.md": "1a61baea2f4e74cb96054af6618d8fc8d63477b3aae4c142a2b493de03657627", + "changed.c": "7f9a5065cebe17b14fcdf03f86faa03eccccc72fcf44d94686cbc4a674f591fe", + "converted.c": "be2c08a69122acd9bbe7aeb3a194a8c0e9028a9597d0fa5ead3a2fbad2b3d9b2", + "good.c": "1dbaa73424e73a8e0cf6d352c890fdf9abc00c8a6d0b1c1fa672a15fa9b4c1e2", + "library.c": "8b73013fdad66f9d4cba38f380baa8822d021b1f67ac193a2bcb99a6711898da", + "manifest.json": "10aa3be66af9a44cd121b12c3a98c80e30cb2e36da59b8917bc575abff7da53d", + "short.c": "3853c959430973b0f1e586c8b2975fa677084d9aed3bc08857dc10ae6bb1d019", + "zero.c": "a2ceebced586b4bdbf67c4c68951336e6fa7acdde4f493fed4038d508381bf6b" +} diff --git a/test/evaluation/rfc0029/conditional-count-arguments/good.c b/test/evaluation/rfc0029/conditional-count-arguments/good.c new file mode 100644 index 00000000..ca34ac39 --- /dev/null +++ b/test/evaluation/rfc0029/conditional-count-arguments/good.c @@ -0,0 +1,2 @@ +void touch(unsigned char*,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[4];touch(out,argc>1?4:2);return 0;} diff --git a/test/evaluation/rfc0029/conditional-count-arguments/library.c b/test/evaluation/rfc0029/conditional-count-arguments/library.c new file mode 100644 index 00000000..c80a9250 --- /dev/null +++ b/test/evaluation/rfc0029/conditional-count-arguments/library.c @@ -0,0 +1 @@ +void touch(unsigned char *p,unsigned n){if(n)p[n-1]=1;} diff --git a/test/evaluation/rfc0029/conditional-count-arguments/manifest.json b/test/evaluation/rfc0029/conditional-count-arguments/manifest.json new file mode 100644 index 00000000..b18da270 --- /dev/null +++ b/test/evaluation/rfc0029/conditional-count-arguments/manifest.json @@ -0,0 +1,85 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "converted", + "sources": [ + "converted.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "zero", + "sources": [ + "zero.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent" + }, + { + "name": "changed", + "sources": [ + "changed.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent" + } + ] +} diff --git a/test/evaluation/rfc0029/conditional-count-arguments/short.c b/test/evaluation/rfc0029/conditional-count-arguments/short.c new file mode 100644 index 00000000..995e4093 --- /dev/null +++ b/test/evaluation/rfc0029/conditional-count-arguments/short.c @@ -0,0 +1,2 @@ +void touch(unsigned char*,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[3];touch(out,argc>1?4:2);return 0;} diff --git a/test/evaluation/rfc0029/conditional-count-arguments/zero.c b/test/evaluation/rfc0029/conditional-count-arguments/zero.c new file mode 100644 index 00000000..03f9de27 --- /dev/null +++ b/test/evaluation/rfc0029/conditional-count-arguments/zero.c @@ -0,0 +1,2 @@ +void touch(unsigned char*,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[4];touch(out,argc>1?4:0);return 0;} diff --git a/test/evaluation/rfc0029/construction-helpers/build.c b/test/evaluation/rfc0029/construction-helpers/build.c new file mode 100644 index 00000000..12c91d1e --- /dev/null +++ b/test/evaluation/rfc0029/construction-helpers/build.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned char value; struct node *left, *right; }; +static void destroy(struct node *p) { if(p){destroy(p->left);destroy(p->right);free(p);} } +static unsigned char first(const unsigned char *data) { return *data; } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=first(data); + if (n>1) { + p->left=build(data+1,n-1); + if (!p->left) {free(p);return 0;} + p->right=build(data+1,n-1); + if (!p->right) {destroy(p->left);free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/construction-helpers/double.c b/test/evaluation/rfc0029/construction-helpers/double.c new file mode 100644 index 00000000..63fc0b1e --- /dev/null +++ b/test/evaluation/rfc0029/construction-helpers/double.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned char value; struct node *left, *right; }; +static void destroy(struct node *p) { if(p){destroy(p->left);destroy(p->right);free(p);} } +static unsigned char first(const unsigned char *data) { return *data; } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=first(data); + if (n>1) { + p->left=build(data+1,n-1); + if (!p->left) {free(p);return 0;} + p->right=build(data+1,n-1); + if (!p->right) {destroy(p->left);destroy(p->left);free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/construction-helpers/frozen-sha256.json b/test/evaluation/rfc0029/construction-helpers/frozen-sha256.json new file mode 100644 index 00000000..c19264b4 --- /dev/null +++ b/test/evaluation/rfc0029/construction-helpers/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "build.c": "61b81019178c691acc7d2a9fdefb460f22eafa7a0c0a6c9d5e312a22af9d066c", + "double.c": "c9a585048a096f1f18f393fd70d41642b416db62e671143707d087fd060a3e7a", + "leak.c": "76cb2b23e346d05d8650b3155c484cb1011f95268be1c1d65f384cf5e69d368e", + "manifest.json": "44cb1740d687a774c64a2af99461e13936af6069035ae8b30204c82136858bf1", + "provenance.md": "b29b0aa88103c8350e202a0503da39cd7df459335922e3d52fbb1348aa620dfc", + "short.c": "1a2edbd72017af5bf65b733b25dfc05495d56dba7ffb2ca016b37e66bbfeb335" +} diff --git a/test/evaluation/rfc0029/construction-helpers/leak.c b/test/evaluation/rfc0029/construction-helpers/leak.c new file mode 100644 index 00000000..7194da22 --- /dev/null +++ b/test/evaluation/rfc0029/construction-helpers/leak.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned char value; struct node *left, *right; }; +static void destroy(struct node *p) { if(p){destroy(p->left);destroy(p->right);free(p);} } +static unsigned char first(const unsigned char *data) { return *data; } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=first(data); + if (n>1) { + p->left=build(data+1,n-1); + if (!p->left) {free(p);return 0;} + p->right=build(data+1,n-1); + if (!p->right) {free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/construction-helpers/manifest.json b/test/evaluation/rfc0029/construction-helpers/manifest.json new file mode 100644 index 00000000..fc6a9390 --- /dev/null +++ b/test/evaluation/rfc0029/construction-helpers/manifest.json @@ -0,0 +1,68 @@ +{ + "version": 1, + "cases": [ + { + "name": "build", + "sources": [ + "build.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "leak", + "sources": [ + "leak.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|leak|cleanup|contract" + }, + { + "name": "double", + "sources": [ + "double.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "freed|release|live" + }, + { + "name": "short", + "sources": [ + "short.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + } + ] +} diff --git a/test/evaluation/rfc0029/construction-helpers/provenance.md b/test/evaluation/rfc0029/construction-helpers/provenance.md new file mode 100644 index 00000000..1088bba2 --- /dev/null +++ b/test/evaluation/rfc0029/construction-helpers/provenance.md @@ -0,0 +1 @@ +Independent tree-construction probes frozen before admitting verified external helpers in recursive contract candidates. Successful construction returns a fresh initialized binary forest; failure of the second recursive allocation destroys the already-built left subtree. The first-byte helper is read-only and inferred. Negative expectations cover missing partial-tree cleanup, duplicate cleanup, and a short input interval. No annotations or third-party summaries are used. diff --git a/test/evaluation/rfc0029/construction-helpers/short.c b/test/evaluation/rfc0029/construction-helpers/short.c new file mode 100644 index 00000000..37c521f0 --- /dev/null +++ b/test/evaluation/rfc0029/construction-helpers/short.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned char value; struct node *left, *right; }; +static void destroy(struct node *p) { if(p){destroy(p->left);destroy(p->right);free(p);} } +static unsigned char first(const unsigned char *data) { return *data; } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=first(data); + if (n>1) { + p->left=build(data+1,n-1); + if (!p->left) {free(p);return 0;} + p->right=build(data+1,n-1); + if (!p->right) {destroy(p->left);free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,4);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/construction/build.c b/test/evaluation/rfc0029/construction/build.c new file mode 100644 index 00000000..cff1e2aa --- /dev/null +++ b/test/evaluation/rfc0029/construction/build.c @@ -0,0 +1,15 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=build(data+1,n-1); + if (!p->next) {free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/construction/cycle.c b/test/evaluation/rfc0029/construction/cycle.c new file mode 100644 index 00000000..efb0338a --- /dev/null +++ b/test/evaluation/rfc0029/construction/cycle.c @@ -0,0 +1,15 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=build(data,n); + if (!p->next) {free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/construction/double.c b/test/evaluation/rfc0029/construction/double.c new file mode 100644 index 00000000..d4b30352 --- /dev/null +++ b/test/evaluation/rfc0029/construction/double.c @@ -0,0 +1,15 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=build(data+1,n-1); + if (!p->next) {free(p);free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/construction/frozen-sha256.json b/test/evaluation/rfc0029/construction/frozen-sha256.json new file mode 100644 index 00000000..4929e909 --- /dev/null +++ b/test/evaluation/rfc0029/construction/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "build.c": "66e7efd771fd4e71c631b641f228387faeda7b7d0cb2e5b3a9ef806b06fe1507", + "cycle.c": "fe82eabfa3592cc39f1905ae7f511c7f0f68f8d087ba75ccb3a717cdd2638e62", + "double.c": "f26f1579ffe8c650fba70fb8585cf1ade43c44431a1a3f099537db813403c0ab", + "leak.c": "5e0b2b7353f87c828e5563bad2440e5c0dfbf70be74f737eddf961c69838d83d", + "manifest.json": "9e8627bc67a88d37d37caf862bd2fcad4f2b1e68a82346c41ec39f61e0d8d26a", + "provenance.md": "b3b178615176eb6b285a11051ca6fedb26694eb1a04cd9a3fc81ba8bda698356", + "short.c": "4bd1af9c61faa36b2243beb005bccb6672679bb1959dfef51484aca426ba6220", + "uninitialized.c": "216ffe21f0ed664d16000ac505632e32e0b85f5889ea7af3091f14460afef3ca" +} diff --git a/test/evaluation/rfc0029/construction/leak.c b/test/evaluation/rfc0029/construction/leak.c new file mode 100644 index 00000000..afc5586c --- /dev/null +++ b/test/evaluation/rfc0029/construction/leak.c @@ -0,0 +1,15 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=build(data+1,n-1); + if (!p->next) {return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/construction/manifest.json b/test/evaluation/rfc0029/construction/manifest.json new file mode 100644 index 00000000..77380771 --- /dev/null +++ b/test/evaluation/rfc0029/construction/manifest.json @@ -0,0 +1,100 @@ +{ + "version": 1, + "cases": [ + { + "name": "construction-build", + "sources": [ + "build.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "construction-short", + "sources": [ + "short.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + }, + { + "name": "construction-uninitialized", + "sources": [ + "uninitialized.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + }, + { + "name": "construction-leak", + "sources": [ + "leak.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|leak|cleanup|contract" + }, + { + "name": "construction-double", + "sources": [ + "double.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "freed|release" + }, + { + "name": "construction-cycle", + "sources": [ + "cycle.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|progress" + } + ] +} diff --git a/test/evaluation/rfc0029/construction/provenance.md b/test/evaluation/rfc0029/construction/provenance.md new file mode 100644 index 00000000..cf0d249f --- /dev/null +++ b/test/evaluation/rfc0029/construction/provenance.md @@ -0,0 +1 @@ +Independent runtime-length recursive construction probes, frozen before construction hypothesis implementation. The constructor reads a strictly decreasing initialized input interval, returns a fresh initialized chain, and releases its partial head on allocation failure. Counterparts exercise short and uninitialized input, leaking or double-releasing failure paths, and a nondecreasing cycle. Baseline observation: build/rfc29-validation/construct-probe.*. diff --git a/test/evaluation/rfc0029/construction/short.c b/test/evaluation/rfc0029/construction/short.c new file mode 100644 index 00000000..181cda29 --- /dev/null +++ b/test/evaluation/rfc0029/construction/short.c @@ -0,0 +1,15 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=build(data+1,n-1); + if (!p->next) {free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,4);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/construction/uninitialized.c b/test/evaluation/rfc0029/construction/uninitialized.c new file mode 100644 index 00000000..3fadd4b2 --- /dev/null +++ b/test/evaluation/rfc0029/construction/uninitialized.c @@ -0,0 +1,15 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=build(data+1,n-1); + if (!p->next) {free(p);return 0;} + } + return p; +} +int main(void) {unsigned char data[3];data[0]=1;struct node *p=build(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/container-alias-outputs/PROVENANCE.md b/test/evaluation/rfc0029/container-alias-outputs/PROVENANCE.md new file mode 100644 index 00000000..c088562a --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/PROVENANCE.md @@ -0,0 +1,5 @@ +RFC 0029 container output alias regression, frozen against candidate90b. +A complete helper publishes a structural and conserved-footprint output for +an unchanged pointer. A definitely identical zero-offset caller alias must +observe that same proved object. Released pointers, interior pointers and +lost payloads cannot recover ownership from an alias name. diff --git a/test/evaluation/rfc0029/container-alias-outputs/api.h b/test/evaluation/rfc0029/container-alias-outputs/api.h new file mode 100644 index 00000000..501e3145 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/api.h @@ -0,0 +1,3 @@ +#include +struct node{struct node *next;char *payload;unsigned flags;}; +void update(struct node*);void forward(struct node*);void drop(struct node*); diff --git a/test/evaluation/rfc0029/container-alias-outputs/direct.c b/test/evaluation/rfc0029/container-alias-outputs/direct.c new file mode 100644 index 00000000..eed9f9f5 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/direct.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *p=calloc(1,sizeof *p);if(!p)return 0; struct node *a=p;update(a); drop(p);return 0;} diff --git a/test/evaluation/rfc0029/container-alias-outputs/disown.c b/test/evaluation/rfc0029/container-alias-outputs/disown.c new file mode 100644 index 00000000..f5a51052 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/disown.c @@ -0,0 +1,2 @@ +#include "api.h" +void update(struct node *p){p->flags=256;} diff --git a/test/evaluation/rfc0029/container-alias-outputs/disowned.c b/test/evaluation/rfc0029/container-alias-outputs/disowned.c new file mode 100644 index 00000000..8e7d0f2f --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/disowned.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *p=calloc(1,sizeof *p);if(!p)return 0;p->payload=malloc(1);if(!p->payload){drop(p);return 0;} struct node *a=p;update(a); drop(p);return 0;} diff --git a/test/evaluation/rfc0029/container-alias-outputs/drop.c b/test/evaluation/rfc0029/container-alias-outputs/drop.c new file mode 100644 index 00000000..251a8e23 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/drop.c @@ -0,0 +1,2 @@ +#include "api.h" +void drop(struct node *p){while(p){struct node *n=p->next;if(!(p->flags&256))free(p->payload);free(p);p=n;}} diff --git a/test/evaluation/rfc0029/container-alias-outputs/forward.c b/test/evaluation/rfc0029/container-alias-outputs/forward.c new file mode 100644 index 00000000..d175a777 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/forward.c @@ -0,0 +1,2 @@ +#include "api.h" +void forward(struct node *p){update(p);} diff --git a/test/evaluation/rfc0029/container-alias-outputs/forwarded.c b/test/evaluation/rfc0029/container-alias-outputs/forwarded.c new file mode 100644 index 00000000..fc7f0156 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/forwarded.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *p=calloc(1,sizeof *p);if(!p)return 0; struct node *a=p;forward(a); drop(p);return 0;} diff --git a/test/evaluation/rfc0029/container-alias-outputs/frozen-sha256.json b/test/evaluation/rfc0029/container-alias-outputs/frozen-sha256.json new file mode 100644 index 00000000..66386731 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/frozen-sha256.json @@ -0,0 +1,16 @@ +{ + "PROVENANCE.md": "14bb5e9b9f1acd44e2ad844e7f293d01de740c231504cadf69c25154a462ab94", + "api.h": "e5e01e07b0344379666b782e189f996f419dcdb1c2affa3ffebf32690ce9efd3", + "direct.c": "340a34037e47d6f740193f48ded48665348a41f7c936a25c585a583c57a077c0", + "disown.c": "c9dbaf64a92dd63ee8ff963a6162acf86c8b2cc3e10a4bb9ff273893d509404f", + "disowned.c": "7c10390399a36b55009759da01e945e55e8f4f59b615f1211f4adc15a2940752", + "drop.c": "8548d927c8d46808aedb3c1f69744421c6beda7a91a2a7e9e405a13175199b1f", + "forward.c": "4ff545de049916d37052efd3c75aa53d5269a70bdbd0caa8069554f7df032b8d", + "forwarded.c": "f818fb7bf8f6404662df7ef0ce69945843511eaf46e64e77bb1dc9aecfdd467c", + "interior.c": "489d6a50657662f5e8310bb8191713a7184b08810aa954ccbfa0402431ad80fc", + "lost-payload.c": "70b399efff5399634e8730b53439d6d189df607d2f2d50c2a8cbd5b9008265b5", + "manifest.json": "50bed2cbc6b4d7982572d6e8631cdefd468570c63e1a5b5590d66192a0baf0d5", + "payload.c": "7c10390399a36b55009759da01e945e55e8f4f59b615f1211f4adc15a2940752", + "released.c": "852ce59e2066c8cda427a41b464aa00f6bb2a6c32006d6fd85684e33c452d17d", + "update.c": "9f5ace22a2b8c384280a28f84041f11d85012ce2659072cf01d79dce5ed2fe39" +} diff --git a/test/evaluation/rfc0029/container-alias-outputs/interior.c b/test/evaluation/rfc0029/container-alias-outputs/interior.c new file mode 100644 index 00000000..580c412f --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/interior.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *p=calloc(1,sizeof *p);if(!p)return 0; struct node *a=p;a=(struct node*)((char*)a+1);update(a); drop(p);return 0;} diff --git a/test/evaluation/rfc0029/container-alias-outputs/lost-payload.c b/test/evaluation/rfc0029/container-alias-outputs/lost-payload.c new file mode 100644 index 00000000..84d993f4 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/lost-payload.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *p=calloc(1,sizeof *p);if(!p)return 0;p->payload=malloc(1);if(!p->payload){drop(p);return 0;} struct node *a=p;a->payload=0;update(a); drop(p);return 0;} diff --git a/test/evaluation/rfc0029/container-alias-outputs/manifest.json b/test/evaluation/rfc0029/container-alias-outputs/manifest.json new file mode 100644 index 00000000..838c4c46 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/manifest.json @@ -0,0 +1,131 @@ +{ + "version": 1, + "cases": [ + { + "name": "direct", + "sources": [ + "direct.c", + "update.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "forwarded", + "sources": [ + "forwarded.c", + "update.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "payload", + "sources": [ + "payload.c", + "update.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "released", + "sources": [ + "released.c", + "update.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "live|release|recursive|footprint|allocation|interval|leak" + }, + { + "name": "interior", + "sources": [ + "interior.c", + "update.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "live|release|recursive|footprint|allocation|interval|leak" + }, + { + "name": "disowned", + "sources": [ + "disowned.c", + "disown.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "live|release|recursive|footprint|allocation|interval|leak" + }, + { + "name": "lost-payload", + "sources": [ + "lost-payload.c", + "update.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "live|release|recursive|footprint|allocation|interval|leak" + } + ] +} diff --git a/test/evaluation/rfc0029/container-alias-outputs/payload.c b/test/evaluation/rfc0029/container-alias-outputs/payload.c new file mode 100644 index 00000000..8e7d0f2f --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/payload.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *p=calloc(1,sizeof *p);if(!p)return 0;p->payload=malloc(1);if(!p->payload){drop(p);return 0;} struct node *a=p;update(a); drop(p);return 0;} diff --git a/test/evaluation/rfc0029/container-alias-outputs/released.c b/test/evaluation/rfc0029/container-alias-outputs/released.c new file mode 100644 index 00000000..c83a4269 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/released.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *p=calloc(1,sizeof *p);if(!p)return 0; struct node *a=p;drop(a);update(a); drop(p);return 0;} diff --git a/test/evaluation/rfc0029/container-alias-outputs/update.c b/test/evaluation/rfc0029/container-alias-outputs/update.c new file mode 100644 index 00000000..84b6ce98 --- /dev/null +++ b/test/evaluation/rfc0029/container-alias-outputs/update.c @@ -0,0 +1,2 @@ +#include "api.h" +void update(struct node *p){p->flags=2;} diff --git a/test/evaluation/rfc0029/container-call-frames/child.c b/test/evaluation/rfc0029/container-call-frames/child.c new file mode 100644 index 00000000..65a24185 --- /dev/null +++ b/test/evaluation/rfc0029/container-call-frames/child.c @@ -0,0 +1,24 @@ +#include +#include +struct node { unsigned value; struct node *left, *right; }; +struct state { unsigned depth, mode; unsigned char *data; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +void set(struct state *s) { s->depth = 1; s->data[0] = 7; } +void corrupt(struct node *p) { memset(p, 0xa5, sizeof(*p)); } +unsigned inspect(struct node *p) { + struct state state[1]; + memset(state, 0, sizeof(state)); + state->data = malloc(1); + if (!state->data) return 0; + set(state); corrupt(p->left); + unsigned value = walk(p) + state->depth; + free(state->data); + return value; +} +int main(void) { + struct node child = {2, 0, 0}, root = {1, &child, 0}; + (void)inspect(&root); return 0; +} diff --git a/test/evaluation/rfc0029/container-call-frames/frozen-sha256.json b/test/evaluation/rfc0029/container-call-frames/frozen-sha256.json new file mode 100644 index 00000000..05ac9431 --- /dev/null +++ b/test/evaluation/rfc0029/container-call-frames/frozen-sha256.json @@ -0,0 +1,6 @@ +{ + "child.c": "5328a77a5354016e794832ad877dafdedea41245d357c398abe8d41601d6d25b", + "local.c": "107299542ed708b85ba4f39a25a12b0076937481f7afbaf05ac12a9b0bbb676d", + "manifest.json": "f54d7411a934d742749c8b2077d9e7dcb831ce5eea91c4486b6b9f9d21d260ec", + "root.c": "9a804b96be4942baa539efbaa43e733c1634c4b40e82d9764dafaf2cb512e41f" +} diff --git a/test/evaluation/rfc0029/container-call-frames/local.c b/test/evaluation/rfc0029/container-call-frames/local.c new file mode 100644 index 00000000..4c9ef6a7 --- /dev/null +++ b/test/evaluation/rfc0029/container-call-frames/local.c @@ -0,0 +1,24 @@ +#include +#include +struct node { unsigned value; struct node *left, *right; }; +struct state { unsigned depth, mode; unsigned char *data; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +void set(struct state *s) { s->depth = 1; s->data[0] = 7; } +void corrupt(struct node *p) { memset(p, 0xa5, sizeof(*p)); } +unsigned inspect(struct node *p) { + struct state state[1]; + memset(state, 0, sizeof(state)); + state->data = malloc(1); + if (!state->data) return 0; + set(state); + unsigned value = walk(p) + state->depth; + free(state->data); + return value; +} +int main(void) { + struct node child = {2, 0, 0}, root = {1, &child, 0}; + (void)inspect(&root); return 0; +} diff --git a/test/evaluation/rfc0029/container-call-frames/manifest.json b/test/evaluation/rfc0029/container-call-frames/manifest.json new file mode 100644 index 00000000..62ab7591 --- /dev/null +++ b/test/evaluation/rfc0029/container-call-frames/manifest.json @@ -0,0 +1,52 @@ +{ + "version": 1, + "cases": [ + { + "name": "local", + "sources": [ + "local.c" + ], + "functions": [ + "inspect", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "root", + "sources": [ + "root.c" + ], + "functions": [ + "inspect", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "container|recursive|extent|live|initialized" + }, + { + "name": "child", + "sources": [ + "child.c" + ], + "functions": [ + "inspect", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "container|recursive|extent|live|initialized" + } + ] +} diff --git a/test/evaluation/rfc0029/container-call-frames/root.c b/test/evaluation/rfc0029/container-call-frames/root.c new file mode 100644 index 00000000..7ee5b7a3 --- /dev/null +++ b/test/evaluation/rfc0029/container-call-frames/root.c @@ -0,0 +1,24 @@ +#include +#include +struct node { unsigned value; struct node *left, *right; }; +struct state { unsigned depth, mode; unsigned char *data; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +void set(struct state *s) { s->depth = 1; s->data[0] = 7; } +void corrupt(struct node *p) { memset(p, 0xa5, sizeof(*p)); } +unsigned inspect(struct node *p) { + struct state state[1]; + memset(state, 0, sizeof(state)); + state->data = malloc(1); + if (!state->data) return 0; + set(state); corrupt(p); + unsigned value = walk(p) + state->depth; + free(state->data); + return value; +} +int main(void) { + struct node child = {2, 0, 0}, root = {1, &child, 0}; + (void)inspect(&root); return 0; +} diff --git a/test/evaluation/rfc0029/container-local-frames/child.c b/test/evaluation/rfc0029/container-local-frames/child.c new file mode 100644 index 00000000..4f3a1919 --- /dev/null +++ b/test/evaluation/rfc0029/container-local-frames/child.c @@ -0,0 +1,17 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +struct state { unsigned depth, mode; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned inspect(struct node *p) { + struct state state[1]; + memset(p->left, 0xa5, sizeof(*p)); + state->depth = 1; + return walk(p) + state->depth; +} +int main(void) { + struct node child = {2, 0, 0}, root = {1, &child, 0}; + return inspect(&root) != 4; +} diff --git a/test/evaluation/rfc0029/container-local-frames/frozen-sha256.json b/test/evaluation/rfc0029/container-local-frames/frozen-sha256.json new file mode 100644 index 00000000..0a9fa168 --- /dev/null +++ b/test/evaluation/rfc0029/container-local-frames/frozen-sha256.json @@ -0,0 +1,6 @@ +{ + "child.c": "23319ac315e2ae2fdb71e583aa876dc257d29d9e652b99e6019498387d52644a", + "local.c": "60367faca2b33c9d17b6a696b1d1ace26668949ea069e34365d314198f1d2329", + "manifest.json": "f54d7411a934d742749c8b2077d9e7dcb831ce5eea91c4486b6b9f9d21d260ec", + "root.c": "8b8c528a15d11cc32168d2e155fe744c0e719d39dde49ee86aa1d2273aebe03a" +} diff --git a/test/evaluation/rfc0029/container-local-frames/local.c b/test/evaluation/rfc0029/container-local-frames/local.c new file mode 100644 index 00000000..7f96b8bb --- /dev/null +++ b/test/evaluation/rfc0029/container-local-frames/local.c @@ -0,0 +1,17 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +struct state { unsigned depth, mode; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned inspect(struct node *p) { + struct state state[1]; + memset(state, 0, sizeof(state)); + state->depth = 1; + return walk(p) + state->depth; +} +int main(void) { + struct node child = {2, 0, 0}, root = {1, &child, 0}; + return inspect(&root) != 4; +} diff --git a/test/evaluation/rfc0029/container-local-frames/manifest.json b/test/evaluation/rfc0029/container-local-frames/manifest.json new file mode 100644 index 00000000..62ab7591 --- /dev/null +++ b/test/evaluation/rfc0029/container-local-frames/manifest.json @@ -0,0 +1,52 @@ +{ + "version": 1, + "cases": [ + { + "name": "local", + "sources": [ + "local.c" + ], + "functions": [ + "inspect", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "root", + "sources": [ + "root.c" + ], + "functions": [ + "inspect", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "container|recursive|extent|live|initialized" + }, + { + "name": "child", + "sources": [ + "child.c" + ], + "functions": [ + "inspect", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "container|recursive|extent|live|initialized" + } + ] +} diff --git a/test/evaluation/rfc0029/container-local-frames/root.c b/test/evaluation/rfc0029/container-local-frames/root.c new file mode 100644 index 00000000..e58ef9dc --- /dev/null +++ b/test/evaluation/rfc0029/container-local-frames/root.c @@ -0,0 +1,17 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +struct state { unsigned depth, mode; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned inspect(struct node *p) { + struct state state[1]; + memset(p, 0xa5, sizeof(*p)); + state->depth = 1; + return walk(p) + state->depth; +} +int main(void) { + struct node child = {2, 0, 0}, root = {1, &child, 0}; + return inspect(&root) != 4; +} diff --git a/test/evaluation/rfc0029/container-outcome-frames/PROVENANCE.md b/test/evaluation/rfc0029/container-outcome-frames/PROVENANCE.md new file mode 100644 index 00000000..751ec4ad --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate87a before structural outputs retain their common must-descriptor across distinct returning outcomes. A helper may update non-ownership selector bits while preserving an existing owned payload; callers can ignore the return or forward it. Negative variants release the head, abandon the payload or change its ownership bit without settling the entry footprint. diff --git a/test/evaluation/rfc0029/container-outcome-frames/api.h b/test/evaluation/rfc0029/container-outcome-frames/api.h new file mode 100644 index 00000000..7347233d --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/api.h @@ -0,0 +1,5 @@ +#include +struct node {struct node *next,*child;char *text;unsigned flags;}; +int inspect(struct node *); +int forward(struct node *); +void drop(struct node *); diff --git a/test/evaluation/rfc0029/container-outcome-frames/branch-inspect.c b/test/evaluation/rfc0029/container-outcome-frames/branch-inspect.c new file mode 100644 index 00000000..6fbb95ba --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/branch-inspect.c @@ -0,0 +1,2 @@ +#include "api.h" +int inspect(struct node *n){void *p=malloc(4);if(!p)return 0;free(p);n->flags=2;return 1;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/branch.c b/test/evaluation/rfc0029/container-outcome-frames/branch.c new file mode 100644 index 00000000..52b0eb10 --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/branch.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->text=malloc(4);if(!n->text){drop(n);return 0;}if(inspect(n))drop(n);else drop(n);return 0;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/direct-inspect.c b/test/evaluation/rfc0029/container-outcome-frames/direct-inspect.c new file mode 100644 index 00000000..6fbb95ba --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/direct-inspect.c @@ -0,0 +1,2 @@ +#include "api.h" +int inspect(struct node *n){void *p=malloc(4);if(!p)return 0;free(p);n->flags=2;return 1;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/direct.c b/test/evaluation/rfc0029/container-outcome-frames/direct.c new file mode 100644 index 00000000..31c5cd07 --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/direct.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->text=malloc(4);if(!n->text){drop(n);return 0;}(void)inspect(n);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/disowned-inspect.c b/test/evaluation/rfc0029/container-outcome-frames/disowned-inspect.c new file mode 100644 index 00000000..f2f7fb60 --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/disowned-inspect.c @@ -0,0 +1,2 @@ +#include "api.h" +int inspect(struct node *n){void *p=malloc(4);if(!p)return 0;free(p);n->flags=1;return 1;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/disowned.c b/test/evaluation/rfc0029/container-outcome-frames/disowned.c new file mode 100644 index 00000000..31c5cd07 --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/disowned.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->text=malloc(4);if(!n->text){drop(n);return 0;}(void)inspect(n);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/drop.c b/test/evaluation/rfc0029/container-outcome-frames/drop.c new file mode 100644 index 00000000..eeee956c --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/drop.c @@ -0,0 +1,2 @@ +#include "api.h" +void drop(struct node *n){while(n){struct node *next=n->next;drop(n->child);if(!(n->flags&1))free(n->text);free(n);n=next;}} diff --git a/test/evaluation/rfc0029/container-outcome-frames/forward.c b/test/evaluation/rfc0029/container-outcome-frames/forward.c new file mode 100644 index 00000000..b3961eda --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/forward.c @@ -0,0 +1,2 @@ +#include "api.h" +int forward(struct node *n){return inspect(n);} diff --git a/test/evaluation/rfc0029/container-outcome-frames/frozen-sha256.json b/test/evaluation/rfc0029/container-outcome-frames/frozen-sha256.json new file mode 100644 index 00000000..f543aaff --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/frozen-sha256.json @@ -0,0 +1,19 @@ +{ + "PROVENANCE.md": "f75b2b619885da75dea70e2c06901e7a59e0e276d4de12c4ee845f382f29c9ef", + "api.h": "6f0eb61e5d790339c00effc3c97997f7d84dfb68b6dad7e548694ecf325f1224", + "branch-inspect.c": "29aa8208bf1c6bbbc04f01936424a728ad82a1aa669bc3f3d33814ab3ee83dc2", + "branch.c": "eaf4726144c680f45b0b6fe168310de20696f27405d76841b5bf7954681506eb", + "direct-inspect.c": "29aa8208bf1c6bbbc04f01936424a728ad82a1aa669bc3f3d33814ab3ee83dc2", + "direct.c": "647f0dadc23a42d5425d6ee68e864dfc43633f8dc4143f240287b78b954b73c5", + "disowned-inspect.c": "889f7c3877f8a5ef6965c6cf00d74c8a6f700d4308df2c0beaeeddee665775af", + "disowned.c": "647f0dadc23a42d5425d6ee68e864dfc43633f8dc4143f240287b78b954b73c5", + "drop.c": "7438fb7735555abfdaf6a3e43ca37483cf14b30ff3e0e3bc1beb132ef2b2d020", + "forward.c": "eb1a1d0543607717fc236f0b62043cc5167f8dbf14bacaf1455eaf5f37c1cee8", + "helper-inspect.c": "29aa8208bf1c6bbbc04f01936424a728ad82a1aa669bc3f3d33814ab3ee83dc2", + "helper.c": "122ef3a6f04eb4a60694144bbc91ed5b712d8efb03eef529244bae836c23432d", + "lost-inspect.c": "745c817e0e863ca68f6dca1caf7998ab33fae1391bed8bfea1a40e9e15f5791a", + "lost.c": "647f0dadc23a42d5425d6ee68e864dfc43633f8dc4143f240287b78b954b73c5", + "manifest.json": "d7fe41fd032e3a8ce35e3dfe3691c0df1bba6b9820bb48caa0656ad2a4459bd8", + "released-inspect.c": "582752c051d4a9a3807203b6d4aaeecb6bb36feaed42abeabf4ca421cdd8af91", + "released.c": "647f0dadc23a42d5425d6ee68e864dfc43633f8dc4143f240287b78b954b73c5" +} diff --git a/test/evaluation/rfc0029/container-outcome-frames/helper-inspect.c b/test/evaluation/rfc0029/container-outcome-frames/helper-inspect.c new file mode 100644 index 00000000..6fbb95ba --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/helper-inspect.c @@ -0,0 +1,2 @@ +#include "api.h" +int inspect(struct node *n){void *p=malloc(4);if(!p)return 0;free(p);n->flags=2;return 1;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/helper.c b/test/evaluation/rfc0029/container-outcome-frames/helper.c new file mode 100644 index 00000000..15981695 --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/helper.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->text=malloc(4);if(!n->text){drop(n);return 0;}(void)forward(n);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/lost-inspect.c b/test/evaluation/rfc0029/container-outcome-frames/lost-inspect.c new file mode 100644 index 00000000..55e9af3c --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/lost-inspect.c @@ -0,0 +1,2 @@ +#include "api.h" +int inspect(struct node *n){void *p=malloc(4);if(!p)return 0;free(p);n->text=0;return 1;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/lost.c b/test/evaluation/rfc0029/container-outcome-frames/lost.c new file mode 100644 index 00000000..31c5cd07 --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/lost.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->text=malloc(4);if(!n->text){drop(n);return 0;}(void)inspect(n);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/manifest.json b/test/evaluation/rfc0029/container-outcome-frames/manifest.json new file mode 100644 index 00000000..fd8e989a --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/manifest.json @@ -0,0 +1,113 @@ +{ + "version": 1, + "cases": [ + { + "name": "direct", + "sources": [ + "direct.c", + "direct-inspect.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "helper", + "sources": [ + "helper.c", + "helper-inspect.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "branch", + "sources": [ + "branch.c", + "branch-inspect.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "released", + "sources": [ + "released.c", + "released-inspect.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "release|live|allocation|contract|container|footprint|leak" + }, + { + "name": "lost", + "sources": [ + "lost.c", + "lost-inspect.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "release|live|allocation|contract|container|footprint|leak" + }, + { + "name": "disowned", + "sources": [ + "disowned.c", + "disowned-inspect.c", + "forward.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "release|live|allocation|contract|container|footprint|leak" + } + ] +} diff --git a/test/evaluation/rfc0029/container-outcome-frames/released-inspect.c b/test/evaluation/rfc0029/container-outcome-frames/released-inspect.c new file mode 100644 index 00000000..f824af63 --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/released-inspect.c @@ -0,0 +1,2 @@ +#include "api.h" +int inspect(struct node *n){void *p=malloc(4);if(!p)return 0;free(p);free(n);return 1;} diff --git a/test/evaluation/rfc0029/container-outcome-frames/released.c b/test/evaluation/rfc0029/container-outcome-frames/released.c new file mode 100644 index 00000000..31c5cd07 --- /dev/null +++ b/test/evaluation/rfc0029/container-outcome-frames/released.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->text=malloc(4);if(!n->text){drop(n);return 0;}(void)inspect(n);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/container-value-guards/README.md b/test/evaluation/rfc0029/container-value-guards/README.md new file mode 100644 index 00000000..38532a4d --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/README.md @@ -0,0 +1,15 @@ +# Container value guards (RFC 0029) + +Reduced from the unchanged forced-print cJSON client after candidate70a. +Its live forest retains flags=1 in the head predicate after configuring a +failing allocation callback. The helper returns null, but its value guard +cannot be discharged through the ordinary scalar map alone. The baseline +probe in build/rfc29-validation/container-guard-probes/forest.c rejects the +release of that impossible result and loses the unrelated forest footprint. + +The shared-query implementation was drafted before this reduced population +was frozen and has not yet been built or validated. Expectations here follow +the actual C paths: the null result must exclude the out-of-bounds branch; +mutating/replacing the head tag or selecting an actual allocator reaches it. +Releasing the head before the call invalidates its storage and its predicate. +No source name, layout candidate or callback prototype supplies a value fact. diff --git a/test/evaluation/rfc0029/container-value-guards/actual-allocation.c b/test/evaluation/rfc0029/container-value-guards/actual-allocation.c new file mode 100644 index 00000000..effae17a --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/actual-allocation.c @@ -0,0 +1,13 @@ +#include "forest.h" +static void *fail(size_t n) { (void)n;return 0; } +static void *render(const struct node *p) { + if(p->flags!=1)return malloc(1); + void *out=global_hooks.allocate(1);if(!out)return 0; + *(char *)out='a';return out; +} +int main(void) { + reset(0);struct node *p=create();if(!p)return 0; + struct hooks h={malloc,free};reset(&h); + + char *out=render(p);if(out){out[2]=1;free(out);}destroy(p); return 0; +} diff --git a/test/evaluation/rfc0029/container-value-guards/changed-alias.c b/test/evaluation/rfc0029/container-value-guards/changed-alias.c new file mode 100644 index 00000000..87c1700a --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/changed-alias.c @@ -0,0 +1,13 @@ +#include "forest.h" +static void *fail(size_t n) { (void)n;return 0; } +static void *render(const struct node *p) { + if(p->flags!=1)return malloc(1); + void *out=global_hooks.allocate(1);if(!out)return 0; + *(char *)out='a';return out; +} +int main(void) { + reset(0);struct node *p=create();if(!p)return 0; + struct hooks h={fail,free};reset(&h); + struct node *q=p;q->flags=2; + char *out=render(p);if(out){out[2]=1;free(out);}destroy(p); return 0; +} diff --git a/test/evaluation/rfc0029/container-value-guards/changed-selector.c b/test/evaluation/rfc0029/container-value-guards/changed-selector.c new file mode 100644 index 00000000..639e05d8 --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/changed-selector.c @@ -0,0 +1,13 @@ +#include "forest.h" +static void *fail(size_t n) { (void)n;return 0; } +static void *render(const struct node *p) { + if(p->flags!=1)return malloc(1); + void *out=global_hooks.allocate(1);if(!out)return 0; + *(char *)out='a';return out; +} +int main(void) { + reset(0);struct node *p=create();if(!p)return 0; + struct hooks h={fail,free};reset(&h); + p->flags=2; + char *out=render(p);if(out){out[2]=1;free(out);}destroy(p); return 0; +} diff --git a/test/evaluation/rfc0029/container-value-guards/forest.h b/test/evaluation/rfc0029/container-value-guards/forest.h new file mode 100644 index 00000000..fa926515 --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/forest.h @@ -0,0 +1,15 @@ + +#define NULL ((void *)0) +typedef __SIZE_TYPE__ size_t; +void *malloc(size_t); +void free(void *); +void *memset(void *, int, size_t); +struct hooks { void *(*allocate)(size_t); void (*deallocate)(void*); }; +static struct hooks global_hooks = {malloc,free}; +struct node { struct node *next,*prev,*child; int flags; char *text,*key; }; +static void reset(struct hooks *h){if(!h){global_hooks.allocate=malloc;global_hooks.deallocate=free;return;}global_hooks=*h;} +static struct node *new_item(const struct hooks *h){struct node *p=(struct node*)h->allocate(sizeof(struct node));if(p)memset(p,0,sizeof *p);return p;} +static struct node *create(void){struct node *p=new_item(&global_hooks);if(p)p->flags=1;return p;} +static void destroy(struct node *p){struct node *next=0;while(p){next=p->next;if(!(p->flags&256)&&p->child)destroy(p->child);if(!(p->flags&256)&&p->text){global_hooks.deallocate(p->text);p->text=0;}if(!(p->flags&512)&&p->key){global_hooks.deallocate(p->key);p->key=0;}global_hooks.deallocate(p);p=next;}} +static void suffix(struct node *last,struct node *item){last->next=item;item->prev=last;} +static int add(struct node *array,struct node *item){struct node *child=0;if(!item||!array||array==item)return 0;child=array->child;if(!child){array->child=item;item->prev=item;item->next=0;}else{if(child->prev){suffix(child->prev,item);array->child->prev=item;}}return 1;} diff --git a/test/evaluation/rfc0029/container-value-guards/frozen-sha256.json b/test/evaluation/rfc0029/container-value-guards/frozen-sha256.json new file mode 100644 index 00000000..972ad27f --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/frozen-sha256.json @@ -0,0 +1,11 @@ +{ + "README.md": "c0a3b4dd1bd8cb361b0641daf6bab6e4a091a6990cdf8f2be9934d87c6c645cb", + "actual-allocation.c": "97c052ef18ff178b0e4e2edcfc98105e347722e9687ec1cc2485b34c291fc15f", + "changed-alias.c": "76be84b92d6139d7db6a0ae8843b9678b0026fdb958b65a0cf6251e134d00b6a", + "changed-selector.c": "0318ecef72e5cd76459c15aae39a18aedee488dcf9c7c89845c06de4042b8511", + "forest.h": "8fbdfe0bb63f5b3bc9fe25bc58eddeaecfe4ee7661a96e5e0d0c6e21c792e2ec", + "manifest.json": "1d6d439b0b583e83f82185f0b9e297e62223ec438bd176753c15468cd0574d9d", + "null-result.c": "0c3ab17ad5e9009b3c598a1e2b88c7c056848eb1232d3d2fea10fa32764d482d", + "released-head.c": "a9e6de35e0719d6ac5fef65a619316e258047caecbb16c14a5ab556c0c971e90", + "replaced-head.c": "1f48af846c84a17f93ed00a349e3b99cae56d0eea105afb4c1d7e59a7b892d10" +} diff --git a/test/evaluation/rfc0029/container-value-guards/manifest.json b/test/evaluation/rfc0029/container-value-guards/manifest.json new file mode 100644 index 00000000..7e4ab570 --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/manifest.json @@ -0,0 +1,95 @@ +{ + "version": 1, + "cases": [ + { + "name": "null-result", + "sources": [ + "null-result.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "changed-selector", + "sources": [ + "changed-selector.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|live|freed|precondition" + }, + { + "name": "changed-alias", + "sources": [ + "changed-alias.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|live|freed|precondition" + }, + { + "name": "replaced-head", + "sources": [ + "replaced-head.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|live|freed|precondition" + }, + { + "name": "released-head", + "sources": [ + "released-head.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|live|freed|precondition" + }, + { + "name": "actual-allocation", + "sources": [ + "actual-allocation.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|live|freed|precondition" + } + ] +} diff --git a/test/evaluation/rfc0029/container-value-guards/null-result.c b/test/evaluation/rfc0029/container-value-guards/null-result.c new file mode 100644 index 00000000..09b35f31 --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/null-result.c @@ -0,0 +1,13 @@ +#include "forest.h" +static void *fail(size_t n) { (void)n;return 0; } +static void *render(const struct node *p) { + if(p->flags!=1)return malloc(1); + void *out=global_hooks.allocate(1);if(!out)return 0; + *(char *)out='a';return out; +} +int main(void) { + reset(0);struct node *p=create();if(!p)return 0; + struct hooks h={fail,free};reset(&h); + + char *out=render(p);if(out){out[2]=1;free(out);}destroy(p); return 0; +} diff --git a/test/evaluation/rfc0029/container-value-guards/released-head.c b/test/evaluation/rfc0029/container-value-guards/released-head.c new file mode 100644 index 00000000..a8b15392 --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/released-head.c @@ -0,0 +1,13 @@ +#include "forest.h" +static void *fail(size_t n) { (void)n;return 0; } +static void *render(const struct node *p) { + if(p->flags!=1)return malloc(1); + void *out=global_hooks.allocate(1);if(!out)return 0; + *(char *)out='a';return out; +} +int main(void) { + reset(0);struct node *p=create();if(!p)return 0; + struct hooks h={fail,free};reset(&h); + destroy(p); + char *out=render(p);if(out){out[2]=1;free(out);} return 0; +} diff --git a/test/evaluation/rfc0029/container-value-guards/replaced-head.c b/test/evaluation/rfc0029/container-value-guards/replaced-head.c new file mode 100644 index 00000000..6e3bd781 --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/replaced-head.c @@ -0,0 +1,13 @@ +#include "forest.h" +static void *fail(size_t n) { (void)n;return 0; } +static void *render(const struct node *p) { + if(p->flags!=1)return malloc(1); + void *out=global_hooks.allocate(1);if(!out)return 0; + *(char *)out='a';return out; +} +int main(void) { + reset(0);struct node *p=create();if(!p)return 0; + struct hooks h={fail,free};reset(&h); + destroy(p);p=create();if(!p)return 0;p->flags=2; + char *out=render(p);if(out){out[2]=1;free(out);}destroy(p); return 0; +} diff --git a/test/evaluation/rfc0029/container-value-guards/replaced-live-head.c b/test/evaluation/rfc0029/container-value-guards/replaced-live-head.c new file mode 100644 index 00000000..016d5671 --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/replaced-live-head.c @@ -0,0 +1,13 @@ +#include "forest.h" +static void *fail(size_t n) { (void)n;return 0; } +static void *render(const struct node *p) { + if(p->flags!=1)return malloc(1); + void *out=global_hooks.allocate(1);if(!out)return 0; + *(char *)out='a';return out; +} +int main(void) { + reset(0);struct node *p=create();if(!p)return 0; + struct hooks h={fail,free};reset(&h); + destroy(p);reset(0);p=create();if(!p)return 0;p->flags=2;reset(&h); + char *out=render(p);if(out){out[2]=1;free(out);}destroy(p); return 0; +} diff --git a/test/evaluation/rfc0029/container-value-guards/review.md b/test/evaluation/rfc0029/container-value-guards/review.md new file mode 100644 index 00000000..63df5585 --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/review.md @@ -0,0 +1,8 @@ +The original replaced-head case is safe: the newly configured allocator +always fails, so replacement returns null and the client exits before the tag +write. Candidate70a correctly accepts it. Preserve that original inventory +and failed baseline observation. The reviewed inventory expects this safe +case to pass and adds replaced-live-head, which resets malloc for construction +and reinstalls the failing callback before the actual out-of-bounds branch. +No original source or manifest was modified; the reviewed population has seven +cases instead of six. diff --git a/test/evaluation/rfc0029/container-value-guards/reviewed-manifest.json b/test/evaluation/rfc0029/container-value-guards/reviewed-manifest.json new file mode 100644 index 00000000..e10e6070 --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/reviewed-manifest.json @@ -0,0 +1,110 @@ +{ + "version": 1, + "cases": [ + { + "name": "null-result", + "sources": [ + "null-result.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "changed-selector", + "sources": [ + "changed-selector.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|live|freed|precondition" + }, + { + "name": "changed-alias", + "sources": [ + "changed-alias.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|live|freed|precondition" + }, + { + "name": "replaced-head", + "sources": [ + "replaced-head.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "released-head", + "sources": [ + "released-head.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|live|freed|precondition" + }, + { + "name": "actual-allocation", + "sources": [ + "actual-allocation.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|live|freed|precondition" + }, + { + "name": "replaced-live-head", + "sources": [ + "replaced-live-head.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "bounds|extent|live|freed|precondition", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/container-value-guards/reviewed-sha256.json b/test/evaluation/rfc0029/container-value-guards/reviewed-sha256.json new file mode 100644 index 00000000..4805d075 --- /dev/null +++ b/test/evaluation/rfc0029/container-value-guards/reviewed-sha256.json @@ -0,0 +1,5 @@ +{ + "review.md": "2c1910437290efb7b042004a44fe049c098b818ace3c2d3aedbabcdd00dd705c", + "reviewed-manifest.json": "8d3798f9a9d2cb2c1b53a4ef86f9ebe9e3804cede8c96f1a49908064546d4e06", + "replaced-live-head.c": "7f39c5241506f14dc749ed7296bf9541a02d80ca4548c0ca51c76f506aab3d91" +} diff --git a/test/evaluation/rfc0029/corpus-regressions/api.h b/test/evaluation/rfc0029/corpus-regressions/api.h new file mode 100644 index 00000000..75c32e89 --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/api.h @@ -0,0 +1,4 @@ +#include +struct node { struct node *next, *child; unsigned value; }; +void destroy(struct node *p); +struct node *at(const struct node *array, size_t item); diff --git a/test/evaluation/rfc0029/corpus-regressions/discovered-manifest.json b/test/evaluation/rfc0029/corpus-regressions/discovered-manifest.json new file mode 100644 index 00000000..2a65d27c --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/discovered-manifest.json @@ -0,0 +1,66 @@ +{ + "version": 1, + "cases": [ + { + "name": "imported-links", + "sources": [ + "lookup.c", + "library.c" + ], + "functions": [ + "at", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "entry-cursor", + "sources": [ + "update.c" + ], + "functions": [ + "update", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "discovered-entry-cursor", + "sources": [ + "update-discovered.c" + ], + "functions": [ + "update", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "unterminated", + "sources": [ + "unterminated.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "terminated|terminator|initializ", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/corpus-regressions/discovered-sha256.json b/test/evaluation/rfc0029/corpus-regressions/discovered-sha256.json new file mode 100644 index 00000000..1f08bb13 --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/discovered-sha256.json @@ -0,0 +1,5 @@ +{ + "update-discovered.c": "9615847eef2dd3a46bb1c9250b98e7ba377062cc31830f24eb155200690591f5", + "discovered-manifest.json": "b8b2835b57c6cbd0c8de9d3c40973f8f4bd80b14488445645189fb9700570008", + "discovery.md": "d8e62607062981ea2cb73e4cc7663deb4c3e9d02179f49130dae1e6f14f27090" +} diff --git a/test/evaluation/rfc0029/corpus-regressions/discovery.md b/test/evaluation/rfc0029/corpus-regressions/discovery.md new file mode 100644 index 00000000..de244862 --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/discovery.md @@ -0,0 +1 @@ +The original cursor reduction is a preservation case: with no capacity use anywhere in its TU, the three counters do not nominate a buffer. The additional update-discovered.c supplies a separate append helper so semantic discovery can identify cursor and capacity, reproducing the actual upstream interaction. Both original inputs and expectations remain unchanged. These new files were frozen before their first analysis against immutable candidate 19. diff --git a/test/evaluation/rfc0029/corpus-regressions/frozen-sha256.json b/test/evaluation/rfc0029/corpus-regressions/frozen-sha256.json new file mode 100644 index 00000000..e0255a30 --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "api.h": "8660f0bfdc197eee56028825f5d70fa77e88ec3a41520314794f9390159dd4b8", + "library.c": "e4c7dc1b04b0aaf77fc20b546ca410a12f1df27fcd9be56305c686ea96facbdc", + "lookup.c": "b1ebc68290aca0e8671421b4b1b5e2f203fe7e3a0da1a87bfcb422f56a99eb71", + "manifest.json": "6abf4ffb5254ca9c320abf242a23c9bd41891daaa13b6df30650d8ea0e479bc1", + "provenance.md": "626ca5788866a4f73a7c336a881ae259e693349edfc071678b192905bc1aae71", + "unterminated.c": "45820d92b3f5a956408b11628782b3c105bc08ce6ead0bc6145a16f3a3ebd0a2", + "update.c": "8a388c9dc5ed98d213dc1bd1209d5cc66de68735e9104c95ce79356500c3d9b3" +} diff --git a/test/evaluation/rfc0029/corpus-regressions/library.c b/test/evaluation/rfc0029/corpus-regressions/library.c new file mode 100644 index 00000000..726f6122 --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/library.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +void destroy(struct node *p) {while(p){struct node *next=p->next;destroy(p->child);free(p);p=next;}} diff --git a/test/evaluation/rfc0029/corpus-regressions/lookup.c b/test/evaluation/rfc0029/corpus-regressions/lookup.c new file mode 100644 index 00000000..1b76998c --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/lookup.c @@ -0,0 +1,9 @@ +#include "api.h" +void clear(struct node *p) { if(p)destroy(p->child); } +struct node *at(const struct node *array, size_t item) { + struct node *child=array?array->child:0; + while(child && item>0){--item;child=child->next;} + return child; +} +int main(void) { struct node child={0,0,7}, root={0,&child,0}; + struct node *p=at(&root,0);return p?(int)p->value:0;} diff --git a/test/evaluation/rfc0029/corpus-regressions/manifest.json b/test/evaluation/rfc0029/corpus-regressions/manifest.json new file mode 100644 index 00000000..de5adbda --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/manifest.json @@ -0,0 +1,51 @@ +{ + "version": 1, + "cases": [ + { + "name": "imported-links", + "sources": [ + "lookup.c", + "library.c" + ], + "functions": [ + "at", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "entry-cursor", + "sources": [ + "update.c" + ], + "functions": [ + "update", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "unterminated", + "sources": [ + "unterminated.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "terminated|terminator|initializ", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/corpus-regressions/provenance.md b/test/evaluation/rfc0029/corpus-regressions/provenance.md new file mode 100644 index 00000000..db2dc0b1 --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/provenance.md @@ -0,0 +1 @@ +Frozen before fixes to candidate 19 corpus regressions. The retained RFC 0028 cJSON corpus had complete update_offset and cJSON_Utils get_array_item contracts; candidate 19 lost both. These reduced probes cover an unchanged entry backing pointer with a mutable cursor and imported recursive topology in a unit that calls an external child destructor. The unterminated counterpart must still reject. Original corpus source and prior populations remain unchanged. diff --git a/test/evaluation/rfc0029/corpus-regressions/unterminated.c b/test/evaluation/rfc0029/corpus-regressions/unterminated.c new file mode 100644 index 00000000..3d4de5f4 --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/unterminated.c @@ -0,0 +1,9 @@ +#include +#include +struct output { char *data; size_t capacity, cursor, depth; }; +void update(struct output *p) { + if(!p || !p->data)return; + const char *old=p->data+p->cursor; + p->cursor+=strlen(old); +} +int main(void) {char data[]={1,2,3,4};struct output p={data,sizeof data,0,0};update(&p);return 0;} diff --git a/test/evaluation/rfc0029/corpus-regressions/update-discovered.c b/test/evaluation/rfc0029/corpus-regressions/update-discovered.c new file mode 100644 index 00000000..c8cd0836 --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/update-discovered.c @@ -0,0 +1,13 @@ +#include +#include +struct output { char *data; size_t capacity, cursor, depth; }; +int append(struct output *p, char c) { + if(p->cursor>=p->capacity)return 0; + p->data[p->cursor++]=c;return 1; +} +void update(struct output *p) { + if(!p || !p->data)return; + const char *old=p->data+p->cursor; + p->cursor+=strlen(old); +} +int main(void) {char data[]="abc";struct output p={data,sizeof data,0,0};update(&p);return 0;} diff --git a/test/evaluation/rfc0029/corpus-regressions/update.c b/test/evaluation/rfc0029/corpus-regressions/update.c new file mode 100644 index 00000000..790113bc --- /dev/null +++ b/test/evaluation/rfc0029/corpus-regressions/update.c @@ -0,0 +1,9 @@ +#include +#include +struct output { char *data; size_t capacity, cursor, depth; }; +void update(struct output *p) { + if(!p || !p->data)return; + const char *old=p->data+p->cursor; + p->cursor+=strlen(old); +} +int main(void) {char data[]="abc";struct output p={data,sizeof data,0,0};update(&p);return 0;} diff --git a/test/evaluation/rfc0029/counter-reset-ranges-reviewed/README.md b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/README.md new file mode 100644 index 00000000..e245f36d --- /dev/null +++ b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/README.md @@ -0,0 +1,8 @@ +# Counter bounds before reset (RFC 0029) + +Reviewed before candidate 55. The exploratory inventory in +`build/rfc29-validation/counter-reset-ranges` and `manifest-original.json` +preserve the original observation: the negative matcher omitted the actual +leak and freed-use wording. This separate inventory corrects those matchers +before implementation; source bytes and accept/reject expectations agree. +A numeric scan count stays unchanged while its formerly equal index is reused. diff --git a/test/evaluation/rfc0029/counter-reset-ranges-reviewed/SHA256SUMS b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/SHA256SUMS new file mode 100644 index 00000000..a0fff7e7 --- /dev/null +++ b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/SHA256SUMS @@ -0,0 +1,9 @@ +0ab11746dcb8d598ee64503440335e79ddd66fe320b80578db346c394d795500 README.md +c5ca189a6761fdce2db90cb506abc7de2ef54fd2ee65bd55532891da960608ed decimal.c +3d339d16341111d73770d9c7f8ef5ac06a457d429eb89f3105db8ad9dd42529e good.c +8b7402c796c0bd9d753c0ca3762e50a1d0ab55addb14113e800667e058ff4b97 leak.c +0ba157c94cf8fed880f0d7511947c3b91bae8be5726f03e07bd5f7ac98c366b5 library.c +5304984d91a650e60b000217db37d5bc590d9235fb1b8c2af0bdc54688ebf083 manifest.json +12477f5c21811e10e8d95c81bd8f9be7beff2a983556fcc36cf106503f65e4ea reader.h +5f18fb7559fe6c49dbba5465e629e319273fdf945e6e01936c063203e185e7b6 released.c +36efffbd7921fe33de7970c2a18f463ea5b0cc18011ec98ae2290e9e11c2e9de short.c diff --git a/test/evaluation/rfc0029/counter-reset-ranges-reviewed/decimal.c b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/decimal.c new file mode 100644 index 00000000..ca102a41 --- /dev/null +++ b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/decimal.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void){const unsigned char input[]="1.0";struct reader r={input,0,sizeof input,0};return (int)scan(&r);} diff --git a/test/evaluation/rfc0029/counter-reset-ranges-reviewed/frozen-sha256.json b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/frozen-sha256.json new file mode 100644 index 00000000..45ec1654 --- /dev/null +++ b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/frozen-sha256.json @@ -0,0 +1,12 @@ +{ + "README.md": "7684f522a50332ee2812c672865ca261bb7f6d8ddcd0b30138f60b41ac0b8271", + "decimal.c": "c5ca189a6761fdce2db90cb506abc7de2ef54fd2ee65bd55532891da960608ed", + "good.c": "3d339d16341111d73770d9c7f8ef5ac06a457d429eb89f3105db8ad9dd42529e", + "leak.c": "8b7402c796c0bd9d753c0ca3762e50a1d0ab55addb14113e800667e058ff4b97", + "library.c": "0ba157c94cf8fed880f0d7511947c3b91bae8be5726f03e07bd5f7ac98c366b5", + "manifest-original.json": "5304984d91a650e60b000217db37d5bc590d9235fb1b8c2af0bdc54688ebf083", + "manifest.json": "1964a34f58789fc9e61955e76e549a1bb7a637b35ba575064bdcc9d543f32f55", + "reader.h": "12477f5c21811e10e8d95c81bd8f9be7beff2a983556fcc36cf106503f65e4ea", + "released.c": "5f18fb7559fe6c49dbba5465e629e319273fdf945e6e01936c063203e185e7b6", + "short.c": "36efffbd7921fe33de7970c2a18f463ea5b0cc18011ec98ae2290e9e11c2e9de" +} diff --git a/test/evaluation/rfc0029/counter-reset-ranges-reviewed/good.c b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/good.c new file mode 100644 index 00000000..f9f3d3b0 --- /dev/null +++ b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/good.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void){const unsigned char input[]="1";struct reader r={input,0,sizeof input,0};return (int)scan(&r);} diff --git a/test/evaluation/rfc0029/counter-reset-ranges-reviewed/leak.c b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/leak.c new file mode 100644 index 00000000..ef6a27a4 --- /dev/null +++ b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/leak.c @@ -0,0 +1,20 @@ +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +long scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +long scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +long scan(struct reader *); diff --git a/test/evaluation/rfc0029/counter-reset-ranges-reviewed/released.c b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/released.c new file mode 100644 index 00000000..5993bfc5 --- /dev/null +++ b/test/evaluation/rfc0029/counter-reset-ranges-reviewed/released.c @@ -0,0 +1,20 @@ +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +long scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +long scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + char *out=malloc(count);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i +int main(void) {const unsigned char data[]= "\"a\\nb\"";size_t size=sizeof data; + const unsigned char *first=data+1,*last=data+1; + while((size_t)(last-data)=size)return 0;last++;} + last++; + } + if((size_t)(last-data)>=size || *last!='"')return 0; + while(first +int scan(const unsigned char *data,size_t size) { + const unsigned char *first=data+1,*last=data+1; + while((size_t)(last-data)=size)return 0;last++;} + last++; + } + if((size_t)(last-data)>=size || *last!='"')return 0; + while(first +int main(void) {const unsigned char data[]= "\"aaaaa";size_t size=sizeof data+1; + const unsigned char *first=data+1,*last=data+1; + while((size_t)(last-data)=size)return 0;last++;} + last++; + } + if((size_t)(last-data)>=size || *last!='"')return 0; + while(first +int main(void) {const unsigned char data[]= "\"a\\nb\"";size_t size=sizeof data; + const unsigned char *first=data+1,*last=data+1; + while((size_t)(last-data)=size)return 0;last++;} + last++; + } + if((size_t)(last-data)>=size || *last!='"')return 0; + while(first +int main(void) {unsigned char data[7];data[0]=34;size_t size=sizeof data; + const unsigned char *first=data+1,*last=data+1; + while((size_t)(last-data)=size)return 0;last++;} + last++; + } + if((size_t)(last-data)>=size || *last!='"')return 0; + while(firstpos == r->limit) return -1; + int value=r->data[r->pos]; + r->pos=r->limit+1; + return value; +} diff --git a/test/evaluation/rfc0029/cursor-readers/escaped-read.c b/test/evaluation/rfc0029/cursor-readers/escaped-read.c new file mode 100644 index 00000000..ac11c0f9 --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/escaped-read.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void){unsigned char data[3]={1,2,3};struct reader r={0,data,3,0};take(&r);return r.data[r.pos];} diff --git a/test/evaluation/rfc0029/cursor-readers/frozen-sha256.json b/test/evaluation/rfc0029/cursor-readers/frozen-sha256.json new file mode 100644 index 00000000..445011e0 --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/frozen-sha256.json @@ -0,0 +1,16 @@ +{ + "README.md": "3b6f0e490b916519ef96b5d4e8653ce684ba7df5184b31614df7f4cb00f4ec19", + "capacity.c": "a3b695ec9b4503dd223027684fa40101fbc980e837428d4916545d4f14632552", + "client.c": "de39c0bcce3878c4ec4a07726a13ab9b629b39d6c988bd883bd51f7f1ce0ead8", + "copy.c": "7d22c104d10f51e7e8a3ae307214830c579d6db71783e8b0f7cd9037a839b2e8", + "cursor.c": "2f694205963e361066d6ad7ddfac7220cfa074be16e60b0e1db26f2dab79b07e", + "empty.c": "bef8c8ccd34047b02bf3b4fcf51b242612331a71af25f190ab9f009a318f8e47", + "escape.c": "4b8e830aafd4cf1bb79baa2d0b44de09c08fc4c77b298f321e8076fee4f1b878", + "library.c": "4b5ff344e9fafe0ce0592a10cf43f0b49a27efa7849c39721b44d2bb3d5c1e41", + "manifest.json": "c0f7473cd87050d8e68681ad63877c131b3ec7cad3c6a85abefded81130532d0", + "off-by-one.c": "42419565708aaaf4ba1153e206b0d7eae44ac990292bed10621fd8ed4febbe0b", + "partial.c": "c7fbb879440ed28a1c83607f9ce996938cb97b9c43cdd924ad1d68440eb341fe", + "reader.h": "52ba3e1894db7e8a1f8cc59d618f8bd38c0e82ba8321eba5063b6f4b844acb79", + "readonly.c": "3e5f52c9ae8a12d441f9dde1ee0ea9c91d485fc1d118ca8d1c7c4911076adcda", + "uninitialized.c": "0e6e28a9b09c7e89dee59f87321d845a19e72a90e82fdc53bbb55c0bb970b4de" +} diff --git a/test/evaluation/rfc0029/cursor-readers/library.c b/test/evaluation/rfc0029/cursor-readers/library.c new file mode 100644 index 00000000..7d4cb2f4 --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/library.c @@ -0,0 +1,7 @@ +#include "reader.h" +int take(struct reader *r) { + if (r->pos == r->limit) return -1; + int value=r->data[r->pos]; + ++r->pos; + return value; +} diff --git a/test/evaluation/rfc0029/cursor-readers/manifest.json b/test/evaluation/rfc0029/cursor-readers/manifest.json new file mode 100644 index 00000000..95245379 --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/manifest.json @@ -0,0 +1,184 @@ +{ + "version": 1, + "cases": [ + { + "name": "cursor-reader-client", + "sources": [ + "client.c", + "library.c" + ], + "functions": [ + "take", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0, + "entry_functions": [ + "main" + ] + }, + { + "name": "cursor-reader-empty", + "sources": [ + "empty.c", + "library.c" + ], + "functions": [ + "take", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0, + "entry_functions": [ + "main" + ] + }, + { + "name": "cursor-reader-copy", + "sources": [ + "copy.c", + "library.c" + ], + "functions": [ + "take", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0, + "entry_functions": [ + "main" + ] + }, + { + "name": "cursor-reader-uninitialized", + "sources": [ + "uninitialized.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|buffer|capacity|extent|range|bounds|cursor" + }, + { + "name": "cursor-reader-capacity", + "sources": [ + "capacity.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|buffer|capacity|extent|range|bounds|cursor" + }, + { + "name": "cursor-reader-cursor", + "sources": [ + "cursor.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|buffer|capacity|extent|range|bounds|cursor" + }, + { + "name": "cursor-reader-off-by-one", + "sources": [ + "client.c", + "off-by-one.c" + ], + "functions": [ + "take", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|range|buffer|writable|read-only|overflow" + }, + { + "name": "cursor-reader-escape", + "sources": [ + "client.c", + "escape.c" + ], + "functions": [ + "take", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|range|buffer|writable|read-only|overflow" + }, + { + "name": "cursor-reader-readonly", + "sources": [ + "client.c", + "readonly.c" + ], + "functions": [ + "take", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|range|buffer|writable|read-only|overflow" + }, + { + "name": "cursor-reader-partial", + "sources": [ + "client.c", + "partial.c" + ], + "functions": [ + "take", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0, + "entry_functions": [ + "main" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/cursor-readers/off-by-one.c b/test/evaluation/rfc0029/cursor-readers/off-by-one.c new file mode 100644 index 00000000..a0c15244 --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/off-by-one.c @@ -0,0 +1,7 @@ +#include "reader.h" +int take(struct reader *r) { + if (r->pos > r->limit) return -1; + int value=r->data[r->pos]; + ++r->pos; + return value; +} diff --git a/test/evaluation/rfc0029/cursor-readers/partial.c b/test/evaluation/rfc0029/cursor-readers/partial.c new file mode 100644 index 00000000..710d9bb1 --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/partial.c @@ -0,0 +1,8 @@ +#include "reader.h" +int take(struct reader *r) { + if (r->pos == r->limit) return -1; + int value=r->data[r->pos]; + ++r->pos; + if (value==0) return -1; + return value; +} diff --git a/test/evaluation/rfc0029/cursor-readers/reader.h b/test/evaluation/rfc0029/cursor-readers/reader.h new file mode 100644 index 00000000..bc3f994a --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/reader.h @@ -0,0 +1,3 @@ +#include +struct reader { unsigned depth; const unsigned char *data; size_t limit, pos; }; +int take(struct reader *r); diff --git a/test/evaluation/rfc0029/cursor-readers/readonly.c b/test/evaluation/rfc0029/cursor-readers/readonly.c new file mode 100644 index 00000000..0cdfbaa5 --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/readonly.c @@ -0,0 +1,8 @@ +#include "reader.h" +int take(struct reader *r) { + if (r->pos == r->limit) return -1; + ((unsigned char*)r->data)[r->pos]=0; + int value=r->data[r->pos]; + ++r->pos; + return value; +} diff --git a/test/evaluation/rfc0029/cursor-readers/reviewed-manifest.json b/test/evaluation/rfc0029/cursor-readers/reviewed-manifest.json new file mode 100644 index 00000000..ed495217 --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/reviewed-manifest.json @@ -0,0 +1,181 @@ +{ + "version": 1, + "cases": [ + { + "name": "cursor-reader-client", + "sources": [ + "client.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cursor-reader-empty", + "sources": [ + "empty.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cursor-reader-copy", + "sources": [ + "copy.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cursor-reader-generic", + "sources": [ + "client.c", + "library.c" + ], + "functions": [ + "take", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cursor-reader-partial", + "sources": [ + "client.c", + "partial.c" + ], + "functions": [ + "take", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cursor-reader-uninitialized", + "sources": [ + "uninitialized-read.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "initialized|buffer", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cursor-reader-capacity", + "sources": [ + "capacity-read.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "extent|bounds|buffer", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cursor-reader-cursor", + "sources": [ + "cursor.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "range|bounds|buffer|cursor", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cursor-reader-off-by-one", + "sources": [ + "endpoint-read.c", + "off-by-one.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "range|bounds|buffer|extent", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cursor-reader-escape", + "sources": [ + "escaped-read.c", + "escape.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "range|bounds|buffer|extent", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cursor-reader-readonly", + "sources": [ + "client.c", + "readonly.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "writable|read-only", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/cursor-readers/reviewed-sha256.json b/test/evaluation/rfc0029/cursor-readers/reviewed-sha256.json new file mode 100644 index 00000000..1165d02a --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/reviewed-sha256.json @@ -0,0 +1,8 @@ +{ + "audit.md": "dce51e1453061c61d67045019c3ff1b361f09156640f296faacef75373e689b1", + "capacity-read.c": "b01b36b98937dc73fbae8d2787b7cc17b3b3aeea46fef9c75e55a2c9fc076612", + "endpoint-read.c": "e3f20462e650fd623bf6a46a5e11e7ed935e6ba1bf37749ea2c8e45c8d7cd940", + "escaped-read.c": "7b3049e9e1f40a66cc28ecc90b6d7dd02198b5d552eaac5337a017836e0fe5bb", + "reviewed-manifest.json": "fde9304d6f52a89e34c53d5b574d1f6d31ab9cbabcdabaa65c02bab5d7455d47", + "uninitialized-read.c": "d4d193e3859deab3956e3ee153a443f1ef1ac05e77e46cdd4fb9ebfa6a16f1f1" +} diff --git a/test/evaluation/rfc0029/cursor-readers/uninitialized-read.c b/test/evaluation/rfc0029/cursor-readers/uninitialized-read.c new file mode 100644 index 00000000..9a59733f --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/uninitialized-read.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void){unsigned char data[3];data[0]=1;struct reader r={0,data,3,1};return take(&r);} diff --git a/test/evaluation/rfc0029/cursor-readers/uninitialized.c b/test/evaluation/rfc0029/cursor-readers/uninitialized.c new file mode 100644 index 00000000..0459ce9d --- /dev/null +++ b/test/evaluation/rfc0029/cursor-readers/uninitialized.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void){unsigned char data[3];data[0]=1;struct reader r={0,data,3,0};return take(&r);} diff --git a/test/evaluation/rfc0029/fixed-span-steps/README.md b/test/evaluation/rfc0029/fixed-span-steps/README.md new file mode 100644 index 00000000..9925205e --- /dev/null +++ b/test/evaluation/rfc0029/fixed-span-steps/README.md @@ -0,0 +1,5 @@ +Frozen before the fixed-step span proof correction. A complete generic helper +returns zero or exactly two bytes, bounded by its input span. Actual callers +advance within initialized arrays of even, odd, and runtime lengths. Invalid +variants overstate or change the step, pass unrelated endpoints, or leave input +uninitialized. Candidate72c is the unchanged baseline. diff --git a/test/evaluation/rfc0029/fixed-span-steps/api.h b/test/evaluation/rfc0029/fixed-span-steps/api.h new file mode 100644 index 00000000..fdf7fabf --- /dev/null +++ b/test/evaluation/rfc0029/fixed-span-steps/api.h @@ -0,0 +1 @@ +unsigned consume(const unsigned char*,const unsigned char*); diff --git a/test/evaluation/rfc0029/fixed-span-steps/frozen-sha256.json b/test/evaluation/rfc0029/fixed-span-steps/frozen-sha256.json new file mode 100644 index 00000000..01a1ab78 --- /dev/null +++ b/test/evaluation/rfc0029/fixed-span-steps/frozen-sha256.json @@ -0,0 +1,13 @@ +{ + "README.md": "b60a3d367722f554b0615f6abf772033e96f89fafca9e6b357fa622a2bc7827b", + "api.h": "dcd79313caa77f910e2ef89d9d2b56f3b13513f2adc96fdba3843d486561b937", + "good.c": "993758539950e3310aa64004f4d0281f0999b51b727321f78970b6a57e37d3b0", + "library.c": "8eaa045bb23e15482ad541628d0eed6c5baf7a00402352cf6fe370136d308aee", + "manifest.json": "3b27a5a903c50b27370c74f58c06dbef98b6e0c6107a6a83f68b96917cbd4f54", + "odd-tail.c": "a28f7364c39e2550bc459ca111a6a7176d24f63c49a77abf54fb1e7d2938f73e", + "over-count.c": "8b083ca42bdc9fea9beef5bcd5b429d0a94d4e676599b5476fb39d305ad832f0", + "over-step.c": "2b8891ff57fe70b1e936d076f002fa25932032093d1dd8a1a1df4556506a4959", + "runtime.c": "a3d21ad6ad318c3af2092aede6ec0f1f6352d6b8679998ff0480060f914358d2", + "uninitialized.c": "b01883ef14c8a47c872190b55855101b28e04deea6b2fc52a67f0f6c9bb46bff", + "unrelated.c": "24ded7a119bbf911adebd25eff33e70c706edb66afd08c01bc365772678d17b6" +} diff --git a/test/evaluation/rfc0029/fixed-span-steps/good.c b/test/evaluation/rfc0029/fixed-span-steps/good.c new file mode 100644 index 00000000..306ba2b4 --- /dev/null +++ b/test/evaluation/rfc0029/fixed-span-steps/good.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char src[10]={0};const unsigned char *p=src,*end=src+10;while(p10)return 0;const unsigned char src[10]={0};const unsigned char *p=src,*end=src+length;while(p +int clamp(double);int forward(double); diff --git a/test/evaluation/rfc0029/floating-call-premises/changed-clamp.c b/test/evaluation/rfc0029/floating-call-premises/changed-clamp.c new file mode 100644 index 00000000..461e7cb2 --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/changed-clamp.c @@ -0,0 +1,2 @@ +#include "api.h" +int clamp(double x){x=__builtin_nan("");if(x>=INT_MAX)return INT_MAX;if(x<=(double)INT_MIN)return INT_MIN;return (int)x;} diff --git a/test/evaluation/rfc0029/floating-call-premises/changed.c b/test/evaluation/rfc0029/floating-call-premises/changed.c new file mode 100644 index 00000000..ceb69c9b --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/changed.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){return clamp(1.0);} diff --git a/test/evaluation/rfc0029/floating-call-premises/clamp.c b/test/evaluation/rfc0029/floating-call-premises/clamp.c new file mode 100644 index 00000000..f193a9ef --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/clamp.c @@ -0,0 +1,2 @@ +#include "api.h" +int clamp(double x){if(x>=INT_MAX)return INT_MAX;if(x<=(double)INT_MIN)return INT_MIN;return (int)x;} diff --git a/test/evaluation/rfc0029/floating-call-premises/finite.c b/test/evaluation/rfc0029/floating-call-premises/finite.c new file mode 100644 index 00000000..ceb69c9b --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/finite.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){return clamp(1.0);} diff --git a/test/evaluation/rfc0029/floating-call-premises/forward.c b/test/evaluation/rfc0029/floating-call-premises/forward.c new file mode 100644 index 00000000..298e1261 --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/forward.c @@ -0,0 +1,2 @@ +#include "api.h" +int forward(double x){return clamp(x);} diff --git a/test/evaluation/rfc0029/floating-call-premises/forwarded.c b/test/evaluation/rfc0029/floating-call-premises/forwarded.c new file mode 100644 index 00000000..4cd41c3b --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/forwarded.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){return forward(1.0);} diff --git a/test/evaluation/rfc0029/floating-call-premises/frozen-sha256.json b/test/evaluation/rfc0029/floating-call-premises/frozen-sha256.json new file mode 100644 index 00000000..6ac18c88 --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/frozen-sha256.json @@ -0,0 +1,14 @@ +{ + "PROVENANCE.md": "782ce03ebc1f90713cd7d908ccf935fc8444f80e25c3fad4f18133bb6cd60c7c", + "api.h": "de265eb50563c7641fa5190cf1408bdf4d17eaf0ec823c28d434d7f282e285b6", + "changed-clamp.c": "48acd804079ba63cbd51634e48f755a93030b0bb5ca7d8772e09bd7866d236b4", + "changed.c": "91f7e418c8706c4955aa503ef90a07cf7efd17a754ebde2a283e2227fc8e3ecc", + "clamp.c": "8fa411dc8f26e9ac1c9cc1acdc555b87b9b7f4315182a5f6d6c86802c6bf7b3b", + "finite.c": "91f7e418c8706c4955aa503ef90a07cf7efd17a754ebde2a283e2227fc8e3ecc", + "forward.c": "cd07341f6de6a60d20d65c68620725d29dda3e54bcd70d04fabac87dad67f6e1", + "forwarded.c": "fdb4767c6c7f521c26ea458012601bb4b2544f340576dde2e054415f6b8026b4", + "infinite.c": "9f8b3edde90484e992cbc870d7e521e385736a5702884c6627df4823537f6a7d", + "manifest.json": "91dc89e2a7fe37016f406e3a63c65e8aef53000d88dcbfbab78075347b3fc0fa", + "mixed.c": "079e989acafc784059d47c9bf50a4ea1931b2d6cd28459ab3924e45c9fdbdfe2", + "nan.c": "713f8cfac248acfccb38591476c0da8fce7e15e56d7772bd5d4c74c53bf9bb9a" +} diff --git a/test/evaluation/rfc0029/floating-call-premises/infinite.c b/test/evaluation/rfc0029/floating-call-premises/infinite.c new file mode 100644 index 00000000..66119b2f --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/infinite.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){return clamp(__builtin_inf());} diff --git a/test/evaluation/rfc0029/floating-call-premises/manifest.json b/test/evaluation/rfc0029/floating-call-premises/manifest.json new file mode 100644 index 00000000..ea03d04d --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/manifest.json @@ -0,0 +1,107 @@ +{ + "version": 1, + "cases": [ + { + "name": "finite", + "sources": [ + "finite.c", + "clamp.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "forwarded", + "sources": [ + "forwarded.c", + "clamp.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "infinite", + "sources": [ + "infinite.c", + "clamp.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "nan", + "sources": [ + "nan.c", + "clamp.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "floating|unsupported|convert" + }, + { + "name": "changed", + "sources": [ + "changed.c", + "changed-clamp.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "floating|unsupported|convert" + }, + { + "name": "mixed", + "sources": [ + "mixed.c", + "clamp.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "floating|unsupported|convert" + } + ] +} diff --git a/test/evaluation/rfc0029/floating-call-premises/mixed.c b/test/evaluation/rfc0029/floating-call-premises/mixed.c new file mode 100644 index 00000000..54a131c7 --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/mixed.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){volatile int k=0;return clamp(k?1.0:__builtin_nan(""));} diff --git a/test/evaluation/rfc0029/floating-call-premises/nan.c b/test/evaluation/rfc0029/floating-call-premises/nan.c new file mode 100644 index 00000000..4a4a9a06 --- /dev/null +++ b/test/evaluation/rfc0029/floating-call-premises/nan.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){return clamp(__builtin_nan(""));} diff --git a/test/evaluation/rfc0029/frozen-sha256.json b/test/evaluation/rfc0029/frozen-sha256.json new file mode 100644 index 00000000..59d8756a --- /dev/null +++ b/test/evaluation/rfc0029/frozen-sha256.json @@ -0,0 +1,16 @@ +{ + "callback-allocate.c": "82719819dbd15d89153618e587c2e4523e636544d34e0ed8cad1990c391eb7bc", + "callback-short-bad.c": "32ccdf5c8290d3674927e2552da65f3d463753ff221729eeebdb05e41c137495", + "mutual-cleanup.c": "240b2ea77dfe9e36b8db657f2aee00a78fe58d4b0723e96dcb76625e7d9958eb", + "mutual-skipped-bad.c": "2d4e67478b2b80c256cb02226482980c2cb1af5f2cd7fdf7c00f30fab72bd7ff", + "reader-good.c": "21317bafa6a5b9f164562de54066091f3a8ab573538afdc850aed12fcafaffe1", + "reader-short-bad.c": "527b8c81e9cd06dfa34a833148d6850a01fa0cb2805a5f6eee25d6e09939f84a", + "reader.h": "5cf577d32f78fe6ea9acc43b7388141b7ca2700680a315e905f2fb2d02e3be0d", + "reassigned-release.c": "b06ddbb94bfcdc4f4cba54ae36b8646547e26829155b8e86bc3682b82a2a7ebf", + "state-capacity-bad.c": "2e107fc6090bbc930fbcba8027b69d92d0c242278d874389b6843213b1b2c1ef", + "state-good.c": "4f2403614021070261c6361efa93ff18f5197aad4c0fbc38c42c33f6b8005880", + "state-stale-bad.c": "f18627713db7d88ebd0edccf88156695f6dad6ce385bd4478d0ae14962d557b2", + "state-tail-bad.c": "45f3fba689a0cef72126a8293564a0ac73956cc583608273ace062c7bfa80b98", + "state.h": "4d5d6c4f5b95757fcf6664fb202a2b086b4c337e014a8823335c29890fce0349", + "manifest.json": "9f5207a15dbde1bcbc64873804957116169cbac919b6d37fd0bdf6f6cf3899af" +} diff --git a/test/evaluation/rfc0029/guarded-advance/README.md b/test/evaluation/rfc0029/guarded-advance/README.md new file mode 100644 index 00000000..dc4bb296 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-advance/README.md @@ -0,0 +1,4 @@ +# Guarded initialization after a cursor advance (RFC 0029) + +Frozen before candidate 56. Only successful calls initialize the advanced bytes. +The actual result guard must reach a later read; replacing or dropping it fails. diff --git a/test/evaluation/rfc0029/guarded-advance/changed.c b/test/evaluation/rfc0029/guarded-advance/changed.c new file mode 100644 index 00000000..8782d794 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-advance/changed.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char **argv){(void)argv;unsigned char out[4],*p=out;unsigned ok=encode(&p,argc>1?65536:0);ok=1;if(!ok)return 0;return p[-1];} diff --git a/test/evaluation/rfc0029/guarded-advance/failure.c b/test/evaluation/rfc0029/guarded-advance/failure.c new file mode 100644 index 00000000..962ece22 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-advance/failure.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char **argv){(void)argv;unsigned char out[4],*p=out;unsigned ok=encode(&p,argc>1?65536:0);(void)ok;return p[-1];} diff --git a/test/evaluation/rfc0029/guarded-advance/frozen-sha256.json b/test/evaluation/rfc0029/guarded-advance/frozen-sha256.json new file mode 100644 index 00000000..174819c2 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-advance/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "README.md": "3b49058621867bd88b0b6d7600a163922517bf3fe2388161b9854416ce94cd0b", + "changed.c": "6a362128adf259e3fec8da8f4ce1c74d064b32e3143b40a0b8b6009c0b78be7b", + "failure.c": "7d6364cc3ec2f03267fd86aa4cecbb271a0523a5a265561b50d82e5f636cb7ac", + "good.c": "abad9d8f99bad38da26552fea9c5e672b89930af34a49465b70b451f4067b8b8", + "interior.c": "c7fc168a459b18b0bba757bc1aa57510f8d0cc95d8ed44bf4fc3094ddd7d0ccc", + "library.c": "4becc6e1f5db931342b04eef9674eca1725f09b8be8338dedb2a2c5a7992010a", + "manifest.json": "dcf82a025353c7b769a2807006f1ad72d8b2d7b09ea3fb3033ca1d286e9568bb", + "short.c": "405c46eafbcf482090dcbc583cb0211057af2bb0c3a1dd5032c34b7f420d6c17" +} diff --git a/test/evaluation/rfc0029/guarded-advance/good.c b/test/evaluation/rfc0029/guarded-advance/good.c new file mode 100644 index 00000000..d25ec545 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-advance/good.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char **argv){(void)argv;unsigned char out[4],*p=out;unsigned ok=encode(&p,argc>1?65536:0);if(!ok)return 0;return p[-1];} diff --git a/test/evaluation/rfc0029/guarded-advance/interior.c b/test/evaluation/rfc0029/guarded-advance/interior.c new file mode 100644 index 00000000..ed3fa2c1 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-advance/interior.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char **argv){(void)argv;unsigned char out[5],*p=out+1;unsigned ok=encode(&p,argc>1?65536:0);if(!ok)return 0;return p[-1];} diff --git a/test/evaluation/rfc0029/guarded-advance/library.c b/test/evaluation/rfc0029/guarded-advance/library.c new file mode 100644 index 00000000..a0fac72f --- /dev/null +++ b/test/evaluation/rfc0029/guarded-advance/library.c @@ -0,0 +1,7 @@ +unsigned encode(unsigned char **out,unsigned code) { + if(code==0){*out+=1;return 0;} + unsigned char n=code>255?4:1; + unsigned char i; + for(i=(unsigned char)(n-1);i>0;i--) (*out)[i]=42; + (*out)[0]=1; *out+=n;return n; +} diff --git a/test/evaluation/rfc0029/guarded-advance/manifest.json b/test/evaluation/rfc0029/guarded-advance/manifest.json new file mode 100644 index 00000000..af268337 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-advance/manifest.json @@ -0,0 +1,85 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "interior", + "sources": [ + "interior.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "failure", + "sources": [ + "failure.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + }, + { + "name": "changed", + "sources": [ + "changed.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + } + ] +} diff --git a/test/evaluation/rfc0029/guarded-advance/short.c b/test/evaluation/rfc0029/guarded-advance/short.c new file mode 100644 index 00000000..5e0d92e7 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-advance/short.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char **argv){(void)argv;unsigned char out[3],*p=out;unsigned ok=encode(&p,argc>1?65536:0);if(!ok)return 0;return p[-1];} diff --git a/test/evaluation/rfc0029/guarded-cursor-bounds/README.md b/test/evaluation/rfc0029/guarded-cursor-bounds/README.md new file mode 100644 index 00000000..dfc60d54 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-cursor-bounds/README.md @@ -0,0 +1,4 @@ +# Result-guarded cursor bounds (RFC 0029) + +Frozen before candidate 56c. A zero result leaves the cursor unchanged, while +success advances at least one initialized byte. Mutation retires the old bound. diff --git a/test/evaluation/rfc0029/guarded-cursor-bounds/changed.c b/test/evaluation/rfc0029/guarded-cursor-bounds/changed.c new file mode 100644 index 00000000..8782d794 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-cursor-bounds/changed.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char **argv){(void)argv;unsigned char out[4],*p=out;unsigned ok=encode(&p,argc>1?65536:0);ok=1;if(!ok)return 0;return p[-1];} diff --git a/test/evaluation/rfc0029/guarded-cursor-bounds/frozen-sha256.json b/test/evaluation/rfc0029/guarded-cursor-bounds/frozen-sha256.json new file mode 100644 index 00000000..fcbd10bc --- /dev/null +++ b/test/evaluation/rfc0029/guarded-cursor-bounds/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "README.md": "0c6e9588ba2170bab793a9fae0bafd7eba6464372199056b3542c448fc592e4c", + "changed.c": "6a362128adf259e3fec8da8f4ce1c74d064b32e3143b40a0b8b6009c0b78be7b", + "good.c": "abad9d8f99bad38da26552fea9c5e672b89930af34a49465b70b451f4067b8b8", + "interior.c": "c7fc168a459b18b0bba757bc1aa57510f8d0cc95d8ed44bf4fc3094ddd7d0ccc", + "library.c": "fcac9fa6e65e1c02dff34afecd519cbc40b05e3949dc199ed2391d7fef841032", + "manifest.json": "a8e98cb05579c567ccf1ae44b0c80bb731f5487e6f6c9aeb9c957b25acabc4bc", + "moved.c": "b3b7863e7fafd7d22024ccef04817fa1a6aecd5e3f0eaf54273d8321a3d07b75", + "short.c": "405c46eafbcf482090dcbc583cb0211057af2bb0c3a1dd5032c34b7f420d6c17" +} diff --git a/test/evaluation/rfc0029/guarded-cursor-bounds/good.c b/test/evaluation/rfc0029/guarded-cursor-bounds/good.c new file mode 100644 index 00000000..d25ec545 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-cursor-bounds/good.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char **argv){(void)argv;unsigned char out[4],*p=out;unsigned ok=encode(&p,argc>1?65536:0);if(!ok)return 0;return p[-1];} diff --git a/test/evaluation/rfc0029/guarded-cursor-bounds/interior.c b/test/evaluation/rfc0029/guarded-cursor-bounds/interior.c new file mode 100644 index 00000000..ed3fa2c1 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-cursor-bounds/interior.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char **argv){(void)argv;unsigned char out[5],*p=out+1;unsigned ok=encode(&p,argc>1?65536:0);if(!ok)return 0;return p[-1];} diff --git a/test/evaluation/rfc0029/guarded-cursor-bounds/library.c b/test/evaluation/rfc0029/guarded-cursor-bounds/library.c new file mode 100644 index 00000000..9eaca14c --- /dev/null +++ b/test/evaluation/rfc0029/guarded-cursor-bounds/library.c @@ -0,0 +1,7 @@ +unsigned encode(unsigned char **out,unsigned code) { + if(code==0)return 0; + unsigned char n=code>255?4:1; + unsigned char i; + for(i=(unsigned char)(n-1);i>0;i--) (*out)[i]=42; + (*out)[0]=1; *out+=n;return n; +} diff --git a/test/evaluation/rfc0029/guarded-cursor-bounds/manifest.json b/test/evaluation/rfc0029/guarded-cursor-bounds/manifest.json new file mode 100644 index 00000000..f4d46900 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-cursor-bounds/manifest.json @@ -0,0 +1,85 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "interior", + "sources": [ + "interior.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "changed", + "sources": [ + "changed.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|pointer" + }, + { + "name": "moved", + "sources": [ + "moved.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|pointer" + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|pointer" + } + ] +} diff --git a/test/evaluation/rfc0029/guarded-cursor-bounds/moved.c b/test/evaluation/rfc0029/guarded-cursor-bounds/moved.c new file mode 100644 index 00000000..1fa2277f --- /dev/null +++ b/test/evaluation/rfc0029/guarded-cursor-bounds/moved.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char **argv){(void)argv;unsigned char out[4],*p=out;unsigned ok=encode(&p,argc>1?65536:0);if(!ok)return 0;p-=4;return p[-1];} diff --git a/test/evaluation/rfc0029/guarded-cursor-bounds/short.c b/test/evaluation/rfc0029/guarded-cursor-bounds/short.c new file mode 100644 index 00000000..5e0d92e7 --- /dev/null +++ b/test/evaluation/rfc0029/guarded-cursor-bounds/short.c @@ -0,0 +1,2 @@ +unsigned encode(unsigned char**,unsigned); +int main(int argc,char **argv){(void)argv;unsigned char out[3],*p=out;unsigned ok=encode(&p,argc>1?65536:0);if(!ok)return 0;return p[-1];} diff --git a/test/evaluation/rfc0029/helper-cursor-pairs/README.md b/test/evaluation/rfc0029/helper-cursor-pairs/README.md new file mode 100644 index 00000000..8f01cd6b --- /dev/null +++ b/test/evaluation/rfc0029/helper-cursor-pairs/README.md @@ -0,0 +1,6 @@ +Frozen before the helper cursor-pair correction. Candidate72e supplies the +baseline. Each successful helper call consumes two input bytes and emits one +output byte; failure emits none. Independent direct and helper advances may +share a traversal. Unsafe variants shorten the output, remove input progress, +leave input uninitialized, or enlarge the output stride. The source contains +no parser-specific names or annotations. diff --git a/test/evaluation/rfc0029/helper-cursor-pairs/api.h b/test/evaluation/rfc0029/helper-cursor-pairs/api.h new file mode 100644 index 00000000..aea70e13 --- /dev/null +++ b/test/evaluation/rfc0029/helper-cursor-pairs/api.h @@ -0,0 +1 @@ +unsigned decode(const unsigned char *,const unsigned char *,unsigned char **); diff --git a/test/evaluation/rfc0029/helper-cursor-pairs/frozen-sha256.json b/test/evaluation/rfc0029/helper-cursor-pairs/frozen-sha256.json new file mode 100644 index 00000000..ccc0cfd4 --- /dev/null +++ b/test/evaluation/rfc0029/helper-cursor-pairs/frozen-sha256.json @@ -0,0 +1,13 @@ +{ + "README.md": "e0cf7e0ca26d9bbc13a23f7f5f5055151abcb9bb4444a35c51fb70b002afcc7f", + "api.h": "8b6a6f8c2af771dbd8bd4b1f615eb6f5f076ccef6119d97694b319ca553f84e9", + "good.c": "b378f771f76103a1612713df740ab99fd96e65251062e933057489a5a46caa09", + "library.c": "6e053f17f8610903b0d5937994c8c9305b8a4e8e164d0edb00520acd5a656ea8", + "manifest.json": "dd97419466a52911fa6c00ba5ffad05bb8e5aa26535084ed4ae5169fba4cda01", + "mixed.c": "b11f257dde8da8924f01cec8d6feb715d7c65e2db0fc66cf6578a8fef2a79169", + "no-progress.c": "1c9993da9a92f8f863737179c9727e063fcfd443045e2c435e90d389aa8f2ee7", + "once.c": "2343bc6366a31bbc5c869b9737c31542af407a942b4967aadcc0c09ba16a850f", + "short.c": "22219b0106d0f393d7ae7474c74907fdf06660701a19f471348e38f964277d8a", + "uninitialized.c": "a00ad7315eeda1dbfa99fa697f9dd0e497a5f6fbbd50ef31d44a03c968e784a7", + "wrong-step.c": "d53ba0a7488e1a15fe8472bd4e731b13e8b807a61f5ff1683ebb15b527128c5a" +} diff --git a/test/evaluation/rfc0029/helper-cursor-pairs/good.c b/test/evaluation/rfc0029/helper-cursor-pairs/good.c new file mode 100644 index 00000000..ea7b9080 --- /dev/null +++ b/test/evaluation/rfc0029/helper-cursor-pairs/good.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const unsigned char bytes[10]={0};unsigned char dst[10];const unsigned char *p=bytes,*end=bytes+10;unsigned char *out=dst;while(p +struct node { struct node *next,*child; }; +static void drop(struct node*n){while(n){struct node*next=n->next;drop(n->child);free(n);n=next;}} +static int grow(struct node*n,unsigned depth){if(!depth)return 1;struct node*c=calloc(1,sizeof *c);if(!c)return 0;if(!grow(c,depth-1)){drop(c); return 0;}n->child=c;n->next=c; return 1;} +int main(int argc,char**argv){(void)argv;struct node*n=calloc(1,sizeof *n);if(!n)return 0;(void)grow(n,(unsigned)argc);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/in-place-extension/failed-cleanup.c b/test/evaluation/rfc0029/in-place-extension/failed-cleanup.c new file mode 100644 index 00000000..68b6a516 --- /dev/null +++ b/test/evaluation/rfc0029/in-place-extension/failed-cleanup.c @@ -0,0 +1,5 @@ +#include +struct node { struct node *next,*child; }; +static void drop(struct node*n){while(n){struct node*next=n->next;drop(n->child);free(n);n=next;}} +static int grow(struct node*n,unsigned depth){if(!depth)return 1;struct node*c=calloc(1,sizeof *c);if(!c)return 0;if(!grow(c,depth-1)){ return 0;}n->child=c; return 1;} +int main(int argc,char**argv){(void)argv;struct node*n=calloc(1,sizeof *n);if(!n)return 0;(void)grow(n,(unsigned)argc);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/in-place-extension/frozen-sha256.json b/test/evaluation/rfc0029/in-place-extension/frozen-sha256.json new file mode 100644 index 00000000..bf48f92e --- /dev/null +++ b/test/evaluation/rfc0029/in-place-extension/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "failed-cleanup.c": "577fea68a611cff75f3acacd86137c71504d8c43a7c2444e1031febc369bc422", + "nondecreasing.c": "e89a6179521443e4e076e1e106b0c6936880a09949b49f3a8cd867615df63d1b", + "README.md": "57a39083851691beac1f09dada2832eadfbfbdd758daf58279e2c137de8bb06c", + "lost-child.c": "c232f62eb73b4682a8a2507527fbe2d775eb40a94b7ed1e443d3a4c797ddc87f", + "recursive.c": "9c6d6e4a89a1bec01a73f0edf3f1c7b40ffc534eb78d7e1b8032f5fd04f10d2f", + "manifest.json": "46f0e117aceef5216f47c47dccb6fd09a202539903221400fb2685fb16870c05", + "duplicate-child.c": "e496ee9cef899cdb217c5d9fd18f74cf227ca34bc8222132d88d1aa5a8d15fd3" +} diff --git a/test/evaluation/rfc0029/in-place-extension/lost-child.c b/test/evaluation/rfc0029/in-place-extension/lost-child.c new file mode 100644 index 00000000..7187a770 --- /dev/null +++ b/test/evaluation/rfc0029/in-place-extension/lost-child.c @@ -0,0 +1,5 @@ +#include +struct node { struct node *next,*child; }; +static void drop(struct node*n){while(n){struct node*next=n->next;drop(n->child);free(n);n=next;}} +static int grow(struct node*n,unsigned depth){if(!depth)return 1;struct node*c=calloc(1,sizeof *c);if(!c)return 0;if(!grow(c,depth-1)){drop(c); return 0;} return 1;} +int main(int argc,char**argv){(void)argv;struct node*n=calloc(1,sizeof *n);if(!n)return 0;(void)grow(n,(unsigned)argc);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/in-place-extension/manifest.json b/test/evaluation/rfc0029/in-place-extension/manifest.json new file mode 100644 index 00000000..bed66fb3 --- /dev/null +++ b/test/evaluation/rfc0029/in-place-extension/manifest.json @@ -0,0 +1,80 @@ +{ + "version": 1, + "cases": [ + { + "name": "recursive", + "sources": [ + "recursive.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "entry_requirements": 0 + }, + { + "name": "lost-child", + "sources": [ + "lost-child.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|allocation|recursive|progress|leak|ownership|complete" + }, + { + "name": "duplicate-child", + "sources": [ + "duplicate-child.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|allocation|recursive|progress|leak|ownership|complete" + }, + { + "name": "nondecreasing", + "sources": [ + "nondecreasing.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|allocation|recursive|progress|leak|ownership|complete" + }, + { + "name": "failed-cleanup", + "sources": [ + "failed-cleanup.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|allocation|recursive|progress|leak|ownership|complete" + } + ] +} diff --git a/test/evaluation/rfc0029/in-place-extension/nondecreasing.c b/test/evaluation/rfc0029/in-place-extension/nondecreasing.c new file mode 100644 index 00000000..60dba516 --- /dev/null +++ b/test/evaluation/rfc0029/in-place-extension/nondecreasing.c @@ -0,0 +1,5 @@ +#include +struct node { struct node *next,*child; }; +static void drop(struct node*n){while(n){struct node*next=n->next;drop(n->child);free(n);n=next;}} +static int grow(struct node*n,unsigned depth){if(!depth)return 1;struct node*c=calloc(1,sizeof *c);if(!c)return 0;if(!grow(c,depth)){drop(c); return 0;}n->child=c; return 1;} +int main(int argc,char**argv){(void)argv;struct node*n=calloc(1,sizeof *n);if(!n)return 0;(void)grow(n,(unsigned)argc);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/in-place-extension/recursive.c b/test/evaluation/rfc0029/in-place-extension/recursive.c new file mode 100644 index 00000000..5c6d1cd8 --- /dev/null +++ b/test/evaluation/rfc0029/in-place-extension/recursive.c @@ -0,0 +1,5 @@ +#include +struct node { struct node *next,*child; }; +static void drop(struct node*n){while(n){struct node*next=n->next;drop(n->child);free(n);n=next;}} +static int grow(struct node*n,unsigned depth){if(!depth)return 1;struct node*c=calloc(1,sizeof *c);if(!c)return 0;if(!grow(c,depth-1)){drop(c); return 0;}n->child=c; return 1;} +int main(int argc,char**argv){(void)argv;struct node*n=calloc(1,sizeof *n);if(!n)return 0;(void)grow(n,(unsigned)argc);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/independent-cursors/README.md b/test/evaluation/rfc0029/independent-cursors/README.md new file mode 100644 index 00000000..8b8ac676 --- /dev/null +++ b/test/evaluation/rfc0029/independent-cursors/README.md @@ -0,0 +1,10 @@ +RFC 0029 independent cursor coordinates +======================================= + +Frozen before candidate 72 checker edits. Independent input and output objects +start their byte coordinates at zero and advance together. The relation is +about numeric byte coordinates; it supplies no common pointer provenance. +The population requires generic and closed copies and rejects a short output, +uninitialized input, a changed stride, and an initially shifted destination. +Sources are independent reductions of the decoder investigation, not cJSON +source replacements. Candidate 71a is the retained baseline. diff --git a/test/evaluation/rfc0029/independent-cursors/api.h b/test/evaluation/rfc0029/independent-cursors/api.h new file mode 100644 index 00000000..e5768c9e --- /dev/null +++ b/test/evaluation/rfc0029/independent-cursors/api.h @@ -0,0 +1,2 @@ +#include +void copy_bytes(const unsigned char *, unsigned char *, size_t); diff --git a/test/evaluation/rfc0029/independent-cursors/closed.c b/test/evaluation/rfc0029/independent-cursors/closed.c new file mode 100644 index 00000000..e24872d2 --- /dev/null +++ b/test/evaluation/rfc0029/independent-cursors/closed.c @@ -0,0 +1 @@ +int main(void){const unsigned char src[10]={0};unsigned char dst[10];const unsigned char *p=src;unsigned char *q=dst;while(p +void copy_bytes(const unsigned char *src, unsigned char *dst, size_t count) { + const unsigned char *p=src; unsigned char *q=dst; + while(p1?65536:1);if(p>out)return p[-1];return 0;} diff --git a/test/evaluation/rfc0029/initialized-advance/interior.c b/test/evaluation/rfc0029/initialized-advance/interior.c new file mode 100644 index 00000000..17f55d66 --- /dev/null +++ b/test/evaluation/rfc0029/initialized-advance/interior.c @@ -0,0 +1,2 @@ +void encode(unsigned char**,unsigned); +int main(int argc,char**argv){(void)argv;unsigned char out[5];unsigned char *p=out+1;encode(&p,argc>1?65536:1);if(p>out+1)return p[-1];return 0;} diff --git a/test/evaluation/rfc0029/initialized-advance/library.c b/test/evaluation/rfc0029/initialized-advance/library.c new file mode 100644 index 00000000..e2a44f0c --- /dev/null +++ b/test/evaluation/rfc0029/initialized-advance/library.c @@ -0,0 +1,6 @@ +void encode(unsigned char **out,unsigned code) { + unsigned char n=code>255?4:1; + unsigned char i; + for(i=(unsigned char)(n-1);i>0;i--) (*out)[i]=42; + (*out)[0]=1; *out+=n; +} diff --git a/test/evaluation/rfc0029/initialized-advance/manifest.json b/test/evaluation/rfc0029/initialized-advance/manifest.json new file mode 100644 index 00000000..6dc9c984 --- /dev/null +++ b/test/evaluation/rfc0029/initialized-advance/manifest.json @@ -0,0 +1,83 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "interior", + "sources": [ + "interior.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + }, + { + "name": "skipped", + "sources": [ + "skipped.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + }, + { + "name": "over-advance", + "sources": [ + "over-advance.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + } + ] +} diff --git a/test/evaluation/rfc0029/initialized-advance/over-advance.c b/test/evaluation/rfc0029/initialized-advance/over-advance.c new file mode 100644 index 00000000..2133f6fa --- /dev/null +++ b/test/evaluation/rfc0029/initialized-advance/over-advance.c @@ -0,0 +1,2 @@ +void encode(unsigned char **out,unsigned code){unsigned n=code>255?4:1;for(unsigned i=n-1;i>0;i--)(*out)[i]=42;(*out)[0]=1;*out+=n+1;} +int main(void){unsigned char out[5];unsigned char *p=out;encode(&p,65536);return p[-1];} diff --git a/test/evaluation/rfc0029/initialized-advance/short.c b/test/evaluation/rfc0029/initialized-advance/short.c new file mode 100644 index 00000000..0867d6e3 --- /dev/null +++ b/test/evaluation/rfc0029/initialized-advance/short.c @@ -0,0 +1,2 @@ +void encode(unsigned char**,unsigned); +int main(void){unsigned char out[3];unsigned char *p=out;encode(&p,65536);return 0;} diff --git a/test/evaluation/rfc0029/initialized-advance/skipped.c b/test/evaluation/rfc0029/initialized-advance/skipped.c new file mode 100644 index 00000000..3deb7b4e --- /dev/null +++ b/test/evaluation/rfc0029/initialized-advance/skipped.c @@ -0,0 +1,2 @@ +void encode(unsigned char **out,unsigned code){unsigned n=code>255?4:1;for(unsigned i=n-1;i>0;i--){if(i!=2)(*out)[i]=42;}(*out)[0]=1;*out+=n;} +int main(void){unsigned char out[4];unsigned char *p=out;encode(&p,65536);return out[2];} diff --git a/test/evaluation/rfc0029/initialized-spans/README.md b/test/evaluation/rfc0029/initialized-spans/README.md new file mode 100644 index 00000000..4d7b6096 --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/README.md @@ -0,0 +1 @@ +Frozen before RFC 0029 initialized-span inference. Separate byte-pointer endpoints must carry a caller-discharged same-array, live, initialized interval and representable distance. Generic and closed pair readers, including an empty span, are positive. Unrelated arrays, uninitialized contents, released storage, escaped endpoints, const writes and unknown mutation remain rejected for their actual obligations. diff --git a/test/evaluation/rfc0029/initialized-spans/clobber.c b/test/evaluation/rfc0029/initialized-spans/clobber.c new file mode 100644 index 00000000..a85b5869 --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/clobber.c @@ -0,0 +1,5 @@ +extern void clobber(unsigned char *); +int pair(unsigned char *first, unsigned char *last) { + if(last-first<2)return 0; clobber(first);return first[1]; +} +int main(void) { unsigned char a[2]={1,2};return pair(a,a+2); } diff --git a/test/evaluation/rfc0029/initialized-spans/empty.c b/test/evaluation/rfc0029/initialized-spans/empty.c new file mode 100644 index 00000000..dc3b8343 --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/empty.c @@ -0,0 +1,2 @@ +int pair(const unsigned char *, const unsigned char *); +int main(void) { const unsigned char a[1] = {0}; return pair(a,a); } diff --git a/test/evaluation/rfc0029/initialized-spans/freed.c b/test/evaluation/rfc0029/initialized-spans/freed.c new file mode 100644 index 00000000..2e51eb0e --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/freed.c @@ -0,0 +1,3 @@ +#include +int pair(const unsigned char *, const unsigned char *); +int main(void) { unsigned char *a=malloc(2); if(!a)return 0; a[0]=1;a[1]=2; free(a); return pair(a,a+2); } diff --git a/test/evaluation/rfc0029/initialized-spans/frozen-sha256.json b/test/evaluation/rfc0029/initialized-spans/frozen-sha256.json new file mode 100644 index 00000000..ff72f406 --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/frozen-sha256.json @@ -0,0 +1,13 @@ +{ + "README.md": "ea4a6cc4d17e9a05eaeac2ef7974099222a7ca10e8d688c3478f6ff7ea767dce", + "clobber.c": "be978f3cd7ca7cb9043d468d6f31679c19e1a9d5023a338f1cbb50e2c97a4c54", + "empty.c": "13664585ee7507d52e56654ff175e2326e8127ad2512ee8d8ddc38466d00ff75", + "freed.c": "2767bd7a0480e70f1aed6fb3d9813bf6da5422703c1277dc52b089e6fd332cc2", + "good.c": "bfa0f50886be9550e8e352bdf8ffe60fd803e223b8f6a8ab43f2c8ba3ecb2c06", + "library.c": "3360161c36cf03787e328b8122a77a7b5e285cf3bf2ac00206383d01c2a797f7", + "manifest.json": "fc2c9730fb4191c752a3ea860554ccb3a6342f6e127572c77d4f7c090b841316", + "outside.c": "d7f4738d8edf93de0408ca355a80d4163da51a58358f0ee2fd519738c300cfc3", + "uninitialized.c": "d9dcaba385f913dd17b7a1ebb6881c83b8d1cfa357f5d11590d9b52e1ae544c3", + "unrelated.c": "20c82ec7c43ec1cb55f77dd8b071b8ae3430c86f052c4c9c898916666a3c5fba", + "write.c": "6f3e9fe9f8e947f93b1059c4dad92037fde69063d08e159548761cde95030c86" +} diff --git a/test/evaluation/rfc0029/initialized-spans/good.c b/test/evaluation/rfc0029/initialized-spans/good.c new file mode 100644 index 00000000..3e281aac --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/good.c @@ -0,0 +1,2 @@ +int pair(const unsigned char *, const unsigned char *); +int main(void) { const unsigned char a[4] = {1,2,3,4}; return pair(a+1,a+4)==5 ? 0 : 1; } diff --git a/test/evaluation/rfc0029/initialized-spans/library.c b/test/evaluation/rfc0029/initialized-spans/library.c new file mode 100644 index 00000000..a0ae5cda --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/library.c @@ -0,0 +1,5 @@ +int pair(const unsigned char *first, const unsigned char *last) { + const unsigned char *copy = first; + if (last - copy < 2) return 0; + return copy[0] + copy[1]; +} diff --git a/test/evaluation/rfc0029/initialized-spans/manifest.json b/test/evaluation/rfc0029/initialized-spans/manifest.json new file mode 100644 index 00000000..ebc35cf5 --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/manifest.json @@ -0,0 +1,145 @@ +{ + "version": 1, + "cases": [ + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "pair" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "empty", + "sources": [ + "empty.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "unrelated", + "sources": [ + "unrelated.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "span|interval|bounds|extent|shared-object|initialized|live|freed|writable|complete|unresolved" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "span|interval|bounds|extent|shared-object|initialized|live|freed|writable|complete|unresolved" + }, + { + "name": "freed", + "sources": [ + "freed.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "span|interval|bounds|extent|shared-object|initialized|live|freed|writable|complete|unresolved" + }, + { + "name": "outside", + "sources": [ + "outside.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "span|interval|bounds|extent|shared-object|initialized|live|freed|writable|complete|unresolved" + }, + { + "name": "write", + "sources": [ + "write.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "span|interval|bounds|extent|shared-object|initialized|live|freed|writable|complete|unresolved" + }, + { + "name": "clobber", + "sources": [ + "clobber.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "span|interval|bounds|extent|shared-object|initialized|live|freed|writable|complete|unresolved" + } + ] +} diff --git a/test/evaluation/rfc0029/initialized-spans/outside.c b/test/evaluation/rfc0029/initialized-spans/outside.c new file mode 100644 index 00000000..aea49f3b --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/outside.c @@ -0,0 +1,2 @@ +int pair(const unsigned char *, const unsigned char *); +int main(void) { const unsigned char a[2] = {1,2}; return pair(a,a+3); } diff --git a/test/evaluation/rfc0029/initialized-spans/uninitialized.c b/test/evaluation/rfc0029/initialized-spans/uninitialized.c new file mode 100644 index 00000000..375a337c --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/uninitialized.c @@ -0,0 +1,2 @@ +int pair(const unsigned char *, const unsigned char *); +int main(void) { unsigned char a[2]; a[0]=1; return pair(a,a+2); } diff --git a/test/evaluation/rfc0029/initialized-spans/unrelated.c b/test/evaluation/rfc0029/initialized-spans/unrelated.c new file mode 100644 index 00000000..3ea1b360 --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/unrelated.c @@ -0,0 +1,2 @@ +int pair(const unsigned char *, const unsigned char *); +int main(void) { const unsigned char a[2] = {1,2}, b[2] = {3,4}; return pair(a,b+2); } diff --git a/test/evaluation/rfc0029/initialized-spans/write.c b/test/evaluation/rfc0029/initialized-spans/write.c new file mode 100644 index 00000000..967d0850 --- /dev/null +++ b/test/evaluation/rfc0029/initialized-spans/write.c @@ -0,0 +1,4 @@ +int overwrite(unsigned char *first, unsigned char *last) { + if(last-first<2)return 0; first[1]=3;return 1; +} +int main(void) { const unsigned char a[2]={1,2};return overwrite((unsigned char*)a,(unsigned char*)a+2); } diff --git a/test/evaluation/rfc0029/local-callee-copies/README.md b/test/evaluation/rfc0029/local-callee-copies/README.md new file mode 100644 index 00000000..6b155788 --- /dev/null +++ b/test/evaluation/rfc0029/local-callee-copies/README.md @@ -0,0 +1,6 @@ +# Copies stored in tracked automatic slots (RFC 0029) + +Frozen before candidate 51. Returning an input pointer through a tracked local +slot does not hand it to unrepresented storage. The input allocation remains +live and releasable; aliases still die with it. Leaking all local aliases and +freeing an unknown interior pointer are unsafe counterparts. diff --git a/test/evaluation/rfc0029/local-callee-copies/copy.c b/test/evaluation/rfc0029/local-callee-copies/copy.c new file mode 100644 index 00000000..bb28d8ff --- /dev/null +++ b/test/evaluation/rfc0029/local-callee-copies/copy.c @@ -0,0 +1,5 @@ +#include +#include +#include +void copy(char *p,char **out){*out=p;} +int main(void){char *p=malloc(3);if(!p)return 0;p[0]=1;char *q=0;copy(p,&q);int n=p[0];free(p);return n;} diff --git a/test/evaluation/rfc0029/local-callee-copies/freed-alias.c b/test/evaluation/rfc0029/local-callee-copies/freed-alias.c new file mode 100644 index 00000000..8fa01591 --- /dev/null +++ b/test/evaluation/rfc0029/local-callee-copies/freed-alias.c @@ -0,0 +1,5 @@ +#include +#include +#include +void copy(char *p,char **out){*out=p;} +int main(void){char *p=malloc(3);if(!p)return 0;p[0]=1;char *q=0;copy(p,&q);free(p);return q[0];} diff --git a/test/evaluation/rfc0029/local-callee-copies/frozen-sha256.json b/test/evaluation/rfc0029/local-callee-copies/frozen-sha256.json new file mode 100644 index 00000000..ad3b1804 --- /dev/null +++ b/test/evaluation/rfc0029/local-callee-copies/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "README.md": "18fa2f74105d24daf53eeb1ef4b35bd81dec26aff632252ef4018e0ddab6b233", + "copy.c": "a86f1263411ed658fcae493176c1b4877a442494091aaee0bb9a619a8c64ab99", + "freed-alias.c": "91063565ba93f36b58ea842be75e147d1c1040e458f045ee351aa74689d502f8", + "interior-release.c": "8b581dc3182dc77578884843b8e2ac488d55bbb8ce3c0676ce32e013539a3575", + "leak.c": "d4470c8ebbf92a0636764b8ef795e22295814c5ca8a116063d49febd1f487fec", + "manifest.json": "2aa205070c09866dfd5c26010b48710443148a1d6321b4edac3b7e1dd6b510f5", + "numeric.c": "c165affb299b7be4cadad1726a0d4c5fc92d8967fc1826984dfd77714c6af8dd", + "unsigned.c": "8c1b097642478835dc986248548d410c50d243780bdec52fa42e0dbb7710d4c0" +} diff --git a/test/evaluation/rfc0029/local-callee-copies/interior-release.c b/test/evaluation/rfc0029/local-callee-copies/interior-release.c new file mode 100644 index 00000000..f835339b --- /dev/null +++ b/test/evaluation/rfc0029/local-callee-copies/interior-release.c @@ -0,0 +1,4 @@ +#include +#include +#include +int main(void){char *p=malloc(3);if(!p)return 0;memcpy(p,"12",3);char *end=0;(void)strtod(p,&end);free(end);return 0;} diff --git a/test/evaluation/rfc0029/local-callee-copies/leak.c b/test/evaluation/rfc0029/local-callee-copies/leak.c new file mode 100644 index 00000000..4ebbf8ea --- /dev/null +++ b/test/evaluation/rfc0029/local-callee-copies/leak.c @@ -0,0 +1,5 @@ +#include +#include +#include +void copy(char *p,char **out){*out=p;} +int main(void){char *p=malloc(3);if(!p)return 0;p[0]=1;char *q=0;copy(p,&q);return q[0];} diff --git a/test/evaluation/rfc0029/local-callee-copies/manifest.json b/test/evaluation/rfc0029/local-callee-copies/manifest.json new file mode 100644 index 00000000..1df5cbdc --- /dev/null +++ b/test/evaluation/rfc0029/local-callee-copies/manifest.json @@ -0,0 +1,95 @@ +{ + "version": 1, + "cases": [ + { + "name": "numeric", + "sources": [ + "numeric.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "unsigned", + "sources": [ + "unsigned.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "copy", + "sources": [ + "copy.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "freed-alias", + "sources": [ + "freed-alias.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "freed|release|live|leak|allocation" + }, + { + "name": "interior-release", + "sources": [ + "interior-release.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "freed|release|live|leak|allocation" + }, + { + "name": "leak", + "sources": [ + "leak.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "freed|release|live|leak|allocation" + } + ] +} diff --git a/test/evaluation/rfc0029/local-callee-copies/numeric.c b/test/evaluation/rfc0029/local-callee-copies/numeric.c new file mode 100644 index 00000000..290c7f59 --- /dev/null +++ b/test/evaluation/rfc0029/local-callee-copies/numeric.c @@ -0,0 +1,4 @@ +#include +#include +#include +int main(void){char *p=malloc(3);if(!p)return 0;memcpy(p,"12",3);char *end=0;(void)strtod(p,&end);ptrdiff_t n=end-p;free(p);return (int)n;} diff --git a/test/evaluation/rfc0029/local-callee-copies/unsigned.c b/test/evaluation/rfc0029/local-callee-copies/unsigned.c new file mode 100644 index 00000000..f12debdd --- /dev/null +++ b/test/evaluation/rfc0029/local-callee-copies/unsigned.c @@ -0,0 +1,4 @@ +#include +#include +#include +int main(void){unsigned char *p=malloc(3);if(!p)return 0;memcpy(p,"12",3);unsigned char *end=0;(void)strtod((const char*)p,(char**)&end);ptrdiff_t n=end-p;free(p);return (int)n;} diff --git a/test/evaluation/rfc0029/manifest.json b/test/evaluation/rfc0029/manifest.json new file mode 100644 index 00000000..2548005b --- /dev/null +++ b/test/evaluation/rfc0029/manifest.json @@ -0,0 +1,231 @@ +{ + "version": 1, + "cases": [ + { + "name": "state-runtime", + "sources": [ + "state-good.c" + ], + "functions": [ + "client", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "state-generic", + "sources": [ + "state-good.c" + ], + "functions": [ + "reserve", + "append", + "last" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "state-capacity", + "sources": [ + "state-capacity-bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|extent|bounds" + }, + { + "name": "state-stale", + "sources": [ + "state-stale-bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "freed|valid|live" + }, + { + "name": "state-tail", + "sources": [ + "state-tail-bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ|buffer" + }, + { + "name": "reader-closed", + "sources": [ + "reader-good.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "reader-generic", + "sources": [ + "reader-good.c" + ], + "functions": [ + "take", + "forwarded" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "reader-short", + "sources": [ + "reader-short-bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + }, + { + "name": "reassigned-release", + "sources": [ + "reassigned-release.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "mutual-cleanup", + "sources": [ + "mutual-cleanup.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "mutual-generic", + "sources": [ + "mutual-cleanup.c" + ], + "functions": [ + "destroy_even", + "destroy_odd" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "mutual-skipped", + "sources": [ + "mutual-skipped-bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "leak|footprint|cleanup|consum" + }, + { + "name": "callback-closed", + "sources": [ + "callback-allocate.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "callback-generic", + "sources": [ + "callback-allocate.c" + ], + "functions": [ + "create" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "callback-short", + "sources": [ + "callback-short-bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval|callback" + } + ] +} diff --git a/test/evaluation/rfc0029/mixed-allocation-ledger/double.c b/test/evaluation/rfc0029/mixed-allocation-ledger/double.c new file mode 100644 index 00000000..a0832781 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-allocation-ledger/double.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data = malloc(4); + if (!data) return 0; + data[0] = 0; + free(data); free(data); return 0; +} +int main(void) { + struct node child = {2,0,0}, root = {1,&child,0}; + unsigned char *data = make(&root); + free(data); return 0; +} diff --git a/test/evaluation/rfc0029/mixed-allocation-ledger/frozen-sha256.json b/test/evaluation/rfc0029/mixed-allocation-ledger/frozen-sha256.json new file mode 100644 index 00000000..60a4cbd2 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-allocation-ledger/frozen-sha256.json @@ -0,0 +1,7 @@ +{ + "double.c": "673e65c6ab71d26d8d753f2da73efa2f484769362b7b6830b412439cebf46fcc", + "lost.c": "10be5cee6a8de27996c197be1531156afe05757100b51b31fee45078184daf08", + "manifest.json": "17f80ea3f391b4fd7129f70fa2e921bfdaf314617e1b08e35f6a81568849f564", + "resize.c": "e71d16a982341cd049a05699573226e9a4882e2ff3ca319dfe1abc8cbc4560ea", + "return.c": "5d99445089b0780bc7d92e0096a74952db698ca6165bd5d8902775b4653e3127" +} diff --git a/test/evaluation/rfc0029/mixed-allocation-ledger/lost.c b/test/evaluation/rfc0029/mixed-allocation-ledger/lost.c new file mode 100644 index 00000000..76f3f240 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-allocation-ledger/lost.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data = malloc(4); + if (!data) return 0; + data[0] = 0; + return 0; +} +int main(void) { + struct node child = {2,0,0}, root = {1,&child,0}; + unsigned char *data = make(&root); + free(data); return 0; +} diff --git a/test/evaluation/rfc0029/mixed-allocation-ledger/manifest.json b/test/evaluation/rfc0029/mixed-allocation-ledger/manifest.json new file mode 100644 index 00000000..7a33c25d --- /dev/null +++ b/test/evaluation/rfc0029/mixed-allocation-ledger/manifest.json @@ -0,0 +1,67 @@ +{ + "version": 1, + "cases": [ + { + "name": "return", + "sources": [ + "return.c" + ], + "functions": [ + "make", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "lost", + "sources": [ + "lost.c" + ], + "functions": [ + "make", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "allocation|footprint|leak|release|freed|live" + }, + { + "name": "double", + "sources": [ + "double.c" + ], + "functions": [ + "make", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "allocation|footprint|leak|release|freed|live" + }, + { + "name": "resize", + "sources": [ + "resize.c" + ], + "functions": [ + "make", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/mixed-allocation-ledger/resize.c b/test/evaluation/rfc0029/mixed-allocation-ledger/resize.c new file mode 100644 index 00000000..64068b04 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-allocation-ledger/resize.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data = malloc(4); + if (!data) return 0; + data[0] = 0; + unsigned char *out = realloc(data, 2); if (!out) { free(data); return 0; } return out; +} +int main(void) { + struct node child = {2,0,0}, root = {1,&child,0}; + unsigned char *data = make(&root); + free(data); return 0; +} diff --git a/test/evaluation/rfc0029/mixed-allocation-ledger/return.c b/test/evaluation/rfc0029/mixed-allocation-ledger/return.c new file mode 100644 index 00000000..c9c656bd --- /dev/null +++ b/test/evaluation/rfc0029/mixed-allocation-ledger/return.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data = malloc(4); + if (!data) return 0; + data[0] = 0; + return data; +} +int main(void) { + struct node child = {2,0,0}, root = {1,&child,0}; + unsigned char *data = make(&root); + free(data); return 0; +} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/PROVENANCE.md b/test/evaluation/rfc0029/mixed-helper-frames/PROVENANCE.md new file mode 100644 index 00000000..3e448ec4 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/PROVENANCE.md @@ -0,0 +1,4 @@ +RFC 0029 mixed entry/fresh-helper frame regression, frozen against candidate91b. +The incoming singleton is extended with a verified fresh helper allocation. +A later store targets a separate entry record. Lost, released and disowned +children must retain their actual ownership failures. diff --git a/test/evaluation/rfc0029/mixed-helper-frames/api.h b/test/evaluation/rfc0029/mixed-helper-frames/api.h new file mode 100644 index 00000000..6daafc18 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/api.h @@ -0,0 +1,4 @@ +#include +struct node {struct node *next,*child;unsigned flags;}; +struct reader {unsigned position,capacity,extra;}; +struct node *make(void);void drop(struct node*);void build(struct node*,struct reader*);void step(struct reader*); diff --git a/test/evaluation/rfc0029/mixed-helper-frames/client.c b/test/evaluation/rfc0029/mixed-helper-frames/client.c new file mode 100644 index 00000000..20157265 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *p=make();if(!p)return 0;struct reader r={0,2,7};build(p,&r);drop(p);return 0;} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/direct.c b/test/evaluation/rfc0029/mixed-helper-frames/direct.c new file mode 100644 index 00000000..22b7e080 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/direct.c @@ -0,0 +1,2 @@ +#include "api.h" +void build(struct node *p,struct reader *r){struct node *q=make();if(!q)return;p->child=q;r->position=1;} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/disowned.c b/test/evaluation/rfc0029/mixed-helper-frames/disowned.c new file mode 100644 index 00000000..25a9b42b --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/disowned.c @@ -0,0 +1,2 @@ +#include "api.h" +void build(struct node *p,struct reader *r){struct node *q=make();if(!q)return;p->child=q;p->flags=1;r->position=1;} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/drop.c b/test/evaluation/rfc0029/mixed-helper-frames/drop.c new file mode 100644 index 00000000..60fb58b6 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/drop.c @@ -0,0 +1,2 @@ +#include "api.h" +void drop(struct node *p){while(p){struct node *n=p->next;if(!(p->flags&1))drop(p->child);free(p);p=n;}} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/freed.c b/test/evaluation/rfc0029/mixed-helper-frames/freed.c new file mode 100644 index 00000000..778b3307 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/freed.c @@ -0,0 +1,2 @@ +#include "api.h" +void build(struct node *p,struct reader *r){struct node *q=make();if(!q)return;p->child=q;drop(q);r->position=1;} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/frozen-sha256.json b/test/evaluation/rfc0029/mixed-helper-frames/frozen-sha256.json new file mode 100644 index 00000000..a48b0186 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/frozen-sha256.json @@ -0,0 +1,14 @@ +{ + "PROVENANCE.md": "65441e7cdf53194c87ac736f291f952cc32ee1639a332f35734dffec213fafa5", + "api.h": "d926fe7ba5f0828179e7687c77d160a67a751303beb4ffc729f287c07c92ab4c", + "client.c": "bed5f755034d2bd23e8f8639d1e7d0aa7f1029f6d6252d51e8799d12bf2c71f7", + "direct.c": "afdec70bd7f5045b15821d355d0620a0d8adc5b603833f7ea4fac80ad6f68933", + "disowned.c": "87224ed58cb85ba6ab264e97e208134177f8807923f4e9e5023f3b5d9b0c8e48", + "drop.c": "d3b77067ce2d555d17b8b87821b18e27140287892105374536853ff8864e841d", + "freed.c": "abe80f9c15ab93962443535835bb78e3dbfd09bb8fdf3ee5c56431358f6d5d68", + "helper.c": "cb7ba9f7be2db8599d5ff7a07b11c99cab044d53ed3ec5b82e9148f9519e994f", + "lost.c": "3185d7b7a8974b7a6ef449214aed2c5a43872950556eba88abd3661cd93f2df9", + "make.c": "7b4535a1b2f1a89641d25531f5e0113b435803f9d7075e466d0d257f8713af46", + "manifest.json": "d8f9e95246a2c04b796d184aa61f3fa3cd4032a1aa4085c0143fdf37db9386cf", + "step.c": "0955ad47eb6f350dbb972d46486ea73e1bb49bcf8fd359d597377f186d21f6d5" +} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/helper.c b/test/evaluation/rfc0029/mixed-helper-frames/helper.c new file mode 100644 index 00000000..7889ae7b --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/helper.c @@ -0,0 +1,2 @@ +#include "api.h" +void build(struct node *p,struct reader *r){struct node *q=make();if(!q)return;p->child=q;step(r);} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/lost.c b/test/evaluation/rfc0029/mixed-helper-frames/lost.c new file mode 100644 index 00000000..2ddd4117 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/lost.c @@ -0,0 +1,2 @@ +#include "api.h" +void build(struct node *p,struct reader *r){struct node *q=make();if(!q)return;p->child=q;p->child=0;r->position=1;} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/make.c b/test/evaluation/rfc0029/mixed-helper-frames/make.c new file mode 100644 index 00000000..48bfd482 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/make.c @@ -0,0 +1,2 @@ +#include "api.h" +struct node *make(void){return calloc(1,sizeof(struct node));} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/manifest.json b/test/evaluation/rfc0029/mixed-helper-frames/manifest.json new file mode 100644 index 00000000..af82e89a --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/manifest.json @@ -0,0 +1,100 @@ +{ + "version": 1, + "cases": [ + { + "name": "direct", + "sources": [ + "client.c", + "direct.c", + "make.c", + "step.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "helper", + "sources": [ + "client.c", + "helper.c", + "make.c", + "step.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "lost", + "sources": [ + "client.c", + "lost.c", + "make.c", + "step.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|footprint|live|release|allocation|leak" + }, + { + "name": "freed", + "sources": [ + "client.c", + "freed.c", + "make.c", + "step.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|footprint|live|release|allocation|leak" + }, + { + "name": "disowned", + "sources": [ + "client.c", + "disowned.c", + "make.c", + "step.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|footprint|live|release|allocation|leak" + } + ] +} diff --git a/test/evaluation/rfc0029/mixed-helper-frames/step.c b/test/evaluation/rfc0029/mixed-helper-frames/step.c new file mode 100644 index 00000000..9f66db12 --- /dev/null +++ b/test/evaluation/rfc0029/mixed-helper-frames/step.c @@ -0,0 +1,2 @@ +#include "api.h" +void step(struct reader *r){r->position=1;} diff --git a/test/evaluation/rfc0029/mutable-reader-construction/build.c b/test/evaluation/rfc0029/mutable-reader-construction/build.c new file mode 100644 index 00000000..c89ef493 --- /dev/null +++ b/test/evaluation/rfc0029/mutable-reader-construction/build.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader *r) { + if (!r->remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r->data[0]; + if(r->remaining>1){ + r->data++; r->remaining--; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,3}; struct node *p=build(&r); destroy(p); return 0; } diff --git a/test/evaluation/rfc0029/mutable-reader-construction/cycle.c b/test/evaluation/rfc0029/mutable-reader-construction/cycle.c new file mode 100644 index 00000000..e2c8821a --- /dev/null +++ b/test/evaluation/rfc0029/mutable-reader-construction/cycle.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader *r) { + if (!r->remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r->data[0]; + if(r->remaining>1){ + r->data++; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,3}; struct node *p=build(&r); destroy(p); return 0; } diff --git a/test/evaluation/rfc0029/mutable-reader-construction/double.c b/test/evaluation/rfc0029/mutable-reader-construction/double.c new file mode 100644 index 00000000..35b23924 --- /dev/null +++ b/test/evaluation/rfc0029/mutable-reader-construction/double.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader *r) { + if (!r->remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r->data[0]; + if(r->remaining>1){ + r->data++; r->remaining--; + p->next=build(r); + if(!p->next){free(p);free(p);return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,3}; struct node *p=build(&r); destroy(p); return 0; } diff --git a/test/evaluation/rfc0029/mutable-reader-construction/escape.c b/test/evaluation/rfc0029/mutable-reader-construction/escape.c new file mode 100644 index 00000000..cfeb986e --- /dev/null +++ b/test/evaluation/rfc0029/mutable-reader-construction/escape.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader *r) { + if (!r->remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r->data[0]; + if(r->remaining>1){ + r->data+=2; r->remaining--; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,3}; struct node *p=build(&r); destroy(p); return 0; } diff --git a/test/evaluation/rfc0029/mutable-reader-construction/frozen-sha256.json b/test/evaluation/rfc0029/mutable-reader-construction/frozen-sha256.json new file mode 100644 index 00000000..78763f8d --- /dev/null +++ b/test/evaluation/rfc0029/mutable-reader-construction/frozen-sha256.json @@ -0,0 +1,11 @@ +{ + "build.c": "85ca4d8d40b61e474f901a82347e00ee2b6822221847f7996509ad3b03b8c1e0", + "cycle.c": "8d5ebcfcb07e08c42a77e0c89b491830bf3ef205bc885b5c7f3fafe2ad04951a", + "double.c": "f5b7934650aeb4fdddd27c4f56f482a8657707c4784d8264f7ddb9e148f52db9", + "escape.c": "d01932a019d1a81b0e50378497a2c6c0b7ad771b3d2ea1b92c34007cc4de4fb2", + "leak.c": "8da973f344d998fa21c70472323554ea625921c0856e97d25e86bf2ac5299c54", + "manifest.json": "8d5cffb8aaa092762adcdddaace7d8e204c12629fd410b2788b2382baecdc4a6", + "provenance.md": "e40f849b7504e9fe3983da78ff1e2d768fd14f50b1856b16ec40e3a097dc01cc", + "short.c": "3d3c6b18f2d2d28f53ffe059e32525db2aadf82ce9ec9eb205415e2d31554bf3", + "uninitialized.c": "f59c180635cb6d45fb6d1c7c96ef1bd194f88e7a05332aa9edd9a3c1ca38f63c" +} diff --git a/test/evaluation/rfc0029/mutable-reader-construction/leak.c b/test/evaluation/rfc0029/mutable-reader-construction/leak.c new file mode 100644 index 00000000..8a33aefd --- /dev/null +++ b/test/evaluation/rfc0029/mutable-reader-construction/leak.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader *r) { + if (!r->remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r->data[0]; + if(r->remaining>1){ + r->data++; r->remaining--; + p->next=build(r); + if(!p->next){return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,3}; struct node *p=build(&r); destroy(p); return 0; } diff --git a/test/evaluation/rfc0029/mutable-reader-construction/manifest.json b/test/evaluation/rfc0029/mutable-reader-construction/manifest.json new file mode 100644 index 00000000..9d886a78 --- /dev/null +++ b/test/evaluation/rfc0029/mutable-reader-construction/manifest.json @@ -0,0 +1,116 @@ +{ + "version": 1, + "cases": [ + { + "name": "mutable-reader-construction-build", + "sources": [ + "build.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "mutable-reader-construction-short", + "sources": [ + "short.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + }, + { + "name": "mutable-reader-construction-uninitialized", + "sources": [ + "uninitialized.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + }, + { + "name": "mutable-reader-construction-cycle", + "sources": [ + "cycle.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|progress" + }, + { + "name": "mutable-reader-construction-leak", + "sources": [ + "leak.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|leak|cleanup|contract" + }, + { + "name": "mutable-reader-construction-double", + "sources": [ + "double.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "freed|release" + }, + { + "name": "mutable-reader-construction-escape", + "sources": [ + "escape.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|extent|bounds|recursive" + } + ] +} diff --git a/test/evaluation/rfc0029/mutable-reader-construction/provenance.md b/test/evaluation/rfc0029/mutable-reader-construction/provenance.md new file mode 100644 index 00000000..e014ed68 --- /dev/null +++ b/test/evaluation/rfc0029/mutable-reader-construction/provenance.md @@ -0,0 +1 @@ +Frozen before mutable pointer-reader construction inference on 2026-09-16. The positive consumes a runtime byte interval through a caller-owned reader and returns a fresh forest. This population requires no claim about the reader state after a returning call. Truncation, uninitialized input, nondecreasing recursion, interval escape, leaks and duplicate releases retain their original intended obligations. Candidate 22 supplies the baseline. It does not replace the mandatory unchanged cJSON workflows. diff --git a/test/evaluation/rfc0029/mutable-reader-construction/short.c b/test/evaluation/rfc0029/mutable-reader-construction/short.c new file mode 100644 index 00000000..cd5c8a96 --- /dev/null +++ b/test/evaluation/rfc0029/mutable-reader-construction/short.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader *r) { + if (!r->remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r->data[0]; + if(r->remaining>1){ + r->data++; r->remaining--; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,4}; struct node *p=build(&r); destroy(p); return 0; } diff --git a/test/evaluation/rfc0029/mutable-reader-construction/uninitialized.c b/test/evaluation/rfc0029/mutable-reader-construction/uninitialized.c new file mode 100644 index 00000000..9261fdda --- /dev/null +++ b/test/evaluation/rfc0029/mutable-reader-construction/uninitialized.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader *r) { + if (!r->remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r->data[0]; + if(r->remaining>1){ + r->data++; r->remaining--; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; +} +int main(void) { unsigned char data[3]; data[0]=1; struct reader r={42,data,3}; struct node *p=build(&r); destroy(p); return 0; } diff --git a/test/evaluation/rfc0029/mutual-cases/README.md b/test/evaluation/rfc0029/mutual-cases/README.md new file mode 100644 index 00000000..fc7d8160 --- /dev/null +++ b/test/evaluation/rfc0029/mutual-cases/README.md @@ -0,0 +1 @@ +Frozen before candidate29 case-limit refinement. A concrete mutual call chain can reach a complete base case without traversing a cycle. The separately selected generic SCC remains exhausted. Null input and an unresolved cycle retain their actual errors. Baseline candidate28. diff --git a/test/evaluation/rfc0029/mutual-cases/frozen-sha256.json b/test/evaluation/rfc0029/mutual-cases/frozen-sha256.json new file mode 100644 index 00000000..6d1b0c1d --- /dev/null +++ b/test/evaluation/rfc0029/mutual-cases/frozen-sha256.json @@ -0,0 +1,7 @@ +{ + "README.md": "a141647101dd930a267b2579b042f29b78dfd8382fddada8f91e904203ae6cf8", + "live.c": "f7c64e7dba9fc6fe32b70303ec439d55fe226051117c7b5dbe31027fa7bc15cb", + "manifest.json": "6cdfde73c101c98f40887dc20b2b391a7fd196839dfb0b32aae48f1ed73a5a77", + "null.c": "14eec3594c76d7e520cf780ee071653efde7b83cbb640789fd59323e3d9f58c7", + "unbounded.c": "736d5fe60435311294fc1f7a70de3bb8b7be204c22adff6040c7cb702ea30cf1" +} diff --git a/test/evaluation/rfc0029/mutual-cases/live.c b/test/evaluation/rfc0029/mutual-cases/live.c new file mode 100644 index 00000000..61ca3c67 --- /dev/null +++ b/test/evaluation/rfc0029/mutual-cases/live.c @@ -0,0 +1,13 @@ +struct state {unsigned depth,tag;char *data;}; +static int next(struct state *s); +static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth>=1000) return 0; + ++s->depth; + return next(s); +} +static int next(struct state *s) { + if (*s->data==1) return 100 / *s->data; + return walk(s); +} +int main(void){char data=1;struct state s={0,1,&data};return walk(&s);} diff --git a/test/evaluation/rfc0029/mutual-cases/manifest.json b/test/evaluation/rfc0029/mutual-cases/manifest.json new file mode 100644 index 00000000..7b667c3a --- /dev/null +++ b/test/evaluation/rfc0029/mutual-cases/manifest.json @@ -0,0 +1,66 @@ +{ + "version": 1, + "cases": [ + { + "name": "mutual-case-live", + "sources": [ + "live.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "mutual-case-null", + "sources": [ + "null.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "null|live|valid" + }, + { + "name": "mutual-case-unbounded", + "sources": [ + "unbounded.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "limit|recursive" + }, + { + "name": "mutual-case-generic", + "sources": [ + "live.c" + ], + "functions": [ + "walk", + "next" + ], + "expect": "rejected", + "reason": "limit|recursive", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/mutual-cases/null.c b/test/evaluation/rfc0029/mutual-cases/null.c new file mode 100644 index 00000000..7099cba2 --- /dev/null +++ b/test/evaluation/rfc0029/mutual-cases/null.c @@ -0,0 +1,13 @@ +struct state {unsigned depth,tag;char *data;}; +static int next(struct state *s); +static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth>=1000) return 0; + ++s->depth; + return next(s); +} +static int next(struct state *s) { + if (*s->data==1) return 100 / *s->data; + return walk(s); +} +int main(void){struct state s={0,1,0};return walk(&s);} diff --git a/test/evaluation/rfc0029/mutual-cases/unbounded.c b/test/evaluation/rfc0029/mutual-cases/unbounded.c new file mode 100644 index 00000000..0321adf7 --- /dev/null +++ b/test/evaluation/rfc0029/mutual-cases/unbounded.c @@ -0,0 +1,13 @@ +struct state {unsigned depth,tag;char *data;}; +static int next(struct state *s); +static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth>=1000) return 0; + ++s->depth; + return next(s); +} +static int next(struct state *s) { + if (*s->data==1) return 100 / *s->data; + return walk(s); +} +int main(void){char data=0;struct state s={0,1,&data};return walk(&s);} diff --git a/test/evaluation/rfc0029/mutual-cleanup.c b/test/evaluation/rfc0029/mutual-cleanup.c new file mode 100644 index 00000000..c94cec2a --- /dev/null +++ b/test/evaluation/rfc0029/mutual-cleanup.c @@ -0,0 +1,24 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +static void destroy_even(struct node *p); +static void destroy_odd(struct node *p) { + if (!p) return; + destroy_even(p->left); + destroy_even(p->right); + free(p); +} +static void destroy_even(struct node *p) { + if (!p) return; + destroy_odd(p->left); + destroy_odd(p->right); + free(p); +} +int main(void) { + struct node *a = calloc(1, sizeof *a); + if (!a) return 0; + struct node *b = calloc(1, sizeof *b); + if (!b) { free(a); return 0; } + a->left = b; + destroy_even(a); + return 0; +} diff --git a/test/evaluation/rfc0029/mutual-construction/cycle.c b/test/evaluation/rfc0029/mutual-construction/cycle.c new file mode 100644 index 00000000..35a6e921 --- /dev/null +++ b/test/evaluation/rfc0029/mutual-construction/cycle.c @@ -0,0 +1,17 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *even(const unsigned char *data, size_t n); +struct node *odd(const unsigned char *data, size_t n) { return even(data,n); } +struct node *even(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=odd(data,n); + if (!p->next) {free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=even(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/mutual-construction/forwarding.c b/test/evaluation/rfc0029/mutual-construction/forwarding.c new file mode 100644 index 00000000..1cc87eae --- /dev/null +++ b/test/evaluation/rfc0029/mutual-construction/forwarding.c @@ -0,0 +1,17 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *even(const unsigned char *data, size_t n); +struct node *odd(const unsigned char *data, size_t n) { return even(data,n); } +struct node *even(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=odd(data+1,n-1); + if (!p->next) {free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=even(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/mutual-construction/frozen-sha256.json b/test/evaluation/rfc0029/mutual-construction/frozen-sha256.json new file mode 100644 index 00000000..c76636a6 --- /dev/null +++ b/test/evaluation/rfc0029/mutual-construction/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "cycle.c": "f3eaaabc44e29ee2b88d8385959e461eb78ac05fc5957d173ee0c3a23984c987", + "forwarding.c": "0144cf7b2233b4089fa33d7744c6d89385feb9743071362ad3b13b9510a8c7f9", + "leak.c": "abeeae50eef54cb1415010e5033a36aa9dfa654d41739625882004777d592148", + "manifest.json": "7272ee19103f6b9b24d4f5f86f26a777a46fd48cf4b69185a1266ad3b3f719e1", + "provenance.md": "74a7efaf1ec99fb7dee13e5bd7e658a46750a64fda7309e0f7204da917c438bf", + "short.c": "34464e0be90b12b5c51cea00af0eca1636ae4f73140dde8d5ff4b7f565984b13" +} diff --git a/test/evaluation/rfc0029/mutual-construction/leak.c b/test/evaluation/rfc0029/mutual-construction/leak.c new file mode 100644 index 00000000..d8ae425e --- /dev/null +++ b/test/evaluation/rfc0029/mutual-construction/leak.c @@ -0,0 +1,17 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *even(const unsigned char *data, size_t n); +struct node *odd(const unsigned char *data, size_t n) { return even(data,n); } +struct node *even(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=odd(data+1,n-1); + if (!p->next) {return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=even(data,3);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/mutual-construction/manifest.json b/test/evaluation/rfc0029/mutual-construction/manifest.json new file mode 100644 index 00000000..f1b88cec --- /dev/null +++ b/test/evaluation/rfc0029/mutual-construction/manifest.json @@ -0,0 +1,72 @@ +{ + "version": 1, + "cases": [ + { + "name": "forwarding", + "sources": [ + "forwarding.c" + ], + "functions": [ + "even", + "odd", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "cycle", + "sources": [ + "cycle.c" + ], + "functions": [ + "even", + "odd", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|progress" + }, + { + "name": "leak", + "sources": [ + "leak.c" + ], + "functions": [ + "even", + "odd", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|leak|cleanup|contract" + }, + { + "name": "short", + "sources": [ + "short.c" + ], + "functions": [ + "even", + "odd", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + } + ] +} diff --git a/test/evaluation/rfc0029/mutual-construction/provenance.md b/test/evaluation/rfc0029/mutual-construction/provenance.md new file mode 100644 index 00000000..c6894dbf --- /dev/null +++ b/test/evaluation/rfc0029/mutual-construction/provenance.md @@ -0,0 +1 @@ +Independent mutual-construction probes frozen before their first analysis. The implementation already supports direct construction; this population audits atomic publication and forwarding between two constructors. The positive has one non-strict forwarding edge and one strict input-interval edge in its cycle. Negative expectations concern nondecreasing cycles, failure leaks, and short initialized input. Original construction and other populations remain unchanged. diff --git a/test/evaluation/rfc0029/mutual-construction/short.c b/test/evaluation/rfc0029/mutual-construction/short.c new file mode 100644 index 00000000..d925909d --- /dev/null +++ b/test/evaluation/rfc0029/mutual-construction/short.c @@ -0,0 +1,17 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *even(const unsigned char *data, size_t n); +struct node *odd(const unsigned char *data, size_t n) { return even(data,n); } +struct node *even(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=odd(data+1,n-1); + if (!p->next) {free(p);return 0;} + } + return p; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=even(data,4);destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/mutual-skipped-bad.c b/test/evaluation/rfc0029/mutual-skipped-bad.c new file mode 100644 index 00000000..ed8b7394 --- /dev/null +++ b/test/evaluation/rfc0029/mutual-skipped-bad.c @@ -0,0 +1,26 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +static void destroy_even(struct node *p); +static void destroy_odd(struct node *p) { + if (!p) return; + destroy_even(p->left); + free(p); +} +static void destroy_even(struct node *p) { + if (!p) return; + destroy_odd(p->left); + destroy_odd(p->right); + free(p); +} +int main(void) { + struct node *a = calloc(1, sizeof *a); + if (!a) return 0; + struct node *b = calloc(1, sizeof *b); + if (!b) { free(a); return 0; } + struct node *c = calloc(1, sizeof *c); + if (!c) { free(a); free(b); return 0; } + a->left = b; + b->right = c; + destroy_even(a); + return 0; +} diff --git a/test/evaluation/rfc0029/numeric-container-frames/PROVENANCE.md b/test/evaluation/rfc0029/numeric-container-frames/PROVENANCE.md new file mode 100644 index 00000000..fb16dc38 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate85a before framing actual automatic end-pointer output cells of modeled floating parsers. The incoming forest predates that local cell; the null-end variant has no output write. Released nodes, uninitialized input, end pointers published into owned payloads, and unknown calls remain negative. diff --git a/test/evaluation/rfc0029/numeric-container-frames/api.h b/test/evaluation/rfc0029/numeric-container-frames/api.h new file mode 100644 index 00000000..6ed81f79 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/api.h @@ -0,0 +1,5 @@ +#include +struct node {struct node *next,*child;char *text;unsigned value;}; +unsigned inspect(struct node *,const char *); +unsigned read_tree(const struct node *); +void drop(struct node *); diff --git a/test/evaluation/rfc0029/numeric-container-frames/frozen-sha256.json b/test/evaluation/rfc0029/numeric-container-frames/frozen-sha256.json new file mode 100644 index 00000000..a59ae2fa --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/frozen-sha256.json @@ -0,0 +1,20 @@ +{ + "PROVENANCE.md": "4dcc09cf47db9ea9f87175ad29f019722c3ebca353e9fbe899a75ea43fe036ff", + "api.h": "dbe6ae50d721e49fd115b4fca637ecfa04a70a79e7caf6cb690613d3b9efa669", + "local-end-inspect.c": "c9acbf673eba58cd6a7509cf770e02e7fe182e24a440a81d4abbaf6b81226ea4", + "local-end.c": "7571c257f74ca7e6649ca2b67cd66fca68d04e7a84a73d60cc823ad414663326", + "manifest.json": "8272c3791bc5b0c938a70d2471aec78d56ea7449782ff909222355a3ff1a6afe", + "null-end-inspect.c": "28dd1c914cfc9cbc23b22fc2ea56120cf7d39318021605353eb965b8164722a3", + "null-end.c": "7571c257f74ca7e6649ca2b67cd66fca68d04e7a84a73d60cc823ad414663326", + "owned-end-inspect.c": "0a250914032a9c6b82064f9966a7e69a4923dc440586ee5dd165d63c68b8b3d7", + "owned-end.c": "7571c257f74ca7e6649ca2b67cd66fca68d04e7a84a73d60cc823ad414663326", + "reader.c": "5cf5371331839a1cd9a5bbb72dcf79aa5aa6b72fd0779473c327e7a132666912", + "record-end-inspect.c": "8e6141ae5c66563cf33089a138aceb377c912630d3081413e8f20b1cd5f3c7df", + "record-end.c": "7571c257f74ca7e6649ca2b67cd66fca68d04e7a84a73d60cc823ad414663326", + "released-inspect.c": "0569d3d4f5c9ef6980edcc87888a318a929d34b46c208321a8c328ba9b3bbd52", + "released.c": "9aed5d9b7ab3c4d71edf4c705e2853aecd57410d461e85d25b452c438e7423f7", + "uninitialized-inspect.c": "c9acbf673eba58cd6a7509cf770e02e7fe182e24a440a81d4abbaf6b81226ea4", + "uninitialized.c": "42ce993e66fc34e4f411e4dbf2adf8b1957e671dc12dfc21a5a5bf189b9b0ee3", + "unknown-inspect.c": "3ac0a65d7036609c6ff8fa10259b8686efb5915284b4cc66a55e6665fd113a3f", + "unknown.c": "7571c257f74ca7e6649ca2b67cd66fca68d04e7a84a73d60cc823ad414663326" +} diff --git a/test/evaluation/rfc0029/numeric-container-frames/local-end-inspect.c b/test/evaluation/rfc0029/numeric-container-frames/local-end-inspect.c new file mode 100644 index 00000000..b75c7d17 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/local-end-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){char *end=0;struct {char *end;} local={0};(void)strtod(s,&end);return read_tree(n);} diff --git a/test/evaluation/rfc0029/numeric-container-frames/local-end.c b/test/evaluation/rfc0029/numeric-container-frames/local-end.c new file mode 100644 index 00000000..f5312b0a --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/local-end.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="12";unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/numeric-container-frames/manifest.json b/test/evaluation/rfc0029/numeric-container-frames/manifest.json new file mode 100644 index 00000000..ba94b8e5 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/manifest.json @@ -0,0 +1,124 @@ +{ + "version": 1, + "cases": [ + { + "name": "null-end", + "sources": [ + "null-end.c", + "null-end-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "local-end", + "sources": [ + "local-end.c", + "local-end-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "record-end", + "sources": [ + "record-end.c", + "record-end-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "released", + "sources": [ + "released.c", + "released-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|terminator|interval|live|released|unavailable|contract|container|call|footprint" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "uninitialized-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|terminator|interval|live|released|unavailable|contract|container|call|footprint" + }, + { + "name": "owned-end", + "sources": [ + "owned-end.c", + "owned-end-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|terminator|interval|live|released|unavailable|contract|container|call|footprint" + }, + { + "name": "unknown", + "sources": [ + "unknown.c", + "unknown-inspect.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|terminator|interval|live|released|unavailable|contract|container|call|footprint" + } + ] +} diff --git a/test/evaluation/rfc0029/numeric-container-frames/null-end-inspect.c b/test/evaluation/rfc0029/numeric-container-frames/null-end-inspect.c new file mode 100644 index 00000000..b49ba135 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/null-end-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){char *end=0;struct {char *end;} local={0};(void)strtod(s,0);return read_tree(n);} diff --git a/test/evaluation/rfc0029/numeric-container-frames/null-end.c b/test/evaluation/rfc0029/numeric-container-frames/null-end.c new file mode 100644 index 00000000..f5312b0a --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/null-end.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="12";unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/numeric-container-frames/owned-end-inspect.c b/test/evaluation/rfc0029/numeric-container-frames/owned-end-inspect.c new file mode 100644 index 00000000..61ace69d --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/owned-end-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){char *end=0;struct {char *end;} local={0};(void)strtod(s,&n->text);return read_tree(n);} diff --git a/test/evaluation/rfc0029/numeric-container-frames/owned-end.c b/test/evaluation/rfc0029/numeric-container-frames/owned-end.c new file mode 100644 index 00000000..f5312b0a --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/owned-end.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="12";unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/numeric-container-frames/reader.c b/test/evaluation/rfc0029/numeric-container-frames/reader.c new file mode 100644 index 00000000..2681673c --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/reader.c @@ -0,0 +1,3 @@ +#include "api.h" +unsigned read_tree(const struct node *n){return n?n->value+read_tree(n->child)+read_tree(n->next):0;} +void drop(struct node *n){while(n){struct node *next=n->next;drop(n->child);free(n->text);free(n);n=next;}} diff --git a/test/evaluation/rfc0029/numeric-container-frames/record-end-inspect.c b/test/evaluation/rfc0029/numeric-container-frames/record-end-inspect.c new file mode 100644 index 00000000..62fa3202 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/record-end-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){char *end=0;struct {char *end;} local={0};(void)strtod(s,&local.end);return read_tree(n);} diff --git a/test/evaluation/rfc0029/numeric-container-frames/record-end.c b/test/evaluation/rfc0029/numeric-container-frames/record-end.c new file mode 100644 index 00000000..f5312b0a --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/record-end.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="12";unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/numeric-container-frames/released-inspect.c b/test/evaluation/rfc0029/numeric-container-frames/released-inspect.c new file mode 100644 index 00000000..b9b67aa0 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/released-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){char *end=0;struct {char *end;} local={0};free(n);(void)strtod(s,&end);return read_tree(n);} diff --git a/test/evaluation/rfc0029/numeric-container-frames/released.c b/test/evaluation/rfc0029/numeric-container-frames/released.c new file mode 100644 index 00000000..5a9b1e4f --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/released.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="12";unsigned r=inspect(n,s);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/numeric-container-frames/uninitialized-inspect.c b/test/evaluation/rfc0029/numeric-container-frames/uninitialized-inspect.c new file mode 100644 index 00000000..b75c7d17 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/uninitialized-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){char *end=0;struct {char *end;} local={0};(void)strtod(s,&end);return read_tree(n);} diff --git a/test/evaluation/rfc0029/numeric-container-frames/uninitialized.c b/test/evaluation/rfc0029/numeric-container-frames/uninitialized.c new file mode 100644 index 00000000..69119138 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/uninitialized.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;char s[3];s[0]='1';unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/numeric-container-frames/unknown-inspect.c b/test/evaluation/rfc0029/numeric-container-frames/unknown-inspect.c new file mode 100644 index 00000000..5e16a3f1 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/unknown-inspect.c @@ -0,0 +1,3 @@ +#include "api.h" +void unknown(struct node *); +unsigned inspect(struct node *n,const char *s){char *end=0;struct {char *end;} local={0};(void)strtod(s,&end);unknown(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/numeric-container-frames/unknown.c b/test/evaluation/rfc0029/numeric-container-frames/unknown.c new file mode 100644 index 00000000..f5312b0a --- /dev/null +++ b/test/evaluation/rfc0029/numeric-container-frames/unknown.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;const char s[]="12";unsigned r=inspect(n,s);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/numeric-input/end.c b/test/evaluation/rfc0029/numeric-input/end.c new file mode 100644 index 00000000..7c0b44a0 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-input/end.c @@ -0,0 +1,2 @@ +#include +int main(void){char text[]="12.5x";char *end=0;double x=strtod(text,&end);(void)x;return *end==0;} diff --git a/test/evaluation/rfc0029/numeric-input/forged.c b/test/evaluation/rfc0029/numeric-input/forged.c new file mode 100644 index 00000000..4c6b20f2 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-input/forged.c @@ -0,0 +1,2 @@ +double strtod(const char *text,char **end){*end=(char*)text+20;return 0;} +int main(void){char text[]="12";char *end=0;(void)strtod(text,&end);return *end;} diff --git a/test/evaluation/rfc0029/numeric-input/frozen-sha256.json b/test/evaluation/rfc0029/numeric-input/frozen-sha256.json new file mode 100644 index 00000000..4769b385 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-input/frozen-sha256.json @@ -0,0 +1,11 @@ +{ + "end.c": "2760b5ee87cc97e3cd44625085835f68a23b204aa07b4f0b59b3e67fc734b212", + "forged.c": "f6ac6c6015fa902ad302f9e19473eafa636eca2440b04f2b0ae27e78f100808f", + "manifest.json": "fc4db9a9a07871aaa04987c4beef3c1c00223c7865d41e0eb288aec6876ad407", + "null-end.c": "13e89555120db59735e8ebae8c20af5fde07aeda0322ce3e45750f079a68f688", + "past-end.c": "ec861b32f34b172c6bd0d6b1b3a09dd41179480d126c5fe8e35ae8553f37e429", + "provenance.md": "f7590cd1a01914bab0d0bb415a81e95d5f05b602651dc56116d89f2a828649f3", + "readonly-slot.c": "efe45dba375f01d66b15460393a735d54f11f2c63551c14c72a8687a063f5e89", + "uninitialized.c": "c66f269717931ae6efd4c1f8da928b8996eb37b9b9c9eb421818709112d9d4e1", + "unterminated.c": "f373190c3d4f6bf35bad1307d0f93b67bf63e79620f79f98837c58d9a485b1c4" +} diff --git a/test/evaluation/rfc0029/numeric-input/manifest.json b/test/evaluation/rfc0029/numeric-input/manifest.json new file mode 100644 index 00000000..c02a27f4 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-input/manifest.json @@ -0,0 +1,108 @@ +{ + "version": 1, + "cases": [ + { + "name": "numeric-input-end", + "sources": [ + "end.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "numeric-input-null-end", + "sources": [ + "null-end.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "numeric-input-unterminated", + "sources": [ + "unterminated.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ|terminat" + }, + { + "name": "numeric-input-uninitialized", + "sources": [ + "uninitialized.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ|terminat" + }, + { + "name": "numeric-input-readonly-slot", + "sources": [ + "readonly-slot.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "read-only|writ" + }, + { + "name": "numeric-input-past-end", + "sources": [ + "past-end.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|interval|extent|formed pointer" + }, + { + "name": "numeric-input-forged", + "sources": [ + "forged.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|interval|extent|formed pointer" + } + ] +} diff --git a/test/evaluation/rfc0029/numeric-input/null-end.c b/test/evaluation/rfc0029/numeric-input/null-end.c new file mode 100644 index 00000000..ce1b247b --- /dev/null +++ b/test/evaluation/rfc0029/numeric-input/null-end.c @@ -0,0 +1,2 @@ +#include +int main(void){char text[]="12.5";(void)strtod(text,0);return 0;} diff --git a/test/evaluation/rfc0029/numeric-input/past-end.c b/test/evaluation/rfc0029/numeric-input/past-end.c new file mode 100644 index 00000000..9e4b342a --- /dev/null +++ b/test/evaluation/rfc0029/numeric-input/past-end.c @@ -0,0 +1,2 @@ +#include +int main(void){char text[]="12";char *end=0;(void)strtod(text,&end);return end[3];} diff --git a/test/evaluation/rfc0029/numeric-input/provenance.md b/test/evaluation/rfc0029/numeric-input/provenance.md new file mode 100644 index 00000000..602e14d9 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-input/provenance.md @@ -0,0 +1 @@ +Frozen before checked strtod/strtof/strtold input/end-pointer modeling on 2026-09-16. The positive retains a pointer into a terminated initialized input; a null end-pointer slot is permitted. Missing terminators, uninitialized bytes, a read-only destination, out-of-range end-pointer use and a user-defined impostor must not acquire a libc certificate. Candidate 23 supplies the pre-model observation. No floating return value, finiteness or range is asserted. diff --git a/test/evaluation/rfc0029/numeric-input/readonly-slot.c b/test/evaluation/rfc0029/numeric-input/readonly-slot.c new file mode 100644 index 00000000..ddcd0337 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-input/readonly-slot.c @@ -0,0 +1,2 @@ +#include +int main(void){char text[]="1";char *const end=0;(void)strtod(text,(char**)&end);return 0;} diff --git a/test/evaluation/rfc0029/numeric-input/uninitialized.c b/test/evaluation/rfc0029/numeric-input/uninitialized.c new file mode 100644 index 00000000..b50ef781 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-input/uninitialized.c @@ -0,0 +1,2 @@ +#include +int main(void){char text[3];text[0]='1';text[2]=0;(void)strtod(text,0);return 0;} diff --git a/test/evaluation/rfc0029/numeric-input/unterminated.c b/test/evaluation/rfc0029/numeric-input/unterminated.c new file mode 100644 index 00000000..6fcdd2ab --- /dev/null +++ b/test/evaluation/rfc0029/numeric-input/unterminated.c @@ -0,0 +1,2 @@ +#include +int main(void){char text[2]={'1','2'};(void)strtod(text,0);return 0;} diff --git a/test/evaluation/rfc0029/numeric-scan-locals/README.md b/test/evaluation/rfc0029/numeric-scan-locals/README.md new file mode 100644 index 00000000..7b83108e --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scan-locals/README.md @@ -0,0 +1 @@ +Frozen before candidate 62. Unexposed local pointer assignments cannot modify incoming byte contents; writes through an actual input alias must invalidate the numeric alphabet. diff --git a/test/evaluation/rfc0029/numeric-scan-locals/assignment.c b/test/evaluation/rfc0029/numeric-scan-locals/assignment.c new file mode 100644 index 00000000..5e7ac8bc --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scan-locals/assignment.c @@ -0,0 +1,23 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + char *out; + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="111";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-scan-locals/frozen-sha256.json b/test/evaluation/rfc0029/numeric-scan-locals/frozen-sha256.json new file mode 100644 index 00000000..11d2a9f4 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scan-locals/frozen-sha256.json @@ -0,0 +1,6 @@ +{ + "assignment.c": "b155c4ae1cbed4d3e4188b6ffddb108406780f2cbbda73c813b9374bae8345cd", + "manifest.json": "98a9931f890ca726d690a853335134029090ba4c26f205807d591eae66fbede4", + "reset.c": "edab17c817de7da8a7071687cdd78bcf74b39379393d6318eb7f02d16e55b9b7", + "source-write.c": "c829865de4f71bdf9187c808408d655f4484c5529abdc8f97711d6e2f5d46ae3" +} diff --git a/test/evaluation/rfc0029/numeric-scan-locals/manifest.json b/test/evaluation/rfc0029/numeric-scan-locals/manifest.json new file mode 100644 index 00000000..4f366131 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scan-locals/manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "assignment", + "sources": [ + "assignment.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "reset", + "sources": [ + "reset.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "source-write", + "sources": [ + "source-write.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|terminat" + } + ] +} diff --git a/test/evaluation/rfc0029/numeric-scan-locals/reset.c b/test/evaluation/rfc0029/numeric-scan-locals/reset.c new file mode 100644 index 00000000..5cdb6ea5 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scan-locals/reset.c @@ -0,0 +1,24 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + char *out; + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + i=0; + out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="111";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-scan-locals/source-write.c b/test/evaluation/rfc0029/numeric-scan-locals/source-write.c new file mode 100644 index 00000000..c98c37b6 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scan-locals/source-write.c @@ -0,0 +1,24 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + char *out; + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + out=malloc(count+1);if(!out)return 0; + if(r->capacity>=3){unsigned char *p=(unsigned char*)r->data;p[0]='n';p[1]='a';p[2]='n';} + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){unsigned char input[]="111";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-scans/README.md b/test/evaluation/rfc0029/numeric-scans/README.md new file mode 100644 index 00000000..047c0da7 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scans/README.md @@ -0,0 +1 @@ +Frozen before candidate 60. Counted numeric scans, copying and decimal replacement must retain actual byte membership; admitting NaN bytes, mutation and invalid storage do not. diff --git a/test/evaluation/rfc0029/numeric-scans/default.c b/test/evaluation/rfc0029/numeric-scans/default.c new file mode 100644 index 00000000..d9bb32e1 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scans/default.c @@ -0,0 +1,22 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:++count;break; + } + } +done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="nan";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-scans/frozen-sha256.json b/test/evaluation/rfc0029/numeric-scans/frozen-sha256.json new file mode 100644 index 00000000..e95b21a4 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scans/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "default.c": "2c854f3eec4f02d2f99bd633fbbed730446b62d0ddd68d1c71cdb7e97f68c736", + "good.c": "5e1e665f040afb06c5d122f9352f1522604cf5e6360b21434fa2eeb2ad4b7bc8", + "manifest.json": "253da221ee8cfb886797d423a4eb38282c69936adb457cc063ecebedb8d39b9a", + "overwrite.c": "34367bd2d2b7a127bac39299d6e8c6ea76b85d81d5dacfb32985865b1d02fdc7", + "permissive.c": "d7d8f5f78d383ea64969cad51f8e986dd7fce01112c3fe61df9defc2420ad497", + "reordered.c": "6f27d29d6666d0e673aea396b32b25647da36cb768f3a11cefbf096406aaa21e", + "short.c": "c55b8b7f576bf865d9ed55adc03f36ecba31346f6cba3598f0b9f50363e56141", + "uninitialized.c": "88e4d77422e4ce973fe8f511c435ec973d0291a8fa8b424f8dec2446e98f3ab0" +} diff --git a/test/evaluation/rfc0029/numeric-scans/good.c b/test/evaluation/rfc0029/numeric-scans/good.c new file mode 100644 index 00000000..4a911be9 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scans/good.c @@ -0,0 +1,22 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="111";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-scans/manifest.json b/test/evaluation/rfc0029/numeric-scans/manifest.json new file mode 100644 index 00000000..e34beff7 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scans/manifest.json @@ -0,0 +1,110 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "good.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "reordered", + "sources": [ + "reordered.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "permissive", + "sources": [ + "permissive.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|bound|interval|extent" + }, + { + "name": "default", + "sources": [ + "default.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|bound|interval|extent" + }, + { + "name": "overwrite", + "sources": [ + "overwrite.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|bound|interval|extent" + }, + { + "name": "short", + "sources": [ + "short.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|bound|interval|extent" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|bound|interval|extent" + } + ] +} diff --git a/test/evaluation/rfc0029/numeric-scans/overwrite.c b/test/evaluation/rfc0029/numeric-scans/overwrite.c new file mode 100644 index 00000000..f8a37c5f --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scans/overwrite.c @@ -0,0 +1,23 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=3){out[0]='n';out[1]='a';out[2]='n';} + double value=strtod(out,0);free(out); + if(value>=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="111";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-scans/permissive.c b/test/evaluation/rfc0029/numeric-scans/permissive.c new file mode 100644 index 00000000..3b786f36 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scans/permissive.c @@ -0,0 +1,22 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case 'n':case 'a':case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="nan";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-scans/reordered.c b/test/evaluation/rfc0029/numeric-scans/reordered.c new file mode 100644 index 00000000..61c56e7c --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scans/reordered.c @@ -0,0 +1,22 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t extra,capacity,position; }; +int scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="111";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-scans/short.c b/test/evaluation/rfc0029/numeric-scans/short.c new file mode 100644 index 00000000..15ed8e7f --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scans/short.c @@ -0,0 +1,22 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="111";struct reader r={input,0,sizeof input+2,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-scans/uninitialized.c b/test/evaluation/rfc0029/numeric-scans/uninitialized.c new file mode 100644 index 00000000..cca63527 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-scans/uninitialized.c @@ -0,0 +1,22 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){unsigned char input[4];input[0]='1';struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-short-circuit/README.md b/test/evaluation/rfc0029/numeric-short-circuit/README.md new file mode 100644 index 00000000..b6f09b7f --- /dev/null +++ b/test/evaluation/rfc0029/numeric-short-circuit/README.md @@ -0,0 +1 @@ +Frozen before candidate 63. Candidate 62a rejects both equivalent short-circuit positives and all three unsafe counterparts. SHA256SUMS retains the original pre-change inventory; frozen-sha256.json is its exact JSON translation for the workflow runner. The condition operands may preserve the already-visited prefix but may not assume a strict body bound. diff --git a/test/evaluation/rfc0029/numeric-short-circuit/SHA256SUMS b/test/evaluation/rfc0029/numeric-short-circuit/SHA256SUMS new file mode 100644 index 00000000..0abc8f74 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-short-circuit/SHA256SUMS @@ -0,0 +1,6 @@ +4ac1f95649a4828197ea57f363fdb87ae010ca0472bdca30f47e00712f7f1d2f bypass.c +25e4f27e2dbb461621075cd660dc9a51da15c81a2e76eb2577aa198a3e1a806f left.c +bc100705b7c63d004fcf89834d514cbc96415d92b7e1aa1b7390e4ef1ab13d23 manifest.json +67afb8c017c3d9adfa28d10f5ec38f47ff7eae2173f699a7cdc25bbff7492707 mutated.c +c7996e87a1fbcb676a6a567215b1225cf1c001bec4ee127216e329499be09371 permissive.c +405a6259471b7c1090c0d76e6cec917a2d1d2bea3ae89763bed8b92c3e55c0b2 right.c diff --git a/test/evaluation/rfc0029/numeric-short-circuit/bypass.c b/test/evaluation/rfc0029/numeric-short-circuit/bypass.c new file mode 100644 index 00000000..a9c06283 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-short-circuit/bypass.c @@ -0,0 +1,23 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + char *out; + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r && r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:++count;break; + } + } +done:; + out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="nan";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-short-circuit/frozen-sha256.json b/test/evaluation/rfc0029/numeric-short-circuit/frozen-sha256.json new file mode 100644 index 00000000..d437500f --- /dev/null +++ b/test/evaluation/rfc0029/numeric-short-circuit/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "bypass.c": "4ac1f95649a4828197ea57f363fdb87ae010ca0472bdca30f47e00712f7f1d2f", + "left.c": "25e4f27e2dbb461621075cd660dc9a51da15c81a2e76eb2577aa198a3e1a806f", + "manifest.json": "bc100705b7c63d004fcf89834d514cbc96415d92b7e1aa1b7390e4ef1ab13d23", + "mutated.c": "67afb8c017c3d9adfa28d10f5ec38f47ff7eae2173f699a7cdc25bbff7492707", + "permissive.c": "c7996e87a1fbcb676a6a567215b1225cf1c001bec4ee127216e329499be09371", + "right.c": "405a6259471b7c1090c0d76e6cec917a2d1d2bea3ae89763bed8b92c3e55c0b2" +} diff --git a/test/evaluation/rfc0029/numeric-short-circuit/left.c b/test/evaluation/rfc0029/numeric-short-circuit/left.c new file mode 100644 index 00000000..64f66c2f --- /dev/null +++ b/test/evaluation/rfc0029/numeric-short-circuit/left.c @@ -0,0 +1,23 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + char *out; + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r && r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="111";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-short-circuit/manifest.json b/test/evaluation/rfc0029/numeric-short-circuit/manifest.json new file mode 100644 index 00000000..28af4c01 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-short-circuit/manifest.json @@ -0,0 +1,80 @@ +{ + "version": 1, + "cases": [ + { + "name": "left", + "sources": [ + "left.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "right", + "sources": [ + "right.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "permissive", + "sources": [ + "permissive.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|bound|extent" + }, + { + "name": "mutated", + "sources": [ + "mutated.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|bound|extent" + }, + { + "name": "bypass", + "sources": [ + "bypass.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|bound|extent" + } + ] +} diff --git a/test/evaluation/rfc0029/numeric-short-circuit/mutated.c b/test/evaluation/rfc0029/numeric-short-circuit/mutated.c new file mode 100644 index 00000000..de558d29 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-short-circuit/mutated.c @@ -0,0 +1,23 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + char *out; + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r && r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="111";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-short-circuit/permissive.c b/test/evaluation/rfc0029/numeric-short-circuit/permissive.c new file mode 100644 index 00000000..df4aa77f --- /dev/null +++ b/test/evaluation/rfc0029/numeric-short-circuit/permissive.c @@ -0,0 +1,23 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + char *out; + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r && r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case 'n':case 'a':case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="nan";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-short-circuit/right.c b/test/evaluation/rfc0029/numeric-short-circuit/right.c new file mode 100644 index 00000000..2d8ba661 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-short-circuit/right.c @@ -0,0 +1,23 @@ +#include +#include +#include +struct reader { const unsigned char *data; size_t position,capacity,extra; }; +int scan(struct reader *r) { + char *out; + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity && r;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done; + } + } +done:; + out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + if(decimal)for(i=0;i=INT_MAX)return INT_MAX;else if(value<=INT_MIN)return INT_MIN;else return (int)value; +} +int main(void){const unsigned char input[]="111";struct reader r={input,0,sizeof input,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/numeric-text/README.md b/test/evaluation/rfc0029/numeric-text/README.md new file mode 100644 index 00000000..d42c77a4 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-text/README.md @@ -0,0 +1 @@ +Frozen before candidate 59. Numeric byte contents justify exclusion of NaN, independently of finite integer bounds; unsafe content and mutation remain rejected. diff --git a/test/evaluation/rfc0029/numeric-text/changed.c b/test/evaluation/rfc0029/numeric-text/changed.c new file mode 100644 index 00000000..7cb0f6c5 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-text/changed.c @@ -0,0 +1,3 @@ +#include +#include +int main(void){const char text[]="12";double x=strtod(text,0);if(x >= INT_MAX)return INT_MAX;else if(x <= (double)INT_MIN)return INT_MIN;else {x=__builtin_nan("");return (int)x;}} diff --git a/test/evaluation/rfc0029/numeric-text/frozen-sha256.json b/test/evaluation/rfc0029/numeric-text/frozen-sha256.json new file mode 100644 index 00000000..6a4dc641 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-text/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "changed.c": "6919b7e2912d55badbbaf510fd7c934e3f2df2e3c8c347b35899cd9c21327974", + "literal.c": "1bedec947f4355eb2911a720c6b7c0bc50e950273d18010d274b0ee6fd7874f5", + "manifest.json": "4881e5aaecba428040d65e1d48d3686817de4611ccb020acd83255f0866d6cf9", + "nan.c": "1d5e8b280c1137462192a037fced41c9928ee3e838e72db880e70609d470feaf", + "overwrite.c": "fb812927701d8eb54ea95040dcef7efee3cf6f773d5ddc1b328125321c8fcc27", + "stores.c": "8737a1b3854fc3736b564829d9d00f39feb3f34eb4d9f5aa95b2fae11de9e552", + "uninitialized.c": "89a0b04dd9702bb0a67a0438b7d3ffd934c46d6e780411b7f09ec16f844be75c", + "wide.c": "275b4550b0ec1d58f4cc34fbcc84bf141773bd45a9b63b88fbc935c0bc5e5a25" +} diff --git a/test/evaluation/rfc0029/numeric-text/literal.c b/test/evaluation/rfc0029/numeric-text/literal.c new file mode 100644 index 00000000..200843ac --- /dev/null +++ b/test/evaluation/rfc0029/numeric-text/literal.c @@ -0,0 +1,3 @@ +#include +#include +int main(void){const char text[]="12.5e-2";double x=strtod(text,0);if(x >= INT_MAX)return INT_MAX;else if(x <= (double)INT_MIN)return INT_MIN;else return (int)x;} diff --git a/test/evaluation/rfc0029/numeric-text/manifest.json b/test/evaluation/rfc0029/numeric-text/manifest.json new file mode 100644 index 00000000..88ff6858 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-text/manifest.json @@ -0,0 +1,110 @@ +{ + "version": 1, + "cases": [ + { + "name": "stores", + "sources": [ + "stores.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "literal", + "sources": [ + "literal.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "nan", + "sources": [ + "nan.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|terminat" + }, + { + "name": "overwrite", + "sources": [ + "overwrite.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|terminat" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|terminat" + }, + { + "name": "changed", + "sources": [ + "changed.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|terminat" + }, + { + "name": "wide", + "sources": [ + "wide.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "unsupported checked C construct|initializ|terminat" + } + ] +} diff --git a/test/evaluation/rfc0029/numeric-text/nan.c b/test/evaluation/rfc0029/numeric-text/nan.c new file mode 100644 index 00000000..eb3884fa --- /dev/null +++ b/test/evaluation/rfc0029/numeric-text/nan.c @@ -0,0 +1,3 @@ +#include +#include +int main(void){const char text[]="nan";double x=strtod(text,0);if(x >= INT_MAX)return INT_MAX;else if(x <= (double)INT_MIN)return INT_MIN;else return (int)x;} diff --git a/test/evaluation/rfc0029/numeric-text/overwrite.c b/test/evaluation/rfc0029/numeric-text/overwrite.c new file mode 100644 index 00000000..65ee45b4 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-text/overwrite.c @@ -0,0 +1,3 @@ +#include +#include +int main(void){char text[]="123";text[0]='n';text[1]='a';text[2]='n';double x=strtod(text,0);if(x >= INT_MAX)return INT_MAX;else if(x <= (double)INT_MIN)return INT_MIN;else return (int)x;} diff --git a/test/evaluation/rfc0029/numeric-text/stores.c b/test/evaluation/rfc0029/numeric-text/stores.c new file mode 100644 index 00000000..399e6585 --- /dev/null +++ b/test/evaluation/rfc0029/numeric-text/stores.c @@ -0,0 +1,4 @@ +#include +#include +int convert(unsigned digit){if(digit>9)return 0;char text[2];text[0]=(char)('0'+digit);text[1]=0;double x=strtod(text,0);if(x >= INT_MAX)return INT_MAX;else if(x <= (double)INT_MIN)return INT_MIN;else return (int)x;} +int main(void){return convert(3);} diff --git a/test/evaluation/rfc0029/numeric-text/uninitialized.c b/test/evaluation/rfc0029/numeric-text/uninitialized.c new file mode 100644 index 00000000..24a89dba --- /dev/null +++ b/test/evaluation/rfc0029/numeric-text/uninitialized.c @@ -0,0 +1,3 @@ +#include +#include +int main(void){char text[4];text[0]='1';text[3]=0;double x=strtod(text,0);if(x >= INT_MAX)return INT_MAX;else if(x <= (double)INT_MIN)return INT_MIN;else return (int)x;} diff --git a/test/evaluation/rfc0029/numeric-text/wide.c b/test/evaluation/rfc0029/numeric-text/wide.c new file mode 100644 index 00000000..1b8580cb --- /dev/null +++ b/test/evaluation/rfc0029/numeric-text/wide.c @@ -0,0 +1,4 @@ +#include +#include +long long convert(void){const char text[]="9223372036854775808";double x=strtod(text,0);if(x>9223372036854775808.0)return 0;else if(x < -9223372036854775808.0)return 0;else return (long long)x;} +int main(void){return convert()!=0;} diff --git a/test/evaluation/rfc0029/offsets/child.c b/test/evaluation/rfc0029/offsets/child.c new file mode 100644 index 00000000..d006dae0 --- /dev/null +++ b/test/evaluation/rfc0029/offsets/child.c @@ -0,0 +1,13 @@ +#include +struct tree { struct tree *left, *right; }; +static void destroy(struct tree *p) { + if (!p) return; + if (p->left) destroy(p->left + 1); + destroy(p->right); + free(p); +} +int main(void) { + struct tree *p=calloc(1,sizeof *p); if(!p)return 0; + struct tree *q=calloc(1,sizeof *q); if(!q){free(p);return 0;} + p->left=q; destroy(p); return 0; +} diff --git a/test/evaluation/rfc0029/offsets/forward.c b/test/evaluation/rfc0029/offsets/forward.c new file mode 100644 index 00000000..a26fa008 --- /dev/null +++ b/test/evaluation/rfc0029/offsets/forward.c @@ -0,0 +1,12 @@ +#include +struct tree { struct tree *left, *right; }; +static void odd(struct tree *p); +static void even(struct tree *p) { + if(!p) return; + odd(p->left); odd(p->right); free(p); +} +static void odd(struct tree *p) { even(p+1); } +int main(void) { + struct tree *p=calloc(1,sizeof *p); if(!p)return 0; + odd(p); return 0; +} diff --git a/test/evaluation/rfc0029/offsets/frozen-sha256.json b/test/evaluation/rfc0029/offsets/frozen-sha256.json new file mode 100644 index 00000000..63e3dc98 --- /dev/null +++ b/test/evaluation/rfc0029/offsets/frozen-sha256.json @@ -0,0 +1,7 @@ +{ + "child.c": "e600361c81bf4b7501e9f0641d1cc17d7772e7880bc51a2bcd56b10d399a6481", + "forward.c": "63d7058b456aad6347edca65c4f3d5ac6678789c8efdcba66833c28b1bb84557", + "manifest.json": "86a2307ed82945256da5dbc5477f3d95a3c3905e22f8d54ab6049873bd70839c", + "provenance.md": "1627db9fe650c8ff5e6cebc811bda2d2377a06466ded8cdcfe9f8af628d0af58", + "zero.c": "78fa36953b7dc1a5e26d4bd4fbc34cf5a49a98326687cebcb722cc2b6fc90c0c" +} diff --git a/test/evaluation/rfc0029/offsets/manifest.json b/test/evaluation/rfc0029/offsets/manifest.json new file mode 100644 index 00000000..93cbdf3a --- /dev/null +++ b/test/evaluation/rfc0029/offsets/manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "recursive-offset-forward", + "sources": [ + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|recursive|footprint|release|fit|live" + }, + { + "name": "recursive-offset-child", + "sources": [ + "child.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|recursive|footprint|release|fit|live" + }, + { + "name": "recursive-offset-zero", + "sources": [ + "zero.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + } + ] +} diff --git a/test/evaluation/rfc0029/offsets/provenance.md b/test/evaluation/rfc0029/offsets/provenance.md new file mode 100644 index 00000000..8c08d071 --- /dev/null +++ b/test/evaluation/rfc0029/offsets/provenance.md @@ -0,0 +1,9 @@ +# Recursive argument offsets + +Frozen after candidate 17 and before the offset-origin correction. A proof +boundary review found that resolvePointerValue intentionally strips arithmetic +for ordinary ownership identity. It cannot establish the actual argument of an +induction hypothesis. Candidate 17 incorrectly accepts forward.c. The negative +cases require exact-node/proper-child evidence; forming an allowed one-past +pointer cannot justify applying a node cleanup contract to it. zero.c preserves +an equivalent zero-offset spelling. diff --git a/test/evaluation/rfc0029/offsets/zero.c b/test/evaluation/rfc0029/offsets/zero.c new file mode 100644 index 00000000..6196d6a0 --- /dev/null +++ b/test/evaluation/rfc0029/offsets/zero.c @@ -0,0 +1,12 @@ +#include +struct tree { struct tree *left, *right; }; +static void odd(struct tree *p); +static void even(struct tree *p) { + if(!p) return; + odd(p->left); odd(p->right); free(p); +} +static void odd(struct tree *p) { even(p+0); } +int main(void) { + struct tree *p=calloc(1,sizeof *p); if(!p)return 0; + odd(p); return 0; +} diff --git a/test/evaluation/rfc0029/output-construction/build.c b/test/evaluation/rfc0029/output-construction/build.c new file mode 100644 index 00000000..33474680 --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/build.c @@ -0,0 +1,14 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +int build(const unsigned char *data, size_t n, struct node **out) { + *out = 0; + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1 && !build(data+1,n-1,&p->next)) {free(p);return 0;} + *out=p; + return 1; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=0;if(build(data,3,&p))destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/output-construction/cycle.c b/test/evaluation/rfc0029/output-construction/cycle.c new file mode 100644 index 00000000..8fd14099 --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/cycle.c @@ -0,0 +1,14 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +int build(const unsigned char *data, size_t n, struct node **out) { + *out = 0; + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1 && !build(data,n,&p->next)) {free(p);return 0;} + *out=p; + return 1; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=0;if(build(data,3,&p))destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/output-construction/double.c b/test/evaluation/rfc0029/output-construction/double.c new file mode 100644 index 00000000..82841668 --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/double.c @@ -0,0 +1,14 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +int build(const unsigned char *data, size_t n, struct node **out) { + *out = 0; + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1 && !build(data+1,n-1,&p->next)) {free(p);free(p);return 0;} + *out=p; + return 1; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=0;if(build(data,3,&p))destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/output-construction/false-success.c b/test/evaluation/rfc0029/output-construction/false-success.c new file mode 100644 index 00000000..17cc5558 --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/false-success.c @@ -0,0 +1,14 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +int build(const unsigned char *data, size_t n, struct node **out) { + *out = 0; + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1 && !build(data+1,n-1,&p->next)) {free(p);return 0;} + /* no publication */ + return 1; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=0;if(build(data,3,&p))destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/output-construction/frozen-sha256.json b/test/evaluation/rfc0029/output-construction/frozen-sha256.json new file mode 100644 index 00000000..23834887 --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/frozen-sha256.json @@ -0,0 +1,12 @@ +{ + "build.c": "2f318d0f5b918f51a21d10d1538b347baf9c0f5565cabed75d1bc4919129230c", + "cycle.c": "74662b064fe5d2a62063e8eaed9edbba83c4225f64436116a78948e43fcd2c0a", + "double.c": "e98b39f6e01d8c305c784d47ae98aee6cf66e5854312a68ad23ec8c296e848dd", + "false-success.c": "98029252c9d77a1a84e3c45320808ffd08f86452a1e1619a1a76d8ef5047420c", + "leak.c": "4fdb7266d7988c865cb8c84e7b1b1a6a827e312c9ffca23ce64c9e0b7c788ac7", + "manifest.json": "719f5505875eaa55b5a99b50dc01f5a156161c8847fedf1e28b0775e8b3de7de", + "null-slot.c": "aa9611fd9d65b62c03c56a9bba027d468478ef6b3db03e899ae3fefdc2d6b21d", + "provenance.md": "6fe6145cdcf11bc44dbe4416de14b63d7d6df8dad55682b5083446965a2e6c6f", + "short.c": "dc3625ab29630ab1f7564be759b6b5ae616283d9828d448f4de854f907142a58", + "uncleared-failure.c": "43a1eb7dc8372b842f2332d179d4c3e3e46a69c20220a3a2a16f3765931507f1" +} diff --git a/test/evaluation/rfc0029/output-construction/leak.c b/test/evaluation/rfc0029/output-construction/leak.c new file mode 100644 index 00000000..50a23e47 --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/leak.c @@ -0,0 +1,14 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +int build(const unsigned char *data, size_t n, struct node **out) { + *out = 0; + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1 && !build(data+1,n-1,&p->next)) {return 0;} + *out=p; + return 1; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=0;if(build(data,3,&p))destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/output-construction/manifest.json b/test/evaluation/rfc0029/output-construction/manifest.json new file mode 100644 index 00000000..011f9003 --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/manifest.json @@ -0,0 +1,132 @@ +{ + "version": 1, + "cases": [ + { + "name": "output-construction-build", + "sources": [ + "build.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "output-construction-short", + "sources": [ + "short.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + }, + { + "name": "output-construction-leak", + "sources": [ + "leak.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|leak|cleanup|contract" + }, + { + "name": "output-construction-double", + "sources": [ + "double.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "freed|release" + }, + { + "name": "output-construction-cycle", + "sources": [ + "cycle.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|progress" + }, + { + "name": "output-construction-false-success", + "sources": [ + "false-success.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "output|contract|footprint|leak" + }, + { + "name": "output-construction-uncleared-failure", + "sources": [ + "uncleared-failure.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "output|contract" + }, + { + "name": "output-construction-null-slot", + "sources": [ + "null-slot.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "live|null|valid" + } + ] +} diff --git a/test/evaluation/rfc0029/output-construction/null-slot.c b/test/evaluation/rfc0029/output-construction/null-slot.c new file mode 100644 index 00000000..5aa6c2a2 --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/null-slot.c @@ -0,0 +1,14 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +int build(const unsigned char *data, size_t n, struct node **out) { + *out = 0; + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1 && !build(data+1,n-1,&p->next)) {free(p);return 0;} + *out=p; + return 1; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=0;if(build(data,3,0))destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/output-construction/provenance.md b/test/evaluation/rfc0029/output-construction/provenance.md new file mode 100644 index 00000000..e43b22fc --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/provenance.md @@ -0,0 +1 @@ +Frozen before output-slot construction inference on 2026-09-16. The positive builds a runtime-sized chain through a success-returning output parameter. Mutants cover truncated input, lost or repeated failure cleanup, no progress, false success, an unchanged failure slot and a null output slot. The unchanged-failure-slot variant is safe for some callers but does not satisfy this explicit generic candidate; it must not borrow the candidate's null-on-failure guarantee. Candidate 20 is the retained before executable. No existing population or expectation is changed. diff --git a/test/evaluation/rfc0029/output-construction/short.c b/test/evaluation/rfc0029/output-construction/short.c new file mode 100644 index 00000000..c407b44e --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/short.c @@ -0,0 +1,14 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +int build(const unsigned char *data, size_t n, struct node **out) { + *out = 0; + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1 && !build(data+1,n-1,&p->next)) {free(p);return 0;} + *out=p; + return 1; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=0;if(build(data,4,&p))destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/output-construction/uncleared-failure.c b/test/evaluation/rfc0029/output-construction/uncleared-failure.c new file mode 100644 index 00000000..1aa5fe6c --- /dev/null +++ b/test/evaluation/rfc0029/output-construction/uncleared-failure.c @@ -0,0 +1,13 @@ +#include +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +int build(const unsigned char *data, size_t n, struct node **out) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1 && !build(data+1,n-1,&p->next)) {free(p);return 0;} + *out=p; + return 1; +} +int main(void) {const unsigned char data[]={1,2,3};struct node *p=0;if(build(data,3,&p))destroy(p);return 0;} diff --git a/test/evaluation/rfc0029/output-transport/api.h b/test/evaluation/rfc0029/output-transport/api.h new file mode 100644 index 00000000..6c656cc8 --- /dev/null +++ b/test/evaluation/rfc0029/output-transport/api.h @@ -0,0 +1,5 @@ +#include +struct node { unsigned char value; struct node *next; }; +int build(const unsigned char *, size_t, struct node **); +void destroy(struct node *); +unsigned sum(const struct node *); diff --git a/test/evaluation/rfc0029/output-transport/client.c b/test/evaluation/rfc0029/output-transport/client.c new file mode 100644 index 00000000..fde183cd --- /dev/null +++ b/test/evaluation/rfc0029/output-transport/client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void) { const unsigned char data[]={1,2,3}; struct node *p=0; if(!build(data,3,&p))return 0; unsigned value=sum(p); destroy(p); return value!=6; } diff --git a/test/evaluation/rfc0029/output-transport/double.c b/test/evaluation/rfc0029/output-transport/double.c new file mode 100644 index 00000000..efa68900 --- /dev/null +++ b/test/evaluation/rfc0029/output-transport/double.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void) { const unsigned char data[]={1,2,3}; struct node *p=0; if(!build(data,3,&p))return 0; unsigned value=sum(p); destroy(p); destroy(p); return value!=6; } diff --git a/test/evaluation/rfc0029/output-transport/frozen-sha256.json b/test/evaluation/rfc0029/output-transport/frozen-sha256.json new file mode 100644 index 00000000..54482cc2 --- /dev/null +++ b/test/evaluation/rfc0029/output-transport/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "api.h": "fceafb995ad8ee12228142479b2e7b52d7517d4ec4df779224307add07291d86", + "client.c": "008f72cfa37e66e17e31238a560fc317285ec3cb6fece6346d953c5a4652985b", + "double.c": "068d3d7836bf0d720a37fbd1003b494bd747368271676349f8f29f52079b8768", + "library.c": "718710382ea7733de612a5fcfb0206e2978a5c0cf5cfc61d182b44e96e342026", + "manifest.json": "997bfe58eecc640660d7ac019851d294fe4193c2215cb30ec014663ab6818dda", + "provenance.md": "1b5aedebdcea0872bca91fcf4ee9349a63e5885c43295cbb5b672d1cdb1e4fcf", + "short.c": "521d8c2ad7b8f0ae2072c850b6b234c268b4ca0c331a7cf155291c2cc154d6be" +} diff --git a/test/evaluation/rfc0029/output-transport/library.c b/test/evaluation/rfc0029/output-transport/library.c new file mode 100644 index 00000000..1bd2baed --- /dev/null +++ b/test/evaluation/rfc0029/output-transport/library.c @@ -0,0 +1,14 @@ +#include +#include "api.h" +void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +int build(const unsigned char *data, size_t n, struct node **out) { + *out = 0; + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1 && !build(data+1,n-1,&p->next)) {free(p);return 0;} + *out=p; + return 1; +} +unsigned sum(const struct node *p) { if(!p)return 0; return p->value+sum(p->next); } diff --git a/test/evaluation/rfc0029/output-transport/manifest.json b/test/evaluation/rfc0029/output-transport/manifest.json new file mode 100644 index 00000000..6a740183 --- /dev/null +++ b/test/evaluation/rfc0029/output-transport/manifest.json @@ -0,0 +1,69 @@ +{ + "version": 1, + "cases": [ + { + "name": "client", + "sources": [ + "client.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "build", + "destroy", + "sum" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|extent|bounds" + }, + { + "name": "double", + "sources": [ + "double.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "live|freed|release" + } + ] +} diff --git a/test/evaluation/rfc0029/output-transport/provenance.md b/test/evaluation/rfc0029/output-transport/provenance.md new file mode 100644 index 00000000..57fd1cfb --- /dev/null +++ b/test/evaluation/rfc0029/output-transport/provenance.md @@ -0,0 +1 @@ +Separate-source equivalent of the previously frozen output-construction case, frozen on 2026-09-16 after candidate 21 established its output-slot proof. Also selects generic construction, traversal and cleanup independently. Source, ordinary-object and validated-checkpoint runs retain their own observations. Existing populations and expectations are unchanged. diff --git a/test/evaluation/rfc0029/output-transport/short.c b/test/evaluation/rfc0029/output-transport/short.c new file mode 100644 index 00000000..b9a7d261 --- /dev/null +++ b/test/evaluation/rfc0029/output-transport/short.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void) { const unsigned char data[]={1,2,3}; struct node *p=0; if(!build(data,4,&p))return 0; unsigned value=sum(p); destroy(p); return value!=6; } diff --git a/test/evaluation/rfc0029/paired-cursor-loops/README.md b/test/evaluation/rfc0029/paired-cursor-loops/README.md new file mode 100644 index 00000000..a41e6830 --- /dev/null +++ b/test/evaluation/rfc0029/paired-cursor-loops/README.md @@ -0,0 +1,4 @@ +# Sequential loops with paired cursors (RFC 0029) + +Frozen before candidate 58. One cursor discovers a bound; the second traverses +to it. Constant-step arithmetic must retain actual transitive coordinate bounds. diff --git a/test/evaluation/rfc0029/paired-cursor-loops/for.c b/test/evaluation/rfc0029/paired-cursor-loops/for.c new file mode 100644 index 00000000..2e5c1149 --- /dev/null +++ b/test/evaluation/rfc0029/paired-cursor-loops/for.c @@ -0,0 +1,5 @@ +int main(void){const unsigned char data[6]={0};const unsigned char *first=data+1,*last=data+1; + for(;last +int main(void) { + unsigned char data[5]={'x','0','1','0','x'}; + struct reader r={data,5,1,0}; + unsigned char *out=copy_digits(&r); if(out)free(out); return 0; +} diff --git a/test/evaluation/rfc0029/paired-reader-counters/library.c b/test/evaluation/rfc0029/paired-reader-counters/library.c new file mode 100644 index 00000000..3175dc0c --- /dev/null +++ b/test/evaluation/rfc0029/paired-reader-counters/library.c @@ -0,0 +1,19 @@ +#include "reader.h" +#include +#include +unsigned char *copy_digits(struct reader *r) { + size_t i=0, count=0; + if (!r || !r->data) return 0; + for (i=0; r->position+icapacity; ++i) { + switch ((r->data+r->position)[i]) { + case '0': case '1': ++count; break; + default: goto done; + } + } +done: ; + unsigned char *out=malloc(count+1); + if (!out) return 0; + memcpy(out,r->data+r->position,count); + out[count]=0; + return out; +} diff --git a/test/evaluation/rfc0029/paired-reader-counters/manifest.json b/test/evaluation/rfc0029/paired-reader-counters/manifest.json new file mode 100644 index 00000000..f3b10dc7 --- /dev/null +++ b/test/evaluation/rfc0029/paired-reader-counters/manifest.json @@ -0,0 +1,66 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "too-many", + "sources": [ + "too-many.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|extent|projection|bounds" + }, + { + "name": "nonzero", + "sources": [ + "nonzero.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|extent|projection|bounds" + }, + { + "name": "skipped-index", + "sources": [ + "skipped-index.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|initialized|extent|projection|bounds" + } + ] +} diff --git a/test/evaluation/rfc0029/paired-reader-counters/nonzero.c b/test/evaluation/rfc0029/paired-reader-counters/nonzero.c new file mode 100644 index 00000000..d28f3376 --- /dev/null +++ b/test/evaluation/rfc0029/paired-reader-counters/nonzero.c @@ -0,0 +1,20 @@ +#include "reader.h" +#include +#include +unsigned char *copy_digits(struct reader *r) { + size_t i=0, count=2; + if (!r || !r->data) return 0; + for (i=0; r->position+icapacity; ++i) { + switch ((r->data+r->position)[i]) { + case '0': case '1': ++count; break; + default: goto done; + } + } +done: ; + unsigned char *out=malloc(count+1); + if (!out) return 0; + memcpy(out,r->data+r->position,count); + out[count]=0; + return out; +} +int main(void) { unsigned char data[1]={'0'}; struct reader r={data,1,0,0}; unsigned char *p=copy_digits(&r); if(p)free(p); return 0; } diff --git a/test/evaluation/rfc0029/paired-reader-counters/reader.h b/test/evaluation/rfc0029/paired-reader-counters/reader.h new file mode 100644 index 00000000..07747791 --- /dev/null +++ b/test/evaluation/rfc0029/paired-reader-counters/reader.h @@ -0,0 +1,3 @@ +#include +struct reader { const unsigned char *data; size_t capacity, position; unsigned depth; }; +unsigned char *copy_digits(struct reader *r); diff --git a/test/evaluation/rfc0029/paired-reader-counters/skipped-index.c b/test/evaluation/rfc0029/paired-reader-counters/skipped-index.c new file mode 100644 index 00000000..aaf2fcf1 --- /dev/null +++ b/test/evaluation/rfc0029/paired-reader-counters/skipped-index.c @@ -0,0 +1,20 @@ +#include "reader.h" +#include +#include +unsigned char *copy_digits(struct reader *r) { + size_t i=0, count=0; + if (!r || !r->data) return 0; + for (i=0; r->position+icapacity; i+=2) { + switch ((r->data+r->position)[i]) { + case '0': case '1': count+=4; break; + default: goto done; + } + } +done: ; + unsigned char *out=malloc(count+1); + if (!out) return 0; + memcpy(out,r->data+r->position,count); + out[count]=0; + return out; +} +int main(void) { unsigned char data[1]={'0'}; struct reader r={data,1,0,0}; unsigned char *p=copy_digits(&r); if(p)free(p); return 0; } diff --git a/test/evaluation/rfc0029/paired-reader-counters/too-many.c b/test/evaluation/rfc0029/paired-reader-counters/too-many.c new file mode 100644 index 00000000..67e18a5e --- /dev/null +++ b/test/evaluation/rfc0029/paired-reader-counters/too-many.c @@ -0,0 +1,20 @@ +#include "reader.h" +#include +#include +unsigned char *copy_digits(struct reader *r) { + size_t i=0, count=0; + if (!r || !r->data) return 0; + for (i=0; r->position+icapacity; ++i) { + switch ((r->data+r->position)[i]) { + case '0': case '1': count+=8; break; + default: goto done; + } + } +done: ; + unsigned char *out=malloc(count+1); + if (!out) return 0; + memcpy(out,r->data+r->position,count); + out[count]=0; + return out; +} +int main(void) { unsigned char data[1]={'0'}; struct reader r={data,1,0,0}; unsigned char *p=copy_digits(&r); if(p)free(p); return 0; } diff --git a/test/evaluation/rfc0029/payload-early-exits/PROVENANCE.md b/test/evaluation/rfc0029/payload-early-exits/PROVENANCE.md new file mode 100644 index 00000000..246fc2e9 --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate79b before making payload-writer entry ownership premises independent of CFG return visitation order. The goto and direct-return bodies have the same successful publication and failure preservation; negative bodies lose, duplicate or release owned storage. The original payload populations remain unchanged. diff --git a/test/evaluation/rfc0029/payload-early-exits/api.h b/test/evaluation/rfc0029/payload-early-exits/api.h new file mode 100644 index 00000000..5a9bafac --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/api.h @@ -0,0 +1,2 @@ +struct Node {struct Node *next,*child;unsigned flags;char *text,*name;}; +int fill(struct Node *,unsigned *);void drop(struct Node *); diff --git a/test/evaluation/rfc0029/payload-early-exits/client.c b/test/evaluation/rfc0029/payload-early-exits/client.c new file mode 100644 index 00000000..8cf999ba --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/client.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node*n=calloc(1,sizeof *n);if(!n)return 0;unsigned cursor=0;fill(n,&cursor);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-early-exits/drop.c b/test/evaluation/rfc0029/payload-early-exits/drop.c new file mode 100644 index 00000000..7a89cc8b --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/drop.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +void drop(struct Node*n){while(n){struct Node*next=n->next;drop(n->child);if(!(n->flags&1))free(n->text);free(n->name);free(n);n=next;}} diff --git a/test/evaluation/rfc0029/payload-early-exits/duplicate.c b/test/evaluation/rfc0029/payload-early-exits/duplicate.c new file mode 100644 index 00000000..cbfe5b32 --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/duplicate.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node*n,unsigned*cursor){char*p=malloc(4);if(!p)goto fail;p[0]=0;n->text=p;n->name=p;*cursor=1;return 1;fail:*cursor=1;return 0;} diff --git a/test/evaluation/rfc0029/payload-early-exits/early.c b/test/evaluation/rfc0029/payload-early-exits/early.c new file mode 100644 index 00000000..7ec06aff --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/early.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node*n,unsigned*cursor){char*p=malloc(4);if(!p){*cursor=1;return 0;}p[0]=0;n->text=p;*cursor=1;return 1;} diff --git a/test/evaluation/rfc0029/payload-early-exits/frozen-sha256.json b/test/evaluation/rfc0029/payload-early-exits/frozen-sha256.json new file mode 100644 index 00000000..13a37baf --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/frozen-sha256.json @@ -0,0 +1,13 @@ +{ + "PROVENANCE.md": "11bff07e47bae9b1101d173626bffc9c374a3535a69317f29e29423341da4d02", + "api.h": "870d124748066f06dc52bd563b443c12dddaab64a2d5c34bc1b9689f51d72a42", + "client.c": "c3e9ce0727985b454a310a3b99ce35d8a4ba79a4c2bfbff496aeab7127df5778", + "drop.c": "441f232a733aa18db63442d67df15433c09d8282a892d6b6c43b314c6ca8acea", + "duplicate.c": "a27312a4f7c008091244c83e3ccb49c9cb2603e03a75855b5763dc216da6f818", + "early.c": "9c9f88717517bdd5e4bdd83831f9a8216d07d5b0b31ffea53a824600d4245055", + "goto.c": "333db102a2b0fcaa8750291daa52a5e2ede27666f587bdf03e1324ed64f793d3", + "leak.c": "876b3072bf49807bce08709f8905847caaca445ad2e982c23df89756576e6b80", + "manifest.json": "319d050804dffa03a5e756292af025f9f0bb3b459d8e9db162adb90d1ce449fd", + "released-head.c": "c72afefc37ad7568484819c0300104aab510a663cd038c44d86fb05750643395", + "released-payload.c": "85efa26d7d6dab83a2a3b77dd3fdf315fe9b143ccdc75ce38c1fd478e75761cd" +} diff --git a/test/evaluation/rfc0029/payload-early-exits/goto.c b/test/evaluation/rfc0029/payload-early-exits/goto.c new file mode 100644 index 00000000..fb8d654a --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/goto.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node*n,unsigned*cursor){char*p=malloc(4);if(!p)goto fail;p[0]=0;n->text=p;*cursor=1;return 1;fail:*cursor=1;return 0;} diff --git a/test/evaluation/rfc0029/payload-early-exits/leak.c b/test/evaluation/rfc0029/payload-early-exits/leak.c new file mode 100644 index 00000000..e53b0322 --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/leak.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node*n,unsigned*cursor){char*p=malloc(4);if(!p)goto fail;p[0]=0;*cursor=1;return 1;fail:*cursor=1;return 0;} diff --git a/test/evaluation/rfc0029/payload-early-exits/manifest.json b/test/evaluation/rfc0029/payload-early-exits/manifest.json new file mode 100644 index 00000000..31d4222c --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/manifest.json @@ -0,0 +1,107 @@ +{ + "version": 1, + "cases": [ + { + "name": "goto", + "sources": [ + "client.c", + "goto.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "early", + "sources": [ + "client.c", + "early.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "leak", + "sources": [ + "client.c", + "leak.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + }, + { + "name": "duplicate", + "sources": [ + "client.c", + "duplicate.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + }, + { + "name": "released-head", + "sources": [ + "client.c", + "released-head.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + }, + { + "name": "released-payload", + "sources": [ + "client.c", + "released-payload.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + } + ] +} diff --git a/test/evaluation/rfc0029/payload-early-exits/released-head.c b/test/evaluation/rfc0029/payload-early-exits/released-head.c new file mode 100644 index 00000000..909006e5 --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/released-head.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node*n,unsigned*cursor){char*p=malloc(4);if(!p)goto fail;p[0]=0;n->text=p;*cursor=1;return 1;fail:free(n);*cursor=1;return 0;} diff --git a/test/evaluation/rfc0029/payload-early-exits/released-payload.c b/test/evaluation/rfc0029/payload-early-exits/released-payload.c new file mode 100644 index 00000000..d573cbc3 --- /dev/null +++ b/test/evaluation/rfc0029/payload-early-exits/released-payload.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node*n,unsigned*cursor){char*p=malloc(4);if(!p)goto fail;p[0]=0;free(p);n->text=p;*cursor=1;return 1;fail:*cursor=1;return 0;} diff --git a/test/evaluation/rfc0029/payload-publication/PROVENANCE.md b/test/evaluation/rfc0029/payload-publication/PROVENANCE.md new file mode 100644 index 00000000..d42bc3d5 --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate77f before publishing a fresh payload through a local allocation alias. The independent recursive lifecycle includes helper-returned allocation, duplicated ownership, interior and released pointers, and a lost allocation. diff --git a/test/evaluation/rfc0029/payload-publication/api.h b/test/evaluation/rfc0029/payload-publication/api.h new file mode 100644 index 00000000..6a4d37c7 --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/api.h @@ -0,0 +1,2 @@ +struct Node {struct Node *next,*child;char *text,*name;}; +int fill(struct Node *);void drop(struct Node *); diff --git a/test/evaluation/rfc0029/payload-publication/client.c b/test/evaluation/rfc0029/payload-publication/client.c new file mode 100644 index 00000000..e3efbec5 --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/client.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node *n=calloc(1,sizeof *n);if(!n)return 0;fill(n);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-publication/drop.c b/test/evaluation/rfc0029/payload-publication/drop.c new file mode 100644 index 00000000..a9b19b77 --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/drop.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +void drop(struct Node *n){while(n){struct Node *next=n->next;drop(n->child);free(n->text);free(n->name);free(n);n=next;}} diff --git a/test/evaluation/rfc0029/payload-publication/duplicate.c b/test/evaluation/rfc0029/payload-publication/duplicate.c new file mode 100644 index 00000000..b882a04f --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/duplicate.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node *n){char *p=malloc(4);if(!p)return 0;p[0]=0;n->text=p;n->name=p;return 1;} diff --git a/test/evaluation/rfc0029/payload-publication/frozen-sha256.json b/test/evaluation/rfc0029/payload-publication/frozen-sha256.json new file mode 100644 index 00000000..8d423076 --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/frozen-sha256.json @@ -0,0 +1,13 @@ +{ + "PROVENANCE.md": "afd64084d44f464abbaeb5fc303d2e65a9bfa9d29ce1f5c5e3b660376bf2e53e", + "api.h": "84fbe8a9e59e6146352d61d123a60961fd25a7d3eee884bd71807a8c5b57fbcb", + "client.c": "5e03d0d0fe1e2fc032c77982d94df6db2b7b8c9a66e3dcf3844ae2216cd35898", + "drop.c": "3b7b3fa3db9e68277738aaf53a7352de8fdbbd891662f32231525d346203f866", + "duplicate.c": "de0cbdd4ce7ddc1006acb3593dd8da5badd783cc0af6c19a0a357e62b83fbe20", + "good.c": "60ba2bc4c6c63b14c9c480d37326561e0a3df6c414658f7d2dffdaded0c09a85", + "helper.c": "418b46596665d56a687cf6976133381163bdc13424ce9a18fa952b45583d058b", + "interior.c": "fc4dfa0d832c4d5684c0e1a55ed7c49df71fce81abe0b02b145b76bd953f692d", + "lost.c": "280982874121f9776febc5bbab2eb3e9c7ba0c8291c67182239a882dbbebdb3c", + "manifest.json": "bde8a0416c3094f10572ed64b29fbf363b8e7b4fa6d6a0d6521104d3b62ef212", + "released.c": "92d3ad0a0013f5c9127e60cd63febf7002d01a0ff1e3433b2a9f64c4cb0e8c86" +} diff --git a/test/evaluation/rfc0029/payload-publication/good.c b/test/evaluation/rfc0029/payload-publication/good.c new file mode 100644 index 00000000..35b8cb79 --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/good.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node *n){char *p=malloc(4);if(!p)return 0;p[0]=0;n->text=p;return 1;} diff --git a/test/evaluation/rfc0029/payload-publication/helper.c b/test/evaluation/rfc0029/payload-publication/helper.c new file mode 100644 index 00000000..e3311206 --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/helper.c @@ -0,0 +1,4 @@ +#include "api.h" +#include +static char *make(void){char *p=malloc(4);if(p)p[0]=0;return p;} +int fill(struct Node *n){char *p=make();if(!p)return 0;n->text=p;return 1;} diff --git a/test/evaluation/rfc0029/payload-publication/interior.c b/test/evaluation/rfc0029/payload-publication/interior.c new file mode 100644 index 00000000..7b1d7a58 --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/interior.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node *n){char *p=malloc(4);if(!p)return 0;p[0]=0;n->text=p+1;return 1;} diff --git a/test/evaluation/rfc0029/payload-publication/lost.c b/test/evaluation/rfc0029/payload-publication/lost.c new file mode 100644 index 00000000..651cef3f --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/lost.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node *n){char *p=malloc(4);if(!p)return 0;p[0]=0;return 1;} diff --git a/test/evaluation/rfc0029/payload-publication/manifest.json b/test/evaluation/rfc0029/payload-publication/manifest.json new file mode 100644 index 00000000..2b2c400f --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/manifest.json @@ -0,0 +1,107 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "client.c", + "good.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "helper", + "sources": [ + "client.c", + "helper.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "duplicate", + "sources": [ + "client.c", + "duplicate.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|release|live|allocation|freed|contract" + }, + { + "name": "interior", + "sources": [ + "client.c", + "interior.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|release|live|allocation|freed|contract" + }, + { + "name": "released", + "sources": [ + "client.c", + "released.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|release|live|allocation|freed|contract" + }, + { + "name": "lost", + "sources": [ + "client.c", + "lost.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|release|live|allocation|freed|contract" + } + ] +} diff --git a/test/evaluation/rfc0029/payload-publication/released.c b/test/evaluation/rfc0029/payload-publication/released.c new file mode 100644 index 00000000..d0a8d64e --- /dev/null +++ b/test/evaluation/rfc0029/payload-publication/released.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node *n){char *p=malloc(4);if(!p)return 0;p[0]=0;free(p);n->text=p;return 1;} diff --git a/test/evaluation/rfc0029/payload-reader-returns/PROVENANCE.md b/test/evaluation/rfc0029/payload-reader-returns/PROVENANCE.md new file mode 100644 index 00000000..92acdddb --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate80c before retaining complete singleton input descriptors through forwarding helpers. A successful helper extends its incoming head and updates a separate reader, and a second helper advances that reader again. Allocation failure preserves the incoming head. Lost, duplicated, released payloads and an early released head remain negative. This is independent of the unchanged upstream cJSON acceptance population. diff --git a/test/evaluation/rfc0029/payload-reader-returns/api.h b/test/evaluation/rfc0029/payload-reader-returns/api.h new file mode 100644 index 00000000..9c6411ad --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/api.h @@ -0,0 +1,4 @@ +#include +struct Node{struct Node *next,*child;unsigned flags;char *text,*name;}; +struct Reader{const unsigned char *content;size_t length,offset,depth;}; +int fill(struct Node *,struct Reader *);int wrap(struct Node *,struct Reader *);void drop(struct Node *); diff --git a/test/evaluation/rfc0029/payload-reader-returns/client.c b/test/evaluation/rfc0029/payload-reader-returns/client.c new file mode 100644 index 00000000..ac6ddadd --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/client.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node*n=calloc(1,sizeof *n);if(!n)return 0;static const unsigned char bytes[]="abc";struct Reader r={bytes,sizeof bytes,0,0};wrap(n,&r);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-reader-returns/drop.c b/test/evaluation/rfc0029/payload-reader-returns/drop.c new file mode 100644 index 00000000..7a89cc8b --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/drop.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +void drop(struct Node*n){while(n){struct Node*next=n->next;drop(n->child);if(!(n->flags&1))free(n->text);free(n->name);free(n);n=next;}} diff --git a/test/evaluation/rfc0029/payload-reader-returns/duplicate.c b/test/evaluation/rfc0029/payload-reader-returns/duplicate.c new file mode 100644 index 00000000..ef6d56a3 --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/duplicate.c @@ -0,0 +1,4 @@ +#include "api.h" +#include +int role(struct Reader*r){if(r->offsetlength){r->offset++;return 1;}return 0;} +int fill(struct Node*n,struct Reader*r){char*p=malloc(4);if(!p)goto fail;p[0]=0;n->text=p;n->name=p;r->offset++;return 1;fail:r->offset=1;return 0;} diff --git a/test/evaluation/rfc0029/payload-reader-returns/early.c b/test/evaluation/rfc0029/payload-reader-returns/early.c new file mode 100644 index 00000000..0a304f83 --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/early.c @@ -0,0 +1,4 @@ +#include "api.h" +#include +int role(struct Reader*r){if(r->offsetlength){r->offset++;return 1;}return 0;} +int fill(struct Node*n,struct Reader*r){char*p=malloc(4);if(!p){r->offset=1;return 0;}p[0]=0;n->text=p;r->offset++;return 1;} diff --git a/test/evaluation/rfc0029/payload-reader-returns/frozen-sha256.json b/test/evaluation/rfc0029/payload-reader-returns/frozen-sha256.json new file mode 100644 index 00000000..fdfbf500 --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/frozen-sha256.json @@ -0,0 +1,14 @@ +{ + "PROVENANCE.md": "c866d4c988edd9f6a41afc2eaee158484fdc0f77a10c126ad4937cd77943c7a2", + "api.h": "4067c004e75d18f514cc35dd0c02091ca4afa1456f50cd631c0cbe81181137dc", + "client.c": "2e6d9bbe0435a8a79e8c26e266e1d04b4e13bafd1827874e8f62002d66763510", + "drop.c": "441f232a733aa18db63442d67df15433c09d8282a892d6b6c43b314c6ca8acea", + "duplicate.c": "bb0189dbc775f9ec2af3cbf3efac6149f460a5a065d32fd0c71598879822a7a0", + "early.c": "d8234eeedde141e107209da526e100b70452cc16be89c5f2a96fcc26f2656a7c", + "good.c": "86293975c06e3dac98cd9a47390384239dd809e68c2cb4f330e248972bbbe4ae", + "leak.c": "d34f672afe0646cec734d122d6f191394595a31058f2634829106f8f92ac34cf", + "manifest.json": "aea0a2edb0039f2bf4edd5dd6f79a597d83e5437d16e962dec6e854345e35ca9", + "released-head.c": "73fa348e559a5f7ac1498f8637f4e4d3285f97b3d69b62ac73b05a2ac81b0212", + "released-payload.c": "0e8ec062a4a7b6103a5e47ea98aed2c48918017abc2d0c09c8cc5a475e83d42e", + "wrap.c": "a9074315cfe28f5d18475c739900ac840642b190f02d2465c89d774ca3b1fee2" +} diff --git a/test/evaluation/rfc0029/payload-reader-returns/good.c b/test/evaluation/rfc0029/payload-reader-returns/good.c new file mode 100644 index 00000000..44d9b0f5 --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/good.c @@ -0,0 +1,4 @@ +#include "api.h" +#include +int role(struct Reader*r){if(r->offsetlength){r->offset++;return 1;}return 0;} +int fill(struct Node*n,struct Reader*r){char*p=malloc(4);if(!p)goto fail;p[0]=0;n->text=p;r->offset++;return 1;fail:r->offset=1;return 0;} diff --git a/test/evaluation/rfc0029/payload-reader-returns/leak.c b/test/evaluation/rfc0029/payload-reader-returns/leak.c new file mode 100644 index 00000000..c13d2090 --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/leak.c @@ -0,0 +1,4 @@ +#include "api.h" +#include +int role(struct Reader*r){if(r->offsetlength){r->offset++;return 1;}return 0;} +int fill(struct Node*n,struct Reader*r){char*p=malloc(4);if(!p)goto fail;p[0]=0;r->offset++;return 1;fail:r->offset=1;return 0;} diff --git a/test/evaluation/rfc0029/payload-reader-returns/manifest.json b/test/evaluation/rfc0029/payload-reader-returns/manifest.json new file mode 100644 index 00000000..3e6eecd2 --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/manifest.json @@ -0,0 +1,113 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "client.c", + "wrap.c", + "good.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "early", + "sources": [ + "client.c", + "wrap.c", + "early.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "leak", + "sources": [ + "client.c", + "wrap.c", + "leak.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + }, + { + "name": "duplicate", + "sources": [ + "client.c", + "wrap.c", + "duplicate.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + }, + { + "name": "released-head", + "sources": [ + "client.c", + "wrap.c", + "released-head.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + }, + { + "name": "released-payload", + "sources": [ + "client.c", + "wrap.c", + "released-payload.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + } + ] +} diff --git a/test/evaluation/rfc0029/payload-reader-returns/released-head.c b/test/evaluation/rfc0029/payload-reader-returns/released-head.c new file mode 100644 index 00000000..9314dcdf --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/released-head.c @@ -0,0 +1,4 @@ +#include "api.h" +#include +int role(struct Reader*r){if(r->offsetlength){r->offset++;return 1;}return 0;} +int fill(struct Node*n,struct Reader*r){char*p=malloc(4);if(!p)goto fail;p[0]=0;n->text=p;r->offset++;return 1;fail:free(n);r->offset=1;return 0;} diff --git a/test/evaluation/rfc0029/payload-reader-returns/released-payload.c b/test/evaluation/rfc0029/payload-reader-returns/released-payload.c new file mode 100644 index 00000000..7314f96d --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/released-payload.c @@ -0,0 +1,4 @@ +#include "api.h" +#include +int role(struct Reader*r){if(r->offsetlength){r->offset++;return 1;}return 0;} +int fill(struct Node*n,struct Reader*r){char*p=malloc(4);if(!p)goto fail;p[0]=0;free(p);n->text=p;r->offset++;return 1;fail:r->offset=1;return 0;} diff --git a/test/evaluation/rfc0029/payload-reader-returns/wrap.c b/test/evaluation/rfc0029/payload-reader-returns/wrap.c new file mode 100644 index 00000000..fa2e9297 --- /dev/null +++ b/test/evaluation/rfc0029/payload-reader-returns/wrap.c @@ -0,0 +1,2 @@ +#include "api.h" +int wrap(struct Node*n,struct Reader*r){if(!fill(n,r))return 0;r->offset++;return 1;} diff --git a/test/evaluation/rfc0029/payload-relocation/PROVENANCE.md b/test/evaluation/rfc0029/payload-relocation/PROVENANCE.md new file mode 100644 index 00000000..120a07ad --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate81a before re-establishing complete concrete forests after payload slot relocation. Two sequential stores temporarily duplicate a pointer then clear its old slot. The final graph still needs actual initialized links, live allocation bases and unique ownership, and the acquisition ledger must account for every allocation. An uncleared duplicate, interior pointer, overwritten allocation and released payload remain negative. diff --git a/test/evaluation/rfc0029/payload-relocation/api.h b/test/evaluation/rfc0029/payload-relocation/api.h new file mode 100644 index 00000000..6a4d37c7 --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/api.h @@ -0,0 +1,2 @@ +struct Node {struct Node *next,*child;char *text,*name;}; +int fill(struct Node *);void drop(struct Node *); diff --git a/test/evaluation/rfc0029/payload-relocation/drop.c b/test/evaluation/rfc0029/payload-relocation/drop.c new file mode 100644 index 00000000..a9b19b77 --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/drop.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +void drop(struct Node *n){while(n){struct Node *next=n->next;drop(n->child);free(n->text);free(n->name);free(n);n=next;}} diff --git a/test/evaluation/rfc0029/payload-relocation/duplicate.c b/test/evaluation/rfc0029/payload-relocation/duplicate.c new file mode 100644 index 00000000..1b9bc08c --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/duplicate.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node*n=calloc(1,sizeof *n);if(!n)return 0;fill(n);n->name=n->text;drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-relocation/fill.c b/test/evaluation/rfc0029/payload-relocation/fill.c new file mode 100644 index 00000000..d5fb0c22 --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/fill.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node*n){char*p=malloc(4);if(!p)return 0;p[0]=0;n->text=p;return 1;} diff --git a/test/evaluation/rfc0029/payload-relocation/frozen-sha256.json b/test/evaluation/rfc0029/payload-relocation/frozen-sha256.json new file mode 100644 index 00000000..2250158f --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/frozen-sha256.json @@ -0,0 +1,13 @@ +{ + "PROVENANCE.md": "a2505c2c55f46eee0d09f1f456da1a518b6487d5580521c89e8409767b464203", + "api.h": "84fbe8a9e59e6146352d61d123a60961fd25a7d3eee884bd71807a8c5b57fbcb", + "drop.c": "3b7b3fa3db9e68277738aaf53a7352de8fdbbd891662f32231525d346203f866", + "duplicate.c": "112315df92c8760965dfa275ac8e1330065412ae4b683a1f3eee13bade286103", + "fill.c": "9cb7e268efb15c79093654f718824ccea09b0ff640d4c28cc98e5d02faa873f2", + "helper.c": "9a2cf6e3798571abf594ac52036ef90bfb48110d106a37b64587d3e02b647c1e", + "interior.c": "27f8d239e2feb44ecd16028e8ab45f0de1d19b5073fb193f64b188c2ab280b81", + "local.c": "5e90ec224a00dbd5d5aa79fb5060c719db3dcb401efc4e9d9e5ff9ce0261dc5e", + "manifest.json": "62c9cb72213889cbd7cde7b3f5dfa3f317ef759bfc3890a19b7d2030819e0447", + "overwritten.c": "c7959e97e717bf35a13a1a5f402bed53e74e9e2de6d4ce15590fd859105eb052", + "released.c": "21a089e9bddbbba2489606600ae6e375f59961972824dd0cd26a335ca91ff77d" +} diff --git a/test/evaluation/rfc0029/payload-relocation/helper.c b/test/evaluation/rfc0029/payload-relocation/helper.c new file mode 100644 index 00000000..82d5c5e3 --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/helper.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node*n=calloc(1,sizeof *n);if(!n)return 0;fill(n);n->name=n->text;n->text=0;drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-relocation/interior.c b/test/evaluation/rfc0029/payload-relocation/interior.c new file mode 100644 index 00000000..0bc40a5f --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/interior.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node*n=calloc(1,sizeof *n);if(!n)return 0;if(fill(n)){n->name=n->text+1;n->text=0;}drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-relocation/local.c b/test/evaluation/rfc0029/payload-relocation/local.c new file mode 100644 index 00000000..c11e17a1 --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/local.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node*n=calloc(1,sizeof *n);if(!n)return 0;n->text=malloc(4);if(n->text)n->text[0]=0;n->name=n->text;n->text=0;drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-relocation/manifest.json b/test/evaluation/rfc0029/payload-relocation/manifest.json new file mode 100644 index 00000000..4f658070 --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/manifest.json @@ -0,0 +1,107 @@ +{ + "version": 1, + "cases": [ + { + "name": "helper", + "sources": [ + "helper.c", + "fill.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "local", + "sources": [ + "local.c", + "fill.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "duplicate", + "sources": [ + "duplicate.c", + "fill.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + }, + { + "name": "interior", + "sources": [ + "interior.c", + "fill.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + }, + { + "name": "overwritten", + "sources": [ + "overwritten.c", + "fill.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + }, + { + "name": "released", + "sources": [ + "released.c", + "fill.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|container|live|release|allocation|contract" + } + ] +} diff --git a/test/evaluation/rfc0029/payload-relocation/overwritten.c b/test/evaluation/rfc0029/payload-relocation/overwritten.c new file mode 100644 index 00000000..733efead --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/overwritten.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node*n=calloc(1,sizeof *n);if(!n)return 0;fill(n);n->name=malloc(4);n->name=n->text;n->text=0;drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-relocation/released.c b/test/evaluation/rfc0029/payload-relocation/released.c new file mode 100644 index 00000000..8d99e256 --- /dev/null +++ b/test/evaluation/rfc0029/payload-relocation/released.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node*n=calloc(1,sizeof *n);if(!n)return 0;fill(n);free(n->text);n->name=n->text;n->text=0;drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-write-frames/PROVENANCE.md b/test/evaluation/rfc0029/payload-write-frames/PROVENANCE.md new file mode 100644 index 00000000..400e5612 --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate78h before preserving a published payload across writes to a separated cursor. Direct and helper-mediated stores share the same input separation obligation. Aliasing the cursor with the ownership selector makes cleanup leak the payload; duplicate publication still owns one allocation twice. The original payload-publication population remains unchanged. diff --git a/test/evaluation/rfc0029/payload-write-frames/aliased-client.c b/test/evaluation/rfc0029/payload-write-frames/aliased-client.c new file mode 100644 index 00000000..199114c1 --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/aliased-client.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node*n=calloc(1,sizeof *n);if(!n)return 0;fill(n,&n->flags);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-write-frames/api.h b/test/evaluation/rfc0029/payload-write-frames/api.h new file mode 100644 index 00000000..5a9bafac --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/api.h @@ -0,0 +1,2 @@ +struct Node {struct Node *next,*child;unsigned flags;char *text,*name;}; +int fill(struct Node *,unsigned *);void drop(struct Node *); diff --git a/test/evaluation/rfc0029/payload-write-frames/client.c b/test/evaluation/rfc0029/payload-write-frames/client.c new file mode 100644 index 00000000..8cf999ba --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/client.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int main(void){struct Node*n=calloc(1,sizeof *n);if(!n)return 0;unsigned cursor=0;fill(n,&cursor);drop(n);return 0;} diff --git a/test/evaluation/rfc0029/payload-write-frames/drop.c b/test/evaluation/rfc0029/payload-write-frames/drop.c new file mode 100644 index 00000000..7a89cc8b --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/drop.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +void drop(struct Node*n){while(n){struct Node*next=n->next;drop(n->child);if(!(n->flags&1))free(n->text);free(n->name);free(n);n=next;}} diff --git a/test/evaluation/rfc0029/payload-write-frames/duplicate.c b/test/evaluation/rfc0029/payload-write-frames/duplicate.c new file mode 100644 index 00000000..e3d3684d --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/duplicate.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node*n,unsigned*cursor){char*p=malloc(4);if(!p)return 0;p[0]=0;n->text=p;n->name=p;*cursor=1;return 1;} diff --git a/test/evaluation/rfc0029/payload-write-frames/early.c b/test/evaluation/rfc0029/payload-write-frames/early.c new file mode 100644 index 00000000..570fba69 --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/early.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node*n,unsigned*cursor){*cursor=1;char*p=malloc(4);if(!p)return 0;p[0]=0;n->text=p;return 1;} diff --git a/test/evaluation/rfc0029/payload-write-frames/frozen-sha256.json b/test/evaluation/rfc0029/payload-write-frames/frozen-sha256.json new file mode 100644 index 00000000..b5221614 --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/frozen-sha256.json @@ -0,0 +1,12 @@ +{ + "PROVENANCE.md": "07555cce8c113a37d6f3c0242a07503538123ffc074e2f557cd34f0244f490f6", + "aliased-client.c": "efb2dfbea3a0da524981a920585c7d089b2f53bd95792abd2e9259d8865fb0e3", + "api.h": "870d124748066f06dc52bd563b443c12dddaab64a2d5c34bc1b9689f51d72a42", + "client.c": "c3e9ce0727985b454a310a3b99ce35d8a4ba79a4c2bfbff496aeab7127df5778", + "drop.c": "441f232a733aa18db63442d67df15433c09d8282a892d6b6c43b314c6ca8acea", + "duplicate.c": "6e41da5fa98f42879d86f7c327c0898e1186a36f852f8c01f55271ac10fd027f", + "early.c": "87ca5fa454883728663044e243ad9a18b53abbc5b091c3dc03871e172b820db7", + "good.c": "9c9f88717517bdd5e4bdd83831f9a8216d07d5b0b31ffea53a824600d4245055", + "helper.c": "edb8337ebbaa8884b6bfa2ecd663aa8e547da614ba8a1536981187a9c3325f28", + "manifest.json": "7f5aa5f30c92eb3d3c0aefc3cecff28b2944678f47cb01a458e557fac21c8990" +} diff --git a/test/evaluation/rfc0029/payload-write-frames/good.c b/test/evaluation/rfc0029/payload-write-frames/good.c new file mode 100644 index 00000000..7ec06aff --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/good.c @@ -0,0 +1,3 @@ +#include "api.h" +#include +int fill(struct Node*n,unsigned*cursor){char*p=malloc(4);if(!p){*cursor=1;return 0;}p[0]=0;n->text=p;*cursor=1;return 1;} diff --git a/test/evaluation/rfc0029/payload-write-frames/helper.c b/test/evaluation/rfc0029/payload-write-frames/helper.c new file mode 100644 index 00000000..c88a1df8 --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/helper.c @@ -0,0 +1,4 @@ +#include "api.h" +#include +static void step(unsigned*p){*p=1;} +int fill(struct Node*n,unsigned*cursor){char*p=malloc(4);if(!p){step(cursor);return 0;}p[0]=0;n->text=p;step(cursor);return 1;} diff --git a/test/evaluation/rfc0029/payload-write-frames/manifest.json b/test/evaluation/rfc0029/payload-write-frames/manifest.json new file mode 100644 index 00000000..121504f1 --- /dev/null +++ b/test/evaluation/rfc0029/payload-write-frames/manifest.json @@ -0,0 +1,107 @@ +{ + "version": 1, + "cases": [ + { + "name": "good", + "sources": [ + "client.c", + "good.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "early", + "sources": [ + "client.c", + "early.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "helper", + "sources": [ + "client.c", + "helper.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "aliased-selector", + "sources": [ + "aliased-client.c", + "good.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "separat|footprint|container|live|release|allocation|contract" + }, + { + "name": "aliased-helper", + "sources": [ + "aliased-client.c", + "helper.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "separat|footprint|container|live|release|allocation|contract" + }, + { + "name": "duplicate", + "sources": [ + "client.c", + "duplicate.c", + "drop.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "separat|footprint|container|live|release|allocation|contract" + } + ] +} diff --git a/test/evaluation/rfc0029/pointer-count-readers/README.md b/test/evaluation/rfc0029/pointer-count-readers/README.md new file mode 100644 index 00000000..75d56a99 --- /dev/null +++ b/test/evaluation/rfc0029/pointer-count-readers/README.md @@ -0,0 +1 @@ +Frozen before candidate 65. A read interval is nominated by an evaluated comparison between a byte-pointer distance from an unchanged input base and an unchanged unsigned input count. Any inferred initialized-interval premise is an explicit caller obligation. Parameter order supplies no evidence. diff --git a/test/evaluation/rfc0029/pointer-count-readers/frozen-sha256.json b/test/evaluation/rfc0029/pointer-count-readers/frozen-sha256.json new file mode 100644 index 00000000..c5fab2ad --- /dev/null +++ b/test/evaluation/rfc0029/pointer-count-readers/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "README.md": "6d9c9e4d131f753c8a15758f5dc0cf00d9643d1be31802fb33fb10d1d0b6e782", + "good.c": "0ded3e71899a4083d0224bfbb47d33677c3fac17f508d1f272e02f6b66efaff4", + "manifest.json": "1c1455b168a46cd4785a1247476c7a0c4158d3488f3a4984a594ab1cd1cbd084", + "over-step.c": "56f5fcfbb2f6fe7634e7b0e997bd066abec76ddb0a1ac0c305cb67affd15280f", + "reordered.c": "cb6340ec58383b2d8f7b1dc22b96121e817ae904746ea614ab2c8090c8dfd7b6", + "short.c": "b622726f3da833a961d2d2dc973cda27d7b9352b22f7bcd88c4c04b092355024", + "uninitialized.c": "e9d207158b1978f508ae59d77343b64cbb30d6f40aa6808ac06d491b5bc87115" +} diff --git a/test/evaluation/rfc0029/pointer-count-readers/good.c b/test/evaluation/rfc0029/pointer-count-readers/good.c new file mode 100644 index 00000000..98a95073 --- /dev/null +++ b/test/evaluation/rfc0029/pointer-count-readers/good.c @@ -0,0 +1,12 @@ +#include +int scan(const unsigned char *data,size_t size) { + const unsigned char *first=data+1,*last=data+1; + while((size_t)(last-data)=size)return 0;last++;} + last++; + } + if((size_t)(last-data)>=size || *last!='"')return 0; + while(first +int scan(const unsigned char *data,size_t size) { + const unsigned char *first=data+1,*last=data+1; + while((size_t)(last-data)=size)return 0;last++;} + last++; + } + if((size_t)(last-data)>=size || *last!='"')return 0; + while(first +int scan(size_t size,const unsigned char *data) { + const unsigned char *first=data+1,*last=data+1; + while((size_t)(last-data)=size)return 0;last++;} + last++; + } + if((size_t)(last-data)>=size || *last!='"')return 0; + while(first +int scan(const unsigned char *data,size_t size) { + const unsigned char *first=data+1,*last=data+1; + while((size_t)(last-data)=size)return 0;last++;} + last++; + } + if((size_t)(last-data)>=size || *last!='"')return 0; + while(first +int scan(const unsigned char *data,size_t size) { + const unsigned char *first=data+1,*last=data+1; + while((size_t)(last-data)=size)return 0;last++;} + last++; + } + if((size_t)(last-data)>=size || *last!='"')return 0; + while(first +#include +int main(int argc,char **argv) { + (void)argv; + const char data[]="abcdef"; + const char *end=data+(argc>1?3:6); + size_t size=(size_t)(end-data); + char *output=malloc(size+1); + if (!output) return 0; + const char *input=data; + char *cursor=output; end=data+7; + while(input +#include +int main(int argc,char **argv) { + (void)argv; + const char data[]="abcdef"; + const char *end=data+(argc>1?3:6); + const char unrelated[7]={0}; + size_t size=(size_t)(end-unrelated); + char *output=malloc(size+1); + if (!output) return 0; + const char *input=data; + char *cursor=output; + while(input +#include +int main(int argc,char **argv) { + (void)argv; + const char data[]="abcdef"; + const char *end=data+(argc>1?3:6); + size_t size=(size_t)(end-data); + char *output=malloc(size+1); + if (!output) return 0; + const char *input=data; + char *cursor=output; + while(input +#include +int main(int argc,char **argv) { + (void)argv; + const char data[]="abcdef"; + const char *end=data+(argc>1?3:6); + size_t size=(size_t)(end-(data+1)); + char *output=malloc(size+1); + if (!output) return 0; + const char *input=data+1; + char *cursor=output; + while(input +#include +int main(int argc,char **argv) { + (void)argv; + const char data[]="abcdef"; + const char *end=data+(argc>1?3:6); + size_t size=(size_t)(end-data); + char *output=malloc(size+1); + if (!output) return 0; + const char *input=data; + char *cursor=output; + while(input +#include +int main(int argc,char **argv) { + (void)argv; + const char data[]="abcdef"; + const char *end=data+(argc>1?3:6); + size_t size=(size_t)(end-data); + char *output=malloc(size); + if (!output) return 0; + const char *input=data; + char *cursor=output; + while(inputdata || r->position>=r->capacity)return 0; + const unsigned char *cursor=r->data+r->position; + while((size_t)(cursor-r->data)capacity) { + cursor+=8; + if(*cursor=='x')return 1; + ++cursor; + } + return 0; +} +int main(void) { const unsigned char data[4]={'a','b','c','x'}; struct reader r={data,4,0,0};return scan(&r); } diff --git a/test/evaluation/rfc0029/pointer-reader-offsets/forged.c b/test/evaluation/rfc0029/pointer-reader-offsets/forged.c new file mode 100644 index 00000000..4b27ef4a --- /dev/null +++ b/test/evaluation/rfc0029/pointer-reader-offsets/forged.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void) { const unsigned char data[4]={'a','b','c','x'}; struct reader r={data,40,0,0};return scan(&r); } diff --git a/test/evaluation/rfc0029/pointer-reader-offsets/frozen-sha256.json b/test/evaluation/rfc0029/pointer-reader-offsets/frozen-sha256.json new file mode 100644 index 00000000..e68de4c2 --- /dev/null +++ b/test/evaluation/rfc0029/pointer-reader-offsets/frozen-sha256.json @@ -0,0 +1,12 @@ +{ + "README.md": "aee2a7b897774f6e3b69ccff1517d779fa35cab100e4a07a76f88108995bb75a", + "escape.c": "2dac8ff8cb0ef6789f642e8f91132c31451ad2da4c816413e75f3732dcc98776", + "forged.c": "dfabe5f4a308df78cef4aabd4aee07b612bd57881adb073163627f6907c6dc93", + "good.c": "da8288991222616492fdb2d588c38fb05603f0261903272f490c8f4264c1615c", + "library.c": "7221944f97723607eef71251f8d6925986c7f374181a56c8ea7900436def7434", + "manifest.json": "4783624f0e6ef2764c118ecc2b57ec6bc642adeea4f9c79b6ed8a413a09f73da", + "past-end.c": "314e3c14d92be3dedd43896ac842fe27600ada85307dcdad18a62836d43238cd", + "reader.h": "db2d2c314d78f5ac41650031f7cb6ea48cc5910bbeda9f533716bb74a1e732b8", + "uninitialized.c": "be7c95e9abc41c1ecba6140c5fe0242a1b3923dd502672db09e5b587e857e28a", + "unrelated.c": "b5f06bfe884811105635d5daad3882aa9baf65a08a57ec2df17d366d009637ce" +} diff --git a/test/evaluation/rfc0029/pointer-reader-offsets/good.c b/test/evaluation/rfc0029/pointer-reader-offsets/good.c new file mode 100644 index 00000000..ae206665 --- /dev/null +++ b/test/evaluation/rfc0029/pointer-reader-offsets/good.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void) { const unsigned char data[4]={'a','b','c','x'}; struct reader r={data,4,0,0};return scan(&r); } diff --git a/test/evaluation/rfc0029/pointer-reader-offsets/library.c b/test/evaluation/rfc0029/pointer-reader-offsets/library.c new file mode 100644 index 00000000..24b12872 --- /dev/null +++ b/test/evaluation/rfc0029/pointer-reader-offsets/library.c @@ -0,0 +1,10 @@ +#include "reader.h" +int scan(struct reader *r) { + if(!r || !r->data || r->position>=r->capacity)return 0; + const unsigned char *cursor=r->data+r->position; + while((size_t)(cursor-r->data)capacity) { + if(*cursor=='x')return 1; + ++cursor; + } + return 0; +} diff --git a/test/evaluation/rfc0029/pointer-reader-offsets/manifest.json b/test/evaluation/rfc0029/pointer-reader-offsets/manifest.json new file mode 100644 index 00000000..f9418afe --- /dev/null +++ b/test/evaluation/rfc0029/pointer-reader-offsets/manifest.json @@ -0,0 +1,112 @@ +{ + "version": 1, + "cases": [ + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "scan" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "past-end", + "sources": [ + "past-end.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bounds|extent|shared-object|initialized" + }, + { + "name": "escape", + "sources": [ + "escape.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bounds|extent|shared-object|initialized" + }, + { + "name": "unrelated", + "sources": [ + "unrelated.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bounds|extent|shared-object|initialized" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bounds|extent|shared-object|initialized" + }, + { + "name": "forged", + "sources": [ + "forged.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bounds|extent|shared-object|initialized" + } + ] +} diff --git a/test/evaluation/rfc0029/pointer-reader-offsets/past-end.c b/test/evaluation/rfc0029/pointer-reader-offsets/past-end.c new file mode 100644 index 00000000..4f2d7bf2 --- /dev/null +++ b/test/evaluation/rfc0029/pointer-reader-offsets/past-end.c @@ -0,0 +1,11 @@ +#include "reader.h" +int scan(struct reader *r) { + if(!r || !r->data || r->position>=r->capacity)return 0; + const unsigned char *cursor=r->data+r->position; + while((size_t)(cursor-r->data)<=r->capacity) { + if(*cursor=='x')return 1; + ++cursor; + } + return 0; +} +int main(void) { const unsigned char data[4]={'a','b','c','a'}; struct reader r={data,4,0,0};return scan(&r); } diff --git a/test/evaluation/rfc0029/pointer-reader-offsets/reader.h b/test/evaluation/rfc0029/pointer-reader-offsets/reader.h new file mode 100644 index 00000000..6feb26e6 --- /dev/null +++ b/test/evaluation/rfc0029/pointer-reader-offsets/reader.h @@ -0,0 +1,3 @@ +#include +struct reader {const unsigned char *data; size_t capacity,position; unsigned depth;}; +int scan(struct reader *); diff --git a/test/evaluation/rfc0029/pointer-reader-offsets/uninitialized.c b/test/evaluation/rfc0029/pointer-reader-offsets/uninitialized.c new file mode 100644 index 00000000..b07bd562 --- /dev/null +++ b/test/evaluation/rfc0029/pointer-reader-offsets/uninitialized.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void){unsigned char data[4];struct reader r={data,4,0,0};return scan(&r);} diff --git a/test/evaluation/rfc0029/pointer-reader-offsets/unrelated.c b/test/evaluation/rfc0029/pointer-reader-offsets/unrelated.c new file mode 100644 index 00000000..be3bdfeb --- /dev/null +++ b/test/evaluation/rfc0029/pointer-reader-offsets/unrelated.c @@ -0,0 +1,12 @@ +#include "reader.h" +int scan(struct reader *r) { + if(!r || !r->data || r->position>=r->capacity)return 0; + const unsigned char *cursor=r->data+r->position; + const unsigned char other[4]={0}; + while((size_t)(cursor-other)capacity) { + if(*cursor=='x')return 1; + ++cursor; + } + return 0; +} +int main(void) { const unsigned char data[4]={'a','b','c','x'}; struct reader r={data,4,0,0};return scan(&r); } diff --git a/test/evaluation/rfc0029/reader-construction/build.c b/test/evaluation/rfc0029/reader-construction/build.c new file mode 100644 index 00000000..d2326aec --- /dev/null +++ b/test/evaluation/rfc0029/reader-construction/build.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader r) { + if (!r.remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r.data[0]; + if(r.remaining>1){ + r.data++; r.remaining--; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,3}; struct node *p=build(r); destroy(p); return r.remaining!=3; } diff --git a/test/evaluation/rfc0029/reader-construction/cycle.c b/test/evaluation/rfc0029/reader-construction/cycle.c new file mode 100644 index 00000000..484697b3 --- /dev/null +++ b/test/evaluation/rfc0029/reader-construction/cycle.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader r) { + if (!r.remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r.data[0]; + if(r.remaining>1){ + r.data++; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,3}; struct node *p=build(r); destroy(p); return r.remaining!=3; } diff --git a/test/evaluation/rfc0029/reader-construction/double.c b/test/evaluation/rfc0029/reader-construction/double.c new file mode 100644 index 00000000..910ffd4d --- /dev/null +++ b/test/evaluation/rfc0029/reader-construction/double.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader r) { + if (!r.remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r.data[0]; + if(r.remaining>1){ + r.data++; r.remaining--; + p->next=build(r); + if(!p->next){free(p);free(p);return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,3}; struct node *p=build(r); destroy(p); return r.remaining!=3; } diff --git a/test/evaluation/rfc0029/reader-construction/escape.c b/test/evaluation/rfc0029/reader-construction/escape.c new file mode 100644 index 00000000..c5abe03c --- /dev/null +++ b/test/evaluation/rfc0029/reader-construction/escape.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader r) { + if (!r.remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r.data[0]; + if(r.remaining>1){ + r.data+=2; r.remaining--; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,3}; struct node *p=build(r); destroy(p); return r.remaining!=3; } diff --git a/test/evaluation/rfc0029/reader-construction/frozen-sha256.json b/test/evaluation/rfc0029/reader-construction/frozen-sha256.json new file mode 100644 index 00000000..18c5d8d1 --- /dev/null +++ b/test/evaluation/rfc0029/reader-construction/frozen-sha256.json @@ -0,0 +1,11 @@ +{ + "build.c": "eccbca4e4bfce48ec01f4203d9a0717937206d30471cfe167216c41476080ce8", + "cycle.c": "63fa1b3b0eb5b633fad3c8167181cde516473bb7852c14015210d0c534763921", + "double.c": "d621df42c2d1d48a88e31b7912e691ce26ca5cfe9b7f31469108f37d63ced9b0", + "escape.c": "21b2407bc3f6f40c2d90a4e1468f1be2bd0265c20e4986cdc0f6f2fdcc36ce2b", + "leak.c": "03fb79f1a3c436445df16908334354a425122c85ec0355debaf3b26a22570f70", + "manifest.json": "e4e69d8d42c3619e389266116b452603963ab35138aadafbcb35a913d10bc9ef", + "provenance.md": "ed2fafbb9e356c2e14a3d35a0348870a8dedb6587c811855b1833bc1928e4b5a", + "short.c": "61fa40bfae7f21e68ca15558c25e04f9ad3e3234f81720a0608c9f9ce5b3fbfc", + "uninitialized.c": "2ae29978da6a883e9e81a76ff04e9f0144b42a1ce6b806ede94773899bd1a876" +} diff --git a/test/evaluation/rfc0029/reader-construction/leak.c b/test/evaluation/rfc0029/reader-construction/leak.c new file mode 100644 index 00000000..c9758c89 --- /dev/null +++ b/test/evaluation/rfc0029/reader-construction/leak.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader r) { + if (!r.remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r.data[0]; + if(r.remaining>1){ + r.data++; r.remaining--; + p->next=build(r); + if(!p->next){return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,3}; struct node *p=build(r); destroy(p); return r.remaining!=3; } diff --git a/test/evaluation/rfc0029/reader-construction/manifest.json b/test/evaluation/rfc0029/reader-construction/manifest.json new file mode 100644 index 00000000..c88dc68d --- /dev/null +++ b/test/evaluation/rfc0029/reader-construction/manifest.json @@ -0,0 +1,116 @@ +{ + "version": 1, + "cases": [ + { + "name": "reader-construction-build", + "sources": [ + "build.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "reader-construction-short", + "sources": [ + "short.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + }, + { + "name": "reader-construction-uninitialized", + "sources": [ + "uninitialized.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + }, + { + "name": "reader-construction-cycle", + "sources": [ + "cycle.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|progress" + }, + { + "name": "reader-construction-leak", + "sources": [ + "leak.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "footprint|leak|cleanup|contract" + }, + { + "name": "reader-construction-double", + "sources": [ + "double.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "freed|release" + }, + { + "name": "reader-construction-escape", + "sources": [ + "escape.c" + ], + "functions": [ + "build", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|extent|bounds|recursive" + } + ] +} diff --git a/test/evaluation/rfc0029/reader-construction/provenance.md b/test/evaluation/rfc0029/reader-construction/provenance.md new file mode 100644 index 00000000..2a31200c --- /dev/null +++ b/test/evaluation/rfc0029/reader-construction/provenance.md @@ -0,0 +1 @@ +Frozen before copied-record recursive construction inference on 2026-09-16. The reader includes an unrelated depth field and a runtime remaining interval. Its by-value updates must preserve the caller record. Mutants cover physical truncation, an uninitialized tail, a nondecreasing length, a pointer escaping the entry interval, and missing or repeated failure cleanup. Candidate 21 before reader support supplies the retained before observation. This population does not replace the mandatory mutable-reader cJSON workflows. diff --git a/test/evaluation/rfc0029/reader-construction/short.c b/test/evaluation/rfc0029/reader-construction/short.c new file mode 100644 index 00000000..a4294def --- /dev/null +++ b/test/evaluation/rfc0029/reader-construction/short.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader r) { + if (!r.remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r.data[0]; + if(r.remaining>1){ + r.data++; r.remaining--; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; +} +int main(void) { const unsigned char data[]={1,2,3}; struct reader r={42,data,4}; struct node *p=build(r); destroy(p); return r.remaining!=3; } diff --git a/test/evaluation/rfc0029/reader-construction/uninitialized.c b/test/evaluation/rfc0029/reader-construction/uninitialized.c new file mode 100644 index 00000000..8209ca27 --- /dev/null +++ b/test/evaluation/rfc0029/reader-construction/uninitialized.c @@ -0,0 +1,16 @@ +#include +struct reader { size_t depth; const unsigned char *data; size_t remaining; }; +struct node { unsigned char value; struct node *next; }; +static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(struct reader r) { + if (!r.remaining) return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r.data[0]; + if(r.remaining>1){ + r.data++; r.remaining--; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; +} +int main(void) { unsigned char data[3]; data[0]=1; struct reader r={42,data,3}; struct node *p=build(r); destroy(p); return r.remaining!=3; } diff --git a/test/evaluation/rfc0029/reader-good.c b/test/evaluation/rfc0029/reader-good.c new file mode 100644 index 00000000..df40c475 --- /dev/null +++ b/test/evaluation/rfc0029/reader-good.c @@ -0,0 +1,9 @@ +#include "reader.h" +int main(void) { + const unsigned char text[] = {1, 2, 3}; + struct reader r = {text, sizeof text, 0, 8}; + if (forwarded(&r) != 1) return 1; + if (forwarded(&r) != 2) return 1; + if (forwarded(&r) != 3) return 1; + return forwarded(&r) != -1; +} diff --git a/test/evaluation/rfc0029/reader-index-loops/README.md b/test/evaluation/rfc0029/reader-index-loops/README.md new file mode 100644 index 00000000..e7652fea --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/README.md @@ -0,0 +1 @@ +RFC 0029 discovered reader traversal regression, frozen before induction support: unit-stride indexing relative to a stable reader position, with a strict unsigned extent guard, outgoing goto, and switch. Negative clients violate the extent, initialization, index or stable-position premises. diff --git a/test/evaluation/rfc0029/reader-index-loops/body-cursor.c b/test/evaluation/rfc0029/reader-index-loops/body-cursor.c new file mode 100644 index 00000000..2452e949 --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/body-cursor.c @@ -0,0 +1,10 @@ +#include "reader.h" +int scan(struct reader *r) { + if (!r || !r->data) return 0; + for (size_t i = 0; r->position + i < r->capacity; ++i) { + r->position=r->capacity; + if ((r->data + r->position)[i] == 'x') return 1; + } + return 0; +} +int main(void) { unsigned char input[1]={'0'}; struct reader r={input,1,0,0}; return scan(&r); } diff --git a/test/evaluation/rfc0029/reader-index-loops/body-index.c b/test/evaluation/rfc0029/reader-index-loops/body-index.c new file mode 100644 index 00000000..f40f3a0a --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/body-index.c @@ -0,0 +1,10 @@ +#include "reader.h" +int scan(struct reader *r) { + if (!r || !r->data) return 0; + for (size_t i = 0; r->position + i < r->capacity; ++i) { + i=r->capacity; + if ((r->data + r->position)[i] == 'x') return 1; + } + return 0; +} +int main(void) { unsigned char input[1]={'0'}; struct reader r={input,1,0,0}; return scan(&r); } diff --git a/test/evaluation/rfc0029/reader-index-loops/forged.c b/test/evaluation/rfc0029/reader-index-loops/forged.c new file mode 100644 index 00000000..225bb494 --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/forged.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void) { unsigned char input[2]={'x','0'}; struct reader r={input,5,1,0}; return scan(&r); } diff --git a/test/evaluation/rfc0029/reader-index-loops/frozen-sha256.json b/test/evaluation/rfc0029/reader-index-loops/frozen-sha256.json new file mode 100644 index 00000000..221050fc --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/frozen-sha256.json @@ -0,0 +1,12 @@ +{ + "README.md": "5cb2327c413777acaf6f51d43bf0a8e1c8d639352e6186255055383e199b4a7e", + "body-cursor.c": "bcc4ef2085bfade65bf0d164ee82161d765f1cc187e4d31b07a09b9e545438bb", + "body-index.c": "17cf871bf1ff72b1203698f6f9472e01b313272a965c6ec8b96430e84836e7f1", + "forged.c": "469ddde6ade5ed8e1ccaa6d8534db08e8fe9d32307a1d926591df9ab554716d9", + "good.c": "a13e62413345ac036b8a8257826c892b6b19d5900ce8da22ad4938d4908a75bc", + "library.c": "e4927cfa47486334180463e80b48888ba47f1454e2a7823e1351a1d623e61e31", + "manifest.json": "d45f30e3b56be6d25544fbc6bd4d53aa10b6c38ecb83752c0473b93d8bd348e0", + "off-by-one.c": "3d3ceadf56551c27379908214dfb721b73727fa9fd307212ba3ec26f3b417175", + "reader.h": "984e4959143c3852354429689d6999c6acfc39268ea913849fd9b1cfb8e434b3", + "uninitialized.c": "27340651f7145fa2d03b82f675c334a59aec826c08e271ff68e43ce24e260cd5" +} diff --git a/test/evaluation/rfc0029/reader-index-loops/good.c b/test/evaluation/rfc0029/reader-index-loops/good.c new file mode 100644 index 00000000..5f996bb3 --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/good.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void) { unsigned char input[5]={'x','0','1','0','x'}; struct reader r={input,5,1,0}; return scan(&r); } diff --git a/test/evaluation/rfc0029/reader-index-loops/library.c b/test/evaluation/rfc0029/reader-index-loops/library.c new file mode 100644 index 00000000..e5b38f53 --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/library.c @@ -0,0 +1,12 @@ +#include "reader.h" +int scan(struct reader *r) { + if (!r || !r->data) return 0; + for (size_t i = 0; r && r->position + i < r->capacity; ++i) { + switch ((r->data + r->position)[i]) { + case '0': case '1': break; + default: goto done; + } + } +done: + return 1; +} diff --git a/test/evaluation/rfc0029/reader-index-loops/manifest.json b/test/evaluation/rfc0029/reader-index-loops/manifest.json new file mode 100644 index 00000000..24894ef4 --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/manifest.json @@ -0,0 +1,112 @@ +{ + "version": 1, + "cases": [ + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "scan" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "forged", + "sources": [ + "forged.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|interval|represented|initialized|bounds" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|interval|represented|initialized|bounds" + }, + { + "name": "off-by-one", + "sources": [ + "off-by-one.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|interval|represented|initialized|bounds" + }, + { + "name": "body-index", + "sources": [ + "body-index.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|interval|represented|initialized|bounds" + }, + { + "name": "body-cursor", + "sources": [ + "body-cursor.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|interval|represented|initialized|bounds" + } + ] +} diff --git a/test/evaluation/rfc0029/reader-index-loops/off-by-one.c b/test/evaluation/rfc0029/reader-index-loops/off-by-one.c new file mode 100644 index 00000000..391e08aa --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/off-by-one.c @@ -0,0 +1,8 @@ +#include "reader.h" +int scan(struct reader *r) { + if (!r || !r->data) return 0; + for (size_t i = 0; r->position + i <= r->capacity; ++i) + if ((r->data + r->position)[i] == 'x') return 1; + return 0; +} +int main(void) { unsigned char input[1]={'0'}; struct reader r={input,1,0,0}; return scan(&r); } diff --git a/test/evaluation/rfc0029/reader-index-loops/reader.h b/test/evaluation/rfc0029/reader-index-loops/reader.h new file mode 100644 index 00000000..d5111153 --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/reader.h @@ -0,0 +1,3 @@ +#include +struct reader { const unsigned char *data; size_t capacity, position; unsigned depth; }; +int scan(struct reader *r); diff --git a/test/evaluation/rfc0029/reader-index-loops/uninitialized.c b/test/evaluation/rfc0029/reader-index-loops/uninitialized.c new file mode 100644 index 00000000..6b91beaf --- /dev/null +++ b/test/evaluation/rfc0029/reader-index-loops/uninitialized.c @@ -0,0 +1,2 @@ +#include "reader.h" +int main(void) { unsigned char input[5]; input[0]='x'; struct reader r={input,5,1,0}; return scan(&r); } diff --git a/test/evaluation/rfc0029/reader-short-bad.c b/test/evaluation/rfc0029/reader-short-bad.c new file mode 100644 index 00000000..013a960c --- /dev/null +++ b/test/evaluation/rfc0029/reader-short-bad.c @@ -0,0 +1,6 @@ +#include "reader.h" +int main(void) { + const unsigned char text[] = {1}; + struct reader r = {text, 4, 3, 8}; + return forwarded(&r); +} diff --git a/test/evaluation/rfc0029/reader.h b/test/evaluation/rfc0029/reader.h new file mode 100644 index 00000000..c06c85b5 --- /dev/null +++ b/test/evaluation/rfc0029/reader.h @@ -0,0 +1,10 @@ +/* The initialized input extent and the consumed position are distinct. */ +#include +struct reader { const unsigned char *data; size_t size, position, depth; }; +static int take(struct reader *r) { + if (r->position >= r->size) return -1; + int value = r->data[r->position]; + ++r->position; + return value; +} +static int forwarded(struct reader *r) { return take(r); } diff --git a/test/evaluation/rfc0029/readers/audit.md b/test/evaluation/rfc0029/readers/audit.md new file mode 100644 index 00000000..191c60a8 --- /dev/null +++ b/test/evaluation/rfc0029/readers/audit.md @@ -0,0 +1,15 @@ +# Initial reader probe audit + +The initial manifest is retained unchanged, including an invalid negative: +`reader-escape` starts beyond the extent but calls `take`, whose guard returns +without accessing data. That program is safe and cannot support a false-proof +claim. It is excluded from the corrected correctness population; the replacement +must actually access the escaped cursor. Its initial failed expectation remains +recorded in the development results. + +Both the immutable HEAD baseline and candidate 15 wrongly accept `reader-short` +and `reader-uninitialized`: `consume` accesses every position through `end`, but +its exported contract only requires the cell at the incoming `position`. +The affine projection used an entry path even after a numeric write. This is a +pre-existing soundness bug, independently discovered after the main frozen +population and full candidate-15 Debug/sanitizer validation. diff --git a/test/evaluation/rfc0029/readers/client.c b/test/evaluation/rfc0029/readers/client.c new file mode 100644 index 00000000..e3e4cb42 --- /dev/null +++ b/test/evaluation/rfc0029/readers/client.c @@ -0,0 +1,8 @@ +#include "cursor.h" +int main(void) { + const unsigned char data[] = {1, 2, 3, 4}; + struct cursor c = {0, 0, data, sizeof data, 0}; + unsigned char first = 0; + if (!take(&c, &first)) return 1; + return (int)(consume(&c) + first); +} diff --git a/test/evaluation/rfc0029/readers/cursor-escape.c b/test/evaluation/rfc0029/readers/cursor-escape.c new file mode 100644 index 00000000..a589e326 --- /dev/null +++ b/test/evaluation/rfc0029/readers/cursor-escape.c @@ -0,0 +1,7 @@ +#include "cursor.h" +int main(void) { + const unsigned char data[] = {1, 2}; + struct cursor c = {0, 0, data, sizeof data, 3}; + unsigned char out = 0; + return take(&c, &out); +} diff --git a/test/evaluation/rfc0029/readers/cursor.c b/test/evaluation/rfc0029/readers/cursor.c new file mode 100644 index 00000000..a11287d5 --- /dev/null +++ b/test/evaluation/rfc0029/readers/cursor.c @@ -0,0 +1,15 @@ +#include "cursor.h" +int take(struct cursor *c, unsigned char *out) { + if (c->position >= c->end) return 0; + *out = c->data[c->position]; + c->position++; + return 1; +} +unsigned consume(struct cursor *c) { + unsigned sum = 0; + while (c->position < c->end) { + sum += c->data[c->position]; + c->position++; + } + return sum; +} diff --git a/test/evaluation/rfc0029/readers/cursor.h b/test/evaluation/rfc0029/readers/cursor.h new file mode 100644 index 00000000..267a74e1 --- /dev/null +++ b/test/evaluation/rfc0029/readers/cursor.h @@ -0,0 +1,4 @@ +#include +struct cursor { unsigned flags; size_t depth; const unsigned char *data; size_t end, position; }; +int take(struct cursor *, unsigned char *); +unsigned consume(struct cursor *); diff --git a/test/evaluation/rfc0029/readers/escaped-access.c b/test/evaluation/rfc0029/readers/escaped-access.c new file mode 100644 index 00000000..d6c77d1f --- /dev/null +++ b/test/evaluation/rfc0029/readers/escaped-access.c @@ -0,0 +1,7 @@ +#include "cursor.h" +static unsigned peek(const struct cursor *c) { return c->data[c->position]; } +int main(void) { + const unsigned char data[] = {1, 2}; + struct cursor c = {0, 0, data, sizeof data, 3}; + return (int)peek(&c); +} diff --git a/test/evaluation/rfc0029/readers/frozen-sha256.json b/test/evaluation/rfc0029/readers/frozen-sha256.json new file mode 100644 index 00000000..749673f1 --- /dev/null +++ b/test/evaluation/rfc0029/readers/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "client.c": "68e1aec9d1e840d7f62fccb9b8a6e44395db424eaa2434de416eb0565512e61f", + "cursor-escape.c": "07bb67da9b0e623386a84bec743445f4403e7c0403f5b3e1540da2f1050ebce9", + "cursor.c": "c29ecb9d8f68070018fefef271dd65f6410a56596dc15f6c8ef4d97e6b6fdbd0", + "cursor.h": "b89e5f72e74a1637db537ec9c90a7e45b0ccfb7c65941533114d4990dbb97c4e", + "manifest.json": "cf2f0cd1bf921a586f24f99b7e609f8dc42b72b59a38b9e43463103349c9dd71", + "provenance.md": "da9273b1076dbc8635ef17d890beed5325a430ce609568c0acf304daa05faea6", + "short.c": "d21f249ec0e315981ac97e8024916a5afc4588365d112082177c325809ff549d", + "uninitialized.c": "8a0e7db24809a4aa43a1a34094dc8a65e275bfe929d398ee986754ceaa3f948e" +} diff --git a/test/evaluation/rfc0029/readers/manifest.json b/test/evaluation/rfc0029/readers/manifest.json new file mode 100644 index 00000000..a16b1544 --- /dev/null +++ b/test/evaluation/rfc0029/readers/manifest.json @@ -0,0 +1,84 @@ +{ + "version": 1, + "cases": [ + { + "name": "reader-client", + "sources": [ + "client.c", + "cursor.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "reader-generic", + "sources": [ + "cursor.c" + ], + "functions": [ + "take", + "consume" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "reader-short", + "sources": [ + "short.c", + "cursor.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|extent|initialized|bounds" + }, + { + "name": "reader-uninitialized", + "sources": [ + "uninitialized.c", + "cursor.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|extent|initialized|bounds" + }, + { + "name": "reader-escape", + "sources": [ + "cursor-escape.c", + "cursor.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|extent|initialized|bounds" + } + ] +} diff --git a/test/evaluation/rfc0029/readers/provenance.md b/test/evaluation/rfc0029/readers/provenance.md new file mode 100644 index 00000000..529f4ebf --- /dev/null +++ b/test/evaluation/rfc0029/readers/provenance.md @@ -0,0 +1,8 @@ +# Reader population + +Independently frozen after candidate 15, before adding a fully initialized +reader predicate. The original RFC 0029 populations are unchanged. These cases +exercise const byte input, reordered cursor/extent fields, unrelated flags and +depth, separate-source forwarding, a runtime loop, truncated or uninitialized +input, and a cursor outside its advertised extent. Generic helpers must infer +explicit reader premises; the closed client must discharge them. diff --git a/test/evaluation/rfc0029/readers/reviewed-manifest.json b/test/evaluation/rfc0029/readers/reviewed-manifest.json new file mode 100644 index 00000000..c20b37a1 --- /dev/null +++ b/test/evaluation/rfc0029/readers/reviewed-manifest.json @@ -0,0 +1,99 @@ +{ + "version": 1, + "cases": [ + { + "name": "reader-client", + "sources": [ + "client.c", + "cursor.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "reader-generic", + "sources": [ + "cursor.c" + ], + "functions": [ + "take", + "consume" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "reader-short", + "sources": [ + "short.c", + "cursor.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|extent|initialized|bounds" + }, + { + "name": "reader-uninitialized", + "sources": [ + "uninitialized.c", + "cursor.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|extent|initialized|bounds" + }, + { + "name": "reader-guarded-escape", + "sources": [ + "cursor-escape.c", + "cursor.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "reader-escaped-access", + "sources": [ + "escaped-access.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|fit|past" + } + ] +} diff --git a/test/evaluation/rfc0029/readers/reviewed-sha256.json b/test/evaluation/rfc0029/readers/reviewed-sha256.json new file mode 100644 index 00000000..d9e86a2a --- /dev/null +++ b/test/evaluation/rfc0029/readers/reviewed-sha256.json @@ -0,0 +1,5 @@ +{ + "escaped-access.c": "18ae13e51982b2339110f0df68549c107b9845625ada5a8b1c033116f91e87c4", + "reviewed-manifest.json": "e36f1b196867cc0f82460f8a428d9f4913de3a0248a6ca7a92db55ca5fa269d2", + "audit.md": "39e708a7a5d4a7995f9b39dabc1102938629242e179193a8acd964907b772344" +} diff --git a/test/evaluation/rfc0029/readers/short.c b/test/evaluation/rfc0029/readers/short.c new file mode 100644 index 00000000..0c99493c --- /dev/null +++ b/test/evaluation/rfc0029/readers/short.c @@ -0,0 +1,6 @@ +#include "cursor.h" +int main(void) { + const unsigned char data[] = {1, 2}; + struct cursor c = {0, 0, data, 4, 0}; + return (int)consume(&c); +} diff --git a/test/evaluation/rfc0029/readers/uninitialized.c b/test/evaluation/rfc0029/readers/uninitialized.c new file mode 100644 index 00000000..b0f4a369 --- /dev/null +++ b/test/evaluation/rfc0029/readers/uninitialized.c @@ -0,0 +1,6 @@ +#include "cursor.h" +int main(void) { + unsigned char data[4]; data[0] = 1; + struct cursor c = {0, 0, data, sizeof data, 0}; + return (int)consume(&c); +} diff --git a/test/evaluation/rfc0029/reallocation-ledger-failures/failure-leak.c b/test/evaluation/rfc0029/reallocation-ledger-failures/failure-leak.c new file mode 100644 index 00000000..da66de92 --- /dev/null +++ b/test/evaluation/rfc0029/reallocation-ledger-failures/failure-leak.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data = malloc(4); + if (!data) return 0; + data[0] = 0; + unsigned char *out = realloc(data, 2); if (!out) return 0; return out; +} +int main(void) { + struct node child = {2,0,0}, root = {1,&child,0}; + unsigned char *data = make(&root); + free(data); return 0; +} diff --git a/test/evaluation/rfc0029/reallocation-ledger-failures/frozen-sha256.json b/test/evaluation/rfc0029/reallocation-ledger-failures/frozen-sha256.json new file mode 100644 index 00000000..bbb3955d --- /dev/null +++ b/test/evaluation/rfc0029/reallocation-ledger-failures/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "failure-leak.c": "cf2caad7cc11f277ed1bff22657cb6fa3b5739ee8422359151efe1408453cf75", + "lost-result.c": "566ef91c6d88c316a636698ff5899f9a5e3a5f412a6d804821157bdfb4dc88d7", + "manifest.json": "1199bddb6bf9c6cba69b679e159870e97f602bc658cabe233c81bc867e68ea1d", + "null-input.c": "99050029d111d7a6f6df8ea4efd355a0101dd2a2bd934d12f8ccc71b8e74cd60", + "overwrite-failure.c": "1351d69a600fc13ccdbcfa42b12b63b5bce21790684a27f519d4aa3db69984b8", + "stale-release.c": "1eab800e38867c560a3ad6539ea368c37daf57de74635aa198cf4fb91e0af012", + "zero-size.c": "98304b580a0308fe5eefc38c6b5b8db17013079c2ff7206e3ab8549fc6fc87e4" +} diff --git a/test/evaluation/rfc0029/reallocation-ledger-failures/lost-result.c b/test/evaluation/rfc0029/reallocation-ledger-failures/lost-result.c new file mode 100644 index 00000000..63a40260 --- /dev/null +++ b/test/evaluation/rfc0029/reallocation-ledger-failures/lost-result.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data = malloc(4); + if (!data) return 0; + data[0] = 0; + unsigned char *out = realloc(data, 2); if (!out) { free(data); return 0; } return 0; +} +int main(void) { + struct node child = {2,0,0}, root = {1,&child,0}; + unsigned char *data = make(&root); + free(data); return 0; +} diff --git a/test/evaluation/rfc0029/reallocation-ledger-failures/manifest.json b/test/evaluation/rfc0029/reallocation-ledger-failures/manifest.json new file mode 100644 index 00000000..dec8b062 --- /dev/null +++ b/test/evaluation/rfc0029/reallocation-ledger-failures/manifest.json @@ -0,0 +1,100 @@ +{ + "version": 1, + "cases": [ + { + "name": "failure-leak", + "sources": [ + "failure-leak.c" + ], + "functions": [ + "make", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "allocation|footprint|leak|release|freed|live|positive size" + }, + { + "name": "stale-release", + "sources": [ + "stale-release.c" + ], + "functions": [ + "make", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "allocation|footprint|leak|release|freed|live|positive size" + }, + { + "name": "lost-result", + "sources": [ + "lost-result.c" + ], + "functions": [ + "make", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "allocation|footprint|leak|release|freed|live|positive size" + }, + { + "name": "overwrite-failure", + "sources": [ + "overwrite-failure.c" + ], + "functions": [ + "make", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "allocation|footprint|leak|release|freed|live|positive size" + }, + { + "name": "zero-size", + "sources": [ + "zero-size.c" + ], + "functions": [ + "make", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "allocation|footprint|leak|release|freed|live|positive size" + }, + { + "name": "null-input", + "sources": [ + "null-input.c" + ], + "functions": [ + "make", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/reallocation-ledger-failures/null-input.c b/test/evaluation/rfc0029/reallocation-ledger-failures/null-input.c new file mode 100644 index 00000000..d96eed1a --- /dev/null +++ b/test/evaluation/rfc0029/reallocation-ledger-failures/null-input.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data = malloc(4); + if (!data) return 0; + data[0] = 0; + free(data); data = 0; unsigned char *out = realloc(data, 2); if (!out) return 0; return out; +} +int main(void) { + struct node child = {2,0,0}, root = {1,&child,0}; + unsigned char *data = make(&root); + free(data); return 0; +} diff --git a/test/evaluation/rfc0029/reallocation-ledger-failures/overwrite-failure.c b/test/evaluation/rfc0029/reallocation-ledger-failures/overwrite-failure.c new file mode 100644 index 00000000..b7b3265a --- /dev/null +++ b/test/evaluation/rfc0029/reallocation-ledger-failures/overwrite-failure.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data = malloc(4); + if (!data) return 0; + data[0] = 0; + data = realloc(data, 2); if (!data) return 0; return data; +} +int main(void) { + struct node child = {2,0,0}, root = {1,&child,0}; + unsigned char *data = make(&root); + free(data); return 0; +} diff --git a/test/evaluation/rfc0029/reallocation-ledger-failures/stale-release.c b/test/evaluation/rfc0029/reallocation-ledger-failures/stale-release.c new file mode 100644 index 00000000..1ea3da60 --- /dev/null +++ b/test/evaluation/rfc0029/reallocation-ledger-failures/stale-release.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data = malloc(4); + if (!data) return 0; + data[0] = 0; + unsigned char *out = realloc(data, 2); if (!out) { free(data); return 0; } free(data); return out; +} +int main(void) { + struct node child = {2,0,0}, root = {1,&child,0}; + unsigned char *data = make(&root); + free(data); return 0; +} diff --git a/test/evaluation/rfc0029/reallocation-ledger-failures/zero-size.c b/test/evaluation/rfc0029/reallocation-ledger-failures/zero-size.c new file mode 100644 index 00000000..f0b0e3d7 --- /dev/null +++ b/test/evaluation/rfc0029/reallocation-ledger-failures/zero-size.c @@ -0,0 +1,18 @@ +#include +struct node { unsigned value; struct node *left, *right; }; +unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); +} +unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data = malloc(4); + if (!data) return 0; + data[0] = 0; + unsigned char *out = realloc(data, 0); return out; +} +int main(void) { + struct node child = {2,0,0}, root = {1,&child,0}; + unsigned char *data = make(&root); + free(data); return 0; +} diff --git a/test/evaluation/rfc0029/reassigned-release.c b/test/evaluation/rfc0029/reassigned-release.c new file mode 100644 index 00000000..f64340d6 --- /dev/null +++ b/test/evaluation/rfc0029/reassigned-release.c @@ -0,0 +1,8 @@ +#include +static void destroy(void *p) { free(p); p = 0; } +int main(void) { + void *p = malloc(8); + if (!p) return 0; + destroy(p); + return 0; +} diff --git a/test/evaluation/rfc0029/record-arrays/README.md b/test/evaluation/rfc0029/record-arrays/README.md new file mode 100644 index 00000000..d4177009 --- /dev/null +++ b/test/evaluation/rfc0029/record-arrays/README.md @@ -0,0 +1,12 @@ +# Record-array workflow regressions (RFCs 0015/0029) + +Frozen before correcting array-arrow storage access and exact cell selection. +Baseline: candidate29b Release, weavec SHA-256 +`246dd010435569ba0b37ddf29065927678dae28c86685b50fe489bd931ac602b`. + +The positive cases exercise equivalent arrow, subscript and dereference +spellings, a forwarded first record, and a recursive writer called with a +one-element automatic record array. Its node has a known base-case selector; +the case does not claim arbitrary recursive input coverage. The negatives +require repeated-release, initialization and one-past extent rejection. +No source or expectation is amended to fit observed analyzer behavior. diff --git a/test/evaluation/rfc0029/record-arrays/alias-bad.c b/test/evaluation/rfc0029/record-arrays/alias-bad.c new file mode 100644 index 00000000..a88e2153 --- /dev/null +++ b/test/evaluation/rfc0029/record-arrays/alias-bad.c @@ -0,0 +1,6 @@ +#include +struct slot { int *p; unsigned n; }; +int main(void) { + struct slot a[2]; a->p = malloc(sizeof(int)); + a[1].p = (*a).p; free(a[1].p); free(a->p); return 0; +} diff --git a/test/evaluation/rfc0029/record-arrays/bounds-bad.c b/test/evaluation/rfc0029/record-arrays/bounds-bad.c new file mode 100644 index 00000000..61b5f17a --- /dev/null +++ b/test/evaluation/rfc0029/record-arrays/bounds-bad.c @@ -0,0 +1,5 @@ +#include +struct slot { int *p; unsigned n; }; +int main(void) { + struct slot a[1]; a->p = 0; a[1].p = 0; return 0; +} diff --git a/test/evaluation/rfc0029/record-arrays/forward.c b/test/evaluation/rfc0029/record-arrays/forward.c new file mode 100644 index 00000000..c29a9e22 --- /dev/null +++ b/test/evaluation/rfc0029/record-arrays/forward.c @@ -0,0 +1,7 @@ +#include +struct slot { int *p; unsigned n; }; +static void release(struct slot *s) { free(s->p); } +int main(void) { + struct slot a[1]; a->p = malloc(sizeof(int)); + release(a); return 0; +} diff --git a/test/evaluation/rfc0029/record-arrays/frozen-sha256.json b/test/evaluation/rfc0029/record-arrays/frozen-sha256.json new file mode 100644 index 00000000..1406a66d --- /dev/null +++ b/test/evaluation/rfc0029/record-arrays/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "README.md": "600127cef7518cb2ed6c05c3ce1ad13db4ecff78fb94e9ec057c74e7792ff8dc", + "alias-bad.c": "9fa6c6155b4f8c6b8f10c95c2f0c8b3b94bd5f8d4476658306343d79794e5b19", + "bounds-bad.c": "e61896b8f04a4d48eb67afb76dac31c88ed4702fc7e7756c85958b3e34698397", + "forward.c": "4201abed7513b25c377725800b77c609cce4b924dc3722fd7563ea873188f82d", + "manifest.json": "68db42a280114beb7891c1b07f55b396c46bfbcf65fe0f57ac2d06215453a3ee", + "spellings.c": "670a4bd8ccb6312eaacde8d229205fa7089a6406f8418beff6578b22d64d8f9f", + "uninit-bad.c": "fed4c54de81d4cd2af3e699f8a0ea8672d20e0048d9b6bd8dd238e8886d60b0f", + "writer.c": "769858c794ad12e7e4d284481600e4e38fbb1f3e408d8d614607020ca0330b82" +} diff --git a/test/evaluation/rfc0029/record-arrays/manifest.json b/test/evaluation/rfc0029/record-arrays/manifest.json new file mode 100644 index 00000000..10b09da6 --- /dev/null +++ b/test/evaluation/rfc0029/record-arrays/manifest.json @@ -0,0 +1,95 @@ +{ + "version": 1, + "cases": [ + { + "name": "record-array-spellings", + "sources": [ + "spellings.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "record-array-forward", + "sources": [ + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "record-array-alias-bad", + "sources": [ + "alias-bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "released|double|consumed|live|valid" + }, + { + "name": "record-array-uninit-bad", + "sources": [ + "uninit-bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "uninitialized|initializ" + }, + { + "name": "record-array-bounds-bad", + "sources": [ + "bounds-bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|outside" + }, + { + "name": "record-array-writer", + "sources": [ + "writer.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + } + ] +} diff --git a/test/evaluation/rfc0029/record-arrays/spellings.c b/test/evaluation/rfc0029/record-arrays/spellings.c new file mode 100644 index 00000000..6eec9811 --- /dev/null +++ b/test/evaluation/rfc0029/record-arrays/spellings.c @@ -0,0 +1,11 @@ +#include +struct slot { int *p; unsigned n; }; +int main(void) { + struct slot a[2]; + a->p = malloc(sizeof(int)); + a[1].p = malloc(sizeof(int)); + if (a[0].p) *(*a).p = 7; + if (a[1].p) *a[1].p = 9; + free((*a).p); free(a[1].p); + return 0; +} diff --git a/test/evaluation/rfc0029/record-arrays/uninit-bad.c b/test/evaluation/rfc0029/record-arrays/uninit-bad.c new file mode 100644 index 00000000..214267f5 --- /dev/null +++ b/test/evaluation/rfc0029/record-arrays/uninit-bad.c @@ -0,0 +1,5 @@ +#include +struct slot { int *p; unsigned n; }; +int main(void) { + struct slot a[2]; a[1].p = 0; free(a->p); return 0; +} diff --git a/test/evaluation/rfc0029/record-arrays/writer.c b/test/evaluation/rfc0029/record-arrays/writer.c new file mode 100644 index 00000000..8f802706 --- /dev/null +++ b/test/evaluation/rfc0029/record-arrays/writer.c @@ -0,0 +1,19 @@ +#include +#include +struct node {struct node *child;int kind;}; +struct writer {unsigned char *data;size_t cap,len,depth;int flags;}; +static int emit(const struct node *n,struct writer *w) { + if(n->kind==64) { + if(w->len>w->cap || w->cap-w->len<3)return 0; + unsigned char *p=w->data+w->len; + p[0]='{';p[1]='}';p[2]=0;w->len+=2;return 1; + } + return emit(n->child,w); +} +int main(void){ + struct node n={0,64};struct writer w[1];memset(w,0,sizeof w); + w->data=malloc(256);w->cap=256;if(!w->data)return 0; + if(!emit(&n,w)){free(w->data);return 0;} + int ok=strlen((char*)w->data)==2; + free(w->data);return !ok; +} diff --git a/test/evaluation/rfc0029/recursive-cases/frozen-sha256.json b/test/evaluation/rfc0029/recursive-cases/frozen-sha256.json new file mode 100644 index 00000000..d9fa1a04 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-cases/frozen-sha256.json @@ -0,0 +1,7 @@ +{ + "generic.c": "e22a59edfabc5ae7d300fe638fb7998070758d52576dba9c78d803f7f1653e0d", + "live.c": "2167f6abe42cdf59bd7e80440c19fa3a0888325d310b171636e10d5ce9c25a3d", + "null.c": "c58eaf3a7263be0c30d90a0746e9b0ea7b55df013e013aae7acd00ad06dd43c9", + "unknown.c": "58de955c22a5996d2b2cba7b598e03e3cb9eca9f79245a78c05ee7822853d1c6", + "zero.c": "e22a59edfabc5ae7d300fe638fb7998070758d52576dba9c78d803f7f1653e0d" +} diff --git a/test/evaluation/rfc0029/recursive-cases/generic.c b/test/evaluation/rfc0029/recursive-cases/generic.c new file mode 100644 index 00000000..b60f1392 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-cases/generic.c @@ -0,0 +1,9 @@ +struct state { unsigned depth, tag; char *data; }; +static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth >= 1000) return 0; + ++s->depth; + if (*s->data == 1) return 100 / *s->data; + return walk(s); +} +int main(void) { struct state s={0,0,0};return walk(&s); } diff --git a/test/evaluation/rfc0029/recursive-cases/live.c b/test/evaluation/rfc0029/recursive-cases/live.c new file mode 100644 index 00000000..670996f5 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-cases/live.c @@ -0,0 +1,9 @@ +struct state { unsigned depth, tag; char *data; }; +static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth >= 1000) return 0; + ++s->depth; + if (*s->data == 1) return 100 / *s->data; + return walk(s); +} +int main(void){char data=1;struct state s={0,1,&data};return walk(&s);} diff --git a/test/evaluation/rfc0029/recursive-cases/manifest.json b/test/evaluation/rfc0029/recursive-cases/manifest.json new file mode 100644 index 00000000..95109722 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-cases/manifest.json @@ -0,0 +1,81 @@ +{ + "version": 1, + "cases": [ + { + "name": "recursive-case-zero", + "sources": [ + "zero.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "recursive-case-live", + "sources": [ + "live.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "recursive-case-null", + "sources": [ + "null.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "null|live|valid" + }, + { + "name": "recursive-case-unknown", + "sources": [ + "unknown.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "null|live|valid" + }, + { + "name": "recursive-case-generic", + "sources": [ + "generic.c" + ], + "functions": [ + "walk", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "summary iteration limit" + } + ] +} diff --git a/test/evaluation/rfc0029/recursive-cases/null.c b/test/evaluation/rfc0029/recursive-cases/null.c new file mode 100644 index 00000000..7e5b7613 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-cases/null.c @@ -0,0 +1,9 @@ +struct state { unsigned depth, tag; char *data; }; +static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth >= 1000) return 0; + ++s->depth; + if (*s->data == 1) return 100 / *s->data; + return walk(s); +} +int main(void) { struct state s={0,1,0};return walk(&s); } diff --git a/test/evaluation/rfc0029/recursive-cases/provenance.md b/test/evaluation/rfc0029/recursive-cases/provenance.md new file mode 100644 index 00000000..a4abcc48 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-cases/provenance.md @@ -0,0 +1 @@ +Frozen before case-local exhaustion handling. Candidate24 Release and candidate25 have the same existing case-limit handling. The zero-tag case executes no recursive call; the generic summary exhausts its fixed-point budget. A case must prove its own operations and still reject a null access, unknown selector or independently selected generic definition. diff --git a/test/evaluation/rfc0029/recursive-cases/unknown.c b/test/evaluation/rfc0029/recursive-cases/unknown.c new file mode 100644 index 00000000..9952fd0c --- /dev/null +++ b/test/evaluation/rfc0029/recursive-cases/unknown.c @@ -0,0 +1,9 @@ +struct state { unsigned depth, tag; char *data; }; +static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth >= 1000) return 0; + ++s->depth; + if (*s->data == 1) return 100 / *s->data; + return walk(s); +} +int main(int argc,char **argv){(void)argv;struct state s={0,(unsigned)argc,0};return walk(&s);} diff --git a/test/evaluation/rfc0029/recursive-cases/zero.c b/test/evaluation/rfc0029/recursive-cases/zero.c new file mode 100644 index 00000000..b60f1392 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-cases/zero.c @@ -0,0 +1,9 @@ +struct state { unsigned depth, tag; char *data; }; +static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth >= 1000) return 0; + ++s->depth; + if (*s->data == 1) return 100 / *s->data; + return walk(s); +} +int main(void) { struct state s={0,0,0};return walk(&s); } diff --git a/test/evaluation/rfc0029/recursive-contexts/README.md b/test/evaluation/rfc0029/recursive-contexts/README.md new file mode 100644 index 00000000..87d0684b --- /dev/null +++ b/test/evaluation/rfc0029/recursive-contexts/README.md @@ -0,0 +1 @@ +RFC0029 bounded recursive context nomination. Frozen before implementing the filter. Thirty-six unrelated mutable-output initial values must not consume every context slot before a concrete input selector is available. The generic recursive definition remains incomplete. This changes no proof premises or context budgets. Baseline candidate27c. diff --git a/test/evaluation/rfc0029/recursive-contexts/bad.c b/test/evaluation/rfc0029/recursive-contexts/bad.c new file mode 100644 index 00000000..d308b386 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-contexts/bad.c @@ -0,0 +1,45 @@ +struct node {struct node *next;unsigned tag;}; +struct state {unsigned seen;}; +static int walk(struct node *p,struct state *s) { + if(!p)return 0; + s->seen=1; + if(p->tag==1)return 1; + return walk(p->next,s); +} +int generic_0(struct node *p){struct state s={0};return walk(p,&s);} +int generic_1(struct node *p){struct state s={1};return walk(p,&s);} +int generic_2(struct node *p){struct state s={2};return walk(p,&s);} +int generic_3(struct node *p){struct state s={3};return walk(p,&s);} +int generic_4(struct node *p){struct state s={4};return walk(p,&s);} +int generic_5(struct node *p){struct state s={5};return walk(p,&s);} +int generic_6(struct node *p){struct state s={6};return walk(p,&s);} +int generic_7(struct node *p){struct state s={7};return walk(p,&s);} +int generic_8(struct node *p){struct state s={8};return walk(p,&s);} +int generic_9(struct node *p){struct state s={9};return walk(p,&s);} +int generic_10(struct node *p){struct state s={10};return walk(p,&s);} +int generic_11(struct node *p){struct state s={11};return walk(p,&s);} +int generic_12(struct node *p){struct state s={12};return walk(p,&s);} +int generic_13(struct node *p){struct state s={13};return walk(p,&s);} +int generic_14(struct node *p){struct state s={14};return walk(p,&s);} +int generic_15(struct node *p){struct state s={15};return walk(p,&s);} +int generic_16(struct node *p){struct state s={16};return walk(p,&s);} +int generic_17(struct node *p){struct state s={17};return walk(p,&s);} +int generic_18(struct node *p){struct state s={18};return walk(p,&s);} +int generic_19(struct node *p){struct state s={19};return walk(p,&s);} +int generic_20(struct node *p){struct state s={20};return walk(p,&s);} +int generic_21(struct node *p){struct state s={21};return walk(p,&s);} +int generic_22(struct node *p){struct state s={22};return walk(p,&s);} +int generic_23(struct node *p){struct state s={23};return walk(p,&s);} +int generic_24(struct node *p){struct state s={24};return walk(p,&s);} +int generic_25(struct node *p){struct state s={25};return walk(p,&s);} +int generic_26(struct node *p){struct state s={26};return walk(p,&s);} +int generic_27(struct node *p){struct state s={27};return walk(p,&s);} +int generic_28(struct node *p){struct state s={28};return walk(p,&s);} +int generic_29(struct node *p){struct state s={29};return walk(p,&s);} +int generic_30(struct node *p){struct state s={30};return walk(p,&s);} +int generic_31(struct node *p){struct state s={31};return walk(p,&s);} +int generic_32(struct node *p){struct state s={32};return walk(p,&s);} +int generic_33(struct node *p){struct state s={33};return walk(p,&s);} +int generic_34(struct node *p){struct state s={34};return walk(p,&s);} +int generic_35(struct node *p){struct state s={35};return walk(p,&s);} +int main(void){struct node n={0,1};struct state *s=0;return walk(&n,s);} diff --git a/test/evaluation/rfc0029/recursive-contexts/client.c b/test/evaluation/rfc0029/recursive-contexts/client.c new file mode 100644 index 00000000..c6eb0da1 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-contexts/client.c @@ -0,0 +1,45 @@ +struct node {struct node *next;unsigned tag;}; +struct state {unsigned seen;}; +static int walk(struct node *p,struct state *s) { + if(!p)return 0; + s->seen=1; + if(p->tag==1)return 1; + return walk(p->next,s); +} +int generic_0(struct node *p){struct state s={0};return walk(p,&s);} +int generic_1(struct node *p){struct state s={1};return walk(p,&s);} +int generic_2(struct node *p){struct state s={2};return walk(p,&s);} +int generic_3(struct node *p){struct state s={3};return walk(p,&s);} +int generic_4(struct node *p){struct state s={4};return walk(p,&s);} +int generic_5(struct node *p){struct state s={5};return walk(p,&s);} +int generic_6(struct node *p){struct state s={6};return walk(p,&s);} +int generic_7(struct node *p){struct state s={7};return walk(p,&s);} +int generic_8(struct node *p){struct state s={8};return walk(p,&s);} +int generic_9(struct node *p){struct state s={9};return walk(p,&s);} +int generic_10(struct node *p){struct state s={10};return walk(p,&s);} +int generic_11(struct node *p){struct state s={11};return walk(p,&s);} +int generic_12(struct node *p){struct state s={12};return walk(p,&s);} +int generic_13(struct node *p){struct state s={13};return walk(p,&s);} +int generic_14(struct node *p){struct state s={14};return walk(p,&s);} +int generic_15(struct node *p){struct state s={15};return walk(p,&s);} +int generic_16(struct node *p){struct state s={16};return walk(p,&s);} +int generic_17(struct node *p){struct state s={17};return walk(p,&s);} +int generic_18(struct node *p){struct state s={18};return walk(p,&s);} +int generic_19(struct node *p){struct state s={19};return walk(p,&s);} +int generic_20(struct node *p){struct state s={20};return walk(p,&s);} +int generic_21(struct node *p){struct state s={21};return walk(p,&s);} +int generic_22(struct node *p){struct state s={22};return walk(p,&s);} +int generic_23(struct node *p){struct state s={23};return walk(p,&s);} +int generic_24(struct node *p){struct state s={24};return walk(p,&s);} +int generic_25(struct node *p){struct state s={25};return walk(p,&s);} +int generic_26(struct node *p){struct state s={26};return walk(p,&s);} +int generic_27(struct node *p){struct state s={27};return walk(p,&s);} +int generic_28(struct node *p){struct state s={28};return walk(p,&s);} +int generic_29(struct node *p){struct state s={29};return walk(p,&s);} +int generic_30(struct node *p){struct state s={30};return walk(p,&s);} +int generic_31(struct node *p){struct state s={31};return walk(p,&s);} +int generic_32(struct node *p){struct state s={32};return walk(p,&s);} +int generic_33(struct node *p){struct state s={33};return walk(p,&s);} +int generic_34(struct node *p){struct state s={34};return walk(p,&s);} +int generic_35(struct node *p){struct state s={35};return walk(p,&s);} +int main(void){struct node n={0,1};struct state s={0};return walk(&n,&s);} diff --git a/test/evaluation/rfc0029/recursive-contexts/frozen-sha256.json b/test/evaluation/rfc0029/recursive-contexts/frozen-sha256.json new file mode 100644 index 00000000..1e0c49cb --- /dev/null +++ b/test/evaluation/rfc0029/recursive-contexts/frozen-sha256.json @@ -0,0 +1,7 @@ +{ + "README.md": "e0eaf4b35f255a2bcc3d117ef884f0c9436dbc1da07ec4c2c293cf1c3ec4350b", + "bad.c": "f6976836272a648ad97ba29973c1a75f1870224987bbace915db8819c82535a0", + "client.c": "1b580761e8a0388bfef4630acb441e0471f2a6a8fbe619a93af0dc288581118b", + "generic.c": "9cf04c9613344ea16d799a07a48eee0866745d3a87f01fa2c56a60338e391732", + "manifest.json": "91d60a5df10cfc6752667a3d8f3d29c3de8307547b5235f1954df419b05d1db5" +} diff --git a/test/evaluation/rfc0029/recursive-contexts/generic.c b/test/evaluation/rfc0029/recursive-contexts/generic.c new file mode 100644 index 00000000..299b7425 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-contexts/generic.c @@ -0,0 +1,44 @@ +struct node {struct node *next;unsigned tag;}; +struct state {unsigned seen;}; +static int walk(struct node *p,struct state *s) { + if(!p)return 0; + s->seen=1; + if(p->tag==1)return 1; + return walk(p->next,s); +} +int generic_0(struct node *p){struct state s={0};return walk(p,&s);} +int generic_1(struct node *p){struct state s={1};return walk(p,&s);} +int generic_2(struct node *p){struct state s={2};return walk(p,&s);} +int generic_3(struct node *p){struct state s={3};return walk(p,&s);} +int generic_4(struct node *p){struct state s={4};return walk(p,&s);} +int generic_5(struct node *p){struct state s={5};return walk(p,&s);} +int generic_6(struct node *p){struct state s={6};return walk(p,&s);} +int generic_7(struct node *p){struct state s={7};return walk(p,&s);} +int generic_8(struct node *p){struct state s={8};return walk(p,&s);} +int generic_9(struct node *p){struct state s={9};return walk(p,&s);} +int generic_10(struct node *p){struct state s={10};return walk(p,&s);} +int generic_11(struct node *p){struct state s={11};return walk(p,&s);} +int generic_12(struct node *p){struct state s={12};return walk(p,&s);} +int generic_13(struct node *p){struct state s={13};return walk(p,&s);} +int generic_14(struct node *p){struct state s={14};return walk(p,&s);} +int generic_15(struct node *p){struct state s={15};return walk(p,&s);} +int generic_16(struct node *p){struct state s={16};return walk(p,&s);} +int generic_17(struct node *p){struct state s={17};return walk(p,&s);} +int generic_18(struct node *p){struct state s={18};return walk(p,&s);} +int generic_19(struct node *p){struct state s={19};return walk(p,&s);} +int generic_20(struct node *p){struct state s={20};return walk(p,&s);} +int generic_21(struct node *p){struct state s={21};return walk(p,&s);} +int generic_22(struct node *p){struct state s={22};return walk(p,&s);} +int generic_23(struct node *p){struct state s={23};return walk(p,&s);} +int generic_24(struct node *p){struct state s={24};return walk(p,&s);} +int generic_25(struct node *p){struct state s={25};return walk(p,&s);} +int generic_26(struct node *p){struct state s={26};return walk(p,&s);} +int generic_27(struct node *p){struct state s={27};return walk(p,&s);} +int generic_28(struct node *p){struct state s={28};return walk(p,&s);} +int generic_29(struct node *p){struct state s={29};return walk(p,&s);} +int generic_30(struct node *p){struct state s={30};return walk(p,&s);} +int generic_31(struct node *p){struct state s={31};return walk(p,&s);} +int generic_32(struct node *p){struct state s={32};return walk(p,&s);} +int generic_33(struct node *p){struct state s={33};return walk(p,&s);} +int generic_34(struct node *p){struct state s={34};return walk(p,&s);} +int generic_35(struct node *p){struct state s={35};return walk(p,&s);} diff --git a/test/evaluation/rfc0029/recursive-contexts/manifest.json b/test/evaluation/rfc0029/recursive-contexts/manifest.json new file mode 100644 index 00000000..e1cc9f55 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-contexts/manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "recursive-context-client", + "sources": [ + "client.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "recursive-context-null-output", + "sources": [ + "bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "null|live|valid", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "recursive-context-generic", + "sources": [ + "generic.c" + ], + "functions": [ + "walk" + ], + "expect": "rejected", + "reason": "recursive|iteration limit|context", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/recursive-output-cases/README.md b/test/evaluation/rfc0029/recursive-output-cases/README.md new file mode 100644 index 00000000..cd6e4c39 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-output-cases/README.md @@ -0,0 +1 @@ +Frozen before active recursive output-context nomination. A stable mode flag alone must not force separate recursive requests for each changing depth value before a concrete node selector is available. Baseline candidate28; generic unresolved recursion remains incomplete. diff --git a/test/evaluation/rfc0029/recursive-output-cases/client.c b/test/evaluation/rfc0029/recursive-output-cases/client.c new file mode 100644 index 00000000..e3704e87 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-output-cases/client.c @@ -0,0 +1,12 @@ +struct node {struct node *next;unsigned tag;}; +struct state {unsigned depth,flag;}; +static int walk(struct node *p,struct state *s) { + if (!p) return 0; + if (s->depth>=1000) return 0; + if (!s->flag) ++s->depth; + if (p->tag==1) return 1; + if (p->next) return walk(p->next,s); + return 0; +} +int generic(struct node *p) {struct state s={0,0};return walk(p,&s);} +int main(void){struct node n={0,1};struct state s={0,0};return walk(&n,&s);} diff --git a/test/evaluation/rfc0029/recursive-output-cases/frozen-sha256.json b/test/evaluation/rfc0029/recursive-output-cases/frozen-sha256.json new file mode 100644 index 00000000..1ba42795 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-output-cases/frozen-sha256.json @@ -0,0 +1,6 @@ +{ + "README.md": "4873864f2e0f8b2bc405aa1c5ffea8ee89c0be64861d67927d1288b530c384a3", + "client.c": "5f91092206c5ce76ecc223543c3e8e66cb2b18477064b84433a4e9176b16af93", + "manifest.json": "e83ce5cf5e0482ec3ec1032e4f04440874bcca5b388a3a188a161b68d9a18f38", + "null.c": "0ea09d745f349c93b43e4bcce2a027defc4d7da24755cdcbeba92595ee6061a6" +} diff --git a/test/evaluation/rfc0029/recursive-output-cases/manifest.json b/test/evaluation/rfc0029/recursive-output-cases/manifest.json new file mode 100644 index 00000000..3c147879 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-output-cases/manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "recursive-output-client", + "sources": [ + "client.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "recursive-output-null", + "sources": [ + "null.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "null|live|valid", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "recursive-output-generic", + "sources": [ + "client.c" + ], + "functions": [ + "walk" + ], + "expect": "rejected", + "reason": "limit|context|recursive", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/recursive-output-cases/null.c b/test/evaluation/rfc0029/recursive-output-cases/null.c new file mode 100644 index 00000000..1f054f23 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-output-cases/null.c @@ -0,0 +1,12 @@ +struct node {struct node *next;unsigned tag;}; +struct state {unsigned depth,flag;}; +static int walk(struct node *p,struct state *s) { + if (!p) return 0; + if (s->depth>=1000) return 0; + if (!s->flag) ++s->depth; + if (p->tag==1) return 1; + if (p->next) return walk(p->next,s); + return 0; +} +int generic(struct node *p) {struct state s={0,0};return walk(p,&s);} +int main(void){struct node n={0,1};return walk(&n,0);} diff --git a/test/evaluation/rfc0029/recursive-state-cases/bad.c b/test/evaluation/rfc0029/recursive-state-cases/bad.c new file mode 100644 index 00000000..8e56d8c1 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-state-cases/bad.c @@ -0,0 +1,45 @@ +struct node { struct node *next; unsigned tag; }; +struct state { unsigned seen, mode; }; +static int walk(struct node *p, struct state *s) { + if (!p) return 0; + s->seen = s->mode; + if (p->tag == 1) return 1; + return walk(p->next, s); +} +int generic_0(struct node *p) { struct state s = {0, 0}; return walk(p, &s); } +int generic_1(struct node *p) { struct state s = {0, 1}; return walk(p, &s); } +int generic_2(struct node *p) { struct state s = {0, 2}; return walk(p, &s); } +int generic_3(struct node *p) { struct state s = {0, 3}; return walk(p, &s); } +int generic_4(struct node *p) { struct state s = {0, 4}; return walk(p, &s); } +int generic_5(struct node *p) { struct state s = {0, 5}; return walk(p, &s); } +int generic_6(struct node *p) { struct state s = {0, 6}; return walk(p, &s); } +int generic_7(struct node *p) { struct state s = {0, 7}; return walk(p, &s); } +int generic_8(struct node *p) { struct state s = {0, 8}; return walk(p, &s); } +int generic_9(struct node *p) { struct state s = {0, 9}; return walk(p, &s); } +int generic_10(struct node *p) { struct state s = {0, 10}; return walk(p, &s); } +int generic_11(struct node *p) { struct state s = {0, 11}; return walk(p, &s); } +int generic_12(struct node *p) { struct state s = {0, 12}; return walk(p, &s); } +int generic_13(struct node *p) { struct state s = {0, 13}; return walk(p, &s); } +int generic_14(struct node *p) { struct state s = {0, 14}; return walk(p, &s); } +int generic_15(struct node *p) { struct state s = {0, 15}; return walk(p, &s); } +int generic_16(struct node *p) { struct state s = {0, 16}; return walk(p, &s); } +int generic_17(struct node *p) { struct state s = {0, 17}; return walk(p, &s); } +int generic_18(struct node *p) { struct state s = {0, 18}; return walk(p, &s); } +int generic_19(struct node *p) { struct state s = {0, 19}; return walk(p, &s); } +int generic_20(struct node *p) { struct state s = {0, 20}; return walk(p, &s); } +int generic_21(struct node *p) { struct state s = {0, 21}; return walk(p, &s); } +int generic_22(struct node *p) { struct state s = {0, 22}; return walk(p, &s); } +int generic_23(struct node *p) { struct state s = {0, 23}; return walk(p, &s); } +int generic_24(struct node *p) { struct state s = {0, 24}; return walk(p, &s); } +int generic_25(struct node *p) { struct state s = {0, 25}; return walk(p, &s); } +int generic_26(struct node *p) { struct state s = {0, 26}; return walk(p, &s); } +int generic_27(struct node *p) { struct state s = {0, 27}; return walk(p, &s); } +int generic_28(struct node *p) { struct state s = {0, 28}; return walk(p, &s); } +int generic_29(struct node *p) { struct state s = {0, 29}; return walk(p, &s); } +int generic_30(struct node *p) { struct state s = {0, 30}; return walk(p, &s); } +int generic_31(struct node *p) { struct state s = {0, 31}; return walk(p, &s); } +int generic_32(struct node *p) { struct state s = {0, 32}; return walk(p, &s); } +int generic_33(struct node *p) { struct state s = {0, 33}; return walk(p, &s); } +int generic_34(struct node *p) { struct state s = {0, 34}; return walk(p, &s); } +int generic_35(struct node *p) { struct state s = {0, 35}; return walk(p, &s); } +int main(void) { struct node n = {0, 1}; return walk(&n, 0); } diff --git a/test/evaluation/rfc0029/recursive-state-cases/client.c b/test/evaluation/rfc0029/recursive-state-cases/client.c new file mode 100644 index 00000000..2f08a524 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-state-cases/client.c @@ -0,0 +1,45 @@ +struct node { struct node *next; unsigned tag; }; +struct state { unsigned seen, mode; }; +static int walk(struct node *p, struct state *s) { + if (!p) return 0; + s->seen = s->mode; + if (p->tag == 1) return 1; + return walk(p->next, s); +} +int generic_0(struct node *p) { struct state s = {0, 0}; return walk(p, &s); } +int generic_1(struct node *p) { struct state s = {0, 1}; return walk(p, &s); } +int generic_2(struct node *p) { struct state s = {0, 2}; return walk(p, &s); } +int generic_3(struct node *p) { struct state s = {0, 3}; return walk(p, &s); } +int generic_4(struct node *p) { struct state s = {0, 4}; return walk(p, &s); } +int generic_5(struct node *p) { struct state s = {0, 5}; return walk(p, &s); } +int generic_6(struct node *p) { struct state s = {0, 6}; return walk(p, &s); } +int generic_7(struct node *p) { struct state s = {0, 7}; return walk(p, &s); } +int generic_8(struct node *p) { struct state s = {0, 8}; return walk(p, &s); } +int generic_9(struct node *p) { struct state s = {0, 9}; return walk(p, &s); } +int generic_10(struct node *p) { struct state s = {0, 10}; return walk(p, &s); } +int generic_11(struct node *p) { struct state s = {0, 11}; return walk(p, &s); } +int generic_12(struct node *p) { struct state s = {0, 12}; return walk(p, &s); } +int generic_13(struct node *p) { struct state s = {0, 13}; return walk(p, &s); } +int generic_14(struct node *p) { struct state s = {0, 14}; return walk(p, &s); } +int generic_15(struct node *p) { struct state s = {0, 15}; return walk(p, &s); } +int generic_16(struct node *p) { struct state s = {0, 16}; return walk(p, &s); } +int generic_17(struct node *p) { struct state s = {0, 17}; return walk(p, &s); } +int generic_18(struct node *p) { struct state s = {0, 18}; return walk(p, &s); } +int generic_19(struct node *p) { struct state s = {0, 19}; return walk(p, &s); } +int generic_20(struct node *p) { struct state s = {0, 20}; return walk(p, &s); } +int generic_21(struct node *p) { struct state s = {0, 21}; return walk(p, &s); } +int generic_22(struct node *p) { struct state s = {0, 22}; return walk(p, &s); } +int generic_23(struct node *p) { struct state s = {0, 23}; return walk(p, &s); } +int generic_24(struct node *p) { struct state s = {0, 24}; return walk(p, &s); } +int generic_25(struct node *p) { struct state s = {0, 25}; return walk(p, &s); } +int generic_26(struct node *p) { struct state s = {0, 26}; return walk(p, &s); } +int generic_27(struct node *p) { struct state s = {0, 27}; return walk(p, &s); } +int generic_28(struct node *p) { struct state s = {0, 28}; return walk(p, &s); } +int generic_29(struct node *p) { struct state s = {0, 29}; return walk(p, &s); } +int generic_30(struct node *p) { struct state s = {0, 30}; return walk(p, &s); } +int generic_31(struct node *p) { struct state s = {0, 31}; return walk(p, &s); } +int generic_32(struct node *p) { struct state s = {0, 32}; return walk(p, &s); } +int generic_33(struct node *p) { struct state s = {0, 33}; return walk(p, &s); } +int generic_34(struct node *p) { struct state s = {0, 34}; return walk(p, &s); } +int generic_35(struct node *p) { struct state s = {0, 35}; return walk(p, &s); } +int main(void) { struct node n = {0, 1}; struct state s = {0, 100}; return walk(&n, &s); } diff --git a/test/evaluation/rfc0029/recursive-state-cases/frozen-sha256.json b/test/evaluation/rfc0029/recursive-state-cases/frozen-sha256.json new file mode 100644 index 00000000..8bd15e29 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-state-cases/frozen-sha256.json @@ -0,0 +1,6 @@ +{ + "bad.c": "a46260e38698acdb17436dc7ede953f067630c9ed600152ffcc9cf48d08b4fa2", + "client.c": "193335d2ca9502d5c40c8c695c53c45e700dabb43fb46b80ca83542df7b2428e", + "generic.c": "c5ba71129e9d03fe72a21cb6e2452ef505b0b86a814217ce1ec06ab202e1995b", + "manifest.json": "975b01dd501d333f064b1ffb36c211a6676b2f19310c3fae2980ba3e80aab958" +} diff --git a/test/evaluation/rfc0029/recursive-state-cases/generic.c b/test/evaluation/rfc0029/recursive-state-cases/generic.c new file mode 100644 index 00000000..5fc9ec87 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-state-cases/generic.c @@ -0,0 +1,8 @@ +struct node { struct node *next; unsigned tag; }; +struct state { unsigned seen, mode; }; +int walk(struct node *p, struct state *s) { + if (!p) return 0; + s->seen = s->mode; + if (p->tag == 1) return 1; + return walk(p->next, s); +} diff --git a/test/evaluation/rfc0029/recursive-state-cases/manifest.json b/test/evaluation/rfc0029/recursive-state-cases/manifest.json new file mode 100644 index 00000000..47899322 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-state-cases/manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "state-case-client", + "sources": [ + "client.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "state-case-null", + "sources": [ + "bad.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "null|live|valid" + }, + { + "name": "state-case-generic", + "sources": [ + "generic.c" + ], + "functions": [ + "walk" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "context|recursive|incomplete" + } + ] +} diff --git a/test/evaluation/rfc0029/recursive-transport/api.h b/test/evaluation/rfc0029/recursive-transport/api.h new file mode 100644 index 00000000..761437b4 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-transport/api.h @@ -0,0 +1,5 @@ +#include +struct node { unsigned char value; struct node *next; }; +struct node *build(const unsigned char *, size_t); +unsigned sum(const struct node *); +void destroy(struct node *); diff --git a/test/evaluation/rfc0029/recursive-transport/client.c b/test/evaluation/rfc0029/recursive-transport/client.c new file mode 100644 index 00000000..8f5f6573 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-transport/client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,3);unsigned n=sum(p);destroy(p);return n != 6;} diff --git a/test/evaluation/rfc0029/recursive-transport/frozen-sha256.json b/test/evaluation/rfc0029/recursive-transport/frozen-sha256.json new file mode 100644 index 00000000..3cc15fe5 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-transport/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "api.h": "014699454c96cef9e29e453dc6800dd48c38f5af7ff93b7558d96d76705a6856", + "client.c": "aa3cb973c7cca32e8139810f8e73cf7c7ce999103a8a0a3efe43778df3d504b7", + "library.c": "1c9b7ce362e8235d361cfef74616d8925843b1ba2b6dde94fee734af0c98971d", + "manifest.json": "b1704ba9cfbbcb0ff99ae5731b4065e5220ee1a21ca2457ae1d7f6f16aa3bc03", + "provenance.md": "b926f57aa400573fb9a321bf562880bb54be5d380afefff3e73184549076151d", + "short.c": "d1669d1f48162870cfa815423f880b57676f3371be82b13fc130b7816e918dd7", + "uninitialized.c": "55953051b3b1ba44c7b4c9e7f4771d5bba1c4dd97f7bd56d28f4f255bca612eb" +} diff --git a/test/evaluation/rfc0029/recursive-transport/library.c b/test/evaluation/rfc0029/recursive-transport/library.c new file mode 100644 index 00000000..7ed324d5 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-transport/library.c @@ -0,0 +1,15 @@ +#include +#include "api.h" +void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } +struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p=calloc(1,sizeof *p); + if (!p) return 0; + p->value=data[0]; + if (n>1) { + p->next=build(data+1,n-1); + if (!p->next) {free(p);return 0;} + } + return p; +} +unsigned sum(const struct node *p) { if (!p) return 0; return p->value + sum(p->next); } diff --git a/test/evaluation/rfc0029/recursive-transport/manifest.json b/test/evaluation/rfc0029/recursive-transport/manifest.json new file mode 100644 index 00000000..3d065d88 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-transport/manifest.json @@ -0,0 +1,69 @@ +{ + "version": 1, + "cases": [ + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "build", + "sum", + "destroy" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "client", + "sources": [ + "client.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|interval|bounds" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + } + ] +} diff --git a/test/evaluation/rfc0029/recursive-transport/provenance.md b/test/evaluation/rfc0029/recursive-transport/provenance.md new file mode 100644 index 00000000..346885c5 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-transport/provenance.md @@ -0,0 +1 @@ +Frozen after the source constructor proof was developed, before the first separate-source, object or checkpoint analysis of this population. It combines runtime-sized recursive construction, traversal and destruction in one imported library. The negative clients preserve short-input and uninitialized-input obligations. diff --git a/test/evaluation/rfc0029/recursive-transport/short.c b/test/evaluation/rfc0029/recursive-transport/short.c new file mode 100644 index 00000000..caa10405 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-transport/short.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void) {const unsigned char data[]={1,2,3};struct node *p=build(data,4);unsigned n=sum(p);destroy(p);return n != 6;} diff --git a/test/evaluation/rfc0029/recursive-transport/uninitialized.c b/test/evaluation/rfc0029/recursive-transport/uninitialized.c new file mode 100644 index 00000000..2539b3ad --- /dev/null +++ b/test/evaluation/rfc0029/recursive-transport/uninitialized.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void) {unsigned char data[3];data[0]=1;struct node *p=build(data,3);unsigned n=sum(p);destroy(p);return n != 6;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/alias.c b/test/evaluation/rfc0029/recursive-writer-reviewed/alias.c new file mode 100644 index 00000000..6d08dbdd --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/alias.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void){unsigned char input[]={1,2,3};struct writer w={7,input,0,3};(void)emit(input,3,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/capacity.c b/test/evaluation/rfc0029/recursive-writer-reviewed/capacity.c new file mode 100644 index 00000000..980528f5 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/capacity.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[]={1,2,3,4},output[3];struct writer w={7,output,0,4};(void)emit(input,4,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/emit.c b/test/evaluation/rfc0029/recursive-writer-reviewed/emit.c new file mode 100644 index 00000000..3f0ac4d4 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/emit.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,3,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/false-prefix.c b/test/evaluation/rfc0029/recursive-writer-reviewed/false-prefix.c new file mode 100644 index 00000000..bb1e7208 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/false-prefix.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity){w->used++;return 1;} + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void){unsigned char input[]={1,2,3},output[2];struct writer w={7,output,0,2};(void)emit(input,3,&w);if(w.used)return w.data[w.used-1];return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/false-success-read.c b/test/evaluation/rfc0029/recursive-writer-reviewed/false-success-read.c new file mode 100644 index 00000000..c08b7716 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/false-success-read.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 1; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void){unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,2};if(emit(input,3,&w))return output[2];return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/frozen-sha256.json b/test/evaluation/rfc0029/recursive-writer-reviewed/frozen-sha256.json new file mode 100644 index 00000000..bdc8a49a --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/frozen-sha256.json @@ -0,0 +1,13 @@ +{ + "alias.c": "b688aeccefea3059b16a98323ef6c57e36b95d33e88bc061e61a10d4a6d6ba77", + "capacity.c": "72d39cede149c7d6c2ac0be3fb58c9c46776c7719a6149a2d1f045f490dacfab", + "emit.c": "c18191f154fe2dc3458ba98c95012e6080fca181d38326c9105bb274c8757db2", + "false-prefix.c": "6a7d7f641624413c34ac79dd3e17d8e8b5036ebf7bc01a732f682ac2f7963c73", + "false-success-read.c": "0f658d3c16b67be15669998f2761bf79be2312e811d5e81cafe7ae7711d6a60a", + "mutual.c": "3304767426377ec0340fe71b8e17ce121b5e37a7b20ea2a6380d5672c904e4bc", + "off-by-one.c": "605de515fa90f8c17dfe540c4732dfbe86d51c65a237f7986a6be4cb0b52a8b3", + "partial.c": "e92c7493e9179f8306854796802935ce088f9c7d80b1215dee61e7f07a025b98", + "prefix.c": "d8c979dc0d422e5d1f3137ed0007450fc4ed652300d827709da9fdad7b4d049f", + "short.c": "f667d91e43de6d744e5b5656dc3a542e6b72cbb2c30fbb29f0b43540fd3b7022", + "uninitialized.c": "42a27858f818af695bc02836bef98cec5af851b2c8670991ac41bb9d6ca75412" +} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/manifest.json b/test/evaluation/rfc0029/recursive-writer-reviewed/manifest.json new file mode 100644 index 00000000..aeb57eb8 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/manifest.json @@ -0,0 +1,178 @@ +{ + "version": 1, + "cases": [ + { + "name": "writer-emit", + "sources": [ + "emit.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "writer-short", + "sources": [ + "short.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|extent|bounds|interval" + }, + { + "name": "writer-capacity", + "sources": [ + "capacity.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|extent|bounds|interval" + }, + { + "name": "writer-uninitialized", + "sources": [ + "uninitialized.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + }, + { + "name": "writer-off-by-one", + "sources": [ + "off-by-one.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|extent|bounds|interval" + }, + { + "name": "writer-prefix", + "sources": [ + "prefix.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "writer-partial", + "sources": [ + "partial.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "writer-false-prefix", + "sources": [ + "false-prefix.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "buffer|output|contract" + }, + { + "name": "writer-false-success-read", + "sources": [ + "false-success-read.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + }, + { + "name": "writer-alias", + "sources": [ + "alias.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "separat" + }, + { + "name": "writer-mutual", + "sources": [ + "mutual.c" + ], + "functions": [ + "emit", + "main", + "other" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/mutual.c b/test/evaluation/rfc0029/recursive-writer-reviewed/mutual.c new file mode 100644 index 00000000..9363cdeb --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/mutual.c @@ -0,0 +1,12 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int other(const unsigned char *,size_t,struct writer*); +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!other(input+1,n-1,w))return 0; + return 1; +} +int other(const unsigned char *input,size_t n,struct writer*w){return emit(input,n,w);} +int main(void){unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,3,&w);if(w.used)return w.data[w.used-1];return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/off-by-one.c b/test/evaluation/rfc0029/recursive-writer-reviewed/off-by-one.c new file mode 100644 index 00000000..90d87b09 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/off-by-one.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,3,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/partial.c b/test/evaluation/rfc0029/recursive-writer-reviewed/partial.c new file mode 100644 index 00000000..8e23ad52 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/partial.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void){unsigned char input[]={1,2,3},output[2];struct writer w={7,output,0,2};(void)emit(input,3,&w);if(w.used)return w.data[w.used-1];return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/prefix.c b/test/evaluation/rfc0029/recursive-writer-reviewed/prefix.c new file mode 100644 index 00000000..f1d887ef --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/prefix.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void){unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,3,&w);if(w.used)return w.data[w.used-1];return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/provenance.md b/test/evaluation/rfc0029/recursive-writer-reviewed/provenance.md new file mode 100644 index 00000000..03145980 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/provenance.md @@ -0,0 +1 @@ +Frozen before writer inference changes, against candidate24 Release 5e223b65df861ed030d2bd1ac99aee430b602b396956e6eefe5b5d380e6276bd. See ../recursive-writer/audit.md for the original inventory audit. Aliasing is a conservative separation-premise rejection, not a claim that every overlapping byte copy is unsafe. diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/short.c b/test/evaluation/rfc0029/recursive-writer-reviewed/short.c new file mode 100644 index 00000000..722497c1 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/short.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,4,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer-reviewed/uninitialized.c b/test/evaluation/rfc0029/recursive-writer-reviewed/uninitialized.c new file mode 100644 index 00000000..07eabe20 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer-reviewed/uninitialized.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[3],output[3];input[0]=1;struct writer w={7,output,0,3};(void)emit(input,3,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer/audit.md b/test/evaluation/rfc0029/recursive-writer/audit.md new file mode 100644 index 00000000..0230636c --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/audit.md @@ -0,0 +1,19 @@ +# Frozen writer population audit + +The original candidate24 inventory and baseline are retained unchanged. Two +expectations need a more precise interpretation before writer implementation: + +- `cycle.c` is memory safe: its output-capacity guard eventually returns zero, + even though the input interval never decreases. Candidate24 accepts it with + an ordinary conditional memory contract. It is a conservative nondecreasing + input-induction probe, not a demonstrated memory-safety counterexample. +- `false-success.c` returns success without finishing the input, but its client + reads no output. That standalone program is memory safe. A buffer invariant + alone does not promise complete copying, so accepting it is not a false proof. + +The separately frozen `recursive-writer-reviewed` population retains the five +well-founded original cases and adds clients that inspect the initialized +prefix, a genuinely false advertised length, partial failure, aliasing and +mutual forwarding. Original expectations are neither overwritten nor counted +as passing measurements for the reviewed population. The stronger complete- +serialization obligation still belongs to RFC 0029's required workflow gates. diff --git a/test/evaluation/rfc0029/recursive-writer/capacity.c b/test/evaluation/rfc0029/recursive-writer/capacity.c new file mode 100644 index 00000000..980528f5 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/capacity.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[]={1,2,3,4},output[3];struct writer w={7,output,0,4};(void)emit(input,4,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer/cycle.c b/test/evaluation/rfc0029/recursive-writer/cycle.c new file mode 100644 index 00000000..c1032de5 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/cycle.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input,n,w))return 0; + return 1; +} +int main(void) {unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,3,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer/emit.c b/test/evaluation/rfc0029/recursive-writer/emit.c new file mode 100644 index 00000000..3f0ac4d4 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/emit.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,3,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer/false-success.c b/test/evaluation/rfc0029/recursive-writer/false-success.c new file mode 100644 index 00000000..1dd20a23 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/false-success.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 1; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,3,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer/frozen-sha256.json b/test/evaluation/rfc0029/recursive-writer/frozen-sha256.json new file mode 100644 index 00000000..e74e290c --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/frozen-sha256.json @@ -0,0 +1,11 @@ +{ + "capacity.c": "72d39cede149c7d6c2ac0be3fb58c9c46776c7719a6149a2d1f045f490dacfab", + "cycle.c": "2c9852988b64bc030dd89aaae85699409c3a12ef4f74a683d9d23bee416c6926", + "emit.c": "c18191f154fe2dc3458ba98c95012e6080fca181d38326c9105bb274c8757db2", + "false-success.c": "7f1db27bfc2c6ae21e06994e8d3c79c7b7800fc9a6d8212447920f6fe41a64f6", + "manifest.json": "9b2c4aec84a45447db2ee6b09b670bef65fbef2f987201667cf38efc19b473fd", + "off-by-one.c": "605de515fa90f8c17dfe540c4732dfbe86d51c65a237f7986a6be4cb0b52a8b3", + "provenance.md": "113f189a3dab46e6eed4082a753112ffc7b3d8671ace79e8cdb17d067c674ed0", + "short.c": "f667d91e43de6d744e5b5656dc3a542e6b72cbb2c30fbb29f0b43540fd3b7022", + "uninitialized.c": "42a27858f818af695bc02836bef98cec5af851b2c8670991ac41bb9d6ca75412" +} diff --git a/test/evaluation/rfc0029/recursive-writer/manifest.json b/test/evaluation/rfc0029/recursive-writer/manifest.json new file mode 100644 index 00000000..c49ddf91 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/manifest.json @@ -0,0 +1,116 @@ +{ + "version": 1, + "cases": [ + { + "name": "recursive-writer-emit", + "sources": [ + "emit.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "recursive-writer-short", + "sources": [ + "short.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + }, + { + "name": "recursive-writer-capacity", + "sources": [ + "capacity.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + }, + { + "name": "recursive-writer-uninitialized", + "sources": [ + "uninitialized.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + }, + { + "name": "recursive-writer-cycle", + "sources": [ + "cycle.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|progress" + }, + { + "name": "recursive-writer-false-success", + "sources": [ + "false-success.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "output|contract" + }, + { + "name": "recursive-writer-off-by-one", + "sources": [ + "off-by-one.c" + ], + "functions": [ + "emit", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + } + ] +} diff --git a/test/evaluation/rfc0029/recursive-writer/off-by-one.c b/test/evaluation/rfc0029/recursive-writer/off-by-one.c new file mode 100644 index 00000000..90d87b09 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/off-by-one.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,3,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer/provenance.md b/test/evaluation/rfc0029/recursive-writer/provenance.md new file mode 100644 index 00000000..f0113f9d --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/provenance.md @@ -0,0 +1 @@ +Frozen before recursive byte-writer contract inference on 2026-09-16. The interface consumes runtime input into a bounded initialized output prefix. Positive returns must account for all input bytes in the output count; failure may leave a partial valid prefix. Generic emit and the closed caller are both selected. Counterparts retain physical input/output bounds, initialization, progress and false-success obligations. This independent byte workflow does not replace the cJSON owned-tree writer or its unchanged public clients. Candidate 24 supplies the pre-change baseline. diff --git a/test/evaluation/rfc0029/recursive-writer/short.c b/test/evaluation/rfc0029/recursive-writer/short.c new file mode 100644 index 00000000..722497c1 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/short.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,4,&w);return 0;} diff --git a/test/evaluation/rfc0029/recursive-writer/uninitialized.c b/test/evaluation/rfc0029/recursive-writer/uninitialized.c new file mode 100644 index 00000000..07eabe20 --- /dev/null +++ b/test/evaluation/rfc0029/recursive-writer/uninitialized.c @@ -0,0 +1,10 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} +int main(void) {unsigned char input[3],output[3];input[0]=1;struct writer w={7,output,0,3};(void)emit(input,3,&w);return 0;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/PROVENANCE.md b/test/evaluation/rfc0029/repeated-fresh-outputs/PROVENANCE.md new file mode 100644 index 00000000..d83e8d5d --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/PROVENANCE.md @@ -0,0 +1,6 @@ +RFC 0029 repeated fresh-call regression, frozen against candidate96e. +A bounded byte traversal repeatedly allocates and appends independently owned +nodes, updates them through a separate helper, and publishes the complete +list under its live parent. Factory calls share a source site but each +successful acquisition is distinct from still-live preceding nodes. +Reused nodes, lost prefixes, released nodes and interior bases are negative. diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/api.h b/test/evaluation/rfc0029/repeated-fresh-outputs/api.h new file mode 100644 index 00000000..68262c02 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/api.h @@ -0,0 +1,4 @@ +#include +struct node {struct node *next,*prev,*child;unsigned flags;}; +struct reader {const unsigned char *data;size_t length,offset;}; +struct node *make(void);void drop(struct node*);void mark(struct node*);void forward_mark(struct node*);int build(struct node*,struct reader*); diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/direct-build.c b/test/evaluation/rfc0029/repeated-fresh-outputs/direct-build.c new file mode 100644 index 00000000..f72f9523 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/direct-build.c @@ -0,0 +1,2 @@ +#include "api.h" +int build(struct node *p,struct reader *r){struct node *head=0,*tail=0;while(r->offsetlength&&r->data[r->offset]){struct node *q=make();if(!q){drop(head);return 0;}if(!head){head=tail=q;}else{tail->next=q;q->prev=tail;tail=q;}mark(tail);r->offset++;}if(head)head->prev=tail;p->child=head;return 1;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/direct-client.c b/test/evaluation/rfc0029/repeated-fresh-outputs/direct-client.c new file mode 100644 index 00000000..cb3bff52 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/direct-client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){static const unsigned char input[]="abc";struct reader r={input,sizeof input,0};struct node *p=make();if(!p)return 0;build(p,&r);drop(p);return 0;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/drop.c b/test/evaluation/rfc0029/repeated-fresh-outputs/drop.c new file mode 100644 index 00000000..c3ff600a --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/drop.c @@ -0,0 +1,2 @@ +#include "api.h" +void drop(struct node *p){while(p){struct node *next=p->next;if(!(p->flags&256))drop(p->child);free(p);p=next;}} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/five-build.c b/test/evaluation/rfc0029/repeated-fresh-outputs/five-build.c new file mode 100644 index 00000000..f72f9523 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/five-build.c @@ -0,0 +1,2 @@ +#include "api.h" +int build(struct node *p,struct reader *r){struct node *head=0,*tail=0;while(r->offsetlength&&r->data[r->offset]){struct node *q=make();if(!q){drop(head);return 0;}if(!head){head=tail=q;}else{tail->next=q;q->prev=tail;tail=q;}mark(tail);r->offset++;}if(head)head->prev=tail;p->child=head;return 1;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/five-client.c b/test/evaluation/rfc0029/repeated-fresh-outputs/five-client.c new file mode 100644 index 00000000..37c9c253 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/five-client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){static const unsigned char input[]="abcde";struct reader r={input,sizeof input,0};struct node *p=make();if(!p)return 0;build(p,&r);drop(p);return 0;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/forwarded-build.c b/test/evaluation/rfc0029/repeated-fresh-outputs/forwarded-build.c new file mode 100644 index 00000000..1fe5fae2 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/forwarded-build.c @@ -0,0 +1,2 @@ +#include "api.h" +int build(struct node *p,struct reader *r){struct node *head=0,*tail=0;while(r->offsetlength&&r->data[r->offset]){struct node *q=make();if(!q){drop(head);return 0;}if(!head){head=tail=q;}else{tail->next=q;q->prev=tail;tail=q;}forward_mark(tail);r->offset++;}if(head)head->prev=tail;p->child=head;return 1;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/forwarded-client.c b/test/evaluation/rfc0029/repeated-fresh-outputs/forwarded-client.c new file mode 100644 index 00000000..cb3bff52 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/forwarded-client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){static const unsigned char input[]="abc";struct reader r={input,sizeof input,0};struct node *p=make();if(!p)return 0;build(p,&r);drop(p);return 0;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/frozen-sha256.json b/test/evaluation/rfc0029/repeated-fresh-outputs/frozen-sha256.json new file mode 100644 index 00000000..02690f52 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/frozen-sha256.json @@ -0,0 +1,22 @@ +{ + "PROVENANCE.md": "07d812fe8b762c850e1163edc2a4c6a3a838e872e3f44d0d0d382fb057ef56cd", + "api.h": "0d18988918b00c9a6fd88ddf9f38885ea4d291adfb0da77c1c1aad68b17f9972", + "direct-build.c": "05a6648b74fd02c4421c7bdb9d3ff32722a9ec1ad5b04985ecd0de295c2be907", + "direct-client.c": "2506b5c3c69d007c8a227c9c21a2a218380c07b6aa85106be55dc98380bc17b5", + "drop.c": "4a43a1ffbb644ca2b8a09b7e539637076fea637decb8c6438ae3a11f819b7776", + "five-build.c": "05a6648b74fd02c4421c7bdb9d3ff32722a9ec1ad5b04985ecd0de295c2be907", + "five-client.c": "ead107ee81500ba23ebf30c89ecb5688743c847f3b442166fed7d0353f05c86d", + "forwarded-build.c": "485692d572a0a27dc43649260115562422325e7ff4a450f6e9497959582c9776", + "forwarded-client.c": "2506b5c3c69d007c8a227c9c21a2a218380c07b6aa85106be55dc98380bc17b5", + "interior-build.c": "db39d9390a72d731a0143a53b50d37ac3856ecedc7163e3c2a6234b77bbdfccd", + "interior-client.c": "2506b5c3c69d007c8a227c9c21a2a218380c07b6aa85106be55dc98380bc17b5", + "lost-build.c": "3a9c515af90b5df2356efdf051a7f48a3754d3f32ef41332fd3317e9e9c590f0", + "lost-client.c": "2506b5c3c69d007c8a227c9c21a2a218380c07b6aa85106be55dc98380bc17b5", + "make.c": "4e8169f41e73bd657931d8a5107bafd8332333f5ad371cc80b10eeac1eab3962", + "manifest.json": "585b71f519e40954aaa9e91a10353fea5cb8d5057b6b062b804a5bd0ab9ccf1b", + "mark.c": "00b353626f5ad002bfa104cc1b90473ca0b118c74f1b8f5e015ff8b419869f7f", + "released-build.c": "273ddeda776fab61ad2929a00c5b2c40c239b5e50eb6cab486e7391677ec6b30", + "released-client.c": "2506b5c3c69d007c8a227c9c21a2a218380c07b6aa85106be55dc98380bc17b5", + "reused-build.c": "1f255bd2d3cd3f8f29d393da92ad8d0247b06c97d2a70f1256d797d8ef447e44", + "reused-client.c": "2506b5c3c69d007c8a227c9c21a2a218380c07b6aa85106be55dc98380bc17b5" +} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/interior-build.c b/test/evaluation/rfc0029/repeated-fresh-outputs/interior-build.c new file mode 100644 index 00000000..e20e630d --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/interior-build.c @@ -0,0 +1,2 @@ +#include "api.h" +int build(struct node *p,struct reader *r){struct node *head=0,*tail=0;while(r->offsetlength&&r->data[r->offset]){struct node *q=make();if(!q){drop(head);return 0;}q++;if(!head){head=tail=q;}else{tail->next=q;q->prev=tail;tail=q;}mark(tail);r->offset++;}if(head)head->prev=tail;p->child=head;return 1;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/interior-client.c b/test/evaluation/rfc0029/repeated-fresh-outputs/interior-client.c new file mode 100644 index 00000000..cb3bff52 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/interior-client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){static const unsigned char input[]="abc";struct reader r={input,sizeof input,0};struct node *p=make();if(!p)return 0;build(p,&r);drop(p);return 0;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/lost-build.c b/test/evaluation/rfc0029/repeated-fresh-outputs/lost-build.c new file mode 100644 index 00000000..eeeec664 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/lost-build.c @@ -0,0 +1,2 @@ +#include "api.h" +int build(struct node *p,struct reader *r){struct node *head=0,*tail=0;while(r->offsetlength&&r->data[r->offset]){struct node *q=make();if(!q){drop(head);return 0;}if(!head){head=tail=q;}else{tail->next=q;q->prev=tail;tail=q;head=q;}mark(tail);r->offset++;}if(head)head->prev=tail;p->child=head;return 1;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/lost-client.c b/test/evaluation/rfc0029/repeated-fresh-outputs/lost-client.c new file mode 100644 index 00000000..cb3bff52 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/lost-client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){static const unsigned char input[]="abc";struct reader r={input,sizeof input,0};struct node *p=make();if(!p)return 0;build(p,&r);drop(p);return 0;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/make.c b/test/evaluation/rfc0029/repeated-fresh-outputs/make.c new file mode 100644 index 00000000..355f3a62 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/make.c @@ -0,0 +1,2 @@ +#include "api.h" +struct node *make(void){struct node *p=malloc(sizeof *p);if(p){p->next=0;p->prev=0;p->child=0;p->flags=0;}return p;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/manifest.json b/test/evaluation/rfc0029/repeated-fresh-outputs/manifest.json new file mode 100644 index 00000000..e3276f81 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/manifest.json @@ -0,0 +1,138 @@ +{ + "version": 1, + "cases": [ + { + "name": "direct", + "sources": [ + "direct-client.c", + "direct-build.c", + "make.c", + "drop.c", + "mark.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "entry_requirements": 0 + }, + { + "name": "forwarded", + "sources": [ + "forwarded-client.c", + "forwarded-build.c", + "make.c", + "drop.c", + "mark.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "entry_requirements": 0 + }, + { + "name": "five", + "sources": [ + "five-client.c", + "five-build.c", + "make.c", + "drop.c", + "mark.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "entry_requirements": 0 + }, + { + "name": "reused", + "sources": [ + "reused-client.c", + "reused-build.c", + "make.c", + "drop.c", + "mark.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|ownership|release|valid|container|interval" + }, + { + "name": "lost", + "sources": [ + "lost-client.c", + "lost-build.c", + "make.c", + "drop.c", + "mark.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|ownership|release|valid|container|interval" + }, + { + "name": "released", + "sources": [ + "released-client.c", + "released-build.c", + "make.c", + "drop.c", + "mark.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|ownership|release|valid|container|interval" + }, + { + "name": "interior", + "sources": [ + "interior-client.c", + "interior-build.c", + "make.c", + "drop.c", + "mark.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|ownership|release|valid|container|interval" + } + ] +} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/mark.c b/test/evaluation/rfc0029/repeated-fresh-outputs/mark.c new file mode 100644 index 00000000..2eba55ac --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/mark.c @@ -0,0 +1,3 @@ +#include "api.h" +void mark(struct node *p){p->flags=2;} +void forward_mark(struct node *p){mark(p);} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/released-build.c b/test/evaluation/rfc0029/repeated-fresh-outputs/released-build.c new file mode 100644 index 00000000..3b787426 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/released-build.c @@ -0,0 +1,2 @@ +#include "api.h" +int build(struct node *p,struct reader *r){struct node *head=0,*tail=0;while(r->offsetlength&&r->data[r->offset]){struct node *q=make();if(!q){drop(head);return 0;}if(!head){head=tail=q;}else{tail->next=q;q->prev=tail;tail=q;}mark(tail);drop(q);r->offset++;}if(head)head->prev=tail;p->child=head;return 1;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/released-client.c b/test/evaluation/rfc0029/repeated-fresh-outputs/released-client.c new file mode 100644 index 00000000..cb3bff52 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/released-client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){static const unsigned char input[]="abc";struct reader r={input,sizeof input,0};struct node *p=make();if(!p)return 0;build(p,&r);drop(p);return 0;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/reused-build.c b/test/evaluation/rfc0029/repeated-fresh-outputs/reused-build.c new file mode 100644 index 00000000..ba525ee9 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/reused-build.c @@ -0,0 +1,2 @@ +#include "api.h" +int build(struct node *p,struct reader *r){struct node *head=0,*tail=0;while(r->offsetlength&&r->data[r->offset]){struct node *q=head?head:make();if(!q){drop(head);return 0;}if(!head){head=tail=q;}else{tail->next=q;q->prev=tail;tail=q;}mark(tail);r->offset++;}if(head)head->prev=tail;p->child=head;return 1;} diff --git a/test/evaluation/rfc0029/repeated-fresh-outputs/reused-client.c b/test/evaluation/rfc0029/repeated-fresh-outputs/reused-client.c new file mode 100644 index 00000000..cb3bff52 --- /dev/null +++ b/test/evaluation/rfc0029/repeated-fresh-outputs/reused-client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){static const unsigned char input[]="abc";struct reader r={input,sizeof input,0};struct node *p=make();if(!p)return 0;build(p,&r);drop(p);return 0;} diff --git a/test/evaluation/rfc0029/reverse-byte-writes/README.md b/test/evaluation/rfc0029/reverse-byte-writes/README.md new file mode 100644 index 00000000..16461158 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-byte-writes/README.md @@ -0,0 +1 @@ +Frozen before projecting scalar range bounds as sufficient access envelopes. A reverse byte writer computes a width from actual control flow and decrements a positive narrow counter. Storage extent, write permission, exact evaluated offsets and caller requirements must remain independent. diff --git a/test/evaluation/rfc0029/reverse-byte-writes/frozen-sha256.json b/test/evaluation/rfc0029/reverse-byte-writes/frozen-sha256.json new file mode 100644 index 00000000..974ca96a --- /dev/null +++ b/test/evaluation/rfc0029/reverse-byte-writes/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "README.md": "f353344826764f665d57681f013f031c91c62ea0bd35974e97510be4189be53f", + "good.c": "7ad75a949f3967ebd7e820d47b74c7283d5cc402316d208361d0bc70d4e329f0", + "library.c": "9a362eeab46d0c79ef31415203ab7069ad597c8ffbed5e330eb66b603f6dec76", + "manifest.json": "db2ce840e6fee21a9825ff09296af39bf2a7352531062578fc4966431e8a5f1a", + "past-end.c": "94fe52665077959e67192dcdea03cb7c9eaede7f52724e97029a11d2993c557f", + "readonly.c": "3fda21dfa8eea3787ec786946de97e405509b9d859bc068f9338bf0741a49bd9", + "short.c": "149a140abc536ead996af47dd16238414fb4941ddfd3eec917eaa3d79e1ab569" +} diff --git a/test/evaluation/rfc0029/reverse-byte-writes/good.c b/test/evaluation/rfc0029/reverse-byte-writes/good.c new file mode 100644 index 00000000..dbc8a030 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-byte-writes/good.c @@ -0,0 +1,2 @@ +void encode(unsigned char **,unsigned); +int main(void){unsigned char out[4];unsigned char *p=out;encode(&p,0x10000);return 0;} diff --git a/test/evaluation/rfc0029/reverse-byte-writes/library.c b/test/evaluation/rfc0029/reverse-byte-writes/library.c new file mode 100644 index 00000000..5ff3050d --- /dev/null +++ b/test/evaluation/rfc0029/reverse-byte-writes/library.c @@ -0,0 +1,11 @@ +void encode(unsigned char **out, unsigned code) { + unsigned char length; + if(code<128)length=1; + else if(code<2048)length=2; + else if(code<65536)length=3; + else length=4; + unsigned char i; + for(i=(unsigned char)(length-1);i>0;i--) (*out)[i]=42; + (*out)[0]=1; + *out+=length; +} diff --git a/test/evaluation/rfc0029/reverse-byte-writes/manifest.json b/test/evaluation/rfc0029/reverse-byte-writes/manifest.json new file mode 100644 index 00000000..63c2aa0b --- /dev/null +++ b/test/evaluation/rfc0029/reverse-byte-writes/manifest.json @@ -0,0 +1,82 @@ +{ + "version": 1, + "cases": [ + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "encode" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|writ" + }, + { + "name": "readonly", + "sources": [ + "readonly.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|writ" + }, + { + "name": "past-end", + "sources": [ + "past-end.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|writ" + } + ] +} diff --git a/test/evaluation/rfc0029/reverse-byte-writes/past-end.c b/test/evaluation/rfc0029/reverse-byte-writes/past-end.c new file mode 100644 index 00000000..20eb8ee7 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-byte-writes/past-end.c @@ -0,0 +1,12 @@ +void encode(unsigned char **out, unsigned code) { + unsigned char length; + if(code<128)length=1; + else if(code<2048)length=2; + else if(code<65536)length=3; + else length=4; + unsigned char i; + for(i=(unsigned char)(length);i>0;i--) (*out)[i]=42; + (*out)[0]=1; + *out+=length; +} +int main(void){unsigned char out[4];unsigned char *p=out;encode(&p,0x10000);return 0;} diff --git a/test/evaluation/rfc0029/reverse-byte-writes/readonly.c b/test/evaluation/rfc0029/reverse-byte-writes/readonly.c new file mode 100644 index 00000000..ced348b9 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-byte-writes/readonly.c @@ -0,0 +1,2 @@ +void encode(unsigned char **,unsigned); +int main(void){const unsigned char out[4]={0};unsigned char *p=(unsigned char*)out;encode(&p,0x10000);return 0;} diff --git a/test/evaluation/rfc0029/reverse-byte-writes/short.c b/test/evaluation/rfc0029/reverse-byte-writes/short.c new file mode 100644 index 00000000..465c2e7f --- /dev/null +++ b/test/evaluation/rfc0029/reverse-byte-writes/short.c @@ -0,0 +1,2 @@ +void encode(unsigned char **,unsigned); +int main(void){unsigned char out[3];unsigned char *p=out;encode(&p,0x10000);return 0;} diff --git a/test/evaluation/rfc0029/reverse-initialization/README.md b/test/evaluation/rfc0029/reverse-initialization/README.md new file mode 100644 index 00000000..5683d251 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/README.md @@ -0,0 +1,6 @@ +# Reverse initialization (RFC 0029) + +Frozen before candidate 52. A stable reverse unit-stride loop with an +unconditional byte write establishes exactly the visited suffix. Reads still +need bounds and initialization; skipped iterations, early exits, non-unit +strides and an unwritten zero index cannot establish the full prefix. diff --git a/test/evaluation/rfc0029/reverse-initialization/early.c b/test/evaluation/rfc0029/reverse-initialization/early.c new file mode 100644 index 00000000..417918e3 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/early.c @@ -0,0 +1 @@ +int main(void){unsigned char out[4];unsigned i;for(i=3;i>0;i--){out[i]=1;break;}out[0]=1;return out[2];} diff --git a/test/evaluation/rfc0029/reverse-initialization/frozen-sha256.json b/test/evaluation/rfc0029/reverse-initialization/frozen-sha256.json new file mode 100644 index 00000000..056e534d --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/frozen-sha256.json @@ -0,0 +1,12 @@ +{ + "README.md": "558cf8dfcc9b63b935a4ff1b09ae3d3067c6320b540ba55c95e05f266fb15e5e", + "early.c": "ac9c3ff999733995a88eb10c055f605aaf6ae5e88b6aaf9b70cd0cb08f5a59a3", + "good.c": "cd9699bf0bea91ae45ae552da8f906fff19decaf60df20d36662c00c4597ee69", + "library.c": "a5bb0a6379b138e7c5ba2c947e84e44d39787da2186b1e11bc714f777b64d4a7", + "local.c": "7c80decbc7b2be00caabc4f90a6b3f3a2ba7539cf3b28375dfa02ae88faecddd", + "manifest.json": "f0d2ef9c2520d6024c2a47ddff0ab00e42f2b19ad57d499e8feebc753e2372d6", + "short.c": "c554fae4e02d32fc93cb6981acc5fd8da18a7701658c37b15628f93cc47dff12", + "skipped.c": "76ff7ddd9e1c3f16035828208a32ba60b6388b11c3fa90dd90e712a002cf2b4c", + "stride.c": "461e2b3953a91ee8959851202d4cbd622d57dd94766d251b24d05d5a6f9e14b4", + "zero.c": "36588d605e42a798c47adb38fbb9e6ce336ee54b533a32206920178ae0cd0e81" +} diff --git a/test/evaluation/rfc0029/reverse-initialization/good.c b/test/evaluation/rfc0029/reverse-initialization/good.c new file mode 100644 index 00000000..c158272a --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/good.c @@ -0,0 +1,2 @@ +int fill(unsigned char*,unsigned char); +int main(int argc,char**argv){(void)argv;unsigned char out[4];return fill(out,argc>1?4:2);} diff --git a/test/evaluation/rfc0029/reverse-initialization/library.c b/test/evaluation/rfc0029/reverse-initialization/library.c new file mode 100644 index 00000000..46c7b185 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/library.c @@ -0,0 +1,8 @@ +int fill(unsigned char *out,unsigned char length) { + if(length<1 || length>4)return 0; + unsigned char i; + unsigned code=42; + for(i=(unsigned char)(length-1);i>0;i--){out[i]=(unsigned char)code;code>>=1;} + out[0]=1; + return out[length-1]; +} diff --git a/test/evaluation/rfc0029/reverse-initialization/local.c b/test/evaluation/rfc0029/reverse-initialization/local.c new file mode 100644 index 00000000..597e66d6 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/local.c @@ -0,0 +1,6 @@ +int main(int argc,char**argv){ + (void)argv;unsigned char out[4];unsigned char length=argc>1?4:2; + unsigned char i;unsigned code=42; + for(i=(unsigned char)(length-1);i>0;i--){out[i]=(unsigned char)code;code>>=1;} + out[0]=1;return out[length-1]; +} diff --git a/test/evaluation/rfc0029/reverse-initialization/manifest.json b/test/evaluation/rfc0029/reverse-initialization/manifest.json new file mode 100644 index 00000000..f1050dd1 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/manifest.json @@ -0,0 +1,126 @@ +{ + "version": 1, + "cases": [ + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "fill" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "local", + "sources": [ + "local.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "skipped", + "sources": [ + "skipped.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + }, + { + "name": "early", + "sources": [ + "early.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + }, + { + "name": "stride", + "sources": [ + "stride.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + }, + { + "name": "zero", + "sources": [ + "zero.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|interval|bound|extent" + } + ] +} diff --git a/test/evaluation/rfc0029/reverse-initialization/short.c b/test/evaluation/rfc0029/reverse-initialization/short.c new file mode 100644 index 00000000..236f728c --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/short.c @@ -0,0 +1,2 @@ +int fill(unsigned char*,unsigned char); +int main(void){unsigned char out[3];return fill(out,4);} diff --git a/test/evaluation/rfc0029/reverse-initialization/skipped.c b/test/evaluation/rfc0029/reverse-initialization/skipped.c new file mode 100644 index 00000000..b31c2200 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/skipped.c @@ -0,0 +1 @@ +int main(void){unsigned char out[4];unsigned i;for(i=3;i>0;i--){if(i!=2)out[i]=1;}out[0]=1;return out[2];} diff --git a/test/evaluation/rfc0029/reverse-initialization/stride.c b/test/evaluation/rfc0029/reverse-initialization/stride.c new file mode 100644 index 00000000..242d56b8 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/stride.c @@ -0,0 +1 @@ +int main(void){unsigned char out[4];int i;for(i=3;i>0;i-=2)out[i]=1;out[0]=1;return out[2];} diff --git a/test/evaluation/rfc0029/reverse-initialization/zero.c b/test/evaluation/rfc0029/reverse-initialization/zero.c new file mode 100644 index 00000000..ab021275 --- /dev/null +++ b/test/evaluation/rfc0029/reverse-initialization/zero.c @@ -0,0 +1 @@ +int main(void){unsigned char out[4];unsigned i;for(i=3;i>0;i--)out[i]=1;return out[0];} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/advanced-manifest.json b/test/evaluation/rfc0029/scalar-write-offsets/advanced-manifest.json new file mode 100644 index 00000000..753567af --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/advanced-manifest.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "cases": [ + { + "name": "advanced-read", + "sources": [ + "advanced-read.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval" + } + ] +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/advanced-read.c b/test/evaluation/rfc0029/scalar-write-offsets/advanced-read.c new file mode 100644 index 00000000..edc48cd7 --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/advanced-read.c @@ -0,0 +1,6 @@ +static int advanced(unsigned char *p) { *p = 7; ++p; return *p; } +int main(void) { + unsigned char bytes[2] = {1, 42}; + if (advanced(bytes) != 7) bytes[2] = 1; + return 0; +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/advanced-sha256.json b/test/evaluation/rfc0029/scalar-write-offsets/advanced-sha256.json new file mode 100644 index 00000000..1dc0076d --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/advanced-sha256.json @@ -0,0 +1,4 @@ +{ + "advanced-read.c": "ee8c69cf355131f668e013a4d12fd50a9dcfc0433f79a5f5d4af4829f59b2aad", + "advanced-manifest.json": "889dc3050049db22b5eae0605860c68d45b51bf73a0b7b58d6710aadc84376fb" +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/audit.md b/test/evaluation/rfc0029/scalar-write-offsets/audit.md new file mode 100644 index 00000000..9bfe98d1 --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/audit.md @@ -0,0 +1,17 @@ +# Scalar cell identity regressions + +The original `frozen-sha256.json` and `manifest.json` predate the scalar-cell +fixes. `extended-sha256.json` adds heap equivalents without changing the two +original files or their expected outcomes. Both inventories must be verified +when evaluating `extended-manifest.json`. + +The separately frozen `advanced-sha256.json` covers `advanced-read.c` and its +manifest. Its helper writes 7 through a pointer, advances to the second byte, +and returns that byte. The client initializes the second byte to 42, so its +out-of-bounds write executes. The RFC0028 baseline executable (SHA-256 +`47b881a29c1bb9b7ee547f144e72ef0342376d9a12a2d4ca72db6417156510a3`) +and RFC0029 candidate39d (`d9374707a644f21517563d6a2c727e45f15da4dd72c3d0a36f9c3be6242c5607`) +accept the selected client. An ordinary Clang build of the unchanged file with +`-std=c11 -O0 -g -fsanitize=address,undefined` reports the executed array index +and stack-buffer-overflow. Candidate40b rejects the selected client. This is a +concrete counterexample, not an expectation changed to match the checker. diff --git a/test/evaluation/rfc0029/scalar-write-offsets/audited-manifest.json b/test/evaluation/rfc0029/scalar-write-offsets/audited-manifest.json new file mode 100644 index 00000000..9890e19f --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/audited-manifest.json @@ -0,0 +1,78 @@ +{ + "version": 1, + "cases": [ + { + "name": "wrong-base-byte", + "sources": [ + "wrong-base-byte.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval" + }, + { + "name": "unreachable-write", + "sources": [ + "unreachable-write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "heap-wrong-base-byte", + "sources": [ + "heap-wrong-base-byte.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval" + }, + { + "name": "heap-unreachable-write", + "sources": [ + "heap-unreachable-write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "advanced-read", + "sources": [ + "advanced-read.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval" + } + ] +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/audited-sha256.json b/test/evaluation/rfc0029/scalar-write-offsets/audited-sha256.json new file mode 100644 index 00000000..19b9d785 --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/audited-sha256.json @@ -0,0 +1,8 @@ +{ + "heap-wrong-base-byte.c": "3bea322945fed9ed9e11b07a0e66e56be2a5cad7c8f1f83817c848763b6e4401", + "heap-unreachable-write.c": "d7282e75af503b68adb2abcad140ff865d823df36cab6db39bfe140dbeffdffc", + "extended-manifest.json": "e7f2efc46eb36207a47009bc4cf52d54482d3fe1b6b9c4f399facdcc6af52ae8", + "advanced-read.c": "ee8c69cf355131f668e013a4d12fd50a9dcfc0433f79a5f5d4af4829f59b2aad", + "advanced-manifest.json": "889dc3050049db22b5eae0605860c68d45b51bf73a0b7b58d6710aadc84376fb", + "audited-manifest.json": "0b142f85bb76a24f4f3773d3735ab3a6e08232d70c6598ec280fa54ce2327c2a" +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/extended-manifest.json b/test/evaluation/rfc0029/scalar-write-offsets/extended-manifest.json new file mode 100644 index 00000000..d00b8655 --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/extended-manifest.json @@ -0,0 +1,63 @@ +{ + "version": 1, + "cases": [ + { + "name": "wrong-base-byte", + "sources": [ + "wrong-base-byte.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval" + }, + { + "name": "unreachable-write", + "sources": [ + "unreachable-write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "heap-wrong-base-byte", + "sources": [ + "heap-wrong-base-byte.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval" + }, + { + "name": "heap-unreachable-write", + "sources": [ + "heap-unreachable-write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/extended-sha256.json b/test/evaluation/rfc0029/scalar-write-offsets/extended-sha256.json new file mode 100644 index 00000000..8cae3fcb --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/extended-sha256.json @@ -0,0 +1,5 @@ +{ + "heap-wrong-base-byte.c": "3bea322945fed9ed9e11b07a0e66e56be2a5cad7c8f1f83817c848763b6e4401", + "heap-unreachable-write.c": "d7282e75af503b68adb2abcad140ff865d823df36cab6db39bfe140dbeffdffc", + "extended-manifest.json": "e7f2efc46eb36207a47009bc4cf52d54482d3fe1b6b9c4f399facdcc6af52ae8" +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/frozen-sha256.json b/test/evaluation/rfc0029/scalar-write-offsets/frozen-sha256.json new file mode 100644 index 00000000..f3138a2a --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/frozen-sha256.json @@ -0,0 +1,5 @@ +{ + "manifest.json": "097e4bf37179bc3442726f7b1b4168c01a9bbaab0fba84177654f675005c0337", + "unreachable-write.c": "1512dedc92c03fe5e4d5dfb59ac6f9142a067472448ea8558efdab794d9e3aed", + "wrong-base-byte.c": "ad319d7bdf072162092c3bf382964b9c1d4d714ea1199b863adccde05a21e50a" +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/heap-unreachable-write.c b/test/evaluation/rfc0029/scalar-write-offsets/heap-unreachable-write.c new file mode 100644 index 00000000..f354ce17 --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/heap-unreachable-write.c @@ -0,0 +1,14 @@ +static void fill(unsigned char *p) { + unsigned char *out = p; + *out++ = 7; + *out = 0; +} +#include +int main(void) { + unsigned char *bytes = malloc(2); + if (!bytes) return 0; + bytes[0] = bytes[1] = 1; + fill(bytes); + if (*bytes == 0) bytes[2] = 1; + free(bytes); return 0; +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/heap-wrong-base-byte.c b/test/evaluation/rfc0029/scalar-write-offsets/heap-wrong-base-byte.c new file mode 100644 index 00000000..aa2e29a3 --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/heap-wrong-base-byte.c @@ -0,0 +1,14 @@ +static void fill(unsigned char *p) { + unsigned char *out = p; + *out++ = 7; + *out = 0; +} +#include +int main(void) { + unsigned char *bytes = malloc(2); + if (!bytes) return 0; + bytes[0] = bytes[1] = 1; + fill(bytes); + if (*bytes == 7) bytes[2] = 1; + free(bytes); return 0; +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/manifest.json b/test/evaluation/rfc0029/scalar-write-offsets/manifest.json new file mode 100644 index 00000000..db4dac4f --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/manifest.json @@ -0,0 +1,34 @@ +{ + "version": 1, + "cases": [ + { + "name": "wrong-base-byte", + "sources": [ + "wrong-base-byte.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|extent|interval" + }, + { + "name": "unreachable-write", + "sources": [ + "unreachable-write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/unreachable-write.c b/test/evaluation/rfc0029/scalar-write-offsets/unreachable-write.c new file mode 100644 index 00000000..1e0f93d9 --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/unreachable-write.c @@ -0,0 +1,11 @@ +static void fill(unsigned char *p) { + unsigned char *out = p; + *out++ = 7; + *out = 0; +} +int main(void) { + unsigned char bytes[2] = {1, 1}; + fill(bytes); + if (bytes[0] == 0) bytes[2] = 1; + return 0; +} diff --git a/test/evaluation/rfc0029/scalar-write-offsets/wrong-base-byte.c b/test/evaluation/rfc0029/scalar-write-offsets/wrong-base-byte.c new file mode 100644 index 00000000..5d9295a3 --- /dev/null +++ b/test/evaluation/rfc0029/scalar-write-offsets/wrong-base-byte.c @@ -0,0 +1,11 @@ +static void fill(unsigned char *p) { + unsigned char *out = p; + *out++ = 7; + *out = 0; +} +int main(void) { + unsigned char bytes[2] = {1, 1}; + fill(bytes); + if (bytes[0] == 7) bytes[2] = 1; + return 0; +} diff --git a/test/evaluation/rfc0029/serializer/client.c b/test/evaluation/rfc0029/serializer/client.c new file mode 100644 index 00000000..b8abf89a --- /dev/null +++ b/test/evaluation/rfc0029/serializer/client.c @@ -0,0 +1,13 @@ +#include "hex.h" +#include +int encode_client(size_t count) { + if (count > 4096) return 0; + unsigned char *input = calloc(count, 1); + if (!input) return 0; + struct text output = {0}; + int success = hex_encode(&output, input, count); + hex_dispose(&output); + free(input); + return success; +} +int main(void) { return encode_client(33) < 0; } diff --git a/test/evaluation/rfc0029/serializer/frozen-sha256.json b/test/evaluation/rfc0029/serializer/frozen-sha256.json new file mode 100644 index 00000000..6d30db9c --- /dev/null +++ b/test/evaluation/rfc0029/serializer/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "client.c": "934a81aaf0da28ac009724e9332c5c9a9868af340ab71673da56c5145caa54da", + "hex.c": "1c441c1ca087a0606d6506250489092d032443c75040627e521ebdb792d115be", + "hex.h": "2c6a51e16e64ff874dd03a1d68d768fcb086119377b04e265f3bdc1784d41511", + "manifest.json": "79250eb9f8926fb3f6837d26d5174840f50e17f1401b469745b015921568ff8f", + "provenance.md": "902b6502b0fc47578ddb70b82f67f881dd107046a9654fe8b29b324f66d0e15c", + "short.c": "73260d366d63f5a08c93b1e615640039e02ecfb2b729bdf982c26312cbd255f1" +} diff --git a/test/evaluation/rfc0029/serializer/hex.c b/test/evaluation/rfc0029/serializer/hex.c new file mode 100644 index 00000000..c171ff28 --- /dev/null +++ b/test/evaluation/rfc0029/serializer/hex.c @@ -0,0 +1,30 @@ +#include "hex.h" +#include +static int push(struct text *out, unsigned char value) { + if (out->written == out->allocated) { + if (out->allocated > 1048576) return 0; + size_t next = out->allocated + 32; + unsigned char *grown = realloc(out->storage, next); + if (!grown) return 0; + out->storage = grown; + out->allocated = next; + } + out->storage[out->written++] = value; + return 1; +} +int hex_encode(struct text *out, const unsigned char *data, size_t size) { + for (size_t i = 0; i < size; ++i) { + unsigned char byte = data[i]; + unsigned char high = byte >> 4; + unsigned char low = byte & 15; + if (!push(out, high < 10 ? '0' + high : 'a' + high - 10)) return 0; + if (!push(out, low < 10 ? '0' + low : 'a' + low - 10)) return 0; + } + return 1; +} +void hex_dispose(struct text *out) { + free(out->storage); + out->storage = 0; + out->written = 0; + out->allocated = 0; +} diff --git a/test/evaluation/rfc0029/serializer/hex.h b/test/evaluation/rfc0029/serializer/hex.h new file mode 100644 index 00000000..6e6fa5f8 --- /dev/null +++ b/test/evaluation/rfc0029/serializer/hex.h @@ -0,0 +1,14 @@ +#ifndef RFC29_HEX_H +#define RFC29_HEX_H +#include +struct text { + unsigned generation; + size_t allocated; + unsigned char *storage; + unsigned flags; + size_t written; + size_t depth; +}; +int hex_encode(struct text *out, const unsigned char *data, size_t size); +void hex_dispose(struct text *out); +#endif diff --git a/test/evaluation/rfc0029/serializer/manifest.json b/test/evaluation/rfc0029/serializer/manifest.json new file mode 100644 index 00000000..7518f44c --- /dev/null +++ b/test/evaluation/rfc0029/serializer/manifest.json @@ -0,0 +1,54 @@ +{ + "version": 1, + "cases": [ + { + "name": "runtime-hex", + "sources": [ + "client.c", + "hex.c" + ], + "functions": [ + "encode_client", + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "generic-hex", + "sources": [ + "hex.c" + ], + "functions": [ + "hex_encode", + "push", + "hex_dispose" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "short-input", + "sources": [ + "short.c", + "hex.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "interval|extent|bounds", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/serializer/provenance.md b/test/evaluation/rfc0029/serializer/provenance.md new file mode 100644 index 00000000..232b9deb --- /dev/null +++ b/test/evaluation/rfc0029/serializer/provenance.md @@ -0,0 +1 @@ +Independent RFC 0029 workflow, authored and frozen on 2026-09-16 after the primary fixtures. A streaming hexadecimal serializer with runtime-sized input, repeated growth, allocation failure, separate-source calls and explicit cleanup. It does not share implementation headers with the primary writer. Frozen before its first analyzer run. diff --git a/test/evaluation/rfc0029/serializer/short.c b/test/evaluation/rfc0029/serializer/short.c new file mode 100644 index 00000000..2bc39f0b --- /dev/null +++ b/test/evaluation/rfc0029/serializer/short.c @@ -0,0 +1,8 @@ +#include "hex.h" +int main(void) { + unsigned char input[2] = {1, 2}; + struct text output = {0}; + int result = hex_encode(&output, input, 8); + hex_dispose(&output); + return result; +} diff --git a/test/evaluation/rfc0029/shifted-pointee-facts/README.md b/test/evaluation/rfc0029/shifted-pointee-facts/README.md new file mode 100644 index 00000000..4775a649 --- /dev/null +++ b/test/evaluation/rfc0029/shifted-pointee-facts/README.md @@ -0,0 +1 @@ +Frozen before candidate 66. The source establishes a zero byte at the original pointer, then reads a distinct byte through an adjusted pointer. Carrying the first byte value across that adjustment would hide an actual out-of-bounds stack write. The unchanged-pointer case has the same cell and must remain accepted. diff --git a/test/evaluation/rfc0029/shifted-pointee-facts/back.c b/test/evaluation/rfc0029/shifted-pointee-facts/back.c new file mode 100644 index 00000000..b72424d4 --- /dev/null +++ b/test/evaluation/rfc0029/shifted-pointee-facts/back.c @@ -0,0 +1,2 @@ +int f(const unsigned char *p,unsigned n){if(*p!=0)return 0;const unsigned char *q=p-1;int a[1]={0};if(*q!=0)a[3]=1;return a[0];} +int main(void){const unsigned char a[2]={1,0};return f(a+1,1);} diff --git a/test/evaluation/rfc0029/shifted-pointee-facts/frozen-sha256.json b/test/evaluation/rfc0029/shifted-pointee-facts/frozen-sha256.json new file mode 100644 index 00000000..56a8cecc --- /dev/null +++ b/test/evaluation/rfc0029/shifted-pointee-facts/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "README.md": "11236ac6f13875845cfab852a68c3b52d1359634d294eb06673ae6e9ec202654", + "back.c": "c1ce9c5f0e7f186c60e544b59bc8e84fababb7957d6c02f793beebf3960c3fc2", + "manifest.json": "062591db325c917a3f6f893f58ca7131c8d9ee287ad67936c9b239490d6998ff", + "same.c": "1fcbfbccbf0234351a050f3b9c6e8744163e82fa8185456e06a7bbad1538e6c3", + "shift.c": "6554ee2117f0a43e3071676c150e802cba6bf1855a96107361f67ea4a198b013", + "unknown.c": "69a832ff2e9d9e4be816f04dd33f9a44d2a370ccdf0baf273bce1928d9d21c64" +} diff --git a/test/evaluation/rfc0029/shifted-pointee-facts/manifest.json b/test/evaluation/rfc0029/shifted-pointee-facts/manifest.json new file mode 100644 index 00000000..ac3ed5c7 --- /dev/null +++ b/test/evaluation/rfc0029/shifted-pointee-facts/manifest.json @@ -0,0 +1,65 @@ +{ + "version": 1, + "cases": [ + { + "name": "same", + "sources": [ + "same.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "shift", + "sources": [ + "shift.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|interval|extent" + }, + { + "name": "unknown", + "sources": [ + "unknown.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|interval|extent" + }, + { + "name": "back", + "sources": [ + "back.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bound|interval|extent" + } + ] +} diff --git a/test/evaluation/rfc0029/shifted-pointee-facts/same.c b/test/evaluation/rfc0029/shifted-pointee-facts/same.c new file mode 100644 index 00000000..fc4d30bd --- /dev/null +++ b/test/evaluation/rfc0029/shifted-pointee-facts/same.c @@ -0,0 +1,2 @@ +int f(const unsigned char *p,unsigned n){if(*p!=0)return 0;const unsigned char *q=p;int a[1]={0};if(*q!=0)a[3]=1;return a[0];} +int main(void){const unsigned char a[2]={0,1};return f(a,1);} diff --git a/test/evaluation/rfc0029/shifted-pointee-facts/shift.c b/test/evaluation/rfc0029/shifted-pointee-facts/shift.c new file mode 100644 index 00000000..4f48e81f --- /dev/null +++ b/test/evaluation/rfc0029/shifted-pointee-facts/shift.c @@ -0,0 +1,2 @@ +int f(const unsigned char *p,unsigned n){if(*p!=0)return 0;const unsigned char *q=p+1;int a[1]={0};if(*q!=0)a[3]=1;return a[0];} +int main(void){const unsigned char a[2]={0,1};return f(a,1);} diff --git a/test/evaluation/rfc0029/shifted-pointee-facts/unknown.c b/test/evaluation/rfc0029/shifted-pointee-facts/unknown.c new file mode 100644 index 00000000..c7ecd02c --- /dev/null +++ b/test/evaluation/rfc0029/shifted-pointee-facts/unknown.c @@ -0,0 +1,2 @@ +int f(const unsigned char *p,unsigned n){if(*p!=0)return 0;const unsigned char *q=p+n;int a[1]={0};if(*q!=0)a[3]=1;return a[0];} +int main(void){const unsigned char a[2]={0,1};return f(a,1);} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/PROVENANCE.md b/test/evaluation/rfc0029/singleton-link-ownership/PROVENANCE.md new file mode 100644 index 00000000..147c6cd9 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/PROVENANCE.md @@ -0,0 +1,6 @@ +RFC 0029 independently initialized singleton ownership regression. +Frozen against candidate93a before evaluating candidate94a. Unlike the +separately retained bare calloc-return population, this constructor spells +out its initialized links and selector. Actual owned heads can accept fresh +children and preserve them across an independent reader update, including +helper forwarding. Lost, released and disowned children remain negative. diff --git a/test/evaluation/rfc0029/singleton-link-ownership/api.h b/test/evaluation/rfc0029/singleton-link-ownership/api.h new file mode 100644 index 00000000..97c6c799 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/api.h @@ -0,0 +1,4 @@ +#include +struct node{struct node *next,*child;unsigned flags;}; +struct reader{unsigned position,capacity;}; +struct node *make(void);void drop(struct node*);void build(struct node*,struct reader*);void step(struct reader*); diff --git a/test/evaluation/rfc0029/singleton-link-ownership/client.c b/test/evaluation/rfc0029/singleton-link-ownership/client.c new file mode 100644 index 00000000..0c1813c0 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *p=make();if(!p)return 0;struct reader r={0,2};build(p,&r);drop(p);return 0;} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/direct.c b/test/evaluation/rfc0029/singleton-link-ownership/direct.c new file mode 100644 index 00000000..22b7e080 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/direct.c @@ -0,0 +1,2 @@ +#include "api.h" +void build(struct node *p,struct reader *r){struct node *q=make();if(!q)return;p->child=q;r->position=1;} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/disowned.c b/test/evaluation/rfc0029/singleton-link-ownership/disowned.c new file mode 100644 index 00000000..25a9b42b --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/disowned.c @@ -0,0 +1,2 @@ +#include "api.h" +void build(struct node *p,struct reader *r){struct node *q=make();if(!q)return;p->child=q;p->flags=1;r->position=1;} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/drop.c b/test/evaluation/rfc0029/singleton-link-ownership/drop.c new file mode 100644 index 00000000..60fb58b6 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/drop.c @@ -0,0 +1,2 @@ +#include "api.h" +void drop(struct node *p){while(p){struct node *n=p->next;if(!(p->flags&1))drop(p->child);free(p);p=n;}} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/forwarded.c b/test/evaluation/rfc0029/singleton-link-ownership/forwarded.c new file mode 100644 index 00000000..7889ae7b --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/forwarded.c @@ -0,0 +1,2 @@ +#include "api.h" +void build(struct node *p,struct reader *r){struct node *q=make();if(!q)return;p->child=q;step(r);} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/frozen-sha256.json b/test/evaluation/rfc0029/singleton-link-ownership/frozen-sha256.json new file mode 100644 index 00000000..5a2270e1 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/frozen-sha256.json @@ -0,0 +1,14 @@ +{ + "PROVENANCE.md": "199626301983d2c85e09ed7a34fe8fcfffa7618a47cfb437966a215192fb3640", + "api.h": "1baba5b2d61893855be2f88d3136e9f44fd47c7327357cddf237ecbc1b014b13", + "client.c": "c47b60552cf294b376e5f1d3796efdd953ff9141b6a3cf29fa084baf14b17cb4", + "direct.c": "afdec70bd7f5045b15821d355d0620a0d8adc5b603833f7ea4fac80ad6f68933", + "disowned.c": "87224ed58cb85ba6ab264e97e208134177f8807923f4e9e5023f3b5d9b0c8e48", + "drop.c": "d3b77067ce2d555d17b8b87821b18e27140287892105374536853ff8864e841d", + "forwarded.c": "cb7ba9f7be2db8599d5ff7a07b11c99cab044d53ed3ec5b82e9148f9519e994f", + "lost.c": "3185d7b7a8974b7a6ef449214aed2c5a43872950556eba88abd3661cd93f2df9", + "make.c": "895e881cecd5b887eea8cefaa41169decf73044d85e926c5634edf00dfbf761e", + "manifest.json": "1da91bc6de3c223b1f71eb5de4bbd84e6491b6813cc62e0e38f2a19f676c0dcc", + "released.c": "abe80f9c15ab93962443535835bb78e3dbfd09bb8fdf3ee5c56431358f6d5d68", + "step.c": "0955ad47eb6f350dbb972d46486ea73e1bb49bcf8fd359d597377f186d21f6d5" +} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/lost.c b/test/evaluation/rfc0029/singleton-link-ownership/lost.c new file mode 100644 index 00000000..2ddd4117 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/lost.c @@ -0,0 +1,2 @@ +#include "api.h" +void build(struct node *p,struct reader *r){struct node *q=make();if(!q)return;p->child=q;p->child=0;r->position=1;} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/make.c b/test/evaluation/rfc0029/singleton-link-ownership/make.c new file mode 100644 index 00000000..ea31e074 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/make.c @@ -0,0 +1,2 @@ +#include "api.h" +struct node *make(void){struct node *p=malloc(sizeof *p);if(p){p->next=0;p->child=0;p->flags=0;}return p;} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/manifest.json b/test/evaluation/rfc0029/singleton-link-ownership/manifest.json new file mode 100644 index 00000000..f066f994 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/manifest.json @@ -0,0 +1,100 @@ +{ + "version": 1, + "cases": [ + { + "name": "direct", + "sources": [ + "client.c", + "direct.c", + "make.c", + "drop.c", + "step.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "entry_requirements": 0 + }, + { + "name": "forwarded", + "sources": [ + "client.c", + "forwarded.c", + "make.c", + "drop.c", + "step.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "entry_requirements": 0 + }, + { + "name": "lost", + "sources": [ + "client.c", + "lost.c", + "make.c", + "drop.c", + "step.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|ownership|release|valid|container" + }, + { + "name": "released", + "sources": [ + "client.c", + "released.c", + "make.c", + "drop.c", + "step.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|ownership|release|valid|container" + }, + { + "name": "disowned", + "sources": [ + "client.c", + "disowned.c", + "make.c", + "drop.c", + "step.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "footprint|ownership|release|valid|container" + } + ] +} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/released.c b/test/evaluation/rfc0029/singleton-link-ownership/released.c new file mode 100644 index 00000000..778b3307 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/released.c @@ -0,0 +1,2 @@ +#include "api.h" +void build(struct node *p,struct reader *r){struct node *q=make();if(!q)return;p->child=q;drop(q);r->position=1;} diff --git a/test/evaluation/rfc0029/singleton-link-ownership/step.c b/test/evaluation/rfc0029/singleton-link-ownership/step.c new file mode 100644 index 00000000..9f66db12 --- /dev/null +++ b/test/evaluation/rfc0029/singleton-link-ownership/step.c @@ -0,0 +1,2 @@ +#include "api.h" +void step(struct reader *r){r->position=1;} diff --git a/test/evaluation/rfc0029/span-advances/README.md b/test/evaluation/rfc0029/span-advances/README.md new file mode 100644 index 00000000..0821fb75 --- /dev/null +++ b/test/evaluation/rfc0029/span-advances/README.md @@ -0,0 +1,4 @@ +# Returned span counts used to advance cursors (RFC 0029) + +Frozen before candidate 57. Capture and use the actual count and entry +coordinates. A changed step or a count larger than the span remains rejected. diff --git a/test/evaluation/rfc0029/span-advances/frozen-sha256.json b/test/evaluation/rfc0029/span-advances/frozen-sha256.json new file mode 100644 index 00000000..137e40cd --- /dev/null +++ b/test/evaluation/rfc0029/span-advances/frozen-sha256.json @@ -0,0 +1,10 @@ +{ + "README.md": "1d0cb9e9c3f1580edf52d18114592efcab770daefab78175be5077033193c8c3", + "good.c": "597619f251073adffa37dd2403b28f214f47730826d5d91dac290689fad74bb8", + "library.c": "7133e824131b1cb21becfa1c70b375f7184b55c2c50f142de0c8cddd2939d33d", + "manifest.json": "99fad3060211754477993a32b4ddb9fd806ee7b2abb13d2e72b5df0052ef957d", + "once.c": "8fca10dd00378c582ffc7c81ddb37cf13655c12971abfb7f9a9224252522fc76", + "over-count.c": "3f444c988057c77d97e924b80330041588c1c237ed39c2afb5e374043a8e2b7c", + "over-step.c": "24b0a0debf4c1beb4a738acb2b26c71582b61e7ec5e815661a4d86c397fb6275", + "uninitialized.c": "269c20de514ba97e95d5600eb38211c631e702a9883342631cc2c96a77aada79" +} diff --git a/test/evaluation/rfc0029/span-advances/good.c b/test/evaluation/rfc0029/span-advances/good.c new file mode 100644 index 00000000..88e2db01 --- /dev/null +++ b/test/evaluation/rfc0029/span-advances/good.c @@ -0,0 +1,5 @@ +unsigned char consume(const unsigned char*,const unsigned char*,int); +int main(int argc,char**argv){(void)argv;const unsigned char input[8]={0};const unsigned char *p=input,*end=input+8; + while(p1);if(!n)return 0;p+=n;} + return p==end?0:1; +} diff --git a/test/evaluation/rfc0029/span-advances/library.c b/test/evaluation/rfc0029/span-advances/library.c new file mode 100644 index 00000000..5a63495e --- /dev/null +++ b/test/evaluation/rfc0029/span-advances/library.c @@ -0,0 +1,5 @@ +unsigned char consume(const unsigned char *first,const unsigned char *last,int mode){ + unsigned char n=0;if(last-first<2)return 0; + if(mode){n=4;if(last-(first+2)<2)return 0;}else n=2; + return n; +} diff --git a/test/evaluation/rfc0029/span-advances/manifest.json b/test/evaluation/rfc0029/span-advances/manifest.json new file mode 100644 index 00000000..6e25ef85 --- /dev/null +++ b/test/evaluation/rfc0029/span-advances/manifest.json @@ -0,0 +1,98 @@ +{ + "version": 1, + "cases": [ + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "consume" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "once", + "sources": [ + "once.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "over-count", + "sources": [ + "over-count.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|pointer|span" + }, + { + "name": "over-step", + "sources": [ + "over-step.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|pointer|span" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|pointer|span" + } + ] +} diff --git a/test/evaluation/rfc0029/span-advances/once.c b/test/evaluation/rfc0029/span-advances/once.c new file mode 100644 index 00000000..ca632edf --- /dev/null +++ b/test/evaluation/rfc0029/span-advances/once.c @@ -0,0 +1,5 @@ +unsigned char consume(const unsigned char*,const unsigned char*,int); +int main(int argc,char**argv){(void)argv;const unsigned char input[8]={0};const unsigned char *p=input,*end=input+8; + if(p1);if(!n)return 0;p+=n;} + return p==end?0:1; +} diff --git a/test/evaluation/rfc0029/span-advances/over-count.c b/test/evaluation/rfc0029/span-advances/over-count.c new file mode 100644 index 00000000..b8daa764 --- /dev/null +++ b/test/evaluation/rfc0029/span-advances/over-count.c @@ -0,0 +1,9 @@ +unsigned char consume(const unsigned char *first,const unsigned char *last,int mode){ + unsigned char n=0;if(last-first<2)return 0; + if(mode){n=5;if(last-(first+2)<2)return 0;}else n=2; + return n; +} +int main(int argc,char**argv){(void)argv;const unsigned char input[8]={0};const unsigned char *p=input,*end=input+8; + while(p1);if(!n)return 0;p+=n;} + return p==end?0:1; +} diff --git a/test/evaluation/rfc0029/span-advances/over-step.c b/test/evaluation/rfc0029/span-advances/over-step.c new file mode 100644 index 00000000..000e1109 --- /dev/null +++ b/test/evaluation/rfc0029/span-advances/over-step.c @@ -0,0 +1,5 @@ +unsigned char consume(const unsigned char*,const unsigned char*,int); +int main(int argc,char**argv){(void)argv;const unsigned char input[8]={0};const unsigned char *p=input,*end=input+8; + while(p1);if(!n)return 0;p+=n+1;} + return p==end?0:1; +} diff --git a/test/evaluation/rfc0029/span-advances/uninitialized.c b/test/evaluation/rfc0029/span-advances/uninitialized.c new file mode 100644 index 00000000..2a4cec0e --- /dev/null +++ b/test/evaluation/rfc0029/span-advances/uninitialized.c @@ -0,0 +1,5 @@ +unsigned char consume(const unsigned char*,const unsigned char*,int); +int main(int argc,char**argv){(void)argv;unsigned char input[8];input[0]=0;const unsigned char *p=input,*end=input+8; + while(p1);if(!n)return 0;p+=n;} + return p==end?0:1; +} diff --git a/test/evaluation/rfc0029/span-count-joins/README.md b/test/evaluation/rfc0029/span-count-joins/README.md new file mode 100644 index 00000000..771bb40e --- /dev/null +++ b/test/evaluation/rfc0029/span-count-joins/README.md @@ -0,0 +1,5 @@ +# Joined byte-span counts (RFC 0029) + +Frozen before candidate 50. These cases retain a returned count established +on separate branches, including an assignment before its validating guard. +The false count and post-guard mutation must remain rejected. diff --git a/test/evaluation/rfc0029/span-count-joins/changed-count.c b/test/evaluation/rfc0029/span-count-joins/changed-count.c new file mode 100644 index 00000000..939f23eb --- /dev/null +++ b/test/evaluation/rfc0029/span-count-joins/changed-count.c @@ -0,0 +1,9 @@ +unsigned consume(const unsigned char *first,const unsigned char *last,int mode){ + unsigned count=0; + if(last-first<2)return 0; + if(mode){count=4;if(last-first<4)return 0;} + else count=2; + count+=4; + return count; +} +int main(void){const unsigned char bytes[4]={1,2,3,4};return bytes[consume(bytes,bytes+4,1)-1];} diff --git a/test/evaluation/rfc0029/span-count-joins/false-count.c b/test/evaluation/rfc0029/span-count-joins/false-count.c new file mode 100644 index 00000000..e15af9ba --- /dev/null +++ b/test/evaluation/rfc0029/span-count-joins/false-count.c @@ -0,0 +1,8 @@ +unsigned consume(const unsigned char *first,const unsigned char *last,int mode){ + unsigned count=0; + if(last-first<2)return 0; + if(mode){count=5;if(last-first<4)return 0;} + else count=2; + return count; +} +int main(void){const unsigned char bytes[4]={1,2,3,4};return bytes[consume(bytes,bytes+4,1)-1];} diff --git a/test/evaluation/rfc0029/span-count-joins/frozen-sha256.json b/test/evaluation/rfc0029/span-count-joins/frozen-sha256.json new file mode 100644 index 00000000..5d99853c --- /dev/null +++ b/test/evaluation/rfc0029/span-count-joins/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "README.md": "02a97092bf16bcbc2a5ed3e2690599c7075545b5889cb239a2cb554832ad318d", + "changed-count.c": "a61c2e7b192d2d55c3353d15c0eb0d9723aaeabeade0b3077588d31d1b92d4a4", + "false-count.c": "d5d0524196ad361a222ec9851d0d1db84a85892ace04b6c32e3826495bc1aefe", + "good.c": "e9c0c62c554564d93cc13f52ebf419a6c4256dba98f218ccb41ac330c9c3c6ef", + "library.c": "e9e65f94289152c82a7c548d23d1ec09bb57b88216f00cb24d74431420e4e19e", + "manifest.json": "1fe0e1aa055adf9bebe225a7f4919dbe209676f471723468408f8f0ed03ccabf", + "short.c": "7416b1a9b29a0a0fb76c76aa2ba3d0603e87385c6ae0f5ce7dda9f53976c2bfe" +} diff --git a/test/evaluation/rfc0029/span-count-joins/good.c b/test/evaluation/rfc0029/span-count-joins/good.c new file mode 100644 index 00000000..4f634bde --- /dev/null +++ b/test/evaluation/rfc0029/span-count-joins/good.c @@ -0,0 +1,2 @@ +int probe(const unsigned char*,const unsigned char*,int); +int main(int argc,char**argv){(void)argv;const unsigned char bytes[4]={1,2,3,4};return probe(bytes,bytes+4,argc>1);} diff --git a/test/evaluation/rfc0029/span-count-joins/library.c b/test/evaluation/rfc0029/span-count-joins/library.c new file mode 100644 index 00000000..9c54f740 --- /dev/null +++ b/test/evaluation/rfc0029/span-count-joins/library.c @@ -0,0 +1,13 @@ +unsigned char consume(const unsigned char *first,const unsigned char *last,int mode) { + unsigned char count=0; + if(last-first<2)return 0; + if(mode){count=4; if(last-(first+2)<2)return 0;} + else {count=2;} + return count; +} +int probe(const unsigned char *first,const unsigned char *last,int mode){ + if(last-first<1)return 0; + unsigned char n=consume(first,last,mode); + if(!n)return 0; + return first[n-1]; +} diff --git a/test/evaluation/rfc0029/span-count-joins/manifest.json b/test/evaluation/rfc0029/span-count-joins/manifest.json new file mode 100644 index 00000000..6551330c --- /dev/null +++ b/test/evaluation/rfc0029/span-count-joins/manifest.json @@ -0,0 +1,81 @@ +{ + "version": 1, + "cases": [ + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "probe" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "false-count", + "sources": [ + "false-count.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|span|initialized" + }, + { + "name": "changed-count", + "sources": [ + "changed-count.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|span|initialized" + } + ] +} diff --git a/test/evaluation/rfc0029/span-count-joins/short.c b/test/evaluation/rfc0029/span-count-joins/short.c new file mode 100644 index 00000000..b563ce3f --- /dev/null +++ b/test/evaluation/rfc0029/span-count-joins/short.c @@ -0,0 +1,2 @@ +int probe(const unsigned char*,const unsigned char*,int); +int main(void){const unsigned char bytes[1]={1};return probe(bytes,bytes+1,1);} diff --git a/test/evaluation/rfc0029/span-counts/README.md b/test/evaluation/rfc0029/span-counts/README.md new file mode 100644 index 00000000..f3312191 --- /dev/null +++ b/test/evaluation/rfc0029/span-counts/README.md @@ -0,0 +1 @@ +Frozen before RFC 0029 count-within-span outputs. A bounded count from a complete helper must retain its relation to the supplied entry endpoints. Generic and concrete callers, including a short early-return input, are positive. A false produced count and uninitialized input remain rejected. No result implies initialized output or processed contents. diff --git a/test/evaluation/rfc0029/span-counts/false-count.c b/test/evaluation/rfc0029/span-counts/false-count.c new file mode 100644 index 00000000..261533dd --- /dev/null +++ b/test/evaluation/rfc0029/span-counts/false-count.c @@ -0,0 +1,12 @@ +unsigned consume(const unsigned char *first, const unsigned char *last, int mode) { + if(last-first<2)return 0; + if(mode){if(last-first<4)return 0;return 5;} + return 2; +} +int probe(const unsigned char *first,const unsigned char *last,int mode){ + if(last-first<1)return 0; + unsigned n=consume(first,last,mode); + if(!n)return 0; + return first[n-1]; +} +int main(void){const unsigned char a[4]={1,2,3,4};return probe(a,a+4,1);} diff --git a/test/evaluation/rfc0029/span-counts/frozen-sha256.json b/test/evaluation/rfc0029/span-counts/frozen-sha256.json new file mode 100644 index 00000000..fb571d3f --- /dev/null +++ b/test/evaluation/rfc0029/span-counts/frozen-sha256.json @@ -0,0 +1,9 @@ +{ + "README.md": "07226c5c93bce3d5fb9fdc82f40b31725a4e48b898f88430391896e0766c3c2b", + "false-count.c": "b723bc49e800852fbc50ce3a778c42c8e14dff188683f399df3156aa0022e07d", + "good.c": "5cb86ef8f745ea5166b088cb3bd76df9302da7f6ae888c262c4a48dc5d447876", + "library.c": "3a292b04f76268cd42883c9a9c15b4dc3e23a447571022215843b8fe857aa805", + "manifest.json": "b0b0f721a2980420c5fceae6c24f85d734eb884fc7012fb1bd20c022e1194979", + "short.c": "3600444cad8800928bab128b5f9beaf3d329776e77a496c443c5ebe4c41a905a", + "uninitialized.c": "a5fdc75dc147d2544ff4eaa9aff03c68befd695c3e4fdd013d0c44578d8cfc8c" +} diff --git a/test/evaluation/rfc0029/span-counts/good.c b/test/evaluation/rfc0029/span-counts/good.c new file mode 100644 index 00000000..529b579a --- /dev/null +++ b/test/evaluation/rfc0029/span-counts/good.c @@ -0,0 +1,2 @@ +int probe(const unsigned char *,const unsigned char *,int); +int main(int argc,char **argv){const unsigned char a[4]={1,2,3,4};return probe(a,a+4,argc>1);} diff --git a/test/evaluation/rfc0029/span-counts/library.c b/test/evaluation/rfc0029/span-counts/library.c new file mode 100644 index 00000000..0dd55b5a --- /dev/null +++ b/test/evaluation/rfc0029/span-counts/library.c @@ -0,0 +1,11 @@ +unsigned consume(const unsigned char *first, const unsigned char *last, int mode) { + if(last-first<2)return 0; + if(mode){if(last-first<4)return 0;return 4;} + return 2; +} +int probe(const unsigned char *first,const unsigned char *last,int mode){ + if(last-first<1)return 0; + unsigned n=consume(first,last,mode); + if(!n)return 0; + return first[n-1]; +} diff --git a/test/evaluation/rfc0029/span-counts/manifest.json b/test/evaluation/rfc0029/span-counts/manifest.json new file mode 100644 index 00000000..f58835a3 --- /dev/null +++ b/test/evaluation/rfc0029/span-counts/manifest.json @@ -0,0 +1,82 @@ +{ + "version": 1, + "cases": [ + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "probe" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "false-count", + "sources": [ + "false-count.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|span|initialized" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "interval|bound|extent|span|initialized" + } + ] +} diff --git a/test/evaluation/rfc0029/span-counts/short.c b/test/evaluation/rfc0029/span-counts/short.c new file mode 100644 index 00000000..780ac817 --- /dev/null +++ b/test/evaluation/rfc0029/span-counts/short.c @@ -0,0 +1,2 @@ +int probe(const unsigned char *,const unsigned char *,int); +int main(void){const unsigned char a[1]={1};return probe(a,a+1,1);} diff --git a/test/evaluation/rfc0029/span-counts/uninitialized.c b/test/evaluation/rfc0029/span-counts/uninitialized.c new file mode 100644 index 00000000..2952e9c5 --- /dev/null +++ b/test/evaluation/rfc0029/span-counts/uninitialized.c @@ -0,0 +1,2 @@ +int probe(const unsigned char *,const unsigned char *,int); +int main(void){unsigned char a[4];a[0]=1;return probe(a,a+4,1);} diff --git a/test/evaluation/rfc0029/span-outputs/README.md b/test/evaluation/rfc0029/span-outputs/README.md new file mode 100644 index 00000000..1ffcdd9a --- /dev/null +++ b/test/evaluation/rfc0029/span-outputs/README.md @@ -0,0 +1 @@ +Frozen before the RFC 0029 read-only span-pair separation exemption. Writing a distinct output does not require two read-only endpoints of one input span to be separate arrays. Output permissions and bounds remain independent caller obligations. diff --git a/test/evaluation/rfc0029/span-outputs/frozen-sha256.json b/test/evaluation/rfc0029/span-outputs/frozen-sha256.json new file mode 100644 index 00000000..a04ad026 --- /dev/null +++ b/test/evaluation/rfc0029/span-outputs/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "README.md": "6323e67753eebb0b56824cd969ace985c49bfe9cd009c17268d59863a3c706e2", + "good.c": "0cc7ad47750706f7c963b4db309eac2b60d0ef3504854723780705c94e1b38c5", + "library.c": "2f0b521543d558ad1a38b9b940f7f25700dd6c1a94d056e97e6d7cfdf8da9af8", + "manifest.json": "fabcf74b565c4ccbf30d2c7755dc97060b3264c1d7c3d3c74926de72e8640048", + "overflow.c": "48004fc25697df364e5982095c94580c378ad6f2232c601594d4c599dabcba72", + "readonly.c": "dfd8b20b752ab4f82b277b7620eded821f03f10765d58ef54488843fc33b5a42" +} diff --git a/test/evaluation/rfc0029/span-outputs/good.c b/test/evaluation/rfc0029/span-outputs/good.c new file mode 100644 index 00000000..fff0cf32 --- /dev/null +++ b/test/evaluation/rfc0029/span-outputs/good.c @@ -0,0 +1,2 @@ +int decode(const unsigned char *,const unsigned char *,unsigned char **); +int main(void){const unsigned char in[2]={1,2};unsigned char a[2]={0,0};unsigned char *p=a;return decode(in,in+2,&p);} diff --git a/test/evaluation/rfc0029/span-outputs/library.c b/test/evaluation/rfc0029/span-outputs/library.c new file mode 100644 index 00000000..5e155446 --- /dev/null +++ b/test/evaluation/rfc0029/span-outputs/library.c @@ -0,0 +1,4 @@ +int decode(const unsigned char *first, const unsigned char *last, unsigned char **out) { + if(last-first<2)return 0; + **out=first[1]; ++*out; return 1; +} diff --git a/test/evaluation/rfc0029/span-outputs/manifest.json b/test/evaluation/rfc0029/span-outputs/manifest.json new file mode 100644 index 00000000..9ad0d6d7 --- /dev/null +++ b/test/evaluation/rfc0029/span-outputs/manifest.json @@ -0,0 +1,67 @@ +{ + "version": 1, + "cases": [ + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "decode" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "good", + "sources": [ + "good.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "readonly", + "sources": [ + "readonly.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "writ|interval|bound|extent|span" + }, + { + "name": "overflow", + "sources": [ + "overflow.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "writ|interval|bound|extent|span" + } + ] +} diff --git a/test/evaluation/rfc0029/span-outputs/overflow.c b/test/evaluation/rfc0029/span-outputs/overflow.c new file mode 100644 index 00000000..c9336d9b --- /dev/null +++ b/test/evaluation/rfc0029/span-outputs/overflow.c @@ -0,0 +1,2 @@ +int decode(const unsigned char *,const unsigned char *,unsigned char **); +int main(void){const unsigned char in[2]={1,2};unsigned char a[2]={0,0};unsigned char *p=a+2;return decode(in,in+2,&p);} diff --git a/test/evaluation/rfc0029/span-outputs/readonly.c b/test/evaluation/rfc0029/span-outputs/readonly.c new file mode 100644 index 00000000..dba9fcc2 --- /dev/null +++ b/test/evaluation/rfc0029/span-outputs/readonly.c @@ -0,0 +1,2 @@ +int decode(const unsigned char *,const unsigned char *,unsigned char **); +int main(void){const unsigned char in[2]={1,2},a[2]={0,0};unsigned char *p=(unsigned char*)a;return decode(in,in+2,&p);} diff --git a/test/evaluation/rfc0029/state-capacity-bad.c b/test/evaluation/rfc0029/state-capacity-bad.c new file mode 100644 index 00000000..6ed96d57 --- /dev/null +++ b/test/evaluation/rfc0029/state-capacity-bad.c @@ -0,0 +1,11 @@ +#include "state.h" +int main(void) { + struct output b = {0}; + b.bytes = malloc(1); + if (!b.bytes) return 0; + b.available = 100; + b.used = 99; + int result = append(&b, 7); + free(b.bytes); + return result; +} diff --git a/test/evaluation/rfc0029/state-good.c b/test/evaluation/rfc0029/state-good.c new file mode 100644 index 00000000..b4d859a4 --- /dev/null +++ b/test/evaluation/rfc0029/state-good.c @@ -0,0 +1,14 @@ +#include "state.h" +int client(unsigned n) { + if (n > 1000000) return 0; + struct output b = {0}; + b.depth = 3; + b.generation = 42; + for (unsigned i = 0; i < n; ++i) { + if (append(&b, 7)) { free(b.bytes); return 0; } + } + int result = last(&b); + free(b.bytes); + return result; +} +int main(void) { return client(19) != 7; } diff --git a/test/evaluation/rfc0029/state-stale-bad.c b/test/evaluation/rfc0029/state-stale-bad.c new file mode 100644 index 00000000..e206ef00 --- /dev/null +++ b/test/evaluation/rfc0029/state-stale-bad.c @@ -0,0 +1,10 @@ +#include "state.h" +int main(void) { + struct output b = {0}; + if (append(&b, 7)) return 0; + unsigned char *saved = b.bytes; + if (reserve(&b, 128)) { free(b.bytes); return 0; } + int result = *saved; + free(b.bytes); + return result; +} diff --git a/test/evaluation/rfc0029/state-tail-bad.c b/test/evaluation/rfc0029/state-tail-bad.c new file mode 100644 index 00000000..d37f7103 --- /dev/null +++ b/test/evaluation/rfc0029/state-tail-bad.c @@ -0,0 +1,9 @@ +#include "state.h" +int main(void) { + struct output b = {0}; + if (reserve(&b, 8)) return 0; + b.used = 8; + int result = last(&b); + free(b.bytes); + return result; +} diff --git a/test/evaluation/rfc0029/state.h b/test/evaluation/rfc0029/state.h new file mode 100644 index 00000000..911341a2 --- /dev/null +++ b/test/evaluation/rfc0029/state.h @@ -0,0 +1,31 @@ +/* RFC 0029: frozen field-role and helper-composition population. */ +#include +#include +struct output { + unsigned char *bytes; + size_t depth; + size_t used; + unsigned mode; + size_t available; + size_t generation; +}; +static int reserve(struct output *b, size_t wanted) { + if (wanted <= b->available) return 0; + unsigned char *p = realloc(b->bytes, wanted); + if (!p) return -1; + b->bytes = p; + b->available = wanted; + return 0; +} +static int append(struct output *b, unsigned char value) { + if (b->used == b->available) { + if (b->available > SIZE_MAX - 8) return -1; + if (reserve(b, b->available + 8)) return -1; + } + b->bytes[b->used] = value; + ++b->used; + return 0; +} +static unsigned char last(struct output *b) { + return b->used ? b->bytes[b->used - 1] : 0; +} diff --git a/test/evaluation/rfc0029/string-length-copies/PROVENANCE.md b/test/evaluation/rfc0029/string-length-copies/PROVENANCE.md new file mode 100644 index 00000000..6d689110 --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/PROVENANCE.md @@ -0,0 +1,5 @@ +RFC 0029 first-zero string-length composition regression, frozen before candidate90. +The unchanged upstream duplication helper computes strlen(input)+1, allocates +through a callback, then copies through the zero. Generic equivalents include +direct and extracted allocation, overread, stale storage, missing zero and an +uninitialized prefix. Baseline: immutable candidate89d-bin. diff --git a/test/evaluation/rfc0029/string-length-copies/api.h b/test/evaluation/rfc0029/string-length-copies/api.h new file mode 100644 index 00000000..c6074b35 --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/api.h @@ -0,0 +1,4 @@ +#include +#include +char *duplicate(const char *); +struct hooks {void *(*allocate)(size_t);}; diff --git a/test/evaluation/rfc0029/string-length-copies/callback.c b/test/evaluation/rfc0029/string-length-copies/callback.c new file mode 100644 index 00000000..24bcd3f1 --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/callback.c @@ -0,0 +1,3 @@ +#include "api.h" +static char *copy(const char *s,struct hooks *h){if(!s)return 0;size_t n=strlen(s)+1;char *p=h->allocate(n);if(!p)return 0; memcpy(p,s,n);return p;} +char *duplicate(const char *s){struct hooks h={malloc};return copy(s,&h);} diff --git a/test/evaluation/rfc0029/string-length-copies/client.c b/test/evaluation/rfc0029/string-length-copies/client.c new file mode 100644 index 00000000..73555d43 --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const char input[]="hi";char *p=duplicate(input);if(p)free(p);return 0;} diff --git a/test/evaluation/rfc0029/string-length-copies/direct.c b/test/evaluation/rfc0029/string-length-copies/direct.c new file mode 100644 index 00000000..f830377f --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/direct.c @@ -0,0 +1,2 @@ +#include "api.h" +char *duplicate(const char *s){if(!s)return 0;size_t n=strlen(s)+1;char *p=malloc(n);if(!p)return 0; memcpy(p,s,n);return p;} diff --git a/test/evaluation/rfc0029/string-length-copies/forward.c b/test/evaluation/rfc0029/string-length-copies/forward.c new file mode 100644 index 00000000..bc17371f --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/forward.c @@ -0,0 +1,3 @@ +#include "api.h" +static char *copy(const char *s){if(!s)return 0;size_t n=strlen(s)+1;char *p=malloc(n);if(!p)return 0; memcpy(p,s,n);return p;} +char *duplicate(const char *s){return copy(s);} diff --git a/test/evaluation/rfc0029/string-length-copies/frozen-sha256.json b/test/evaluation/rfc0029/string-length-copies/frozen-sha256.json new file mode 100644 index 00000000..e3e8ff1b --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/frozen-sha256.json @@ -0,0 +1,13 @@ +{ + "PROVENANCE.md": "0742c6b43174392ec46cc77c61f0f26b2c0c2f61e1c3d328120143f0a8d8e9e4", + "api.h": "2aac672f8b8e2c1a7783751f2c89f21eccaa36ebfb7a99bf65808ce23be9145b", + "callback.c": "cb9b63b2c4785bd449fba69eed868c43cb1860f68f75fd3d249693fc782c9547", + "client.c": "89a1d150ffe06b5a33f72fed6cbade39ecc0e92cf3c7a1cc9b194a9ce04a3177", + "direct.c": "585ee6eee35983131c991cffdc409d5dfd44e741cde4cbe6c963d1a76d79bcf4", + "forward.c": "7237811f797645891f197823b61e684fe50033bf3ee36a4ddc5971e3709b8f5a", + "manifest.json": "9794c3ba3da055b7e1c49637ce329e964e05aaf8f52dc74eae0ba01a269a7321", + "missing.c": "a022ec92089aee0dfa78e528c216a6305831e437adde2b61e0717762358257b6", + "overread.c": "5ca73cc2ff91be494ddd3e65ebeb164d9c9db689812041597776301853578a59", + "stale.c": "c875d5333f9f925980bbea713f078e6100927e4db6e59f73c05b2f42d99da928", + "tail.c": "f16f41e3451b3c4a660214663d885d0bb78c2407744263fa6391aab3e53cfa41" +} diff --git a/test/evaluation/rfc0029/string-length-copies/manifest.json b/test/evaluation/rfc0029/string-length-copies/manifest.json new file mode 100644 index 00000000..bdfa24c9 --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/manifest.json @@ -0,0 +1,117 @@ +{ + "version": 1, + "cases": [ + { + "name": "direct", + "sources": [ + "client.c", + "direct.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "callback", + "sources": [ + "client.c", + "callback.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "forward", + "sources": [ + "client.c", + "forward.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "overread", + "sources": [ + "client.c", + "overread.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ|terminat|interval|live|release|allocation" + }, + { + "name": "stale", + "sources": [ + "client.c", + "stale.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ|terminat|interval|live|release|allocation" + }, + { + "name": "missing", + "sources": [ + "missing.c", + "direct.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ|terminat|interval|live|release|allocation" + }, + { + "name": "tail", + "sources": [ + "tail.c", + "direct.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ|terminat|interval|live|release|allocation" + } + ] +} diff --git a/test/evaluation/rfc0029/string-length-copies/missing.c b/test/evaluation/rfc0029/string-length-copies/missing.c new file mode 100644 index 00000000..f60c7745 --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/missing.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){const char input[]={104,105};char *p=duplicate(input);if(p)free(p);return 0;} diff --git a/test/evaluation/rfc0029/string-length-copies/overread.c b/test/evaluation/rfc0029/string-length-copies/overread.c new file mode 100644 index 00000000..570127a9 --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/overread.c @@ -0,0 +1,2 @@ +#include "api.h" +char *duplicate(const char *s){if(!s)return 0;size_t n=strlen(s)+2;char *p=malloc(n);if(!p)return 0; memcpy(p,s,n);return p;} diff --git a/test/evaluation/rfc0029/string-length-copies/stale.c b/test/evaluation/rfc0029/string-length-copies/stale.c new file mode 100644 index 00000000..0189f704 --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/stale.c @@ -0,0 +1,2 @@ +#include "api.h" +char *duplicate(const char *s){if(!s)return 0;size_t n=strlen(s)+1;char *p=malloc(n);if(!p)return 0; free((void*)s);memcpy(p,s,n);return p;} diff --git a/test/evaluation/rfc0029/string-length-copies/tail.c b/test/evaluation/rfc0029/string-length-copies/tail.c new file mode 100644 index 00000000..610b6660 --- /dev/null +++ b/test/evaluation/rfc0029/string-length-copies/tail.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){char input[3];input[0]=104;input[2]=0;char *p=duplicate(input);if(p)free(p);return 0;} diff --git a/test/evaluation/rfc0029/temporary-release-frames/PROVENANCE.md b/test/evaluation/rfc0029/temporary-release-frames/PROVENANCE.md new file mode 100644 index 00000000..b0569f31 --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/PROVENANCE.md @@ -0,0 +1 @@ +Frozen against candidate86a before a modeled release of a proved local temporary preserves independently live entry-only forest facts. Positives cover guarded, nullable and inferred helper allocations. Negative cases release an interior pointer, release twice, lose an allocation, or release a payload still attached to its parent. diff --git a/test/evaluation/rfc0029/temporary-release-frames/api.h b/test/evaluation/rfc0029/temporary-release-frames/api.h new file mode 100644 index 00000000..aba4c134 --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/api.h @@ -0,0 +1,5 @@ +#include +struct node {struct node *next,*child;char *text;unsigned value;}; +unsigned inspect(struct node *); +unsigned read_tree(const struct node *); +void drop(struct node *); diff --git a/test/evaluation/rfc0029/temporary-release-frames/attached.c b/test/evaluation/rfc0029/temporary-release-frames/attached.c new file mode 100644 index 00000000..b35dd76a --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/attached.c @@ -0,0 +1,3 @@ +#include "api.h" +static char *make(void){return malloc(4);} +unsigned inspect(struct node *n){char *p=malloc(4);if(!p)return read_tree(n);n->text=p;free(p);return read_tree(n);} diff --git a/test/evaluation/rfc0029/temporary-release-frames/client.c b/test/evaluation/rfc0029/temporary-release-frames/client.c new file mode 100644 index 00000000..1d9bba3c --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){struct node *n=calloc(1,sizeof *n);if(!n)return 0;n->value=3;unsigned r=inspect(n);drop(n);return r==3?0:1;} diff --git a/test/evaluation/rfc0029/temporary-release-frames/frozen-sha256.json b/test/evaluation/rfc0029/temporary-release-frames/frozen-sha256.json new file mode 100644 index 00000000..8e4552c5 --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/frozen-sha256.json @@ -0,0 +1,14 @@ +{ + "PROVENANCE.md": "ec4bd684baeced3964d2536244ee5ed2f1c38e347784b3800f1c800579f31a79", + "api.h": "de924ac226f017f5f4e8a7bbe4c47912d7c9774d35c57d08a76385e12bb1ad96", + "attached.c": "09c71e35a5defa63f39146b2682e5af93575365f85b1139f10de2411e269b26e", + "client.c": "7887789afdd449b95c6997431a0897f8187dee8425d2da9315e6852806f6625f", + "guarded.c": "fb7e5e9f305c910893f05560d023a2185a42c88916b7be7ab4f49de9bff3473d", + "helper.c": "e0e08af5fb8c253b610a87ac81519e609db77ae54049d9b60f3b8c4af8d7f871", + "interior.c": "2926cc0406b8a43ed6255ecd0b2d3b69939a8235fc25cfed1b6794eb71e78615", + "lost.c": "e6b33c469edad94980c4b369884ead150e0123d1f145adb044c46e061a933f86", + "manifest.json": "21b23d45446814061216bb4b6aaeb16d2afe4a74a30d6bfcd0ea0b5909d7d1ab", + "nullable.c": "b2b5f259e1a7aec353c93e837942dc0f1fe736358247e08b2f16b5f33de6e2a3", + "reader.c": "5cf5371331839a1cd9a5bbb72dcf79aa5aa6b72fd0779473c327e7a132666912", + "twice.c": "45625503eab2c043a9d788b41985b79aa102f71baa1b347433dac7100448d488" +} diff --git a/test/evaluation/rfc0029/temporary-release-frames/guarded.c b/test/evaluation/rfc0029/temporary-release-frames/guarded.c new file mode 100644 index 00000000..11fa2b1b --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/guarded.c @@ -0,0 +1,3 @@ +#include "api.h" +static char *make(void){return malloc(4);} +unsigned inspect(struct node *n){char *p=malloc(4);if(!p)return read_tree(n);free(p);return read_tree(n);} diff --git a/test/evaluation/rfc0029/temporary-release-frames/helper.c b/test/evaluation/rfc0029/temporary-release-frames/helper.c new file mode 100644 index 00000000..df8c3725 --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/helper.c @@ -0,0 +1,3 @@ +#include "api.h" +static char *make(void){return malloc(4);} +unsigned inspect(struct node *n){char *p=make();if(!p)return read_tree(n);free(p);return read_tree(n);} diff --git a/test/evaluation/rfc0029/temporary-release-frames/interior.c b/test/evaluation/rfc0029/temporary-release-frames/interior.c new file mode 100644 index 00000000..fddf5824 --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/interior.c @@ -0,0 +1,3 @@ +#include "api.h" +static char *make(void){return malloc(4);} +unsigned inspect(struct node *n){char *p=malloc(4);if(!p)return read_tree(n);free(p+1);return read_tree(n);} diff --git a/test/evaluation/rfc0029/temporary-release-frames/lost.c b/test/evaluation/rfc0029/temporary-release-frames/lost.c new file mode 100644 index 00000000..f9447214 --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/lost.c @@ -0,0 +1,3 @@ +#include "api.h" +static char *make(void){return malloc(4);} +unsigned inspect(struct node *n){char *p=malloc(4);if(!p)return read_tree(n);return read_tree(n);} diff --git a/test/evaluation/rfc0029/temporary-release-frames/manifest.json b/test/evaluation/rfc0029/temporary-release-frames/manifest.json new file mode 100644 index 00000000..564c5a65 --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/manifest.json @@ -0,0 +1,124 @@ +{ + "version": 1, + "cases": [ + { + "name": "guarded", + "sources": [ + "client.c", + "guarded.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "nullable", + "sources": [ + "client.c", + "nullable.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "helper", + "sources": [ + "client.c", + "helper.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "interior", + "sources": [ + "client.c", + "interior.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "release|live|allocation|contract|container|footprint|leak" + }, + { + "name": "twice", + "sources": [ + "client.c", + "twice.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "release|live|allocation|contract|container|footprint|leak" + }, + { + "name": "lost", + "sources": [ + "client.c", + "lost.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "release|live|allocation|contract|container|footprint|leak" + }, + { + "name": "attached", + "sources": [ + "client.c", + "attached.c", + "reader.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "release|live|allocation|contract|container|footprint|leak" + } + ] +} diff --git a/test/evaluation/rfc0029/temporary-release-frames/nullable.c b/test/evaluation/rfc0029/temporary-release-frames/nullable.c new file mode 100644 index 00000000..be9c2e1f --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/nullable.c @@ -0,0 +1,3 @@ +#include "api.h" +static char *make(void){return malloc(4);} +unsigned inspect(struct node *n){char *p=malloc(4);free(p);return read_tree(n);} diff --git a/test/evaluation/rfc0029/temporary-release-frames/reader.c b/test/evaluation/rfc0029/temporary-release-frames/reader.c new file mode 100644 index 00000000..2681673c --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/reader.c @@ -0,0 +1,3 @@ +#include "api.h" +unsigned read_tree(const struct node *n){return n?n->value+read_tree(n->child)+read_tree(n->next):0;} +void drop(struct node *n){while(n){struct node *next=n->next;drop(n->child);free(n->text);free(n);n=next;}} diff --git a/test/evaluation/rfc0029/temporary-release-frames/twice.c b/test/evaluation/rfc0029/temporary-release-frames/twice.c new file mode 100644 index 00000000..f4050fbc --- /dev/null +++ b/test/evaluation/rfc0029/temporary-release-frames/twice.c @@ -0,0 +1,3 @@ +#include "api.h" +static char *make(void){return malloc(4);} +unsigned inspect(struct node *n){char *p=malloc(4);if(!p)return read_tree(n);free(p);free(p);return read_tree(n);} diff --git a/test/evaluation/rfc0029/transport/api.h b/test/evaluation/rfc0029/transport/api.h new file mode 100644 index 00000000..dd4b0e36 --- /dev/null +++ b/test/evaluation/rfc0029/transport/api.h @@ -0,0 +1,9 @@ +#ifndef RFC29_API_H +#define RFC29_API_H +#include +struct node { unsigned value; struct node *left, *right; }; +void destroy_even(struct node *p); +void destroy_odd(struct node *p); +void *make(void *(*allocate)(size_t), size_t count); +void dispose(void (*release)(void *), void *p); +#endif diff --git a/test/evaluation/rfc0029/transport/bad.c b/test/evaluation/rfc0029/transport/bad.c new file mode 100644 index 00000000..c6a74861 --- /dev/null +++ b/test/evaluation/rfc0029/transport/bad.c @@ -0,0 +1,4 @@ +#include "api.h" +#include +static void *short_one(size_t count) { (void)count; return malloc(1); } +int main(void) { void *p = make(short_one, 19); free(p); return 0; } diff --git a/test/evaluation/rfc0029/transport/client.c b/test/evaluation/rfc0029/transport/client.c new file mode 100644 index 00000000..cda6a9bc --- /dev/null +++ b/test/evaluation/rfc0029/transport/client.c @@ -0,0 +1,13 @@ +#include "api.h" +#include +int main(void) { + struct node *a = calloc(1, sizeof *a); + if (!a) return 0; + struct node *b = calloc(1, sizeof *b); + if (!b) { free(a); return 0; } + a->right = b; + destroy_even(a); + void *bytes = make(malloc, 19); + dispose(free, bytes); + return 0; +} diff --git a/test/evaluation/rfc0029/transport/frozen-sha256.json b/test/evaluation/rfc0029/transport/frozen-sha256.json new file mode 100644 index 00000000..b6af3919 --- /dev/null +++ b/test/evaluation/rfc0029/transport/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "api.h": "52cf873314e4a54628a92ce5c9b0cbeadb1d580b48623fa074062d0cfdf1a348", + "bad.c": "bd8c18a63cef2f5319f00c08f552099a31091f25b72f659c31330b230475b36e", + "client.c": "18399b5e94bf9f0767f3902bda85caeeff088ca3a57f422a30c88a1e18fc18a6", + "library.c": "e7b6ec01ab52470df3611c7cfde2ab1e0b1291b4c2c17b4e6cac35e7545534b0", + "manifest.json": "746b53269300e1c12c2f60cf18f41765877c1fea4322b493084dc14255f36186", + "provenance.md": "140af548f9cb564a952791930aa69e02a8d40617fb463350566bfd0b8f0c5d1d" +} diff --git a/test/evaluation/rfc0029/transport/library.c b/test/evaluation/rfc0029/transport/library.c new file mode 100644 index 00000000..56ea071d --- /dev/null +++ b/test/evaluation/rfc0029/transport/library.c @@ -0,0 +1,17 @@ +#include "api.h" +#include +void destroy_even(struct node *p) { + if (!p) return; + destroy_odd(p->left); destroy_odd(p->right); free(p); +} +void destroy_odd(struct node *p) { + if (!p) return; + destroy_even(p->left); destroy_even(p->right); free(p); +} +void *make(void *(*allocate)(size_t), size_t count) { + if (!count) return 0; + unsigned char *p = allocate(count); + if (p) p[count - 1] = 7; + return p; +} +void dispose(void (*release)(void *), void *p) { release(p); p = 0; } diff --git a/test/evaluation/rfc0029/transport/manifest.json b/test/evaluation/rfc0029/transport/manifest.json new file mode 100644 index 00000000..33a7887f --- /dev/null +++ b/test/evaluation/rfc0029/transport/manifest.json @@ -0,0 +1,54 @@ +{ + "version": 1, + "cases": [ + { + "name": "composed-client", + "sources": [ + "client.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "generic-interfaces", + "sources": [ + "library.c" + ], + "functions": [ + "destroy_even", + "destroy_odd", + "make", + "dispose" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "undersized-callback", + "sources": [ + "bad.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "reason": "interval|extent|bounds|callback", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/transport/provenance.md b/test/evaluation/rfc0029/transport/provenance.md new file mode 100644 index 00000000..caa46c60 --- /dev/null +++ b/test/evaluation/rfc0029/transport/provenance.md @@ -0,0 +1 @@ +RFC 0029 transport population frozen after primary-source implementation, before first transport analysis. Exercises mutual cleanup and allocator/releaser callbacks across source/object boundaries and checkpoints. Expected outcomes are independent of source selection, compact reporting and cache state. diff --git a/test/evaluation/rfc0029/traversal/cycle.c b/test/evaluation/rfc0029/traversal/cycle.c new file mode 100644 index 00000000..253aa809 --- /dev/null +++ b/test/evaluation/rfc0029/traversal/cycle.c @@ -0,0 +1,15 @@ +#include +struct tree { unsigned value; struct tree *left, *right; }; +unsigned visit_odd(const struct tree *); +unsigned visit_even(const struct tree *p) { + if (!p) return 0; + return p->value + visit_odd(p) + visit_odd(p->right); +} +unsigned visit_odd(const struct tree *p) { + if (!p) return 0; + return p->value + visit_even(p) + visit_even(p->right); +} +int main(void) { + struct tree b = {2, 0, 0}, a = {1, &b, 0}; + return visit_even(&a) != 3; +} diff --git a/test/evaluation/rfc0029/traversal/frozen-sha256.json b/test/evaluation/rfc0029/traversal/frozen-sha256.json new file mode 100644 index 00000000..ee248da3 --- /dev/null +++ b/test/evaluation/rfc0029/traversal/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "interior.c": "20cb2329f2d2935081028eada3ed248c202d3aa2e7491c343c19aaa631ccb2b0", + "manifest.json": "603a3067bf0164b5563235052c091cd4b545ca914f6198ad47101cf6c734cac4", + "mutation.c": "04c00e5604fc78c892417fd14914393d58e5b7fd46a9a5cf83fa982321f56bb3", + "mutual.c": "90507130985f2e11f7808f1e588caf3462310c66606feecb352dba374e89fa28", + "nondecreasing.c": "31a051db3331f948cf1492a2af487fb3991e627edf6aba6e46cbe73eca716074", + "provenance.md": "1661440bda9482086b1f80e95b037c4fc7dbf763db5c86942d9ae533d2575755" +} diff --git a/test/evaluation/rfc0029/traversal/interior.c b/test/evaluation/rfc0029/traversal/interior.c new file mode 100644 index 00000000..80b257d1 --- /dev/null +++ b/test/evaluation/rfc0029/traversal/interior.c @@ -0,0 +1,15 @@ +#include +struct tree { unsigned value; struct tree *left, *right; }; +unsigned visit_odd(const struct tree *); +unsigned visit_even(const struct tree *p) { + if (!p) return 0; + return p->value + visit_odd(p->left) + visit_odd(p->right); +} +unsigned visit_odd(const struct tree *p) { + if (!p) return 0; + return p->value + visit_even(p->left + 1) + visit_even(p->right); +} +int main(void) { + struct tree b = {2, 0, 0}, a = {1, &b, 0}; + return visit_even(&a) != 3; +} diff --git a/test/evaluation/rfc0029/traversal/manifest.json b/test/evaluation/rfc0029/traversal/manifest.json new file mode 100644 index 00000000..a36edda7 --- /dev/null +++ b/test/evaluation/rfc0029/traversal/manifest.json @@ -0,0 +1,72 @@ +{ + "version": 1, + "cases": [ + { + "name": "traversal-mutual", + "sources": [ + "mutual.c" + ], + "functions": [ + "visit_even", + "visit_odd", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "traversal-nondecreasing", + "sources": [ + "nondecreasing.c" + ], + "functions": [ + "visit_even", + "visit_odd", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|progress|callee|extent|contract" + }, + { + "name": "traversal-interior", + "sources": [ + "interior.c" + ], + "functions": [ + "visit_even", + "visit_odd", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|progress|callee|extent|contract" + }, + { + "name": "traversal-mutation", + "sources": [ + "mutation.c" + ], + "functions": [ + "visit_even", + "visit_odd", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "release|freed|live|callee|extent|contract" + } + ] +} diff --git a/test/evaluation/rfc0029/traversal/mutation.c b/test/evaluation/rfc0029/traversal/mutation.c new file mode 100644 index 00000000..b801afb0 --- /dev/null +++ b/test/evaluation/rfc0029/traversal/mutation.c @@ -0,0 +1,15 @@ +#include +struct tree { unsigned value; struct tree *left, *right; }; +unsigned visit_odd(struct tree *); +unsigned visit_even(struct tree *p) { + if (!p) return 0; + return p->value + visit_odd(p->left) + visit_odd(p->right); +} +unsigned visit_odd(struct tree *p) { + if (!p) return 0; + free(p->left); return p->value + visit_even(p->left) + visit_even(p->right); +} +int main(void) { + struct tree b = {2, 0, 0}, a = {1, &b, 0}; + return visit_even(&a) != 3; +} diff --git a/test/evaluation/rfc0029/traversal/mutual.c b/test/evaluation/rfc0029/traversal/mutual.c new file mode 100644 index 00000000..7b35686d --- /dev/null +++ b/test/evaluation/rfc0029/traversal/mutual.c @@ -0,0 +1,15 @@ +#include +struct tree { unsigned value; struct tree *left, *right; }; +unsigned visit_odd(const struct tree *); +unsigned visit_even(const struct tree *p) { + if (!p) return 0; + return p->value + visit_odd(p->left) + visit_odd(p->right); +} +unsigned visit_odd(const struct tree *p) { + if (!p) return 0; + return p->value + visit_even(p->left) + visit_even(p->right); +} +int main(void) { + struct tree b = {2, 0, 0}, a = {1, &b, 0}; + return visit_even(&a) != 3; +} diff --git a/test/evaluation/rfc0029/traversal/nondecreasing.c b/test/evaluation/rfc0029/traversal/nondecreasing.c new file mode 100644 index 00000000..719fe985 --- /dev/null +++ b/test/evaluation/rfc0029/traversal/nondecreasing.c @@ -0,0 +1,15 @@ +#include +struct tree { unsigned value; struct tree *left, *right; }; +unsigned visit_odd(const struct tree *); +unsigned visit_even(const struct tree *p) { + if (!p) return 0; + return p->value + visit_odd(p->left) + visit_odd(p->right); +} +unsigned visit_odd(const struct tree *p) { + if (!p) return 0; + return p->value + visit_even(p) + visit_even(p->right); +} +int main(void) { + struct tree b = {2, 0, 0}, a = {1, &b, 0}; + return visit_even(&a) != 3; +} diff --git a/test/evaluation/rfc0029/traversal/provenance.md b/test/evaluation/rfc0029/traversal/provenance.md new file mode 100644 index 00000000..31e8397d --- /dev/null +++ b/test/evaluation/rfc0029/traversal/provenance.md @@ -0,0 +1 @@ +Frozen before the RFC 0029 recursive traversal implementation. These independent probes exercise read-only finite-tree traversal and changes that invalidate its progress or preservation premises. The baseline observation is retained under `build/rfc29-validation/traversal-probe.*`. The analyzer must check actual writes and exact child pointers; a recursive declaration supplies no proof. diff --git a/test/evaluation/rfc0029/traversal/review.md b/test/evaluation/rfc0029/traversal/review.md new file mode 100644 index 00000000..28178b52 --- /dev/null +++ b/test/evaluation/rfc0029/traversal/review.md @@ -0,0 +1 @@ +The original nondecreasing probe has a strict edge from visit_even to visit_odd, so every cycle still makes progress. It is a valid forwarding case. The original manifest and failed observation remain unchanged. The reviewed manifest accepts it and adds cycle.c, with non-strict edges in both directions. Frozen before the failed-progress diagnostic was run against cycle.c. diff --git a/test/evaluation/rfc0029/traversal/reviewed-manifest.json b/test/evaluation/rfc0029/traversal/reviewed-manifest.json new file mode 100644 index 00000000..f3d843a8 --- /dev/null +++ b/test/evaluation/rfc0029/traversal/reviewed-manifest.json @@ -0,0 +1,87 @@ +{ + "version": 1, + "cases": [ + { + "name": "traversal-mutual", + "sources": [ + "mutual.c" + ], + "functions": [ + "visit_even", + "visit_odd", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "traversal-nondecreasing", + "sources": [ + "nondecreasing.c" + ], + "functions": [ + "visit_even", + "visit_odd", + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "traversal-interior", + "sources": [ + "interior.c" + ], + "functions": [ + "visit_even", + "visit_odd", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive|progress|callee|extent|contract" + }, + { + "name": "traversal-mutation", + "sources": [ + "mutation.c" + ], + "functions": [ + "visit_even", + "visit_odd", + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "release|freed|live|callee|extent|contract" + }, + { + "name": "traversal-cycle", + "sources": [ + "cycle.c" + ], + "functions": [ + "visit_even", + "visit_odd" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "recursive proof cycle has no strict progress" + } + ] +} diff --git a/test/evaluation/rfc0029/traversal/reviewed-sha256.json b/test/evaluation/rfc0029/traversal/reviewed-sha256.json new file mode 100644 index 00000000..422725df --- /dev/null +++ b/test/evaluation/rfc0029/traversal/reviewed-sha256.json @@ -0,0 +1,5 @@ +{ + "cycle.c": "69714a77221f42a7cb9495841c647acd5487c57ca8c421017220ef8ab8d175e2", + "reviewed-manifest.json": "50b1d9bd6607acae06f3c5892664e2af183823265b3c0a5774ce5613e1a49ff1", + "review.md": "66ca52b18bef42e7c36e88a1021a9ed5b7e7b8c9fb0b9720d6e6980773e7e006" +} diff --git a/test/evaluation/rfc0029/upstream-construction/README.md b/test/evaluation/rfc0029/upstream-construction/README.md new file mode 100644 index 00000000..bf7ae74b --- /dev/null +++ b/test/evaluation/rfc0029/upstream-construction/README.md @@ -0,0 +1 @@ +Frozen against candidate88c, before further upstream workflow fixes. Uses unchanged pinned public APIs to construct an object containing a number and an array with an owned string, serialize it, and release both outputs. Every failed constructor or attachment retains explicit cleanup. Unlike the separate parser clients it has no input pointer retained by the upstream error state. A no-failure concrete run compiled with ASan/UBSan passes before freezing; allocation-failure and negative runtime coverage is required separately. Two mutations omit or duplicate the serialized-output release. diff --git a/test/evaluation/rfc0029/upstream-construction/frozen-sha256.json b/test/evaluation/rfc0029/upstream-construction/frozen-sha256.json new file mode 100644 index 00000000..e5c27b71 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-construction/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "README.md": "064f0084032d277ca61346541dfb5fb17bd0790bddacd31184fec63771f7dd39", + "manifest.json": "4891f12a0f19603883cca9da701577c829da5b0a61f7484ac20ced43cbe12532", + "nested-serialize.c": "d9aeb3d53f9c0f8f3ce611034844e8c5e900a91851eeacb75f0feedfd1feca3e", + "output-leak.c": "dd04f646ffd8af417e43427c1b5e1723de9dadce398446d2411b3776b16ad52e", + "output-twice.c": "73efa4bbf7e4cd213c9c7b78aec90abe0743f759798d6c81ae3ccb9d80cf39c8", + "upstream-identity.json": "3aa0f5db2be2097c3f6f12ea1eb63ef004ed6df7b97df59f75ed07f5ba847980" +} diff --git a/test/evaluation/rfc0029/upstream-construction/manifest.json b/test/evaluation/rfc0029/upstream-construction/manifest.json new file mode 100644 index 00000000..090fe485 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-construction/manifest.json @@ -0,0 +1,53 @@ +{ + "version": 1, + "cases": [ + { + "name": "nested-serialize", + "sources": [ + "nested-serialize.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "output-leak", + "sources": [ + "output-leak.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "live|release|freed|footprint|allocation|leak" + }, + { + "name": "output-twice", + "sources": [ + "output-twice.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "live|release|freed|footprint|allocation|leak" + } + ] +} diff --git a/test/evaluation/rfc0029/upstream-construction/nested-serialize.c b/test/evaluation/rfc0029/upstream-construction/nested-serialize.c new file mode 100644 index 00000000..47f015dd --- /dev/null +++ b/test/evaluation/rfc0029/upstream-construction/nested-serialize.c @@ -0,0 +1,20 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +int main(void) { + cJSON_InitHooks(0); + cJSON *root = cJSON_CreateObject(); + if (!root) return 0; + if (!cJSON_AddNumberToObject(root, "n", 1.0)) { + cJSON_Delete(root); return 0; + } + cJSON *array = cJSON_AddArrayToObject(root, "items"); + if (!array) { cJSON_Delete(root); return 0; } + cJSON *item = cJSON_CreateString("hi"); + if (!item) { cJSON_Delete(root); return 0; } + if (!cJSON_AddItemToArray(array, item)) { + cJSON_Delete(item); cJSON_Delete(root); return 0; + } + char *text = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + if (text) cJSON_free(text); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-construction/output-leak.c b/test/evaluation/rfc0029/upstream-construction/output-leak.c new file mode 100644 index 00000000..5598e899 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-construction/output-leak.c @@ -0,0 +1,20 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +int main(void) { + cJSON_InitHooks(0); + cJSON *root = cJSON_CreateObject(); + if (!root) return 0; + if (!cJSON_AddNumberToObject(root, "n", 1.0)) { + cJSON_Delete(root); return 0; + } + cJSON *array = cJSON_AddArrayToObject(root, "items"); + if (!array) { cJSON_Delete(root); return 0; } + cJSON *item = cJSON_CreateString("hi"); + if (!item) { cJSON_Delete(root); return 0; } + if (!cJSON_AddItemToArray(array, item)) { + cJSON_Delete(item); cJSON_Delete(root); return 0; + } + char *text = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + (void)text; + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-construction/output-twice.c b/test/evaluation/rfc0029/upstream-construction/output-twice.c new file mode 100644 index 00000000..535e6219 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-construction/output-twice.c @@ -0,0 +1,20 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +int main(void) { + cJSON_InitHooks(0); + cJSON *root = cJSON_CreateObject(); + if (!root) return 0; + if (!cJSON_AddNumberToObject(root, "n", 1.0)) { + cJSON_Delete(root); return 0; + } + cJSON *array = cJSON_AddArrayToObject(root, "items"); + if (!array) { cJSON_Delete(root); return 0; } + cJSON *item = cJSON_CreateString("hi"); + if (!item) { cJSON_Delete(root); return 0; } + if (!cJSON_AddItemToArray(array, item)) { + cJSON_Delete(item); cJSON_Delete(root); return 0; + } + char *text = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + if (text) { cJSON_free(text); cJSON_free(text); } + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-construction/upstream-identity.json b/test/evaluation/rfc0029/upstream-construction/upstream-identity.json new file mode 100644 index 00000000..cd44c182 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-construction/upstream-identity.json @@ -0,0 +1,11 @@ +{ + "commit": "fb16e5cf358798aabb049655975cde8427101056", + "files": { + "cJSON.h": "25b0145150d500498e4d209cec69c18c42cf818bffcc54690be3b895a2a16dee", + "cJSON_Utils.h": "1050a7cce8ffe352c509e0c1faad505b9b8a09cac3a1c45c544447868e05f3b5", + "cJSON.c": "607e756460fa0de37d20a7a9181f2de29c97bfb7ce5a0e6c2f548243836cd852", + "test.c": "9073e70d626d83f202768c3a486aa5496ac33277b7f265fdba200888fc93941e", + "cJSON_Utils.c": "11786fd89807c52c80aabec0df58aca4286b7360e11bd6eb2b464a1acab3eab1" + }, + "note": "Frozen before RFC 0027 checker changes; lifecycle clients use the full unchanged pinned source." +} diff --git a/test/evaluation/rfc0029/upstream-extended/failed-parse.c b/test/evaluation/rfc0029/upstream-extended/failed-parse.c new file mode 100644 index 00000000..b093f8f3 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-extended/failed-parse.c @@ -0,0 +1,12 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +#include +static void *fail_allocate(size_t size) { (void)size; return 0; } +int main(void) { + cJSON_Hooks hooks = {fail_allocate, free}; + cJSON_InitHooks(&hooks); + const char input[] = "{\"x\":[1,true]}"; + cJSON *value = cJSON_ParseWithLength(input, sizeof input); + if (value) cJSON_Delete(value); + cJSON_InitHooks(0); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-extended/failed-print.c b/test/evaluation/rfc0029/upstream-extended/failed-print.c new file mode 100644 index 00000000..019a8af9 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-extended/failed-print.c @@ -0,0 +1,15 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +#include +static void *fail_allocate(size_t size) { (void)size; return 0; } +int main(void) { + cJSON_InitHooks(0); + cJSON *value = cJSON_CreateObject(); + if (!value) return 0; + cJSON_Hooks hooks = {fail_allocate, free}; + cJSON_InitHooks(&hooks); + char *output = cJSON_PrintUnformatted(value); + if (output) cJSON_free(output); + cJSON_Delete(value); + cJSON_InitHooks(0); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-extended/frozen-sha256.json b/test/evaluation/rfc0029/upstream-extended/frozen-sha256.json new file mode 100644 index 00000000..ccf6cfa2 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-extended/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "failed-parse.c": "cf34d246210bbc54cc6dab9d5ac8a46aa33bb678e92fbd2d32871ceb1b7c298e", + "failed-print.c": "a387b9b87516d2c5fd937edd1690a55ff73615cc5b2fc8387c22a0d53048d1fc", + "malformed-delete.c": "16b7dfa2a74b75af92138d220b49218164f1c3a07744956b74c17960ba0f9028", + "manifest.json": "e1ffc32764e4eefbc33860a260803d938de6d85ba9d244f1e7dd7b2e5861fbc4", + "nested-print.c": "386f74d1fcea08dc52274d6ff4d4a1cbf46ad7bcf1650e363716a6ace9f9dced", + "upstream-identity.json": "3aa0f5db2be2097c3f6f12ea1eb63ef004ed6df7b97df59f75ed07f5ba847980" +} diff --git a/test/evaluation/rfc0029/upstream-extended/malformed-delete.c b/test/evaluation/rfc0029/upstream-extended/malformed-delete.c new file mode 100644 index 00000000..4464da25 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-extended/malformed-delete.c @@ -0,0 +1,8 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +int main(void) { + cJSON_InitHooks(0); + const char input[] = "{\"x\":[1,true,{\"a\":\"unterminated"; + cJSON *value = cJSON_ParseWithLength(input, sizeof input); + if (value) cJSON_Delete(value); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-extended/manifest.json b/test/evaluation/rfc0029/upstream-extended/manifest.json new file mode 100644 index 00000000..2024314d --- /dev/null +++ b/test/evaluation/rfc0029/upstream-extended/manifest.json @@ -0,0 +1,69 @@ +{ + "version": 1, + "cases": [ + { + "name": "nested-print", + "sources": [ + "nested-print.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "malformed-delete", + "sources": [ + "malformed-delete.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "failed-parse", + "sources": [ + "failed-parse.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "failed-print", + "sources": [ + "failed-print.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/upstream-extended/nested-print.c b/test/evaluation/rfc0029/upstream-extended/nested-print.c new file mode 100644 index 00000000..5571c255 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-extended/nested-print.c @@ -0,0 +1,11 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +int main(void) { + cJSON_InitHooks(0); + const char input[] = "{\"x\":[1,true,\"hi\",null,{\"a\":[]}]}"; + cJSON *value = cJSON_ParseWithLength(input, sizeof input); + if (!value) return 0; + char *output = cJSON_PrintUnformatted(value); + cJSON_Delete(value); + if (output) cJSON_free(output); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-extended/upstream-identity.json b/test/evaluation/rfc0029/upstream-extended/upstream-identity.json new file mode 100644 index 00000000..cd44c182 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-extended/upstream-identity.json @@ -0,0 +1,11 @@ +{ + "commit": "fb16e5cf358798aabb049655975cde8427101056", + "files": { + "cJSON.h": "25b0145150d500498e4d209cec69c18c42cf818bffcc54690be3b895a2a16dee", + "cJSON_Utils.h": "1050a7cce8ffe352c509e0c1faad505b9b8a09cac3a1c45c544447868e05f3b5", + "cJSON.c": "607e756460fa0de37d20a7a9181f2de29c97bfb7ce5a0e6c2f548243836cd852", + "test.c": "9073e70d626d83f202768c3a486aa5496ac33277b7f265fdba200888fc93941e", + "cJSON_Utils.c": "11786fd89807c52c80aabec0df58aca4286b7360e11bd6eb2b464a1acab3eab1" + }, + "note": "Frozen before RFC 0027 checker changes; lifecycle clients use the full unchanged pinned source." +} diff --git a/test/evaluation/rfc0029/upstream-lifetime-audit/README.md b/test/evaluation/rfc0029/upstream-lifetime-audit/README.md new file mode 100644 index 00000000..e73e0ca7 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-lifetime-audit/README.md @@ -0,0 +1,19 @@ +# RFC 0029 upstream lifetime audit + +This additional population preserves the discovery made at candidate70a. The +original `upstream-extended/failed-parse.c` and its accepted expectation remain +immutable. That client leaves cJSON's global error pointer referring to an +automatic input after its lifetime ends, violating RFC 0001's stored-borrow rule. +The stack case here must reject for that lifetime obligation. Its separate +static-storage counterpart must complete with no entry requirements and no +annotation or unsafe trust. Upstream cJSON sources are unchanged. + +`oracle.c` retrieves and reads the retained error pointer after the parsing +helper returns. ASan diagnoses stack-use-after-return with automatic storage; +compiling with `-DSTATIC_INPUT` runs clean. This corroborates the escaped borrow, +not arbitrary parser safety. The original client itself does not dereference +the dangling error pointer, so it is not claimed to execute that invalid read. + +Sources use repository-relative includes for reproducible validation; the +original absolute-path development probes and their hashes remain under +`build/rfc29-validation/upstream-lifetime-audit`. diff --git a/test/evaluation/rfc0029/upstream-lifetime-audit/failed-parse-stack.c b/test/evaluation/rfc0029/upstream-lifetime-audit/failed-parse-stack.c new file mode 100644 index 00000000..b093f8f3 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-lifetime-audit/failed-parse-stack.c @@ -0,0 +1,12 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +#include +static void *fail_allocate(size_t size) { (void)size; return 0; } +int main(void) { + cJSON_Hooks hooks = {fail_allocate, free}; + cJSON_InitHooks(&hooks); + const char input[] = "{\"x\":[1,true]}"; + cJSON *value = cJSON_ParseWithLength(input, sizeof input); + if (value) cJSON_Delete(value); + cJSON_InitHooks(0); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-lifetime-audit/failed-parse-static.c b/test/evaluation/rfc0029/upstream-lifetime-audit/failed-parse-static.c new file mode 100644 index 00000000..4eefb041 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-lifetime-audit/failed-parse-static.c @@ -0,0 +1,12 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +#include +static void *fail_allocate(size_t size) { (void)size; return 0; } +int main(void) { + cJSON_Hooks hooks = {fail_allocate, free}; + cJSON_InitHooks(&hooks); + static const char input[] = "{\"x\":[1,true]}"; + cJSON *value = cJSON_ParseWithLength(input, sizeof input); + if (value) cJSON_Delete(value); + cJSON_InitHooks(0); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-lifetime-audit/frozen-sha256.json b/test/evaluation/rfc0029/upstream-lifetime-audit/frozen-sha256.json new file mode 100644 index 00000000..9db18f4f --- /dev/null +++ b/test/evaluation/rfc0029/upstream-lifetime-audit/frozen-sha256.json @@ -0,0 +1,8 @@ +{ + "README.md": "acadb64ecd4f1ce3a855a110ca943ca3f6cf1dcb90fc0a9b7f9fad60e0cc4858", + "failed-parse-stack.c": "cf34d246210bbc54cc6dab9d5ac8a46aa33bb678e92fbd2d32871ceb1b7c298e", + "failed-parse-static.c": "df0112698800b83884504126ea756897a912c85bd137c5a5af8f6567eb53e293", + "manifest.json": "fc31b7005082e8df5f2413c52f308ed74820952b2b540ff074118e041389d26a", + "oracle.c": "0690ccef61960a74abb0718696ec17394214e3ec47fb8698b59c93a39fa68b11", + "upstream-identity.json": "3aa0f5db2be2097c3f6f12ea1eb63ef004ed6df7b97df59f75ed07f5ba847980" +} diff --git a/test/evaluation/rfc0029/upstream-lifetime-audit/manifest.json b/test/evaluation/rfc0029/upstream-lifetime-audit/manifest.json new file mode 100644 index 00000000..1491da89 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-lifetime-audit/manifest.json @@ -0,0 +1,37 @@ +{ + "version": 1, + "cases": [ + { + "name": "failed-parse-stack", + "sources": [ + "failed-parse-stack.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "reason": "may outlive" + }, + { + "name": "failed-parse-static", + "sources": [ + "failed-parse-static.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "annotation", + "unsafe" + ], + "entry_requirements": 0 + } + ] +} diff --git a/test/evaluation/rfc0029/upstream-lifetime-audit/oracle.c b/test/evaluation/rfc0029/upstream-lifetime-audit/oracle.c new file mode 100644 index 00000000..260dfb2c --- /dev/null +++ b/test/evaluation/rfc0029/upstream-lifetime-audit/oracle.c @@ -0,0 +1,19 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +#include +static void *fail_allocate(size_t size) {(void)size;return 0;} +static void parse_temporary(void) { +#ifdef STATIC_INPUT + static const char input[]="{}"; +#else + const char input[]="{}"; +#endif + (void)cJSON_ParseWithLength(input,sizeof input); +} +int main(void) { + cJSON_Hooks hooks={fail_allocate,free}; + cJSON_InitHooks(&hooks); + parse_temporary(); + const char *error=cJSON_GetErrorPtr(); + if(!error) return 2; + return *error=='{'?0:3; +} diff --git a/test/evaluation/rfc0029/upstream-lifetime-audit/upstream-identity.json b/test/evaluation/rfc0029/upstream-lifetime-audit/upstream-identity.json new file mode 100644 index 00000000..cd44c182 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-lifetime-audit/upstream-identity.json @@ -0,0 +1,11 @@ +{ + "commit": "fb16e5cf358798aabb049655975cde8427101056", + "files": { + "cJSON.h": "25b0145150d500498e4d209cec69c18c42cf818bffcc54690be3b895a2a16dee", + "cJSON_Utils.h": "1050a7cce8ffe352c509e0c1faad505b9b8a09cac3a1c45c544447868e05f3b5", + "cJSON.c": "607e756460fa0de37d20a7a9181f2de29c97bfb7ce5a0e6c2f548243836cd852", + "test.c": "9073e70d626d83f202768c3a486aa5496ac33277b7f265fdba200888fc93941e", + "cJSON_Utils.c": "11786fd89807c52c80aabec0df58aca4286b7360e11bd6eb2b464a1acab3eab1" + }, + "note": "Frozen before RFC 0027 checker changes; lifecycle clients use the full unchanged pinned source." +} diff --git a/test/evaluation/rfc0029/upstream-oracle/frozen-sha256.json b/test/evaluation/rfc0029/upstream-oracle/frozen-sha256.json new file mode 100644 index 00000000..4e561300 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-oracle/frozen-sha256.json @@ -0,0 +1,4 @@ +{ + "oracle.c": "5150fc3ec788cc4f49ed0d40bd4cc87b27de7c0cca61f9711c87b7c9f7c8e477", + "upstream-identity.json": "3aa0f5db2be2097c3f6f12ea1eb63ef004ed6df7b97df59f75ed07f5ba847980" +} diff --git a/test/evaluation/rfc0029/upstream-oracle/oracle.c b/test/evaluation/rfc0029/upstream-oracle/oracle.c new file mode 100644 index 00000000..0d4ae631 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-oracle/oracle.c @@ -0,0 +1,93 @@ +/* RFC 0029: finite independent allocation-failure audit of pinned cJSON. */ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +#include +#include +#include + +static void *live[128]; +static size_t attempts, fail_at, released, acquired; + +static void *allocate(size_t size) { + ++attempts; + if (attempts == fail_at) return NULL; + void *result = malloc(size); + if (!result) abort(); + for (size_t i = 0; i < sizeof live / sizeof *live; ++i) { + if (!live[i]) { + live[i] = result; + ++acquired; + return result; + } + } + abort(); +} + +static void release(void *pointer) { + if (!pointer) return; + for (size_t i = 0; i < sizeof live / sizeof *live; ++i) { + if (live[i] == pointer) { + live[i] = NULL; + ++released; + free(pointer); + return; + } + } + /* A repeated release or a pointer outside the tracked allocation set. */ + abort(); +} + +struct example { const char *input; const char *output; }; +static const struct example examples[] = { + {"{}", "{}"}, + {"[]", "[]"}, + {"{\"x\":[1,true,\"hi\",null,{\"a\":[]}]}", + "{\"x\":[1,true,\"hi\",null,{\"a\":[]}]}"}, + {"[1,-2,0.5,false,\"a\\nb\\t\\u0063\"]", + "[1,-2,0.5,false,\"a\\nb\\tc\"]"}, + {"{\"x\":[1,true,{\"a\":\"unterminated", NULL}, + {"[1,2,", NULL}, + {"{\"a\": [}", NULL}, + {"", NULL} +}; + +static size_t workflow(const struct example *example, size_t failure) { + attempts = acquired = released = 0; + fail_at = failure; + cJSON_Hooks hooks = {allocate, release}; + cJSON_InitHooks(&hooks); + cJSON *value = cJSON_ParseWithLength(example->input, + strlen(example->input) + 1); + if (value) { + char *output = cJSON_PrintUnformatted(value); + if (output) { + if (!example->output || strcmp(output, example->output)) abort(); + cJSON_free(output); + } else if (!failure) { + abort(); + } + cJSON_Delete(value); + } else if (!failure && example->output) { + abort(); + } + cJSON_InitHooks(NULL); + if (acquired != released) abort(); + for (size_t i = 0; i < sizeof live / sizeof *live; ++i) + if (live[i]) abort(); + return attempts; +} + +int main(void) { + size_t runs = 0, failures = 0; + for (size_t i = 0; i < sizeof examples / sizeof *examples; ++i) { + const size_t count = workflow(&examples[i], 0); + ++runs; + for (size_t failure = 1; failure <= count + 1; ++failure) { + const size_t observed = workflow(&examples[i], failure); + ++runs; + failures += failure <= observed; + } + } + printf("{\"documents\":%zu,\"runs\":%zu,\"injected_failures\":%zu}\n", + sizeof examples / sizeof *examples, runs, failures); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-oracle/upstream-identity.json b/test/evaluation/rfc0029/upstream-oracle/upstream-identity.json new file mode 100644 index 00000000..cd44c182 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-oracle/upstream-identity.json @@ -0,0 +1,11 @@ +{ + "commit": "fb16e5cf358798aabb049655975cde8427101056", + "files": { + "cJSON.h": "25b0145150d500498e4d209cec69c18c42cf818bffcc54690be3b895a2a16dee", + "cJSON_Utils.h": "1050a7cce8ffe352c509e0c1faad505b9b8a09cac3a1c45c544447868e05f3b5", + "cJSON.c": "607e756460fa0de37d20a7a9181f2de29c97bfb7ce5a0e6c2f548243836cd852", + "test.c": "9073e70d626d83f202768c3a486aa5496ac33277b7f265fdba200888fc93941e", + "cJSON_Utils.c": "11786fd89807c52c80aabec0df58aca4286b7360e11bd6eb2b464a1acab3eab1" + }, + "note": "Frozen before RFC 0027 checker changes; lifecycle clients use the full unchanged pinned source." +} diff --git a/test/evaluation/rfc0029/upstream-static-inputs/README.md b/test/evaluation/rfc0029/upstream-static-inputs/README.md new file mode 100644 index 00000000..20f6f406 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-static-inputs/README.md @@ -0,0 +1 @@ +Frozen after the lifetime audit and before static byte-content inference. These clients retain the exact original nested JSON and malformed JSON inputs, but use immutable static storage so cJSON may retain its error pointer on failure. Original upstream populations and expectations remain unchanged. No cJSON source edits or trusted third-party contracts. Baseline: immutable Debug73l binaries. diff --git a/test/evaluation/rfc0029/upstream-static-inputs/frozen-sha256.json b/test/evaluation/rfc0029/upstream-static-inputs/frozen-sha256.json new file mode 100644 index 00000000..824d3dfc --- /dev/null +++ b/test/evaluation/rfc0029/upstream-static-inputs/frozen-sha256.json @@ -0,0 +1,7 @@ +{ + "README.md": "c652bcfca8d4f2cb073970ecbfa7f5ecd82bfca0ff25297c8b0c01e579e07397", + "malformed-delete.c": "e819c8ca8d43e9609220e82a0d65393ef989bb33746d2ea54fedb51eb369a422", + "manifest.json": "b57ac996c3f55739312f5aba2005251df7332ee5c09eee69b1eb1fb03ff0c610", + "parse-delete.c": "26bd69473a2e85f291587ac843aa3c0c4dc63a074e3c6f93d6a14cf14cc2337a", + "upstream-identity.json": "3aa0f5db2be2097c3f6f12ea1eb63ef004ed6df7b97df59f75ed07f5ba847980" +} diff --git a/test/evaluation/rfc0029/upstream-static-inputs/malformed-delete.c b/test/evaluation/rfc0029/upstream-static-inputs/malformed-delete.c new file mode 100644 index 00000000..5ac40009 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-static-inputs/malformed-delete.c @@ -0,0 +1,8 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +int main(void) { + cJSON_InitHooks(0); + static const char input[] = "{\"x\":[1,true,{\"a\":\"unterminated"; + cJSON *value = cJSON_ParseWithLength(input, sizeof input); + if (value) cJSON_Delete(value); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-static-inputs/manifest.json b/test/evaluation/rfc0029/upstream-static-inputs/manifest.json new file mode 100644 index 00000000..a6f1ffd4 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-static-inputs/manifest.json @@ -0,0 +1,37 @@ +{ + "version": 1, + "cases": [ + { + "name": "parse-delete", + "sources": [ + "parse-delete.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + }, + { + "name": "malformed-delete", + "sources": [ + "malformed-delete.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "entry_requirements": 0, + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/upstream-static-inputs/parse-delete.c b/test/evaluation/rfc0029/upstream-static-inputs/parse-delete.c new file mode 100644 index 00000000..c19cba24 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-static-inputs/parse-delete.c @@ -0,0 +1,9 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +int main(void) { + cJSON_InitHooks(0); + static const char text[] = "{\"x\":[1,true,\"hi\"]}"; + cJSON *value = cJSON_ParseWithLength(text, sizeof text); + if (!value) return 0; + cJSON_Delete(value); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream-static-inputs/upstream-identity.json b/test/evaluation/rfc0029/upstream-static-inputs/upstream-identity.json new file mode 100644 index 00000000..cd44c182 --- /dev/null +++ b/test/evaluation/rfc0029/upstream-static-inputs/upstream-identity.json @@ -0,0 +1,11 @@ +{ + "commit": "fb16e5cf358798aabb049655975cde8427101056", + "files": { + "cJSON.h": "25b0145150d500498e4d209cec69c18c42cf818bffcc54690be3b895a2a16dee", + "cJSON_Utils.h": "1050a7cce8ffe352c509e0c1faad505b9b8a09cac3a1c45c544447868e05f3b5", + "cJSON.c": "607e756460fa0de37d20a7a9181f2de29c97bfb7ce5a0e6c2f548243836cd852", + "test.c": "9073e70d626d83f202768c3a486aa5496ac33277b7f265fdba200888fc93941e", + "cJSON_Utils.c": "11786fd89807c52c80aabec0df58aca4286b7360e11bd6eb2b464a1acab3eab1" + }, + "note": "Frozen before RFC 0027 checker changes; lifecycle clients use the full unchanged pinned source." +} diff --git a/test/evaluation/rfc0029/upstream/frozen-sha256.json b/test/evaluation/rfc0029/upstream/frozen-sha256.json new file mode 100644 index 00000000..544a0a20 --- /dev/null +++ b/test/evaluation/rfc0029/upstream/frozen-sha256.json @@ -0,0 +1,7 @@ +{ + "parse-delete.c": "4c8a12cb39dec01f3fba0857b0f00e6ea057396ead670a620b20237050046e93", + "parse-double-bad.c": "ea45f4379ac922401ae30b53fd105250532a04a3b03e2985a520abbe20232208", + "print-delete.c": "a65e5408a0c7b542e317654ee085c9f7300d710edf29430ff0592bcecca4d162", + "manifest.json": "cf33f89e9ec3efedf6708a3bf318df0f2832e8764d173f9146eae3f66a746ff5", + "upstream-identity.json": "3aa0f5db2be2097c3f6f12ea1eb63ef004ed6df7b97df59f75ed07f5ba847980" +} diff --git a/test/evaluation/rfc0029/upstream/manifest.json b/test/evaluation/rfc0029/upstream/manifest.json new file mode 100644 index 00000000..9eb72739 --- /dev/null +++ b/test/evaluation/rfc0029/upstream/manifest.json @@ -0,0 +1,53 @@ +{ + "version": 1, + "cases": [ + { + "name": "parse-delete", + "sources": [ + "parse-delete.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "print-delete", + "sources": [ + "print-delete.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "parse-double-bad", + "sources": [ + "parse-double-bad.c", + "../../../../build/corpus/cJSON-program/cJSON.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "freed|release|live" + } + ] +} diff --git a/test/evaluation/rfc0029/upstream/parse-delete.c b/test/evaluation/rfc0029/upstream/parse-delete.c new file mode 100644 index 00000000..96581460 --- /dev/null +++ b/test/evaluation/rfc0029/upstream/parse-delete.c @@ -0,0 +1,9 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +int main(void) { + cJSON_InitHooks(0); + const char text[] = "{\"x\":[1,true,\"hi\"]}"; + cJSON *value = cJSON_ParseWithLength(text, sizeof text); + if (!value) return 0; + cJSON_Delete(value); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream/parse-double-bad.c b/test/evaluation/rfc0029/upstream/parse-double-bad.c new file mode 100644 index 00000000..ac0e2355 --- /dev/null +++ b/test/evaluation/rfc0029/upstream/parse-double-bad.c @@ -0,0 +1,10 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +int main(void) { + cJSON_InitHooks(0); + const char text[] = "{}"; + cJSON *value = cJSON_ParseWithLength(text, sizeof text); + if (!value) return 0; + cJSON_Delete(value); + cJSON_Delete(value); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream/print-delete.c b/test/evaluation/rfc0029/upstream/print-delete.c new file mode 100644 index 00000000..f95296fe --- /dev/null +++ b/test/evaluation/rfc0029/upstream/print-delete.c @@ -0,0 +1,10 @@ +#include "../../../../build/corpus/cJSON-program/cJSON.h" +int main(void) { + cJSON_InitHooks(0); + cJSON *value = cJSON_CreateObject(); + if (!value) return 0; + char *text = cJSON_PrintUnformatted(value); + cJSON_Delete(value); + if (text) cJSON_free(text); + return 0; +} diff --git a/test/evaluation/rfc0029/upstream/upstream-identity.json b/test/evaluation/rfc0029/upstream/upstream-identity.json new file mode 100644 index 00000000..cd44c182 --- /dev/null +++ b/test/evaluation/rfc0029/upstream/upstream-identity.json @@ -0,0 +1,11 @@ +{ + "commit": "fb16e5cf358798aabb049655975cde8427101056", + "files": { + "cJSON.h": "25b0145150d500498e4d209cec69c18c42cf818bffcc54690be3b895a2a16dee", + "cJSON_Utils.h": "1050a7cce8ffe352c509e0c1faad505b9b8a09cac3a1c45c544447868e05f3b5", + "cJSON.c": "607e756460fa0de37d20a7a9181f2de29c97bfb7ce5a0e6c2f548243836cd852", + "test.c": "9073e70d626d83f202768c3a486aa5496ac33277b7f265fdba200888fc93941e", + "cJSON_Utils.c": "11786fd89807c52c80aabec0df58aca4286b7360e11bd6eb2b464a1acab3eab1" + }, + "note": "Frozen before RFC 0027 checker changes; lifecycle clients use the full unchanged pinned source." +} diff --git a/test/evaluation/rfc0029/writer-forwarding/audit.md b/test/evaluation/rfc0029/writer-forwarding/audit.md new file mode 100644 index 00000000..7d6dd1a5 --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/audit.md @@ -0,0 +1 @@ +The initial inventory added a forwarding helper but accidentally left main calling render directly. The reviewed sources exercise forward, with the same positive and negative byte writes. Both inventories and baseline outcomes are retained; the reviewed inventory was frozen before the descriptor-discovery change. diff --git a/test/evaluation/rfc0029/writer-forwarding/frozen-sha256.json b/test/evaluation/rfc0029/writer-forwarding/frozen-sha256.json new file mode 100644 index 00000000..b6770182 --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/frozen-sha256.json @@ -0,0 +1,6 @@ +{ + "manifest.json": "2fd66cb9a8a97727cd467372b51b63b947770dde37088995ad5687d7ee036c1f", + "skip.c": "5d0f14d1233fe5e7decc8aa1c635ccd3f0accc0b3f3ef84e318a59685fa2d068", + "write.c": "21f21f5d0044484a76e80d0188e88b92e97f2145e3d590dea1ee952865b49f49", + "wrong-byte.c": "3f13d3d373cdeda74b619f16564848173d20cfbc47e813376f2df7debcab7849" +} diff --git a/test/evaluation/rfc0029/writer-forwarding/manifest.json b/test/evaluation/rfc0029/writer-forwarding/manifest.json new file mode 100644 index 00000000..661e9eed --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "write", + "sources": [ + "write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "skip", + "sources": [ + "skip.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|prefix|terminated" + }, + { + "name": "wrong-byte", + "sources": [ + "wrong-byte.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|prefix|terminated" + } + ] +} diff --git a/test/evaluation/rfc0029/writer-forwarding/reviewed-manifest.json b/test/evaluation/rfc0029/writer-forwarding/reviewed-manifest.json new file mode 100644 index 00000000..755b815d --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/reviewed-manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "write", + "sources": [ + "reviewed-write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "skip", + "sources": [ + "reviewed-skip.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|prefix|terminated" + }, + { + "name": "wrong-byte", + "sources": [ + "reviewed-wrong-byte.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|prefix|terminated" + } + ] +} diff --git a/test/evaluation/rfc0029/writer-forwarding/reviewed-sha256.json b/test/evaluation/rfc0029/writer-forwarding/reviewed-sha256.json new file mode 100644 index 00000000..24280858 --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/reviewed-sha256.json @@ -0,0 +1,7 @@ +{ + "audit.md": "888cb19b97cd50e3b34ea314fcf09fb0626d8a2326cc932fe6a7bb0b1cce1ba9", + "reviewed-manifest.json": "6dc83dcd822a0009adba2cbfe7812441c53075486ef44afda88d0fa62d8f6e68", + "reviewed-skip.c": "753535cc036dd184e6ee7f79ec44b505d333bdbb62428a723b7e133dfde3c05b", + "reviewed-write.c": "35a586ab51b5b99fdfa1185c8380f802809952e9cdea67551b3fc31aab7cb612", + "reviewed-wrong-byte.c": "dabc0b5dfbc7d7dd293fa64151d78c7e4227448c7ba49bd313432255a614e8d9" +} diff --git a/test/evaluation/rfc0029/writer-forwarding/reviewed-skip.c b/test/evaluation/rfc0029/writer-forwarding/reviewed-skip.c new file mode 100644 index 00000000..0edeafa6 --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/reviewed-skip.c @@ -0,0 +1,36 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + return 0; +} +static int render(struct writer *w) { + if (w->format) return render(w); + unsigned char *out = reserve(w, 2); + if (!out) return 0; + ++out; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int forward(struct writer *w) { return render(w); } +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = forward(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-forwarding/reviewed-write.c b/test/evaluation/rfc0029/writer-forwarding/reviewed-write.c new file mode 100644 index 00000000..15e7658d --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/reviewed-write.c @@ -0,0 +1,36 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + return 0; +} +static int render(struct writer *w) { + if (w->format) return render(w); + unsigned char *out = reserve(w, 2); + if (!out) return 0; + *out++ = '{'; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int forward(struct writer *w) { return render(w); } +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = forward(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-forwarding/reviewed-wrong-byte.c b/test/evaluation/rfc0029/writer-forwarding/reviewed-wrong-byte.c new file mode 100644 index 00000000..5665e9f8 --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/reviewed-wrong-byte.c @@ -0,0 +1,36 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + return 0; +} +static int render(struct writer *w) { + if (w->format) return render(w); + unsigned char *out = reserve(w, 2); + if (!out) return 0; + out[1] = '{'; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int forward(struct writer *w) { return render(w); } +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = forward(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-forwarding/skip.c b/test/evaluation/rfc0029/writer-forwarding/skip.c new file mode 100644 index 00000000..3b532482 --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/skip.c @@ -0,0 +1,36 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + return 0; +} +static int render(struct writer *w) { + if (w->format) return render(w); + unsigned char *out = reserve(w, 2); + if (!out) return 0; + ++out; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int forward(struct writer *w) { return render(w); } +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-forwarding/write.c b/test/evaluation/rfc0029/writer-forwarding/write.c new file mode 100644 index 00000000..ea43e44b --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/write.c @@ -0,0 +1,36 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + return 0; +} +static int render(struct writer *w) { + if (w->format) return render(w); + unsigned char *out = reserve(w, 2); + if (!out) return 0; + *out++ = '{'; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int forward(struct writer *w) { return render(w); } +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-forwarding/wrong-byte.c b/test/evaluation/rfc0029/writer-forwarding/wrong-byte.c new file mode 100644 index 00000000..b4d6863c --- /dev/null +++ b/test/evaluation/rfc0029/writer-forwarding/wrong-byte.c @@ -0,0 +1,36 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + return 0; +} +static int render(struct writer *w) { + if (w->format) return render(w); + unsigned char *out = reserve(w, 2); + if (!out) return 0; + out[1] = '{'; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int forward(struct writer *w) { return render(w); } +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-position-bounds/frozen-sha256.json b/test/evaluation/rfc0029/writer-position-bounds/frozen-sha256.json new file mode 100644 index 00000000..bd0adb41 --- /dev/null +++ b/test/evaluation/rfc0029/writer-position-bounds/frozen-sha256.json @@ -0,0 +1,6 @@ +{ + "manifest.json": "2fd66cb9a8a97727cd467372b51b63b947770dde37088995ad5687d7ee036c1f", + "skip.c": "00d33aa1867d0cc09780259d7d2dfaaa21f20de227bcce28aff5548bb6840386", + "write.c": "7d07659e47798770cd34488b55150080760c69a0ccb5124504a7a1585b66913e", + "wrong-byte.c": "a6c634ee6c728be67a332c78c1dcef24785758687c4c6c71a527ddcc60ea257e" +} diff --git a/test/evaluation/rfc0029/writer-position-bounds/manifest.json b/test/evaluation/rfc0029/writer-position-bounds/manifest.json new file mode 100644 index 00000000..661e9eed --- /dev/null +++ b/test/evaluation/rfc0029/writer-position-bounds/manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "write", + "sources": [ + "write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "skip", + "sources": [ + "skip.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|prefix|terminated" + }, + { + "name": "wrong-byte", + "sources": [ + "wrong-byte.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|prefix|terminated" + } + ] +} diff --git a/test/evaluation/rfc0029/writer-position-bounds/skip.c b/test/evaluation/rfc0029/writer-position-bounds/skip.c new file mode 100644 index 00000000..9fe2106a --- /dev/null +++ b/test/evaluation/rfc0029/writer-position-bounds/skip.c @@ -0,0 +1,38 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + unsigned char *replacement = realloc(w->data, needed); + if (!replacement) return 0; + w->data = replacement; + w->size = needed; + return replacement + w->used; +} +static int render(struct writer *w) { + unsigned char *out = reserve(w, 2); + if (!out) return 0; + ++out; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-position-bounds/write.c b/test/evaluation/rfc0029/writer-position-bounds/write.c new file mode 100644 index 00000000..5da5b490 --- /dev/null +++ b/test/evaluation/rfc0029/writer-position-bounds/write.c @@ -0,0 +1,38 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + unsigned char *replacement = realloc(w->data, needed); + if (!replacement) return 0; + w->data = replacement; + w->size = needed; + return replacement + w->used; +} +static int render(struct writer *w) { + unsigned char *out = reserve(w, 2); + if (!out) return 0; + *out++ = '{'; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-position-bounds/wrong-byte.c b/test/evaluation/rfc0029/writer-position-bounds/wrong-byte.c new file mode 100644 index 00000000..92640d26 --- /dev/null +++ b/test/evaluation/rfc0029/writer-position-bounds/wrong-byte.c @@ -0,0 +1,38 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + unsigned char *replacement = realloc(w->data, needed); + if (!replacement) return 0; + w->data = replacement; + w->size = needed; + return replacement + w->used; +} +static int render(struct writer *w) { + unsigned char *out = reserve(w, 2); + if (!out) return 0; + out[1] = '{'; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-return-aliases/frozen-sha256.json b/test/evaluation/rfc0029/writer-return-aliases/frozen-sha256.json new file mode 100644 index 00000000..38929e64 --- /dev/null +++ b/test/evaluation/rfc0029/writer-return-aliases/frozen-sha256.json @@ -0,0 +1,6 @@ +{ + "manifest.json": "2fd66cb9a8a97727cd467372b51b63b947770dde37088995ad5687d7ee036c1f", + "skip.c": "05e4db280efe15317dc75c2e02680f8f292e61774339323995384eba785f893c", + "write.c": "a35040cf8faaa6e9909e3a860f645023b15f9ebef988487034ca32f471e77e9e", + "wrong-byte.c": "07af5482dea1ead866860094d9cb7b4e73d4888e35662426244670406a24457b" +} diff --git a/test/evaluation/rfc0029/writer-return-aliases/manifest.json b/test/evaluation/rfc0029/writer-return-aliases/manifest.json new file mode 100644 index 00000000..661e9eed --- /dev/null +++ b/test/evaluation/rfc0029/writer-return-aliases/manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "write", + "sources": [ + "write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "skip", + "sources": [ + "skip.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|prefix|terminated" + }, + { + "name": "wrong-byte", + "sources": [ + "wrong-byte.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|prefix|terminated" + } + ] +} diff --git a/test/evaluation/rfc0029/writer-return-aliases/skip.c b/test/evaluation/rfc0029/writer-return-aliases/skip.c new file mode 100644 index 00000000..da98f7e3 --- /dev/null +++ b/test/evaluation/rfc0029/writer-return-aliases/skip.c @@ -0,0 +1,34 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + return 0; +} +static int render(struct writer *w) { + unsigned char *out = reserve(w, 2); + if (!out) return 0; + ++out; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-return-aliases/write.c b/test/evaluation/rfc0029/writer-return-aliases/write.c new file mode 100644 index 00000000..64107715 --- /dev/null +++ b/test/evaluation/rfc0029/writer-return-aliases/write.c @@ -0,0 +1,34 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + return 0; +} +static int render(struct writer *w) { + unsigned char *out = reserve(w, 2); + if (!out) return 0; + *out++ = '{'; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-return-aliases/wrong-byte.c b/test/evaluation/rfc0029/writer-return-aliases/wrong-byte.c new file mode 100644 index 00000000..ccbc654e --- /dev/null +++ b/test/evaluation/rfc0029/writer-return-aliases/wrong-byte.c @@ -0,0 +1,34 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + return 0; +} +static int render(struct writer *w) { + unsigned char *out = reserve(w, 2); + if (!out) return 0; + out[1] = '{'; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-return-expressions/frozen-sha256.json b/test/evaluation/rfc0029/writer-return-expressions/frozen-sha256.json new file mode 100644 index 00000000..9f856b04 --- /dev/null +++ b/test/evaluation/rfc0029/writer-return-expressions/frozen-sha256.json @@ -0,0 +1,6 @@ +{ + "manifest.json": "2fd66cb9a8a97727cd467372b51b63b947770dde37088995ad5687d7ee036c1f", + "skip.c": "04c12b6e0a1dc921263133291a5ba45ab5e942dbb537e103b0e210be0c25ae10", + "write.c": "8cdc2866e5b4f3e1fd0d585aab37198f2d3af179dd3f2227e4c5a05d647c05ce", + "wrong-byte.c": "ae1f047ba72744a12ab0efa597fea249591aacafa2dd41f2e1a84dc1a96a3ecf" +} diff --git a/test/evaluation/rfc0029/writer-return-expressions/manifest.json b/test/evaluation/rfc0029/writer-return-expressions/manifest.json new file mode 100644 index 00000000..661e9eed --- /dev/null +++ b/test/evaluation/rfc0029/writer-return-expressions/manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "write", + "sources": [ + "write.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "skip", + "sources": [ + "skip.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|prefix|terminated" + }, + { + "name": "wrong-byte", + "sources": [ + "wrong-byte.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initialized|prefix|terminated" + } + ] +} diff --git a/test/evaluation/rfc0029/writer-return-expressions/skip.c b/test/evaluation/rfc0029/writer-return-expressions/skip.c new file mode 100644 index 00000000..854ae699 --- /dev/null +++ b/test/evaluation/rfc0029/writer-return-expressions/skip.c @@ -0,0 +1,33 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + return needed <= w->size ? w->data + w->used : 0; +} +static int render(struct writer *w) { + unsigned char *out = reserve(w, 2); + if (!out) return 0; + ++out; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-return-expressions/write.c b/test/evaluation/rfc0029/writer-return-expressions/write.c new file mode 100644 index 00000000..cf7ce500 --- /dev/null +++ b/test/evaluation/rfc0029/writer-return-expressions/write.c @@ -0,0 +1,33 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + return needed <= w->size ? w->data + w->used : 0; +} +static int render(struct writer *w) { + unsigned char *out = reserve(w, 2); + if (!out) return 0; + *out++ = '{'; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-return-expressions/wrong-byte.c b/test/evaluation/rfc0029/writer-return-expressions/wrong-byte.c new file mode 100644 index 00000000..5f3ef5fe --- /dev/null +++ b/test/evaluation/rfc0029/writer-return-expressions/wrong-byte.c @@ -0,0 +1,33 @@ +#include +#include +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + return needed <= w->size ? w->data + w->used : 0; +} +static int render(struct writer *w) { + unsigned char *out = reserve(w, 2); + if (!out) return 0; + out[1] = '{'; + ++w->depth; + ++w->used; + out = reserve(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int main(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = render(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} diff --git a/test/evaluation/rfc0029/writer-transport/api.h b/test/evaluation/rfc0029/writer-transport/api.h new file mode 100644 index 00000000..670c39c2 --- /dev/null +++ b/test/evaluation/rfc0029/writer-transport/api.h @@ -0,0 +1,3 @@ +#include +struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; +int emit(const unsigned char *,size_t,struct writer*); diff --git a/test/evaluation/rfc0029/writer-transport/client.c b/test/evaluation/rfc0029/writer-transport/client.c new file mode 100644 index 00000000..2b4e5a96 --- /dev/null +++ b/test/evaluation/rfc0029/writer-transport/client.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,3,&w);if(w.used)return w.data[w.used-1];return 0;} diff --git a/test/evaluation/rfc0029/writer-transport/frozen-sha256.json b/test/evaluation/rfc0029/writer-transport/frozen-sha256.json new file mode 100644 index 00000000..34d9aece --- /dev/null +++ b/test/evaluation/rfc0029/writer-transport/frozen-sha256.json @@ -0,0 +1,7 @@ +{ + "api.h": "3c6c00c95a96687ddf7d846d8803106cd72f1645382f456222ff6b8903e07b38", + "client.c": "0e492a098f355ef48a29a2b4ec06defa3536cdd497f9c71c5b9e69d3e1b521b0", + "library.c": "27605b358c533c4b4a206f7925177a4e8c7ffb8f8600e16157ce4056a0d826d7", + "short.c": "0a7b0e7b5ec29b9ac29f88758144db366f8dc594968e4cbfe807eccb8d08586d", + "uninitialized.c": "40c439508f9485d4f74f285138ace79515ca35df124b716a371029ca44125854" +} diff --git a/test/evaluation/rfc0029/writer-transport/library.c b/test/evaluation/rfc0029/writer-transport/library.c new file mode 100644 index 00000000..8103041c --- /dev/null +++ b/test/evaluation/rfc0029/writer-transport/library.c @@ -0,0 +1,8 @@ +#include "api.h" +int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; +} diff --git a/test/evaluation/rfc0029/writer-transport/manifest.json b/test/evaluation/rfc0029/writer-transport/manifest.json new file mode 100644 index 00000000..b3dd8108 --- /dev/null +++ b/test/evaluation/rfc0029/writer-transport/manifest.json @@ -0,0 +1,67 @@ +{ + "version": 1, + "cases": [ + { + "name": "client", + "sources": [ + "client.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "short", + "sources": [ + "short.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "extent|bounds|interval" + }, + { + "name": "uninitialized", + "sources": [ + "uninitialized.c", + "library.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "initializ" + }, + { + "name": "generic", + "sources": [ + "library.c" + ], + "functions": [ + "emit" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ] + } + ] +} diff --git a/test/evaluation/rfc0029/writer-transport/provenance.md b/test/evaluation/rfc0029/writer-transport/provenance.md new file mode 100644 index 00000000..4978844f --- /dev/null +++ b/test/evaluation/rfc0029/writer-transport/provenance.md @@ -0,0 +1 @@ +Separate transport regression derived after candidate25 writer inference, from the earlier frozen reviewed writer population. Candidate24 Release supplies the retained pre-feature observation. Fields and bodies are unchanged apart from separate compilation. diff --git a/test/evaluation/rfc0029/writer-transport/short.c b/test/evaluation/rfc0029/writer-transport/short.c new file mode 100644 index 00000000..0aef5f91 --- /dev/null +++ b/test/evaluation/rfc0029/writer-transport/short.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){unsigned char input[]={1,2,3},output[3];struct writer w={7,output,0,3};(void)emit(input,4,&w);if(w.used)return w.data[w.used-1];return 0;} diff --git a/test/evaluation/rfc0029/writer-transport/uninitialized.c b/test/evaluation/rfc0029/writer-transport/uninitialized.c new file mode 100644 index 00000000..258b94b6 --- /dev/null +++ b/test/evaluation/rfc0029/writer-transport/uninitialized.c @@ -0,0 +1,2 @@ +#include "api.h" +int main(void){unsigned char input[3],output[3];input[0]=1;struct writer w={7,output,0,3};(void)emit(input,3,&w);if(w.used)return w.data[w.used-1];return 0;} diff --git a/test/evaluation/rfc0029/zero-counters/README.md b/test/evaluation/rfc0029/zero-counters/README.md new file mode 100644 index 00000000..5c8f01f7 --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/README.md @@ -0,0 +1 @@ +RFC0029 zero-byte integer recovery. Frozen before implementation, baseline candidate29b. Only complete, current zero representations authorize zero; partial memset, a later direct/helper write and another record cell preserve their actual obligations. No pointer-null inference is required. diff --git a/test/evaluation/rfc0029/zero-counters/array.c b/test/evaluation/rfc0029/zero-counters/array.c new file mode 100644 index 00000000..29bb6922 --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/array.c @@ -0,0 +1,3 @@ +#include +struct row { char *data; unsigned count; }; +int array(void) { struct row r[2]; int a[1]={7}; memset(r,0,sizeof r); return a[r[1].count]; } diff --git a/test/evaluation/rfc0029/zero-counters/audit.md b/test/evaluation/rfc0029/zero-counters/audit.md new file mode 100644 index 00000000..cd92eb80 --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/audit.md @@ -0,0 +1,9 @@ +# Test harness audit + +The original inventory remains immutable. Its functions shared one file, so +ordinary errors in unselected negative functions also made the positive CLI +invocations fail. The audited population isolates the exact same function +bodies into separate files. `partial` was rejected with the intended bounds +obligation, "access interval must fit its object", which the original reason +regex omitted. The audited matcher includes that explanation. No true negative +or positive property changed. Original results remain in zero-counters29b.json. diff --git a/test/evaluation/rfc0029/zero-counters/audited-manifest.json b/test/evaluation/rfc0029/zero-counters/audited-manifest.json new file mode 100644 index 00000000..b066fc7e --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/audited-manifest.json @@ -0,0 +1,95 @@ +{ + "version": 1, + "cases": [ + { + "name": "zero-counter-plain", + "sources": [ + "plain.c" + ], + "functions": [ + "plain" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "zero-counter-array", + "sources": [ + "array.c" + ], + "functions": [ + "array" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "zero-counter-partial", + "sources": [ + "partial.c" + ], + "functions": [ + "partial" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|outside|access interval must fit its object" + }, + { + "name": "zero-counter-replaced", + "sources": [ + "replaced.c" + ], + "functions": [ + "replaced" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|outside|access interval must fit its object" + }, + { + "name": "zero-counter-helper", + "sources": [ + "helper.c" + ], + "functions": [ + "helper" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|outside|access interval must fit its object" + }, + { + "name": "zero-counter-cells", + "sources": [ + "cells.c" + ], + "functions": [ + "cells" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|outside|access interval must fit its object" + } + ] +} diff --git a/test/evaluation/rfc0029/zero-counters/audited-sha256.json b/test/evaluation/rfc0029/zero-counters/audited-sha256.json new file mode 100644 index 00000000..9c4e87fa --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/audited-sha256.json @@ -0,0 +1,10 @@ +{ + "array.c": "ff74af16157a0bb3f5d354fcff16558f8c4e07f733e07b31ef3083a67e3f4833", + "audit.md": "1447fd425c12107d56ed0044131ee9658c120469ee7f1efff670b55e0121283f", + "audited-manifest.json": "73a14289d81d23693f97682b6dbcef028f86bae469d6a944a2b9e36727583996", + "cells.c": "393c305a8c6d37de1fdb2c9f7a2538090e18dd040c49a542859ccc76b4b79872", + "helper.c": "0f9c349386a91cd6bb42e2a2bcddbf8496e1bb65e8171b516d62c04cf155a22f", + "partial.c": "fb7dc765877254e05c154d093eec4b79032a52629b5843aaba381fac90857383", + "plain.c": "1ece9e19f638baa304662210af5bbe23483e801abeaf8b7c9f79ca1b832fd791", + "replaced.c": "09f9591c3d07b6363f533a8f2fbe489bf586620451d46c93300908dc403cbaf8" +} diff --git a/test/evaluation/rfc0029/zero-counters/cases.c b/test/evaluation/rfc0029/zero-counters/cases.c new file mode 100644 index 00000000..d77396e7 --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/cases.c @@ -0,0 +1,9 @@ +#include +struct row { char *data; unsigned count; }; +int plain(void) { struct row r; int a[1]={7}; memset(&r,0,sizeof r); return a[r.count]; } +int array(void) { struct row r[2]; int a[1]={7}; memset(r,0,sizeof r); return a[r[1].count]; } +int partial(void) { struct row r; int a[1]={7}; r.count=256; memset(&r.count,0,1); return a[r.count]; } +int replaced(void) { struct row r; int a[1]={7}; memset(&r,0,sizeof r); r.count=1; return a[r.count]; } +static void change(struct row *r) { r->count=1; } +int helper(void) { struct row r; int a[1]={7}; memset(&r,0,sizeof r); change(&r); return a[r.count]; } +int cells(void) { struct row r[2]; int a[1]={7}; memset(r,0,sizeof r); r[1].count=1; return a[r[1].count]; } diff --git a/test/evaluation/rfc0029/zero-counters/cells.c b/test/evaluation/rfc0029/zero-counters/cells.c new file mode 100644 index 00000000..a9ee5cb6 --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/cells.c @@ -0,0 +1,3 @@ +#include +struct row { char *data; unsigned count; }; +int cells(void) { struct row r[2]; int a[1]={7}; memset(r,0,sizeof r); r[1].count=1; return a[r[1].count]; } diff --git a/test/evaluation/rfc0029/zero-counters/frozen-sha256.json b/test/evaluation/rfc0029/zero-counters/frozen-sha256.json new file mode 100644 index 00000000..619af347 --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/frozen-sha256.json @@ -0,0 +1,5 @@ +{ + "README.md": "783671a0cbc63693b86f48593793d97e64062092c3ad2bde96b411a1cbe26f9c", + "cases.c": "2eaa76f692890f42c596f92393a540fd2e23b5e05ee1e41791578d67ca8a971c", + "manifest.json": "ae0fdf1ce9ebcb2eca1a3b7d4784a68264c5140b58203d14a02873c976790c95" +} diff --git a/test/evaluation/rfc0029/zero-counters/helper.c b/test/evaluation/rfc0029/zero-counters/helper.c new file mode 100644 index 00000000..67dee1e2 --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/helper.c @@ -0,0 +1,4 @@ +#include +struct row { char *data; unsigned count; }; +static void change(struct row *r) { r->count=1; } +int helper(void) { struct row r; int a[1]={7}; memset(&r,0,sizeof r); change(&r); return a[r.count]; } diff --git a/test/evaluation/rfc0029/zero-counters/manifest.json b/test/evaluation/rfc0029/zero-counters/manifest.json new file mode 100644 index 00000000..bf7bcb02 --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/manifest.json @@ -0,0 +1,95 @@ +{ + "version": 1, + "cases": [ + { + "name": "zero-counter-plain", + "sources": [ + "cases.c" + ], + "functions": [ + "plain" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "zero-counter-array", + "sources": [ + "cases.c" + ], + "functions": [ + "array" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "zero-counter-partial", + "sources": [ + "cases.c" + ], + "functions": [ + "partial" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|outside" + }, + { + "name": "zero-counter-replaced", + "sources": [ + "cases.c" + ], + "functions": [ + "replaced" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|outside" + }, + { + "name": "zero-counter-helper", + "sources": [ + "cases.c" + ], + "functions": [ + "helper" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|outside" + }, + { + "name": "zero-counter-cells", + "sources": [ + "cases.c" + ], + "functions": [ + "cells" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "bounds|extent|outside" + } + ] +} diff --git a/test/evaluation/rfc0029/zero-counters/partial.c b/test/evaluation/rfc0029/zero-counters/partial.c new file mode 100644 index 00000000..95ffd8b4 --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/partial.c @@ -0,0 +1,3 @@ +#include +struct row { char *data; unsigned count; }; +int partial(void) { struct row r; int a[1]={7}; r.count=256; memset(&r.count,0,1); return a[r.count]; } diff --git a/test/evaluation/rfc0029/zero-counters/plain.c b/test/evaluation/rfc0029/zero-counters/plain.c new file mode 100644 index 00000000..3f9c89b9 --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/plain.c @@ -0,0 +1,3 @@ +#include +struct row { char *data; unsigned count; }; +int plain(void) { struct row r; int a[1]={7}; memset(&r,0,sizeof r); return a[r.count]; } diff --git a/test/evaluation/rfc0029/zero-counters/replaced.c b/test/evaluation/rfc0029/zero-counters/replaced.c new file mode 100644 index 00000000..c0ae751f --- /dev/null +++ b/test/evaluation/rfc0029/zero-counters/replaced.c @@ -0,0 +1,3 @@ +#include +struct row { char *data; unsigned count; }; +int replaced(void) { struct row r; int a[1]={7}; memset(&r,0,sizeof r); r.count=1; return a[r.count]; } diff --git a/test/evaluation/rfc0029/zero-frames/README.md b/test/evaluation/rfc0029/zero-frames/README.md new file mode 100644 index 00000000..8d19c612 --- /dev/null +++ b/test/evaluation/rfc0029/zero-frames/README.md @@ -0,0 +1 @@ +RFC0029 current zero-byte separation. Frozen before shared separation projection for zero-byte frames, baseline candidate29b. The writer must preserve its stored zero while updating a distinct header. A subsequent overwrite or an aliased byte store must not preserve that zero. diff --git a/test/evaluation/rfc0029/zero-frames/alias.c b/test/evaluation/rfc0029/zero-frames/alias.c new file mode 100644 index 00000000..a0837694 --- /dev/null +++ b/test/evaluation/rfc0029/zero-frames/alias.c @@ -0,0 +1,7 @@ +#include +#include +struct writer { unsigned char *data; size_t length, capacity; }; +static void change(unsigned char *p, unsigned char *q) { + p[0]=0; q[0]=1; +} +int main(void) { unsigned char x[1];change(x,x);return strlen((char*)x)!=0; } diff --git a/test/evaluation/rfc0029/zero-frames/frozen-sha256.json b/test/evaluation/rfc0029/zero-frames/frozen-sha256.json new file mode 100644 index 00000000..fc823a51 --- /dev/null +++ b/test/evaluation/rfc0029/zero-frames/frozen-sha256.json @@ -0,0 +1,7 @@ +{ + "README.md": "6187ee2348a47d2789e9fefc063203dcbf186a91bf00f9d669a8ce40fd454dac", + "alias.c": "c33bc46bb857676a37b5043ced0a3c07dd8bb6b76fc6dc84927bb75d30fdd235", + "manifest.json": "417be0f42dd6a173f2481c5ae307f2f832b518895e699694f3c525dc268e7031", + "overwrite.c": "409cb25bde404f2e71ed158462fbb85573a03980f31416860318f3f31be3750d", + "writer.c": "483a55a11cb9ace1f4083641ee3286bf5addbbbb0a5d52cde02e1a91fad7d8a3" +} diff --git a/test/evaluation/rfc0029/zero-frames/manifest.json b/test/evaluation/rfc0029/zero-frames/manifest.json new file mode 100644 index 00000000..53415819 --- /dev/null +++ b/test/evaluation/rfc0029/zero-frames/manifest.json @@ -0,0 +1,50 @@ +{ + "version": 1, + "cases": [ + { + "name": "zero-frame-writer", + "sources": [ + "writer.c" + ], + "functions": [ + "main" + ], + "expect": "accepted", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "entry_requirements": 0 + }, + { + "name": "zero-frame-overwrite", + "sources": [ + "overwrite.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "terminat|separat|disjoint" + }, + { + "name": "zero-frame-alias", + "sources": [ + "alias.c" + ], + "functions": [ + "main" + ], + "expect": "rejected", + "forbidden_trust": [ + "unsafe", + "annotation" + ], + "reason": "terminat|separat|disjoint" + } + ] +} diff --git a/test/evaluation/rfc0029/zero-frames/overwrite.c b/test/evaluation/rfc0029/zero-frames/overwrite.c new file mode 100644 index 00000000..777fad37 --- /dev/null +++ b/test/evaluation/rfc0029/zero-frames/overwrite.c @@ -0,0 +1,11 @@ +#include +#include +struct writer { unsigned char *data; size_t length, capacity; }; +static int emit(struct writer *w) { + if(w->length>w->capacity || w->capacity-w->length<3) return 0; + unsigned char *p=w->data+w->length; + p[0]='x';p[1]='y';p[2]=0;w->length+=2;p[2]='z';return 1; +} +int main(void) { unsigned char bytes[3]; struct writer w={bytes,0,3}; + if(!emit(&w))return 0; return strlen((char*)w.data)!=2; +} diff --git a/test/evaluation/rfc0029/zero-frames/writer.c b/test/evaluation/rfc0029/zero-frames/writer.c new file mode 100644 index 00000000..802d1d57 --- /dev/null +++ b/test/evaluation/rfc0029/zero-frames/writer.c @@ -0,0 +1,11 @@ +#include +#include +struct writer { unsigned char *data; size_t length, capacity; }; +static int emit(struct writer *w) { + if(w->length>w->capacity || w->capacity-w->length<3) return 0; + unsigned char *p=w->data+w->length; + p[0]='x';p[1]='y';p[2]=0;w->length+=2;return 1; +} +int main(void) { unsigned char bytes[3]; struct writer w={bytes,0,3}; + if(!emit(&w))return 0; return strlen((char*)w.data)!=2; +} diff --git a/unittests/Analysis/ArrayOwnershipTest.cpp b/unittests/Analysis/ArrayOwnershipTest.cpp index 856a6d4b..e7789719 100644 --- a/unittests/Analysis/ArrayOwnershipTest.cpp +++ b/unittests/Analysis/ArrayOwnershipTest.cpp @@ -47,6 +47,40 @@ void clean(char **a) { free(a[0]); free(a[1]); } << ::testing::PrintToString(test::messages(result.diagnostics)); } +// RFC 0029: array decay and an explicit selected record share one cell. +TEST(ArrayOwnership, RecordArrayArrowAgreesWithSelectionAndForwarding) { + AnalysisOptions options; + options.checkedFunctions.insert("client"); + const auto result = test::analyze(R"c( +struct slot { char *p; unsigned n; }; +static void release(struct slot *s) { free(s->p); } +void client(void) { + struct slot a[2]; a->p=malloc(4); a[1].p=malloc(4); + if ((*a).p) a[0].p[0]=1; + release(a); free(a[1].p); +} +)c", + options); + ASSERT_TRUE(result.ast); + ASSERT_NE(result.summary("client"), nullptr); + EXPECT_TRUE(result.summary("client")->checked.complete()); + EXPECT_TRUE(result.summary("client")->checked.requirements.empty()); + EXPECT_TRUE(result.diagnostics.empty()) + << ::testing::PrintToString(test::messages(result.diagnostics)); +} + +TEST(ArrayOwnership, RecordArrayArrowPreservesAliasReleaseHistory) { + const auto result = test::analyze(R"c( +struct slot { char *p; }; +void bad(void) { + struct slot a[2]; a->p=malloc(4); a[1].p=(*a).p; + free(a[1].p); free(a->p); +} +)c"); + ASSERT_TRUE(result.ast); + EXPECT_EQ(countId(result, core::diag::DoubleFree), 1U); +} + TEST(ArrayOwnership, AConstantIndexIsCapturedBeforeReassignment) { const auto result = test::analyze(R"c( void bad(char **a) { int i = 0; free(a[i]); i = 1; a[0][0] = 1; } diff --git a/unittests/Analysis/BufferTest.cpp b/unittests/Analysis/BufferTest.cpp index df12c969..6a5620b3 100644 --- a/unittests/Analysis/BufferTest.cpp +++ b/unittests/Analysis/BufferTest.cpp @@ -12,6 +12,371 @@ namespace weavec::analysis { +TEST(BufferAnalysis, HelperCursorDifferencesSurviveMixedLoopPaths) { + for (const auto *contents : {"0", "1,2,3,4,5,6,7,8,9,10"}) { + SCOPED_TRACE(contents); + const std::string code = R"c( + unsigned decode(const unsigned char *p,const unsigned char *end, + unsigned char **out) { + if(end-p<2)return 0; (*out)[0]=p[1]; (*out)++; return 2; + } + int client(void) { + const unsigned char bytes[10]={)c" + + std::string(contents) + R"c(}; + unsigned char dst[10]; + const unsigned char *p=bytes,*end=bytes+10; unsigned char *out=dst; + while(pchecked.complete()); + EXPECT_TRUE(result.summary("client")->checked.requirements.empty()); + ASSERT_NE(result.summary("changed_zero"), nullptr); + EXPECT_FALSE(result.summary("changed_zero")->checked.complete()); + } +} + +TEST(BufferAnalysis, IndependentCursorCountsPreserveOnlyProvedRelations) { + const auto result = test::analyze(R"c( + void copy_bytes(const unsigned char *src, unsigned char *dst, size_t n) { + const unsigned char *p=src; unsigned char *q=dst; + while(pchecked.complete()) << name; + } + EXPECT_TRUE(result.summary("good")->checked.requirements.empty()); + for (const auto *name : {"short_output", "uninitialized_input", + "changed_stride", "unrelated_order"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_FALSE(result.summary(name)->checked.complete()) << name; + } +} + +TEST(BufferAnalysis, FixedSpanStepsRetainTheirActualRemainingLengthBound) { + const auto result = test::analyze(R"c( + unsigned consume(const unsigned char *p,const unsigned char *end) { + if(end-p<2)return 0; (void)p[0]; return 2; + } + int client(unsigned length) { + if(length>10)return 0; + const unsigned char src[10]={0}; + const unsigned char *p=src,*end=src+length; + while(p10)return 0; + const unsigned char src[10]={0}; + const unsigned char *p=src,*end=src+length; + while(pchecked.complete()) << name; + } + EXPECT_TRUE(result.summary("client")->checked.requirements.empty()); + ASSERT_NE(result.summary("too_far"), nullptr); + EXPECT_FALSE(result.summary("too_far")->checked.complete()); + ASSERT_NE(result.summary("wrapped_remaining"), nullptr); + EXPECT_FALSE(result.summary("wrapped_remaining")->checked.complete()); + EXPECT_TRUE(std::ranges::any_of( + result.summary("consume")->checked.establishes, [](const auto &post) { + return post.kind == core::CheckedRequirementKind::CountWithinSpan; + })); +} + +TEST(BufferAnalysis, AdvertisedCapacityDoesNotReplaceActualInputExtent) { + for (const unsigned extent : {2U, 4U}) { + const std::string code = R"c( + void *memcpy(void *, const void *, size_t); + struct B {char *data; size_t used, capacity;}; + static void copy(struct B *b, char *out) { + if(b->used>b->capacity)return; + if(b->data[b->used])memcpy(out,b->data,b->used+1); + } + int client(void) { + char in[)c" + std::to_string(extent) + + R"c(]={'a','b'},out[4]={0}; + struct B b={in,2,2};copy(&b,out);return 0; + } + )c"; + const auto result = + test::analyze(code, {.checkContracts = true, .checked = true}); + ASSERT_TRUE(result.ast); + ASSERT_NE(result.summary("client"), nullptr); + EXPECT_EQ(result.summary("client")->checked.complete(), extent == 4); + EXPECT_TRUE(result.summary("client")->checked.requirements.empty()); + } +} + +TEST(BufferAnalysis, PointerOffsetGuardsPreserveOnlyRepresentableDifferences) { + const auto result = test::analyze(R"c( + struct reader { + const unsigned char *data; size_t capacity, position; unsigned depth; + }; + int scan(struct reader *r) { + if (!r || !r->data || r->position>=r->capacity) return 0; + const unsigned char *cursor=r->data+r->position; + while ((size_t)(cursor-r->data)capacity) { + if (*cursor=='x') return 1; + ++cursor; + } + return 0; + } + int narrow(struct reader *r) { + if (!r || !r->data || r->position>=r->capacity) return 0; + const unsigned char *cursor=r->data+r->position; + while ((unsigned char)(cursor-r->data)capacity) { + if (*cursor=='x') return 1; + ++cursor; + } + return 0; + } + int client(void) { + const unsigned char input[4]={'a','b','c','x'}; + struct reader r={input,4,0,0}; return scan(&r); + } + int lossy(void) { + const unsigned char input[300]={0}; + struct reader r={input,300,0,0}; return narrow(&r); + } + )c", + {.checkContracts = true, .checked = true}); + ASSERT_TRUE(result.ast); + for (const auto *name : {"scan", "client"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_TRUE(result.summary(name)->checked.complete()) << name; + } + EXPECT_TRUE(result.summary("client")->checked.requirements.empty()); + EXPECT_TRUE(std::ranges::any_of( + result.summary("scan")->checked.requirements, + [](const auto &requirement) { + return requirement.kind == core::CheckedRequirementKind::SumFits; + })); + ASSERT_NE(result.summary("lossy"), nullptr); + EXPECT_FALSE(result.summary("lossy")->checked.complete()); +} + +TEST(BufferAnalysis, PairedReaderCountersRequireActualPreservedEquality) { + for (const auto &increment : {"++count;", "count+=8;", "count=0;"}) { + SCOPED_TRACE(increment); + const std::string code = R"c( + void *memcpy(void *, const void *, size_t); + struct reader { + const unsigned char *data; size_t capacity, position; unsigned depth; + }; + unsigned char *copy_digits(struct reader *r) { + size_t i=0, count=0; + if (!r || !r->data) return 0; + for (i=0; r->position+icapacity; ++i) { + switch ((r->data+r->position)[i]) { + case '0': case '1': + )c" + std::string(increment) + + R"c( + break; + default: goto done; + } + } + done: ; + unsigned char *out=malloc(count+1); + if (!out) return 0; + memcpy(out,r->data+r->position,count); + out[count]=0; + return out; + } + int client(void) { + unsigned char input[5]={'x','0','1','0','x'}; + struct reader r={input,5,1,0}; + unsigned char *out=copy_digits(&r); if(out)free(out); return 0; + } + )c"; + AnalysisOptions options; + options.checkedFunctions.insert("client"); + const auto result = test::analyze(code, options); + ASSERT_TRUE(result.ast); + ASSERT_NE(result.summary("client"), nullptr); + // A reset count is safe but need not remain equal to the scan index. + const bool safe = std::string_view(increment) != "count+=8;"; + EXPECT_EQ(result.summary("client")->checked.complete(), safe); + if (safe) + EXPECT_TRUE(result.summary("client")->checked.requirements.empty()); + } +} + +TEST(BufferAnalysis, StableReaderIndexLoopsProveOnlyStrictUnchangedBounds) { + const auto result = test::analyze(R"c( + struct reader { + const unsigned char *data; size_t capacity, position; unsigned depth; + }; + int scan(struct reader *r) { + if (!r || !r->data) return 0; + for (size_t i=0; r->position+icapacity; ++i) { + switch ((r->data+r->position)[i]) { + case '0': case '1': break; + default: goto done; + } + } + done: return 1; + } + int changed_index(struct reader *r) { + if (!r || !r->data) return 0; + for (size_t i=0; r->position+icapacity; ++i) { + i=r->capacity; + if ((r->data+r->position)[i]) return 1; + } + return 0; + } + int client(void) { + const unsigned char input[4]={'x','0','1','x'}; + struct reader r={input,4,1,7}; return scan(&r); + } + int changed_client(void) { + const unsigned char input[4]={'x','0','1','x'}; + struct reader r={input,4,1,7}; return changed_index(&r); + } + )c", + {.checkContracts = true, .checked = true}); + ASSERT_TRUE(result.ast); + for (const auto *name : {"scan", "client"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_TRUE(result.summary(name)->checked.complete()) << name; + } + EXPECT_TRUE(result.summary("client")->checked.requirements.empty()); + ASSERT_NE(result.summary("changed_index"), nullptr); + // RFC 0029 permits an additional interval beyond advertised capacity. + // The generic helper can export that sufficient premise, but the actual + // four-byte client must reject its access at position + capacity. + const auto &changed = result.summary("changed_index")->checked; + EXPECT_TRUE(changed.complete()); + EXPECT_TRUE(std::ranges::any_of(changed.requirements, [](const auto &pre) { + return pre.kind == core::CheckedRequirementKind::Extent && + pre.path == core::SummaryPath::param(0).deref().field("data"); + })); + ASSERT_NE(result.summary("changed_client"), nullptr); + EXPECT_FALSE(result.summary("changed_client")->checked.complete()); +} + +TEST(BufferAnalysis, ExplicitByteCastsPreserveEvaluatedPointerOffsets) { + const auto result = test::analyze(R"c( + int memcmp(const void *, const void *, size_t); + int good(void) { + unsigned short data[3] = {0, 0, 0}; + return memcmp((const char *)(data + 1), "xx", 2); + } + int past_end(void) { + unsigned short data[3] = {0, 0, 0}; + return memcmp((const char *)(data + 3), "xx", 2); + } + int before_start(void) { + unsigned short data[3] = {0, 0, 0}; + return memcmp((const char *)(data - 1), "xx", 2); + } + )c", + {.checkContracts = true, .checked = true}); + ASSERT_TRUE(result.ast); + ASSERT_NE(result.summary("good"), nullptr); + EXPECT_TRUE(result.summary("good")->checked.complete()); + EXPECT_TRUE(result.summary("good")->checked.requirements.empty()); + for (const auto *name : {"past_end", "before_start"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_FALSE(result.summary(name)->checked.complete()) << name; + } +} + +TEST(BufferAnalysis, BoundedStringCursorRequiresInitializedTermination) { + const auto result = test::analyze(R"c( + size_t strlen(const char *); + struct writer { unsigned char *data; size_t capacity, length; int flags; }; + void finish(struct writer *w) { + if (!w || !w->data) return; + const unsigned char *next = w->data + w->length; + w->length += strlen((const char *)next); + } + void good(void) { + unsigned char *p=malloc(4); if(!p)return; + p[0]=0; p[1]=42; p[2]=0; p[3]=0; + struct writer w={p,4,1,0}; finish(&w); + if(w.length>=w.capacity) p[4]=1; + else p[w.length]=0; + free(p); + } + void uninitialized(void) { + unsigned char *p=malloc(4); if(!p)return; + p[0]=42; p[3]=0; + struct writer w={p,4,1,0}; finish(&w); free(p); + } + void beyond_bound(void) { + unsigned char *p=malloc(4); if(!p)return; + p[0]=42; p[1]=42; p[2]=42; p[3]=0; + struct writer w={p,3,1,0}; finish(&w); free(p); + } + )c", + {.checkContracts = true, .checked = true}); + ASSERT_TRUE(result.ast); + ASSERT_NE(result.summary("good"), nullptr); + EXPECT_TRUE(result.summary("good")->checked.complete()); + for (const auto *name : {"uninitialized", "beyond_bound"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_FALSE(result.summary(name)->checked.complete()) << name; + } +} + static const std::string BufferPrelude = R"c( typedef __SIZE_TYPE__ size_t; void *malloc(size_t); @@ -49,6 +414,154 @@ static core::CheckedContract bufferCheck(const std::string &body, return unit.summary(name)->checked; } +TEST(BufferAnalysis, ZeroBytesEstablishConcreteIntegerCells) { + const auto contract = bufferCheck(R"c( +void *memset(void *, int, size_t); +int client(void) { + struct bytes rows[2]; int a[1]={7}; + memset(rows,0,sizeof rows); + return a[rows[1].length]; +} +)c"); + EXPECT_TRUE(contract.complete()); + EXPECT_TRUE(contract.requirements.empty()); +} + +TEST(BufferAnalysis, PartialOrReplacedZeroBytesSupplyNoStaleCounter) { + for (const auto *change : {"rows[1].length=256; memset(&rows[1].length,0,1);", + "rows[1].length=1;", "change(&rows[1]);"}) { + SCOPED_TRACE(change); + const auto contract = bufferCheck( + R"c( +void *memset(void *, int, size_t); +static void change(struct bytes *b) { b->length=1; } +int client(void) { + struct bytes rows[2]; int a[1]={7}; + memset(rows,0,sizeof rows); +)c" + std::string(change) + + "return a[rows[1].length]; }"); + EXPECT_FALSE(contract.complete()); + } +} + +TEST(BufferAnalysis, HeaderWritesPreserveOnlySeparatedTerminatingBytes) { + for (const bool overwrite : {false, true}) { + SCOPED_TRACE(overwrite); + const auto contract = bufferCheck( + R"c( +size_t strlen(const char *); +static int emit(struct bytes *b) { + if(b->length>b->capacity || b->capacity-b->length<3) return 0; + unsigned char *p=b->data+b->length; + p[0]='x'; p[1]='y'; p[2]=0; b->length+=2; +)c" + std::string(overwrite ? "p[2]='z';" : "") + + R"c( + return 1; +} +int client(void) { + unsigned char output[3]; struct bytes b={output,0,3}; + if(!emit(&b)) return 0; + return strlen((const char *)b.data)!=2; +} +)c"); + EXPECT_EQ(contract.complete(), !overwrite); + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(BufferAnalysis, HelperReturnsKeepActualCapacityAndWrittenPrefix) { + for (const auto *reserveBody : + {"return needed<=w->size ? w->data+w->used : 0;", + "if(needed<=w->size) return w->data+w->used;" + "unsigned char *p=realloc(w->data,needed);if(!p)return 0;" + "w->data=p;w->size=needed;return p+w->used;"}) { + SCOPED_TRACE(reserveBody); + for (const auto *write : {"*out++ = '{';", "++out;", "out[1] = '{';"}) { + SCOPED_TRACE(write); + const auto contract = bufferCheck(R"c( +size_t strlen(const char *); +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *next_slot(struct writer *w, size_t needed) { + if(!w || !w->data) return 0; + if(w->size>0 && w->used>=w->size) return 0; + if(needed>2147483647u) return 0; + needed += w->used+1; +)c" + std::string(reserveBody) + R"c( +} +static int emit(struct writer *w) { + unsigned char *out=next_slot(w,2); + if(!out) return 0; +)c" + std::string(write) + R"c( + ++w->depth; ++w->used; + out=next_slot(w,2); + if(!out) return 0; + *out++='}'; *out=0; --w->depth; + return 1; +} +int client(void) { + struct writer w={0}; + w.data=malloc(256); + if(!w.data) return 0; + w.size=256; + int result=emit(&w); + if(result) (void)strlen((const char *)w.data); + free(w.data); + return 0; +} +)c"); + EXPECT_EQ(contract.complete(), + std::string_view(write) == "*out++ = '{';"); + EXPECT_TRUE(contract.requirements.empty()); + } + } +} + +TEST(BufferAnalysis, ForwardingImportsAnIncompleteGenericCalleesLayoutOnly) { + for (const std::string write : {"*out++ = '{';", "out[1] = '{';"}) { + SCOPED_TRACE(write); + const auto contract = bufferCheck(R"c( +size_t strlen(const char *); +struct writer { unsigned char *data; size_t size, used, depth; int format; }; +static unsigned char *reserve_forwarded(struct writer *w, size_t needed) { + if (!w || !w->data) return 0; + if (w->size > 0 && w->used >= w->size) return 0; + if (needed > 2147483647u) return 0; + needed += w->used + 1; + if (needed <= w->size) return w->data + w->used; + return 0; +} +static int render(struct writer *w) { + if (w->format) return render(w); + unsigned char *out = reserve_forwarded(w, 2); + if (!out) return 0; +)c" + write + R"c( + + ++w->depth; + ++w->used; + out = reserve_forwarded(w, 2); + if (!out) return 0; + *out++ = '}'; + *out = 0; + --w->depth; + return 1; +} +int forward(struct writer *w) { return render(w); } +int client(void) { + struct writer w = {0}; + w.data = malloc(256); + if (!w.data) return 0; + w.size = 256; + int result = forward(&w); + if (result) (void)strlen((char *)w.data); + free(w.data); + return 0; +} +)c"); + EXPECT_EQ(contract.complete(), write == "*out++ = '{';"); + EXPECT_TRUE(contract.requirements.empty()); + } +} + TEST(BufferAnalysis, GenericReserveSeparatesContentsFromReleasePermission) { const auto contract = bufferCheck("", "reserve"); EXPECT_TRUE(contract.complete()); @@ -373,4 +886,80 @@ TEST(BufferAnalysis, ForwardingFailureCannotHideAnAllocatedBacking) { .complete()); } +TEST(BufferAnalysis, ReadersPreserveFullInputAndPartialConsumption) { + const auto result = test::analyze(R"c( + typedef __SIZE_TYPE__ size_t; + struct reader { unsigned depth; const unsigned char *data; size_t size, pos; }; + int read_next(struct reader *r) { + if (r->pos == r->size) return -1; + int value = r->data[r->pos]; + ++r->pos; + if (value == 0) return -1; + return value; + } + )c", + {.checkContracts = true, .checked = true}); + const auto *summary = result.summary("read_next"); + ASSERT_NE(summary, nullptr); + EXPECT_TRUE(summary->checked.complete()); + bool input = false; + bool output = false; + for (const auto &requirement : summary->checked.requirements) + if (requirement.kind == core::CheckedRequirementKind::Buffer) { + const auto shape = core::BufferShape::decode(requirement.family); + input |= shape && shape->reader && !shape->ownsBacking; + } + for (const auto &post : summary->checked.establishes) + if (post.kind == core::CheckedRequirementKind::Buffer) { + const auto shape = core::BufferShape::decode(post.family); + output |= shape && shape->reader && !post.on && post.when.trivial(); + } + EXPECT_TRUE(input); + EXPECT_TRUE(output); +} + +TEST(BufferAnalysis, ReadersRequireActualInitializedInputAndNoWrites) { + for (const auto *body : + {"if(r->possize) ((unsigned char*)r->data)[r->pos]=0;", + "if(r->possize) return r->data[r->size];"}) { + const auto result = test::analyze(R"c( + typedef __SIZE_TYPE__ size_t; + struct reader { unsigned depth; const unsigned char *data; size_t size,pos; }; + int read_next(struct reader *r) { + )c" + std::string(body) + R"c(return 0;} + int client(void) { + const unsigned char input[2] = {1, 2}; + struct reader r = {0, input, 2, 0}; + return read_next(&r); + } + )c", + {.checkContracts = true, + .checked = true}); + ASSERT_NE(result.summary("read_next"), nullptr); + const auto &contract = result.summary("read_next")->checked; + if (std::string(body).find("=0") != std::string::npos) { + // C permits casting const away when the actual object is writable. + // The reader's initialized input cannot supply that permission. + bool writable = false; + for (const auto &requirement : contract.requirements) + writable |= + requirement.kind == core::CheckedRequirementKind::Writable && + requirement.path == + core::SummaryPath::param(0).deref().field("data"); + EXPECT_TRUE(writable || !contract.complete()); + } else { + // An actual allocation may exceed its advertised readable prefix. + // Reading past that prefix exports an additional caller obligation. + EXPECT_TRUE(std::ranges::any_of( + contract.requirements, [](const auto &requirement) { + return requirement.kind == core::CheckedRequirementKind::Extent && + requirement.path == + core::SummaryPath::param(0).deref().field("data"); + })); + } + ASSERT_NE(result.summary("client"), nullptr); + EXPECT_FALSE(result.summary("client")->checked.complete()) << body; + } +} + } // namespace weavec::analysis diff --git a/unittests/Analysis/CMakeLists.txt b/unittests/Analysis/CMakeLists.txt index a5fcaef5..228386db 100644 --- a/unittests/Analysis/CMakeLists.txt +++ b/unittests/Analysis/CMakeLists.txt @@ -5,6 +5,7 @@ weavec_add_unittest( RuntimeContractsTest.cpp ContainerTest.cpp RecursiveContainerTest.cpp + RecursiveContractsTest.cpp BufferTest.cpp AllocatorsTest.cpp AnnotationsTest.cpp diff --git a/unittests/Analysis/CallContextTest.cpp b/unittests/Analysis/CallContextTest.cpp index ef7c1e7a..013442fb 100644 --- a/unittests/Analysis/CallContextTest.cpp +++ b/unittests/Analysis/CallContextTest.cpp @@ -17,6 +17,377 @@ namespace weavec::analysis { +TEST(CompositionalCall, RuntimeComparisonsKeepOnlyTheirEstablishedSign) { + const auto result = test::analyze(R"c( + typedef __SIZE_TYPE__ size_t; + int memcmp(const void *,const void *,size_t); + int strcmp(const char *,const char *); + int strncmp(const char *,const char *,size_t); + int inspect(const unsigned char *p, unsigned n, unsigned mode) { + if (mode==0 && strncmp((const char*)p,"\xef\xbb\xbf",3)==0) return p[n]; + if (mode==1 && memcmp(p,"\0y",2)>=0) return p[n]; + if (mode==2 && strcmp((const char*)p,"\0y")!=0) return p[n]; + if (mode==3 && memcmp(p,"a",1)<=0) return p[n]; + if (mode==4 && memcmp(p,"abc",3)==1) return p[n]; + return 0; + } + int absent(void){const unsigned char p[]="abc";return inspect(p,4,0);} + int embedded(void){const unsigned char p[]={0,120};return inspect(p,2,1);} + int terminated(void){const unsigned char p[]={0,120};return inspect(p,2,2);} + int high(void){const unsigned char p[]={255};return inspect(p,1,3);} + int present(void){const unsigned char p[]={239,187,191,0};return inspect(p,4,0);} + int magnitude(void){const unsigned char p[]="dbc";return inspect(p,4,4);} + int short_input(void){const unsigned char p[]={239};return inspect(p,1,0);} + int unknown(void){unsigned char p[4];p[0]=97;return inspect(p,4,0);} + )c", + {.checkContracts = true, .checked = true}); + ASSERT_TRUE(result.ast); + for (const auto *name : {"absent", "embedded", "terminated", "high"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_TRUE(result.summary(name)->checked.complete()) << name; + EXPECT_TRUE(result.summary(name)->checked.requirements.empty()) << name; + } + for (const auto *name : {"present", "magnitude", "short_input", "unknown"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_FALSE(result.summary(name)->checked.complete()) << name; + } +} + +TEST(CompositionalCall, CompleteLocalHelperWritesKeepSeparateInputBytes) { + const auto result = test::analyze(R"c( + void bump(unsigned *p) { ++*p; } + int inspect(const unsigned char *p, unsigned n) { + unsigned counter=0; bump(&counter); + for (unsigned i=0; ichecked.complete()); + EXPECT_TRUE(result.summary("good")->checked.requirements.empty()); + ASSERT_NE(result.summary("changed"), nullptr); + EXPECT_FALSE(result.summary("changed")->checked.complete()); +} + +TEST(CompositionalCall, ConstantByteObjectsRetainContentsAcrossGlobalWrites) { + const auto result = test::analyze(R"c( + static unsigned calls; + int inspect(const unsigned char *p, unsigned n) { + ++calls; + for (unsigned i=0; ichecked.complete()) << name; + EXPECT_TRUE(result.summary(name)->checked.requirements.empty()) << name; + } + for (const auto *name : {"changed", "invalid_write"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_FALSE(result.summary(name)->checked.complete()) << name; + } +} + +TEST(CompositionalCall, + ExactBytesRetainTheirReadableIntervalThroughForwarding) { + const auto result = test::analyze(R"c( + int scan(const unsigned char *p, unsigned n) { + for (unsigned i=0; ichecked.complete()); + EXPECT_TRUE(result.summary("good")->checked.requirements.empty()); + for (const auto *name : {"changed", "oversized", "partial"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_FALSE(result.summary(name)->checked.complete()) << name; + } +} + +TEST(CompositionalCall, ExactLocalPointerCellsShareTheirCurrentCursor) { + for (const auto *step : {"(*slot)++", "*slot+=3"}) { + SCOPED_TRACE(step); + const std::string source = + "int client(void){const unsigned char text[]=\"abc\";" + "const unsigned char *p=text;const unsigned char **slot=&p;" + "while(pchecked.complete(), + std::string(step) == "(*slot)++"); + } +} + +TEST(CompositionalCall, ByteContentsUseIndependentlyProvedCursorRelations) { + for (const bool escape : {false, true}) { + SCOPED_TRACE(escape); + const std::string source = R"c( + int scan(const unsigned char *data, size_t size) { + const unsigned char *p=data+1,*end=data+1; + while((size_t)(end-data)=size || *end!=34) return 0; + while(pchecked.complete(), !escape); + } +} + +TEST(CompositionalCall, CompleteCalleeFramesRequireActualConstantObjects) { + for (const std::string variant : {"static", "local", "wrong", "mutable-write", + "const-write", "partial", "unknown"}) { + SCOPED_TRACE(variant); + const bool write = variant == "mutable-write" || variant == "const-write"; + std::string source = "static unsigned config;struct state{unsigned n;};"; + if (variant == "unknown") { + source += "void change(struct state *,unsigned char *);"; + } else { + source += + "void change(struct state *s,unsigned char *p){s->n=1;config=1;"; + if (write) + source += "p[0]=98;"; + source += '}'; + } + source += "int inspect(struct state *s,const unsigned char *p){" + "change(s,(unsigned char*)p);if(p[0]!=97)return p[8];return 0;}" + "int client(void){struct state s={0};"; + if (variant == "local") + source += "const unsigned char p[]={97};"; + else if (variant == "partial") + source += "unsigned char p[1];"; + else if (variant == "mutable-write") + source += "unsigned char p[]={97};"; + else if (variant == "wrong") + source += "static const unsigned char p[]={98};"; + else + source += "static const unsigned char p[]={97};"; + source += "return inspect(&s,p);}"; + const auto result = + test::analyze(source, {.checkContracts = true, .checked = true}); + ASSERT_NE(result.summary("client"), nullptr); + const auto &contract = result.summary("client")->checked; + EXPECT_EQ(contract.complete(), variant == "static" || variant == "local"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(CompositionalCall, ByteLoopPartitionsKeepActualFirstExitAndFallback) { + for (const std::string variant : + {"while", "do", "helper", "skipped", "changed", "missing", + "uninitialized", "late"}) { + SCOPED_TRACE(variant); + const std::string step = variant == "skipped" ? "i+=2" : "i++"; + std::string source = "unsigned inner(const unsigned char *p,unsigned n) {"; + if (variant == "do") + source += "unsigned i=0;if(!n)return 0;do{if(p[i]==34)return i;i++;}" + "while(ichecked; + EXPECT_EQ(contract.complete(), + variant == "while" || variant == "do" || variant == "helper"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(CompositionalCall, ByteSwitchPartitionsRetainFirstExitAndFallback) { + for (const std::string variant : + {"direct", "forward", "shifted", "skip", "changed", "missing", + "uninitialized", "beyond-bound"}) { + SCOPED_TRACE(variant); + std::string source = R"c( + typedef __SIZE_TYPE__ size_t; + static size_t scan(const unsigned char *p,size_t n,size_t start) { + size_t i; + for(i=0;start+ichecked; + EXPECT_EQ(contract.complete(), variant == "direct" || + variant == "forward" || + variant == "shifted"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(CompositionalCall, ExactBytesRequireCurrentInitializedStorage) { + const auto result = test::analyze(R"c( + int inspect(const unsigned char *p, unsigned n) { + for (unsigned i=0; ichecked.complete()) << name; + EXPECT_TRUE(result.summary(name)->checked.requirements.empty()) << name; + } + for (const auto *name : {"changed", "unknown_write", "partial", "escaped"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_FALSE(result.summary(name)->checked.complete()) << name; + } +} + static std::size_t countContextDiagnostic(const test::AnalysisResult &result, std::string_view id) { return static_cast( @@ -387,6 +758,31 @@ TEST(CompositionalCall, OversizedInputFootprintsHaveAnExplicitBoundary) { })); } +TEST(CompositionalCall, CallbackAndScalarPremisesShareOneBodyCase) { + core::AnalysisStats stats; + AnalysisOptions options; + options.checkedFunctions.insert("client"); + options.stats = &stats; + const auto result = test::analyze(R"c( +static void fill(char *p) { *p=7; } +int invoke(int enabled, void (*write)(char *), char *p) { + if(!enabled) return 0; + write(p); return *p; +} +int client(void) { char p[1]; return invoke(1,fill,p); } +)c", + options); + ASSERT_TRUE(result.ast); + ASSERT_NE(result.summary("client"), nullptr); + EXPECT_TRUE(result.summary("client")->checked.complete()); + EXPECT_TRUE(result.summary("client")->checked.requirements.empty()); + EXPECT_GT(stats.count("combined_callback_case_requests"), 0U); + const auto &store = result.analyzer->summaries(); + const auto requests = store.callbackRequests.find("invoke"); + EXPECT_TRUE(requests == store.callbackRequests.end() || + requests->second.empty()); +} + TEST(CompositionalCall, InvalidTypedContextsAreNeverCachedAsChecked) { auto result = test::analyze("void zap(int value) { (void)value; }"); ASSERT_TRUE(result.ast); @@ -881,14 +1277,17 @@ TEST(ContextDependencies, ChangesDuringAnalysisCannotProduceACurrentSnapshot) { EXPECT_TRUE(store.dependenciesCurrent(current)); } -TEST(ContextDependencies, - SettledSilentRecursiveAnalysisIsReusedBeforeReporting) { +TEST(ContextDependencies, SettledRecursiveValuesAreRecheckedBeforeReporting) { core::AnalysisStats stats; const auto result = test::analyze("int f(int n) { if (n <= 0) return 0; return f(n - 1); }", {.stats = &stats}); ASSERT_TRUE(result.ast); - EXPECT_GT(stats.count("silent_function_reuses"), 0U); + // RFC 0029 rechecks the body against settled conservative dependencies to + // refine value outcomes; a pre-finalization silent result cannot supply it. + EXPECT_EQ(stats.count("silent_function_reuses"), 0U); + EXPECT_GT(stats.count("function_analyses"), + stats.count("function_fixpoint_rounds")); EXPECT_EQ(countContextDiagnostic(result, core::diag::AnalysisIncomplete), 0U); } @@ -916,11 +1315,18 @@ int concrete(void) { EXPECT_FALSE(generic->callbackInputs.empty()); EXPECT_TRUE(concrete->checked.complete()); EXPECT_TRUE(concrete->checked.requirements.empty()); - const auto &requests = result.analyzer->summaries().callbackRequests; - const auto found = requests.find("invoke_hook"); - ASSERT_NE(found, requests.end()); - ASSERT_FALSE(found->second.empty()); - for (const auto &bindings : found->second) + const auto &store = result.analyzer->summaries(); + std::vector requests; + if (const auto found = store.callbackRequests.find("invoke_hook"); + found != store.callbackRequests.end()) + requests.insert(requests.end(), found->second.begin(), found->second.end()); + if (const auto found = store.memoryRequests.find("invoke_hook"); + found != store.memoryRequests.end()) + for (const auto &input : found->second) + if (!input.callbacks.empty()) + requests.push_back(input.callbacks); + ASSERT_FALSE(requests.empty()); + for (const auto &bindings : requests) for (const auto &[path, targets] : bindings) { EXPECT_TRUE(path.isGlobal()); EXPECT_FALSE(targets.unknown); @@ -928,4 +1334,114 @@ int concrete(void) { } } +TEST(CompositionalCall, AnUnexecutedRecursiveBranchKeepsCaseLocalLimits) { + const std::string helper = R"c( + struct state { unsigned depth, tag; char *data; }; + static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth >= 1000) return 0; + ++s->depth; + if (*s->data == 1) return 100 / *s->data; + return walk(s); + } + )c"; + for (const auto *entry : + {"struct state s={0,0,0};return walk(&s);", + "char data=1;struct state s={0,1,&data};return walk(&s);"}) { + AnalysisOptions options; + options.checkedFunctions.insert("client"); + const auto result = + test::analyze(helper + "int client(void){" + entry + "}", options); + ASSERT_NE(result.summary("client"), nullptr); + EXPECT_TRUE(result.summary("client")->checked.complete()) << entry; + EXPECT_TRUE(result.summary("client")->checked.requirements.empty()); + ASSERT_NE(result.summary("walk"), nullptr); + EXPECT_FALSE(result.summary("walk")->checked.complete()); + } +} + +TEST(CompositionalCall, ReachableRecursiveCasesRetainLimitsAndLocalErrors) { + AnalysisOptions options; + options.checkedFunctions.insert("client"); + for (const auto *entry : + {"struct state s={0,1,0};", "char data=0;struct state s={0,1,&data};"}) { + const auto result = test::analyze(R"c( + struct state { unsigned depth, tag; char *data; }; + static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth >= 1000) return 0; + ++s->depth; + if (*s->data == 1) return 100 / *s->data; + return walk(s); + } + int client(void){ + )c" + std::string(entry) + "return walk(&s);}", + options); + ASSERT_NE(result.summary("client"), nullptr); + EXPECT_FALSE(result.summary("client")->checked.complete()) << entry; + } +} + +TEST(CompositionalCall, CapturedPointeeValuesNeedExactLiveScalarStorage) { + AnalysisOptions options; + options.checkedFunctions.insert("client"); + for (const auto *entry : {"char data=1;struct state s={0,1,&data};data=0;", + "char data[2]={1,0};struct state s={0,1,data+1};", + "char *data=malloc(1);if(!data)return 0;*data=1;" + "struct state s={0,1,data};free(data);", + "int data=1;struct state s={0,1,(char*)&data};"}) { + const auto result = test::analyze(R"c( + void *malloc(__SIZE_TYPE__); + void free(void *); + struct state { unsigned depth, tag; char *data; }; + static int walk(struct state *s) { + if (!s->tag) return 0; + if (s->depth >= 1000) return 0; + ++s->depth; + if (*s->data == 1) return 100 / *s->data; + return walk(s); + } + int client(void){ + )c" + std::string(entry) + "return walk(&s);}", + options); + ASSERT_NE(result.summary("client"), nullptr); + EXPECT_FALSE(result.summary("client")->checked.complete()) << entry; + } +} + +TEST(CompositionalCall, MutableStateConstantsDoNotCrowdOutRecursiveInputs) { + std::string code = R"c( + struct node { struct node *next; unsigned tag; }; + struct state { unsigned seen, mode; }; + static int walk(struct node *p, struct state *s) { + if (!p) return 0; + s->seen = s->mode; + if (p->tag == 1) return 1; + return walk(p->next, s); + } + )c"; + for (unsigned i = 0; i < 36; ++i) + code += "int generic_" + std::to_string(i) + + "(struct node *p){struct state s={0," + std::to_string(i) + + "};return walk(p,&s);}"; + code += R"c( + int client(void) { + struct node n = {0, 1}; + struct state s = {0, 100}; + return walk(&n, &s); + } + int bad(void) { struct node n = {0, 1}; return walk(&n, 0); } + )c"; + AnalysisOptions options; + options.checkedFunctions = {"client", "bad", "walk"}; + const auto result = test::analyze(code, options); + ASSERT_NE(result.summary("client"), nullptr); + EXPECT_TRUE(result.summary("client")->checked.complete()); + EXPECT_TRUE(result.summary("client")->checked.requirements.empty()); + ASSERT_NE(result.summary("bad"), nullptr); + EXPECT_FALSE(result.summary("bad")->checked.complete()); + ASSERT_NE(result.summary("walk"), nullptr); + EXPECT_FALSE(result.summary("walk")->checked.complete()); +} + } // namespace weavec::analysis diff --git a/unittests/Analysis/CheckedCodeTest.cpp b/unittests/Analysis/CheckedCodeTest.cpp index 0a02672e..363341c1 100644 --- a/unittests/Analysis/CheckedCodeTest.cpp +++ b/unittests/Analysis/CheckedCodeTest.cpp @@ -21,6 +21,19 @@ static core::CheckedContract check(const std::string &code, } return result.summary(name)->checked; } + +TEST(CheckedCode, SavedPostfixCursorRetainsOnlyItsCurrentAllocationPermission) { + for (const auto &change : {"", "free(out);", "p=(unsigned char*)\"x\";"}) { + SCOPED_TRACE(change); + const std::string code = + "int f(void){unsigned char *out=malloc(3);if(!out)return 0;" + "unsigned char *p=out;unsigned char **slot=&p;(void)slot;" + + std::string(change) + "*p++=1;" + + (std::string(change) == "free(out);" ? "" : "free(out);") + + "return 0;}"; + EXPECT_EQ(check(code).complete(), std::string(change).empty()); + } +} // RFC 0028: private output storage participates in portable contracts. TEST(CheckedCode, PrivateOutputFactsSurviveAnExportedSetter) { AnalysisOptions options; diff --git a/unittests/Analysis/DataflowTest.cpp b/unittests/Analysis/DataflowTest.cpp index fad2fdc8..d99236f5 100644 --- a/unittests/Analysis/DataflowTest.cpp +++ b/unittests/Analysis/DataflowTest.cpp @@ -920,7 +920,7 @@ TEST(Dataflow, ArrayDecayBorrowsTheElements) { const auto result = analyze(code, Exclusive); ASSERT_TRUE(result.ast); EXPECT_EQ(messages(result.diagnostics), - (Strings{"5: cannot borrow 'a[*]' as mutable because it is already " + (Strings{"5: cannot borrow 'a[1]' as mutable because it is already " "borrowed"})); } diff --git a/unittests/Analysis/IntegerSemanticsTest.cpp b/unittests/Analysis/IntegerSemanticsTest.cpp index 4c276329..4143ee9f 100644 --- a/unittests/Analysis/IntegerSemanticsTest.cpp +++ b/unittests/Analysis/IntegerSemanticsTest.cpp @@ -13,6 +13,155 @@ using namespace weavec; using namespace weavec::test; +TEST(IntegerSemantics, NumericResultRefinesConservativePendingOutcomes) { + for (const bool negativeFailure : {false, true}) { + const auto library = + analyze("int make(char **out){*out=malloc(1);if(!*out)return " + + std::string(negativeFailure ? "-1" : "0") + ";return 1;}"); + ASSERT_TRUE(library.ast); + auto exports = library.analyzer->exports(); + auto summary = exports.functions.at("make").summary.get(); + // A conservative recursive approximation may retain an extra sign class + // after the body's independently established numeric result is refined. + summary.addOutcome(core::Outcome::Negative); + exports.functions.at("make").summary.assign(std::move(summary)); + analysis::ProgramDatabase database; + database.add(exports); + for (const bool saved : {false, true}) { + const auto caller = test::analyzeInProgram( + "int make(char **out);void client(void){char *p=0;" + + std::string(saved ? "int ok=make(&p);if(!ok)return;" + : "if(!make(&p))return;") + + "*p=0;free(p);}", + &database); + ASSERT_TRUE(caller.ast); + const bool nullError = std::ranges::any_of( + caller.diagnostics.diagnostics(), [](const auto &diagnostic) { + return diagnostic.id == core::diag::NullDereference; + }); + EXPECT_EQ(nullError, negativeFailure) + << ::testing::PrintToString(messages(caller.diagnostics)); + } + } +} + +TEST(IntegerSemantics, PointerDifferenceSizesKeepTheirEvaluatedCoordinates) { + for (const auto *variant : + {"good", "offset", "undersized", "overrun", "changed", "narrowed"}) { + SCOPED_TRACE(variant); + const std::string kind = variant; + const bool offset = kind == "offset"; + const bool narrowed = kind == "narrowed"; + std::string source = R"c( + typedef __SIZE_TYPE__ size_t; + void *malloc(size_t); void free(void *); + int copy(int choose) { + )c"; + source += + narrowed + ? "const char data[300]={0};const char *end=data+(choose?3:260);" + : "const char data[]=\"abcdef\";const char *end=data+(choose?3:6);"; + source += "size_t size="; + source += narrowed ? "(unsigned char)" : "(size_t)"; + source += offset ? "(end-(data+1));" : "(end-data);"; + source += kind == "undersized" ? "char *output=malloc(size);" + : "char *output=malloc(size+1);"; + source += "if(!output)return 0;"; + source += offset ? "const char *input=data+1;" : "const char *input=data;"; + source += "char *cursor=output;"; + if (kind == "changed") + source += "end=data+7;"; + source += "while(inputchecked.complete(), + kind == "good" || offset); + if (kind == "good" || offset) + EXPECT_TRUE(result.summary("copy")->checked.requirements.empty()); + } +} + +TEST(IntegerSemantics, AdvancedPointerDoesNotRetainThePreviousScalarCell) { + const auto result = analyze(R"c( + int advanced(unsigned char *p) { *p=7; ++p; return *p; } + int assigned(unsigned char *p) { *p=7; p=p+1; return *p; } + int original(unsigned char *p) { + unsigned char *out=p; + *out++=7; *out=0; + return *p; + } + int joined(int n, unsigned char *p, unsigned char *q) { + unsigned char *out=n?p:q; + *out=7; + return *p; + } + )c", + {.checkContracts = true, .checked = true}); + ASSERT_TRUE(result.ast); + for (const auto *name : {"advanced", "assigned", "joined"}) { + const auto *summary = result.summary(name); + ASSERT_NE(summary, nullptr); + const auto outputs = + summary->numericOutputs.find(core::SummaryPath::result()); + ASSERT_NE(outputs, summary->numericOutputs.end()); + EXPECT_TRUE(std::ranges::any_of(outputs->second, [](const auto &output) { + return !output.value || !output.value->constantValue(); + })) << name; + } + const auto *summary = result.summary("original"); + ASSERT_NE(summary, nullptr); + const auto outputs = + summary->numericOutputs.find(core::SummaryPath::result()); + ASSERT_NE(outputs, summary->numericOutputs.end()); + for (const auto &output : outputs->second) { + ASSERT_TRUE(output.value); + const auto value = output.value->constantValue(); + ASSERT_TRUE(value); + EXPECT_EQ(value->bits, 7U); + } +} + +TEST(IntegerSemantics, LocalArrayCellsRetainActualInitializationAndWrites) { + const auto result = analyze(R"c( + void fill(unsigned char *p) { unsigned char *q=p; *q++=7; *q=0; } + void single(unsigned char *p) { *p=7; } + void safe(void) { + unsigned char a[2]={1,1}; fill(a); + if(a[0]==0) a[2]=1; + } + void bad(void) { + unsigned char a[2]={1,1}; fill(a); + if(a[0]==7) a[2]=1; + } + void literal(void) { char a[3]="ab"; if(a[1]=='b') a[3]=1; } + void narrow_literal(void) { + signed char a[2]="\xff"; if(a[0]==-1) a[2]=1; + } + void overlapping(unsigned n) { + unsigned char a[2]={0,0}; if(n>1)return; + a[n]=7; if(a[0]==7) a[2]=1; + } + void interior(void) { + unsigned char *p=malloc(2); if(!p)return; + *p=42; single(p+1); if(*p==42) p[2]=1; free(p); + } + )c", + {.checkContracts = true, .checked = true}); + ASSERT_TRUE(result.ast); + ASSERT_NE(result.summary("safe"), nullptr); + EXPECT_TRUE(result.summary("safe")->checked.complete()); + for (const auto *name : + {"bad", "literal", "narrow_literal", "overlapping", "interior"}) { + ASSERT_NE(result.summary(name), nullptr); + EXPECT_FALSE(result.summary(name)->checked.complete()) << name; + EXPECT_TRUE(result.summary(name)->checked.obligations.violated()) << name; + } +} + TEST(IntegerSemantics, CheckedAllocationFailureSurvivesReturnedPointers) { const auto result = analyze(R"c( void *calloc(size_t, size_t); diff --git a/unittests/Analysis/InterfaceTypesTest.cpp b/unittests/Analysis/InterfaceTypesTest.cpp index 30aaca3e..150e71e6 100644 --- a/unittests/Analysis/InterfaceTypesTest.cpp +++ b/unittests/Analysis/InterfaceTypesTest.cpp @@ -63,6 +63,38 @@ TEST(InterfaceTypes, PrivateNestedStorageRetainsTargetLayoutAndQualifiers) { EXPECT_FALSE(record->isCompleteDefinition()); } +TEST(InterfaceTypes, AnonymousTypedefsRetainViewsWithoutEnteringClientLookup) { + auto owner = interfaceAST(R"c( + typedef struct { const unsigned char *data; unsigned long offset; } cursor; + typedef struct { cursor saved; const cursor *current; } session; + static const session state; + )c"); + auto foreign = interfaceAST("typedef int cursor; int client;", "client.c"); + ASSERT_TRUE(owner); + ASSERT_TRUE(foreign); + const auto &source = owner->getASTContext(); + auto &target = foreign->getASTContext(); + const auto description = describeInterfaceType( + interfaceVariable(source, "state")->getType(), source); + ASSERT_TRUE(description); + ASSERT_EQ(description->nodes.front().typedefName, "session"); + const auto declarations = + std::distance(target.getTranslationUnitDecl()->decls_begin(), + target.getTranslationUnitDecl()->decls_end()); + const auto materialized = materializeInterfaceType(*description, target); + ASSERT_FALSE(materialized.isNull()); + EXPECT_TRUE(materialized.isConstQualified()); + EXPECT_EQ(describeInterfaceType(materialized, target), description); + EXPECT_EQ(declarations, + std::distance(target.getTranslationUnitDecl()->decls_begin(), + target.getTranslationUnitDecl()->decls_end())); + auto forged = *description; + forged.nodes.front().typedefName = "other"; + EXPECT_TRUE(materializeInterfaceType(forged, target).isNull()); + forged.nodes.front().typedefName.clear(); + EXPECT_TRUE(materializeInterfaceType(forged, target).isNull()); +} + TEST(InterfaceTypes, TargetMismatchAndForgedViewsCannotBeMaterialized) { auto owner = interfaceAST("struct item { int value; }; static struct item state;"); diff --git a/unittests/Analysis/RecursiveContainerTest.cpp b/unittests/Analysis/RecursiveContainerTest.cpp index ee1e5b0c..ecec52f5 100644 --- a/unittests/Analysis/RecursiveContainerTest.cpp +++ b/unittests/Analysis/RecursiveContainerTest.cpp @@ -70,6 +70,53 @@ TEST(RecursiveContainerAnalysis, EXPECT_TRUE(contract.complete()); EXPECT_TRUE(contract.requirements.empty()); } + +TEST(RecursiveContainerAnalysis, + HeadPredicateValuesDischargeOnlyCurrentReturnGuards) { + for (const std::string variant : + {"null", "changed", "alias", "failed-replacement", "replacement", + "released", "allocation"}) { + SCOPED_TRACE(variant); + std::string mutation; + if (variant == "changed") + mutation = "p->flags=2;"; + else if (variant == "alias") + mutation = "struct node *q=p;q->flags=2;"; + else if (variant == "failed-replacement") + mutation = "destroy(p);p=create();if(!p)return 0;p->flags=2;"; + else if (variant == "replacement") + mutation = "destroy(p);reset(0);p=create();if(!p)return 0;" + "p->flags=2;reset(&h);"; + else if (variant == "released") + mutation = "destroy(p);"; + const auto contract = + forestCheck(R"c( + static void *fail(size_t n) { (void)n; return 0; } + static void *render(const struct node *p) { + if(p->flags!=1)return malloc(1); + char *out=global_hooks.allocate(1); + if(!out)return 0; + *out='a';return out; + } + int client(void) { + reset(0);struct node *p=create();if(!p)return 0; + struct hooks h={)c" + + std::string(variant == "allocation" ? "malloc" : "fail") + + R"c(,free};reset(&h); + )c" + mutation + + R"c( + char *out=render(p);if(out){out[2]=1;free(out);} + )c" + (variant == "released" ? "" : "destroy(p);") + + R"c( + return 0; + } + )c"); + EXPECT_EQ(contract.complete(), + variant == "null" || variant == "failed-replacement"); + EXPECT_TRUE(contract.requirements.empty()); + } +} + TEST(RecursiveContainerAnalysis, UnknownHooksAreNotAssumedToBeLibc) { EXPECT_FALSE(forestCheck(R"c( int client(struct hooks *h) { reset(h); struct node *p=create(); @@ -244,6 +291,393 @@ TEST(RecursiveContainerAnalysis, .complete()); } +TEST(RecursiveContainerAnalysis, + PayloadPublicationRequiresTheWholeAcquisition) { + for (const std::string variant : + {"direct", "helper", "duplicate", "interior", "released", "lost", + "overwritten", "helper-leak"}) { + SCOPED_TRACE(variant); + const bool helper = variant == "helper" || variant == "helper-leak"; + std::string store = "n->text=p;"; + if (variant == "duplicate") + store += "n->name=p;"; + else if (variant == "interior") + store = "n->text=p+1;"; + else if (variant == "released") + store = "free(p);n->text=p;"; + else if (variant == "lost") + store.clear(); + const std::string source = R"c( + typedef __SIZE_TYPE__ size_t; + void *malloc(size_t); void *calloc(size_t,size_t); void free(void *); + struct node { struct node *next,*child; char *text,*name; }; + static void drop(struct node *n) { + while(n) { struct node *next=n->next; drop(n->child); + free(n->text);free(n->name);free(n);n=next; } + } + static char *make(void) { + )c" + std::string(variant == "helper-leak" ? "(void)malloc(2);" : "") + + R"c( + char *p=malloc(4);if(p)p[0]=0;return p; + } + static int fill(struct node *n) { char *p= + )c" + std::string(helper ? "make()" : "malloc(4)") + + ";if(!p)return 0;p[0]=0;" + store + R"c( + return 1; + } + int client(void) { + struct node *n=calloc(1,sizeof *n);if(!n)return 0; + )c" + std::string(variant == "overwritten" ? "n->text=malloc(2);" : "") + + "fill(n);drop(n);return 0;}"; + AnalysisOptions options; + options.checkedFunctions.insert("client"); + const auto unit = test::analyze(source, options); + ASSERT_NE(unit.summary("client"), nullptr); + const auto &contract = unit.summary("client")->checked; + EXPECT_EQ(contract.complete(), variant == "direct" || variant == "helper"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(RecursiveContainerAnalysis, PayloadFramesRequireActualInputSeparation) { + for (const std::string variant : + {"direct", "early", "helper", "alias", "aliased-helper", "duplicate"}) { + SCOPED_TRACE(variant); + const bool helper = variant == "helper" || variant == "aliased-helper"; + const bool alias = variant == "alias" || variant == "aliased-helper"; + const std::string write = helper ? "step(cursor);" : "*cursor=256;"; + std::string body = R"c( + static void step(int *p) { *p=256; } + static int fill(struct node *n,int *cursor) { + )c" + std::string(variant == "early" ? write : "") + + R"c( + char *p=malloc(4);if(!p){ + )c" + write + R"c( + return 0; + } + p[0]=0;n->text=p; + )c"; + if (variant == "duplicate") + body += "n->key=p;"; + body += write; + body += R"c( + return 1; + } + int client(void) { + reset(0);struct node *n=create();if(!n)return 0;int cursor=0; + fill(n, + )c" + std::string(alias ? "&n->flags" : "&cursor") + + ");destroy(n);return 0;}"; + const auto contract = forestCheck(body); + EXPECT_EQ(contract.complete(), !alias && variant != "duplicate"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(RecursiveContainerAnalysis, PayloadRelocationConservesActualOwnership) { + for (const std::string variant : + {"helper", "local", "alias", "duplicate", "interior", "overwritten", + "released", "intervening"}) { + SCOPED_TRACE(variant); + std::string source = R"c( + typedef __SIZE_TYPE__ size_t; + void *malloc(size_t);void *calloc(size_t,size_t);void free(void *); + struct node {struct node *next,*child;char *text,*name;}; + static void drop(struct node *n) {while(n){struct node *next=n->next; + drop(n->child);free(n->text);free(n->name);free(n);n=next;}} + static int fill(struct node *n) {char *p=malloc(4);if(!p)return 0; + p[0]=0;n->text=p;return 1;} + int client(void) {struct node *n=calloc(1,sizeof *n);if(!n)return 0; + )c"; + source += variant == "local" ? "n->text=malloc(4);" : "fill(n);"; + if (variant == "overwritten") + source += "n->name=malloc(4);"; + if (variant == "released") + source += "free(n->text);"; + if (variant == "alias") { + source += "struct node *p=n;p->name=p->text;p->text=0;"; + } else if (variant == "interior") { + source += "if(n->text){n->name=n->text+1;n->text=0;}"; + } else { + source += "n->name=n->text;"; + if (variant == "intervening") + source += "free(n->text);"; + if (variant != "duplicate") + source += "n->text=0;"; + } + source += "drop(n);return 0;}"; + AnalysisOptions options; + options.checkedFunctions.insert("client"); + const auto unit = test::analyze(source, options); + ASSERT_NE(unit.summary("client"), nullptr); + const auto &contract = unit.summary("client")->checked; + EXPECT_EQ(contract.complete(), + variant == "helper" || variant == "local" || variant == "alias"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(RecursiveContainerAnalysis, + ComparisonFramesRetainOrdinaryReadObligations) { + for (const std::string variant : {"bounded", "bytes", "string", "released", + "uninitialized", "extent", "unknown"}) { + SCOPED_TRACE(variant); + std::string source = R"c( + typedef __SIZE_TYPE__ size_t; + void *calloc(size_t,size_t);void free(void *); + int strcmp(const char *,const char *); + int strncmp(const char *,const char *,size_t); + int memcmp(const void *,const void *,size_t); + struct node {struct node *next,*child;unsigned value;}; + void unknown(struct node *); + static unsigned read_tree(const struct node *n) { + return n?n->value+read_tree(n->child)+read_tree(n->next):0; + } + static void drop(struct node *n) {while(n){struct node *next=n->next; + drop(n->child);free(n);n=next;}} + static unsigned inspect(struct node *n,const char *s) { + )c"; + if (variant == "released") + source += "free(n);"; + if (variant == "unknown") + source += "unknown(n);"; + std::string comparison = "strncmp(s,\"ok\",2)"; + if (variant == "string") + comparison = "strcmp(s,\"ok\")"; + else if (variant == "bytes") + comparison = "memcmp(s,\"ok\",2)"; + else if (variant == "extent") + comparison = "memcmp(s,\"okay\",4)"; + source += + "if(" + comparison + "==0)return read_tree(n);return read_tree(n);}"; + source += R"c( + int client(void) {struct node *n=calloc(1,sizeof *n);if(!n)return 0; + n->value=3; + )c"; + source += variant == "uninitialized" ? "char s[3];s[0]='o';" + : "const char s[]=\"ok\";"; + source += "unsigned r=inspect(n,s);"; + if (variant != "released") + source += "drop(n);"; + source += "return r==3?0:1;}"; + AnalysisOptions options; + options.checkedFunctions.insert("client"); + const auto unit = test::analyze(source, options); + ASSERT_NE(unit.summary("client"), nullptr); + const auto &contract = unit.summary("client")->checked; + EXPECT_EQ(contract.complete(), variant == "bounded" || variant == "bytes" || + variant == "string"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(RecursiveContainerAnalysis, PayloadExtensionSurvivesReaderForwarding) { + for (const std::string variant : {"goto", "early", "leak", "duplicate", + "released-head", "released-payload"}) { + SCOPED_TRACE(variant); + std::string source = R"c( + typedef __SIZE_TYPE__ size_t; + void *malloc(size_t);void *calloc(size_t,size_t);void free(void *); + struct node {struct node *next,*child;unsigned flags;char *text,*name;}; + struct reader {const unsigned char *content;size_t length,offset,depth;}; + static void drop(struct node *n) { + while(n){struct node *next=n->next;drop(n->child); + if(!(n->flags&1))free(n->text);free(n->name);free(n);n=next;} + } + static int role(struct reader *r) { + if(r->offsetlength){r->offset++;return 1;}return 0; + } + static int fill(struct node *n,struct reader *r) {char *p=malloc(4); + )c"; + source += variant == "early" ? "if(!p){r->offset=1;return 0;}" + : "if(!p)goto fail;"; + source += "p[0]=0;"; + if (variant == "released-payload") + source += "free(p);"; + if (variant != "leak") + source += "n->text=p;"; + if (variant == "duplicate") + source += "n->name=p;"; + source += "r->offset++;return 1;"; + if (variant != "early") { + source += "fail:"; + if (variant == "released-head") + source += "free(n);"; + source += "r->offset=1;return 0;"; + } + source += R"c( + } + static int wrap(struct node *n,struct reader *r) { + if(!fill(n,r))return 0;r->offset++;return 1; + } + int client(void) { + struct node *n=calloc(1,sizeof *n);if(!n)return 0; + static const unsigned char bytes[]="abc"; + struct reader r={bytes,sizeof bytes,0,0};wrap(n,&r);drop(n);return 0; + } + )c"; + AnalysisOptions options; + options.checkedFunctions.insert("client"); + const auto unit = test::analyze(source, options); + ASSERT_NE(unit.summary("client"), nullptr); + const auto &contract = unit.summary("client")->checked; + EXPECT_EQ(contract.complete(), variant == "goto" || variant == "early"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(RecursiveContainerAnalysis, NumericParserFramesRequireSeparateEndCells) { + for (const std::string variant : + {"null-end", "local-end", "record-end", "released", "uninitialized", + "owned-end", "unknown"}) { + SCOPED_TRACE(variant); + std::string source = R"c( + typedef __SIZE_TYPE__ size_t; + void *calloc(size_t,size_t);void free(void *); + double strtod(const char *,char **); + struct node {struct node *next,*child;char *text;unsigned value;}; + void unknown(struct node *); + static unsigned read_tree(const struct node *n) { + return n?n->value+read_tree(n->child)+read_tree(n->next):0; + } + static void drop(struct node *n) {while(n){struct node *next=n->next; + drop(n->child);free(n->text);free(n);n=next;}} + static unsigned inspect(struct node *n,const char *s) { + char *end=0;struct {char *end;} local={0}; + )c"; + if (variant == "released") + source += "free(n);"; + std::string output = "&end"; + if (variant == "null-end") + output = "0"; + else if (variant == "record-end") + output = "&local.end"; + else if (variant == "owned-end") + output = "&n->text"; + source += "(void)strtod(s," + output + ");"; + if (variant == "unknown") + source += "unknown(n);"; + source += R"c( + return read_tree(n); + } + int client(void) {struct node *n=calloc(1,sizeof *n);if(!n)return 0; + n->value=3; + )c"; + source += variant == "uninitialized" ? "char s[3];s[0]='1';" + : "const char s[]=\"12\";"; + source += "unsigned r=inspect(n,s);"; + if (variant != "released") + source += "drop(n);"; + source += "return r==3?0:1;}"; + AnalysisOptions options; + options.checkedFunctions.insert("client"); + const auto unit = test::analyze(source, options); + ASSERT_NE(unit.summary("client"), nullptr); + const auto &contract = unit.summary("client")->checked; + EXPECT_EQ(contract.complete(), variant == "null-end" || + variant == "local-end" || + variant == "record-end"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(RecursiveContainerAnalysis, TemporaryReleaseFramesKeepActualEntryForests) { + for (const std::string variant : {"guarded", "nullable", "helper", "interior", + "twice", "lost", "attached"}) { + SCOPED_TRACE(variant); + std::string source = R"c( + typedef __SIZE_TYPE__ size_t; + void *malloc(size_t);void *calloc(size_t,size_t);void free(void *); + struct node {struct node *next,*child;char *text;unsigned value;}; + static unsigned read_tree(const struct node *n) { + return n?n->value+read_tree(n->child)+read_tree(n->next):0; + } + static void drop(struct node *n) {while(n){struct node *next=n->next; + drop(n->child);free(n->text);free(n);n=next;}} + static char *make(void) {return malloc(4);} + static unsigned inspect(struct node *n) {char *p= + )c"; + source += variant == "helper" ? "make();" : "malloc(4);"; + if (variant != "nullable") + source += "if(!p)return read_tree(n);"; + if (variant == "attached") + source += "n->text=p;"; + if (variant != "lost") + source += variant == "interior" ? "free(p+1);" : "free(p);"; + if (variant == "twice") + source += "free(p);"; + source += R"c( + return read_tree(n); + } + int client(void) {struct node *n=calloc(1,sizeof *n);if(!n)return 0; + n->value=3;unsigned r=inspect(n);drop(n);return r==3?0:1;} + )c"; + AnalysisOptions options; + options.checkedFunctions.insert("client"); + const auto unit = test::analyze(source, options); + ASSERT_NE(unit.summary("client"), nullptr); + const auto &contract = unit.summary("client")->checked; + EXPECT_EQ(contract.complete(), variant == "guarded" || + variant == "nullable" || + variant == "helper"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + +TEST(RecursiveContainerAnalysis, OutcomeJoinsRetainCommonOwnershipStructure) { + for (const std::string variant : + {"direct", "helper", "branch", "released", "lost", "disowned"}) { + SCOPED_TRACE(variant); + std::string source = R"c( + typedef __SIZE_TYPE__ size_t; + void *malloc(size_t);void *calloc(size_t,size_t);void free(void *); + struct node {struct node *next,*child;char *text;unsigned flags;}; + static void drop(struct node *n) {while(n){struct node *next=n->next; + drop(n->child);if(!(n->flags&1))free(n->text);free(n);n=next;}} + static int inspect(struct node *n) { + void *p=malloc(4);if(!p)return 0;free(p); + )c"; + if (variant == "released") + source += "free(n);"; + else if (variant == "lost") + source += "n->text=0;"; + else if (variant == "disowned") + source += "n->flags=1;"; + else + source += "n->flags=2;"; + source += R"c( + return 1; + } + static int forward(struct node *n) {return inspect(n);} + int client(void) {struct node *n=calloc(1,sizeof *n);if(!n)return 0; + n->text=malloc(4);if(!n->text){drop(n);return 0;} + )c"; + if (variant == "branch") + source += "if(inspect(n))drop(n);else drop(n);"; + else if (variant == "helper") + source += "(void)forward(n);drop(n);"; + else + source += "(void)inspect(n);drop(n);"; + source += "return 0;}"; + AnalysisOptions options; + options.checkedFunctions.insert("client"); + const auto unit = test::analyze(source, options); + ASSERT_NE(unit.summary("client"), nullptr); + const auto &contract = unit.summary("client")->checked; + EXPECT_EQ(contract.complete(), variant == "direct" || variant == "helper" || + variant == "branch"); + if (contract.complete()) + EXPECT_TRUE(contract.requirements.empty()); + } +} + TEST(RecursiveContainerAnalysis, DetachmentPreservesBothAllocationPartitions) { const std::string helpers = R"c( static struct node *get_array_item(const struct node *array, size_t index){ struct node *child=NULL;if(array==NULL)return NULL;child=array->child;while(child!=NULL&&index>0){index--;child=child->next;}return child;} @@ -305,6 +739,57 @@ int client(void){reset(0);struct node*a=create();if(!a)return 0;struct node*b=cr .complete()); } +TEST(RecursiveContainerAnalysis, AttachedPayloadsTransferOnTheTestedOutcome) { + // RFC 0029: a complete attach publishes the union of both owned inputs and + // its own fresh payload on success, and preserves them separately on + // failure. A forwarding wrapper carries that outcome-specific guarantee. + for (const std::string variant : {"good", "leak", "twice", "ignored"}) { + const std::string body = + "static int attach(struct node *object,struct node *item){char *key=0;" + "if(!object||!item||object==item)return 0;key=(char*)malloc(4);" + "if(!key)return 0;key[0]=0;item->key=key;return add(object,item);}" + "static int attach_wrapper(struct node *o,struct node *i){" + "return attach(o,i);}" + "int client(void){reset(0);struct node *o=create();if(!o)return 0;" + "struct node *i=create();if(!i){destroy(o);return 0;}" + "if(!attach_wrapper(o,i)){" + + std::string(variant == "good" ? "destroy(i);destroy(o);return 0;" + : variant == "leak" ? "destroy(o);return 0;" + : variant == "twice" ? "destroy(i);destroy(o);return 0;" + : "") + + "}destroy(o);" + std::string(variant == "twice" ? "destroy(i);" : "") + + "return 0;}"; + EXPECT_EQ(forestCheck(body).complete(), variant == "good") << variant; + } +} + +TEST(RecursiveContainerAnalysis, + DirectByteResultsAndHelperReleasesAreLedgered) { + // RFC 0029: a complete callee's fresh byte result returned without a local + // holder transfers one allocation; a complete release helper settles it. + for (const std::string variant : + {"good", "unguarded", "leak", "twice", "interior"}) { + const std::string body = + "static char *render(const struct node *p){" + "char *out=(char*)global_hooks.allocate(4);if(!out)return 0;" + "out[0]=p->flags?'x':'y';out[1]=0;return out;}" + "static char *publish(const struct node *p){return (char*)render(p)" + + std::string(variant == "interior" ? "+1" : "") + + ";}static void release(void *p){global_hooks.deallocate(p);}" + "int client(void){reset(0);struct node *v=create();if(!v)return 0;" + "char *t=publish(v);destroy(v);" + + std::string(variant == "good" || variant == "interior" + ? "if(t)release(t);" + : variant == "unguarded" ? "release(t);" + : variant == "twice" ? "if(t){release(t);release(t);}" + : "") + + "return 0;}"; + EXPECT_EQ(forestCheck(body).complete(), + variant == "good" || variant == "unguarded") + << variant; + } +} + TEST(RecursiveContainerAnalysis, EveryCallbackTargetMustConsumeTheWholeInput) { const std::string helpers = R"c( static void first(struct node *p) { destroy(p); } diff --git a/unittests/Analysis/RecursiveContractsTest.cpp b/unittests/Analysis/RecursiveContractsTest.cpp new file mode 100644 index 00000000..e6399a56 --- /dev/null +++ b/unittests/Analysis/RecursiveContractsTest.cpp @@ -0,0 +1,1946 @@ +//===- RecursiveContractsTest.cpp - Composed proofs (RFC 0029) -----------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "TestUtils.h" + +#include + +#include +#include +#include +#include +#include + +namespace weavec::analysis { + +static core::CheckedContract workflowContract(const std::string &source, + const std::string &name) { + AnalysisOptions options; + options.checkedFunctions.insert(name); + const auto unit = test::analyze(source, options); + if (!unit.summary(name)) { + ADD_FAILURE() << "missing workflow contract: " << name; + return {}; + } + return unit.summary(name)->checked; +} + +TEST(RecursiveContracts, ConcreteBytesKeepCompleteInductiveWriterAvailable) { + const auto result = test::analyze(R"c( + struct writer { unsigned flags; unsigned char *data; size_t used, capacity; }; + int emit(const unsigned char *input,size_t n,struct writer *w) { + if(!n)return 1; + if(w->used>=w->capacity)return 0; + w->data[w->used]=input[0];w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; + } + int main(void) { + unsigned char input[]={1,2,3},output[3]; + struct writer w={7,output,0,3}; + (void)emit(input,3,&w); + if(w.used)return w.data[w.used-1]; + return 0; + } + )c", + {.checkedFunctions = {"emit", "main"}}); + ASSERT_TRUE(result.ast); + ASSERT_NE(result.summary("emit"), nullptr); + ASSERT_NE(result.summary("main"), nullptr); + EXPECT_TRUE(result.summary("emit")->checked.complete()); + EXPECT_TRUE(result.summary("main")->checked.complete()); + EXPECT_TRUE(test::ids(result.diagnostics).empty()); +} + +TEST(RecursiveContracts, InPlaceExtensionAccountsForSuccessAndFailure) { + for (const std::string variant : + {"recursive", "lost-child", "duplicate-child", "nondecreasing", + "failed-cleanup"}) { + SCOPED_TRACE(variant); + const std::string source = + std::string(R"c( + void *calloc(size_t, size_t); void free(void *); + struct node { struct node *next, *child; }; + void drop(struct node *n) { + while(n) { struct node *next=n->next; drop(n->child); free(n); n=next; } + } + int grow(struct node *n, unsigned depth) { + if(!depth)return 1; + struct node *c=calloc(1,sizeof *c); + if(!c)return 0; + if(!grow(c, )c") + + (variant == "nondecreasing" ? "depth" : "depth-1") + ")) {" + + (variant == "failed-cleanup" ? "" : "drop(c);") + "return 0;}" + + (variant == "lost-child" ? "" : "n->child=c;") + + (variant == "duplicate-child" ? "n->next=c;" : "") + + R"c( + return 1; + } + int client(unsigned depth) { + struct node *n=calloc(1,sizeof *n); if(!n)return 0; + (void)grow(n,depth); drop(n); return 0; + } + )c"; + const bool safe = variant == "recursive"; + const auto constructor = workflowContract(source, "grow"); + EXPECT_EQ(constructor.complete(), safe); + EXPECT_EQ(std::ranges::any_of( + constructor.establishes, + [](const auto &post) { + return post.kind == + core::CheckedRequirementKind::ContainerExtended; + }), + safe); + const auto client = workflowContract(source, "client"); + EXPECT_EQ(client.complete(), safe); + EXPECT_TRUE(client.requirements.empty()); + } +} + +TEST(RecursiveContracts, LocalAutomaticWritesFrameOnlyUnchangedEntryForests) { + for (const std::string write : + {"memset(state, 0, sizeof(state));", "memset(p, 0xa5, sizeof(*p));", + "memset(p->left, 0xa5, sizeof(*p));", + "struct node local = {3, 0, 0}; p->left = &local; " + "memset(&local, 0xa5, sizeof(local));"}) { + SCOPED_TRACE(write); + const std::string source = R"c( + void *memset(void *, int, size_t); + struct node { unsigned value; struct node *left, *right; }; + struct state { unsigned depth, mode; }; + unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); + } + unsigned inspect(struct node *p) { + struct state state[1]; + )c" + write + R"c( + state->depth = 1; + return walk(p) + state->depth; + } + int client(void) { + struct node child = {2, 0, 0}, root = {1, &child, 0}; + return inspect(&root) != 4; + } + )c"; + const bool safe = write == "memset(state, 0, sizeof(state));"; + EXPECT_EQ(workflowContract(source, "inspect").complete(), safe); + const auto client = workflowContract(source, "client"); + EXPECT_EQ(client.complete(), safe); + EXPECT_TRUE(client.requirements.empty()); + } +} + +TEST(RecursiveContracts, OrdinaryAllocationsShareTheForestConservationLedger) { + for (const auto &[action, safe] : std::vector>{ + {"return data;", true}, + {"return data + 1;", false}, + {"return 0;", false}, + {"free(data); free(data); return 0;", false}, + {"unsigned char *out=realloc(data,2); " + "if(!out){free(data);return 0;} return out;", + true}, + {"unsigned char *out=realloc(data,2); if(!out)return 0; return out;", + false}, + {"unsigned char *out=realloc(data,2); " + "if(!out){free(data);return 0;} free(data); return out;", + false}, + {"data=realloc(data,2); if(!data)return 0; return data;", false}, + {"free(data); data=0; unsigned char *out=realloc(data,2); " + "if(!out)return 0; return out;", + true}}) { + SCOPED_TRACE(action); + const std::string source = R"c( + void *malloc(size_t); void *realloc(void *, size_t); void free(void *); + struct node { unsigned value; struct node *left, *right; }; + unsigned walk(const struct node *p) { + if (!p) return 0; + return p->value + walk(p->left) + walk(p->right); + } + unsigned char *make(const struct node *p) { + (void)walk(p); + unsigned char *data=malloc(4); + if(!data)return 0; + data[0]=0; + )c" + action + R"c( + } + int client(void) { + struct node child={2,0,0}, root={1,&child,0}; + unsigned char *data=make(&root);free(data);return 0; + } + )c"; + EXPECT_EQ(workflowContract(source, "make").complete(), safe); + const auto client = workflowContract(source, "client"); + EXPECT_EQ(client.complete(), safe); + EXPECT_TRUE(client.requirements.empty()); + } +} + +TEST(RecursiveContracts, MutatingFieldCursorCannotProjectOnlyItsEntryCell) { + const std::string helper = R"c( + struct reader { const unsigned char *data; size_t position, end; }; + unsigned consume(struct reader *r) { + unsigned sum = 0; + while (r->position < r->end) { + sum += r->data[r->position]; + r->position++; + } + return sum; + } + )c"; + EXPECT_FALSE(workflowContract(helper + R"c( + int client(void) { + unsigned char data[] = {1, 2}; + struct reader r = {data, 0, 4}; + return (int)consume(&r); + } + )c", + "client") + .complete()); + EXPECT_FALSE(workflowContract(helper + R"c( + int client(void) { + unsigned char data[4]; data[0] = 1; + struct reader r = {data, 0, 4}; + return (int)consume(&r); + } + )c", + "client") + .complete()); +} + +TEST(RecursiveContracts, DiscoveredBufferCanRequireItsUnchangedEntryBacking) { + const std::string helper = R"c( + size_t strlen(const char *); + struct output { char *data; size_t capacity, cursor, depth; }; + int append(struct output *p, char c) { + if (p->cursor >= p->capacity) return 0; + p->data[p->cursor++] = c; return 1; + } + void update(struct output *p) { + if (!p || !p->data) return; + const char *old = p->data + p->cursor; + p->cursor += strlen(old); + } + )c"; + EXPECT_TRUE(workflowContract(helper, "update").complete()); + const auto source = helper + R"c( + void invalid(struct output *p) { + char *entry = p->data; (void)entry; + p->data = 0; p->capacity = p->cursor = 0; + const char *q = p->data + p->cursor; (void)q; + } + )c"; + EXPECT_FALSE(workflowContract(source, "invalid").complete()); +} + +static const std::string OutputConstructor = R"c( + void *calloc(size_t, size_t); + struct node { unsigned char value; struct node *next; }; + static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } + int build(const unsigned char *data, size_t n, struct node **out) { + *out=0; + if(!n)return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=data[0]; + if(n>1 && !build(data+1,n-1,&p->next)){free(p);return 0;} + *out=p; return 1; + } +)c"; + +TEST(RecursiveContracts, OutputConstructionPublishesTheCompleteForest) { + const auto contract = workflowContract(OutputConstructor, "build"); + EXPECT_TRUE(contract.complete()); + EXPECT_TRUE(std::ranges::any_of(contract.establishes, [](const auto &post) { + return post.kind == core::CheckedRequirementKind::ContainerFresh && + post.path == core::SummaryPath::param(2).deref() && + post.on == core::Outcome::Positive; + })); + EXPECT_TRUE(workflowContract(OutputConstructor + R"c( + int client(void) { + unsigned char data[]={1,2,3}; struct node *p=0; + if(build(data,3,&p))destroy(p); return 0; + } + )c", + "client") + .complete()); +} + +TEST(RecursiveContracts, OutputConstructionNeedsActualFailureAndSuccessFacts) { + for (const auto &removed : {"*out=0;", "*out=p;", "free(p);return 0;"}) { + auto bad = OutputConstructor; + const auto at = bad.find(removed); + ASSERT_NE(at, std::string::npos); + bad.replace(at, std::string(removed).size(), + std::string(removed).starts_with("free") ? "return 0;" : ""); + EXPECT_FALSE(workflowContract(bad, "build").complete()) << removed; + } +} + +TEST(RecursiveContracts, OutputConstructionCannotLoseOrDuplicateAnOwnedTree) { + for (const auto &cleanup : {"free(p);", "destroy(p); destroy(p);"}) { + EXPECT_FALSE(workflowContract(OutputConstructor + R"c( + int client(void) { + unsigned char data[]={1,2,3}; struct node *p=0; + if(build(data,3,&p)){)c" + cleanup + + R"c(} return 0; + } + )c", + "client") + .complete()) + << cleanup; + } +} + +TEST(RecursiveContracts, OutputConstructionDoesNotAcquireThePreviousSlot) { + EXPECT_FALSE(workflowContract(OutputConstructor + R"c( + int client(void) { + unsigned char data[]={1,2,3}; struct node *p=0; + if(!build(data,3,&p))return 0; + if(build(data,3,&p))destroy(p); return 0; + } + )c", + "client") + .complete()); + EXPECT_FALSE(workflowContract(OutputConstructor + R"c( + int client(void) { + unsigned char data[]={1,2,3}; struct node *p=0; + int made=build(data,3,&p); free(p); + if(made)destroy(p); return 0; + } + )c", + "client") + .complete()); + auto failed = OutputConstructor; + failed.replace(failed.find("*out=p; return 1;"), + std::string("*out=p; return 1;").size(), "*out=p; return 0;"); + EXPECT_FALSE(workflowContract(failed, "build").complete()); +} + +TEST(RecursiveContracts, OutputConstructionSupportsMutualProgress) { + auto source = OutputConstructor; + const auto *const declaration = + "int forward(const unsigned char *, size_t, struct node **);\n"; + source.insert(source.find("int build("), declaration); + source.replace(source.find("!build(data+1,n-1,&p->next)"), + std::string("!build(data+1,n-1,&p->next)").size(), + "!forward(data+1,n-1,&p->next)"); + source += R"c( + int forward(const unsigned char *data, size_t n, struct node **out) { + if(build(data,n,out))return 1; return 0; + } + )c"; + EXPECT_TRUE(workflowContract(source, "build").complete()); + EXPECT_TRUE(workflowContract(source, "forward").complete()); +} + +TEST(RecursiveContracts, AddressedMemberRetainsItsOwnSubobjectBounds) { + const auto source = [](const std::string &index) { + return "struct pair { unsigned first, second, third; }; " + "static void set(unsigned *p) { p[" + + index + + "] = 7; } " + "int client(void) { struct pair p={0}; set(&p.second); return 0; }"; + }; + EXPECT_TRUE(workflowContract(source("0"), "client").complete()); + EXPECT_FALSE(workflowContract(source("1"), "client").complete()); + EXPECT_FALSE(workflowContract(source("-1"), "client").complete()); +} + +static const std::string ReaderConstructor = R"c( + void *calloc(size_t, size_t); + struct reader { size_t depth; const unsigned char *data; size_t remaining; }; + struct node { unsigned char value; struct node *next; }; + static void destroy(struct node *p) { if(p){destroy(p->next);free(p);} } + struct node *build(struct reader r) { + if(!r.remaining)return 0; + struct node *p=calloc(1,sizeof *p); if(!p)return 0; + p->value=r.data[0]; + if(r.remaining>1){ + r.data++; r.remaining--; + p->next=build(r); + if(!p->next){free(p);return 0;} + } + return p; + } +)c"; + +TEST(RecursiveContracts, CopiedReaderConstructionUsesTheCompleteInput) { + EXPECT_TRUE(workflowContract(ReaderConstructor, "build").complete()); + for (const auto &count : {"3", "4"}) { + const auto source = ReaderConstructor + R"c( + int client(void) { + unsigned char data[]={1,2,3}; struct reader r={42,data,)c" + + count + R"c(}; + struct node *p=build(r); destroy(p); return r.remaining!=3; + } + )c"; + EXPECT_EQ(workflowContract(source, "client").complete(), + std::string_view(count) == "3"); + } +} + +TEST(RecursiveContracts, CopiedReaderConstructionChecksProgressAndCleanup) { + for (const auto &[before, after] : + {std::pair{"r.remaining--;", ""}, std::pair{"r.data++;", "r.data+=2;"}, + std::pair{"free(p);return 0;", "return 0;"}, + std::pair{"free(p);return 0;", "free(p);free(p);return 0;"}}) { + auto source = ReaderConstructor; + source.replace(source.find(before), std::string(before).size(), after); + EXPECT_FALSE(workflowContract(source, "build").complete()) << before; + } +} + +static std::string mutableReaderConstructor() { + auto source = ReaderConstructor; + source.replace(source.find("build(struct reader r)"), + std::string("build(struct reader r)").size(), + "build(struct reader *r)"); + for (const auto *field : {"data", "remaining"}) { + const auto before = std::string("r.") + field; + for (auto at = source.find(before); at != std::string::npos; + at = source.find(before)) + source.replace(at, before.size(), std::string("r->") + field); + } + return source; +} + +TEST(RecursiveContracts, MutableReaderConstructionAccountsForChangedInputs) { + const auto source = mutableReaderConstructor(); + EXPECT_TRUE(workflowContract(source, "build").complete()); + EXPECT_TRUE(workflowContract(source + R"c( + int client(void) { + unsigned char data[]={1,2,3}; struct reader r={42,data,3}; + struct node *p=build(&r); destroy(p); return 0; + } + )c", + "client") + .complete()); + EXPECT_FALSE(workflowContract(source + R"c( + int client(void) { + unsigned char data[]={1,2,3}; struct reader r={42,data,4}; + struct node *p=build(&r); destroy(p); return 0; + } + )c", + "client") + .complete()); + EXPECT_FALSE(workflowContract(source + R"c( + int client(void) { + struct reader r={42,0,3}; r.data=(const unsigned char *)&r; + struct node *p=build(&r); destroy(p); return 0; + } + )c", + "client") + .complete()); +} + +TEST(RecursiveContracts, MutableReaderConstructionChecksEveryRecursiveExit) { + for (const auto &[before, after] : + {std::pair{"r->remaining--;", ""}, + std::pair{"r->data++;", "r->data+=2;"}, + std::pair{"free(p);return 0;", "return 0;"}, + std::pair{"free(p);return 0;", "free(p);free(p);return 0;"}}) { + auto source = mutableReaderConstructor(); + source.replace(source.find(before), std::string(before).size(), after); + EXPECT_FALSE(workflowContract(source, "build").complete()) << before; + } +} + +TEST(RecursiveContracts, ReaderConstructionSupportsMutualForwarding) { + for (const bool pointer : {false, true}) { + auto source = pointer ? mutableReaderConstructor() : ReaderConstructor; + const std::string parameter = + pointer ? "struct reader *r" : "struct reader r"; + source.insert(source.find("struct node *build("), + "struct node *forward(" + parameter + ");\n"); + source.replace(source.find("p->next=build(r)"), + std::string("p->next=build(r)").size(), + "p->next=forward(r)"); + source += "struct node *forward(" + parameter + ") { return build(r); }"; + EXPECT_TRUE(workflowContract(source, "build").complete()) << pointer; + EXPECT_TRUE(workflowContract(source, "forward").complete()) << pointer; + } +} + +TEST(RecursiveContracts, ReaderWritesCannotFrameBorrowedOrReplacedForests) { + const auto source = mutableReaderConstructor(); + EXPECT_FALSE(workflowContract(source + R"c( + int client(void) { + unsigned char data[]={1,2,3}; struct reader r={42,data,3}; + struct node *p=build(&r); r.data=0; + struct node *q=build(&r); destroy(p); destroy(q); return 0; + } + )c", + "client") + .complete()); + EXPECT_FALSE(workflowContract(source + R"c( + int client(void) { + unsigned char data[]={1,2,3}; struct reader r={42,data,3}; + struct node *p=build(&r); if(!p)return 0; + struct reader *alias=(struct reader *)p; + alias->data=0; destroy(p); return 0; + } + )c", + "client") + .complete()); +} + +TEST(RecursiveContracts, NumericInputModelsOnlyMemoryAndEndProvenance) { + for (const auto &[type, name] : + {std::pair{"double", "strtod"}, std::pair{"float", "strtof"}, + std::pair{"long double", "strtold"}}) { + const auto declaration = + std::string(type) + " " + name + "(const char *,char **);\n"; + EXPECT_TRUE(workflowContract(declaration + R"c( + int client(void) { + char data[]="12.5x"; char *end=0; + )c" + "(void)" + name + R"c((data,&end);return *end==0; + } + )c", + "client") + .complete()) + << name; + EXPECT_FALSE(workflowContract(declaration + R"c( + int client(void) { + char data[]="nan"; return (int))c" + + name + R"c((data,0); + } + )c", + "client") + .complete()) + << name; + } +} + +TEST(RecursiveContracts, NumericInputNeedsInitializationAndWritableEndSlot) { + const std::string declaration = "double strtod(const char *, char **);\n"; + for (const auto &source : {"int client(void){char a[3];a[0]='1';a[2]=0;" + "(void)strtod(a,0);return 0;}", + "int client(void){char a[2]={'1','2'};" + "(void)strtod(a,0);return 0;}", + "int client(void){char a[]=\"12\";char *const e=0;" + "(void)strtod(a,(char **)&e);return 0;}", + "int client(void){char a[]=\"12\";char *e=0;" + "(void)strtod(a,&e);return e[3];}"}) + EXPECT_FALSE(workflowContract(declaration + source, "client").complete()); + EXPECT_FALSE(workflowContract(R"c( + double strtod(const char *text, char **end) { + *end=(char *)text+20;return 0; + } + int client(void) {char a[]="12";char *end=0; + (void)strtod(a,&end);return *end;} + )c", + "client") + .complete()); +} + +TEST(RecursiveContracts, CharacterPointerSlotsKeepMutabilityAndProvenance) { + const std::string declaration = "double strtod(const char *, char **);\n"; + EXPECT_TRUE(workflowContract(declaration + R"c( + int client(void) { + unsigned char data[]="12.5x"; unsigned char *end=0; + (void)strtod((const char *)data,(char **)&end); return *end==0; + } + )c", + "client") + .complete()); + for (const auto &source : + {"int client(void){unsigned char a[]=\"12\";" + "unsigned char *const e=0;" + "(void)strtod((const char *)a,(char **)&e);return 0;}", + "int client(void){unsigned char a[]=\"12\";unsigned char *e=0;" + "(void)strtod((const char *)a,(char **)&e);return e[3];}", + "int client(void){unsigned char *a=malloc(2);if(!a)return 0;" + "a[0]='1';a[1]=0;unsigned char *e=0;" + "(void)strtod((const char *)a,(char **)&e);free(a);return *e;}"}) + EXPECT_FALSE(workflowContract(declaration + source, "client").complete()); + EXPECT_FALSE(workflowContract(R"c( + void set(float **out,float *value) { *out=value; } + int client(void) {float a=0;int *p=0;set((float **)&p,&a);return *p;} + )c", + "client") + .complete()); +} + +TEST(RecursiveContracts, NumericEndPointerDoesNotOutliveItsInput) { + EXPECT_FALSE(workflowContract(R"c( + double strtod(const char *, char **); + int client(void) { + char *data=malloc(2);if(!data)return 0; + data[0]='1';data[1]=0;char *end=0; + (void)strtod(data,&end);free(data);return *end; + } + )c", + "client") + .complete()); +} + +TEST(RecursiveContracts, FiniteFloatingConversionsUseTargetBounds) { + for (const auto &source : + {"int f(double x) { if(x >= -2147483648.0 && x <= 2147483647.0) " + "return (int)x; return 0; }", + "int f(double x) { if(-129.0 < x) { if(128.0 > x) return " + "(signed char)x; } return 0; }", + "unsigned f(double x) { if(x > -1.0 && x < 4294967296.0) " + "return (unsigned)x; return 0; }", + "long long f(double x) { if(x >= -9223372036854775808.0 && " + "x < 9223372036854775808.0) return (long long)x; return 0; }", + "int f(double x) { if(x == 12.5) return (int)x; return 0; }", + "int f(void) { return (int)12.75; }"}) + EXPECT_TRUE(workflowContract(source, "f").complete()) << source; +} + +TEST(RecursiveContracts, FloatingConversionsRejectUnorderedAndChangedValues) { + for (const auto &source : + {"int f(double x) { return (int)x; }", + "int f(double x) { if(x < -2147483648.0 || x > 2147483647.0) " + "return 0; return (int)x; }", + "int f(double x) { if(x >= -2147483648.0 || x <= 2147483647.0) " + "return (int)x; return 0; }", + "long long f(double x) { if(x >= -9223372036854775808.0 && " + "x <= 9223372036854775808.0) return (long long)x; return 0; }", + "int f(double x) { if(x >= 0 && x <= 2147483647.0) { x += 1; " + "return (int)x; } return 0; }", + "int f(double x) { if(x >= 0 && x <= 2147483647.0) return " + "(int)(float)x; return 0; }", + "int f(double x) { double *p = &x; if(x >= 0 && x <= 127) { " + "*p = 1000; return (signed char)x; } return 0; }", + "#pragma STDC FENV_ACCESS ON\n" + "int f(double x) { if(x >= 0 && x <= 127) return (int)x; " + "return 0; }", + "int f(void) { return (int)__builtin_inf(); }", + "int f(void) { return (int)__builtin_nan(\"\"); }", + "int f(double x) { goto inside; if(x >= 0 && x <= 127) { " + "inside: return (signed char)x; } return 0; }", + "int f(double x, int k) { switch(k) { if(x >= 0 && x <= 127) { " + "case 1: return (signed char)x; } } return 0; }"}) + EXPECT_FALSE(workflowContract(source, "f").complete()) << source; +} + +TEST(RecursiveContracts, LaterReassignmentPreservesAnEarlierEntryRelease) { + const auto contract = workflowContract(R"c( + void destroy(void *p) { free(p); p=0; } + int client(void) { + void *p=malloc(8); if(!p)return 0; destroy(p); return 0; + } + )c", + "client"); + EXPECT_TRUE(contract.complete()); + EXPECT_TRUE(contract.requirements.empty()); + const auto helper = + workflowContract("void destroy(void *p) { free(p); p=0; }", "destroy"); + EXPECT_TRUE(helper.complete()); + EXPECT_TRUE(std::ranges::any_of(helper.establishes, [](const auto &post) { + return post.kind == core::CheckedRequirementKind::AllocationConsumed; + })); +} + +TEST(RecursiveContracts, ReplacementCannotConsumeOrValidateTheOldInput) { + EXPECT_FALSE(workflowContract(R"c( + void destroy(void *p) { p=malloc(8); free(p); } + int client(void) { + void *p=malloc(8); if(!p)return 0; destroy(p); return 0; + } + )c", + "client") + .complete()); + EXPECT_FALSE(workflowContract(R"c( + void destroy(void *p) { free(p); p=0; } + int client(void) { + char *p=malloc(8); if(!p)return 0; destroy(p); return *p; + } + )c", + "client") + .complete()); +} + +static const std::string MutualTraversal = R"c( + struct tree { unsigned value; struct tree *left, *right; }; + unsigned odd(const struct tree *p); + unsigned even(const struct tree *p) { + if (!p) return 0; + return p->value + odd(p->left) + odd(p->right); + } + unsigned odd(const struct tree *p) { + if (!p) return 0; + return p->value + even(p->left) + even(p->right); + } + int client(void) { + struct tree b = {2, 0, 0}, a = {1, &b, 0}; + return even(&a) != 3; + } +)c"; + +TEST(RecursiveContracts, TraversalPreservesTheBorrowedInputFootprint) { + for (const auto *name : {"even", "odd", "client"}) { + const auto contract = workflowContract(MutualTraversal, name); + EXPECT_TRUE(contract.complete()) << name; + if (std::string_view(name) == "client") { + EXPECT_TRUE(contract.requirements.empty()); + } else { + EXPECT_TRUE( + std::ranges::any_of(contract.establishes, [](const auto &post) { + return post.kind == + core::CheckedRequirementKind::ContainerPreserved && + post.path == core::SummaryPath::param(0) && !post.on; + })); + } + } +} + +TEST(RecursiveContracts, TraversalForwardingRequiresAProgressingCycle) { + auto source = MutualTraversal; + source.replace(source.find("even(p->left)"), + std::string("even(p->left)").size(), "even(p)"); + EXPECT_TRUE(workflowContract(source, "even").complete()); + source.replace(source.find("odd(p->left)"), + std::string("odd(p->left)").size(), "odd(p)"); + for (const auto *name : {"even", "odd"}) + EXPECT_FALSE(workflowContract(source, name).complete()); +} + +TEST(RecursiveContracts, TraversalHypothesesCannotHideLocalErrors) { + for (const auto *replacement : + {"even(p->left + 1)", "even(p->left) + *(unsigned *)0"}) { + auto source = MutualTraversal; + source.replace(source.find("even(p->left)"), + std::string("even(p->left)").size(), replacement); + EXPECT_FALSE(workflowContract(source, "even").complete()) << replacement; + } + auto source = MutualTraversal; + source.replace(source.find("struct tree b = {2, 0, 0}"), + std::string("struct tree b = {2, 0, 0}").size(), + "struct tree b"); + EXPECT_FALSE(workflowContract(source, "client").complete()); +} + +TEST(RecursiveContracts, DirectTraversalUsesTheSameGroupProof) { + const auto contract = workflowContract(R"c( + struct tree { unsigned value; struct tree *left, *right; }; + unsigned sum(const struct tree *p) { + if (!p) return 0; + return p->value + sum(p->left) + sum(p->right); + } + )c", + "sum"); + EXPECT_TRUE(contract.complete()); + EXPECT_TRUE(std::ranges::any_of(contract.establishes, [](const auto &post) { + return post.kind == core::CheckedRequirementKind::ContainerPreserved; + })); +} + +TEST(RecursiveContracts, PrivateProofMembersCannotPublishNestedContexts) { + AnalysisOptions options; + options.checkedFunctions.insert("even"); + const auto unit = test::analyze(MutualTraversal, options); + const auto *function = unit.function("even"); + ASSERT_NE(function, nullptr); + auto &store = unit.analyzer->summaries(); + const auto callbacks = store.specialized.size(); + const auto memories = store.memorySpecialized.size(); + store.activeRecursiveContracts = {.members = {function->getCanonicalDecl()}, + .releases = false}; + EXPECT_FALSE(store.specialize(*function, {}, options, nullptr)); + EXPECT_FALSE( + store.specializeMemory(callableSymbol(*function), {}, options, nullptr)); + EXPECT_EQ(store.specialized.size(), callbacks); + EXPECT_EQ(store.memorySpecialized.size(), memories); + store.activeRecursiveContracts = {}; +} + +static const std::string RecursiveConstruction = R"c( + void *calloc(size_t, size_t); + struct node { unsigned char value; struct node *next; }; + static void destroy(struct node *p) { + if (p) { destroy(p->next); free(p); } + } + struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p = calloc(1, sizeof *p); + if (!p) return 0; + p->value = data[0]; + if (n > 1) { + p->next = build(data + 1, n - 1); + if (!p->next) { free(p); return 0; } + } + return p; + } + int client(void) { + const unsigned char data[] = {1, 2, 3}; + struct node *p = build(data, 3); + destroy(p); + return 0; + } +)c"; + +TEST(RecursiveContracts, ConstructionProvesFreshOutputsAndFailureCleanup) { + const auto contract = workflowContract(RecursiveConstruction, "build"); + EXPECT_TRUE(contract.complete()); + EXPECT_TRUE(std::ranges::any_of(contract.establishes, [](const auto &post) { + return post.kind == core::CheckedRequirementKind::ContainerFresh && + post.path == core::SummaryPath::result(); + })); + const auto client = workflowContract(RecursiveConstruction, "client"); + EXPECT_TRUE(client.complete()); + EXPECT_TRUE(client.requirements.empty()); +} + +TEST(RecursiveContracts, ConstructionNeedsTheWholeInitializedInput) { + auto source = RecursiveConstruction; + source.replace(source.find("build(data, 3)"), + std::string("build(data, 3)").size(), "build(data, 4)"); + EXPECT_FALSE(workflowContract(source, "client").complete()); + source = RecursiveConstruction; + source.replace(source.find("const unsigned char data[] = {1, 2, 3};"), + std::string("const unsigned char data[] = {1, 2, 3};").size(), + "unsigned char data[3]; data[0] = 1;"); + EXPECT_FALSE(workflowContract(source, "client").complete()); +} + +TEST(RecursiveContracts, ConstructionRejectsLeaksAndInvalidReturnedForests) { + for (const auto *replacement : {"return 0;", "free(p); free(p); return 0;"}) { + auto source = RecursiveConstruction; + source.replace(source.find("free(p); return 0;"), + std::string("free(p); return 0;").size(), replacement); + EXPECT_FALSE(workflowContract(source, "build").complete()) << replacement; + } + auto source = RecursiveConstruction; + source.replace(source.find("return p;"), std::string("return p;").size(), + "free(p); return (struct node *)data;"); + EXPECT_FALSE(workflowContract(source, "build").complete()); + source = RecursiveConstruction; + source.replace(source.find("calloc(1, sizeof *p)"), + std::string("calloc(1, sizeof *p)").size(), + "malloc(sizeof *p)"); + EXPECT_FALSE(workflowContract(source, "build").complete()); + source = RecursiveConstruction; + source.replace(source.find("if (!n) return 0;"), + std::string("if (!n) return 0;").size(), "(void)n;"); + EXPECT_FALSE(workflowContract(source, "build").complete()); +} + +TEST(RecursiveContracts, ConstructionProgressUsesTheImmutableEntryCount) { + for (const auto *change : + {"if (n < 16) ++n;", "size_t *alias = &n; if (*alias < 16) ++*alias;"}) { + auto source = RecursiveConstruction; + source.insert(source.find("if (n > 1)"), change); + EXPECT_FALSE(workflowContract(source, "build").complete()) << change; + } + auto source = RecursiveConstruction; + source.replace(source.find("build(data + 1, n - 1)"), + std::string("build(data + 1, n - 1)").size(), + "build(data, n)"); + EXPECT_FALSE(workflowContract(source, "build").complete()); +} + +TEST(RecursiveContracts, MutualConstructionPublishesTheWholeGroup) { + auto source = RecursiveConstruction; + source.insert(source.find("struct node *build("), R"c( + struct node *build(const unsigned char *, size_t); + struct node *forward(const unsigned char *data, size_t n) { + return build(data, n); + } + )c"); + source.replace(source.find("build(data + 1, n - 1)"), + std::string("build(data + 1, n - 1)").size(), + "forward(data + 1, n - 1)"); + for (const auto *name : {"build", "forward", "client"}) + EXPECT_TRUE(workflowContract(source, name).complete()) << name; + source.replace(source.find("forward(data + 1, n - 1)"), + std::string("forward(data + 1, n - 1)").size(), + "forward(data, n)"); + for (const auto *name : {"build", "forward", "client"}) + EXPECT_FALSE(workflowContract(source, name).complete()) << name; +} + +static const std::string TreeConstruction = R"c( + void *calloc(size_t, size_t); + struct node { unsigned char value; struct node *left, *right; }; + static void destroy(struct node *p) { + if (p) { destroy(p->left); destroy(p->right); free(p); } + } + static unsigned char first(const unsigned char *data) { return *data; } + struct node *build(const unsigned char *data, size_t n) { + if (!n) return 0; + struct node *p = calloc(1, sizeof *p); + if (!p) return 0; + p->value = first(data); + if (n > 1) { + p->left = build(data + 1, n - 1); + if (!p->left) { free(p); return 0; } + p->right = build(data + 1, n - 1); + if (!p->right) { destroy(p->left); free(p); return 0; } + } + return p; + } + int client(void) { + const unsigned char data[] = {1, 2, 3}; + struct node *p = build(data, 3); destroy(p); return 0; + } +)c"; + +TEST(RecursiveContracts, ConstructionComposesVerifiedPartialCleanup) { + for (const auto *name : {"build", "client"}) + EXPECT_TRUE(workflowContract(TreeConstruction, name).complete()) << name; + for (const auto *replacement : {"", "destroy(p->left); destroy(p->left);"}) { + auto source = TreeConstruction; + source.replace(source.find("destroy(p->left); free(p); return 0;"), + std::string("destroy(p->left);").size(), replacement); + EXPECT_FALSE(workflowContract(source, "build").complete()) << replacement; + } +} + +TEST(RecursiveContracts, ConstructionCannotDuplicateAnAttachedSubtree) { + auto source = TreeConstruction; + source.replace(source.find("p->right = build(data + 1, n - 1)"), + std::string("p->right = build(data + 1, n - 1)").size(), + "p->right = p->left"); + EXPECT_FALSE(workflowContract(source, "build").complete()); + EXPECT_FALSE(workflowContract(source, "client").complete()); +} + +static const std::string MutualCleanup = R"c( + void *calloc(size_t, size_t); + struct tree { unsigned value; struct tree *left, *right; }; + static void odd(struct tree *p); + static void even(struct tree *p) { + if(!p)return; odd(p->left); odd(p->right); free(p); + } + static void odd(struct tree *p) { + if(!p)return; even(p->left); even(p->right); free(p); + } + int client(void) { + struct tree *a=calloc(1,sizeof *a); if(!a)return 0; + struct tree *b=calloc(1,sizeof *b); if(!b){free(a);return 0;} + a->right=b; even(a); return 0; + } +)c"; + +TEST(RecursiveContracts, MutualCleanupPublishesOnlyVerifiedGroupOutputs) { + for (const auto *name : {"even", "odd", "client"}) { + const auto contract = workflowContract(MutualCleanup, name); + EXPECT_TRUE(contract.complete()) << name; + if (std::string_view(name) == "client") + EXPECT_TRUE(contract.requirements.empty()); + else + EXPECT_TRUE( + std::ranges::any_of(contract.establishes, [](const auto &post) { + return post.kind == core::CheckedRequirementKind::ContainerConsumed; + })); + } +} + +TEST(RecursiveContracts, FailedMemberCannotAuthorizeItsPeers) { + auto source = MutualCleanup; + const auto position = source.find("even(p->right);"); + ASSERT_NE(position, std::string::npos); + source.erase(position, std::string("even(p->right);").size()); + EXPECT_FALSE(workflowContract(source, "client").complete()); + source = MutualCleanup; + source.replace(source.find("even(p->left)"), + std::string("even(p->left)").size(), "even(p)"); + EXPECT_FALSE(workflowContract(source, "odd").complete()); +} + +TEST(RecursiveContracts, RecursiveGlobalEffectsCannotDisappear) { + auto source = "static void *extra;" + MutualCleanup; + source.replace(source.find("even(p->right);"), + std::string("even(p->right);").size(), + "even(p->right); free(extra);"); + EXPECT_FALSE(workflowContract(source, "odd").complete()); +} + +TEST(RecursiveContracts, SharedAndCyclicChildrenCannotBecomeOwnedForests) { + for (const auto *attachment : + {"a->right=b; a->left=b;", "a->right=a; free(b);", + "a->right=b; b->left=a;"}) { + auto source = MutualCleanup; + source.replace(source.find("a->right=b;"), + std::string("a->right=b;").size(), attachment); + EXPECT_FALSE(workflowContract(source, "client").complete()) << attachment; + } +} + +TEST(RecursiveContracts, CallbackAllocatorDemandIsExplicitAndForwardable) { + const std::string source = R"c( + static void *make(void *(*allocate)(size_t), size_t n) { + if (!n) return 0; + unsigned char *p=allocate(n); if(p)p[n-1]=7; return p; + } + void *forward(void *(*allocate)(size_t), size_t n) { + return make(allocate,n); + } + )c"; + for (const auto *name : {"make", "forward"}) { + const auto contract = workflowContract(source, name); + EXPECT_TRUE(contract.complete()) << name; + EXPECT_TRUE(std::ranges::any_of(contract.requirements, [](const auto &pre) { + return pre.kind == core::CheckedRequirementKind::CallbackAllocate && + pre.path == core::SummaryPath::param(0); + })); + EXPECT_FALSE(contract.obligations.trusted()); + } +} + +TEST(RecursiveContracts, CallbackReleaseDemandConsumesTheIncomingAllocation) { + const std::string source = R"c( + static void destroy(void (*release)(void *), void *p) { release(p); } + static void forward(void (*release)(void *), void *p) { destroy(release,p); } + int client(void) { void *p=malloc(8); forward(free,p); return 0; } + )c"; + for (const auto *name : {"destroy", "forward", "client"}) { + const auto contract = workflowContract(source, name); + EXPECT_TRUE(contract.complete()) << name; + if (std::string_view(name) == "client") + EXPECT_TRUE(contract.requirements.empty()); + else + EXPECT_TRUE( + std::ranges::any_of(contract.requirements, [](const auto &pre) { + return pre.kind == core::CheckedRequirementKind::CallbackRelease; + })); + } +} + +TEST(RecursiveContracts, CallbackBadBindingsRemainUnproved) { + const std::string helper = R"c( + static void *make(void *(*allocate)(size_t), size_t n) { + if (!n) return 0; + unsigned char *p=allocate(n); if(p)p[n-1]=7; return p; + } + static void *short_one(size_t n) { return malloc(1); } + )c"; + for (const auto *body : + {"void *p=make(short_one,8); free(p);", "void *p=make(0,8); free(p);", + "void *p=make(flag?malloc:short_one,8); free(p);", + "void *p=make(flag?malloc:0,8); free(p);"}) { + const auto source = helper + "int client(int flag){" + body + "return 0;}"; + EXPECT_FALSE(workflowContract(source, "client").complete()) << body; + } +} + +TEST(RecursiveContracts, CallbackCastAndReplacementCannotRecoverEntryBehavior) { + EXPECT_FALSE(workflowContract(R"c( + void *client(void *(*f)(unsigned), size_t n) { + unsigned char *p=((void *(*)(size_t))f)(n); + if(n&&p)p[n-1]=7; return p; + } + )c", + "client") + .complete()); + EXPECT_FALSE(workflowContract(R"c( + static void *bad(size_t n) { return malloc(1); } + void *client(void *(*f)(size_t), size_t n) { + f=bad; unsigned char *p=f(n); if(n&&p)p[n-1]=7; return p; + } + )c", + "client") + .complete()); +} + +TEST(RecursiveContracts, ParameterSlotIsNotInitializedCallerMemory) { + const auto contract = workflowContract("void reset(char *p){p=0;}", "reset"); + EXPECT_TRUE(contract.complete()); + EXPECT_TRUE(contract.establishes.empty()); +} + +TEST(RecursiveContracts, RecursiveForwardingRequiresTheExactNode) { + for (const auto *argument : {"p+1", "p-1", "(struct tree *)((char *)p+1)"}) { + const std::string source = R"c( + void *calloc(size_t, size_t); + struct tree { struct tree *left, *right; }; + static void odd(struct tree *p); + static void even(struct tree *p) { + if(!p)return; + odd(p->left); odd(p->right); free(p); + } + static void odd(struct tree *p) { even()c" + + std::string(argument) + R"c(); } + int client(void) { + struct tree *p=calloc(1,sizeof *p); if(!p)return 0; + odd(p); return 0; + } + )c"; + EXPECT_FALSE(workflowContract(source, "client").complete()) << argument; + } +} + +TEST(RecursiveContracts, ForwardingNeedsProgressOnEveryCycle) { + const std::string source = R"c( + struct tree { struct tree *left, *right; }; + static void walk(struct tree *p); + static void forward(struct tree *p) { walk(p); } + static void walk(struct tree *p) { + if(!p)return; forward(p->left); forward(p->right); free(p); + } + )c"; + EXPECT_TRUE(workflowContract(source, "forward").complete()); + EXPECT_TRUE(workflowContract(source, "walk").complete()); + auto bad = source; + bad.replace(bad.find("forward(p->left)"), + std::string("forward(p->left)").size(), "forward(p)"); + EXPECT_FALSE(workflowContract(bad, "forward").complete()); + EXPECT_FALSE(workflowContract(bad, "walk").complete()); +} + +TEST(RecursiveContracts, ExcludedNullCallbackDoesNotRequireBehavior) { + const auto contract = workflowContract(R"c( + static void *make(void *(*allocate)(size_t), size_t n) { + if(!n)return 0; + unsigned char *p=allocate(n); if(p)p[n-1]=7; return p; + } + int client(void) { void *p=make(0,0); free(p); return 0; } + )c", + "client"); + EXPECT_TRUE(contract.complete()); + EXPECT_TRUE(contract.requirements.empty()); +} + +TEST(RecursiveContracts, VerifiedAllocationWrapperCarriesItsActualTrust) { + const auto contract = workflowContract(R"c( + static void *allocate(size_t n) { return malloc(n); } + static void *make(void *(*callback)(size_t), size_t n) { + if(!n)return 0; + unsigned char *p=callback(n); if(p)p[n-1]=7; return p; + } + int client(void) { void *p=make(allocate,9); free(p); return 0; } + )c", + "client"); + EXPECT_TRUE(contract.complete()); + EXPECT_TRUE(contract.requirements.empty()); + EXPECT_TRUE(contract.obligations.trusted()); +} + +TEST(RecursiveContracts, + HiddenCallbackEffectsRemainVisibleDuringSpecialization) { + const auto contract = workflowContract(R"c( + static void *saved; + static void *allocate(size_t n) { saved=malloc(n); return saved; } + static void *make(void *(*callback)(size_t), size_t n) { + if(!n)return 0; + unsigned char *p=callback(n); if(p)p[n-1]=7; return p; + } + int client(void) { void *p=make(allocate,9); free(p); return p ? *(unsigned char *)saved : 0; } + )c", + "client"); + EXPECT_FALSE(contract.complete()); +} + +TEST(RecursiveContracts, StateRolesIgnoreUnrelatedPointerAndCounterFields) { + const auto contract = workflowContract(R"c( + struct output { + int *unrelated; size_t generation, capacity; unsigned char *bytes; + size_t length, depth; + }; + static int append(struct output *p, unsigned char value) { + if(p->length==p->capacity) { + if(p->capacity>10000)return 0; + size_t capacity=p->capacity+16; + unsigned char *bytes=realloc(p->bytes,capacity); + if(!bytes)return 0; + p->bytes=bytes; p->capacity=capacity; + } + p->bytes[p->length++]=value; return 1; + } + int client(size_t n) { + if(n>1000)return 0; + struct output p={0}; + for(size_t i=0;iused>=w->capacity)return 0; + w->data[w->used]=input[0]; w->used++; + if(!emit(input+1,n-1,w))return 0; + return 1; + } +)c"; + +TEST(RecursiveContracts, ByteWriterEstablishesItsActualInitializedPrefix) { + const auto contract = workflowContract(ByteWriter, "emit"); + EXPECT_TRUE(contract.complete()); + EXPECT_TRUE(std::ranges::any_of(contract.establishes, [](const auto &post) { + return post.kind == core::CheckedRequirementKind::Buffer && + post.path == core::SummaryPath::param(2).deref() && !post.on; + })); + for (const auto *capacity : {"2", "3"}) + EXPECT_TRUE(workflowContract(ByteWriter + R"c( + int client(void) { + unsigned char input[]={1,2,3}, output[3]; + struct writer w={7,output,0,)c" + + capacity + R"c(}; + (void)emit(input,3,&w); + return w.used ? w.data[w.used-1] : 0; + } + )c", + "client") + .complete()) + << capacity; +} + +TEST(RecursiveContracts, ByteWriterDoesNotInventSuccessLengthOrTermination) { + auto early = ByteWriter; + early.replace(early.find("if(w->used>=w->capacity)return 0;"), + std::string("if(w->used>=w->capacity)return 0;").size(), + "if(w->used>=w->capacity)return 1;"); + EXPECT_TRUE(workflowContract(early, "emit").complete()); + EXPECT_FALSE(workflowContract(early + R"c( + int client(void) { + unsigned char input[]={1,2,3}, output[3]; + struct writer w={7,output,0,2}; + return emit(input,3,&w) ? output[2] : 0; + } + )c", + "client") + .complete()); + EXPECT_FALSE(workflowContract(ByteWriter + R"c( + size_t strlen(const char*); + int client(void) { + unsigned char input[]={1,2,3}, output[3]; + struct writer w={7,output,0,3}; + (void)emit(input,3,&w); return (int)strlen((char*)w.data); + } + )c", + "client") + .complete()); +} + +TEST(RecursiveContracts, ByteWriterChecksEveryEffectAndFailurePrefix) { + for (const auto *mutation : {"w->used++;", "w->flags++;", "w->data=0;"}) { + auto bad = ByteWriter; + const std::string original = "if(w->used>=w->capacity)return 0;"; + bad.replace(bad.find(original), original.size(), + "if(w->used>=w->capacity){" + std::string(mutation) + + "return 0;}"); + EXPECT_FALSE(workflowContract(bad, "emit").complete()) << mutation; + } +} + +TEST(RecursiveContracts, InitializedSpansRequireActualCallerEvidence) { + const std::string helper = R"c( + int pair(const unsigned char *first, const unsigned char *last) { + const unsigned char *copy = first; + if (last-copy < 2) return 0; + return copy[0] + copy[1]; + } + )c"; + const auto generic = workflowContract(helper, "pair"); + EXPECT_TRUE(generic.complete()); + EXPECT_TRUE(std::ranges::any_of(generic.requirements, [](const auto &pre) { + return pre.kind == core::CheckedRequirementKind::InitializedSpan; + })); + for (const auto &[body, safe] : std::vector>{ + {"const unsigned char a[3]={1,2,3};return pair(a+1,a+3);", true}, + {"const unsigned char a[1]={1};return pair(a,a);", true}, + {"const unsigned char a[2]={1,2},b[2]={3,4};return pair(a,b+2);", + false}, + {"unsigned char a[2];a[0]=1;return pair(a,a+2);", false}, + {"const unsigned char a[2]={1,2};return pair(a,a+3);", false}}) { + SCOPED_TRACE(body); + auto source = helper; + source += "int client(void){"; + source += body; + source += '}'; + const auto client = workflowContract(source, "client"); + EXPECT_EQ(client.complete(), safe); + EXPECT_TRUE(client.requirements.empty()); + } +} + +TEST(RecursiveContracts, InitializedSpansDoNotSurviveUnknownMutation) { + EXPECT_FALSE(workflowContract(R"c( + void mutate(unsigned char *); + int f(unsigned char *first, unsigned char *last) { + if(last-first<2)return 0; + mutate(first); + return first[1]; + } + )c", + "f") + .complete()); + EXPECT_FALSE(workflowContract(R"c( + int f(const unsigned char *first, const unsigned char *last) { + const unsigned char other[2]={1,2}; + const unsigned char *copy=first; + copy=other; + if(last-copy<2)return 0; + return copy[1]; + } + )c", + "f") + .complete()); +} +TEST(RecursiveContracts, + ReadOnlySpanEndpointsRemainRelatedWithSeparateOutputs) { + const auto contract = workflowContract(R"c( + int decode(const unsigned char *first, const unsigned char *last, + unsigned char **out) { + if(last-first<2)return 0; + **out=first[1]; ++*out; return 1; + } + )c", + "decode"); + EXPECT_TRUE(contract.complete()); + for (const auto &pre : contract.requirements) + if (pre.kind == core::CheckedRequirementKind::Separated) + EXPECT_FALSE(pre.path == core::SummaryPath::param(0) && + pre.other == core::SummaryPath::param(1)); +} + +TEST(RecursiveContracts, NarrowReverseCountersProjectActualWriteBounds) { + const std::string helper = R"c( + void encode(unsigned char **out, unsigned code) { + unsigned char length; + if(code<128)length=1; + else if(code<2048)length=2; + else if(code<65536)length=3; + else length=4; + unsigned char i; + for(i=(unsigned char)(length-1);i>0;i--) (*out)[i]=42; + (*out)[0]=1; + *out+=length; + } + )c"; + const auto generic = workflowContract(helper, "encode"); + EXPECT_TRUE(generic.complete()); + EXPECT_TRUE(std::ranges::any_of(generic.requirements, [](const auto &pre) { + return pre.kind == core::CheckedRequirementKind::Writable && + pre.path == core::SummaryPath::param(0).deref() && + pre.begin == core::PathAffine::ofConstant(0) && + pre.end == core::PathAffine::ofConstant(4); + })); + for (const unsigned capacity : {3U, 4U}) { + const auto client = workflowContract( + helper + "int client(void){unsigned char out[" + + std::to_string(capacity) + + "];unsigned char *p=out;encode(&p,0x10000);return 0;}", + "client"); + EXPECT_EQ(client.complete(), capacity == 4); + EXPECT_TRUE(client.requirements.empty()); + } +} + +TEST(RecursiveContracts, ReturnedCountMustFitTheActualInputSpan) { + for (const unsigned produced : {4U, 5U}) { + const std::string source = R"c( + unsigned consume(const unsigned char *first,const unsigned char *last,int mode) { + if(last-first<2)return 0; + if(mode){if(last-first<4)return 0;return )c" + + std::to_string(produced) + R"c(;} + return 2; + } + int probe(const unsigned char *first,const unsigned char *last,int mode) { + if(last-first<1)return 0; + unsigned n=consume(first,last,mode); + if(!n)return 0; + return first[n-1]; + } + )c"; + const auto helper = workflowContract(source, "consume"); + EXPECT_TRUE(helper.complete()); + EXPECT_EQ(std::ranges::any_of( + helper.establishes, + [](const auto &post) { + return post.kind == + core::CheckedRequirementKind::CountWithinSpan; + }), + produced == 4); + EXPECT_EQ(workflowContract(source, "probe").complete(), produced == 4); + } +} + +TEST(RecursiveContracts, JoinedCountsRetainEveryBranchBound) { + for (const unsigned produced : {4U, 5U}) { + const std::string source = R"c( + unsigned char consume(const unsigned char *first,const unsigned char *last,int mode) { + unsigned char count=0; + if(last-first<2)return 0; + if(mode){count=)c" + std::to_string(produced) + + R"c(; + if(last-(first+2)<2)return 0;} + else count=2; + return count; + } + int probe(const unsigned char *first,const unsigned char *last,int mode) { + if(last-first<1)return 0; + unsigned char n=consume(first,last,mode); + if(!n)return 0; + return first[n-1]; + } + )c"; + const auto helper = workflowContract(source, "consume"); + EXPECT_TRUE(helper.complete()); + EXPECT_EQ(std::ranges::any_of( + helper.establishes, + [](const auto &post) { + return post.kind == + core::CheckedRequirementKind::CountWithinSpan; + }), + produced == 4); + EXPECT_EQ(workflowContract(source, "probe").complete(), produced == 4); + } +} + +TEST(RecursiveContracts, ConfinedCalleeCopiesRetainSourceOwnership) { + const std::string source = R"c( + void copy(char *p,char **out){*out=p;} + int client(void){ + char *p=malloc(3);if(!p)return 0;p[0]=1; + char *q=0;copy(p,&q);int n=p[0];free(p);return n; + } + )c"; + const auto result = workflowContract(source, "client"); + EXPECT_TRUE(result.complete()); + EXPECT_TRUE(result.requirements.empty()); + for (const auto &tail : {"free(p);return q[0];", "return q[0];"}) { + const auto bad = workflowContract( + "void copy(char *p,char **out){*out=p;}" + "int client(void){char *p=malloc(3);if(!p)return 0;p[0]=1;" + "char *q=0;copy(p,&q);" + + std::string(tail) + "}", + "client"); + EXPECT_FALSE(bad.complete()); + } +} + +TEST(RecursiveContracts, NumericEndPointerPreservesOwnedInput) { + const auto result = workflowContract(R"c( + double strtod(const char *, char **); + int client(void){ + char *p=malloc(3);if(!p)return 0;p[0]='1';p[1]='2';p[2]=0; + char *end=0;(void)strtod(p,&end); + long n=end-p;free(p);return (int)n; + } + )c", + "client"); + EXPECT_TRUE(result.complete()); + EXPECT_TRUE(result.requirements.empty()); +} + +TEST(RecursiveContracts, ReverseWritesInitializeOnlyVisitedBytes) { + for (const bool writeZero : {false, true}) { + const auto result = workflowContract( + "int fill(unsigned char *out,unsigned char length) {" + "if(length<1||length>4)return 0; unsigned char i;" + "for(i=(unsigned char)(length-1);i>0;i--)out[i]=42;" + + std::string(writeZero ? "out[0]=1;" : "") + + "return out[0];}" + "int client(void){unsigned char bytes[4];return fill(bytes,4);}", + "client"); + EXPECT_EQ(result.complete(), writeZero); + } +} + +TEST(RecursiveContracts, ConditionalArgumentsKeepConvertedRanges) { + for (const unsigned count : {4U, 5U}) { + const auto result = workflowContract( + "void touch(unsigned char *p,unsigned n){if(n)p[n-1]=1;}" + "int client(int mode){unsigned char out[4];touch(out,mode?" + + std::to_string(count) + ":1);return 0;}", + "client"); + EXPECT_EQ(result.complete(), count == 4); + } +} + +TEST(RecursiveContracts, InitializedAdvanceEndsAtActualOutputPointer) { + for (const bool skip : {false, true}) { + const std::string source = + "void encode(unsigned char **out,unsigned code){" + "unsigned char n=code>255?4:1,i;" + "for(i=(unsigned char)(n-1);i>0;i--)" + + std::string(skip ? "if(i!=2)" : "") + + "(*out)[i]=42;(*out)[0]=1;*out+=n;}" + "int client(int mode){unsigned char out[4],*p=out;" + "encode(&p,mode?65536:1);if(p>out)return p[-1];return 0;}"; + const auto helper = workflowContract(source, "encode"); + EXPECT_TRUE(helper.complete()); + EXPECT_EQ(std::ranges::any_of( + helper.establishes, + [](const auto &post) { + return post.kind == + core::CheckedRequirementKind::InitializedAdvance; + }), + !skip); + EXPECT_EQ(workflowContract(source, "client").complete(), !skip); + } +} + +TEST(RecursiveContracts, AdvanceInitializationIncludesEarlyReturns) { + for (const bool badFailure : {false, true}) { + const std::string source = + "unsigned encode(unsigned char **out,unsigned code){" + "if(!code){" + + std::string(badFailure ? "*out+=1;" : "") + + "return 0;}unsigned char n=code>255?4:1,i;" + "for(i=(unsigned char)(n-1);i>0;i--)(*out)[i]=42;" + "(*out)[0]=1;*out+=n;return n;}" + "int client(int mode){unsigned char out[4],*p=out;" + "encode(&p,mode?65536:0);if(p>out)return p[-1];return 0;}"; + const auto helper = workflowContract(source, "encode"); + EXPECT_TRUE(helper.complete()); + EXPECT_EQ( + std::ranges::any_of( + helper.establishes, + [](const auto &post) { + return post.kind == + core::CheckedRequirementKind::InitializedAdvance && + !post.on; + }), + !badFailure); + EXPECT_EQ(workflowContract(source, "client").complete(), !badFailure); + } +} + +TEST(RecursiveContracts, CounterResetPreservesOnlyUnchangedRanges) { + for (const bool changeCount : {false, true}) { + const std::string source = R"c( + void *memcpy(void *,const void *,size_t); + double strtod(const char *,char **); + struct reader {const unsigned char *data;size_t position,capacity,extra;}; + long scan(struct reader *r) { + size_t i=0,count=0;int decimal=0; + if(!r||!r->data)return 0; + for(i=0;r->position+icapacity;i++){ + switch((r->data+r->position)[i]){ + case '0':case '1':++count;break; + case '.':++count;decimal=1;break; + default:goto done;}} + done:; + char *out=malloc(count+1);if(!out)return 0; + memcpy(out,r->data+r->position,count);out[count]=0; + )c" + std::string(changeCount ? "count+=16;" : "") + + R"c( + if(decimal)for(i=0;idata)return; + for(i=0;r->position+icapacity;i++) { + switch((r->data+r->position)[i]) { + case '0':case '1':case '.':++count;break; + default:goto done; + } + } + done:; + char *out=malloc(count+1);if(!out)return; + memcpy(out,r->data+r->position,count);out[count]=0; + for(i=0;i255?4:1,i;" + "for(i=(unsigned char)(n-1);i>0;i--)(*out)[i]=42;" + "(*out)[0]=1;*out+=n;return n;}" + "int client(int mode){unsigned char out[4],*p=out;" + "unsigned ok=encode(&p,mode?65536:0);" + + mutation + "if(!ok)return 0;return p[-1];}"; + EXPECT_EQ(workflowContract(source, "client").complete(), + mutation.empty()); + } +} + +TEST(RecursiveContracts, ReturnedCountsBoundEachCursorAdvance) { + for (const unsigned extra : {0U, 1U}) { + const std::string source = R"c( + unsigned char consume(const unsigned char *first,const unsigned char *last,int mode){ + unsigned char n=0;if(last-first<2)return 0; + if(mode){n=4;if(last-(first+2)<2)return 0;}else n=2; + return n; + } + int client(int mode){const unsigned char input[8]={0}; + const unsigned char *p=input,*end=input+8; + while(p>{ + {"char text[]=\"12.5\";", true}, + {"char text[]=\"nan\";", false}, + {"char input[]=\"12\",text[3];memcpy(text,input,3);", true}, + {"char text[]=\"123\";char *p=text;p[0]='n';p[1]='a';p[2]='n';", + false}, + {"char " + "text[]=\"123\";if(mode){text[0]='n';text[1]='a';text[2]='n';}", + false}}) { + const std::string source = + "double strtod(const char*,char**);void *memcpy(void*,const " + "void*,size_t);" + "int client(int mode){" + + initial + + "double x=strtod(text,0);if(x>=2147483647.0)return 0;" + "else if(x<=-2147483648.0)return 0;else return (int)x;}"; + EXPECT_EQ(workflowContract(source, "client").complete(), safe) << initial; + } + EXPECT_FALSE( + workflowContract( + "double strtod(const char*,char**);int client(void){char " + "text[]=\"12\";" + "double x=strtod(text,0);if(x>=2147483647.0)return 0;" + "else if(x<=-2147483648.0)return 0;else {x=1e99;return (int)x;}}", + "client") + .complete()); +} + +TEST(RecursiveContracts, RefutedOutputStoresDoNotEscapeTheirSource) { + for (const bool conversion : {false, true}) { + const std::string source = + "double strtod(const char*,char**);" + "void keep(char *s,char **out){if(out)*out=s;}" + "int client(void){char *p=malloc(2);if(!p)return 0;" + "p[0]='1';p[1]=0;" + + std::string(conversion ? "(void)strtod(p,0);" : "keep(p,0);") + + "free(p);return 0;}"; + EXPECT_TRUE(workflowContract(source, "client").complete()); + } +} + +TEST(RecursiveContracts, PrivateScalarWritesPreserveOtherNumericContents) { + for (const bool overwrite : {false, true}) { + const std::string source = + "double strtod(const char*,char**);int client(void){" + "char text[]=\"123\";char *out;unsigned count=0;" + "count=3;out=text;" + + std::string(overwrite ? "out[0]='n';out[1]='a';out[2]='n';" : "") + + "double x=strtod(out,0);if(x>=2147483647.0)return 0;" + "else if(x<=-2147483648.0)return 0;else return (int)x;}"; + EXPECT_EQ(workflowContract(source, "client").complete(), !overwrite); + } +} + +TEST(RecursiveContracts, AdjustedPointersDoNotCopyPointeeValues) { + for (const std::string displacement : {"0", "1", "n"}) { + const auto source = + "int f(const unsigned char *p,unsigned n){if(*p!=0)return 0;" + "const unsigned char *q=p+" + + displacement + + ";int a[1]={0};if(*q!=0)a[3]=1;return a[0];}" + "int client(void){const unsigned char a[2]={0,1};return f(a,1);}"; + EXPECT_EQ(workflowContract(source, "client").complete(), + displacement == "0"); + } +} + +TEST(RecursiveContracts, PointerCountsAndBranchJoinsRetainActualBounds) { + for (const unsigned step : {1U, 8U}) { + const std::string source = + "int scan(const unsigned char *data,size_t size){" + "const unsigned char *p=data+1,*end=data+1;" + "while((size_t)(end-data)=size)return 0;end++;}end++;}" + "if((size_t)(end-data)>=size||*end!=34)return 0;" + "while(pp=0;return ok;"}) { + SCOPED_TRACE(forward); + const std::string code = R"c( + void *malloc(size_t); void free(void *); + struct B {char *p;unsigned tag;}; + static int a(struct B *s,unsigned n); + static int b(struct B *s,unsigned n) { + if(n && !a(s,n-1))return 0; + free(s->p);s->p=malloc(1);if(!s->p)return 0;*s->p=0;return 1; + } + static int c(struct B *s,unsigned n) {return b(s,n);} + static int a(struct B *s,unsigned n) { + s->tag=1; + if(n&1)return b(s,n); + )c" + std::string(forward) + + "}"; + const auto result = + test::analyze(code, {.checkContracts = true, .checked = true}); + ASSERT_TRUE(result.ast); + const auto *summary = result.summary("a"); + ASSERT_NE(summary, nullptr); + const auto facts = summary->nonNullOn.find(core::Outcome::Positive); + const bool nonNull = + facts != summary->nonNullOn.end() && + facts->second.contains(core::SummaryPath::param(0).deref().field("p")); + EXPECT_EQ(nonNull, std::string_view(forward) == "return c(s,n);"); + } +} + +} // namespace weavec::analysis + +namespace weavec::analysis { +TEST(RecursiveContracts, StringLengthCopiesRequireTheActualInitializedPrefix) { + for (const std::string variant : {"direct", "callback", "forward", "overread", + "stale", "missing", "tail"}) { + const std::string helper = + "char *copy(const char *s,void *(*allocate)(size_t)){if(!s)return 0;" + "size_t n=strlen(s)+" + + std::string(variant == "overread" ? "2" : "1") + ";char *p=" + + std::string(variant == "callback" ? "allocate(n)" : "malloc(n)") + + ";if(!p)return 0;" + + std::string(variant == "stale" ? "free((void*)s);" : "") + + "memcpy(p,s,n);return p;}"; + const std::string input = + variant == "missing" ? "const char input[]={104,105};" + : variant == "tail" ? "char input[3];input[0]=104;input[2]=0;" + : "const char input[]=\"hi\";"; + const auto source = + "size_t strlen(const char*);void *memcpy(void*,const void*,size_t);" + + helper + "char *forward(const char *s){return copy(s,malloc);}" + + "int client(void){" + input + "char *p=" + + std::string(variant == "forward" ? "forward(input)" + : "copy(input,malloc)") + + ";if(p)free(p);return 0;}"; + EXPECT_EQ(workflowContract(source, "client").complete(), + variant == "direct" || variant == "callback" || + variant == "forward") + << variant; + } +} +} // namespace weavec::analysis + +namespace weavec::analysis { +TEST(RecursiveContracts, TransparentCharacterCastsRetainLiteralWitnesses) { + for (const std::string input : + {"\"hi\"", "(const char*)\"hi\"", "(const unsigned char*)\"hi\"", + "(const void*)\"hi\"", "(const char*)(\"hi\"+3)"}) { + const std::string source = + "size_t strlen(const char*);void *memcpy(void*,const void*,size_t);" + "char *copy(const char *s){size_t n=strlen(s)+1;char *p=malloc(n);" + "if(p)memcpy(p,s,n);return p;}int client(void){char *p=copy(" + + input + ");free(p);return 0;}"; + EXPECT_EQ(workflowContract(source, "client").complete(), + input.find("+3") == std::string::npos) + << input; + } +} +TEST(RecursiveContracts, ContainerOutputsReachOnlyCurrentDefiniteHeadAliases) { + for (const std::string variant : + {"direct", "forwarded", "payload", "released", "interior", "disowned", + "lost-payload"}) { + const std::string source = + "void *calloc(size_t,size_t);" + "struct node{struct node *next;char *payload;unsigned flags;};" + "void drop(struct node *p){while(p){struct node *n=p->next;" + "if(!(p->flags&256))free(p->payload);free(p);p=n;}}" + "void update(struct node *p){p->flags=" + + std::string(variant == "disowned" ? "256" : "2") + + ";}void forward(struct node *p){update(p);}" + "int client(void){struct node *p=calloc(1,sizeof *p);if(!p)return 0;" + + std::string( + variant == "payload" || variant == "disowned" || + variant == "lost-payload" + ? "p->payload=malloc(1);if(!p->payload){drop(p);return 0;}" + : "") + + "struct node *a=p;" + + std::string(variant == "released" ? "drop(a);" + : variant == "interior" ? "a=(struct node*)((char*)a+1);" + : variant == "lost-payload" ? "a->payload=0;" + : "") + + std::string(variant == "forwarded" ? "forward(a);" : "update(a);") + + "drop(p);return 0;}"; + EXPECT_EQ(workflowContract(source, "client").complete(), + variant == "direct" || variant == "forwarded" || + variant == "payload") + << variant; + } +} +} // namespace weavec::analysis + +namespace weavec::analysis { +TEST(RecursiveContracts, FloatingCallPremisesRequireProvedNonNanValues) { + for (const std::string variant : + {"finite", "forwarded", "infinite", "nan", "changed", "mixed"}) { + const std::string source = + "int clamp(double x){" + + std::string(variant == "changed" ? "x=__builtin_nan(\"\");" : "") + + "if(x>=2147483647.0)return 2147483647;if(x<=-2147483648.0)return " + "(-2147483647-1);return (int)x;}" + "int forward(double x){return clamp(x);}int client(int k){return " + + std::string(variant == "forwarded" ? "forward" : "clamp") + "(" + + std::string(variant == "infinite" ? "__builtin_inf()" + : variant == "nan" ? "__builtin_nan(\"\")" + : variant == "mixed" ? "k?1.0:__builtin_nan(\"\")" + : "1.0") + + ");}"; + EXPECT_FALSE(workflowContract(source, "clamp").complete()); + EXPECT_EQ(workflowContract(source, "client").complete(), + variant == "finite" || variant == "forwarded" || + variant == "infinite") + << variant; + } +} +} // namespace weavec::analysis + +namespace weavec::analysis { +TEST(RecursiveContracts, + FloatingEarlyReturnsRequireDominanceAndUnchangedValues) { + for (const std::string variant : + {"plain", "block", "jump", "write", "address"}) { + const std::string source = + "void change(double *x){*x=1e100;}int clamp(double x){" + + std::string(variant == "jump" ? "goto cast;" : "") + + "if(x>=2147483647.0)" + + std::string(variant == "block" ? "{return 2147483647;}" + : "return 2147483647;") + + "if(x<=-2147483648.0)return (-2147483647-1);" + + std::string(variant == "jump" ? "cast:" + : variant == "write" ? "x=1e100;" + : variant == "address" ? "change(&x);" + : "") + + "return (int)x;}int client(void){return clamp(1.0);}"; + EXPECT_EQ(workflowContract(source, "client").complete(), + variant == "plain" || variant == "block") + << variant; + } +} +} // namespace weavec::analysis + +namespace weavec::analysis { +TEST(RecursiveContracts, AllocationTransfersSurviveEveryReturningAlternative) { + for (const std::string variant : {"kept", "disowned", "lost", "released"}) { + const std::string source = + "struct node{struct node *next,*child;unsigned flags;};" + "struct node *make(void){struct node *p=malloc(sizeof *p);" + "if(p){p->next=0;p->child=0;p->flags=0;}return p;}" + "void drop(struct node *p){while(p){struct node *n=p->next;" + "if(!(p->flags&1))drop(p->child);free(p);p=n;}}" + "void build(struct node *p){struct node *q=make();if(!q)return;" + "p->child=q;" + + std::string(variant == "disowned" ? "p->flags=1;" + : variant == "lost" ? "p->child=0;" + : variant == "released" ? "drop(q);" + : "") + + "}int client(void){struct node *p=make();if(!p)return 0;" + "build(p);drop(p);return 0;}"; + EXPECT_EQ(workflowContract(source, "client").complete(), variant == "kept") + << variant; + } +} +} // namespace weavec::analysis + +namespace weavec::analysis { +TEST(RecursiveContracts, LocalContainerFoldsRetainOnlyLiveHeadAliases) { + for (const std::string variant : + {"direct", "selector", "lost", "duplicate", "released", "interior"}) { + const std::string source = + "void *calloc(size_t,size_t);" + "struct node{struct node *next,*child;unsigned flags;};" + "void drop(struct node *p){while(p){struct node *n=p->next;" + "if(!(p->flags&256))drop(p->child);free(p);p=n;}}" + "int client(void){struct node *p=calloc(1,sizeof *p);if(!p)return 0;" + "struct node *q=calloc(1,sizeof *q);if(!q){drop(p);return 0;}" + "struct node *a=p;" + + std::string(variant == "released" ? "free(p);" + : variant == "interior" ? "a=(struct node*)((char*)p+1);" + : "") + + "a->child=q;" + + std::string(variant == "selector" ? "p->flags=2;" + : variant == "lost" ? "a->child=0;" + : variant == "duplicate" ? "a->next=q;" + : "") + + "drop(p);return 0;}"; + EXPECT_EQ(workflowContract(source, "client").complete(), + variant == "direct" || variant == "selector") + << variant; + } +} +} // namespace weavec::analysis + +namespace weavec::analysis { +TEST(RecursiveContracts, HelperContainerFramesRequireConfinedEffects) { + for (const std::string variant : + {"direct", "forwarded", "reader", "lost", "released", "disowned"}) { + const std::string source = + "struct node{struct node *next,*child;unsigned flags;};" + "struct reader{unsigned count;};" + "struct node *make(void){struct node *p=malloc(sizeof *p);" + "if(p){p->next=0;p->child=0;p->flags=0;}return p;}" + "void drop(struct node *p){while(p){struct node *n=p->next;" + "if(!(p->flags&256))drop(p->child);free(p);p=n;}}" + "void mark(struct node *p){p->flags=2;}" + "void forward(struct node *p){mark(p);}" + "void readmark(struct node *p,struct reader *r){p->flags=2;r->count=1;}" + "void change(struct node *p,struct node *root){p->flags=2;" + + std::string(variant == "lost" ? "root->child=0;" + : variant == "released" ? "drop(root);" + : "root->flags=256;") + + "}int client(void){struct node *p=make();if(!p)return 0;" + "struct node *q=make();if(!q){drop(p);return 0;}" + "struct node *r=make();if(!r){drop(p);drop(q);return 0;}" + "p->child=q;q->next=r;" + + std::string(variant == "direct" ? "mark(r);" + : variant == "forwarded" ? "forward(r);" + : variant == "reader" + ? "struct reader in={0};readmark(r,&in);" + : "change(r,p);") + + "drop(p);return 0;}"; + EXPECT_EQ(workflowContract(source, "client").complete(), + variant == "direct" || variant == "forwarded" || + variant == "reader") + << variant; + } +} +} // namespace weavec::analysis diff --git a/unittests/Analysis/SummariesTest.cpp b/unittests/Analysis/SummariesTest.cpp index 02d453d2..9788480f 100644 --- a/unittests/Analysis/SummariesTest.cpp +++ b/unittests/Analysis/SummariesTest.cpp @@ -516,8 +516,10 @@ TEST(Builtins, Entries) { EXPECT_TRUE(strtolSummary->returns.empty()); ASSERT_EQ(strtolSummary->stores.size(), 1U); EXPECT_EQ(strtolSummary->stores.begin()->dest, SummaryPath::param(1).deref()); - EXPECT_EQ(strtolSummary->stores.begin()->value, - ValueSource::interiorCopy(SummaryPath::param(0))); + auto endPointer = ValueSource::interiorCopy(SummaryPath::param(0)); + endPointer.when.require(SummaryPath::param(1), + core::ValueFact::of(core::Outcome::NonNull)); + EXPECT_EQ(strtolSummary->stores.begin()->value, endPointer); EXPECT_TRUE(strtolSummary->requiresParam(0)); EXPECT_FALSE(strtolSummary->requiresParam(1)) << "`endptr` may be null"; diff --git a/unittests/Analysis/TraversalTest.cpp b/unittests/Analysis/TraversalTest.cpp index 423a79ba..405ef339 100644 --- a/unittests/Analysis/TraversalTest.cpp +++ b/unittests/Analysis/TraversalTest.cpp @@ -66,6 +66,25 @@ TEST(Traversal, SameArrayDifferenceStillRequiresTheTargetIntegerRange) { EXPECT_TRUE(found); } +TEST(Traversal, RecognizedMinimumKeepsItsOperandRelation) { + // RFC 0029: an evaluation-site union must not replace min(a, b) <= b. + const auto contract = traversalCheck( + "typedef __SIZE_TYPE__ size_t;size_t strlen(const char*);" + "void *malloc(size_t);void free(void*);" + "void *memcpy(void*,const void*,size_t);" + "char *copy(const char *text,size_t length){size_t offset=strlen(text);" + "char *out=malloc(offset+1);if(!out)return 0;" + "memcpy(out,text,lengthbytes.at(SummaryPath::global(1)), "xyz"); + EXPECT_FALSE(remapCallContext( + input, [](std::uint32_t) { return std::optional{}; })); + input.bytes[SummaryPath::param(0)] = std::string(MaxCallContextFacts, 'x'); + EXPECT_FALSE(input.valid()); + input.bytes.clear(); + input.bytes[SummaryPath::param(0)] = "abc"; + input.bytes[SummaryPath::param(1)] = "xbc"; + EXPECT_FALSE(input.addAlias({.first = SummaryPath::param(0), + .second = SummaryPath::param(1), + .offset = {}})); +} + +TEST(CallContext, ImmutableBytesRequireTheirPayloadAndRetainStrictIdentity) { + CallContext input; + const auto path = SummaryPath::global(0); + input.immutableBytes.insert(path); + EXPECT_FALSE(input.valid()); + input.bytes[path] = "abc"; + ASSERT_TRUE(input.valid()); + const auto encoded = printCallContext(input, contextGlobalName); + EXPECT_EQ(parseCallContext(encoded, resolveContextGlobal), input); + EXPECT_FALSE(parseCallContext(encoded + encoded.substr(encoded.rfind(';')), + resolveContextGlobal)); + const auto mapped = + remapCallContext(input, [](std::uint32_t) { return std::optional(1U); }); + ASSERT_TRUE(mapped); + EXPECT_TRUE(mapped->immutableBytes.contains(SummaryPath::global(1))); + EXPECT_FALSE(remapCallContext( + input, [](std::uint32_t) { return std::optional{}; })); + input.bytes[path] = std::string(MaxCallContextFacts, 'a'); + EXPECT_FALSE(input.valid()); +} + +TEST(CallContext, ExactByteAliasesCheckTheActualOffsetAndStrictEncoding) { + CallContext input; + input.bytes[SummaryPath::param(0)] = "bc"; + input.bytes[SummaryPath::param(1)] = "abcd"; + ASSERT_TRUE(input.addAlias({.first = SummaryPath::param(0), + .second = SummaryPath::param(1), + .offset = PointerOffset::ofElements(1)})); + const auto encoded = printCallContext(input, contextGlobalName); + EXPECT_EQ(parseCallContext(encoded, resolveContextGlobal), input); + input.bytes[SummaryPath::param(0)] = "bd"; + EXPECT_FALSE(input.valid()); + input.bytes[SummaryPath::param(0)] = "bc"; + input.bytes[SummaryPath::param(1)] = "xabc"; + EXPECT_FALSE(input.valid()); + CallContext bytes; + bytes.bytes[SummaryPath::param(0)] = "a"; + const auto text = printCallContext(bytes, contextGlobalName); + const auto payload = text.substr(0, text.rfind(':') + 1); + for (const auto *bad : {"", "0", "gg", "AF"}) + EXPECT_FALSE(parseCallContext(payload + bad, resolveContextGlobal)) << bad; + EXPECT_FALSE( + parseCallContext(payload + std::string(130, '0'), resolveContextGlobal)); + bytes.bytes[SummaryPath::param(0)] = std::string(MaxCallContextFacts, 'x'); + EXPECT_TRUE(bytes.valid()); + bytes.facts[SummaryPath::param(1)] = ValueFact::ofConstant(0); + EXPECT_FALSE(bytes.valid()); +} + TEST(CallContext, GlobalCallbackBindingsRoundTripAndRemapEveryPremise) { CallContext input; auto targets = CallTargets::function("module.c#allocate"); @@ -472,3 +546,37 @@ TEST(CallContext, GlobalRemappingCannotCollapseTwoPremises) { } } // namespace weavec::core + +namespace weavec::core { +TEST(CallContext, NonNanParametersRetainStrictIdentityAndBounds) { + CallContext input; + input.nonNan.insert(SummaryPath::param(0)); + ASSERT_TRUE(input.valid()); + const auto encoded = printCallContext(input, contextGlobalName); + EXPECT_EQ(parseCallContext(encoded, resolveContextGlobal), input); + EXPECT_EQ( + remapCallContext( + input, [](std::uint32_t) { return std::optional{}; }), + input); + EXPECT_FALSE( + parseCallContext(encoded + ";n:706172616d2030", resolveContextGlobal)); + input.facts[SummaryPath::param(0)] = ValueFact::ofConstant(1); + EXPECT_FALSE(input.valid()); + input.facts.clear(); + input.bytes[SummaryPath::param(0)] = "a"; + EXPECT_FALSE(input.valid()); + input.bytes.clear(); + input.nonNan = {SummaryPath::param(0).deref()}; + EXPECT_FALSE(input.valid()); + input.nonNan = {SummaryPath::global(0)}; + EXPECT_FALSE(input.valid()); + input.nonNan = {SummaryPath::result()}; + EXPECT_FALSE(input.valid()); + input.nonNan.clear(); + for (unsigned i = 0; i < MaxCallContextFacts; ++i) + input.nonNan.insert(SummaryPath::param(i)); + EXPECT_TRUE(input.valid()); + input.nonNan.insert(SummaryPath::param(MaxCallContextFacts)); + EXPECT_FALSE(input.valid()); +} +} // namespace weavec::core diff --git a/unittests/Core/ContainerTest.cpp b/unittests/Core/ContainerTest.cpp index ad805f0b..5343666a 100644 --- a/unittests/Core/ContainerTest.cpp +++ b/unittests/Core/ContainerTest.cpp @@ -700,6 +700,62 @@ TEST(RecursiveContainerContracts, ConservationRequiresAnEntryPremise) { } } +TEST(RecursiveContainerContracts, ExtensionRequiresAnOwnedSingletonHead) { + auto input = treeShape(ContainerAccess::Release); + const auto output = input; + EXPECT_FALSE(input.singletonHead()); + input.terminal = true; + EXPECT_TRUE(input.singletonHead()); + const auto root = SummaryPath::param(0); + CheckedContract contract; + contract.computed = true; + contract.signature = "int (struct node *, unsigned)"; + const CheckedRequirement post{.kind = + CheckedRequirementKind::ContainerExtended, + .path = root, + .other = root, + .family = output.encode()}; + contract.establish(post); + contract.require({.kind = CheckedRequirementKind::Valid, + .path = root, + .other = {}, + .family = {}}); + for (const auto &descriptor : {input, output, treeShape()}) { + auto trial = contract; + trial.require({.kind = CheckedRequirementKind::Container, + .path = root, + .other = {}, + .family = descriptor.encode()}); + const auto parsed = + parseCheckedContract(printCheckedContract(trial, {}), {}); + EXPECT_EQ(parsed.has_value(), descriptor == input); + if (parsed) + EXPECT_EQ(*parsed, trial); + } + contract.require({.kind = CheckedRequirementKind::Container, + .path = root, + .other = {}, + .family = input.encode()}); + for (const unsigned mutation : {0U, 1U, 2U, 3U}) { + auto trial = contract; + auto invalid = post; + if (mutation == 0) + invalid.on = Outcome::Positive; + if (mutation == 1) + invalid.path = SummaryPath::result(); + if (mutation == 2) + invalid.end = PathAffine::ofConstant(1); + if (mutation == 3) + trial.requirements.clear(); + trial.establishes.clear(); + trial.establish(invalid); + EXPECT_FALSE(parseCheckedContract(printCheckedContract(trial, {}), {})); + } + contract.requirements.clear(); + contract.discardUnrepresentedContainerOutputs(); + EXPECT_TRUE(contract.establishes.empty()); +} + TEST(RecursiveContainerShape, EmptySlotsDescribeOnlyTheCurrentHead) { auto descriptor = treeShape(); descriptor.emptyLinks.insert("right"); @@ -722,6 +778,111 @@ TEST(RecursiveContainerShape, EmptySlotsDescribeOnlyTheCurrentHead) { EXPECT_FALSE(descriptor.valid()); // Canonical spelling is terminal=true. } +TEST(RecursiveContainerContracts, + StructuralJoinsForgetOnlyDifferentHeadValues) { + auto firstShape = payloadShape(); + firstShape.terminal = true; + firstShape.emptyPayloads.insert("data"); + firstShape.ownership.emplace( + "data", ContainerCondition{.field = firstShape.initialized.back(), + .mask = 1, + .value = 0}); + firstShape.headValues["value"] = 0; + auto secondShape = firstShape; + secondShape.headValues["value"] = 2; + auto commonShape = firstShape; + commonShape.headValues.clear(); + for (const auto kind : {CheckedRequirementKind::Container, + CheckedRequirementKind::ContainerDerived, + CheckedRequirementKind::ContainerFresh, + CheckedRequirementKind::ContainerExtended}) { + const CheckedRequirement first{.kind = kind, + .path = SummaryPath::param(0), + .other = SummaryPath::param(0), + .family = firstShape.encode()}; + auto second = first; + second.family = secondShape.encode(); + const auto joined = joinContainerOutput(first, second); + ASSERT_TRUE(joined); + EXPECT_EQ(joined->family, commonShape.encode()); + CheckedRequirements posts{first}; + posts.intersect({second}); + EXPECT_EQ(posts, (CheckedRequirements{*joined})); + second.on = Outcome::Positive; + EXPECT_FALSE(joinContainerOutput(first, second)); + second.on.reset(); + auto incompatible = secondShape; + incompatible.ownership.at("data").mask = 2; + second.family = incompatible.encode(); + EXPECT_FALSE(joinContainerOutput(first, second)); + if (kind == CheckedRequirementKind::ContainerDerived) { + second.family = secondShape.encode(); + second.other = SummaryPath::param(1); + const auto combined = joinContainerOutput(first, second); + ASSERT_TRUE(combined); + EXPECT_EQ(combined->family, commonShape.encode()); + EXPECT_EQ(combined->other, SummaryPath::param(0)); + EXPECT_EQ(combined->begin.path, SummaryPath::param(1)); + } + } +} + +TEST(RecursiveContainerContracts, ExtensionJoinsKeepOnlyCommonHeadFacts) { + auto empty = payloadShape(); + empty.terminal = true; + empty.emptyPayloads.insert("data"); + auto populated = empty; + populated.emptyPayloads.clear(); + const auto root = SummaryPath::param(0); + const CheckedRequirement first{.kind = + CheckedRequirementKind::ContainerExtended, + .path = root, + .other = root, + .family = empty.encode()}; + auto second = first; + second.family = populated.encode(); + const auto joined = joinContainerOutput(first, second); + ASSERT_TRUE(joined); + EXPECT_EQ(joined->family, populated.encode()); + CheckedRequirements posts{first}; + posts.intersect({second}); + EXPECT_EQ(posts, (CheckedRequirements{*joined})); + CheckedContract contract; + contract.computed = true; + contract.signature = "int (struct node *)"; + contract.require({.kind = CheckedRequirementKind::Valid, + .path = root, + .other = {}, + .family = {}}); + contract.require({.kind = CheckedRequirementKind::Container, + .path = root, + .other = {}, + .family = empty.encode()}); + contract.establish(*joined); + const auto parsed = + parseCheckedContract(printCheckedContract(contract, {}), {}); + ASSERT_TRUE(parsed); + EXPECT_EQ(*parsed, contract); + for (const unsigned mutation : {0U, 1U, 2U, 3U, 4U}) { + auto incompatible = second; + if (mutation == 0) + incompatible.path = SummaryPath::param(1); + if (mutation == 1) + incompatible.other = SummaryPath::param(1); + if (mutation == 2) + incompatible.on = Outcome::Positive; + if (mutation == 3) + incompatible.ifNonNull = true; + if (mutation == 4) { + auto borrowed = populated; + borrowed.access = ContainerAccess::Read; + borrowed.family.clear(); + incompatible.family = borrowed.encode(); + } + EXPECT_FALSE(joinContainerOutput(first, incompatible)) << mutation; + } +} + TEST(RecursiveContainerFacts, JoiningHeadNullSlotsKeepsTheirIntersection) { auto terminal = treeShape(); terminal.terminal = true; @@ -873,6 +1034,69 @@ TEST(RecursiveContainerContracts, PartitionAndCombinationRequireSeparation) { } } +TEST(RecursiveContainerContracts, + CombinedExtensionNeedsOwnedInputsAndALiveHead) { + // RFC 0029: `end` one adds a fresh region to the two combined inputs. + const auto input = SummaryPath::param(0); + const auto second = SummaryPath::param(1); + const auto owned = treeShape(ContainerAccess::Release).encode(); + const auto borrowed = treeShape(ContainerAccess::Read).encode(); + const auto build = [&](const std::string &secondFamily, + const SummaryPath &path, bool live) { + CheckedContract contract; + contract.computed = true; + contract.signature = "int (struct node *, struct node *)"; + contract.require({.kind = CheckedRequirementKind::Container, + .path = input, + .other = {}, + .family = owned}); + contract.require({.kind = CheckedRequirementKind::Container, + .path = second, + .other = {}, + .family = secondFamily}); + contract.require({.kind = CheckedRequirementKind::ContainerSeparated, + .path = input, + .other = second, + .family = {}}); + if (live) + contract.require({.kind = CheckedRequirementKind::Valid, + .path = input, + .other = {}, + .family = {}}); + contract.establish({.kind = CheckedRequirementKind::ContainerCombined, + .path = path, + .other = input, + .begin = PathAffine::ofPath(second), + .end = PathAffine::ofConstant(1), + .family = owned, + .on = Outcome::Positive}); + return contract; + }; + const auto accepted = build(owned, input, true); + const auto encoded = printCheckedContract(accepted, {}); + const auto parsed = parseCheckedContract(encoded, {}); + ASSERT_TRUE(parsed) << encoded; + EXPECT_EQ(*parsed, accepted); + // A borrowed second input, a missing live head, an unrelated output path + // and any other extension constant have no such relation. + EXPECT_FALSE(parseCheckedContract( + printCheckedContract(build(borrowed, input, true), {}), {})); + EXPECT_FALSE(parseCheckedContract( + printCheckedContract(build(owned, input, false), {}), {})); + EXPECT_FALSE(parseCheckedContract( + printCheckedContract(build(owned, SummaryPath::result(), true), {}), {})); + auto oversized = accepted; + oversized.establishes.clear(); + oversized.establish({.kind = CheckedRequirementKind::ContainerCombined, + .path = input, + .other = input, + .begin = PathAffine::ofPath(second), + .end = PathAffine::ofConstant(2), + .family = owned, + .on = Outcome::Positive}); + EXPECT_FALSE(parseCheckedContract(printCheckedContract(oversized, {}), {})); +} + TEST(RecursiveContainerContracts, SaturationRetiresDanglingOutputs) { for (const auto kind : {CheckedRequirementKind::ContainerDerived, CheckedRequirementKind::ContainerTail, diff --git a/unittests/Core/FormatTest.cpp b/unittests/Core/FormatTest.cpp index 3f6bd051..535f07cf 100644 --- a/unittests/Core/FormatTest.cpp +++ b/unittests/Core/FormatTest.cpp @@ -69,6 +69,34 @@ TEST(RuntimeSafetyTest, BoundedTerminationDoesNotInitializeCapacity) { state.forgetZeros(); EXPECT_TRUE(state.boundedTermination.empty()); } +TEST(RuntimeSafetyTest, BoundedTerminationInputsRoundTripWithoutOutputGuards) { + CheckedContract contract; + contract.computed = true; + CheckedRequirement input{.kind = CheckedRequirementKind::TerminatedWithin, + .path = SummaryPath::param(0), + .other = {}, + .begin = PathAffine::ofPath(SummaryPath::param(1)), + .end = PathAffine::ofPath(SummaryPath::param(2)), + .family = {}}; + contract.require(input); + EXPECT_EQ(parseCheckedContract(printCheckedContract(contract, {}), {}), + contract); + for (unsigned variant = 0; variant < 4; ++variant) { + auto invalid = contract; + auto changed = input; + if (variant == 0) + changed.other = SummaryPath::param(1); + else if (variant == 1) + changed.family = "free"; + else if (variant == 2) + changed.on = Outcome::NonNull; + else + changed.end = PathAffine::ofPath(SummaryPath::result()); + invalid.requirements.clear(); + invalid.require(changed); + EXPECT_FALSE(parseCheckedContract(printCheckedContract(invalid, {}), {})); + } +} TEST(RuntimeSafetyTest, ConditionalInitializationDoesNotActivateAnArgumentList) { SafetyState state; diff --git a/unittests/Core/InductionTest.cpp b/unittests/Core/InductionTest.cpp new file mode 100644 index 00000000..225fcaaa --- /dev/null +++ b/unittests/Core/InductionTest.cpp @@ -0,0 +1,55 @@ +//===- InductionTest.cpp - Recursive progress oracle (RFC 0029) ----------===// +// +// Part of WeaveC, under the Apache License v2.0 with LLVM Exceptions. +// See LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "weavec/Core/Induction.h" + +#include + +#include +#include + +namespace weavec::core { + +TEST(Induction, EveryCycleMustContainProgress) { + // Exhaust every absent/non-strict/strict edge assignment for three members. + // The independent oracle computes transitive closure of non-strict edges. + for (unsigned encoding = 0; encoding < 19683; ++encoding) { + unsigned remaining = encoding; + std::array, 3> reachable{}; + std::vector edges; + for (unsigned from = 0; from < 3; ++from) + for (unsigned to = 0; to < 3; ++to) { + const auto kind = remaining % 3; + remaining /= 3; + if (kind) + edges.push_back({.caller = from, .callee = to, .strict = kind == 2}); + reachable.at(from).at(to) = kind == 1; + } + for (unsigned via = 0; via < 3; ++via) + for (unsigned from = 0; from < 3; ++from) + for (unsigned to = 0; to < 3; ++to) + reachable.at(from).at(to) |= + reachable.at(from).at(via) && reachable.at(via).at(to); + const bool acyclic = !reachable.at(0).at(0) && !reachable.at(1).at(1) && + !reachable.at(2).at(2); + ASSERT_EQ(validInductionProgress(3, edges), acyclic) << encoding; + } +} + +TEST(Induction, MissingMembersAndExhaustedBoundsSupplyNoProof) { + EXPECT_FALSE(validInductionProgress(0, {})); + EXPECT_TRUE(validInductionProgress(32, {})); + EXPECT_FALSE(validInductionProgress(33, {})); + const std::array bad{InductionEdge{.caller = 0, .callee = 1, .strict = true}}; + EXPECT_FALSE(validInductionProgress(1, bad)); + std::vector edges(256, {.strict = true}); + EXPECT_TRUE(validInductionProgress(1, edges)); + edges.push_back({.strict = true}); + EXPECT_FALSE(validInductionProgress(1, edges)); +} + +} // namespace weavec::core diff --git a/unittests/Core/IntegerExpressionTest.cpp b/unittests/Core/IntegerExpressionTest.cpp index a9c18a03..13166e9d 100644 --- a/unittests/Core/IntegerExpressionTest.cpp +++ b/unittests/Core/IntegerExpressionTest.cpp @@ -15,6 +15,53 @@ using Expression = IntegerExpression; static constexpr IntegerType U8{.width = 8, .isSigned = false}; static constexpr IntegerType I8{.width = 8, .isSigned = true}; +TEST(IntegerExpression, UnsignedCancellationPreservesInvalidEvaluations) { + const auto a = Expression::input("a", U8); + const auto b = Expression::input("b", U8); + const auto difference = Expression::operation(IntegerOp::Subtract, b, a); + ASSERT_TRUE(difference); + EXPECT_EQ(Expression::operation(IntegerOp::Add, a, *difference), b); + EXPECT_EQ(Expression::operation(IntegerOp::Add, *difference, a), b); + for (unsigned x = 0; x < 256; ++x) + for (unsigned y = 0; y < 256; ++y) { + const auto delta = + evaluateInteger(IntegerOp::Subtract, IntegerValue::ofBits(U8, y), + IntegerValue::ofBits(U8, x)); + ASSERT_TRUE(delta.value); + EXPECT_EQ(evaluateInteger(IntegerOp::Add, IntegerValue::ofBits(U8, x), + *delta.value) + .value, + IntegerValue::ofBits(U8, y)); + } + const auto quotient = Expression::operation(IntegerOp::Divide, a, b); + ASSERT_TRUE(quotient); + const auto subtract = + Expression::operation(IntegerOp::Subtract, b, *quotient); + ASSERT_TRUE(subtract); + const auto invalid = + Expression::operation(IntegerOp::Add, *quotient, *subtract); + ASSERT_TRUE(invalid); + EXPECT_TRUE(invalid + ->evaluate([](const std::string &, IntegerType type) { + return IntegerRange::full(type); + }) + .mayBeInvalid); + const auto signedA = Expression::input("a", I8); + const auto signedB = Expression::input("b", I8); + const auto signedDifference = + Expression::operation(IntegerOp::Subtract, signedB, signedA); + ASSERT_TRUE(signedDifference); + EXPECT_NE(Expression::operation(IntegerOp::Add, signedA, *signedDifference), + signedB); + const auto booleanA = Expression::input("a", BooleanType); + const auto booleanB = Expression::input("b", BooleanType); + const auto booleanDifference = + Expression::operation(IntegerOp::Subtract, booleanB, booleanA); + ASSERT_TRUE(booleanDifference); + EXPECT_NE(Expression::operation(IntegerOp::Add, booleanA, *booleanDifference), + booleanB); +} + TEST(IntegerExpression, OperationsRetainWidthsAndCanonicalizeCommutativeValues) { const auto a = Expression::input("a", U8); diff --git a/unittests/Core/InterfaceTest.cpp b/unittests/Core/InterfaceTest.cpp index d6ab49ac..de9db82d 100644 --- a/unittests/Core/InterfaceTest.cpp +++ b/unittests/Core/InterfaceTest.cpp @@ -43,12 +43,31 @@ TEST(InterfaceType, CyclicPointerGraphsRoundTripCanonically) { for (std::size_t i = 0; i < text.size(); ++i) EXPECT_FALSE(InterfaceType::decode(text.substr(0, i))) << i; EXPECT_FALSE(InterfaceType::decode(text + " ")); - EXPECT_FALSE(InterfaceType::decode("it1;03;" + text.substr(6))); + EXPECT_FALSE(InterfaceType::decode("it2;03;" + text.substr(6))); + EXPECT_FALSE(InterfaceType::decode("it1;" + text.substr(4))); EXPECT_FALSE(InterfaceType::decode("it0;" + text.substr(4))); - EXPECT_FALSE(InterfaceType::decode("it1;18446744073709551616;")); + EXPECT_FALSE(InterfaceType::decode("it2;18446744073709551616;")); EXPECT_FALSE(InterfaceType::decode(std::string(MaxInterfaceBytes + 1, 'x'))); } +TEST(InterfaceType, AnonymousTypedefNamesAreStrictlyBoundedRecordMetadata) { + auto type = chainInterface(); + type.nodes[0].name.clear(); + type.nodes[0].typedefName = "node_alias"; + ASSERT_TRUE(type.valid()); + EXPECT_EQ(InterfaceType::decode(type.encode()), type); + for (std::size_t i = 0; i < type.encode().size(); ++i) + EXPECT_FALSE(InterfaceType::decode(type.encode().substr(0, i))) << i; + type.nodes[0].name = "tag"; + EXPECT_FALSE(type.valid()); + type.nodes[0].name.clear(); + type.nodes[0].typedefName = "bad name"; + EXPECT_FALSE(type.valid()); + type.nodes[0].typedefName.clear(); + type.nodes[1].typedefName = "scalar_alias"; + EXPECT_FALSE(type.valid()); +} + TEST(InterfaceType, InvalidEdgesLayoutsAndByValueCyclesAreRejected) { auto type = chainInterface(); type.nodes[0].fields[1].type = 3; diff --git a/unittests/Core/RelationTest.cpp b/unittests/Core/RelationTest.cpp index 8cbfb16a..2ad798bc 100644 --- a/unittests/Core/RelationTest.cpp +++ b/unittests/Core/RelationTest.cpp @@ -35,6 +35,39 @@ TEST(Relation, NarrowsAndWidens) { EXPECT_EQ(spelling(Relation::GreaterEqual), ">="); } +// RFC 0029: constant bounds obey the same finite widening as differences. +TEST(Relation, CheckedConstantBoundsWidenThroughZero) { + RelationTracker previous; + RelationTracker incoming; + previous.learnAtMost(I, -5); + incoming.learnAtMost(I, -3); + previous.learnAtLeast(N, 8); + incoming.learnAtLeast(N, 6); + previous.learnAtMost(K, 9); + incoming.learnAtMost(K, 9); + EXPECT_TRUE(previous.join(incoming, true, true)); + EXPECT_EQ(previous.atMost(I), 0); + EXPECT_EQ(previous.atLeast(N), 0); + EXPECT_EQ(previous.atMost(K), 9); + EXPECT_FALSE(previous.join(incoming, true, true)); + incoming.forget(I); + incoming.learnAtMost(I, 1); + incoming.forget(N); + incoming.learnAtLeast(N, -1); + EXPECT_TRUE(previous.join(incoming, true, true)); + EXPECT_FALSE(previous.atMost(I)); + EXPECT_FALSE(previous.atLeast(N)); + EXPECT_FALSE(previous.join(incoming, true, true)); + + RelationTracker exact; + RelationTracker later; + exact.learnAtMost(I, 1); + later.learnAtMost(I, 2); + EXPECT_TRUE(exact.join(later, true, false)); + EXPECT_EQ(exact.atMost(I), 2); + EXPECT_TRUE(exact.isBounded(I)); +} + TEST(RelationTracker, LearnsInEitherOrderAndNarrows) { RelationTracker relations; relations.learn(I, Relation::Less, N); // i < n diff --git a/unittests/Core/SafetyTest.cpp b/unittests/Core/SafetyTest.cpp index 2ddc8ff2..c64ce7dc 100644 --- a/unittests/Core/SafetyTest.cpp +++ b/unittests/Core/SafetyTest.cpp @@ -14,6 +14,34 @@ namespace weavec::core { +TEST(SafetyStateTest, ExactBytesAreSlicedInvalidatedAndJoinedConservatively) { + const PlaceId object{1}; + const InitializedRange input{.begin = Affine::ofConstant(2), + .end = Affine::ofConstant(6), + .bytes = "abcd", + .immutableBytes = true}; + const auto parts = + input.outsideWrite(Affine::ofConstant(3), Affine::ofConstant(5)); + ASSERT_EQ(parts.size(), 2U); + EXPECT_EQ(parts[0].bytes, "a"); + EXPECT_EQ(parts[1].bytes, "d"); + EXPECT_TRUE(parts[0].immutableBytes); + EXPECT_TRUE(parts[1].immutableBytes); + SafetyState state; + state.initialize(object, input); + ASSERT_EQ(state.memory.at(object).size(), 2U); + auto other = state; + other.forgetZeros(); + state.join(other); + ASSERT_EQ(state.memory.at(object).size(), 1U); + EXPECT_TRUE(state.memory.at(object).front().bytes.empty()); + EXPECT_FALSE(state.memory.at(object).front().immutableBytes); + auto malformed = input; + malformed.end = Affine::ofConstant(7); + state.initialize(object, malformed); + ASSERT_EQ(state.memory.at(object).size(), 1U); +} + static SafetyObligation obligation(SafetyOutcome outcome, unsigned line = 1) { return { .property = SafetyProperty::Bounds, @@ -1645,4 +1673,343 @@ TEST(SafetyState, EXPECT_TRUE(a.memory.at(object).front().when.trivial()); } +// RFC 0029: callback behavior is a visible caller premise, not an output. +TEST(CheckedContract, CallbackProtocolsAreStrictInputOnlyRecords) { + const GlobalNamer names = [](std::uint32_t) { return std::string("g"); }; + const GlobalResolver resolve = [](std::string_view) { + return std::optional(0); + }; + for (const auto kind : {CheckedRequirementKind::CallbackAllocate, + CheckedRequirementKind::CallbackRelease}) { + CheckedRequirement pre{.kind = kind, + .path = SummaryPath::param(2).deref().field("hook"), + .other = {}, + .family = "free"}; + CheckedContract contract; + contract.computed = true; + contract.require(pre); + const auto encoded = printCheckedContract(contract, names); + EXPECT_EQ(parseCheckedContract(encoded, resolve), contract); + for (std::size_t n = 0; n < encoded.size(); ++n) + EXPECT_FALSE(parseCheckedContract(encoded.substr(0, n), resolve)); + for (unsigned mutation = 0; mutation < 8; ++mutation) { + auto bad = pre; + switch (mutation) { + case 0: + bad.path = SummaryPath::result(); + break; + case 1: + bad.path = SummaryPath::global(0); + break; + case 2: + bad.other = SummaryPath::param(1); + break; + case 3: + bad.begin = PathAffine::ofConstant(1); + break; + case 4: + bad.end = PathAffine::ofPath(SummaryPath::param(0)); + break; + case 5: + bad.family = "fclose"; + break; + case 6: + bad.on = Outcome::NonNull; + break; + default: + bad.ifNonNull = true; + break; + } + contract.requirements.clear(); + contract.require(bad); + EXPECT_FALSE( + parseCheckedContract(printCheckedContract(contract, names), resolve)); + } + contract.requirements.clear(); + contract.establish(pre); + EXPECT_FALSE( + parseCheckedContract(printCheckedContract(contract, names), resolve)); + } +} + +TEST(InitializedRange, WriteFrameMatchesIndependentByteOracle) { + for (std::int64_t begin = 0; begin <= 8; ++begin) + for (std::int64_t end = begin; end <= 8; ++end) + for (std::int64_t first = 0; first <= 8; ++first) + for (std::int64_t last = first; last <= 8; ++last) { + InitializedRange range{.begin = Affine::ofConstant(begin), + .end = Affine::ofConstant(end), + .zeroed = true}; + range.when.require(PlaceId{3}, ValueFact::of(Outcome::Positive)); + const auto preserved = range.outsideWrite(Affine::ofConstant(first), + Affine::ofConstant(last)); + for (std::int64_t byte = 0; byte < 8; ++byte) { + const bool expected = + begin <= byte && byte < end && (byte < first || byte >= last); + bool actual = false; + for (const auto &part : preserved) { + EXPECT_TRUE(part.zeroed); + EXPECT_EQ(part.when, range.when); + actual |= part.begin.constant <= byte && byte < part.end.constant; + } + EXPECT_EQ(actual, expected); + } + } + InitializedRange range{ + .begin = {}, .end = Affine::ofConstant(8), .zeroed = true}; + EXPECT_TRUE( + range.outsideWrite(Affine::ofPlace(PlaceId{1}), Affine::ofConstant(8)) + .empty()); + EXPECT_TRUE( + range.outsideWrite(Affine::ofConstant(8), Affine::ofConstant(7)).empty()); + range.terminatedWithin = true; + EXPECT_TRUE(range.outsideWrite({}, {}).empty()); +} + +TEST(SafetyState, PendingAllocationReleasesRequireIdenticalIncomingEvidence) { + const PlaceId result{1}; + const PlaceId storage{2}; + const PlaceId snapshot{3}; + const PlaceId otherSnapshot{4}; + SafetyState first; + first.pendingAllocationReleases[result] = {.storage = storage, + .snapshot = snapshot}; + auto same = first; + EXPECT_FALSE(first.join(same)); + EXPECT_EQ(first.pendingAllocationReleases.size(), 1U); + same.pendingAllocationReleases[result].snapshot = otherSnapshot; + EXPECT_TRUE(first.join(same)); + EXPECT_TRUE(first.pendingAllocationReleases.empty()); + for (const auto replaced : {result, storage, snapshot}) { + first.pendingAllocationReleases[result] = {.storage = storage, + .snapshot = snapshot}; + first.forget(replaced); + EXPECT_TRUE(first.pendingAllocationReleases.empty()); + } +} + +TEST(CheckedContract, InitializedSpansAreStrictInputOnlyRecords) { + const GlobalNamer names = [](std::uint32_t) { return std::string("g"); }; + const GlobalResolver resolve = [](std::string_view) { + return std::optional(0); + }; + const CheckedRequirement span{.kind = CheckedRequirementKind::InitializedSpan, + .path = SummaryPath::param(0), + .other = SummaryPath::param(1), + .end = PathAffine::ofConstant(INT64_MAX), + .family = {}}; + CheckedContract contract; + contract.computed = true; + contract.require(span); + EXPECT_EQ( + parseCheckedContract(printCheckedContract(contract, names), resolve), + contract); + for (unsigned mutation = 0; mutation < 9; ++mutation) { + auto bad = span; + switch (mutation) { + case 0: + bad.other = bad.path; + break; + case 1: + bad.path = SummaryPath::result(); + break; + case 2: + bad.begin = PathAffine::ofConstant(1); + break; + case 3: + bad.end = PathAffine::ofConstant(0); + break; + case 4: + bad.end = PathAffine::ofConstant(-1); + break; + case 5: + bad.end = PathAffine::ofPath(SummaryPath::param(2)); + break; + case 6: + bad.family = "free"; + break; + case 7: + bad.on = Outcome::Positive; + break; + default: + bad.ifNonNull = true; + break; + } + contract.requirements.clear(); + contract.require(bad); + EXPECT_FALSE( + parseCheckedContract(printCheckedContract(contract, names), resolve)); + } + contract.requirements.clear(); + contract.establish(span); + EXPECT_FALSE( + parseCheckedContract(printCheckedContract(contract, names), resolve)); +} +TEST(CheckedContract, CountWithinSpanNeedsItsInputAndEveryReturn) { + const GlobalNamer names = [](std::uint32_t n) { return std::to_string(n); }; + const GlobalResolver resolve = [](std::string_view n) { + return std::optional(n == "0" ? 0U : 1U); + }; + const CheckedRequirement pre{.kind = CheckedRequirementKind::InitializedSpan, + .path = SummaryPath::global(0), + .other = SummaryPath::global(1), + .end = PathAffine::ofConstant(INT64_MAX), + .family = {}}; + const CheckedRequirement post{.kind = CheckedRequirementKind::CountWithinSpan, + .path = pre.path, + .other = pre.other, + .family = {}}; + FunctionSummary summary; + summary.checked.computed = true; + summary.checked.require(pre); + summary.checked.establish(post); + EXPECT_EQ(parseCheckedContract(printCheckedContract(summary.checked, names), + resolve), + summary.checked); + const auto mapped = remapGlobals( + summary, [](std::uint32_t n) { return std::optional(n + 4); }); + ASSERT_EQ(mapped.checked.establishes.size(), 1U); + EXPECT_EQ(mapped.checked.establishes.begin()->path, SummaryPath::global(4)); + EXPECT_EQ(mapped.checked.establishes.begin()->other, SummaryPath::global(5)); + const auto missing = remapGlobals(summary, [](std::uint32_t n) { + return n == 0 ? std::optional(n) : std::nullopt; + }); + EXPECT_FALSE(missing.checked.complete()); + for (unsigned mutation = 0; mutation < 8; ++mutation) { + auto bad = summary.checked; + auto invalid = post; + switch (mutation) { + case 0: + bad.requirements.clear(); + break; + case 1: + invalid.on = Outcome::Positive; + break; + case 2: + invalid.ifNonNull = true; + break; + case 3: + invalid.begin = PathAffine::ofConstant(1); + break; + case 4: + invalid.end = PathAffine::ofConstant(1); + break; + case 5: + invalid.family = "free"; + break; + case 6: + invalid.when.require(SummaryPath::param(2), ValueFact::nonZero()); + break; + default: + invalid.other = SummaryPath::param(2); + break; + } + bad.establishes.clear(); + bad.establish(invalid); + EXPECT_FALSE( + parseCheckedContract(printCheckedContract(bad, names), resolve)); + } + summary.checked.establishes.clear(); + summary.checked.require(post); + EXPECT_FALSE(parseCheckedContract( + printCheckedContract(summary.checked, names), resolve)); +} + +TEST(CheckedContract, InitializedAdvanceRequiresMatchingPosition) { + const GlobalNamer names = [](std::uint32_t n) { return std::to_string(n); }; + const GlobalResolver resolve = [](std::string_view) { + return std::optional(0); + }; + const auto path = SummaryPath::param(0).deref(); + const CheckedRequirement position{.kind = CheckedRequirementKind::Position, + .path = path, + .other = path, + .end = PathAffine::ofConstant(4), + .family = {}}; + const CheckedRequirement post{.kind = + CheckedRequirementKind::InitializedAdvance, + .path = path, + .other = path, + .family = {}}; + CheckedContract contract; + contract.computed = true; + contract.establish(position); + contract.establish(post); + EXPECT_EQ( + parseCheckedContract(printCheckedContract(contract, names), resolve), + contract); + for (unsigned mutation = 0; mutation < 8; ++mutation) { + auto bad = contract; + auto invalid = post; + bad.establishes.clear(); + if (mutation != 0) + bad.establish(position); + switch (mutation) { + case 1: + invalid.other = SummaryPath::param(1); + break; + case 2: + invalid.path = SummaryPath::result(); + break; + case 3: + invalid.begin = PathAffine::ofConstant(1); + break; + case 4: + invalid.end = PathAffine::ofConstant(1); + break; + case 5: + invalid.family = "free"; + break; + case 6: + invalid.ifNonNull = true; + break; + case 7: + invalid.when.require(SummaryPath::param(1), ValueFact::nonZero()); + break; + default: + break; + } + bad.establish(invalid); + EXPECT_FALSE( + parseCheckedContract(printCheckedContract(bad, names), resolve)); + } + contract.require(post); + EXPECT_FALSE( + parseCheckedContract(printCheckedContract(contract, names), resolve)); +} + +TEST(SafetyState, NumericContentsNeedEveryPathAndRetireWithWrites) { + const PlaceId storage{1}; + SafetyState state; + state.initialize( + storage, + {.begin = {}, .end = Affine::ofConstant(4), .numericText = true}); + ASSERT_EQ(state.memory.at(storage).size(), 2U); + auto other = state; + other.forgetZeros(); + ASSERT_EQ(other.memory.at(storage).size(), 1U); + state.join(other); + EXPECT_EQ(state, other); + AnalysisState pending; + pending.safety.emplace(); + auto &outcome = pending.pending[PlaceId{2}]; + outcome.consumedBy.try_emplace(Outcome::Zero); + outcome.initializedOn[Outcome::Zero].push_back( + {storage, + {.begin = {}, .end = Affine::ofConstant(4), .numericText = true}}); + pending.forgetZeroedMemory(); + EXPECT_TRUE(outcome.initializedInAll().empty()); +} + +TEST(SafetyState, NonNanFactsRequireEveryPathAndUnchangedStorage) { + const PlaceId value{1}; + SafetyState state; + state.nonNan.insert(value); + auto other = state; + other.forgetDependency(value); + EXPECT_TRUE(other.nonNan.empty()); + state.join(other); + EXPECT_TRUE(state.nonNan.empty()); +} + } // namespace weavec::core diff --git a/unittests/Core/SummaryIOTest.cpp b/unittests/Core/SummaryIOTest.cpp index d4aede9c..b60d9250 100644 --- a/unittests/Core/SummaryIOTest.cpp +++ b/unittests/Core/SummaryIOTest.cpp @@ -212,7 +212,7 @@ TEST(SummaryIO, PrintsAndParsesGuardsAndNeverReturns) { // RFC 0010, *Summary text format (version 6)*: the `share` flag and the // `increment`, `decrement`, `count`, `stored` and `fact` lines. TEST(SummaryIO, PrintsAndParsesSharesAndPerOutcomeLines) { - EXPECT_EQ(SummaryFormatVersion, 23U); + EXPECT_EQ(SummaryFormatVersion, 26U); const SummaryPath rc = SummaryPath::param(0).deref().field("rc"); FunctionSummary unref; unref.addEffect(SummaryPath::param(0), diff --git a/unittests/Frontend/CheckedReportTest.cpp b/unittests/Frontend/CheckedReportTest.cpp index 3393ad0e..7411df49 100644 --- a/unittests/Frontend/CheckedReportTest.cpp +++ b/unittests/Frontend/CheckedReportTest.cpp @@ -107,7 +107,7 @@ TEST(CheckedReport, EscapingAndScopeRoundTripThroughJson) { const auto *object = parsed->getAsObject(); ASSERT_NE(object, nullptr); EXPECT_EQ(object->getInteger("version"), 2); - EXPECT_EQ(object->getInteger("model_version"), 23); + EXPECT_EQ(object->getInteger("model_version"), 26); ASSERT_NE(object->getObject("totals"), nullptr); EXPECT_EQ(object->getObject("totals")->getInteger("complete"), 1); const auto *units = object->getArray("units"); diff --git a/unittests/Frontend/SidecarTest.cpp b/unittests/Frontend/SidecarTest.cpp index 32231945..22fd83e3 100644 --- a/unittests/Frontend/SidecarTest.cpp +++ b/unittests/Frontend/SidecarTest.cpp @@ -104,7 +104,7 @@ TEST(Sidecar, PathIsOutputPlusExtension) { TEST(Sidecar, PrintsStableText) { EXPECT_EQ(printUnitRecord(sample()), - "weavec-summaries 24\n" + "weavec-summaries 27\n" "global-name -:675f6361636865\n" "source src/node.c\n" "cwd /work/build\n" @@ -217,33 +217,33 @@ TEST(Sidecar, RejectsOtherFormatsAndMalformedLines) { EXPECT_FALSE(parseUnitRecord("", &error)); EXPECT_EQ(error, "empty file"); EXPECT_FALSE(parseUnitRecord( - "weavec-summaries 24\nsummary\n return fresh\nend\n", &error)); + "weavec-summaries 27\nsummary\n return fresh\nend\n", &error)); EXPECT_EQ(error, "line 2: summary record without a function"); - EXPECT_FALSE(parseUnitRecord("weavec-summaries 24\nfunction f\n", &error)); + EXPECT_FALSE(parseUnitRecord("weavec-summaries 27\nfunction f\n", &error)); EXPECT_EQ(error, "line 2: malformed 'function' line"); - EXPECT_FALSE(parseUnitRecord("weavec-summaries 24\nfunction f external " + EXPECT_FALSE(parseUnitRecord("weavec-summaries 27\nfunction f external " "plain\nsummary\n return fresh\n", &error)); EXPECT_EQ(error, "line 4: summary record without 'end'"); EXPECT_FALSE( - parseUnitRecord("weavec-summaries 24\nreported x y z\n", &error)); + parseUnitRecord("weavec-summaries 27\nreported x y z\n", &error)); EXPECT_EQ(error, "line 2: malformed 'reported' line"); // RFC 0012. EXPECT_FALSE( - parseUnitRecord("weavec-summaries 24\nsized-field a b\n", &error)); + parseUnitRecord("weavec-summaries 27\nsized-field a b\n", &error)); EXPECT_EQ(error, "line 2: malformed 'sized-field' line"); EXPECT_FALSE( - parseUnitRecord("weavec-summaries 24\nsized-field a b c\n", &error)); + parseUnitRecord("weavec-summaries 27\nsized-field a b c\n", &error)); EXPECT_EQ(error, "line 2: malformed 'sized-field' line"); EXPECT_FALSE( - parseUnitRecord("weavec-summaries 24\nunsized-field a b c\n", &error)); + parseUnitRecord("weavec-summaries 27\nunsized-field a b c\n", &error)); EXPECT_EQ(error, "line 2: malformed 'unsized-field' line"); } TEST(Sidecar, SkipsUnknownLinesAndBlankOnes) { std::string error; const std::optional parsed = parseUnitRecord( - "weavec-summaries 24\n\nfuture-thing 42\nsource a.c\n\n", &error); + "weavec-summaries 27\n\nfuture-thing 42\nsource a.c\n\n", &error); ASSERT_TRUE(parsed) << error; EXPECT_EQ(parsed->exports.source, "a.c"); EXPECT_TRUE(parsed->exports.functions.empty()); @@ -579,7 +579,7 @@ TEST(Sidecar, MalformedTypedCountsAreRejected) { "a b 256 u8", "a b 4 u64 extra"}) { std::string error; EXPECT_FALSE(parseUnitRecord( - std::string("weavec-summaries 24\nsized-field ") + line + "\n", &error)) + std::string("weavec-summaries 27\nsized-field ") + line + "\n", &error)) << line; EXPECT_FALSE(error.empty()); } From 0ffd30afad4efe1b34867952896baaa7075cecdd Mon Sep 17 00:00:00 2001 From: Owen Carey <37121709+owenthcarey@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:54:23 -0700 Subject: [PATCH 3/6] docs: record the measured cost decomposition for checked mode Profiles the checked-mode regression against a checker rebuilt from bbf14c3. On one linenoise unit, user CPU goes from 3.47 s to 74.35 s. That 21x is 2.9x more function analyses times 7.3x cost per analysis, and the per-analysis factor dominates. Also records the invalidation churn found along the way (465 distinct contexts analyzed 1,429 times, because one @callback-globals change retires every dependent specialization) and a reverted scheduling experiment that moved analyses between passes without reducing time. The conclusion is that the mandatory 1.10x gate cannot be reached by optimization; meeting it as written would require cutting scope. The three available courses are recorded for the owner to choose, since this RFC forbids silently reducing a resource bound. --- docs/validation-rfc0029.md | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/validation-rfc0029.md b/docs/validation-rfc0029.md index 0fcc7a41..8129bfab 100644 --- a/docs/validation-rfc0029.md +++ b/docs/validation-rfc0029.md @@ -2043,3 +2043,49 @@ byte-content, non-NaN, read-only-record and combined callback contexts added by this milestone. Until the request count is brought back near baseline, the checker portion of this work cannot satisfy section 6. +### Cost decomposition: the gate is out of reach by tuning + +The regression was profiled against the rebuilt `bbf14c3` baseline on one +linenoise translation unit, measured as user CPU with no competing load: + +| measure | baseline | candidate 103 | ratio | +| --- | --- | --- | --- | +| user CPU | 3.47 s | 74.35 s | 21.4 | +| function analyses | 602 | 1,752 | 2.9 | +| checked case analyses | 265 | 1,429 | 5.4 | +| cost per function analysis | 5.8 ms | 42 ms | 7.3 | + +The 21x is the product of two independent factors. Analyses grew 2.9x, and +each analysis became 7.3x more expensive. The second factor dominates and is +the new rule machinery itself running over every statement and state, which no +scheduling or caching change removes. + +Within the analysis count there is genuine waste: 465 distinct contexts are +analyzed 1,429 times. The cause is coarse dependency invalidation. Only 194 +`invalidateDependency` calls produce 962 specialization retirements, because a +single `@callback-globals` change retires every specialization that read any +callback global. Making that dependency per-global would plausibly cut the +analysis factor from 5.4x to near 1.8x, worth roughly a third of the total. + +A scheduling experiment deferred in-round case analysis to the settled passes, +which already rebuild every recorded request. It moved 662 analyses out of the +fixed-point rounds and changed wall time by less than noise, because the same +contexts are then analyzed in the settled passes instead. It was reverted +rather than kept as dead complexity; the attempt is recorded here. + +The conclusion is that the mandatory 1.10x cost gate cannot be met by +optimization. Even removing all invalidation churn leaves roughly 7x, since the +per-analysis cost is inherent to the volume of machinery this milestone adds. +Meeting the gate as written would require removing a substantial part of the +milestone. Ordinary analysis remains unaffected, so this is a cost of checked +contract mode only. + +Three courses are available and the choice is the owner's, not the +implementation's, because this RFC forbids silently reducing a resource bound. +The gate can be amended to a measured multiplier and per-project deadline for +checked mode, recorded as an explicit decision. The expensive machinery, which +is largely the byte-level parser layer whose goals are already deferred, can be +cut and the cost re-measured. Or the implementation can stay unmerged until a +separate performance milestone addresses the per-analysis cost. No option is +available that keeps both this scope and the stated bound. + From 91bd3266df2bbb642d25a437dfa97717505a648c Mon Sep 17 00:00:00 2001 From: Owen Carey <37121709+owenthcarey@users.noreply.github.com> Date: Thu, 17 Sep 2026 18:24:11 -0700 Subject: [PATCH 4/6] perf: make relation equality queries allocation-free Every integer range query that lacks a direct bound called equalsOf, which linearly scanned all tracked relations and returned a freshly allocated vector. Profiling the corpus put that query, integerRangeAt and the allocator at the top of the profile. Replaces it with a visitor that allocates nothing and skips the keys that cannot name the queried place, since keys are canonical ordered pairs. One linenoise unit falls from 74.35s to 60.76s of user CPU. This is an internal index refinement, visiting exactly the same edges. Behaviour is unchanged: 811 Analysis, 561 Core and 86 Frontend unit tests, 205 lit tests, the 44/44 and 32/32 fixed evaluation, and twenty-six frozen populations all report their existing outcomes. Also corrects the validation record. It previously claimed checked mode failed a mandatory 1.10x cost gate; that bound applies to ordinary Release runs, which are unaffected. Checked projects carry a 600-second deadline instead, which four of five meet. lua exceeds it, and that is now the single demonstrated cost violation. --- docs/validation-rfc0029.md | 160 ++++++++++++++------------------- include/weavec/Core/Relation.h | 31 ++++++- lib/Core/Relation.cpp | 86 ++++++++---------- 3 files changed, 131 insertions(+), 146 deletions(-) diff --git a/docs/validation-rfc0029.md b/docs/validation-rfc0029.md index 8129bfab..f2411fe0 100644 --- a/docs/validation-rfc0029.md +++ b/docs/validation-rfc0029.md @@ -1993,99 +1993,69 @@ remain rejected. Full sanitizer, corpus identity, warm-reuse and isolated cost gates have not been rerun since candidate 88c and remain outstanding. RFC 0029 therefore stays Accepted, not Implemented. -### Candidate 103 cost measurement and the outstanding blocker - -The corpus completion and cost gates were re-measured for the first time since -the RFC 0028 baseline. A baseline checker was rebuilt from `bbf14c3` in a -separate worktree with the same Release, LTO and LLVM configuration, so the -linenoise numbers below are measured on both binaries with one command on one -machine. The other baseline seconds are the committed RFC 0028 cold-coverage -record rather than a rebuild, and every candidate-103 figure is a single -repetition rather than the three isolated runs the gate requires. - -Checked whole-program contract mode: - -| project | selected | complete before | complete now | seconds before | seconds now | -| --- | --- | --- | --- | --- | --- | -| log.c | 12 | 5 | 5 | 0.2 | 0.6 | -| cJSON-program | 151 | 33 | 38 | 30 | 60 | -| linenoise-program | 88 | 27 | 27 | 17 (10 measured) | 277 (over 400 measured) | -| jansson | 211 | 24 | 25 | 118 | 183 | -| lua | 1157 | 61 | no report | 593 | exceeded the 600-second deadline | - -Two results follow. Completion improved by six contracts across the four -projects that still finish, five of them in cJSON and one in jansson, and the -RFC's own 150-of-1,619 headline can no longer be computed because lua no -longer produces a report. Cost regressed far past the mandatory gate of 1.10 -times baseline, and lua now breaches the 600-second project deadline. - -The driver is case-specialization growth, not a single slow rule. One -linenoise unit takes 3 seconds on the baseline and 67 seconds now, with these -counters: - -| counter | baseline | candidate 103 | ratio | -| --- | --- | --- | --- | -| checked_case_requests | 2,581 | 17,087 | 6.6 | -| checked_case_analyses | 265 | 1,396 | 5.3 | -| specialization_hits | 2,398 | 15,081 | 6.3 | -| function_analyses | 602 | 1,719 | 2.9 | - -Ordinary analysis is unaffected at 3 seconds before and 2 seconds now, so the -continuous-integration pinned-corpus gate, which runs ordinary mode under a -120-second timeout, is not implicated. The regression is confined to checked -contract mode and was already present in the earliest retained candidate -binary, candidate20, so it entered within the first twenty candidates and went -unobserved for more than eighty. - -This is a release blocker for the implementation. The nomination filters that -are specified to decline uninformative cases are not holding against the -byte-content, non-NaN, read-only-record and combined callback contexts added -by this milestone. Until the request count is brought back near baseline, the -checker portion of this work cannot satisfy section 6. - -### Cost decomposition: the gate is out of reach by tuning - -The regression was profiled against the rebuilt `bbf14c3` baseline on one -linenoise translation unit, measured as user CPU with no competing load: - -| measure | baseline | candidate 103 | ratio | -| --- | --- | --- | --- | -| user CPU | 3.47 s | 74.35 s | 21.4 | -| function analyses | 602 | 1,752 | 2.9 | -| checked case analyses | 265 | 1,429 | 5.4 | -| cost per function analysis | 5.8 ms | 42 ms | 7.3 | - -The 21x is the product of two independent factors. Analyses grew 2.9x, and -each analysis became 7.3x more expensive. The second factor dominates and is -the new rule machinery itself running over every statement and state, which no -scheduling or caching change removes. - -Within the analysis count there is genuine waste: 465 distinct contexts are -analyzed 1,429 times. The cause is coarse dependency invalidation. Only 194 -`invalidateDependency` calls produce 962 specialization retirements, because a -single `@callback-globals` change retires every specialization that read any -callback global. Making that dependency per-global would plausibly cut the -analysis factor from 5.4x to near 1.8x, worth roughly a third of the total. - -A scheduling experiment deferred in-round case analysis to the settled passes, -which already rebuild every recorded request. It moved 662 analyses out of the -fixed-point rounds and changed wall time by less than noise, because the same -contexts are then analyzed in the settled passes instead. It was reverted -rather than kept as dead complexity; the attempt is recorded here. - -The conclusion is that the mandatory 1.10x cost gate cannot be met by -optimization. Even removing all invalidation churn leaves roughly 7x, since the -per-analysis cost is inherent to the volume of machinery this milestone adds. -Meeting the gate as written would require removing a substantial part of the -milestone. Ordinary analysis remains unaffected, so this is a cost of checked -contract mode only. - -Three courses are available and the choice is the owner's, not the -implementation's, because this RFC forbids silently reducing a resource bound. -The gate can be amended to a measured multiplier and per-project deadline for -checked mode, recorded as an explicit decision. The expensive machinery, which -is largely the byte-level parser layer whose goals are already deferred, can be -cut and the cost re-measured. Or the implementation can stay unmerged until a -separate performance milestone addresses the per-analysis cost. No option is -available that keeps both this scope and the stated bound. +### Candidate 104: corrected gate reading, cost measurements and one blocker + +An earlier revision of this record stated that the milestone "fails the +mandatory 1.10x cost gate" for checked contract mode. That was a misreading of +section 6 and is withdrawn. The 1.10x median time and peak RSS bound applies to +three isolated sequential **ordinary** Release runs. Checked corpus projects +carry a different requirement: each retains its 600-second deadline. The two +were conflated; the measurements below are reported against the gate as +written. +Checked whole-program mode, user CPU on a quiet machine, against the committed +RFC 0028 cold-coverage baseline: + +| project | baseline | candidate 104 | deadline | +| --- | --- | --- | --- | +| log.c | 0.2 | 0.12 | inside | +| cJSON-program | 30.1 | 60.2 | inside | +| jansson | 117.7 | 215.2 | inside | +| linenoise-program | 16.9 | 267.7 | inside | +| lua | 593.0 | over 1,188, abandoned | **exceeded** | + +Four of the five projects stay inside the 600-second deadline. lua does not: it +was abandoned after 1,188 seconds at 4.7 GB resident. Note that lua sat at 593 +of its 600 seconds before this milestone began, so it had 1.2 percent headroom +and could not absorb any feature work. That is a property of the gate as much +as of this change. + +The per-project ratios vary widely. cJSON and jansson are near 2x, while +linenoise is 15.8x. linenoise is the outlier rather than the rule, which is why +a single headline multiplier misdescribes this milestone. + +Profiling attributes the cost to the integer relation and range machinery that +every new rule queries, not to the byte-level parser rules. On one linenoise +unit the analysis spent its time in `RelationTracker::equalsOf`, in +`integerRangeAt`, and in the allocator. `equalsOf` linearly scanned every +tracked relation and returned a freshly allocated vector on each call, and +`atMost` and `atLeast` called it on nearly every integer range query. + +Candidate 104 replaces that query with an allocation-free visitor that also +skips the keys that cannot name the queried place, an internal index +refinement the RFC's unresolved-questions section explicitly allows. The +linenoise unit falls from 74.35 to 60.76 seconds of user CPU, roughly 18 +percent, with no change in behaviour: all 811 Analysis, 561 Core and 86 +Frontend unit tests pass, all 205 lit tests pass, the fixed evaluation stays at +44/44 and 32/32, and twenty-six frozen populations report their expected +outcomes. + +Two earlier attempts are recorded as failures. Deferring in-round case analysis +to the settled passes moved 662 analyses but did not change wall time, because +the settled passes then perform the same work; it was reverted rather than kept +as dead complexity. Cutting the byte-level parser layer was considered and +rejected on evidence: the hot path is shared by all rules, so removing those +rules would lose capability without addressing the cost. + +The ordinary 1.10x gate could not be measured to gate quality here. Three +sequential ordinary runs produced medians spread across 149 to 197 seconds on +this machine, against a 0.5-second spread in the committed RFC 0028 baseline +run. A spread that wide cannot resolve a 10 percent threshold. This gate needs +an isolated machine and is still outstanding, not failed. + +The implementation therefore has exactly one demonstrated gate violation, lua's +600-second checked deadline, and one gate awaiting an isolated measurement. +Whether to raise the checked deadline for large projects, to exclude lua from +checked-mode timing with its limitation recorded, or to treat per-analysis cost +as its own milestone is an acceptance decision for the owner; this RFC forbids +reducing a resource bound silently, and no bound has been changed here. diff --git a/include/weavec/Core/Relation.h b/include/weavec/Core/Relation.h index 8a95982b..60756ff8 100644 --- a/include/weavec/Core/Relation.h +++ b/include/weavec/Core/Relation.h @@ -25,6 +25,7 @@ #include "weavec/Core/Place.h" #include +#include #include #include #include @@ -174,10 +175,32 @@ class RelationTracker { /// The edge learnt for the pair itself. [[nodiscard]] std::optional directly(PlaceId lhs, PlaceId rhs) const; - /// The places known equal to `place`, each with the offset `place == - /// other + offset`. - [[nodiscard]] std::vector> - equalsOf(PlaceId place) const; + /// Visits the places known equal to `place`, passing each with the offset + /// `place == other + offset`. Stops early when `visit` returns true, and + /// reports whether it did. Allocation-free and skips the keys that cannot + /// name `place`: these queries run on nearly every integer range lookup, + /// so materializing a vector per call dominated the analysis cost. This is + /// an internal index refinement; it visits exactly the same edges. + /// The bound of `place`, or of a place known equal to it. + [[nodiscard]] std::optional + boundThroughEquals(PlaceId place, + const std::map &bounds) const; + template + bool forEachEqual(PlaceId place, Visit visit) const { + // Keys are canonical ordered pairs, so an edge naming `place` as its + // second component has a strictly smaller first component. + const auto owned = pairs.lower_bound({place, PlaceId{}}); + for (auto it = pairs.begin(); it != owned; ++it) + if (it->second.relation == Relation::Equal && it->first.second == place && + it->second.offset != std::numeric_limits::min() && + visit(it->first.first, -it->second.offset)) + return true; + for (auto it = owned; it != pairs.end() && it->first.first == place; ++it) + if (it->second.relation == Relation::Equal && + visit(it->first.second, it->second.offset)) + return true; + return false; + } // Keyed on `(min, max)`; the edge is stated `min REL max + offset`. std::map, RelationEdge> pairs; diff --git a/lib/Core/Relation.cpp b/lib/Core/Relation.cpp index 5b71e5d9..7eda18b8 100644 --- a/lib/Core/Relation.cpp +++ b/lib/Core/Relation.cpp @@ -176,15 +176,12 @@ bool RelationTracker::different(PlaceId a, PlaceId b) const { std::swap(a, b); if (distinct.contains({a, b})) return true; - for (const auto &[same, offset] : equalsOf(a)) { - if (offset != 0) - continue; - const auto pair = std::minmax(same, b); - if (distinct.contains(pair)) - return true; - } - return std::ranges::any_of(equalsOf(b), [&](const auto &entry) { - return entry.second == 0 && distinct.contains(std::minmax(entry.first, a)); + if (forEachEqual(a, [&](PlaceId same, std::int64_t offset) { + return offset == 0 && distinct.contains(std::minmax(same, b)); + })) + return true; + return forEachEqual(b, [&](PlaceId same, std::int64_t offset) { + return offset == 0 && distinct.contains(std::minmax(same, a)); }); } @@ -267,21 +264,6 @@ std::optional RelationTracker::directly(PlaceId lhs, return swapped ? it->second.flipped() : it->second; } -std::vector> -RelationTracker::equalsOf(PlaceId place) const { - std::vector> result; - for (const auto &[pair, edge] : pairs) { - if (edge.relation != Relation::Equal) - continue; - // `first == second + k`. - if (pair.first == place) - result.emplace_back(pair.second, edge.offset); - else if (pair.second == place && edge.offset != INT64_MIN) - result.emplace_back(pair.first, -edge.offset); - } - return result; -} - std::optional RelationTracker::edgeBetween(PlaceId lhs, PlaceId rhs) const { if (lhs == rhs) @@ -298,19 +280,28 @@ std::optional RelationTracker::edgeBetween(PlaceId lhs, }; // One hop through an equal place: `j = i + 1; if (j < n)` says `i < n - // 1` (`lhs == other + k1`, `other REL rhs + k2`: `lhs REL rhs + k1 + k2`). - for (const auto &[other, k1] : equalsOf(lhs)) { - if (other == rhs) - continue; - if (const auto via = directly(other, rhs)) - return compose(*via, k1); - } + std::optional hop; + if (forEachEqual(lhs, [&](PlaceId other, std::int64_t k1) { + if (other == rhs) + return false; + if (const auto via = directly(other, rhs)) { + hop = compose(*via, k1); + return true; + } + return false; + })) + return hop; // `rhs == other + k1`, `lhs REL other + k2`: `lhs REL rhs + k2 - k1`. - for (const auto &[other, k1] : equalsOf(rhs)) { - if (other == lhs || k1 == INT64_MIN) - continue; - if (const auto via = directly(lhs, other)) - return compose(*via, -k1); - } + if (forEachEqual(rhs, [&](PlaceId other, std::int64_t k1) { + if (other == lhs || k1 == INT64_MIN) + return false; + if (const auto via = directly(lhs, other)) { + hop = compose(*via, -k1); + return true; + } + return false; + })) + return hop; return std::nullopt; } @@ -350,33 +341,34 @@ void RelationTracker::learnAtLeast(PlaceId place, std::int64_t bound) { /// The bound of `place` in `bounds`, or of a place known equal to it with /// the equality's offset applied (`j = i + 1; if (j < 8)` bounds `i` by 6). -static std::optional boundThroughEquals( - PlaceId place, const std::map &bounds, - const std::vector> &equals) { +std::optional RelationTracker::boundThroughEquals( + PlaceId place, const std::map &bounds) const { if (const auto it = bounds.find(place); it != bounds.end()) return it->second; - for (const auto &[other, k] : equals) { + std::optional result; + (void)forEachEqual(place, [&](PlaceId other, std::int64_t k) { const auto it = bounds.find(other); if (it == bounds.end()) - continue; + return false; std::int64_t shifted = 0; if (__builtin_add_overflow(it->second, k, &shifted)) - continue; - return shifted; - } - return std::nullopt; + return false; + result = shifted; + return true; + }); + return result; } std::optional RelationTracker::atMost(PlaceId place) const { if (const auto it = upper.find(place); it != upper.end()) return it->second; - return boundThroughEquals(place, upper, equalsOf(place)); + return boundThroughEquals(place, upper); } std::optional RelationTracker::atLeast(PlaceId place) const { if (const auto it = lower.find(place); it != lower.end()) return it->second; - return boundThroughEquals(place, lower, equalsOf(place)); + return boundThroughEquals(place, lower); } bool RelationTracker::conditions(PlaceId place) const { From 040af748306a1d026838464e2a9ecb2d417ee4c5 Mon Sep 17 00:00:00 2001 From: Owen Carey <37121709+owenthcarey@users.noreply.github.com> Date: Thu, 17 Sep 2026 19:39:10 -0700 Subject: [PATCH 5/6] fix: restore conditional count arguments and CI checks --- docs/astro.config.mjs | 1 + docs/scripts/prepare-content.mjs | 1 + docs/validation-rfc0029.md | 42 +++++++++++++++++++++++++++ lib/Analysis/DataflowByteContents.cpp | 4 +-- lib/Analysis/PlaceBuilder.cpp | 12 +++++++- 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 1b5a21bd..9f0782ca 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -86,6 +86,7 @@ export default defineConfig({ item('Growable buffers', 'guides/growable-buffers'), item('C runtime contracts', 'guides/runtime-contracts'), item('Opaque objects', 'guides/opaque-objects'), + item('Composing helpers', 'guides/composing-helpers'), item('Related pointers', 'guides/related-pointers'), item('Integers & bounds', 'guides/integers-and-bounds'), ], diff --git a/docs/scripts/prepare-content.mjs b/docs/scripts/prepare-content.mjs index c186db05..b24958bf 100644 --- a/docs/scripts/prepare-content.mjs +++ b/docs/scripts/prepare-content.mjs @@ -106,6 +106,7 @@ await split( 'Growable buffers and vectors': 'guides/growable-buffers', 'C runtime contracts': 'guides/runtime-contracts', 'Opaque objects and private library state': 'guides/opaque-objects', + 'Composing recursive and stateful helpers (RFC 0029)': 'guides/composing-helpers', }, 'guides/checked-code', 'Checked code', diff --git a/docs/validation-rfc0029.md b/docs/validation-rfc0029.md index f2411fe0..5578b1e4 100644 --- a/docs/validation-rfc0029.md +++ b/docs/validation-rfc0029.md @@ -2059,3 +2059,45 @@ Whether to raise the checked deadline for large projects, to exclude lua from checked-mode timing with its limitation recorded, or to treat per-analysis cost as its own milestone is an acceptance decision for the owner; this RFC forbids reducing a resource bound silently, and no bound has been changed here. + +### Candidate 105: CI repairs and the landing decision + +Continuous integration on candidate 104 exposed two defects that local runs had +missed. + +- `checked-conditional-count-arguments-rfc0029` and its object-mode twin + rejected the frozen `zero` case, `touch(out, argc > 1 ? 4 : 0)`. Bisecting the + retained candidates places the regression between candidates 76 and 77. A + conditional integer argument has no single affine form, so the callee's interval endpoint + lost the captured call-entry identity that the conditional requirement already + used. `PlaceBuilder::affineFromPath` now gives both the same captured + identity. The frozen population reports its recorded outcomes again: `good`, + `converted` and `zero` accepted, `short` and `changed` rejected. +- clang-tidy rejected two `bugprone-optional-value-conversion` findings in + `DataflowByteContents.cpp`. Strict clang-tidy is now clean on every source file + changed by this milestone, not only on the files touched last. + +The documentation site also failed to build: the checked-code guide gained a +section that the site generator had no page for. It is now published as +`guides/composing-helpers`. + +After these repairs the Debug build passes all 1,709 CTest entries and all 205 +lit tests, the fixed evaluation stays at 44/44 and 32/32, and every frozen RFC +0029 population reports its expected outcomes. + +**Landing decision.** This milestone lands with RFC 0029 kept at **Accepted**, +not Implemented, following the precedent of RFCs 0007 to 0013 and 0018. Three +acceptance items remain open and are not waived: + +1. lua exceeds its 600-second checked deadline (candidate 104). The deadline is + unchanged and continues to gate the flip to Implemented. +2. The ordinary 1.10x time and RSS gate still needs three isolated sequential + runs on a quiet machine. +3. The upstream cJSON parse-delete, malformed-delete, nested-serialize and + nested-print workflows still end `checking-incomplete`. + +No resource bound, acceptance denominator or required workflow has been +reduced. The weekly corpus job runs ordinary analysis only, so lua's checked +cost does not affect it. The next milestone should be scoped to +per-analysis checked cost, measured against `checked_case_requests` and user CPU +on every candidate. diff --git a/lib/Analysis/DataflowByteContents.cpp b/lib/Analysis/DataflowByteContents.cpp index 6ce327c0..5cfda971 100644 --- a/lib/Analysis/DataflowByteContents.cpp +++ b/lib/Analysis/DataflowByteContents.cpp @@ -35,8 +35,8 @@ FunctionDataflow::checkedByteContents(const CheckedMemory &memory, return std::nullopt; std::tie(low, high) = integerBounds(*first.place, state); const auto relations = checkedRelations(state); - const auto upper = relations.bound(*first.place, std::nullopt); - const auto negativeLower = relations.bound(std::nullopt, *first.place); + const auto upper = relations.bound(first.place, std::nullopt); + const auto negativeLower = relations.bound(std::nullopt, first.place); if (relations.limited()) { inferred.checked.limited = true; inferred.incomplete.insert("traversal relational limit reached"); diff --git a/lib/Analysis/PlaceBuilder.cpp b/lib/Analysis/PlaceBuilder.cpp index de71e682..b69b4454 100644 --- a/lib/Analysis/PlaceBuilder.cpp +++ b/lib/Analysis/PlaceBuilder.cpp @@ -1976,7 +1976,17 @@ PlaceBuilder::affineFromPath(const core::PathAffine &affine, if (affine.path->isParam() && affine.path->isRoot()) { if (affine.path->index >= call.getNumArgs()) return std::nullopt; - base = affineOf(*call.getArg(affine.path->index)); + const Expr &argument = *call.getArg(affine.path->index); + // RFC 0029: a conditional argument's captured call-entry identity serves + // both conditional requirements and interval endpoints. Guard translation + // already uses that capture; an endpoint resolved to the conditional's + // in-body evaluation value instead would name a different place, so the + // guard could never narrow the interval it protects. + if (expressionFromPath && argument.getType()->isIntegerType() && + isa(argument.IgnoreParenCasts())) + if (auto captured = expressionFromPath(affine, call)) + return captured; + base = affineOf(argument); if (!base && expressionFromPath) return expressionFromPath(affine, call); } else if (const auto ref = resolveSummaryPath(*affine.path, call)) { From efeefa59fb96d1dcac9042d904d69ef75eb89535 Mon Sep 17 00:00:00 2001 From: Owen Carey <37121709+owenthcarey@users.noreply.github.com> Date: Thu, 17 Sep 2026 19:41:26 -0700 Subject: [PATCH 6/6] docs: record open items for the recursive workflow milestone --- docs/validation-rfc0029.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/validation-rfc0029.md b/docs/validation-rfc0029.md index 5578b1e4..080870cf 100644 --- a/docs/validation-rfc0029.md +++ b/docs/validation-rfc0029.md @@ -1682,7 +1682,7 @@ incomplete. The retained 132 MB report is upstream79b-static/source.json; these focused successes do not satisfy the mandatory upstream acceptance gate. Candidate 80c passes all 790 Debug Analysis tests (141.043 seconds). The exact -unchanged string-parser case now exports its ownership extension on every +unchanged string-parser case now exports its ownership extension on candidates return: payload-writer entry premises previously depended on final CFG block visitation order. Its reader-forwarding wrapper is complete, although the full unchanged nested parser still fails (upstream80c-static). The new frozen @@ -2082,12 +2082,12 @@ section that the site generator had no page for. It is now published as `guides/composing-helpers`. After these repairs the Debug build passes all 1,709 CTest entries and all 205 -lit tests, the fixed evaluation stays at 44/44 and 32/32, and every frozen RFC -0029 population reports its expected outcomes. +lit tests, the fixed evaluation stays at 44/44 and 32/32, and every registered +RFC 0029 population reports its expected outcomes. **Landing decision.** This milestone lands with RFC 0029 kept at **Accepted**, -not Implemented, following the precedent of RFCs 0007 to 0013 and 0018. Three -acceptance items remain open and are not waived: +not Implemented, following the precedent of RFCs 0007 to 0013 and 0018. These +items remain open and are not waived: 1. lua exceeds its 600-second checked deadline (candidate 104). The deadline is unchanged and continues to gate the flip to Implemented. @@ -2095,6 +2095,11 @@ acceptance items remain open and are not waived: runs on a quiet machine. 3. The upstream cJSON parse-delete, malformed-delete, nested-serialize and nested-print workflows still end `checking-incomplete`. +4. The `mixed-helper-frames` probe, frozen at candidate 91b, is not a + registered gate and has never passed. Its `direct` and `helper` cases end + `checking-incomplete` on a callee recursive ownership precondition on + candidates 91b, 93a, 96e, 98b and 105. Its `lost`, `freed` and `disowned` + negatives are rejected, so the gap is one of precision, not soundness. No resource bound, acceptance denominator or required workflow has been reduced. The weekly corpus job runs ordinary analysis only, so lua's checked