Skip to content

feat: Translate PySpec preconditions to Laurel assertions for bug finding#615

Draft
MikaelMayer wants to merge 29 commits intomainfrom
feat/pyspec-precondition-assertions
Draft

feat: Translate PySpec preconditions to Laurel assertions for bug finding#615
MikaelMayer wants to merge 29 commits intomainfrom
feat/pyspec-precondition-assertions

Conversation

@MikaelMayer
Copy link
Contributor

Translate PySpec preconditions into Laurel assertions for bug-finding verification.

When a PySpec function has assert preconditions (e.g. assert len(kwargs["name"]) >= 1),
the generated Laurel procedure now contains Assert statements instead of an opaque body.
After inlining at call sites, the solver can prove whether preconditions are satisfied
or always violated — detecting real bugs statically.

Changes:

  • Specs/ToLaurel.lean: Translate SpecExpr to Laurel StmtExpr using Core prelude
    functions (Str.Length, Any..as_string!, Any..as_int!, from_int,
    DictStrAny_contains). Untranslatable preconditions emit assert true with a
    diagnostic. kwargs["field"] references resolve to expanded parameter names.
  • PySpecPipeline.lean: Thread type aliases (unprefixed → prefixed class names) so
    Python type annotations resolve to pyspec composite types.
  • PythonToLaurel.lean: Resolve type aliases for composite type creation and method
    dispatch. Emit StaticCall instead of InstanceCall when the method resolves to a
    known pyspec procedure.
  • StrataMain.lean: Add procedure inlining to pyAnalyzeLaurel so pyspec assertions
    are checked at call sites with concrete arguments.

Regex preconditions (string_to_regex, regex_matches) and quantifier preconditions
(forallList, forallDict) are not yet translated (emit assert true).

…ding

Translate SpecExpr preconditions into Assert statements in generated
Laurel procedures. Wire up type alias resolution so Python type
annotations resolve to pyspec composite types, and emit StaticCall
for known pyspec methods. Add procedure inlining to pyAnalyzeLaurel
so assertions are checked at call sites with concrete arguments.
Two test cases:
- test_precondition_pass: valid args satisfy all preconditions (3 always true)
- test_precondition_fail: empty body violates len >= 1 (1 always false detected)
Check Core program structure via string inspection instead of running
the solver, avoiding sorry dependencies from ProcedureInlining.
The hasModel check was too broad — it matched composite type names,
causing method calls like Resource.get_value to be emitted as StaticCall
instead of InstanceCall. Now only checks preludeProcedures.
Inlining all procedures changes verification output for existing tests.
Inlining should be opt-in or conditional on pyspec usage.
Copy link
Contributor Author

@MikaelMayer MikaelMayer left a comment

Choose a reason for hiding this comment

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

The precondition translation is well-structured: specExprToLaurel cleanly maps each SpecExpr constructor to Laurel, buildSpecBody assembles the procedure body, and the type alias plumbing through the pipeline is consistent across all three files. The augmented assignment support is a nice bonus. Two comments, one of which is a bug.

The PR description mentions StrataMain.lean changes (procedure inlining in pyAnalyzeLaurel) but those are no longer in the diff — presumably already merged to main. The description also doesn't mention the augmented assignment feature (AugAssign support, test_augmented_assign, sarif skip). Worth updating the description to match the actual diff.

@@ -301,7 +413,18 @@ def funcDeclToLaurel (procName : String) (func : FunctionDecl)
| .TVoid => []
| _ => [{ name := "result", type := retType }]
if func.preconditions.size > 0 || func.postconditions.size > 0 then
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This condition fires even when only preconditions are present (no postconditions), emitting a misleading "Postconditions not yet supported" warning for functions that only have preconditions — which are now supported. Should be:

Suggested change
if func.preconditions.size > 0 || func.postconditions.size > 0 then
if func.postconditions.size > 0 then

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed — now only warns when postconditions are present.


/-- Build a procedure body that asserts preconditions and havocs outputs. -/
def buildSpecBody (preconditions : Array Assertion) (outputs : List Parameter)
: ToLaurelM (Body × Nat) := do
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nit: translatedCount is returned but always discarded by the caller (let (body, _) ← buildSpecBody ...). Consider either dropping it from the return type or using it for a diagnostic (e.g., logging how many preconditions were successfully translated vs. emitted as assert true).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dropped — buildSpecBody now returns just Body.

- Only warn about postconditions when they are actually present
- Drop unused translatedCount from buildSpecBody return type
- Resolve merge conflict with main (Hole fallback for InstanceCall)
@MikaelMayer
Copy link
Contributor Author

Both review comments addressed in commit 00c9e11:

  • Only warn about postconditions when they are actually present
  • Dropped unused translatedCount from buildSpecBody return type

Only inject procedure aliases, not composite type names. The composite
type resolution for pyspec classes happens via resolveTypeName in
refineFunctionCallExpr instead.
Restrict StaticCall emission to methods where the caller type has a
pyspec type alias, preventing dispatch-resolved types from being
incorrectly converted from Hole to StaticCall.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant