@fairfox/polly 0.34.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -40,6 +40,14 @@ ContextType == {"background", "content", "popup", "devtools", "options", "offscr
|
|
|
40
40
|
PortState == {"connected", "disconnected"}
|
|
41
41
|
MessageStatus == {"pending", "routing", "delivered", "failed", "timeout"}
|
|
42
42
|
|
|
43
|
+
\* Sentinel value for messages that have not been routed to a concrete
|
|
44
|
+
\* target yet. Once RouteMessage selects a target from msg.targets, that
|
|
45
|
+
\* target is recorded in deliveredTo so step-temporal postcondition
|
|
46
|
+
\* properties can identify which single target was actually mutated.
|
|
47
|
+
\* Without this field a property that quantifies over msg.targets
|
|
48
|
+
\* false-positives whenever routing picks one target out of many.
|
|
49
|
+
NoTarget == "NoTarget"
|
|
50
|
+
|
|
43
51
|
Message == [
|
|
44
52
|
id: Nat,
|
|
45
53
|
source: ContextType,
|
|
@@ -47,7 +55,8 @@ Message == [
|
|
|
47
55
|
tabId: Nat,
|
|
48
56
|
msgType: STRING, \* Message type for handler dispatch
|
|
49
57
|
status: MessageStatus,
|
|
50
|
-
timestamp: Nat
|
|
58
|
+
timestamp: Nat,
|
|
59
|
+
deliveredTo: ContextType \cup {NoTarget}
|
|
51
60
|
]
|
|
52
61
|
|
|
53
62
|
-----------------------------------------------------------------------------
|
|
@@ -97,7 +106,8 @@ SendMessage(source, targetSet, tabId, messageType) ==
|
|
|
97
106
|
tabId |-> tabId,
|
|
98
107
|
msgType |-> messageType,
|
|
99
108
|
status |-> "pending",
|
|
100
|
-
timestamp |-> time
|
|
109
|
+
timestamp |-> time,
|
|
110
|
+
deliveredTo |-> NoTarget
|
|
101
111
|
]
|
|
102
112
|
IN /\ messages' = Append(messages, newMsg)
|
|
103
113
|
/\ pendingRequests' = pendingRequests @@
|
|
@@ -118,7 +128,8 @@ RouteMessage(msgIndex) ==
|
|
|
118
128
|
\E target \in msg.targets :
|
|
119
129
|
/\ IF target \in Contexts /\ ports[target] = "connected"
|
|
120
130
|
THEN \* Successful delivery to this target
|
|
121
|
-
/\ messages' = [messages EXCEPT ![msgIndex].status = "delivered"
|
|
131
|
+
/\ messages' = [messages EXCEPT ![msgIndex].status = "delivered",
|
|
132
|
+
![msgIndex].deliveredTo = target]
|
|
122
133
|
/\ delivered' = delivered \union {msg.id}
|
|
123
134
|
/\ pendingRequests' = [id \in DOMAIN pendingRequests \ {msg.id} |->
|
|
124
135
|
pendingRequests[id]]
|
|
@@ -339,6 +339,9 @@ class TemporalTLAGenerator {
|
|
|
339
339
|
case "always":
|
|
340
340
|
lines.push(`${prop.name} == [](${prop.target})`);
|
|
341
341
|
break;
|
|
342
|
+
case "step-always":
|
|
343
|
+
lines.push(`${prop.name} == [][${prop.target}]_allVars`);
|
|
344
|
+
break;
|
|
342
345
|
case "until":
|
|
343
346
|
if (prop.trigger) {
|
|
344
347
|
lines.push(`${prop.name} == (${prop.trigger}) U (${prop.target})`);
|
|
@@ -651,6 +654,7 @@ var init_tla = __esm(() => {
|
|
|
651
654
|
if (this.temporalProperties.length > 0) {
|
|
652
655
|
this.addTemporalProperties();
|
|
653
656
|
}
|
|
657
|
+
this.line("=============================================================================");
|
|
654
658
|
return this.lines.join(`
|
|
655
659
|
`);
|
|
656
660
|
}
|
|
@@ -708,7 +712,7 @@ var init_tla = __esm(() => {
|
|
|
708
712
|
}
|
|
709
713
|
const tabValues = Array.from({ length: this.tabCount }, (_, i) => `Tab${i}`).join(", ");
|
|
710
714
|
lines.push(` Tabs = {${tabValues}}`);
|
|
711
|
-
} else if ("maxTabs" in messages && messages.maxTabs !== undefined) {
|
|
715
|
+
} else if ("maxTabs" in messages && messages.maxTabs !== undefined && messages.maxTabs !== null) {
|
|
712
716
|
const tabValues = Array.from({ length: messages.maxTabs + 1 }, (_, i) => i).join(", ");
|
|
713
717
|
lines.push(` Tabs = {${tabValues}}`);
|
|
714
718
|
} else if (hasProjectConstant) {
|
|
@@ -804,7 +808,7 @@ var init_tla = __esm(() => {
|
|
|
804
808
|
if (hasStateConstants) {
|
|
805
809
|
this.generateConstantDeclarations(config.state, false);
|
|
806
810
|
}
|
|
807
|
-
if (hasPerMessageBounds) {
|
|
811
|
+
if (hasPerMessageBounds && config.messages.perMessageBounds) {
|
|
808
812
|
for (const [msgType, _bound] of Object.entries(config.messages.perMessageBounds)) {
|
|
809
813
|
const constName = `MaxMessages_${msgType}`;
|
|
810
814
|
this.line(`,${constName}`);
|
|
@@ -831,15 +835,15 @@ var init_tla = __esm(() => {
|
|
|
831
835
|
}
|
|
832
836
|
addFieldConstants(field, fieldConfig, constName, first) {
|
|
833
837
|
let isFirst = first;
|
|
834
|
-
if ("maxLength" in fieldConfig && fieldConfig
|
|
838
|
+
if ("maxLength" in fieldConfig && fieldConfig["maxLength"] !== null) {
|
|
835
839
|
this.line(`${isFirst ? "" : ","}${constName}_MaxLength \\* Max length for ${field}`);
|
|
836
840
|
isFirst = false;
|
|
837
841
|
}
|
|
838
|
-
if ("max" in fieldConfig && fieldConfig
|
|
842
|
+
if ("max" in fieldConfig && fieldConfig["max"] !== null) {
|
|
839
843
|
this.line(`${isFirst ? "" : ","}${constName}_Max \\* Max value for ${field}`);
|
|
840
844
|
isFirst = false;
|
|
841
845
|
}
|
|
842
|
-
if ("maxSize" in fieldConfig && fieldConfig
|
|
846
|
+
if ("maxSize" in fieldConfig && fieldConfig["maxSize"] !== null) {
|
|
843
847
|
this.line(`${isFirst ? "" : ","}${constName}_MaxSize \\* Max size for ${field}`);
|
|
844
848
|
isFirst = false;
|
|
845
849
|
}
|
|
@@ -998,7 +1002,6 @@ var init_tla = __esm(() => {
|
|
|
998
1002
|
const messageTypeSet = validMessageTypes.map((t) => `"${t}"`).join(", ");
|
|
999
1003
|
this.line(`UserMessageTypes == {${messageTypeSet}}`);
|
|
1000
1004
|
this.line("");
|
|
1001
|
-
this.filteredMessageTypes = validMessageTypes;
|
|
1002
1005
|
if (config.messages.symmetry && config.messages.symmetry.length > 0) {
|
|
1003
1006
|
this.addSymmetrySets(config.messages.symmetry, validMessageTypes);
|
|
1004
1007
|
}
|
|
@@ -1025,6 +1028,8 @@ var init_tla = __esm(() => {
|
|
|
1025
1028
|
}
|
|
1026
1029
|
for (let i = 0;i < validSymmetryGroups.length; i++) {
|
|
1027
1030
|
const group = validSymmetryGroups[i];
|
|
1031
|
+
if (!group)
|
|
1032
|
+
continue;
|
|
1028
1033
|
const setName = `SymmetrySet${i + 1}`;
|
|
1029
1034
|
const setValues = group.map((t) => `"${t}"`).join(", ");
|
|
1030
1035
|
this.line(`${setName} == {${setValues}}`);
|
|
@@ -1038,7 +1043,8 @@ var init_tla = __esm(() => {
|
|
|
1038
1043
|
console.log(`[INFO] [TLAGenerator] Symmetry reduction: ${validSymmetryGroups.length} independent symmetry groups ` + `(${validSymmetryGroups.map((g) => g.length).join(", ")} message types)`);
|
|
1039
1044
|
} else {
|
|
1040
1045
|
this.line(`Symmetry == Permutations(SymmetrySet1)`);
|
|
1041
|
-
|
|
1046
|
+
const onlyGroup = validSymmetryGroups[0];
|
|
1047
|
+
console.log(`[INFO] [TLAGenerator] Symmetry reduction: 1 symmetry group with ${onlyGroup?.length ?? 0} message types`);
|
|
1042
1048
|
}
|
|
1043
1049
|
this.symmetrySets = ["Symmetry"];
|
|
1044
1050
|
this.line("");
|
|
@@ -1221,11 +1227,12 @@ var init_tla = __esm(() => {
|
|
|
1221
1227
|
addPayloadRefsFromHandler(handler, fields) {
|
|
1222
1228
|
for (const text of this.collectHandlerTexts(handler)) {
|
|
1223
1229
|
const numMatch = TLAGenerator.NUMERIC_PAYLOAD_PATTERN.exec(text);
|
|
1224
|
-
if (numMatch)
|
|
1230
|
+
if (numMatch?.[1])
|
|
1225
1231
|
fields.set(numMatch[1], "0..2");
|
|
1226
1232
|
for (const m of text.matchAll(TLAGenerator.PAYLOAD_REF_PATTERN)) {
|
|
1227
|
-
|
|
1228
|
-
|
|
1233
|
+
const name = m[1];
|
|
1234
|
+
if (name && !fields.has(name))
|
|
1235
|
+
fields.set(name, TLAGenerator.inferFieldType(name));
|
|
1229
1236
|
}
|
|
1230
1237
|
}
|
|
1231
1238
|
}
|
|
@@ -1404,12 +1411,36 @@ var init_tla = __esm(() => {
|
|
|
1404
1411
|
}
|
|
1405
1412
|
this.emitPreconditions(allPreconditions);
|
|
1406
1413
|
const validAssignments = this.processAssignments(allAssignments, config.state);
|
|
1407
|
-
|
|
1408
|
-
if (!usedUnchanged) {
|
|
1409
|
-
this.emitPostconditions(allPostconditions);
|
|
1410
|
-
}
|
|
1414
|
+
this.emitStateUpdates(validAssignments, allPreconditions);
|
|
1411
1415
|
this.indent--;
|
|
1412
1416
|
this.line("");
|
|
1417
|
+
this.recordPostconditionProperty(messageType, actionName, allPostconditions);
|
|
1418
|
+
}
|
|
1419
|
+
recordPostconditionProperty(messageType, actionName, postconditions) {
|
|
1420
|
+
if (postconditions.length === 0)
|
|
1421
|
+
return;
|
|
1422
|
+
const predicateClauses = postconditions.map((pc) => this.tsExpressionToTLA(pc.expression, true)).filter((p) => p && p.length > 0).map((p) => p.replace(/\[ctx\]/g, "[target]"));
|
|
1423
|
+
if (predicateClauses.length === 0)
|
|
1424
|
+
return;
|
|
1425
|
+
const conjunction = predicateClauses.length === 1 ? predicateClauses[0] : predicateClauses.map((c) => ` /\\ ${c}`).join(`
|
|
1426
|
+
`);
|
|
1427
|
+
const propertyName = `EnsuresAfter_${actionName}`;
|
|
1428
|
+
const messages = postconditions.map((pc) => pc.message).filter((m) => Boolean(m)).join("; ");
|
|
1429
|
+
const target = `
|
|
1430
|
+
\\A m \\in 1..Len(messages) :
|
|
1431
|
+
` + ` (messages[m].status = "pending"
|
|
1432
|
+
` + ` /\\ messages'[m].status = "delivered"
|
|
1433
|
+
` + ` /\\ messages[m].msgType = "${messageType}")
|
|
1434
|
+
` + ` => LET target == messages'[m].deliveredTo IN
|
|
1435
|
+
` + ` (target \\in Contexts /\\ ports[target] = "connected")
|
|
1436
|
+
` + ` =>
|
|
1437
|
+
` + (predicateClauses.length === 1 ? ` ${predicateClauses[0]}` : conjunction);
|
|
1438
|
+
this.temporalProperties.push({
|
|
1439
|
+
name: propertyName,
|
|
1440
|
+
description: messages.length > 0 ? `ensures(...) for ${messageType}: ${messages}` : `ensures(...) for ${messageType}`,
|
|
1441
|
+
type: "step-always",
|
|
1442
|
+
target
|
|
1443
|
+
});
|
|
1413
1444
|
}
|
|
1414
1445
|
emitPreconditions(preconditions) {
|
|
1415
1446
|
for (const precondition of preconditions) {
|
|
@@ -1418,14 +1449,6 @@ var init_tla = __esm(() => {
|
|
|
1418
1449
|
this.line(`/\\ ${tlaExpr}${comment}`);
|
|
1419
1450
|
}
|
|
1420
1451
|
}
|
|
1421
|
-
emitPostconditions(postconditions) {
|
|
1422
|
-
for (const postcondition of postconditions) {
|
|
1423
|
-
const tlaExpr = this.tsExpressionToTLA(postcondition.expression, true);
|
|
1424
|
-
const message = postcondition.message ?? "ensures failed";
|
|
1425
|
-
const escapedMessage = message.replace(/"/g, "\\\"");
|
|
1426
|
-
this.line(`/\\ Assert(${tlaExpr}, "${escapedMessage}")`);
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
1452
|
processAssignments(assignments, state) {
|
|
1430
1453
|
return assignments.filter((a) => this.isFieldModeled(a.field, state)).filter((a) => this.shouldIncludeAssignment(a, state)).map((a) => this.mapNullAssignment(a, state));
|
|
1431
1454
|
}
|
|
@@ -1462,7 +1485,7 @@ var init_tla = __esm(() => {
|
|
|
1462
1485
|
if ("nullable" in fieldConfig && fieldConfig.nullable)
|
|
1463
1486
|
return assignment;
|
|
1464
1487
|
if ("values" in fieldConfig && fieldConfig.values) {
|
|
1465
|
-
const nullValue = fieldConfig.values[fieldConfig.values.length - 1];
|
|
1488
|
+
const nullValue = fieldConfig.values[fieldConfig.values.length - 1] ?? null;
|
|
1466
1489
|
return { ...assignment, value: nullValue };
|
|
1467
1490
|
}
|
|
1468
1491
|
return assignment;
|
|
@@ -1673,7 +1696,6 @@ var init_tla = __esm(() => {
|
|
|
1673
1696
|
result = result.replace(/(\w+(?:\.\w+)*)\.includes\(([^)]+)\)/g, (_match, arrayRef, item) => {
|
|
1674
1697
|
return `${item.trim()} \\in ${arrayRef}`;
|
|
1675
1698
|
});
|
|
1676
|
-
const _indexMap = new Map;
|
|
1677
1699
|
result = result.replace(/(\w+(?:\.\w+)*)\[(\d+)\]|\]\[(\d+)\]/g, (_match, identPart, index1, index2) => {
|
|
1678
1700
|
if (identPart) {
|
|
1679
1701
|
const newIndex2 = Number.parseInt(index1, 10) + 1;
|
|
@@ -1996,7 +2018,8 @@ var init_tla = __esm(() => {
|
|
|
1996
2018
|
this.line(" /\\ \\E target \\in msg.targets :");
|
|
1997
2019
|
this.line(' /\\ IF target \\in Contexts /\\ ports[target] = "connected"');
|
|
1998
2020
|
this.line(" THEN \\* Successful delivery - route AND invoke handler");
|
|
1999
|
-
this.line(` /\\ messages' = [messages EXCEPT ![msgIndex].status = "delivered"
|
|
2021
|
+
this.line(` /\\ messages' = [messages EXCEPT ![msgIndex].status = "delivered",`);
|
|
2022
|
+
this.line(" ![msgIndex].deliveredTo = target]");
|
|
2000
2023
|
this.line(" /\\ delivered' = delivered \\union {msg.id}");
|
|
2001
2024
|
this.line(" /\\ pendingRequests' = [id \\in DOMAIN pendingRequests \\ {msg.id} |->");
|
|
2002
2025
|
this.line(" pendingRequests[id]]");
|
|
@@ -2070,7 +2093,6 @@ var init_tla = __esm(() => {
|
|
|
2070
2093
|
}
|
|
2071
2094
|
this.line("\\* State constraint to bound state space");
|
|
2072
2095
|
this.addStateConstraint(config, _analysis);
|
|
2073
|
-
this.line("=============================================================================");
|
|
2074
2096
|
}
|
|
2075
2097
|
addTemporalConstraints(constraints) {
|
|
2076
2098
|
this.line("\\* Tier 2: Temporal constraint invariants");
|
|
@@ -2078,6 +2100,8 @@ var init_tla = __esm(() => {
|
|
|
2078
2100
|
this.line("");
|
|
2079
2101
|
for (let i = 0;i < constraints.length; i++) {
|
|
2080
2102
|
const constraint = constraints[i];
|
|
2103
|
+
if (!constraint)
|
|
2104
|
+
continue;
|
|
2081
2105
|
const invName = `TemporalConstraint${i + 1}`;
|
|
2082
2106
|
if (constraint.description) {
|
|
2083
2107
|
this.line(`\\* ${constraint.description}`);
|
|
@@ -2093,9 +2117,8 @@ var init_tla = __esm(() => {
|
|
|
2093
2117
|
this.extractedInvariants.push({
|
|
2094
2118
|
name: invName,
|
|
2095
2119
|
description: constraint.description || `${constraint.before} must happen before ${constraint.after}`,
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
source: { file: "", line: 0, column: 0 }
|
|
2120
|
+
expression: `${constraint.before} happens-before ${constraint.after}`,
|
|
2121
|
+
location: { file: "", line: 0 }
|
|
2099
2122
|
});
|
|
2100
2123
|
}
|
|
2101
2124
|
}
|
|
@@ -2108,7 +2131,7 @@ var init_tla = __esm(() => {
|
|
|
2108
2131
|
this.indent++;
|
|
2109
2132
|
if (needsConjunction) {
|
|
2110
2133
|
this.line("/\\ Len(messages) <= MaxMessages");
|
|
2111
|
-
if (hasPerMessageBounds) {
|
|
2134
|
+
if (hasPerMessageBounds && config.messages.perMessageBounds) {
|
|
2112
2135
|
for (const [msgType, _bound] of Object.entries(config.messages.perMessageBounds)) {
|
|
2113
2136
|
const constName = `MaxMessages_${msgType}`;
|
|
2114
2137
|
this.line(`/\\ Cardinality({m \\in DOMAIN messages : messages[m].msgType = "${msgType}"}) <= ${constName}`);
|
|
@@ -2154,7 +2177,6 @@ var init_tla = __esm(() => {
|
|
|
2154
2177
|
this.line(propDef);
|
|
2155
2178
|
this.line("");
|
|
2156
2179
|
}
|
|
2157
|
-
this.line("=============================================================================");
|
|
2158
2180
|
}
|
|
2159
2181
|
fieldConfigToTLAType(_fieldPath, fieldConfig, _config) {
|
|
2160
2182
|
const typeResult = this.tryAbstractType(fieldConfig) || this.tryBooleanType(fieldConfig) || this.tryEnumType(fieldConfig) || this.tryArrayType(fieldConfig) || this.tryExplicitNumberType(fieldConfig) || this.tryNumberType(fieldConfig) || this.tryStringType(fieldConfig) || this.tryMapType(fieldConfig);
|
|
@@ -6501,6 +6523,8 @@ class HandlerExtractor {
|
|
|
6501
6523
|
if (match) {
|
|
6502
6524
|
const signalName = match[1];
|
|
6503
6525
|
const fieldName = match[2];
|
|
6526
|
+
if (!signalName)
|
|
6527
|
+
return;
|
|
6504
6528
|
if (fieldName) {
|
|
6505
6529
|
signals.push(`${signalName}_${fieldName}`);
|
|
6506
6530
|
} else {
|
|
@@ -7693,4 +7717,4 @@ main().catch((error) => {
|
|
|
7693
7717
|
process.exit(1);
|
|
7694
7718
|
});
|
|
7695
7719
|
|
|
7696
|
-
//# debugId=
|
|
7720
|
+
//# debugId=F1333D1589EFA94964756E2164756E21
|