sourceverity 1.0.4 → 1.0.5
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/dist/analysis/arrayBounds.d.ts +1 -1
- package/dist/analysis/arrayBounds.d.ts.map +1 -1
- package/dist/analysis/arrayBounds.js +1 -1
- package/dist/analysis/arrayBounds.js.map +1 -1
- package/dist/analysis/arrayBoundsCore.d.ts +2 -2
- package/dist/analysis/arrayBoundsCore.d.ts.map +1 -1
- package/dist/analysis/arrayBoundsCore.js +192 -98
- package/dist/analysis/arrayBoundsCore.js.map +1 -1
- package/dist/analysis/nonNullGuards.d.ts.map +1 -1
- package/dist/analysis/nonNullGuards.js +89 -20
- package/dist/analysis/nonNullGuards.js.map +1 -1
- package/dist/analysis/promiseFlow.d.ts +5 -0
- package/dist/analysis/promiseFlow.d.ts.map +1 -1
- package/dist/analysis/promiseFlow.js +331 -1
- package/dist/analysis/promiseFlow.js.map +1 -1
- package/dist/analysis/values.d.ts.map +1 -1
- package/dist/analysis/values.js +3 -0
- package/dist/analysis/values.js.map +1 -1
- package/dist/baseline/manager.d.ts +2 -0
- package/dist/baseline/manager.d.ts.map +1 -1
- package/dist/baseline/manager.js +45 -18
- package/dist/baseline/manager.js.map +1 -1
- package/dist/rules/async/floatingPromise.d.ts.map +1 -1
- package/dist/rules/async/floatingPromise.js +3 -3
- package/dist/rules/async/floatingPromise.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { isCompleteBoundsExitGuard, isFixedLengthGuardedIndex, isNodeDescendantOf, isParamBoundedCondition, isPositiveBoundsEntryGuard, isStaticArrayLiteralBoundedIndex, normalizeArrayTarget, normalizeIndexExpression, resolveStaticConstArrayLiteral, } from "./arrayBoundsCore.js";
|
|
1
|
+
export { hasArrayMutation, hasMutationBeforeNode, isCompleteBoundsExitGuard, isFixedLengthGuardedIndex, isNodeDescendantOf, isParamBoundedCondition, isPositiveBoundsEntryGuard, isStaticArrayLiteralBoundedIndex, normalizeArrayTarget, normalizeIndexExpression, resolveStaticConstArrayLiteral, } from "./arrayBoundsCore.js";
|
|
2
2
|
export { isFindIndexBoundedIndex, isLoopOrCallbackBoundedIndex, } from "./arrayIterationBounds.js";
|
|
3
3
|
export { isConstructedArrayCardinalityBoundedIndex, isSearchProvenanceBoundedIndex, } from "./arrayConstructionBounds.js";
|
|
4
4
|
//# sourceMappingURL=arrayBounds.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arrayBounds.d.ts","sourceRoot":"","sources":["../../src/analysis/arrayBounds.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,EACvB,0BAA0B,EAC1B,gCAAgC,EAChC,oBAAoB,EACpB,wBAAwB,EACxB,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,yCAAyC,EACzC,8BAA8B,GAC/B,MAAM,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"arrayBounds.d.ts","sourceRoot":"","sources":["../../src/analysis/arrayBounds.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,EACvB,0BAA0B,EAC1B,gCAAgC,EAChC,oBAAoB,EACpB,wBAAwB,EACxB,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,yCAAyC,EACzC,8BAA8B,GAC/B,MAAM,8BAA8B,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { isCompleteBoundsExitGuard, isFixedLengthGuardedIndex, isNodeDescendantOf, isParamBoundedCondition, isPositiveBoundsEntryGuard, isStaticArrayLiteralBoundedIndex, normalizeArrayTarget, normalizeIndexExpression, resolveStaticConstArrayLiteral, } from "./arrayBoundsCore.js";
|
|
1
|
+
export { hasArrayMutation, hasMutationBeforeNode, isCompleteBoundsExitGuard, isFixedLengthGuardedIndex, isNodeDescendantOf, isParamBoundedCondition, isPositiveBoundsEntryGuard, isStaticArrayLiteralBoundedIndex, normalizeArrayTarget, normalizeIndexExpression, resolveStaticConstArrayLiteral, } from "./arrayBoundsCore.js";
|
|
2
2
|
export { isFindIndexBoundedIndex, isLoopOrCallbackBoundedIndex, } from "./arrayIterationBounds.js";
|
|
3
3
|
export { isConstructedArrayCardinalityBoundedIndex, isSearchProvenanceBoundedIndex, } from "./arrayConstructionBounds.js";
|
|
4
4
|
//# sourceMappingURL=arrayBounds.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arrayBounds.js","sourceRoot":"","sources":["../../src/analysis/arrayBounds.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,EACvB,0BAA0B,EAC1B,gCAAgC,EAChC,oBAAoB,EACpB,wBAAwB,EACxB,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,yCAAyC,EACzC,8BAA8B,GAC/B,MAAM,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"arrayBounds.js","sourceRoot":"","sources":["../../src/analysis/arrayBounds.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,EACvB,0BAA0B,EAC1B,gCAAgC,EAChC,oBAAoB,EACpB,wBAAwB,EACxB,8BAA8B,GAC/B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,yCAAyC,EACzC,8BAA8B,GAC/B,MAAM,8BAA8B,CAAC"}
|
|
@@ -26,7 +26,7 @@ declare function normalizeArrayTarget(expr: tsType.Expression, ts: typeof tsType
|
|
|
26
26
|
/**
|
|
27
27
|
* Checks if a binary condition expression guards an index lower bound (e.g. index === -1 or index < 0).
|
|
28
28
|
*/
|
|
29
|
-
declare function isLowerBoundNegativeGuard(cond: tsType.Expression, indexInfo: NormalizedIndex, ts: typeof tsType): boolean;
|
|
29
|
+
declare function isLowerBoundNegativeGuard(cond: tsType.Expression, indexInfo: NormalizedIndex, ts: typeof tsType, checker?: tsType.TypeChecker | undefined): boolean;
|
|
30
30
|
/**
|
|
31
31
|
* Checks if an expression represents `array.length` or `array.length - K`.
|
|
32
32
|
*/
|
|
@@ -34,7 +34,7 @@ declare function parseArrayLengthOffset(expr: tsType.Expression, arrayInfo: Norm
|
|
|
34
34
|
/**
|
|
35
35
|
* Checks if an exit condition contains both a lower-bound check and an upper-bound check.
|
|
36
36
|
*/
|
|
37
|
-
declare function isCompleteBoundsExitGuard(cond: tsType.Expression, indexInfo: NormalizedIndex, arrayInfo: NormalizedArray, ts: typeof tsType): boolean;
|
|
37
|
+
declare function isCompleteBoundsExitGuard(cond: tsType.Expression, indexInfo: NormalizedIndex, arrayInfo: NormalizedArray, ts: typeof tsType, checker?: tsType.TypeChecker | undefined): boolean;
|
|
38
38
|
/**
|
|
39
39
|
* Checks if an entry condition contains both a lower-bound non-negative check and an upper-bound check against array.length.
|
|
40
40
|
* e.g. `if (i >= 0 && i < array.length - 1)`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arrayBoundsCore.d.ts","sourceRoot":"","sources":["../../src/analysis/arrayBoundsCore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,MAAM,YAAY,CAAC;AAE1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,iBAAS,uBAAuB,CAC9B,IAAI,EAAE,MAAM,CAAC,UAAU,EACvB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,OAAO,MAAM,EACjB,WAAW,EAAE,OAAO,GACnB,OAAO,CAiGT;AAED;;;;;GAKG;AACH,UAAU,eAAe;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,iBAAS,wBAAwB,CAC/B,IAAI,EAAE,MAAM,CAAC,UAAU,EACvB,EAAE,EAAE,OAAO,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,CAAC,WAAW,GAAG,SAAS,GACvC,eAAe,GAAG,SAAS,CA2B7B;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,iBAAS,WAAW,CAClB,CAAC,EAAE,eAAe,GAAG,SAAS,EAC9B,CAAC,EAAE,eAAe,GAAG,SAAS,GAC7B,OAAO,CAIT;AAED,iBAAS,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAO5E;AAED,iBAAS,oBAAoB,CAC3B,IAAI,EAAE,MAAM,CAAC,UAAU,EACvB,EAAE,EAAE,OAAO,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,CAAC,WAAW,GAAG,SAAS,GACvC,eAAe,GAAG,SAAS,CAW7B;AAED;;GAEG;AACH,iBAAS,yBAAyB,CAChC,IAAI,EAAE,MAAM,CAAC,UAAU,EACvB,SAAS,EAAE,eAAe,EAC1B,EAAE,EAAE,OAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"arrayBoundsCore.d.ts","sourceRoot":"","sources":["../../src/analysis/arrayBoundsCore.ts"],"names":[],"mappings":"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"}
|
|
@@ -153,21 +153,41 @@ function normalizeArrayTarget(expr, ts, checker) {
|
|
|
153
153
|
/**
|
|
154
154
|
* Checks if a binary condition expression guards an index lower bound (e.g. index === -1 or index < 0).
|
|
155
155
|
*/
|
|
156
|
-
function isLowerBoundNegativeGuard(cond, indexInfo, ts) {
|
|
156
|
+
function isLowerBoundNegativeGuard(cond, indexInfo, ts, checker) {
|
|
157
157
|
const unwrapped = unwrapExpression(cond, ts);
|
|
158
158
|
if (!ts.isBinaryExpression(unwrapped))
|
|
159
159
|
return false;
|
|
160
160
|
const left = unwrapExpression(unwrapped.left, ts);
|
|
161
161
|
const right = unwrapExpression(unwrapped.right, ts);
|
|
162
162
|
const op = unwrapped.operatorToken.kind;
|
|
163
|
+
function matchesIndex(ident) {
|
|
164
|
+
if (!ts.isIdentifier(ident))
|
|
165
|
+
return false;
|
|
166
|
+
if (ident.text !== indexInfo.baseText)
|
|
167
|
+
return false;
|
|
168
|
+
if (checker && indexInfo.baseSymbol) {
|
|
169
|
+
let sym = checker.getSymbolAtLocation(ident);
|
|
170
|
+
if (sym && (sym.flags & ts.SymbolFlags.Alias) !== 0) {
|
|
171
|
+
try {
|
|
172
|
+
sym = checker.getAliasedSymbol(sym);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// ignore
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (sym && sym !== indexInfo.baseSymbol)
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
163
183
|
// index === -1 or -1 === index
|
|
164
184
|
if (op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.EqualsEqualsEqualsToken) {
|
|
165
|
-
if (
|
|
185
|
+
if (matchesIndex(left)) {
|
|
166
186
|
if (ts.isPrefixUnaryExpression(right) && right.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(right.operand) && right.operand.text === "1") {
|
|
167
187
|
return true;
|
|
168
188
|
}
|
|
169
189
|
}
|
|
170
|
-
if (
|
|
190
|
+
if (matchesIndex(right)) {
|
|
171
191
|
if (ts.isPrefixUnaryExpression(left) && left.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(left.operand) && left.operand.text === "1") {
|
|
172
192
|
return true;
|
|
173
193
|
}
|
|
@@ -175,13 +195,13 @@ function isLowerBoundNegativeGuard(cond, indexInfo, ts) {
|
|
|
175
195
|
}
|
|
176
196
|
// index < 0
|
|
177
197
|
if (op === ts.SyntaxKind.LessThanToken) {
|
|
178
|
-
if (
|
|
198
|
+
if (matchesIndex(left) && ts.isNumericLiteral(right) && right.text === "0") {
|
|
179
199
|
return true;
|
|
180
200
|
}
|
|
181
201
|
}
|
|
182
202
|
// 0 > index
|
|
183
203
|
if (op === ts.SyntaxKind.GreaterThanToken) {
|
|
184
|
-
if (ts.isNumericLiteral(left) && left.text === "0" &&
|
|
204
|
+
if (ts.isNumericLiteral(left) && left.text === "0" && matchesIndex(right)) {
|
|
185
205
|
return true;
|
|
186
206
|
}
|
|
187
207
|
}
|
|
@@ -346,30 +366,58 @@ function parseExitGuardedLengthCondition(expr, arrayTarget, ts, checker) {
|
|
|
346
366
|
* Checks if a binary condition expression guards an upper bound against array.length.
|
|
347
367
|
* e.g. `index >= array.length` or `index >= array.length - 1` or `index > array.length - 1`.
|
|
348
368
|
*/
|
|
349
|
-
function isUpperBoundLengthGuard(cond, indexInfo, arrayInfo, ts) {
|
|
369
|
+
function isUpperBoundLengthGuard(cond, indexInfo, arrayInfo, ts, checker) {
|
|
350
370
|
const unwrapped = unwrapExpression(cond, ts);
|
|
351
371
|
if (!ts.isBinaryExpression(unwrapped))
|
|
352
372
|
return { isGuard: false, maxAllowedOffset: 0 };
|
|
353
373
|
const left = unwrapExpression(unwrapped.left, ts);
|
|
354
374
|
const right = unwrapExpression(unwrapped.right, ts);
|
|
355
375
|
const op = unwrapped.operatorToken.kind;
|
|
376
|
+
function matchesIndex(ident) {
|
|
377
|
+
if (!ts.isIdentifier(ident))
|
|
378
|
+
return false;
|
|
379
|
+
if (ident.text !== indexInfo.baseText)
|
|
380
|
+
return false;
|
|
381
|
+
if (checker && indexInfo.baseSymbol) {
|
|
382
|
+
let sym = checker.getSymbolAtLocation(ident);
|
|
383
|
+
if (sym && (sym.flags & ts.SymbolFlags.Alias) !== 0) {
|
|
384
|
+
try {
|
|
385
|
+
sym = checker.getAliasedSymbol(sym);
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
// ignore
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (sym && sym !== indexInfo.baseSymbol)
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
356
396
|
// Pattern: `index >= [length expression]`
|
|
357
|
-
if (op === ts.SyntaxKind.GreaterThanEqualsToken &&
|
|
358
|
-
const lengthMinus = parseArrayLengthOffset(right, arrayInfo, ts);
|
|
397
|
+
if (op === ts.SyntaxKind.GreaterThanEqualsToken && matchesIndex(left)) {
|
|
398
|
+
const lengthMinus = parseArrayLengthOffset(right, arrayInfo, ts, checker);
|
|
399
|
+
if (lengthMinus !== undefined) {
|
|
400
|
+
return { isGuard: true, maxAllowedOffset: lengthMinus };
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Pattern: `[length expression] <= index`
|
|
404
|
+
if (op === ts.SyntaxKind.LessThanEqualsToken && matchesIndex(right)) {
|
|
405
|
+
const lengthMinus = parseArrayLengthOffset(left, arrayInfo, ts, checker);
|
|
359
406
|
if (lengthMinus !== undefined) {
|
|
360
|
-
// index >= array.length - lengthMinus (exit)
|
|
361
|
-
// after exit: index < array.length - lengthMinus
|
|
362
|
-
// => index + offset < array.length is safe if offset <= lengthMinus - (lengthMinus === 0 ? 0 : 0) -> offset < lengthMinus + 1 => offset <= lengthMinus
|
|
363
|
-
// Specifically: if lengthMinus === 1 (index >= array.length - 1), then index < array.length - 1, so index + 1 < array.length (offset 1 is safe).
|
|
364
|
-
// If lengthMinus === 0 (index >= array.length), then index < array.length (offset 0 is safe).
|
|
365
407
|
return { isGuard: true, maxAllowedOffset: lengthMinus };
|
|
366
408
|
}
|
|
367
409
|
}
|
|
368
410
|
// Pattern: `index > [length expression]`
|
|
369
|
-
if (op === ts.SyntaxKind.GreaterThanToken &&
|
|
370
|
-
const lengthMinus = parseArrayLengthOffset(right, arrayInfo, ts);
|
|
411
|
+
if (op === ts.SyntaxKind.GreaterThanToken && matchesIndex(left)) {
|
|
412
|
+
const lengthMinus = parseArrayLengthOffset(right, arrayInfo, ts, checker);
|
|
413
|
+
if (lengthMinus !== undefined) {
|
|
414
|
+
return { isGuard: true, maxAllowedOffset: lengthMinus - 1 };
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Pattern: `[length expression] < index`
|
|
418
|
+
if (op === ts.SyntaxKind.LessThanToken && matchesIndex(right)) {
|
|
419
|
+
const lengthMinus = parseArrayLengthOffset(left, arrayInfo, ts, checker);
|
|
371
420
|
if (lengthMinus !== undefined) {
|
|
372
|
-
// index > array.length - 1 (exit) => index <= array.length - 1 => index < array.length (offset 0 safe)
|
|
373
421
|
return { isGuard: true, maxAllowedOffset: lengthMinus - 1 };
|
|
374
422
|
}
|
|
375
423
|
}
|
|
@@ -378,7 +426,7 @@ function isUpperBoundLengthGuard(cond, indexInfo, arrayInfo, ts) {
|
|
|
378
426
|
/**
|
|
379
427
|
* Checks if an exit condition contains both a lower-bound check and an upper-bound check.
|
|
380
428
|
*/
|
|
381
|
-
function isCompleteBoundsExitGuard(cond, indexInfo, arrayInfo, ts) {
|
|
429
|
+
function isCompleteBoundsExitGuard(cond, indexInfo, arrayInfo, ts, checker) {
|
|
382
430
|
// Collect all OR disjuncts: A || B || C
|
|
383
431
|
const disjuncts = [];
|
|
384
432
|
function collectOrs(e) {
|
|
@@ -395,10 +443,10 @@ function isCompleteBoundsExitGuard(cond, indexInfo, arrayInfo, ts) {
|
|
|
395
443
|
let hasLower = false;
|
|
396
444
|
let maxUpperOffset = -1;
|
|
397
445
|
for (const d of disjuncts) {
|
|
398
|
-
if (isLowerBoundNegativeGuard(d, indexInfo, ts)) {
|
|
446
|
+
if (isLowerBoundNegativeGuard(d, indexInfo, ts, checker)) {
|
|
399
447
|
hasLower = true;
|
|
400
448
|
}
|
|
401
|
-
const upper = isUpperBoundLengthGuard(d, indexInfo, arrayInfo, ts);
|
|
449
|
+
const upper = isUpperBoundLengthGuard(d, indexInfo, arrayInfo, ts, checker);
|
|
402
450
|
if (upper.isGuard) {
|
|
403
451
|
maxUpperOffset = Math.max(maxUpperOffset, upper.maxAllowedOffset);
|
|
404
452
|
}
|
|
@@ -549,11 +597,48 @@ function isFixedLengthGuardedIndex(node, operand, ts, checker) {
|
|
|
549
597
|
if (!arrayTarget)
|
|
550
598
|
return undefined;
|
|
551
599
|
const indexExpr = unwrapExpression(operand.argumentExpression, ts);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
600
|
+
let k;
|
|
601
|
+
let lastElementOffset;
|
|
602
|
+
if (ts.isNumericLiteral(indexExpr)) {
|
|
603
|
+
const num = Number(indexExpr.text);
|
|
604
|
+
if (Number.isInteger(num) && num >= 0) {
|
|
605
|
+
k = num;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
else if (ts.isBinaryExpression(indexExpr) && indexExpr.operatorToken.kind === ts.SyntaxKind.MinusToken) {
|
|
609
|
+
const left = unwrapExpression(indexExpr.left, ts);
|
|
610
|
+
const right = unwrapExpression(indexExpr.right, ts);
|
|
611
|
+
if (ts.isPropertyAccessExpression(left) && left.name.text === "length") {
|
|
612
|
+
const lengthArrayTarget = normalizeArrayTarget(left.expression, ts, checker);
|
|
613
|
+
if (lengthArrayTarget && arraysMatch(lengthArrayTarget, arrayTarget)) {
|
|
614
|
+
if (ts.isNumericLiteral(right)) {
|
|
615
|
+
const offsetVal = Number(right.text);
|
|
616
|
+
if (Number.isInteger(offsetVal) && offsetVal >= 1) {
|
|
617
|
+
lastElementOffset = offsetVal;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (k === undefined && lastElementOffset === undefined)
|
|
556
624
|
return undefined;
|
|
625
|
+
function checkFactSatisfiesBound(fact) {
|
|
626
|
+
if (lastElementOffset !== undefined) {
|
|
627
|
+
return fact.minLength >= lastElementOffset && (fact.exactLength === undefined || fact.exactLength >= lastElementOffset);
|
|
628
|
+
}
|
|
629
|
+
if (k !== undefined) {
|
|
630
|
+
const maxAllowed = fact.exactLength ?? fact.minLength;
|
|
631
|
+
return k < maxAllowed;
|
|
632
|
+
}
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
const targetText = arrayTarget.text;
|
|
636
|
+
function formatEvidence(prefix, exprText, fact) {
|
|
637
|
+
if (lastElementOffset !== undefined) {
|
|
638
|
+
return `${prefix} '${exprText}' proves '${targetText}' has length >= ${fact.minLength}, establishing index '${targetText}.length - ${lastElementOffset}' is strictly in-bounds.`;
|
|
639
|
+
}
|
|
640
|
+
return `${prefix} '${exprText}' proves '${targetText}' has length ${fact.exactLength !== undefined ? `=== ${fact.exactLength}` : `>= ${fact.minLength}`}, establishing index ${k} is in-bounds.`;
|
|
641
|
+
}
|
|
557
642
|
// 1. Walk up enclosing AST nodes (if statements, && expressions, ternary expressions)
|
|
558
643
|
let curr = node;
|
|
559
644
|
while (curr.parent &&
|
|
@@ -568,15 +653,12 @@ function isFixedLengthGuardedIndex(node, operand, ts, checker) {
|
|
|
568
653
|
(ts.isBlock(ifStmt.thenStatement) && ifStmt.thenStatement.statements.includes(curr)) ||
|
|
569
654
|
isNodeDescendantOf(curr, ifStmt.thenStatement)) {
|
|
570
655
|
const fact = parseGuardedLengthCondition(ifStmt.expression, arrayTarget, ts, checker);
|
|
571
|
-
if (fact) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
evidence: `Enclosing guard '${ifStmt.expression.getText()}' proves '${arrayTarget.text}' has length ${fact.exactLength !== undefined ? `=== ${fact.exactLength}` : `>= ${fact.minLength}`}, establishing index ${k} is in-bounds.`,
|
|
578
|
-
};
|
|
579
|
-
}
|
|
656
|
+
if (fact && checkFactSatisfiesBound(fact)) {
|
|
657
|
+
if (!hasMutationBeforeNode(ifStmt.thenStatement, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
|
|
658
|
+
return {
|
|
659
|
+
isGuarded: true,
|
|
660
|
+
evidence: formatEvidence("Enclosing guard", ifStmt.expression.getText(), fact),
|
|
661
|
+
};
|
|
580
662
|
}
|
|
581
663
|
}
|
|
582
664
|
}
|
|
@@ -585,15 +667,12 @@ function isFixedLengthGuardedIndex(node, operand, ts, checker) {
|
|
|
585
667
|
(ts.isBlock(ifStmt.elseStatement) && ifStmt.elseStatement.statements.includes(curr)) ||
|
|
586
668
|
isNodeDescendantOf(curr, ifStmt.elseStatement))) {
|
|
587
669
|
const fact = parseExitGuardedLengthCondition(ifStmt.expression, arrayTarget, ts, checker);
|
|
588
|
-
if (fact) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
evidence: `Enclosing guard 'else' of '${ifStmt.expression.getText()}' proves '${arrayTarget.text}' has length ${fact.exactLength !== undefined ? `=== ${fact.exactLength}` : `>= ${fact.minLength}`}, establishing index ${k} is in-bounds.`,
|
|
595
|
-
};
|
|
596
|
-
}
|
|
670
|
+
if (fact && checkFactSatisfiesBound(fact)) {
|
|
671
|
+
if (!hasMutationBeforeNode(ifStmt.elseStatement, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
|
|
672
|
+
return {
|
|
673
|
+
isGuarded: true,
|
|
674
|
+
evidence: formatEvidence("Else branch of guard", ifStmt.expression.getText(), fact),
|
|
675
|
+
};
|
|
597
676
|
}
|
|
598
677
|
}
|
|
599
678
|
}
|
|
@@ -603,15 +682,12 @@ function isFixedLengthGuardedIndex(node, operand, ts, checker) {
|
|
|
603
682
|
curr.parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&
|
|
604
683
|
(curr.parent.right === curr || isNodeDescendantOf(curr, curr.parent.right))) {
|
|
605
684
|
const fact = parseGuardedLengthCondition(curr.parent.left, arrayTarget, ts, checker);
|
|
606
|
-
if (fact) {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
evidence: `Logical AND guard '${curr.parent.left.getText()}' proves '${arrayTarget.text}' has length ${fact.exactLength !== undefined ? `=== ${fact.exactLength}` : `>= ${fact.minLength}`}, establishing index ${k} is in-bounds.`,
|
|
613
|
-
};
|
|
614
|
-
}
|
|
685
|
+
if (fact && checkFactSatisfiesBound(fact)) {
|
|
686
|
+
if (!hasMutationBeforeNode(curr.parent.right, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
|
|
687
|
+
return {
|
|
688
|
+
isGuarded: true,
|
|
689
|
+
evidence: formatEvidence("Logical AND guard", curr.parent.left.getText(), fact),
|
|
690
|
+
};
|
|
615
691
|
}
|
|
616
692
|
}
|
|
617
693
|
}
|
|
@@ -620,62 +696,60 @@ function isFixedLengthGuardedIndex(node, operand, ts, checker) {
|
|
|
620
696
|
const condExpr = curr.parent;
|
|
621
697
|
if (condExpr.whenTrue === curr || isNodeDescendantOf(curr, condExpr.whenTrue)) {
|
|
622
698
|
const fact = parseGuardedLengthCondition(condExpr.condition, arrayTarget, ts, checker);
|
|
623
|
-
if (fact) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
evidence: `Ternary guard '${condExpr.condition.getText()}' proves '${arrayTarget.text}' has length ${fact.exactLength !== undefined ? `=== ${fact.exactLength}` : `>= ${fact.minLength}`}, establishing index ${k} is in-bounds.`,
|
|
630
|
-
};
|
|
631
|
-
}
|
|
699
|
+
if (fact && checkFactSatisfiesBound(fact)) {
|
|
700
|
+
if (!hasMutationBeforeNode(condExpr.whenTrue, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
|
|
701
|
+
return {
|
|
702
|
+
isGuarded: true,
|
|
703
|
+
evidence: formatEvidence("Ternary guard", condExpr.condition.getText(), fact),
|
|
704
|
+
};
|
|
632
705
|
}
|
|
633
706
|
}
|
|
634
707
|
}
|
|
635
708
|
else if (condExpr.whenFalse === curr || isNodeDescendantOf(curr, condExpr.whenFalse)) {
|
|
636
709
|
const fact = parseExitGuardedLengthCondition(condExpr.condition, arrayTarget, ts, checker);
|
|
637
|
-
if (fact) {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
evidence: `Ternary false branch of '${condExpr.condition.getText()}' proves '${arrayTarget.text}' has length ${fact.exactLength !== undefined ? `=== ${fact.exactLength}` : `>= ${fact.minLength}`}, establishing index ${k} is in-bounds.`,
|
|
644
|
-
};
|
|
645
|
-
}
|
|
710
|
+
if (fact && checkFactSatisfiesBound(fact)) {
|
|
711
|
+
if (!hasMutationBeforeNode(condExpr.whenFalse, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
|
|
712
|
+
return {
|
|
713
|
+
isGuarded: true,
|
|
714
|
+
evidence: formatEvidence("Ternary false branch of", condExpr.condition.getText(), fact),
|
|
715
|
+
};
|
|
646
716
|
}
|
|
647
717
|
}
|
|
648
718
|
}
|
|
649
719
|
}
|
|
650
720
|
curr = curr.parent;
|
|
651
721
|
}
|
|
652
|
-
// 2. Check dominating early-exit guards in prior sibling statements
|
|
653
|
-
let
|
|
654
|
-
while (
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
722
|
+
// 2. Check dominating early-exit guards in prior sibling statements and ancestor blocks
|
|
723
|
+
let childBlockNode = node;
|
|
724
|
+
while (childBlockNode.parent) {
|
|
725
|
+
const container = childBlockNode.parent;
|
|
726
|
+
if (ts.isFunctionDeclaration(container) ||
|
|
727
|
+
ts.isFunctionExpression(container) ||
|
|
728
|
+
ts.isArrowFunction(container) ||
|
|
729
|
+
ts.isMethodDeclaration(container) ||
|
|
730
|
+
ts.isConstructorDeclaration(container) ||
|
|
731
|
+
ts.isGetAccessor(container) ||
|
|
732
|
+
ts.isSetAccessor(container)) {
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
if (ts.isBlock(container) ||
|
|
664
736
|
ts.isSourceFile(container) ||
|
|
665
737
|
ts.isCaseClause(container) ||
|
|
666
|
-
ts.isDefaultClause(container))
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const
|
|
676
|
-
if (
|
|
677
|
-
|
|
678
|
-
|
|
738
|
+
ts.isDefaultClause(container)) {
|
|
739
|
+
let stmtInContainer = childBlockNode;
|
|
740
|
+
while (stmtInContainer.parent && stmtInContainer.parent !== container) {
|
|
741
|
+
stmtInContainer = stmtInContainer.parent;
|
|
742
|
+
}
|
|
743
|
+
const statements = container.statements;
|
|
744
|
+
const targetIndex = statements.indexOf(stmtInContainer);
|
|
745
|
+
if (targetIndex > 0) {
|
|
746
|
+
for (let i = 0; i < targetIndex; i++) {
|
|
747
|
+
const prior = statements[i];
|
|
748
|
+
if (!prior)
|
|
749
|
+
continue;
|
|
750
|
+
if (ts.isIfStatement(prior) && statementExitsControlFlow(prior.thenStatement, ts)) {
|
|
751
|
+
const fact = parseExitGuardedLengthCondition(prior.expression, arrayTarget, ts, checker);
|
|
752
|
+
if (fact && checkFactSatisfiesBound(fact)) {
|
|
679
753
|
let hasInterveningMutation = false;
|
|
680
754
|
for (let m = i + 1; m < targetIndex; m++) {
|
|
681
755
|
const s = statements[m];
|
|
@@ -685,17 +759,37 @@ function isFixedLengthGuardedIndex(node, operand, ts, checker) {
|
|
|
685
759
|
}
|
|
686
760
|
}
|
|
687
761
|
if (!hasInterveningMutation &&
|
|
688
|
-
!hasMutationBeforeNode(
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
762
|
+
!hasMutationBeforeNode(stmtInContainer, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
|
|
763
|
+
// Loop check: if node is inside a loop under container, verify loop body has no mutations
|
|
764
|
+
let loopMutated = false;
|
|
765
|
+
let loopCheck = node;
|
|
766
|
+
while (loopCheck.parent && loopCheck.parent !== container) {
|
|
767
|
+
const lp = loopCheck.parent;
|
|
768
|
+
if (ts.isForStatement(lp) ||
|
|
769
|
+
ts.isForInStatement(lp) ||
|
|
770
|
+
ts.isForOfStatement(lp) ||
|
|
771
|
+
ts.isWhileStatement(lp) ||
|
|
772
|
+
ts.isDoStatement(lp)) {
|
|
773
|
+
if (hasArrayMutation(lp.statement, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
|
|
774
|
+
loopMutated = true;
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
loopCheck = lp;
|
|
779
|
+
}
|
|
780
|
+
if (!loopMutated) {
|
|
781
|
+
return {
|
|
782
|
+
isGuarded: true,
|
|
783
|
+
evidence: formatEvidence("Dominating guard", prior.expression.getText(), fact),
|
|
784
|
+
};
|
|
785
|
+
}
|
|
693
786
|
}
|
|
694
787
|
}
|
|
695
788
|
}
|
|
696
789
|
}
|
|
697
790
|
}
|
|
698
791
|
}
|
|
792
|
+
childBlockNode = container;
|
|
699
793
|
}
|
|
700
794
|
return undefined;
|
|
701
795
|
}
|