apalache
apalache copied to clipboard
Locally declared operators break use of `VariantGetUnsafe`
Description
I found a case where operators defined using LOCAL
seem to break variable substitution when using VariantGetUnsafe
. Presumably this is due to some sort of error in the rewriting logic. Specs that should succeed during checking are instead resulting in errors like:
at.forsyte.apalache.tla.pp.TlaInputError: SubstRule: Variable o$1 is not assigned a value
Impact
Prevents definition of private operators for use in Option.tla
module (see #2097).
Input specification
------------------- MODULE LocalTest -----------------------
EXTENDS Apalache, Variants
\* @type: Foo(Int) => Int;
LOCAL LocalOp(o) == VariantGetUnsafe("Foo", o)
\* @type: Foo(Int) => Int;
PublicOp(o) == VariantGetUnsafe("Foo", o)
GetLocal(o) == LocalOp(o)
GetPublic(o) == PublicOp(o)
Init == TRUE
Next == TRUE
InvLocal == LocalOp(Variant("Foo", 1)) = 1
InvPublic == PublicOp(Variant("Foo", 1)) = 1
InvSucceeds == GetPublic(Variant("Foo", 1)) = 1
InvFails == GetLocal(Variant("Foo", 1)) = 1
============================================================
The command line parameters used to run the tool
apalache-mc check --inv=InvFails test/tla/LocalTest.tla
Expected behavior
This should check successfully, just as all the other invariants do.
Log files
2022-08-28T19:48:17,597 [main] INFO a.f.a.t.Tool$ - # APALACHE version: v0.28.0-50-g1019a7ccd | build: v0.28.0-50-g1019a7ccd
2022-08-28T19:48:17,600 [main] INFO a.f.a.t.t.o.CheckCmd - Tuning: search.outputTraces=false
2022-08-28T19:48:17,601 [main] INFO a.f.a.t.t.o.CheckCmd - Checker options: check --inv=InvFails test/tla/LocalTest.tla
2022-08-28T19:48:17,604 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #0: SanyParser
2022-08-28T19:48:17,951 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #0: SanyParser [OK]
2022-08-28T19:48:17,951 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #1: TypeCheckerSnowcat
2022-08-28T19:48:17,951 [main] INFO a.f.a.t.t.p.EtcTypeCheckerPassImpl - > Running Snowcat .::.
2022-08-28T19:48:18,289 [main] INFO a.f.a.t.t.p.EtcTypeCheckerPassImpl - > Your types are purrfect!
2022-08-28T19:48:18,289 [main] INFO a.f.a.t.t.p.EtcTypeCheckerPassImpl - > All expressions are typed
2022-08-28T19:48:18,290 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #1: TypeCheckerSnowcat [OK]
2022-08-28T19:48:18,291 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #2: ConfigurationPass
2022-08-28T19:48:18,302 [main] INFO a.f.a.t.p.p.ConfigurationPassImpl - > Command line option --init is not set. Using Init
2022-08-28T19:48:18,303 [main] INFO a.f.a.t.p.p.ConfigurationPassImpl - > Command line option --next is not set. Using Next
2022-08-28T19:48:18,303 [main] INFO a.f.a.t.p.p.ConfigurationPassImpl - > Set the initialization predicate to Init
2022-08-28T19:48:18,304 [main] INFO a.f.a.t.p.p.ConfigurationPassImpl - > Set the transition predicate to Next
2022-08-28T19:48:18,305 [main] INFO a.f.a.t.p.p.ConfigurationPassImpl - > Set an invariant to InvFails
2022-08-28T19:48:18,321 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #2: ConfigurationPass [OK]
2022-08-28T19:48:18,322 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #3: DesugarerPass
2022-08-28T19:48:18,323 [main] INFO a.f.a.t.p.p.DesugarerPassImpl - > Desugaring...
2022-08-28T19:48:18,335 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #3: DesugarerPass [OK]
2022-08-28T19:48:18,335 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #4: InlinePass
2022-08-28T19:48:18,337 [main] INFO a.f.a.t.p.p.InlinePassImpl - Leaving only relevant operators: CInitPrimed, Init, InitPrimed, InvFails, Next
2022-08-28T19:48:18,456 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #4: InlinePass [OK]
2022-08-28T19:48:18,458 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #5: TemporalPass
2022-08-28T19:48:18,465 [main] INFO a.f.a.t.p.p.TemporalPassImpl - > Rewriting temporal operators...
2022-08-28T19:48:18,465 [main] INFO a.f.a.t.p.p.TemporalPassImpl - > No temporal property specified, nothing to encode
2022-08-28T19:48:18,467 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #5: TemporalPass [OK]
2022-08-28T19:48:18,469 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #6: InlinePass
2022-08-28T19:48:18,470 [main] INFO a.f.a.t.p.p.InlinePassImpl - Leaving only relevant operators: CInitPrimed, Init, InitPrimed, InvFails, Next
2022-08-28T19:48:18,475 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #6: InlinePass [OK]
2022-08-28T19:48:18,476 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #7: PrimingPass
2022-08-28T19:48:18,487 [main] INFO a.f.a.t.a.p.PrimingPassImpl - > Introducing InitPrimed for Init'
2022-08-28T19:48:18,489 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #7: PrimingPass [OK]
2022-08-28T19:48:18,490 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #8: VCGen
2022-08-28T19:48:18,493 [main] INFO a.f.a.t.b.p.VCGenPassImpl - > Producing verification conditions from the invariant InvFails
2022-08-28T19:48:18,513 [main] INFO a.f.a.t.b.VCGenerator - > VCGen produced 1 verification condition(s)
2022-08-28T19:48:18,522 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #8: VCGen [OK]
2022-08-28T19:48:18,522 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #9: PreprocessingPass
2022-08-28T19:48:18,522 [main] INFO a.f.a.t.p.p.PreproPassImpl - > Before preprocessing: unique renaming
2022-08-28T19:48:18,530 [main] INFO a.f.a.t.p.p.PreproPassImpl - > Applying standard transformations:
2022-08-28T19:48:18,531 [main] INFO a.f.a.t.p.p.PreproPassImpl - > PrimePropagation
2022-08-28T19:48:18,532 [main] INFO a.f.a.t.p.p.PreproPassImpl - > Desugarer
2022-08-28T19:48:18,533 [main] INFO a.f.a.t.p.p.PreproPassImpl - > UniqueRenamer
2022-08-28T19:48:18,534 [main] INFO a.f.a.t.p.p.PreproPassImpl - > Normalizer
2022-08-28T19:48:18,535 [main] INFO a.f.a.t.p.p.PreproPassImpl - > Keramelizer
2022-08-28T19:48:18,537 [main] INFO a.f.a.t.p.p.PreproPassImpl - > After preprocessing: UniqueRenamer
2022-08-28T19:48:18,539 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #9: PreprocessingPass [OK]
2022-08-28T19:48:18,539 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #10: TransitionFinderPass
2022-08-28T19:48:18,549 [main] INFO a.f.a.t.a.p.TransitionPassImpl - > Found 1 initializing transitions
2022-08-28T19:48:18,554 [main] INFO a.f.a.t.a.p.TransitionPassImpl - > Found 1 transitions
2022-08-28T19:48:18,557 [main] INFO a.f.a.t.a.p.TransitionPassImpl - > No constant initializer
2022-08-28T19:48:18,560 [main] INFO a.f.a.t.a.p.TransitionPassImpl - > Applying unique renaming
2022-08-28T19:48:18,562 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #10: TransitionFinderPass [OK]
2022-08-28T19:48:18,563 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #11: OptimizationPass
2022-08-28T19:48:18,577 [main] INFO a.f.a.t.p.p.OptPassImpl - > Applying optimizations:
2022-08-28T19:48:18,580 [main] INFO a.f.a.t.p.p.OptPassImpl - > ConstSimplifier
2022-08-28T19:48:18,585 [main] INFO a.f.a.t.p.p.OptPassImpl - > ExprOptimizer
2022-08-28T19:48:18,587 [main] INFO a.f.a.t.p.p.OptPassImpl - > SetMembershipSimplifier
2022-08-28T19:48:18,588 [main] INFO a.f.a.t.p.p.OptPassImpl - > ConstSimplifier
2022-08-28T19:48:18,589 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #11: OptimizationPass [OK]
2022-08-28T19:48:18,590 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #12: AnalysisPass
2022-08-28T19:48:18,597 [main] INFO a.f.a.t.b.p.AnalysisPassImpl - > Marking skolemizable existentials and sets to be expanded...
2022-08-28T19:48:18,599 [main] INFO a.f.a.t.b.p.AnalysisPassImpl - > Skolemization
2022-08-28T19:48:18,600 [main] INFO a.f.a.t.b.p.AnalysisPassImpl - > Expansion
2022-08-28T19:48:18,602 [main] INFO a.f.a.t.b.p.AnalysisPassImpl - > Remove unused let-in defs
2022-08-28T19:48:18,608 [main] INFO a.f.a.t.b.p.AnalysisPassImpl - > Running analyzers...
2022-08-28T19:48:18,614 [main] INFO a.f.a.t.b.p.AnalysisPassImpl - > Introduced expression grades
2022-08-28T19:48:18,615 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #12: AnalysisPass [OK]
2022-08-28T19:48:18,615 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #13: BoundedChecker
2022-08-28T19:48:18,864 [main] DEBUG a.f.a.t.b.t.TransitionExecutorImpl - Step #0, transition #0
2022-08-28T19:48:18,864 [main] DEBUG a.f.a.t.b.t.TransitionExecutorImpl - Translating to SMT...
2022-08-28T19:48:19,011 [main] DEBUG a.f.a.t.b.SeqModelChecker - Step 0: Transition #0. Is it enabled?
2022-08-28T19:48:19,013 [main] DEBUG a.f.a.t.b.SeqModelChecker - Step 0: Transition #0 is enabled
2022-08-28T19:48:19,014 [main] INFO a.f.a.t.b.SeqModelChecker - State 0: Checking 1 state invariants
2022-08-28T19:48:19,016 [main] DEBUG a.f.a.t.b.SeqModelChecker - State 0: Checking state invariant 0
2022-08-28T19:48:19,017 [main] ERROR a.f.a.t.b.r.SubstRule - This error may show up when CONSTANTS are not initialized.
2022-08-28T19:48:19,018 [main] ERROR a.f.a.t.b.r.SubstRule - Check the manual: https://apalache.informal.systems/docs/apalache/parameters.html
2022-08-28T19:48:19,020 [main] DEBUG a.f.a.i.Executor - Adapted exception intercepted:
at.forsyte.apalache.tla.pp.TlaInputError: SubstRule: Variable o$1 is not assigned a value
at at.forsyte.apalache.tla.bmcmt.rules.SubstRule.apply(SubstRule.scala:36)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.rewriteOnce(SymbStateRewriterImpl.scala:334)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.doRecursive$1(SymbStateRewriterImpl.scala:388)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.rewriteUntilDone(SymbStateRewriterImpl.scala:422)
at at.forsyte.apalache.tla.bmcmt.rules.VariantOpsRule.translateVariantGetUnsafe(VariantOpsRule.scala:73)
at at.forsyte.apalache.tla.bmcmt.rules.VariantOpsRule.apply(VariantOpsRule.scala:37)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.rewriteOnce(SymbStateRewriterImpl.scala:355)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.doRecursive$1(SymbStateRewriterImpl.scala:388)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.rewriteUntilDone(SymbStateRewriterImpl.scala:422)
at at.forsyte.apalache.tla.bmcmt.rules.IntArithPacker.packArithExpr(IntArithPacker.scala:54)
at at.forsyte.apalache.tla.bmcmt.rules.IntArithPacker.packArithExpr$(IntArithPacker.scala:29)
at at.forsyte.apalache.tla.bmcmt.rules.EqRule.packArithExpr(EqRule.scala:16)
at at.forsyte.apalache.tla.bmcmt.rules.EqRule.apply(EqRule.scala:33)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.rewriteOnce(SymbStateRewriterImpl.scala:355)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.doRecursive$1(SymbStateRewriterImpl.scala:388)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.rewriteUntilDone(SymbStateRewriterImpl.scala:422)
at at.forsyte.apalache.tla.bmcmt.rules.NegRule.apply(NegRule.scala:27)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.rewriteOnce(SymbStateRewriterImpl.scala:355)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.doRecursive$1(SymbStateRewriterImpl.scala:388)
at at.forsyte.apalache.tla.bmcmt.SymbStateRewriterImpl.rewriteUntilDone(SymbStateRewriterImpl.scala:422)
at at.forsyte.apalache.tla.bmcmt.trex.TransitionExecutorImpl.assertState(TransitionExecutorImpl.scala:215)
at at.forsyte.apalache.tla.bmcmt.trex.FilteredTransitionExecutor.assertState(FilteredTransitionExecutor.scala:123)
at at.forsyte.apalache.tla.bmcmt.trex.ConstrainedTransitionExecutor.assertState(ConstrainedTransitionExecutor.scala:111)
at at.forsyte.apalache.tla.bmcmt.SeqModelChecker.$anonfun$checkInvariants$2(SeqModelChecker.scala:360)
at at.forsyte.apalache.tla.bmcmt.SeqModelChecker.$anonfun$checkInvariants$2$adapted(SeqModelChecker.scala:348)
at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:563)
at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:561)
at scala.collection.AbstractIterable.foreach(Iterable.scala:926)
at scala.collection.IterableOps$WithFilter.foreach(Iterable.scala:896)
at at.forsyte.apalache.tla.bmcmt.SeqModelChecker.checkInvariants(SeqModelChecker.scala:348)
at at.forsyte.apalache.tla.bmcmt.SeqModelChecker.$anonfun$prepareTransitionsAndCheckInvariants$5(SeqModelChecker.scala:251)
at scala.runtime.java8.JFunction1$mcVI$sp.apply(JFunction1$mcVI$sp.scala:18)
at scala.collection.immutable.Range.foreach(Range.scala:190)
at at.forsyte.apalache.tla.bmcmt.SeqModelChecker.prepareTransitionsAndCheckInvariants(SeqModelChecker.scala:215)
at at.forsyte.apalache.tla.bmcmt.SeqModelChecker.makeStep(SeqModelChecker.scala:133)
at at.forsyte.apalache.tla.bmcmt.SeqModelChecker.run(SeqModelChecker.scala:63)
at at.forsyte.apalache.tla.bmcmt.passes.BoundedCheckerPassImpl.runIncrementalChecker(BoundedCheckerPassImpl.scala:132)
at at.forsyte.apalache.tla.bmcmt.passes.BoundedCheckerPassImpl.execute(BoundedCheckerPassImpl.scala:88)
at at.forsyte.apalache.infra.passes.PassChainExecutor.exec$1(PassChainExecutor.scala:23)
at at.forsyte.apalache.infra.passes.PassChainExecutor.$anonfun$run$5(PassChainExecutor.scala:47)
at scala.util.Either.flatMap(Either.scala:352)
at at.forsyte.apalache.infra.passes.PassChainExecutor.$anonfun$run$3(PassChainExecutor.scala:46)
at scala.collection.LinearSeqOps.foldLeft(LinearSeq.scala:169)
at scala.collection.LinearSeqOps.foldLeft$(LinearSeq.scala:165)
at scala.collection.immutable.List.foldLeft(List.scala:79)
at at.forsyte.apalache.infra.passes.PassChainExecutor.run(PassChainExecutor.scala:41)
at at.forsyte.apalache.infra.Executor.run(Executor.scala:35)
at at.forsyte.apalache.tla.tooling.opt.CheckCmd.run(CheckCmd.scala:109)
at at.forsyte.apalache.tla.Tool$.runCommand(Tool.scala:139)
at at.forsyte.apalache.tla.Tool$.run(Tool.scala:119)
at at.forsyte.apalache.tla.Tool$.main(Tool.scala:36)
at at.forsyte.apalache.tla.Tool.main(Tool.scala)
2022-08-28T19:48:19,021 [main] ERROR a.f.a.t.Tool$ - Input error (see the manual): SubstRule: Variable o$1 is not assigned a value
2022-08-28T19:48:19,022 [main] INFO a.f.a.t.Tool$ - It took me 0 days 0 hours 0 min 1 sec
2022-08-28T19:48:19,022 [main] INFO a.f.a.t.Tool$ - Total time: 1.421 sec
Triage checklist (for maintainers)
- [x] Reproduce the bug on the main development branch.
- [x] Add the issue to the apalache GitHub project.
- [ ] If the bug is high impact, ensure someone available is assigned to fix it.
@Kukovec, could it be something about the inliner?
I looked at this today, and the problem arises way before this gets to the inliner. Essentially,
LOCAL LocalOp(o) == VariantGetUnsafe("Foo", o)
...
GetLocal(o) == LocalOp(o)
turns into
GetLocal(o) ==
LET (*
@type: Foo(Int) => Int;
*)
LOCAL84_i_LocalOp(o) == VariantGetUnsafe("Foo", o)
IN
LOCAL84_i_LocalOp(o)
in 00_OutSanyParser.tla
. As you can see, there's shadowing of o
inside the LET-definition.
Yeah, we could fix it by renaming o
to LOCAL84_i_o
, though this would require some substitution in the importer. We need a systematic approach to shadowing, as it is happening anyways.
We have a systematic approach, it's called "using the scoped builder".
But beyond that, as soon as any form of rewriting occurs, the only reasonable way to consistently avoid shadowing is to mangle all the names into unique, illegal-character prefixed strings.
I just tried sketching out what it would take to add a simple UniqueNameGenerator
to OppApplTranslator::translateLocalOperator
. Since basically none of the classes in the parser pass are injection-managed, you'd need to pass it along all the way from ExprOrOpArgNodeTranslator
, of which tens of instances are created across the code. And there's even more classes above that, but I just gave up at that point.
I think it's worth revisiting the parser pass architecture, at some point in Q2/Q3, and at the very least document it, if not rework it.
We have a systematic approach, it's called "using the scoped builder".
I don't see how this is related. Local operators are substituted in the importer.
We have a systematic approach, it's called "using the scoped builder".
I don't see how this is related. Local operators are substituted in the importer.
Yes, and the importer manually constructs a LetInEx(...)
. If the builder was used instead, this issue would have been found immediately, with a ScopingException.