Skip to content

Commit b33ab79

Browse files
Fix panic on negative parameterIndex in type predicate flow analysis (#2122)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> Co-authored-by: Ryan Cavanaugh <ryanca@microsoft.com>
1 parent c36c210 commit b33ab79

9 files changed

+264
-2
lines changed

internal/checker/flow.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2405,7 +2405,7 @@ func (c *Checker) typeMaybeAssignableTo(source *Type, target *Type) bool {
24052405
func (c *Checker) getTypePredicateArgument(predicate *TypePredicate, callExpression *ast.Node) *ast.Node {
24062406
if predicate.kind == TypePredicateKindIdentifier || predicate.kind == TypePredicateKindAssertsIdentifier {
24072407
arguments := callExpression.Arguments()
2408-
if int(predicate.parameterIndex) < len(arguments) {
2408+
if predicate.parameterIndex >= 0 && int(predicate.parameterIndex) < len(arguments) {
24092409
return arguments[predicate.parameterIndex]
24102410
}
24112411
} else {
@@ -2496,7 +2496,7 @@ func (c *Checker) isReachableFlowNodeWorker(f *FlowState, flow *ast.FlowNode, no
24962496
case flags&ast.FlowFlagsCall != 0:
24972497
if signature := c.getEffectsSignature(flow.Node); signature != nil {
24982498
if predicate := c.getTypePredicateOfSignature(signature); predicate != nil && predicate.kind == TypePredicateKindAssertsIdentifier && predicate.t == nil {
2499-
if arguments := flow.Node.Arguments(); int(predicate.parameterIndex) < len(arguments) && c.isFalseExpression(arguments[predicate.parameterIndex]) {
2499+
if arguments := flow.Node.Arguments(); predicate.parameterIndex >= 0 && int(predicate.parameterIndex) < len(arguments) && c.isFalseExpression(arguments[predicate.parameterIndex]) {
25002500
return false
25012501
}
25022502
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
assertsPredicateParameterMismatch.ts(7,12): error TS1225: Cannot find parameter 'condition'.
2+
3+
4+
==== assertsPredicateParameterMismatch.ts (1 errors) ====
5+
// This test verifies that the checker doesn't panic when an assertion predicate
6+
// references a parameter name that doesn't match any actual function parameter.
7+
// This specifically tests the code path in isReachableFlowNodeWorker.
8+
9+
function assertCondition(
10+
_condition: boolean
11+
): asserts condition { // "condition" doesn't match parameter "_condition"
12+
~~~~~~~~~
13+
!!! error TS1225: Cannot find parameter 'condition'.
14+
if (!_condition) {
15+
throw new Error('Condition failed');
16+
}
17+
}
18+
19+
function test(): void {
20+
assertCondition(false);
21+
console.log("unreachable");
22+
}
23+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//// [tests/cases/compiler/assertsPredicateParameterMismatch.ts] ////
2+
3+
=== assertsPredicateParameterMismatch.ts ===
4+
// This test verifies that the checker doesn't panic when an assertion predicate
5+
// references a parameter name that doesn't match any actual function parameter.
6+
// This specifically tests the code path in isReachableFlowNodeWorker.
7+
8+
function assertCondition(
9+
>assertCondition : Symbol(assertCondition, Decl(assertsPredicateParameterMismatch.ts, 0, 0))
10+
11+
_condition: boolean
12+
>_condition : Symbol(_condition, Decl(assertsPredicateParameterMismatch.ts, 4, 25))
13+
14+
): asserts condition { // "condition" doesn't match parameter "_condition"
15+
if (!_condition) {
16+
>_condition : Symbol(_condition, Decl(assertsPredicateParameterMismatch.ts, 4, 25))
17+
18+
throw new Error('Condition failed');
19+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
20+
}
21+
}
22+
23+
function test(): void {
24+
>test : Symbol(test, Decl(assertsPredicateParameterMismatch.ts, 10, 1))
25+
26+
assertCondition(false);
27+
>assertCondition : Symbol(assertCondition, Decl(assertsPredicateParameterMismatch.ts, 0, 0))
28+
29+
console.log("unreachable");
30+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
31+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
32+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
33+
}
34+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//// [tests/cases/compiler/assertsPredicateParameterMismatch.ts] ////
2+
3+
=== assertsPredicateParameterMismatch.ts ===
4+
// This test verifies that the checker doesn't panic when an assertion predicate
5+
// references a parameter name that doesn't match any actual function parameter.
6+
// This specifically tests the code path in isReachableFlowNodeWorker.
7+
8+
function assertCondition(
9+
>assertCondition : (_condition: boolean) => asserts condition
10+
11+
_condition: boolean
12+
>_condition : boolean
13+
14+
): asserts condition { // "condition" doesn't match parameter "_condition"
15+
if (!_condition) {
16+
>!_condition : boolean
17+
>_condition : boolean
18+
19+
throw new Error('Condition failed');
20+
>new Error('Condition failed') : Error
21+
>Error : ErrorConstructor
22+
>'Condition failed' : "Condition failed"
23+
}
24+
}
25+
26+
function test(): void {
27+
>test : () => void
28+
29+
assertCondition(false);
30+
>assertCondition(false) : void
31+
>assertCondition : (_condition: boolean) => asserts condition
32+
>false : false
33+
34+
console.log("unreachable");
35+
>console.log("unreachable") : void
36+
>console.log : (...data: any[]) => void
37+
>console : Console
38+
>log : (...data: any[]) => void
39+
>"unreachable" : "unreachable"
40+
}
41+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
typePredicateParameterMismatch.ts(10,4): error TS1225: Cannot find parameter 'value'.
2+
3+
4+
==== typePredicateParameterMismatch.ts (1 errors) ====
5+
// This test verifies that the checker doesn't panic when a type predicate
6+
// references a parameter name that doesn't match any actual function parameter.
7+
8+
type TypeA = { kind: 'a' };
9+
type TypeB = { kind: 'b' };
10+
type UnionType = TypeA | TypeB;
11+
12+
function isTypeA(
13+
_value: UnionType
14+
): value is TypeA { // "value" doesn't match parameter "_value"
15+
~~~~~
16+
!!! error TS1225: Cannot find parameter 'value'.
17+
return true;
18+
}
19+
20+
function test(input: UnionType): void {
21+
if (isTypeA(input)) {
22+
console.log(input.kind);
23+
}
24+
}
25+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//// [tests/cases/compiler/typePredicateParameterMismatch.ts] ////
2+
3+
=== typePredicateParameterMismatch.ts ===
4+
// This test verifies that the checker doesn't panic when a type predicate
5+
// references a parameter name that doesn't match any actual function parameter.
6+
7+
type TypeA = { kind: 'a' };
8+
>TypeA : Symbol(TypeA, Decl(typePredicateParameterMismatch.ts, 0, 0))
9+
>kind : Symbol(kind, Decl(typePredicateParameterMismatch.ts, 3, 14))
10+
11+
type TypeB = { kind: 'b' };
12+
>TypeB : Symbol(TypeB, Decl(typePredicateParameterMismatch.ts, 3, 27))
13+
>kind : Symbol(kind, Decl(typePredicateParameterMismatch.ts, 4, 14))
14+
15+
type UnionType = TypeA | TypeB;
16+
>UnionType : Symbol(UnionType, Decl(typePredicateParameterMismatch.ts, 4, 27))
17+
>TypeA : Symbol(TypeA, Decl(typePredicateParameterMismatch.ts, 0, 0))
18+
>TypeB : Symbol(TypeB, Decl(typePredicateParameterMismatch.ts, 3, 27))
19+
20+
function isTypeA(
21+
>isTypeA : Symbol(isTypeA, Decl(typePredicateParameterMismatch.ts, 5, 31))
22+
23+
_value: UnionType
24+
>_value : Symbol(_value, Decl(typePredicateParameterMismatch.ts, 7, 17))
25+
>UnionType : Symbol(UnionType, Decl(typePredicateParameterMismatch.ts, 4, 27))
26+
27+
): value is TypeA { // "value" doesn't match parameter "_value"
28+
>TypeA : Symbol(TypeA, Decl(typePredicateParameterMismatch.ts, 0, 0))
29+
30+
return true;
31+
}
32+
33+
function test(input: UnionType): void {
34+
>test : Symbol(test, Decl(typePredicateParameterMismatch.ts, 11, 1))
35+
>input : Symbol(input, Decl(typePredicateParameterMismatch.ts, 13, 14))
36+
>UnionType : Symbol(UnionType, Decl(typePredicateParameterMismatch.ts, 4, 27))
37+
38+
if (isTypeA(input)) {
39+
>isTypeA : Symbol(isTypeA, Decl(typePredicateParameterMismatch.ts, 5, 31))
40+
>input : Symbol(input, Decl(typePredicateParameterMismatch.ts, 13, 14))
41+
42+
console.log(input.kind);
43+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
44+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
45+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
46+
>input.kind : Symbol(kind, Decl(typePredicateParameterMismatch.ts, 3, 14), Decl(typePredicateParameterMismatch.ts, 4, 14))
47+
>input : Symbol(input, Decl(typePredicateParameterMismatch.ts, 13, 14))
48+
>kind : Symbol(kind, Decl(typePredicateParameterMismatch.ts, 3, 14), Decl(typePredicateParameterMismatch.ts, 4, 14))
49+
}
50+
}
51+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//// [tests/cases/compiler/typePredicateParameterMismatch.ts] ////
2+
3+
=== typePredicateParameterMismatch.ts ===
4+
// This test verifies that the checker doesn't panic when a type predicate
5+
// references a parameter name that doesn't match any actual function parameter.
6+
7+
type TypeA = { kind: 'a' };
8+
>TypeA : TypeA
9+
>kind : "a"
10+
11+
type TypeB = { kind: 'b' };
12+
>TypeB : TypeB
13+
>kind : "b"
14+
15+
type UnionType = TypeA | TypeB;
16+
>UnionType : UnionType
17+
18+
function isTypeA(
19+
>isTypeA : (_value: UnionType) => value is TypeA
20+
21+
_value: UnionType
22+
>_value : UnionType
23+
24+
): value is TypeA { // "value" doesn't match parameter "_value"
25+
return true;
26+
>true : true
27+
}
28+
29+
function test(input: UnionType): void {
30+
>test : (input: UnionType) => void
31+
>input : UnionType
32+
33+
if (isTypeA(input)) {
34+
>isTypeA(input) : boolean
35+
>isTypeA : (_value: UnionType) => value is TypeA
36+
>input : UnionType
37+
38+
console.log(input.kind);
39+
>console.log(input.kind) : void
40+
>console.log : (...data: any[]) => void
41+
>console : Console
42+
>log : (...data: any[]) => void
43+
>input.kind : "a" | "b"
44+
>input : UnionType
45+
>kind : "a" | "b"
46+
}
47+
}
48+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @strict: true
2+
// @noemit: true
3+
4+
// This test verifies that the checker doesn't panic when an assertion predicate
5+
// references a parameter name that doesn't match any actual function parameter.
6+
// This specifically tests the code path in isReachableFlowNodeWorker.
7+
8+
function assertCondition(
9+
_condition: boolean
10+
): asserts condition { // "condition" doesn't match parameter "_condition"
11+
if (!_condition) {
12+
throw new Error('Condition failed');
13+
}
14+
}
15+
16+
function test(): void {
17+
assertCondition(false);
18+
console.log("unreachable");
19+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @strict: true
2+
// @noemit: true
3+
4+
// This test verifies that the checker doesn't panic when a type predicate
5+
// references a parameter name that doesn't match any actual function parameter.
6+
7+
type TypeA = { kind: 'a' };
8+
type TypeB = { kind: 'b' };
9+
type UnionType = TypeA | TypeB;
10+
11+
function isTypeA(
12+
_value: UnionType
13+
): value is TypeA { // "value" doesn't match parameter "_value"
14+
return true;
15+
}
16+
17+
function test(input: UnionType): void {
18+
if (isTypeA(input)) {
19+
console.log(input.kind);
20+
}
21+
}

0 commit comments

Comments
 (0)