doppeldiff 0.1.0 → 0.1.1
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.
- package/README.md +8 -0
- package/dist/cli.js +294 -87
- package/dist/cli.js.map +4 -4
- package/dist/index.js +307 -107
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/schema/report.schema.json +10 -0
package/dist/index.js
CHANGED
|
@@ -330,7 +330,7 @@ var RunnerProcess = class {
|
|
|
330
330
|
}
|
|
331
331
|
/** Execute a job; timeouts and crashes become outcomes, and the process is respawned. */
|
|
332
332
|
async run(job) {
|
|
333
|
-
const
|
|
333
|
+
const timeoutMs = Math.round(this.options.timeoutMs * (job.timeoutScale ?? 1));
|
|
334
334
|
const watchdogMs = timeoutMs + Math.max(150, Math.round(timeoutMs * 0.3));
|
|
335
335
|
const guardKey = `${job.moduleId}#${job.exportName}`;
|
|
336
336
|
const outcomes = new Array(job.inputs.length);
|
|
@@ -354,6 +354,11 @@ var RunnerProcess = class {
|
|
|
354
354
|
if (m.t === "result" && m.reqId === reqId) {
|
|
355
355
|
outcomes[offset + m.index] = m.outcome;
|
|
356
356
|
count = m.index + 1;
|
|
357
|
+
if (m.outcome.kind === "timeout") {
|
|
358
|
+
this.guarded.add(guardKey);
|
|
359
|
+
finish({ count }, true);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
357
362
|
arm();
|
|
358
363
|
} else if (m.t === "done" && m.reqId === reqId) {
|
|
359
364
|
finish({ count }, false);
|
|
@@ -435,18 +440,18 @@ var RunnerPool = class {
|
|
|
435
440
|
return results[0];
|
|
436
441
|
}
|
|
437
442
|
/** Run inputs, spreading them across processes. Outcomes are returned in input order. */
|
|
438
|
-
run(moduleId, exportName, inputs) {
|
|
439
|
-
const result = this.lock.then(() => this.runUnlocked(moduleId, exportName, inputs));
|
|
443
|
+
run(moduleId, exportName, inputs, options = {}) {
|
|
444
|
+
const result = this.lock.then(() => this.runUnlocked(moduleId, exportName, inputs, options.timeoutScale));
|
|
440
445
|
this.lock = result.catch(() => void 0);
|
|
441
446
|
return result;
|
|
442
447
|
}
|
|
443
|
-
async runUnlocked(moduleId, exportName, inputs) {
|
|
448
|
+
async runUnlocked(moduleId, exportName, inputs, timeoutScale) {
|
|
444
449
|
await this.start();
|
|
445
450
|
const chunkSize = Math.max(16, Math.ceil(inputs.length / this.procs.length));
|
|
446
451
|
const chunks = [];
|
|
447
452
|
for (let i = 0; i < inputs.length; i += chunkSize) chunks.push(inputs.slice(i, i + chunkSize));
|
|
448
453
|
const results = await Promise.all(
|
|
449
|
-
chunks.map((c, i) => this.procs[i].run({ moduleId, exportName, inputs: c }))
|
|
454
|
+
chunks.map((c, i) => this.procs[i].run({ moduleId, exportName, inputs: c, timeoutScale }))
|
|
450
455
|
);
|
|
451
456
|
return results.flat();
|
|
452
457
|
}
|
|
@@ -1046,6 +1051,19 @@ function numberArb(ctx) {
|
|
|
1046
1051
|
);
|
|
1047
1052
|
return weighted(typeDriven, ctx.mined.numbers, ctx);
|
|
1048
1053
|
}
|
|
1054
|
+
var HUGE_SIZES = [2 ** 31 - 1, 2 ** 32, Number.MAX_SAFE_INTEGER, 1e15, Infinity, -Infinity, NaN];
|
|
1055
|
+
function sizeArb(ctx) {
|
|
1056
|
+
const typeDriven = fc2.oneof(
|
|
1057
|
+
{ arbitrary: fc2.integer({ min: 0, max: 10 }), weight: 40 },
|
|
1058
|
+
{ arbitrary: fc2.integer({ min: 0, max: 100 }), weight: 25 },
|
|
1059
|
+
{ arbitrary: fc2.integer({ min: 0, max: 1e3 }), weight: 8 },
|
|
1060
|
+
{ arbitrary: fc2.constantFrom(-1, -2, -10), weight: 3 },
|
|
1061
|
+
{ arbitrary: fc2.constantFrom(0.5, 1.5, 2.25), weight: 2 },
|
|
1062
|
+
{ arbitrary: fc2.constantFrom(...HUGE_SIZES), weight: 1 }
|
|
1063
|
+
);
|
|
1064
|
+
const mined = ctx.mined.numbers.filter((n3) => Number.isInteger(n3) && n3 >= -10 && n3 <= 1e3);
|
|
1065
|
+
return weighted(typeDriven, mined, ctx);
|
|
1066
|
+
}
|
|
1049
1067
|
var regexCache = /* @__PURE__ */ new WeakMap();
|
|
1050
1068
|
var sampleCache = /* @__PURE__ */ new WeakMap();
|
|
1051
1069
|
function regexSamples(arbs) {
|
|
@@ -1186,7 +1204,7 @@ function anythingArb(ctx) {
|
|
|
1186
1204
|
function compile(spec, ctx) {
|
|
1187
1205
|
switch (spec.kind) {
|
|
1188
1206
|
case "number":
|
|
1189
|
-
return numberArb(ctx);
|
|
1207
|
+
return spec.size ? sizeArb(ctx) : numberArb(ctx);
|
|
1190
1208
|
case "bigint":
|
|
1191
1209
|
return fc2.oneof(fc2.bigInt({ min: -1000n, max: 1000n }), fc2.bigInt());
|
|
1192
1210
|
case "string":
|
|
@@ -1416,8 +1434,87 @@ function referencedModuleValues(nodes) {
|
|
|
1416
1434
|
return [...result];
|
|
1417
1435
|
}
|
|
1418
1436
|
|
|
1437
|
+
// src/core/analyze/sizeParams.ts
|
|
1438
|
+
import { Node as Node3, SyntaxKind as SyntaxKind3 } from "ts-morph";
|
|
1439
|
+
var SIZE_NAME = /^(n|k|count|size|length|len|times|limit|index|idx|width|height|depth|pages?|parts|chunks?|rows|cols|columns|steps|retries|attempts|digits|places|precision|decimals|people|items|level|levels)$|(Count|Size|Length|Index|Limit|Width|Height|Depth|Times)$/;
|
|
1440
|
+
var LENGTH_CALLS = /* @__PURE__ */ new Set([
|
|
1441
|
+
"repeat",
|
|
1442
|
+
"padStart",
|
|
1443
|
+
"padEnd",
|
|
1444
|
+
"at",
|
|
1445
|
+
"slice",
|
|
1446
|
+
"substring",
|
|
1447
|
+
"substr",
|
|
1448
|
+
"splice"
|
|
1449
|
+
]);
|
|
1450
|
+
function isLoopCondition(node) {
|
|
1451
|
+
for (let p = node.getParent(); p; p = p.getParent()) {
|
|
1452
|
+
if (Node3.isForStatement(p)) return p.getCondition() !== void 0 && isWithin(node, p.getCondition());
|
|
1453
|
+
if (Node3.isWhileStatement(p) || Node3.isDoStatement(p)) return isWithin(node, p.getExpression());
|
|
1454
|
+
if (Node3.isBlock(p) || Node3.isFunctionLikeDeclaration(p)) return false;
|
|
1455
|
+
}
|
|
1456
|
+
return false;
|
|
1457
|
+
}
|
|
1458
|
+
function isWithin(node, container) {
|
|
1459
|
+
return node.getPos() >= container.getPos() && node.getEnd() <= container.getEnd();
|
|
1460
|
+
}
|
|
1461
|
+
function usedAsSize(id) {
|
|
1462
|
+
const parent = id.getParent();
|
|
1463
|
+
if (!parent) return false;
|
|
1464
|
+
if (Node3.isBinaryExpression(parent)) {
|
|
1465
|
+
const op = parent.getOperatorToken().getKind();
|
|
1466
|
+
const comparison = op === SyntaxKind3.LessThanToken || op === SyntaxKind3.LessThanEqualsToken || op === SyntaxKind3.GreaterThanToken || op === SyntaxKind3.GreaterThanEqualsToken;
|
|
1467
|
+
if (comparison && isLoopCondition(parent)) return true;
|
|
1468
|
+
}
|
|
1469
|
+
if (Node3.isElementAccessExpression(parent) && parent.getArgumentExpression() === id) return true;
|
|
1470
|
+
if (Node3.isPropertyAssignment(parent) && parent.getName() === "length" && parent.getInitializer() === id)
|
|
1471
|
+
return true;
|
|
1472
|
+
if (Node3.isShorthandPropertyAssignment(parent) && parent.getName() === "length") return true;
|
|
1473
|
+
if ((Node3.isCallExpression(parent) || Node3.isNewExpression(parent)) && parent.getArguments().includes(id)) {
|
|
1474
|
+
const callee = parent.getExpression();
|
|
1475
|
+
const name = Node3.isPropertyAccessExpression(callee) ? callee.getName() : callee.getText();
|
|
1476
|
+
if (name === "Array" || LENGTH_CALLS.has(name)) return true;
|
|
1477
|
+
}
|
|
1478
|
+
return false;
|
|
1479
|
+
}
|
|
1480
|
+
function sizeLikeParams(fns) {
|
|
1481
|
+
const out = /* @__PURE__ */ new Set();
|
|
1482
|
+
for (const fn of fns) {
|
|
1483
|
+
if (!(Node3.isFunctionDeclaration(fn) || Node3.isFunctionExpression(fn) || Node3.isArrowFunction(fn)))
|
|
1484
|
+
continue;
|
|
1485
|
+
const params = fn.getParameters().map((p) => p.getNameNode()).filter((n3) => Node3.isIdentifier(n3)).map((n3) => n3.getText());
|
|
1486
|
+
for (const name of params) {
|
|
1487
|
+
if (SIZE_NAME.test(name)) {
|
|
1488
|
+
out.add(name);
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
const body = fn.getBody();
|
|
1492
|
+
if (!body) continue;
|
|
1493
|
+
const refs = body.getDescendantsOfKind(SyntaxKind3.Identifier).filter((id) => id.getText() === name).filter((id) => {
|
|
1494
|
+
const p = id.getParent();
|
|
1495
|
+
return !(Node3.isPropertyAccessExpression(p) && p.getNameNode() === id);
|
|
1496
|
+
});
|
|
1497
|
+
if (refs.some(usedAsSize)) {
|
|
1498
|
+
out.add(name);
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
for (const decl of body.getDescendantsOfKind(SyntaxKind3.VariableDeclaration)) {
|
|
1502
|
+
const init = decl.getInitializer();
|
|
1503
|
+
const local = decl.getNameNode();
|
|
1504
|
+
if (!init || !Node3.isIdentifier(local) || !refs.some((r) => isWithin(r, init))) continue;
|
|
1505
|
+
const localRefs = body.getDescendantsOfKind(SyntaxKind3.Identifier).filter((id) => id.getText() === local.getText() && id !== local);
|
|
1506
|
+
if (localRefs.some(usedAsSize)) {
|
|
1507
|
+
out.add(name);
|
|
1508
|
+
break;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
return out;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1419
1516
|
// src/core/detect/toplevel.ts
|
|
1420
|
-
import { Node as
|
|
1517
|
+
import { Node as Node4, SyntaxKind as SyntaxKind4, ts as ts2 } from "ts-morph";
|
|
1421
1518
|
function normalize(node) {
|
|
1422
1519
|
const sf = node.getSourceFile().compilerNode;
|
|
1423
1520
|
const out = [];
|
|
@@ -1435,11 +1532,11 @@ function normalize(node) {
|
|
|
1435
1532
|
return out.join(" ");
|
|
1436
1533
|
}
|
|
1437
1534
|
function topLevelNameOf(decl) {
|
|
1438
|
-
if (
|
|
1535
|
+
if (Node4.isFunctionDeclaration(decl) || Node4.isClassDeclaration(decl) || Node4.isEnumDeclaration(decl)) {
|
|
1439
1536
|
return decl.getName() ?? "default";
|
|
1440
1537
|
}
|
|
1441
|
-
if (
|
|
1442
|
-
if (
|
|
1538
|
+
if (Node4.isVariableDeclaration(decl)) return decl.getName();
|
|
1539
|
+
if (Node4.isExportAssignment(decl)) return "default";
|
|
1443
1540
|
return void 0;
|
|
1444
1541
|
}
|
|
1445
1542
|
function indexModule(sf) {
|
|
@@ -1449,7 +1546,7 @@ function indexModule(sf) {
|
|
|
1449
1546
|
const cjsStatements = [];
|
|
1450
1547
|
const add = (name, kind, node, printed = node) => decls.set(name, { name, kind, node, normalized: normalize(printed), raw: printed.getText() });
|
|
1451
1548
|
for (const st of sf.getStatements()) {
|
|
1452
|
-
if (
|
|
1549
|
+
if (Node4.isFunctionDeclaration(st)) {
|
|
1453
1550
|
const name = st.getName() ?? "default";
|
|
1454
1551
|
const prev = decls.get(name);
|
|
1455
1552
|
add(name, "function", st);
|
|
@@ -1458,21 +1555,21 @@ function indexModule(sf) {
|
|
|
1458
1555
|
cur.normalized = prev.normalized + cur.normalized;
|
|
1459
1556
|
cur.raw = prev.raw + cur.raw;
|
|
1460
1557
|
}
|
|
1461
|
-
} else if (
|
|
1558
|
+
} else if (Node4.isVariableStatement(st)) {
|
|
1462
1559
|
for (const d of st.getDeclarations()) {
|
|
1463
1560
|
const nameNode = d.getNameNode();
|
|
1464
|
-
if (
|
|
1561
|
+
if (Node4.isIdentifier(nameNode)) {
|
|
1465
1562
|
add(d.getName(), "variable", d);
|
|
1466
1563
|
} else {
|
|
1467
|
-
for (const id of nameNode.getDescendantsOfKind(
|
|
1564
|
+
for (const id of nameNode.getDescendantsOfKind(SyntaxKind4.Identifier))
|
|
1468
1565
|
add(id.getText(), "variable", d);
|
|
1469
1566
|
}
|
|
1470
1567
|
}
|
|
1471
|
-
} else if (
|
|
1568
|
+
} else if (Node4.isClassDeclaration(st)) {
|
|
1472
1569
|
add(st.getName() ?? "default", "class", st);
|
|
1473
|
-
} else if (
|
|
1570
|
+
} else if (Node4.isEnumDeclaration(st)) {
|
|
1474
1571
|
add(st.getName(), "enum", st);
|
|
1475
|
-
} else if (
|
|
1572
|
+
} else if (Node4.isImportDeclaration(st)) {
|
|
1476
1573
|
if (st.isTypeOnly()) continue;
|
|
1477
1574
|
const spec = st.getModuleSpecifierValue();
|
|
1478
1575
|
const def = st.getDefaultImport();
|
|
@@ -1504,12 +1601,12 @@ function indexModule(sf) {
|
|
|
1504
1601
|
raw: `${n3.getName()} from ${spec}`
|
|
1505
1602
|
});
|
|
1506
1603
|
}
|
|
1507
|
-
} else if (
|
|
1604
|
+
} else if (Node4.isExportAssignment(st)) {
|
|
1508
1605
|
add("default", "default", st);
|
|
1509
1606
|
} else if (isCjsExport(st)) {
|
|
1510
1607
|
cjsStatements.push(st);
|
|
1511
1608
|
continue;
|
|
1512
|
-
} else if (
|
|
1609
|
+
} else if (Node4.isInterfaceDeclaration(st) || Node4.isTypeAliasDeclaration(st) || Node4.isExportDeclaration(st) || Node4.isModuleDeclaration(st) || Node4.isEmptyStatement(st)) {
|
|
1513
1610
|
continue;
|
|
1514
1611
|
} else {
|
|
1515
1612
|
sideEffects.push(normalize(st));
|
|
@@ -1530,9 +1627,9 @@ function indexModule(sf) {
|
|
|
1530
1627
|
return { sf, decls, sideEffects, exports };
|
|
1531
1628
|
}
|
|
1532
1629
|
function isCjsExport(st) {
|
|
1533
|
-
if (!
|
|
1630
|
+
if (!Node4.isExpressionStatement(st)) return false;
|
|
1534
1631
|
const expr = st.getExpression();
|
|
1535
|
-
return
|
|
1632
|
+
return Node4.isBinaryExpression(expr) && expr.getOperatorToken().getKind() === SyntaxKind4.EqualsToken && cjsTarget(expr.getLeft()) !== void 0;
|
|
1536
1633
|
}
|
|
1537
1634
|
function cjsTarget(left) {
|
|
1538
1635
|
const text2 = left.getText().replace(/\s+/g, "");
|
|
@@ -1541,19 +1638,19 @@ function cjsTarget(left) {
|
|
|
1541
1638
|
return m ? m[1] : void 0;
|
|
1542
1639
|
}
|
|
1543
1640
|
function cjsExport(st, decls, exportsMap, add) {
|
|
1544
|
-
if (!
|
|
1641
|
+
if (!Node4.isExpressionStatement(st)) return false;
|
|
1545
1642
|
const expr = st.getExpression();
|
|
1546
|
-
if (!
|
|
1643
|
+
if (!Node4.isBinaryExpression(expr) || expr.getOperatorToken().getKind() !== SyntaxKind4.EqualsToken)
|
|
1547
1644
|
return false;
|
|
1548
1645
|
const target = cjsTarget(expr.getLeft());
|
|
1549
1646
|
if (target === void 0) return false;
|
|
1550
1647
|
const bind = (exportName, value) => {
|
|
1551
|
-
if (
|
|
1648
|
+
if (Node4.isIdentifier(value) && decls.has(value.getText())) {
|
|
1552
1649
|
exportsMap.set(exportName, value.getText());
|
|
1553
1650
|
return;
|
|
1554
1651
|
}
|
|
1555
1652
|
const synthetic = `exports.${exportName}`;
|
|
1556
|
-
const isFn =
|
|
1653
|
+
const isFn = Node4.isArrowFunction(value) || Node4.isFunctionExpression(value);
|
|
1557
1654
|
add(synthetic, isFn ? "function" : "variable", value);
|
|
1558
1655
|
exportsMap.set(exportName, synthetic);
|
|
1559
1656
|
};
|
|
@@ -1562,12 +1659,12 @@ function cjsExport(st, decls, exportsMap, add) {
|
|
|
1562
1659
|
bind(target, rhs);
|
|
1563
1660
|
return true;
|
|
1564
1661
|
}
|
|
1565
|
-
if (
|
|
1662
|
+
if (Node4.isObjectLiteralExpression(rhs)) {
|
|
1566
1663
|
for (const prop of rhs.getProperties()) {
|
|
1567
|
-
if (
|
|
1568
|
-
else if (
|
|
1664
|
+
if (Node4.isShorthandPropertyAssignment(prop)) bind(prop.getName(), prop.getNameNode());
|
|
1665
|
+
else if (Node4.isPropertyAssignment(prop))
|
|
1569
1666
|
bind(prop.getName().replace(/^['"]|['"]$/g, ""), prop.getInitializerOrThrow());
|
|
1570
|
-
else if (
|
|
1667
|
+
else if (Node4.isMethodDeclaration(prop)) {
|
|
1571
1668
|
add(`exports.${prop.getName()}`, "variable", prop);
|
|
1572
1669
|
exportsMap.set(prop.getName(), `exports.${prop.getName()}`);
|
|
1573
1670
|
}
|
|
@@ -1594,13 +1691,13 @@ function reach(index, start, depth) {
|
|
|
1594
1691
|
for (let d = 0; d < depth && frontier.length > 0; d++) {
|
|
1595
1692
|
const next = [];
|
|
1596
1693
|
for (const { node, path } of frontier) {
|
|
1597
|
-
for (const id of node.getDescendantsOfKind(
|
|
1694
|
+
for (const id of node.getDescendantsOfKind(SyntaxKind4.Identifier)) {
|
|
1598
1695
|
const name = id.getText();
|
|
1599
1696
|
const top = index.decls.get(name);
|
|
1600
1697
|
if (!top || local.has(name) || top.node === start) continue;
|
|
1601
1698
|
const sym = id.getSymbol();
|
|
1602
1699
|
const decls = sym?.getDeclarations() ?? [];
|
|
1603
|
-
if (sym && !decls.some((dn) =>
|
|
1700
|
+
if (sym && !decls.some((dn) => isWithin2(dn, top.node))) continue;
|
|
1604
1701
|
if (top.kind === "import") {
|
|
1605
1702
|
const aliased = sym?.isAlias() ? sym.getAliasedSymbol() : void 0;
|
|
1606
1703
|
for (const ad of aliased?.getDeclarations() ?? []) {
|
|
@@ -1623,7 +1720,7 @@ function reach(index, start, depth) {
|
|
|
1623
1720
|
}
|
|
1624
1721
|
return { local, imported };
|
|
1625
1722
|
}
|
|
1626
|
-
function
|
|
1723
|
+
function isWithin2(node, ancestor) {
|
|
1627
1724
|
for (let cur = node; cur; cur = cur.getParent()) if (cur === ancestor) return true;
|
|
1628
1725
|
return false;
|
|
1629
1726
|
}
|
|
@@ -1797,6 +1894,13 @@ function num(n3) {
|
|
|
1797
1894
|
if (Object.is(n3, -0)) return "-0";
|
|
1798
1895
|
return String(n3);
|
|
1799
1896
|
}
|
|
1897
|
+
var MAX_ITEMS = 10;
|
|
1898
|
+
var SHOWN_ITEMS = 6;
|
|
1899
|
+
var abbreviateLists = true;
|
|
1900
|
+
function list(items, f) {
|
|
1901
|
+
if (!abbreviateLists || items.length <= MAX_ITEMS) return items.map(f).join(", ");
|
|
1902
|
+
return `${items.slice(0, SHOWN_ITEMS).map(f).join(", ")}, \u2026 ${items.length - SHOWN_ITEMS} more`;
|
|
1903
|
+
}
|
|
1800
1904
|
var IDENT = /^[A-Za-z_$][\w$]*$/;
|
|
1801
1905
|
var key = (k) => IDENT.test(k) ? k : JSON.stringify(k);
|
|
1802
1906
|
function formatValue(v, max = MAX_LEN) {
|
|
@@ -1826,12 +1930,11 @@ function formatValue(v, max = MAX_LEN) {
|
|
|
1826
1930
|
if (seen.has(x)) return "[Circular]";
|
|
1827
1931
|
seen.add(x);
|
|
1828
1932
|
try {
|
|
1829
|
-
if (Array.isArray(x)) return `[${x
|
|
1933
|
+
if (Array.isArray(x)) return `[${list(x, f)}]`;
|
|
1830
1934
|
if (x instanceof Date)
|
|
1831
1935
|
return Number.isNaN(x.getTime()) ? "new Date(NaN)" : `new Date(${JSON.stringify(x.toISOString())})`;
|
|
1832
|
-
if (x instanceof Map)
|
|
1833
|
-
|
|
1834
|
-
if (x instanceof Set) return `new Set([${[...x].map(f).join(", ")}])`;
|
|
1936
|
+
if (x instanceof Map) return `new Map([${list([...x], ([k, val]) => `[${f(k)}, ${f(val)}]`)}])`;
|
|
1937
|
+
if (x instanceof Set) return `new Set([${list([...x], f)}])`;
|
|
1835
1938
|
if (x instanceof RegExp) return String(x);
|
|
1836
1939
|
if (isProtoDescriptor(x)) {
|
|
1837
1940
|
const { proto, own } = x[PROTO_TAG];
|
|
@@ -1845,7 +1948,7 @@ function formatValue(v, max = MAX_LEN) {
|
|
|
1845
1948
|
return `new ${x.constructor.name}([${Array.from(x).join(", ")}])`;
|
|
1846
1949
|
const entries = Object.entries(x);
|
|
1847
1950
|
if (entries.length === 0) return "{}";
|
|
1848
|
-
return `{ ${entries
|
|
1951
|
+
return `{ ${list(entries, ([k, val]) => `${key(k)}: ${f(val)}`)} }`;
|
|
1849
1952
|
} finally {
|
|
1850
1953
|
seen.delete(x);
|
|
1851
1954
|
}
|
|
@@ -1855,7 +1958,16 @@ function formatValue(v, max = MAX_LEN) {
|
|
|
1855
1958
|
function formatArgs(args, max = MAX_LEN) {
|
|
1856
1959
|
return clip(args.map((a) => formatValue(a, max)).join(", "), max);
|
|
1857
1960
|
}
|
|
1858
|
-
function formatEnc(e, max = MAX_LEN) {
|
|
1961
|
+
function formatEnc(e, max = MAX_LEN, abbreviate = true) {
|
|
1962
|
+
const previous = abbreviateLists;
|
|
1963
|
+
abbreviateLists = abbreviate;
|
|
1964
|
+
try {
|
|
1965
|
+
return formatEncInner(e, max);
|
|
1966
|
+
} finally {
|
|
1967
|
+
abbreviateLists = previous;
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
function formatEncInner(e, max) {
|
|
1859
1971
|
const f = (x) => {
|
|
1860
1972
|
switch (x.t) {
|
|
1861
1973
|
case "undefined":
|
|
@@ -1873,18 +1985,18 @@ function formatEnc(e, max = MAX_LEN) {
|
|
|
1873
1985
|
case "symbol":
|
|
1874
1986
|
return `Symbol(${x.d})`;
|
|
1875
1987
|
case "array":
|
|
1876
|
-
return `[${x.items
|
|
1988
|
+
return `[${list(x.items, f)}]`;
|
|
1877
1989
|
case "object": {
|
|
1878
1990
|
const prefix = x.ctor && x.ctor !== "null-prototype" ? `${x.ctor} ` : "";
|
|
1879
1991
|
if (x.entries.length === 0) return `${prefix}{}`;
|
|
1880
|
-
return `${prefix}{ ${x.entries
|
|
1992
|
+
return `${prefix}{ ${list(x.entries, ([k, v]) => `${key(k)}: ${f(v)}`)} }`;
|
|
1881
1993
|
}
|
|
1882
1994
|
case "date":
|
|
1883
1995
|
return Number.isNaN(x.v) ? "Invalid Date" : new Date(x.v).toISOString();
|
|
1884
1996
|
case "map":
|
|
1885
|
-
return `Map(${x.entries.length}) { ${x.entries
|
|
1997
|
+
return `Map(${x.entries.length}) { ${list(x.entries, ([k, v]) => `${f(k)} => ${f(v)}`)} }`;
|
|
1886
1998
|
case "set":
|
|
1887
|
-
return `Set(${x.items.length}) { ${x.items
|
|
1999
|
+
return `Set(${x.items.length}) { ${list(x.items, f)} }`;
|
|
1888
2000
|
case "regexp":
|
|
1889
2001
|
return `/${x.source}/${x.flags}`;
|
|
1890
2002
|
case "function":
|
|
@@ -1892,7 +2004,7 @@ function formatEnc(e, max = MAX_LEN) {
|
|
|
1892
2004
|
case "error":
|
|
1893
2005
|
return `${x.ctor}: ${x.message}`;
|
|
1894
2006
|
case "typed":
|
|
1895
|
-
return `${x.ctor} [${x.items
|
|
2007
|
+
return `${x.ctor} [${list(x.items, String)}]`;
|
|
1896
2008
|
case "promise":
|
|
1897
2009
|
return "Promise";
|
|
1898
2010
|
case "cycle":
|
|
@@ -1903,12 +2015,12 @@ function formatEnc(e, max = MAX_LEN) {
|
|
|
1903
2015
|
};
|
|
1904
2016
|
return clip(f(e), max);
|
|
1905
2017
|
}
|
|
1906
|
-
function formatOutcome(o, max = MAX_LEN) {
|
|
2018
|
+
function formatOutcome(o, max = MAX_LEN, abbreviate = true) {
|
|
1907
2019
|
switch (o.kind) {
|
|
1908
2020
|
case "return":
|
|
1909
|
-
return o.async ? `resolves ${formatEnc(o.value, max)}` : formatEnc(o.value, max);
|
|
2021
|
+
return o.async ? `resolves ${formatEnc(o.value, max, abbreviate)}` : formatEnc(o.value, max, abbreviate);
|
|
1910
2022
|
case "throw": {
|
|
1911
|
-
const e = o.error.t === "error" ? `${o.error.ctor}: ${o.error.message}` : formatEnc(o.error, max);
|
|
2023
|
+
const e = o.error.t === "error" ? `${o.error.ctor}: ${o.error.message}` : formatEnc(o.error, max, abbreviate);
|
|
1912
2024
|
return clip(`${o.async ? "rejects" : "throws"} ${e}`, max);
|
|
1913
2025
|
}
|
|
1914
2026
|
case "timeout":
|
|
@@ -2213,6 +2325,10 @@ function biasFor(runIndex) {
|
|
|
2213
2325
|
return 2 + Math.floor(Math.log(runIndex + 1) / Math.log(10));
|
|
2214
2326
|
}
|
|
2215
2327
|
var size = (input) => formatArgs(input, 1e4).length;
|
|
2328
|
+
function witnessCost(input, r) {
|
|
2329
|
+
const out = r ? formatOutcome(r.base, 2e3, false).length + formatOutcome(r.head, 2e3, false).length : 0;
|
|
2330
|
+
return inputComplexity(input) + Math.max(0, out - 40) / 20;
|
|
2331
|
+
}
|
|
2216
2332
|
async function explore(options) {
|
|
2217
2333
|
const {
|
|
2218
2334
|
arbitrary,
|
|
@@ -2225,6 +2341,8 @@ async function explore(options) {
|
|
|
2225
2341
|
shrinkBudget = 500,
|
|
2226
2342
|
maxWitnesses = 3,
|
|
2227
2343
|
maxFailures = 20,
|
|
2344
|
+
maxEdgeFailures = 2,
|
|
2345
|
+
maxTimeoutChecks = 12,
|
|
2228
2346
|
selfCheck = 50,
|
|
2229
2347
|
humanizeBudget = 150
|
|
2230
2348
|
} = options;
|
|
@@ -2242,7 +2360,13 @@ async function explore(options) {
|
|
|
2242
2360
|
return t;
|
|
2243
2361
|
};
|
|
2244
2362
|
let outOfDomain = 0;
|
|
2363
|
+
let edgeFailures = 0;
|
|
2364
|
+
let skippedEdgeInputs = 0;
|
|
2245
2365
|
const wanted = (input) => {
|
|
2366
|
+
if (options.realism && edgeFailures >= maxEdgeFailures && tierOf(input) === 0) {
|
|
2367
|
+
skippedEdgeInputs++;
|
|
2368
|
+
return false;
|
|
2369
|
+
}
|
|
2246
2370
|
if (!options.inDomain || options.inDomain(input)) return true;
|
|
2247
2371
|
if (options.strictDomain) return false;
|
|
2248
2372
|
return outOfDomain++ % 4 === 0;
|
|
@@ -2261,8 +2385,30 @@ async function explore(options) {
|
|
|
2261
2385
|
let failures = 0;
|
|
2262
2386
|
let shrinkEvaluations = 0;
|
|
2263
2387
|
let stoppedBy = "runs";
|
|
2264
|
-
const countFailures = (r) => {
|
|
2265
|
-
if ([r.base, r.head].some((o) => o.kind === "timeout" || o.kind === "crash"))
|
|
2388
|
+
const countFailures = (input, r) => {
|
|
2389
|
+
if (![r.base, r.head].some((o) => o.kind === "timeout" || o.kind === "crash")) return;
|
|
2390
|
+
failures++;
|
|
2391
|
+
if (options.realism && tierOf(input) === 0) edgeFailures++;
|
|
2392
|
+
};
|
|
2393
|
+
const realisticFailures = () => failures - edgeFailures;
|
|
2394
|
+
let timeoutChecks = 0;
|
|
2395
|
+
const timedOut = (r) => r.base.kind === "timeout" || r.head.kind === "timeout";
|
|
2396
|
+
const confirmTimeouts = async (inputs, results) => {
|
|
2397
|
+
const idx2 = results.map((r, i) => i).filter((i) => timedOut(results[i]) && !(options.realism && tierOf(inputs[i]) === 0)).slice(0, Math.max(0, maxTimeoutChecks - timeoutChecks));
|
|
2398
|
+
if (idx2.length === 0) return;
|
|
2399
|
+
timeoutChecks += idx2.length;
|
|
2400
|
+
const again = await evaluate(
|
|
2401
|
+
idx2.map((i) => inputs[i]),
|
|
2402
|
+
{ timeoutScale: 4 }
|
|
2403
|
+
);
|
|
2404
|
+
idx2.forEach((i, j) => {
|
|
2405
|
+
const first2 = results[i];
|
|
2406
|
+
const second = again[j];
|
|
2407
|
+
results[i] = {
|
|
2408
|
+
base: first2.base.kind === "timeout" ? second.base : first2.base,
|
|
2409
|
+
head: first2.head.kind === "timeout" ? second.head : first2.head
|
|
2410
|
+
};
|
|
2411
|
+
});
|
|
2266
2412
|
};
|
|
2267
2413
|
const evalCached = async (inputs) => {
|
|
2268
2414
|
const keys = inputs.map(stableKey);
|
|
@@ -2276,9 +2422,10 @@ async function explore(options) {
|
|
|
2276
2422
|
});
|
|
2277
2423
|
if (missing.length > 0) {
|
|
2278
2424
|
const results = await evaluate(missing);
|
|
2425
|
+
await confirmTimeouts(missing, results);
|
|
2279
2426
|
results.forEach((r, i) => {
|
|
2280
2427
|
cache.set(missingKeys[i], r);
|
|
2281
|
-
countFailures(r);
|
|
2428
|
+
countFailures(missing[i], r);
|
|
2282
2429
|
});
|
|
2283
2430
|
}
|
|
2284
2431
|
return keys.map((k) => cache.get(k));
|
|
@@ -2353,12 +2500,14 @@ async function explore(options) {
|
|
|
2353
2500
|
flakyClusters: 0,
|
|
2354
2501
|
stoppedBy: "runs",
|
|
2355
2502
|
shrinkEvaluations: 0,
|
|
2356
|
-
failures
|
|
2503
|
+
failures,
|
|
2504
|
+
edgeFailures,
|
|
2505
|
+
skippedEdgeInputs
|
|
2357
2506
|
};
|
|
2358
2507
|
}
|
|
2359
2508
|
}
|
|
2360
2509
|
cache.set(stableKey(inputs[i]), a[i]);
|
|
2361
|
-
countFailures(a[i]);
|
|
2510
|
+
countFailures(inputs[i], a[i]);
|
|
2362
2511
|
record(first[i], a[i]);
|
|
2363
2512
|
}
|
|
2364
2513
|
inputsRun = inputs.length;
|
|
@@ -2371,7 +2520,7 @@ async function explore(options) {
|
|
|
2371
2520
|
stoppedBy = "deadline";
|
|
2372
2521
|
break;
|
|
2373
2522
|
}
|
|
2374
|
-
if (
|
|
2523
|
+
if (realisticFailures() >= maxFailures) {
|
|
2375
2524
|
stoppedBy = "failures";
|
|
2376
2525
|
break;
|
|
2377
2526
|
}
|
|
@@ -2397,9 +2546,13 @@ async function explore(options) {
|
|
|
2397
2546
|
}
|
|
2398
2547
|
staleBatches = 0;
|
|
2399
2548
|
const results = await evaluate(batch.map((p) => p.value.value));
|
|
2549
|
+
await confirmTimeouts(
|
|
2550
|
+
batch.map((p) => p.value.value),
|
|
2551
|
+
results
|
|
2552
|
+
);
|
|
2400
2553
|
results.forEach((r, i) => {
|
|
2401
2554
|
cache.set(batch[i].key, r);
|
|
2402
|
-
countFailures(r);
|
|
2555
|
+
countFailures(batch[i].value.value, r);
|
|
2403
2556
|
record(batch[i].value, r);
|
|
2404
2557
|
});
|
|
2405
2558
|
inputsRun += batch.length;
|
|
@@ -2409,6 +2562,12 @@ async function explore(options) {
|
|
|
2409
2562
|
for (const cluster of clusters.values()) {
|
|
2410
2563
|
let cur = cluster.seed;
|
|
2411
2564
|
let evals = 0;
|
|
2565
|
+
const w0 = cluster.witnesses[0];
|
|
2566
|
+
if (w0.base.kind === "timeout" || w0.head.kind === "timeout") {
|
|
2567
|
+
cluster.witnesses.length = 1;
|
|
2568
|
+
cluster.witnesses[0].minimal = true;
|
|
2569
|
+
continue;
|
|
2570
|
+
}
|
|
2412
2571
|
if (exampleValues.has(cur) && !arbitrary.canShrinkWithoutContext(cur.value)) continue;
|
|
2413
2572
|
let improved = true;
|
|
2414
2573
|
while (improved && evals < shrinkBudget) {
|
|
@@ -2435,7 +2594,7 @@ async function explore(options) {
|
|
|
2435
2594
|
break;
|
|
2436
2595
|
}
|
|
2437
2596
|
}
|
|
2438
|
-
if (
|
|
2597
|
+
if (realisticFailures() >= maxFailures * 2) break;
|
|
2439
2598
|
}
|
|
2440
2599
|
shrinkEvaluations += evals;
|
|
2441
2600
|
const sameCluster = async (candidates) => {
|
|
@@ -2450,7 +2609,9 @@ async function explore(options) {
|
|
|
2450
2609
|
finals.set(stableKey(h.input), h.input);
|
|
2451
2610
|
finals.set(stableKey(start), start);
|
|
2452
2611
|
}
|
|
2453
|
-
const ranked = [...finals.values()].filter((input) => classify(input, cache.get(stableKey(input)))?.key === cluster.key).sort(
|
|
2612
|
+
const ranked = [...finals.values()].filter((input) => classify(input, cache.get(stableKey(input)))?.key === cluster.key).sort(
|
|
2613
|
+
(a, b) => tierOf(b) - tierOf(a) || witnessCost(a, cache.get(stableKey(a))) - witnessCost(b, cache.get(stableKey(b)))
|
|
2614
|
+
).slice(0, maxWitnesses);
|
|
2454
2615
|
cluster.witnesses = ranked.map((input, i) => {
|
|
2455
2616
|
const r = cache.get(stableKey(input));
|
|
2456
2617
|
const w = {
|
|
@@ -2468,7 +2629,7 @@ async function explore(options) {
|
|
|
2468
2629
|
for (const cluster of clusters.values()) {
|
|
2469
2630
|
for (const w of cluster.witnesses) w.realism ??= options.realism(w.input);
|
|
2470
2631
|
cluster.witnesses.sort(
|
|
2471
|
-
(a, b) => b.realism.tier - a.realism.tier ||
|
|
2632
|
+
(a, b) => b.realism.tier - a.realism.tier || witnessCost(a.input, { base: a.base, head: a.head }) - witnessCost(b.input, { base: b.base, head: b.head })
|
|
2472
2633
|
);
|
|
2473
2634
|
cluster.witnesses.forEach((w, i) => w.minimal = i === 0);
|
|
2474
2635
|
}
|
|
@@ -2500,13 +2661,15 @@ async function explore(options) {
|
|
|
2500
2661
|
flakyClusters,
|
|
2501
2662
|
stoppedBy,
|
|
2502
2663
|
shrinkEvaluations,
|
|
2503
|
-
failures
|
|
2664
|
+
failures,
|
|
2665
|
+
edgeFailures,
|
|
2666
|
+
skippedEdgeInputs
|
|
2504
2667
|
};
|
|
2505
2668
|
}
|
|
2506
2669
|
|
|
2507
2670
|
// src/core/types/fromType.ts
|
|
2508
2671
|
import {
|
|
2509
|
-
Node as
|
|
2672
|
+
Node as Node5,
|
|
2510
2673
|
ts as ts3
|
|
2511
2674
|
} from "ts-morph";
|
|
2512
2675
|
|
|
@@ -2611,6 +2774,11 @@ function printSpec(spec) {
|
|
|
2611
2774
|
return spec.kind;
|
|
2612
2775
|
}
|
|
2613
2776
|
}
|
|
2777
|
+
function markSize(spec) {
|
|
2778
|
+
if (spec.kind === "number") return { kind: "number", size: true };
|
|
2779
|
+
if (spec.kind === "union") return { ...spec, options: spec.options.map(markSize) };
|
|
2780
|
+
return spec;
|
|
2781
|
+
}
|
|
2614
2782
|
|
|
2615
2783
|
// src/core/types/fromType.ts
|
|
2616
2784
|
var MAX_UNION_OPTIONS = 64;
|
|
@@ -2753,7 +2921,7 @@ function objectLike(type, ctx) {
|
|
|
2753
2921
|
}
|
|
2754
2922
|
function isClassInstance(type) {
|
|
2755
2923
|
const decls = type.getSymbol()?.getDeclarations() ?? [];
|
|
2756
|
-
return decls.some((d) =>
|
|
2924
|
+
return decls.some((d) => Node5.isClassDeclaration(d) || Node5.isClassExpression(d));
|
|
2757
2925
|
}
|
|
2758
2926
|
function unwrapPromise(type) {
|
|
2759
2927
|
const name = symbolName(type);
|
|
@@ -2832,7 +3000,7 @@ function paramSpec(p, location, index) {
|
|
|
2832
3000
|
if (optional && !rest) {
|
|
2833
3001
|
spec = spec.kind === "union" ? spec.options.some((o) => o.kind === "undefined") ? spec : { kind: "union", options: [...spec.options, { kind: "undefined" }] } : spec.kind === "undefined" ? spec : { kind: "union", options: [spec, { kind: "undefined" }] };
|
|
2834
3002
|
}
|
|
2835
|
-
const name =
|
|
3003
|
+
const name = Node5.isIdentifier(p.getNameNode()) ? p.getName() : `arg${index}`;
|
|
2836
3004
|
return { name, spec, optional, rest };
|
|
2837
3005
|
}
|
|
2838
3006
|
function signatureFromNode(fn) {
|
|
@@ -2846,7 +3014,7 @@ function signatureFromCallable(decl) {
|
|
|
2846
3014
|
const checker = decl.getProject().getTypeChecker();
|
|
2847
3015
|
const params = sig.getParameters().map((sym, i) => {
|
|
2848
3016
|
const pdecl = sym.getValueDeclaration();
|
|
2849
|
-
const isParam = pdecl !== void 0 &&
|
|
3017
|
+
const isParam = pdecl !== void 0 && Node5.isParameterDeclaration(pdecl);
|
|
2850
3018
|
const rest = isParam && pdecl.isRestParameter();
|
|
2851
3019
|
const optional = !rest && ((sym.getFlags() & ts3.SymbolFlags.Optional) !== 0 || isParam && (pdecl.isOptional() || pdecl.hasInitializer()));
|
|
2852
3020
|
let type = checker.getTypeOfSymbolAtLocation(sym, decl);
|
|
@@ -2861,26 +3029,26 @@ function signatureFromCallable(decl) {
|
|
|
2861
3029
|
return { params, async: unwrapPromise(sig.getReturnType()) !== void 0 };
|
|
2862
3030
|
}
|
|
2863
3031
|
function signatureOf(node) {
|
|
2864
|
-
return
|
|
3032
|
+
return Node5.isVariableDeclaration(node) ? signatureFromCallable(node) : signatureFromNode(node);
|
|
2865
3033
|
}
|
|
2866
3034
|
|
|
2867
3035
|
// src/core/analyze/guards.ts
|
|
2868
|
-
import { Node as
|
|
3036
|
+
import { Node as Node6, SyntaxKind as SyntaxKind5 } from "ts-morph";
|
|
2869
3037
|
var UNKNOWN = /* @__PURE__ */ Symbol("unknown");
|
|
2870
3038
|
function compile2(node, params) {
|
|
2871
|
-
if (
|
|
2872
|
-
if (
|
|
3039
|
+
if (Node6.isParenthesizedExpression(node)) return compile2(node.getExpression(), params);
|
|
3040
|
+
if (Node6.isNumericLiteral(node)) {
|
|
2873
3041
|
const v = node.getLiteralValue();
|
|
2874
3042
|
return () => v;
|
|
2875
3043
|
}
|
|
2876
|
-
if (
|
|
3044
|
+
if (Node6.isStringLiteral(node) || Node6.isNoSubstitutionTemplateLiteral(node)) {
|
|
2877
3045
|
const v = node.getLiteralValue();
|
|
2878
3046
|
return () => v;
|
|
2879
3047
|
}
|
|
2880
|
-
if (node.getKind() ===
|
|
2881
|
-
if (node.getKind() ===
|
|
2882
|
-
if (node.getKind() ===
|
|
2883
|
-
if (
|
|
3048
|
+
if (node.getKind() === SyntaxKind5.TrueKeyword) return () => true;
|
|
3049
|
+
if (node.getKind() === SyntaxKind5.FalseKeyword) return () => false;
|
|
3050
|
+
if (node.getKind() === SyntaxKind5.NullKeyword) return () => null;
|
|
3051
|
+
if (Node6.isIdentifier(node)) {
|
|
2884
3052
|
const name = node.getText();
|
|
2885
3053
|
if (name === "undefined") return () => void 0;
|
|
2886
3054
|
if (name === "NaN") return () => NaN;
|
|
@@ -2888,7 +3056,7 @@ function compile2(node, params) {
|
|
|
2888
3056
|
if (params.has(name)) return (env) => env.get(name);
|
|
2889
3057
|
return void 0;
|
|
2890
3058
|
}
|
|
2891
|
-
if (
|
|
3059
|
+
if (Node6.isPropertyAccessExpression(node) && node.getName() === "length") {
|
|
2892
3060
|
const obj = compile2(node.getExpression(), params);
|
|
2893
3061
|
if (!obj) return void 0;
|
|
2894
3062
|
return (env) => {
|
|
@@ -2896,7 +3064,7 @@ function compile2(node, params) {
|
|
|
2896
3064
|
return typeof v === "string" || Array.isArray(v) ? v.length : UNKNOWN;
|
|
2897
3065
|
};
|
|
2898
3066
|
}
|
|
2899
|
-
if (
|
|
3067
|
+
if (Node6.isTypeOfExpression(node)) {
|
|
2900
3068
|
const e = compile2(node.getExpression(), params);
|
|
2901
3069
|
if (!e) return void 0;
|
|
2902
3070
|
return (env) => {
|
|
@@ -2904,16 +3072,16 @@ function compile2(node, params) {
|
|
|
2904
3072
|
return v === UNKNOWN ? UNKNOWN : v === null ? "object" : typeof v;
|
|
2905
3073
|
};
|
|
2906
3074
|
}
|
|
2907
|
-
if (
|
|
3075
|
+
if (Node6.isPrefixUnaryExpression(node)) {
|
|
2908
3076
|
const e = compile2(node.getOperand(), params);
|
|
2909
3077
|
if (!e) return void 0;
|
|
2910
3078
|
const op = node.getOperatorToken();
|
|
2911
|
-
if (op ===
|
|
2912
|
-
if (op ===
|
|
3079
|
+
if (op === SyntaxKind5.ExclamationToken) return (env) => e(env) === UNKNOWN ? UNKNOWN : !e(env);
|
|
3080
|
+
if (op === SyntaxKind5.MinusToken)
|
|
2913
3081
|
return (env) => typeof e(env) === "number" ? -e(env) : UNKNOWN;
|
|
2914
3082
|
return void 0;
|
|
2915
3083
|
}
|
|
2916
|
-
if (
|
|
3084
|
+
if (Node6.isCallExpression(node)) {
|
|
2917
3085
|
const callee = node.getExpression().getText().replace(/\s+/g, "");
|
|
2918
3086
|
const fns = {
|
|
2919
3087
|
"Number.isInteger": Number.isInteger,
|
|
@@ -2934,7 +3102,7 @@ function compile2(node, params) {
|
|
|
2934
3102
|
return v === UNKNOWN ? UNKNOWN : fn(v);
|
|
2935
3103
|
};
|
|
2936
3104
|
}
|
|
2937
|
-
if (
|
|
3105
|
+
if (Node6.isBinaryExpression(node)) {
|
|
2938
3106
|
const l = compile2(node.getLeft(), params);
|
|
2939
3107
|
const r = compile2(node.getRight(), params);
|
|
2940
3108
|
if (!l || !r) return void 0;
|
|
@@ -2945,29 +3113,29 @@ function compile2(node, params) {
|
|
|
2945
3113
|
return a === UNKNOWN || b === UNKNOWN ? UNKNOWN : f(a, b);
|
|
2946
3114
|
};
|
|
2947
3115
|
switch (op) {
|
|
2948
|
-
case
|
|
3116
|
+
case SyntaxKind5.LessThanToken:
|
|
2949
3117
|
return cmp((a, b) => a < b);
|
|
2950
|
-
case
|
|
3118
|
+
case SyntaxKind5.LessThanEqualsToken:
|
|
2951
3119
|
return cmp((a, b) => a <= b);
|
|
2952
|
-
case
|
|
3120
|
+
case SyntaxKind5.GreaterThanToken:
|
|
2953
3121
|
return cmp((a, b) => a > b);
|
|
2954
|
-
case
|
|
3122
|
+
case SyntaxKind5.GreaterThanEqualsToken:
|
|
2955
3123
|
return cmp((a, b) => a >= b);
|
|
2956
|
-
case
|
|
3124
|
+
case SyntaxKind5.EqualsEqualsEqualsToken:
|
|
2957
3125
|
return cmp((a, b) => a === b);
|
|
2958
|
-
case
|
|
3126
|
+
case SyntaxKind5.ExclamationEqualsEqualsToken:
|
|
2959
3127
|
return cmp((a, b) => a !== b);
|
|
2960
|
-
case
|
|
3128
|
+
case SyntaxKind5.EqualsEqualsToken:
|
|
2961
3129
|
return cmp((a, b) => a == b);
|
|
2962
|
-
case
|
|
3130
|
+
case SyntaxKind5.ExclamationEqualsToken:
|
|
2963
3131
|
return cmp((a, b) => a != b);
|
|
2964
|
-
case
|
|
3132
|
+
case SyntaxKind5.AmpersandAmpersandToken:
|
|
2965
3133
|
return (env) => {
|
|
2966
3134
|
const a = l(env);
|
|
2967
3135
|
if (a === UNKNOWN) return UNKNOWN;
|
|
2968
3136
|
return a ? r(env) : a;
|
|
2969
3137
|
};
|
|
2970
|
-
case
|
|
3138
|
+
case SyntaxKind5.BarBarToken:
|
|
2971
3139
|
return (env) => {
|
|
2972
3140
|
const a = l(env);
|
|
2973
3141
|
if (a === UNKNOWN) return UNKNOWN;
|
|
@@ -2980,30 +3148,30 @@ function compile2(node, params) {
|
|
|
2980
3148
|
return void 0;
|
|
2981
3149
|
}
|
|
2982
3150
|
function isExit(stmt) {
|
|
2983
|
-
if (
|
|
2984
|
-
if (
|
|
3151
|
+
if (Node6.isThrowStatement(stmt) || Node6.isReturnStatement(stmt)) return true;
|
|
3152
|
+
if (Node6.isBlock(stmt)) {
|
|
2985
3153
|
const s = stmt.getStatements();
|
|
2986
3154
|
return s.length >= 1 && isExit(s[s.length - 1]);
|
|
2987
3155
|
}
|
|
2988
3156
|
return false;
|
|
2989
3157
|
}
|
|
2990
3158
|
function inferGuards(fn) {
|
|
2991
|
-
if (!(
|
|
3159
|
+
if (!(Node6.isFunctionDeclaration(fn) || Node6.isFunctionExpression(fn) || Node6.isArrowFunction(fn)))
|
|
2992
3160
|
return void 0;
|
|
2993
3161
|
const body = fn.getBody();
|
|
2994
|
-
if (!body || !
|
|
3162
|
+
if (!body || !Node6.isBlock(body)) return void 0;
|
|
2995
3163
|
const paramDecls = fn.getParameters();
|
|
2996
|
-
const names = paramDecls.map((p) =>
|
|
3164
|
+
const names = paramDecls.map((p) => Node6.isIdentifier(p.getNameNode()) ? p.getName() : void 0);
|
|
2997
3165
|
const params = new Set(names.filter((n3) => !!n3));
|
|
2998
3166
|
const checks = [];
|
|
2999
3167
|
for (const stmt of body.getStatements()) {
|
|
3000
|
-
if (
|
|
3168
|
+
if (Node6.isIfStatement(stmt) && !stmt.getElseStatement() && isExit(stmt.getThenStatement())) {
|
|
3001
3169
|
const expr = compile2(stmt.getExpression(), params);
|
|
3002
3170
|
if (expr) checks.push({ text: stmt.getExpression().getText(), expr });
|
|
3003
3171
|
continue;
|
|
3004
3172
|
}
|
|
3005
|
-
if (
|
|
3006
|
-
if (
|
|
3173
|
+
if (Node6.isExpressionStatement(stmt) && Node6.isCallExpression(stmt.getExpression())) continue;
|
|
3174
|
+
if (Node6.isVariableStatement(stmt) && checks.length === 0) continue;
|
|
3007
3175
|
break;
|
|
3008
3176
|
}
|
|
3009
3177
|
if (checks.length === 0) return void 0;
|
|
@@ -3113,7 +3281,7 @@ function inputRealism(input, ctx) {
|
|
|
3113
3281
|
}
|
|
3114
3282
|
|
|
3115
3283
|
// src/core/verify/verifyFunction.ts
|
|
3116
|
-
import { Node as
|
|
3284
|
+
import { Node as Node7 } from "ts-morph";
|
|
3117
3285
|
|
|
3118
3286
|
// src/core/types/accepts.ts
|
|
3119
3287
|
function accepts(base, head) {
|
|
@@ -3279,11 +3447,16 @@ async function verifyFunction(session, target, options) {
|
|
|
3279
3447
|
values: usage.numbers.length + usage.strings.length
|
|
3280
3448
|
};
|
|
3281
3449
|
}
|
|
3282
|
-
const guards =
|
|
3450
|
+
const guards = Node7.isVariableDeclaration(baseLoc.node) ? void 0 : inferGuards(baseLoc.node);
|
|
3283
3451
|
if (guards) base.guards = guards.conditions;
|
|
3284
3452
|
const realismCtx = realismContext(usage);
|
|
3285
3453
|
base.mined = { numbers: mined.numbers.length, strings: mined.strings.length, thresholds: mined.thresholds };
|
|
3286
|
-
const
|
|
3454
|
+
const sizeNames = sizeLikeParams([baseLoc.node, headLoc.node]);
|
|
3455
|
+
const genSig = sizeNames.size === 0 ? headSig : {
|
|
3456
|
+
...headSig,
|
|
3457
|
+
params: headSig.params.map((p) => sizeNames.has(p.name) ? { ...p, spec: markSize(p.spec) } : p)
|
|
3458
|
+
};
|
|
3459
|
+
const arbitrary = options.arbitrary ?? compileSignature(genSig, { mined });
|
|
3287
3460
|
if (!arbitrary) return skip("could not build an input generator for the signature");
|
|
3288
3461
|
let examples = [...usage?.tuples ?? [], ...options.examples ?? []];
|
|
3289
3462
|
if (options.where) examples = examples.filter((e) => safeWhere(options.where, e));
|
|
@@ -3324,10 +3497,10 @@ async function verifyFunction(session, target, options) {
|
|
|
3324
3497
|
if (!m.load.exports.includes(target.exportName))
|
|
3325
3498
|
return skip(`export \`${target.exportName}\` not found at runtime (${label})`);
|
|
3326
3499
|
}
|
|
3327
|
-
const evaluate = async (inputs) => {
|
|
3500
|
+
const evaluate = async (inputs, opts) => {
|
|
3328
3501
|
const [b, h] = await Promise.all([
|
|
3329
|
-
session.pools.base.run(baseMod.moduleId, target.exportName, inputs),
|
|
3330
|
-
session.pools.head.run(headMod.moduleId, target.exportName, inputs)
|
|
3502
|
+
session.pools.base.run(baseMod.moduleId, target.exportName, inputs, opts),
|
|
3503
|
+
session.pools.head.run(headMod.moduleId, target.exportName, inputs, opts)
|
|
3331
3504
|
]);
|
|
3332
3505
|
return b.map((o, i) => ({ base: o, head: h[i] }));
|
|
3333
3506
|
};
|
|
@@ -3352,6 +3525,8 @@ async function verifyFunction(session, target, options) {
|
|
|
3352
3525
|
flakyClusters: result.flakyClusters,
|
|
3353
3526
|
stoppedBy: result.stoppedBy,
|
|
3354
3527
|
failures: result.failures,
|
|
3528
|
+
edgeFailures: result.edgeFailures,
|
|
3529
|
+
skippedEdgeInputs: result.skippedEdgeInputs,
|
|
3355
3530
|
shrinkEvaluations: result.shrinkEvaluations,
|
|
3356
3531
|
durationMs: Date.now() - started
|
|
3357
3532
|
};
|
|
@@ -5047,6 +5222,10 @@ var FunctionReport = z.object({
|
|
|
5047
5222
|
inputsRun: z.number().int().nonnegative(),
|
|
5048
5223
|
stoppedBy: z.enum(["runs", "deadline", "failures", "exhausted"]).optional(),
|
|
5049
5224
|
failures: z.number().int().nonnegative(),
|
|
5225
|
+
/** Timeouts/crashes on edge-case inputs (huge numbers, NaN, …), included in `failures`. */
|
|
5226
|
+
edgeTimeouts: z.number().int().nonnegative().optional(),
|
|
5227
|
+
/** Edge-case inputs skipped after repeated timeouts on edge-case inputs. */
|
|
5228
|
+
skippedEdgeInputs: z.number().int().nonnegative().optional(),
|
|
5050
5229
|
durationMs: z.number().nonnegative(),
|
|
5051
5230
|
seed: z.number().int(),
|
|
5052
5231
|
usage: z.object({ files: z.number().int(), callSites: z.number().int(), values: z.number().int() }).optional(),
|
|
@@ -5151,14 +5330,14 @@ function applyRuleLabels(report, forceRefactor = false) {
|
|
|
5151
5330
|
return report;
|
|
5152
5331
|
}
|
|
5153
5332
|
var MAX_DISPLAY = 300;
|
|
5154
|
-
function outcomeView(o) {
|
|
5333
|
+
function outcomeView(o, abbreviate = true) {
|
|
5155
5334
|
switch (o.kind) {
|
|
5156
5335
|
case "return": {
|
|
5157
5336
|
const code2 = encToCode(o.value);
|
|
5158
5337
|
return {
|
|
5159
5338
|
kind: "return",
|
|
5160
5339
|
async: o.async,
|
|
5161
|
-
display: formatOutcome(o, MAX_DISPLAY),
|
|
5340
|
+
display: formatOutcome(o, MAX_DISPLAY, abbreviate),
|
|
5162
5341
|
...code2 !== void 0 && code2.length < 4e3 ? { code: code2 } : {}
|
|
5163
5342
|
};
|
|
5164
5343
|
}
|
|
@@ -5167,7 +5346,7 @@ function outcomeView(o) {
|
|
|
5167
5346
|
return {
|
|
5168
5347
|
kind: "throw",
|
|
5169
5348
|
async: o.async,
|
|
5170
|
-
display: formatOutcome(o, MAX_DISPLAY),
|
|
5349
|
+
display: formatOutcome(o, MAX_DISPLAY, abbreviate),
|
|
5171
5350
|
...e.t === "error" ? { errorClass: e.ctor.slice(0, 200), message: e.message.slice(0, 4e3) } : {}
|
|
5172
5351
|
};
|
|
5173
5352
|
}
|
|
@@ -5184,16 +5363,21 @@ function exampleFrom(exportName, w) {
|
|
|
5184
5363
|
new: outcomeView(w.head),
|
|
5185
5364
|
minimal: w.minimal
|
|
5186
5365
|
};
|
|
5366
|
+
if (ex.old.display === ex.new.display) {
|
|
5367
|
+
ex.old = outcomeView(w.base, false);
|
|
5368
|
+
ex.new = outcomeView(w.head, false);
|
|
5369
|
+
}
|
|
5187
5370
|
if (w.diff.aspect === "mutation" && (w.base.kind === "return" || w.base.kind === "throw") && (w.head.kind === "return" || w.head.kind === "throw")) {
|
|
5188
5371
|
const i = Number(w.diff.path[0]);
|
|
5189
5372
|
const a = w.base.args[i];
|
|
5190
5373
|
const b = w.head.args[i];
|
|
5191
5374
|
if (a && b) {
|
|
5192
5375
|
const newCode = encToCode(b);
|
|
5376
|
+
const full = formatEnc(a, MAX_DISPLAY) === formatEnc(b, MAX_DISPLAY);
|
|
5193
5377
|
ex.mutation = {
|
|
5194
5378
|
argIndex: i,
|
|
5195
|
-
old: formatEnc(a, MAX_DISPLAY),
|
|
5196
|
-
new: formatEnc(b, MAX_DISPLAY),
|
|
5379
|
+
old: formatEnc(a, MAX_DISPLAY, !full),
|
|
5380
|
+
new: formatEnc(b, MAX_DISPLAY, !full),
|
|
5197
5381
|
...newCode !== void 0 && newCode.length < 4e3 ? { newCode } : {}
|
|
5198
5382
|
};
|
|
5199
5383
|
}
|
|
@@ -5225,6 +5409,8 @@ function functionReport(f) {
|
|
|
5225
5409
|
inputsRun: f.inputsRun,
|
|
5226
5410
|
...f.stoppedBy ? { stoppedBy: f.stoppedBy } : {},
|
|
5227
5411
|
failures: f.failures,
|
|
5412
|
+
...f.edgeFailures ? { edgeTimeouts: f.edgeFailures } : {},
|
|
5413
|
+
...f.skippedEdgeInputs ? { skippedEdgeInputs: f.skippedEdgeInputs } : {},
|
|
5228
5414
|
durationMs: f.durationMs,
|
|
5229
5415
|
seed: f.seed,
|
|
5230
5416
|
...f.usage ? { usage: f.usage } : {},
|
|
@@ -5406,6 +5592,18 @@ function render(r, maxExamples, sticky) {
|
|
|
5406
5592
|
const edge = r.functions.flatMap(
|
|
5407
5593
|
(f) => f.behaviors.filter((b) => b.realism === "edge").map((b) => ({ f, b }))
|
|
5408
5594
|
);
|
|
5595
|
+
const timedOut = r.functions.filter((f) => (f.edgeTimeouts ?? 0) > 0);
|
|
5596
|
+
const timeoutLine = timedOut.length ? `\u23F1\uFE0F Timed out on edge-case inputs (huge numbers, NaN, \u2026); similar inputs were skipped: ${timedOut.map((f) => `${code(f.exportName, 60)} (${n(f.edgeTimeouts ?? 0)})`).join(", ")}.` : "";
|
|
5597
|
+
if (edge.length === 0 && timeoutLine) {
|
|
5598
|
+
lines.push(
|
|
5599
|
+
"",
|
|
5600
|
+
`<details><summary>\u23F1\uFE0F Some edge-case inputs timed out</summary>`,
|
|
5601
|
+
"",
|
|
5602
|
+
timeoutLine,
|
|
5603
|
+
"",
|
|
5604
|
+
"</details>"
|
|
5605
|
+
);
|
|
5606
|
+
}
|
|
5409
5607
|
if (edge.length > 0) {
|
|
5410
5608
|
const reasons = [...new Set(edge.map(({ b }) => b.realismNote).filter(Boolean))].slice(0, 4).join(", ");
|
|
5411
5609
|
const tag = {
|
|
@@ -5418,7 +5616,9 @@ function render(r, maxExamples, sticky) {
|
|
|
5418
5616
|
"",
|
|
5419
5617
|
`<details><summary>\u{1F9EA} ${edge.length} behavior change${edge.length === 1 ? "" : "s"} only on edge-case inputs${reasons ? ` (${escapeText(reasons)})` : ""}</summary>`,
|
|
5420
5618
|
"",
|
|
5421
|
-
|
|
5619
|
+
// Compact: one example per edge-case behavior.
|
|
5620
|
+
edge.map(({ f, b }) => `${tag[b.label] ?? ""}${behaviorBlock(f, b, 1)}`).join("\n\n"),
|
|
5621
|
+
...timeoutLine ? ["", timeoutLine] : [],
|
|
5422
5622
|
"",
|
|
5423
5623
|
"</details>"
|
|
5424
5624
|
);
|