Fix namespace collision and SMACK assert encoding in BoogieToStrata#1149
Fix namespace collision and SMACK assert encoding in BoogieToStrata#1149PROgram52bc wants to merge 5 commits into
Conversation
1. Namespace collision (issue 1 in #1148): Constants that share a name with a procedure are now prefixed with __const_ in the Strata output. The rename is applied consistently in declarations and references. 2. Recursive synonym resolution (Fixes issue 2 in #1148): DealiasTypeExpr now recurses on its result, resolving synonym chains like ref := i64 := int fully to the base type. Previously it resolved only one level, causing panics on comparison/arithmetic operators applied to multi-level type synonyms. 3. SMACK assert encoding (issue 3 in #1148): Procedures named assert_.* now get a requires (arg != 0) precondition emitted. Call elimination generates VCs at each call site, making SMACK assertions verifiable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the separate _constantRenames/_procedureNames approach with a unified _renames dictionary populated by scanning all declarations upfront. Procedures are claimed first; constants, functions, and globals that collide get prefixed with their kind (__const_, __func_, __var_). Also restores InferModifies and Prune options in BoogieToStrata.cs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prune=true caused Boogie to remove declarations needed by axioms and quantifiers, shifting line numbers and breaking verification for tests like BooleanQuantification, Axioms, TypeSynonyms2, Quantifiers, and Lambda. InferModifies is retained as it's needed for SMACK support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tautschnig
left a comment
There was a problem hiding this comment.
Thanks for tackling all three #1148 issues in one PR — I ran the three fixes end-to-end on the included reproducers and each of them does what the description claims. The direction looks right, and I especially like that fix (3) follows atomb's suggestion of an opaque procedure with a precondition rather than inlining assert. I have a mix of blocking and non-blocking comments, grouped below:
Blocking
-
Test harness gap.
VerifyTestFileadds--checkwhen no.expectfile exists, which literally emitsSkipping verification.without invoking the solver. I re-ran the three new tests: with no.expect,SmackAssert.bplpasses CI even though the whole point of the test is thatcall $r := assert_.i32(0);should now fail verification. Please add.expectfiles for all three new tests (SmackAssert.expectcrucially must contain afailedline so the harness flipsexpectedExitCodeto 2 and actually catches regressions). -
Fix (2) has no regression test.
dealiasTypeExprnow recurses, but there is no.bpltest exercising a multi-level synonym chain (the issue's reproducerTypeSynonymComparison.core.stwasn't carried over). ExistingTypeSynonyms{1,2}.bplonly use single-level synonyms. Without a test, this fix has no CI coverage. A 10-line.bplusingtype i64 = int; type ref = i64;with<=/+overrefwould close this. -
Rename mechanism is not applied consistently. Four of the places that emit variable names still call
Name(...)instead ofNameOf(decl, ...). This is only triggered when a global gets renamed (i.e. its sanitized name collides with a proc/const/func), but once it does, the output is broken. Reproducer:const $a.b: int; axiom $a.b > 0; var $a_b: int; // both sanitize to _a_b procedure main() returns (r: int) modifies $a_b; { $a_b := 1; havoc $a_b; r := $a.b + $a_b; }
Produces (inout parameter name and havoc target still use the pre-rename identifier, while the identifier expression uses the renamed one):
procedure main(inout _a_b : int, out r : int) { _a_b := 1; havoc _a_b; r := (_a_b + __var__a_b); // __var__a_b is undefined }...and
strata verifyreportsUnknown expr identifier __var__a_b. See the inline comment atVisitCallCmdfor the full list of affected sites. -
StartsWith("assert_")is too permissive and triggers on user-written procedures. Reproducer:procedure assert_helper(p: int) returns (r: int); procedure main() returns (r: int) { call r := assert_helper(0); }
emits
requires (p != 0);onassert_helpereven though the user never wrote it. Any Boogie program with a procedure whose name happens to start withassert_(e.g.assert_helper,assert_invariant,assert_equal) would start failing verification. SMACK's actual pattern isassert_.<type>(literal dot); please tighten to match that specifically.
Non-blocking
InferModifies = trueisn't mentioned in the PR body. It's not just a knob — it runsModSetCollector(mutating the program) and, viaCheckModifies, suppresses modifies-clause typechecking. Please document the motivation and add a.bpltest with a procedure that mutates a global without an explicitmodifiesclause, so regressions in this new assumption are caught.- Proof opportunities for
dealiasTypeExpr. Since this ispartial def, strong termination isn't provable without an acyclicity argument onp.globalContext. Natural lemmas that the rest of the file could rely on:dealiasTypeExpr_idempotent : dealiasTypeExpr p (dealiasTypeExpr p te) = dealiasTypeExpr p te, and a "result is fully unwound" lemma: ifdealiasTypeExpr p teis.fvar _ idx #[], thenp.globalContext.kindOf! idxis neither.expr _nor.type [] (.some _). If you don't want to prove these, at least consider a Lean-side#guardtest constructing a three-level synonymProgramand checking the result is.int. - Refactoring the rename builder. The
ClaimOrRenamelogic would be more readable as a named static helper that takes theclaimedset and the_renamesdict as parameters, rather than a closure insideEmitProgramAsStrata. The ordering "procs → impls → consts → funcs → globals" is a deliberate tie-breaker (first-seen wins) — please document it at the_renamesfield, since it's a semantic choice with implications (e.g. a name collision between a proc and a const always renames the const, never the proc). - Duplicated spec emission for
assert_.*. The newspec { requires ... }block inVisitProcedureduplicates the emission pattern inWriteProcedureHeader. Consider injecting a syntheticRequiresintonode.Requiresjust before callingWriteProcedureHeaderso the existing code path handles it uniformly. IdentifierExprfallback.identExpr.Decl != null ? NameOf(...) : Name(...): post-Boogie-resolution,Declshould always be non-null. Silently falling back toName(...)emits the unrenamed name and is wrong in the collision case. Consider asserting non-null instead, or at least add a comment about whenDeclcan be null and why the fallback is safe.
1. Rename mechanism: use NameOf(decl, ...) instead of Name(...) at all
sites that emit variable names (EmitSimpleAssign, VisitCallCmd,
VisitHavocCmd, VisitGlobalVariable, WriteFormals) so that renamed
globals are referenced consistently throughout the output.
2. SMACK assert pattern: tighten StartsWith("assert_") to
StartsWith("assert_.") to match only SMACK's actual encoding
(assert_.i32, assert_.i64) and avoid false positives on user
procedures like assert_helper.
3. Regression tests: add .expect files for SmackAssert,
NamespaceCollision, SanitizationCollision (fixing the test harness
gap where --check mode skipped verification), plus new test cases
for TypeSynonymChain, GlobalVarRenameCollision, and
AssertPrefixFalsePositive.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Issue #, if available: #1148
Description of changes:
Namespace collision (issue 1 in BoogieToStrata: Blockers preventing SMACK-generated Boogie verification #1148): Constants that share a name with a procedure are now prefixed with _const in the Strata output. The rename is applied consistently in declarations and references.
Recursive synonym resolution (Fixes issue 2 in BoogieToStrata: Blockers preventing SMACK-generated Boogie verification #1148): DealiasTypeExpr now recurses on its result, resolving synonym chains like ref := i64 := int fully to the base type. Previously it resolved only one level, causing panics on comparison/arithmetic operators applied to multi-level type synonyms.
SMACK assert encoding (issue 3 in BoogieToStrata: Blockers preventing SMACK-generated Boogie verification #1148): Procedures named assert_.* now get a requires (arg != 0) precondition emitted. Call elimination generates VCs at each call site, making SMACK assertions verifiable.
InferModifies: SMACK-generated Boogie often omits explicit
modifiesclauses on procedures that mutate globals. SettingInferModifies = truein BoogieToStrata has two effects: (1) it runsModSetCollector.CollectModifies(program)to populate empty modifies clauses, and (2) throughCheckModifiesin Boogie's resolution context, it suppresses typechecking of modifies clauses. Without this,ResolveAndTypecheckwould reject SMACK programs that omit modifies clauses.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.