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.
@@ -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":"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"}
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 (ts.isIdentifier(left) && left.text === indexInfo.baseText) {
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 (ts.isIdentifier(right) && right.text === indexInfo.baseText) {
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 (ts.isIdentifier(left) && left.text === indexInfo.baseText && ts.isNumericLiteral(right) && right.text === "0") {
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" && ts.isIdentifier(right) && right.text === indexInfo.baseText) {
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 && ts.isIdentifier(left) && left.text === indexInfo.baseText) {
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 && ts.isIdentifier(left) && left.text === indexInfo.baseText) {
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
- if (!ts.isNumericLiteral(indexExpr))
553
- return undefined;
554
- const k = Number(indexExpr.text);
555
- if (!Number.isInteger(k) || k < 0)
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
- const maxAllowed = fact.exactLength ?? fact.minLength;
573
- if (k < maxAllowed) {
574
- if (!hasMutationBeforeNode(ifStmt.thenStatement, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
575
- return {
576
- isGuarded: true,
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
- const maxAllowed = fact.exactLength ?? fact.minLength;
590
- if (k < maxAllowed) {
591
- if (!hasMutationBeforeNode(ifStmt.elseStatement, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
592
- return {
593
- isGuarded: true,
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
- const maxAllowed = fact.exactLength ?? fact.minLength;
608
- if (k < maxAllowed) {
609
- if (!hasMutationBeforeNode(curr.parent.right, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
610
- return {
611
- isGuarded: true,
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
- const maxAllowed = fact.exactLength ?? fact.minLength;
625
- if (k < maxAllowed) {
626
- if (!hasMutationBeforeNode(condExpr.whenTrue, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
627
- return {
628
- isGuarded: true,
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
- const maxAllowed = fact.exactLength ?? fact.minLength;
639
- if (k < maxAllowed) {
640
- if (!hasMutationBeforeNode(condExpr.whenFalse, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
641
- return {
642
- isGuarded: true,
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 stmt = node;
654
- while (stmt.parent &&
655
- !ts.isBlock(stmt.parent) &&
656
- !ts.isSourceFile(stmt.parent) &&
657
- !ts.isCaseClause(stmt.parent) &&
658
- !ts.isDefaultClause(stmt.parent)) {
659
- stmt = stmt.parent;
660
- }
661
- const container = stmt.parent;
662
- if (container &&
663
- (ts.isBlock(container) ||
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
- const statements = container.statements;
668
- const targetIndex = statements.indexOf(stmt);
669
- if (targetIndex > 0) {
670
- for (let i = 0; i < targetIndex; i++) {
671
- const prior = statements[i];
672
- if (!prior)
673
- continue;
674
- if (ts.isIfStatement(prior) && statementExitsControlFlow(prior.thenStatement, ts)) {
675
- const fact = parseExitGuardedLengthCondition(prior.expression, arrayTarget, ts, checker);
676
- if (fact) {
677
- const maxAllowed = fact.exactLength ?? fact.minLength;
678
- if (k < maxAllowed) {
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(stmt, node, arrayTarget.text, ts, checker, arrayTarget.symbol)) {
689
- return {
690
- isGuarded: true,
691
- evidence: `Dominating guard '${prior.expression.getText()}' proves '${arrayTarget.text}' has length ${fact.exactLength !== undefined ? `=== ${fact.exactLength}` : `>= ${fact.minLength}`}, establishing index ${k} is in-bounds.`,
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
  }