Skip to content

Commit d2864a8

Browse files
authored
Merge pull request #1670 from informalsystems/gabriela/stubborn-oneOf
Make `oneOf()` more stubborn
2 parents b2aa9a9 + bad8460 commit d2864a8

File tree

7 files changed

+226
-95
lines changed

7 files changed

+226
-95
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- `quint run` now reports statistics on trace length and the simulation speec in traces/second (#1669)
1616

1717
### Changed
18+
19+
- `oneOf()` now automatically handles empty sets instead of giving an error and has a retry strategy for small sets (#1670)
20+
1821
### Deprecated
1922
### Removed
2023
### Fixed

quint/cli-tests.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,6 @@ fi
310310
<!-- !test check simplePonzi - Run progressInv -->
311311
quint run \
312312
--invariant=progressInv --main=simplePonziTest \
313-
--seed=0x1f035d45bcece7 \
314313
../examples/solidity/SimplePonzi/simplePonzi.qnt
315314

316315
### OK on run gradualPonzi::noNegativeInv
@@ -327,7 +326,7 @@ fi
327326
<!-- !test check gradualPonzi - Run progressInv -->
328327
quint run --invariant=progressInv --main=gradualPonziTest \
329328
--max-samples=1000 --max-steps=50 \
330-
--seed=0x18df4a4a4f958b \
329+
--seed=0x144e3011359c7f \
331330
../examples/solidity/GradualPonzi/gradualPonzi.qnt
332331

333332
### FAIL on run gradualPonzi::noLeftoversInv

quint/io-cli-tests.md

Lines changed: 47 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -534,9 +534,9 @@ An example execution:
534534
535535
[State 1]
536536
{
537-
balances: Map("alice" -> 0, "bob" -> 0, "charlie" -> -75),
537+
balances: Map("alice" -> 0, "bob" -> 0, "charlie" -> -53),
538538
mbt::actionTaken: "withdraw",
539-
mbt::nondetPicks: { account: Some("charlie"), amount: Some(75) }
539+
mbt::nondetPicks: { account: Some("charlie"), amount: Some(53) }
540540
}
541541
542542
[violation] Found an issue (duration).
@@ -631,7 +631,7 @@ The command `run` finds an overflow in Coin.
631631

632632
<!-- !test in run finds overflow -->
633633
```
634-
output=$(quint run --max-steps=5 --seed=0x1e352e160ffb15 --invariant=totalSupplyDoesNotOverflowInv \
634+
output=$(quint run --max-steps=5 --seed=0x1e352e16100007 --invariant=totalSupplyDoesNotOverflowInv \
635635
../examples/tutorials/coin.qnt 2>&1)
636636
exit_code=$?
637637
echo "$output" | sed -e 's/([0-9]*ms.*)/(duration)/g' -e 's#^.*coin.qnt# HOME/coin.qnt#g'
@@ -647,41 +647,41 @@ An example execution:
647647
{
648648
balances:
649649
Map("alice" -> 0, "bob" -> 0, "charlie" -> 0, "eve" -> 0, "null" -> 0),
650-
minter: "alice"
650+
minter: "bob"
651651
}
652652
653653
[State 1]
654654
{
655655
balances:
656656
Map(
657-
"alice" ->
658-
94541396474536885635239092281406920580729946306097367569491485195445962194366,
657+
"alice" -> 0,
659658
"bob" -> 0,
660659
"charlie" -> 0,
661-
"eve" -> 0,
660+
"eve" ->
661+
111865848465544047420411899136876802528313267264666266547206011867817131473571,
662662
"null" -> 0
663663
),
664-
minter: "alice"
664+
minter: "bob"
665665
}
666666
667667
[State 2]
668668
{
669669
balances:
670670
Map(
671-
"alice" ->
672-
94541396474536885635239092281406920580729946306097367569491485195445962194366,
671+
"alice" -> 0,
673672
"bob" ->
674-
53481678647226234506653603827987361074517460680431655489916483293654777859474,
673+
106835923319054853476296948761788592011112700034147492144754744060268203518249,
675674
"charlie" -> 0,
676-
"eve" -> 0,
675+
"eve" ->
676+
111865848465544047420411899136876802528313267264666266547206011867817131473571,
677677
"null" -> 0
678678
),
679-
minter: "alice"
679+
minter: "bob"
680680
}
681681
682682
[violation] Found an issue (duration).
683683
Use --verbosity=3 to show executions.
684-
Use --seed=0x1e352e160ffb15 to reproduce.
684+
Use --seed=0x1e352e16100007 to reproduce.
685685
error: Invariant violated
686686
```
687687

@@ -691,7 +691,7 @@ The command `run` finds an overflow in Coin and shows the operator calls.
691691

692692
<!-- !test in run shows calls -->
693693
```
694-
output=$(quint run --max-steps=5 --seed=0x1786e678d460ed \
694+
output=$(quint run --max-steps=5 --seed=0x136507ae9037f5 \
695695
--invariant=totalSupplyDoesNotOverflowInv \
696696
--verbosity=3 \
697697
../examples/tutorials/coin.qnt 2>&1)
@@ -714,67 +714,61 @@ q::initAndInvariant => true
714714
{
715715
balances:
716716
Map("alice" -> 0, "bob" -> 0, "charlie" -> 0, "eve" -> 0, "null" -> 0),
717-
minter: "alice"
717+
minter: "eve"
718718
}
719719
720720
[Frame 1]
721721
q::stepAndInvariant => true
722722
├─ step => true
723723
│ └─ send(
724+
│ "eve",
724725
│ "alice",
725-
│ "null",
726-
│ 78071281284846825193495013785477188646129629244112530489195111677146856342020
726+
│ 10060541798179252735561881697278111912297141188982098138305297944456003639647
727727
│ ) => false
728728
│ └─ require(false) => false
729729
└─ isUInt(
730-
78071281284846825193495013785477188646129629244112530489195111677146856342020
730+
10060541798179252735561881697278111912297141188982098138305297944456003639647
731731
) => true
732732
733733
[State 1]
734734
{
735735
balances:
736736
Map(
737-
"alice" -> 0,
737+
"alice" ->
738+
10060541798179252735561881697278111912297141188982098138305297944456003639647,
738739
"bob" -> 0,
739740
"charlie" -> 0,
740741
"eve" -> 0,
741-
"null" ->
742-
78071281284846825193495013785477188646129629244112530489195111677146856342020
742+
"null" -> 0
743743
),
744-
minter: "alice"
744+
minter: "eve"
745745
}
746746
747747
[Frame 2]
748748
q::stepAndInvariant => false
749749
├─ step => true
750-
│ └─ send(
751-
│ "alice",
752-
│ "charlie",
753-
│ 71516992819340132902114648681722204450273946921092387316973942850220744245470
754-
│ ) => false
755-
│ └─ require(false) => false
756750
└─ isUInt(
757-
149588274104186958095609662467199393096403576165204917806169054527367600587490
751+
119924674537974698230049391428816171173783073186451018303718372310243659214259
758752
) => false
759753
760754
[State 2]
761755
{
762756
balances:
763757
Map(
764-
"alice" -> 0,
758+
"alice" ->
759+
10060541798179252735561881697278111912297141188982098138305297944456003639647,
765760
"bob" -> 0,
766761
"charlie" ->
767-
71516992819340132902114648681722204450273946921092387316973942850220744245470,
762+
109864132739795445494487509731538059261485931997468920165413074365787655574612,
768763
"eve" -> 0,
769-
"null" ->
770-
78071281284846825193495013785477188646129629244112530489195111677146856342020
764+
"null" -> 0
771765
),
772-
minter: "alice"
766+
minter: "eve"
773767
}
774768
775769
[violation] Found an issue (duration).
776770
Use --verbosity=3 to show executions.
777-
Use --seed=0x1786e678d460ed to reproduce.
771+
Use --seed=0x136507ae9037f5 to reproduce.
778772
error: Invariant violated
779773
```
780774

@@ -833,13 +827,13 @@ rm out-itf-mbt-example.itf.json
833827
[
834828
"charlie",
835829
{
836-
"#bigint": "79626045751699704635016553258820412024546765398372583361896346889345270192783"
830+
"#bigint": "0"
837831
}
838832
],
839833
[
840834
"eve",
841835
{
842-
"#bigint": "0"
836+
"#bigint": "20086964742617499085873481662929437057498704222762050481882156750446625292787"
843837
}
844838
],
845839
[
@@ -855,19 +849,19 @@ rm out-itf-mbt-example.itf.json
855849
"amount": {
856850
"tag": "Some",
857851
"value": {
858-
"#bigint": "79626045751699704635016553258820412024546765398372583361896346889345270192783"
852+
"#bigint": "20086964742617499085873481662929437057498704222762050481882156750446625292787"
859853
}
860854
},
861855
"receiver": {
862856
"tag": "Some",
863-
"value": "charlie"
857+
"value": "eve"
864858
},
865859
"sender": {
866860
"tag": "Some",
867-
"value": "eve"
861+
"value": "null"
868862
}
869863
},
870-
"minter": "eve"
864+
"minter": "null"
871865
}
872866
```
873867

@@ -1062,7 +1056,7 @@ cd - > /dev/null
10621056
<!-- !test exit 1 -->
10631057
<!-- !test in verbose test -->
10641058
```
1065-
output=$(quint test --seed=0x1286bf2e1dacb3 --match=mintTwiceThenSendError \
1059+
output=$(quint test --seed=0x1286bf2e1dad07 --match=mintTwiceThenSendError \
10661060
--verbosity=3 ../examples/tutorials/coin.qnt)
10671061
exit_code=$?
10681062
echo "$output" | sed -e 's/([0-9]*ms)/(duration)/g' -e 's#^.*coin.qnt# HOME/coin.qnt#g'
@@ -1086,45 +1080,45 @@ init => true
10861080
10871081
[Frame 1]
10881082
mint(
1089-
"bob",
1083+
"alice",
10901084
"eve",
1091-
74252675173190743514494160784973331842148624838292266741626378055869698233769
1085+
59566460029516684323034576273723724420849386347074116311626296723707538074470
10921086
) => true
10931087
├─ require(true) => true
10941088
└─ require(true) => true
10951089
└─ isUInt(
1096-
74252675173190743514494160784973331842148624838292266741626378055869698233769
1090+
59566460029516684323034576273723724420849386347074116311626296723707538074470
10971091
) => true
10981092
10991093
[Frame 2]
11001094
mint(
1095+
"alice",
11011096
"bob",
1102-
"bob",
1103-
97700478479458321253548605902971263977055085704583752584562220159652816914987
1097+
93368484419625019040077192713095698377461332472838851169076929942152020904440
11041098
) => true
11051099
├─ require(true) => true
11061100
└─ require(true) => true
11071101
└─ isUInt(
1108-
97700478479458321253548605902971263977055085704583752584562220159652816914987
1102+
93368484419625019040077192713095698377461332472838851169076929942152020904440
11091103
) => true
11101104
11111105
[Frame 3]
11121106
send(
11131107
"eve",
11141108
"bob",
1115-
47769583726968424739901588588333904197787985995488944788698867328177315688645
1109+
58557567944388148967996441447677399573201687288362292728576564719669790227709
11161110
) => false
11171111
├─ require(true) => true
11181112
├─ require(true) => true
11191113
│ └─ isUInt(
1120-
26483091446222318774592572196639427644360638842803321952927510727692382545124
1114+
1008892085128535355038134826046324847647699058711823583049732004037747846761
11211115
│ ) => true
11221116
└─ require(false) => false
11231117
└─ isUInt(
1124-
145470062206426745993450194491305168174843071700072697373261087487830132603632
1118+
151926052364013168008073634160773097950663019761201143897653494661821811132149
11251119
) => false
11261120
1127-
Use --seed=0x1286bf2e1dacb3 --match=mintTwiceThenSendError to repeat.
1121+
Use --seed=0x1286bf2e1dad07 --match=mintTwiceThenSendError to repeat.
11281122
```
11291123

11301124
### test fails on invalid seed

quint/src/environment.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
declare global {
2+
namespace NodeJS {
3+
interface ProcessEnv {
4+
QUINT_RETRY_NONDET_SMALLER_THAN?: string
5+
}
6+
}
7+
}
8+
9+
export {}

quint/src/runtime/impl/builder.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { QuintApp, QuintEx, QuintVar } from '../../ir/quintIr'
2727
import { LookupDefinition, LookupTable } from '../../names/base'
2828
import { NamedRegister, VarStorage, initialRegisterValue } from './VarStorage'
2929
import { List } from 'immutable'
30+
import { evalNondet } from './nondet'
3031

3132
/**
3233
* The type returned by the builder in its methods, which can be called to get the
@@ -308,24 +309,12 @@ function buildDefCore(builder: Builder, def: LookupDefinition): EvalFunction {
308309
const cachedValue = builder.scopedCachedValues.get(def.id)!
309310

310311
const bodyEval = buildExpr(builder, def.expr)
311-
if (def.qualifier === 'nondet') {
312-
// Create an entry in the map for this nondet pick,
313-
// as we want the resulting record to be the same at every state.
314-
// Value is optional, and starts with undefined
315-
builder.initialNondetPicks.set(def.name, undefined)
316-
}
317312

318313
return ctx => {
319314
if (cachedValue.value === undefined) {
320315
cachedValue.value = bodyEval(ctx)
321316
}
322317

323-
if (def.qualifier === 'nondet') {
324-
cachedValue.value
325-
.map(value => ctx.varStorage.nondetPicks.set(def.name, value))
326-
.mapLeft(_ => ctx.varStorage.nondetPicks.set(def.name, undefined))
327-
}
328-
329318
return cachedValue.value
330319
}
331320
}
@@ -494,6 +483,32 @@ function buildExprCore(builder: Builder, expr: QuintEx): EvalFunction {
494483
}
495484
}
496485
case 'let': {
486+
if (expr.opdef.qualifier === 'nondet') {
487+
// For `nondet`, we want to retry the the `oneOf()` call in case the body returns false.
488+
// So we take care of the compilation at the let-in level.
489+
490+
if (expr.opdef.expr.kind !== 'app') {
491+
// impossible, added to make Typescript's type checker happy.
492+
throw new Error('Impossible case: nondet let expression is not an app')
493+
}
494+
495+
// Create an entry in the map for this nondet pick,
496+
// as we want the resulting record to be the same at every state.
497+
// Value is optional, and starts with undefined
498+
builder.initialNondetPicks.set(expr.opdef.name, undefined)
499+
500+
// Set up cache and memo in the same way we do for regular let-ins.
501+
// We don't need to add it to `scopedCachedValues` since we'll clear
502+
// the cache in here
503+
let cache: CachedValue = { value: undefined }
504+
builder.memo.set(expr.opdef.id, _ => cache.value!)
505+
506+
const setEval = buildExpr(builder, expr.opdef.expr.args[0])
507+
const bodyEval = buildExpr(builder, expr.expr)
508+
509+
return evalNondet(expr.opdef.name, cache, setEval, bodyEval)
510+
}
511+
497512
// First, we create a cached value (a register with optional value) for the definition in this let expression
498513
let cachedValue = builder.scopedCachedValues.get(expr.opdef.id)
499514
if (!cachedValue) {

0 commit comments

Comments
 (0)