Skip to content

Commit 935e27a

Browse files
committed
Detect comparisons between large unions or intersections
If their multiplied size is greater than 1E7 (chosen based on the repro in #41517), then we'll expend a large amount of time and memory comparing them, so alert the user instead. Fixes #41517
1 parent 79ffd03 commit 935e27a

12 files changed

+30681
-3
lines changed

src/compiler/checker.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16812,10 +16812,9 @@ namespace ts {
1681216812
if (source === target) return Ternary.True;
1681316813

1681416814
if (relation === identityRelation) {
16815-
return isIdenticalTo(source, target);
16815+
return isIdenticalTo(source, target, reportErrors);
1681616816
}
1681716817

16818-
1681916818
// We fastpath comparing a type parameter to exactly its constraint, as this is _super_ common,
1682016819
// and otherwise, for type parameters in large unions, causes us to need to compare the union to itself,
1682116820
// as we break down the _target_ union first, _then_ get the source constraint - so for every
@@ -16876,6 +16875,10 @@ namespace ts {
1687616875
return Ternary.False;
1687716876
}
1687816877

16878+
if (areUnionsOrIntersectionsTooLarge(source, target, reportErrors)) {
16879+
return Ternary.False;
16880+
}
16881+
1687916882
let result = Ternary.False;
1688016883
const saveErrorInfo = captureErrorCalculationState();
1688116884

@@ -17011,11 +17014,30 @@ namespace ts {
1701117014
}
1701217015
}
1701317016

17014-
function isIdenticalTo(source: Type, target: Type): Ternary {
17017+
function areUnionsOrIntersectionsTooLarge(source: Type, target: Type, reportErrors: boolean) {
17018+
if ((source.flags & TypeFlags.UnionOrIntersection) && (target.flags & TypeFlags.UnionOrIntersection)) {
17019+
const sourceSize = (source as UnionOrIntersectionType).types.length;
17020+
const targetSize = (target as UnionOrIntersectionType).types.length;
17021+
if (sourceSize * targetSize > 1E7) {
17022+
if (reportErrors) {
17023+
tracing.instant(tracing.Phase.CheckTypes, "areUnionsOrIntersectionsTooLarge_DepthLimit", { sourceId: source.id, sourceSize, targetId: target.id, targetSize });
17024+
reportError(Diagnostics.Expression_requires_comparison_of_excessively_large_unions_or_intersections);
17025+
}
17026+
return true;
17027+
}
17028+
}
17029+
17030+
return false;
17031+
}
17032+
17033+
function isIdenticalTo(source: Type, target: Type, reportErrors: boolean): Ternary {
1701517034
const flags = source.flags & target.flags;
1701617035
if (!(flags & TypeFlags.Substructure)) {
1701717036
return Ternary.False;
1701817037
}
17038+
if (areUnionsOrIntersectionsTooLarge(source, target, reportErrors)) {
17039+
return Ternary.False;
17040+
}
1701917041
if (flags & TypeFlags.UnionOrIntersection) {
1702017042
let result = eachTypeRelatedToSomeType(<UnionOrIntersectionType>source, <UnionOrIntersectionType>target);
1702117043
if (result) {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3059,6 +3059,10 @@
30593059
"category": "Error",
30603060
"code": 2796
30613061
},
3062+
"Expression requires comparison of excessively large unions or intersections.": {
3063+
"category": "Error",
3064+
"code": 2797
3065+
},
30623066

30633067
"Import declaration '{0}' is using private name '{1}'.": {
30643068
"category": "Error",

tests/baselines/reference/largeUnionIntersectionComparisons.errors.txt

Lines changed: 354 additions & 0 deletions
Large diffs are not rendered by default.

tests/baselines/reference/largeUnionIntersectionComparisons.js

Lines changed: 350 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/baselines/reference/largeUnionIntersectionComparisons.symbols

Lines changed: 7095 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)