Skip to content

Add "Heap reference monotone" post condition for procedures that modify the Heap#1099

Open
thanhnguyen-aws wants to merge 8 commits intostrata-org:mainfrom
thanhnguyen-aws:heapcountermono
Open

Add "Heap reference monotone" post condition for procedures that modify the Heap#1099
thanhnguyen-aws wants to merge 8 commits intostrata-org:mainfrom
thanhnguyen-aws:heapcountermono

Conversation

@thanhnguyen-aws
Copy link
Copy Markdown
Contributor

@thanhnguyen-aws thanhnguyen-aws commented May 1, 2026

Issue #, if available: #1098

Problem

When two distinct class instances are allocated on the heap, the analyzer cannot prove that their heap references are different. For example:

class ClassA:
    def __init__(self, n: int):
        self.val : int = n

a1 = ClassA(1)
a2 = ClassA(2)

a1.val = 1
a2.val = 2

assert a1.val != a2.val  # ❓ unknown — should be ✅ pass

Root cause

During the heap-parameterization pass (HeapParameterization.lean), every procedure that writes to the heap is rewritten to accept an input heap ($heap_in) and produce an output heap ($heap). However, there was no postcondition constraining the relationship between the two heaps' internal reference counters. Without such a constraint, the verifier cannot determine that references returned by separate allocations are distinct — a1 and a2 could be assigned the same heap reference, making a1.val != a2.val unprovable.

Solution

This PR injects a "heap reference counter monotone" postcondition into every heap-writing procedure during the heapTransformProcedure pass in HeapParameterization.lean.

How it works

Three new expressions are constructed before the body match:

Expression Definition Purpose
inHeapRef Heap..nextReference!($heap_in) The reference counter of the input heap state
outHeapRef Heap..nextReference!($heap) The reference counter of the output heap state
monoCond outHeapRef >= inHeapRef A Condition asserting the counter never decreases, with summary "Heap reference counter monotone"

This monoCond is prepended to the postcondition list of both Opaque and Abstract body variants. The other body kinds are unaffected:

  • Transparent — exposes its full implementation to the verifier, which can derive the monotonicity property directly from the body.
  • External — has no heap parameters, so the postcondition does not apply.

The postcondition tells the verifier that each heap-writing call advances (or at least preserves) the heap's reference counter. Since each allocation increments this counter, references obtained from different calls are guaranteed to be distinct.

Note: The base branch already disallows transparent bodies on non-functional procedures (merged in #1076), so Python functions (including __main__) are emitted with Opaque bodies. This ensures the monotonicity postcondition is present at every call boundary where heap allocations occur. An earlier revision of this PR included a PythonToLaurel.lean change to force Opaque bodies, but that was reverted since #1076 made it unnecessary.

Changes

File Change
Strata/Languages/Laurel/HeapParameterization.lean Construct monoCond and prepend it to Opaque and Abstract postcondition lists
StrataTest/Languages/Python/tests/test_heap_reference_monotone.py New test: the motivating example from #1098 — two ClassA instances with different values, asserting a1.val != a2.val
StrataTest/Languages/Python/run_py_analyze_sarif.py Register the new test
StrataTest/Languages/Python/expected_laurel/test_heap_reference_monotone.expected Expected output for the new test (5 passed, 0 failed, 0 inconclusive)
StrataTest/Languages/Python/expected_interpret/test_heap_reference_monotone.expected Expected interpreter output for the new test
18 existing expected_laurel/*.expected files Updated to include the additional "Heap reference counter monotone" verification results now emitted by the analyzer

Testing

  • New test (test_heap_reference_monotone.py): The motivating example from Python Class objects does not have unique Heap reference #1098. Two ClassA instances are allocated with different values, and assert a1.val != a2.val is verified successfully — the assertion now passes thanks to the monotonicity postcondition establishing that a1 and a2 have distinct heap references.
  • Existing tests: 18 expected-output files are updated to reflect the additional "Heap reference counter monotone" results now emitted by the analyzer. All changes are additive — no previously passing assertions regress.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@thanhnguyen-aws
Copy link
Copy Markdown
Contributor Author

@keyboardDrummer-bot Please rewrite and add more details to the PR description.

@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator

I've rewritten the PR description. Key improvements:

  • Removed the section about the PythonToLaurel.lean change, which was reverted and is no longer part of this PR. Added a note explaining that Various Laurel resolver improvements - including disallowing transparent procedures #1076 (already merged into main) made that change unnecessary.
  • Added a table summarizing the three new expressions (inHeapRef, outHeapRef, monoCond) and their purposes.
  • Added a file-level change summary table covering all 25 changed files.
  • Clarified which body variants are affected (Opaque, Abstract) and why the others (Transparent, External) are not.


procedure modifyContainerWithoutPermission1(c: Container, d: Container)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: modifies clause does not hold
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: modifies clause could not be proved
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message changed from "modifies clause does not hold" to "modifies clause could not be proved". The former means the SMT solver found a counterexample; the latter means it timed out or returned unknown.

This is a diagnostic quality regression caused by the additional monotonicity postcondition making the VC harder for the solver. The same regression occurs at lines 130 and 155.

Please investigate whether the solver timeout can be avoided — e.g., by increasing the timeout for these VCs, or by structuring the postcondition differently so it doesn't interfere with the solver's ability to find counterexamples for unrelated assertions.

Comment thread Strata/Languages/Laurel/HeapParameterization.lean Outdated
Comment thread Strata/Languages/Laurel/HeapParameterization.lean
Comment thread StrataTest/Languages/Python/tests/test_heap_reference_monotone.py
@@ -0,0 +1,7 @@
unknown location: ✅ pass - Heap reference counter monotone
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unknown location prefix confirms that the synthesized postcondition has no source location. This will be confusing for users when the monotonicity check fails — they won't know which procedure it relates to.

Also: this test has two Heap reference counter monotone results (one for ClassA@__init__ and one for __main__). In a program with many classes, this could produce a lot of noise. Consider whether the monotonicity postcondition should be suppressed from user-facing output (e.g., by marking it as an internal/infrastructure check).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants