Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add type instantiation count limiter #32079

Merged
merged 3 commits into from
Jul 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ namespace ts {
let typeCount = 0;
let symbolCount = 0;
let enumCount = 0;
let instantiationCount = 0;
let instantiationDepth = 0;
let constraintDepth = 0;
let currentNode: Node | undefined;
Expand Down Expand Up @@ -11408,13 +11409,14 @@ namespace ts {
if (!type || !mapper || mapper === identityMapper) {
return type;
}
if (instantiationDepth === 50) {
if (instantiationDepth === 50 || instantiationCount >= 5000000) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need instantiationDepth if we're also looking at a total count?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely do. The depth limiter is there to catch infinitely recursive types, we'd blow the stack way before the count limiter gets to 5M.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Although we could always modify instantiation to use a trampoline instead of direct recursion so we don't have to worry about stack depth, and then the total count of instantiations would really be the absolute measure of complexity, without need for a specific depth-measuring mechanism. (And would enable people to nest those if/else style conditionals more than 50 levels deep, like we've occasionally seen reports of - those may be deep, but they're often trivial)

// We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing
// with a combination of infinite generic types that perpetually generate new type identities. We stop
// the recursion here by yielding the error type.
error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite);
return errorType;
}
instantiationCount++;
instantiationDepth++;
const result = instantiateTypeWorker(type, mapper);
instantiationDepth--;
Expand Down Expand Up @@ -24494,6 +24496,7 @@ namespace ts {
function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type {
const saveCurrentNode = currentNode;
currentNode = node;
instantiationCount = 0;
const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple);
const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode);
if (isConstEnumObjectType(type)) {
Expand Down Expand Up @@ -29204,6 +29207,7 @@ namespace ts {
if (node) {
const saveCurrentNode = currentNode;
currentNode = node;
instantiationCount = 0;
checkSourceElementWorker(node);
currentNode = saveCurrentNode;
}
Expand Down Expand Up @@ -29475,6 +29479,7 @@ namespace ts {
function checkDeferredNode(node: Node) {
const saveCurrentNode = currentNode;
currentNode = node;
instantiationCount = 0;
switch (node.kind) {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
Expand Down
17 changes: 16 additions & 1 deletion tests/baselines/reference/infiniteConstraints.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' ca
tests/cases/compiler/infiniteConstraints.ts(31,43): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(31,63): error TS2322: Type 'Record<"val", "dup">' is not assignable to type 'never'.
tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.
tests/cases/compiler/infiniteConstraints.ts(48,16): error TS2589: Type instantiation is excessively deep and possibly infinite.


==== tests/cases/compiler/infiniteConstraints.ts (4 errors) ====
==== tests/cases/compiler/infiniteConstraints.ts (5 errors) ====
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint

type T1<B extends { [K in keyof B]: Extract<B[Exclude<keyof B, K>], { val: string }>["val"] }> = B;
Expand Down Expand Up @@ -51,4 +52,18 @@ tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' c
declare function function1<T extends {[K in keyof T]: Cond<T[K]>}>(): T[keyof T]["foo"];
~~~~~~~~~~~~~~~~~
!!! error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'.

// Repro from #31823

export type Prepend<Elm, T extends unknown[]> =
T extends unknown ?
((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 :
never :
never;
export type ExactExtract<T, U> = T extends U ? U extends T ? T : never : never;

type Conv<T, U = T> =
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2589: Type instantiation is excessively deep and possibly infinite.

13 changes: 13 additions & 0 deletions tests/baselines/reference/infiniteConstraints.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,24 @@ const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("

type Cond<T> = T extends number ? number : never;
declare function function1<T extends {[K in keyof T]: Cond<T[K]>}>(): T[keyof T]["foo"];

// Repro from #31823

export type Prepend<Elm, T extends unknown[]> =
T extends unknown ?
((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 :
never :
never;
export type ExactExtract<T, U> = T extends U ? U extends T ? T : never : never;

type Conv<T, U = T> =
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];


//// [infiniteConstraints.js]
"use strict";
// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint
exports.__esModule = true;
var out = myBug({ obj1: { a: "test" } });
var noError = ensureNoDuplicates({ main: value("test"), alternate: value("test2") });
var shouldBeNoError = ensureNoDuplicates({ main: value("test") });
Expand Down
50 changes: 50 additions & 0 deletions tests/baselines/reference/infiniteConstraints.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,53 @@ declare function function1<T extends {[K in keyof T]: Cond<T[K]>}>(): T[keyof T]
>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27))
>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27))

// Repro from #31823

export type Prepend<Elm, T extends unknown[]> =
>Prepend : Symbol(Prepend, Decl(infiniteConstraints.ts, 35, 88))
>Elm : Symbol(Elm, Decl(infiniteConstraints.ts, 39, 20))
>T : Symbol(T, Decl(infiniteConstraints.ts, 39, 24))

T extends unknown ?
>T : Symbol(T, Decl(infiniteConstraints.ts, 39, 24))

((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 :
>arg : Symbol(arg, Decl(infiniteConstraints.ts, 41, 4))
>Elm : Symbol(Elm, Decl(infiniteConstraints.ts, 39, 20))
>rest : Symbol(rest, Decl(infiniteConstraints.ts, 41, 13))
>T : Symbol(T, Decl(infiniteConstraints.ts, 39, 24))
>args : Symbol(args, Decl(infiniteConstraints.ts, 41, 45))
>T2 : Symbol(T2, Decl(infiniteConstraints.ts, 41, 59))
>T2 : Symbol(T2, Decl(infiniteConstraints.ts, 41, 59))

never :
never;
export type ExactExtract<T, U> = T extends U ? U extends T ? T : never : never;
>ExactExtract : Symbol(ExactExtract, Decl(infiniteConstraints.ts, 43, 8))
>T : Symbol(T, Decl(infiniteConstraints.ts, 44, 25))
>U : Symbol(U, Decl(infiniteConstraints.ts, 44, 27))
>T : Symbol(T, Decl(infiniteConstraints.ts, 44, 25))
>U : Symbol(U, Decl(infiniteConstraints.ts, 44, 27))
>U : Symbol(U, Decl(infiniteConstraints.ts, 44, 27))
>T : Symbol(T, Decl(infiniteConstraints.ts, 44, 25))
>T : Symbol(T, Decl(infiniteConstraints.ts, 44, 25))

type Conv<T, U = T> =
>Conv : Symbol(Conv, Decl(infiniteConstraints.ts, 44, 79))
>T : Symbol(T, Decl(infiniteConstraints.ts, 46, 10))
>U : Symbol(U, Decl(infiniteConstraints.ts, 46, 12))
>T : Symbol(T, Decl(infiniteConstraints.ts, 46, 10))

{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];
>0 : Symbol(0, Decl(infiniteConstraints.ts, 47, 3))
>T : Symbol(T, Decl(infiniteConstraints.ts, 46, 10))
>1 : Symbol(1, Decl(infiniteConstraints.ts, 47, 11))
>Prepend : Symbol(Prepend, Decl(infiniteConstraints.ts, 35, 88))
>T : Symbol(T, Decl(infiniteConstraints.ts, 46, 10))
>Conv : Symbol(Conv, Decl(infiniteConstraints.ts, 44, 79))
>ExactExtract : Symbol(ExactExtract, Decl(infiniteConstraints.ts, 43, 8))
>U : Symbol(U, Decl(infiniteConstraints.ts, 46, 12))
>T : Symbol(T, Decl(infiniteConstraints.ts, 46, 10))
>U : Symbol(U, Decl(infiniteConstraints.ts, 46, 12))
>T : Symbol(T, Decl(infiniteConstraints.ts, 46, 10))

23 changes: 23 additions & 0 deletions tests/baselines/reference/infiniteConstraints.types

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions tests/cases/compiler/infiniteConstraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,15 @@ const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("

type Cond<T> = T extends number ? number : never;
declare function function1<T extends {[K in keyof T]: Cond<T[K]>}>(): T[keyof T]["foo"];

// Repro from #31823

export type Prepend<Elm, T extends unknown[]> =
T extends unknown ?
((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 :
never :
never;
export type ExactExtract<T, U> = T extends U ? U extends T ? T : never : never;

type Conv<T, U = T> =
{ 0: [T]; 1: Prepend<T, Conv<ExactExtract<U, T>>>;}[U extends T ? 0 : 1];