Skip to content

Commit 6650496

Browse files
committed
Enforce a size limit in getSpreadType
When a union is spread into a union, the sizes are multiplied, potentially resulting in an enormous union (especially if there are repeated spreads). This check detects cases that used to run out of memory. Fixes microsoft#40754
1 parent fc03982 commit 6650496

File tree

7 files changed

+974
-2
lines changed

7 files changed

+974
-2
lines changed

src/compiler/checker.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14458,14 +14458,14 @@ namespace ts {
1445814458
if (merged) {
1445914459
return getSpreadType(merged, right, symbol, objectFlags, readonly);
1446014460
}
14461-
return mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly));
14461+
return errorTypeIfTooLarge() ?? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly));
1446214462
}
1446314463
if (right.flags & TypeFlags.Union) {
1446414464
const merged = tryMergeUnionOfObjectTypeAndEmptyObject(right as UnionType, readonly);
1446514465
if (merged) {
1446614466
return getSpreadType(left, merged, symbol, objectFlags, readonly);
1446714467
}
14468-
return mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly));
14468+
return errorTypeIfTooLarge() ?? mapType(right, t => getSpreadType(left, t, symbol, objectFlags, readonly));
1446914469
}
1447014470
if (right.flags & (TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)) {
1447114471
return left;
@@ -14544,6 +14544,17 @@ namespace ts {
1454414544
getIndexInfoWithReadonly(numberIndexInfo, readonly));
1454514545
spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags;
1454614546
return spread;
14547+
14548+
function errorTypeIfTooLarge(): Type | undefined {
14549+
if (left.flags & right.flags & TypeFlags.Union) {
14550+
const resultSize = (left as UnionType).types.length * (right as UnionType).types.length;
14551+
if (resultSize > 100000) {
14552+
tracing.instant(tracing.Phase.Check, "getSpreadType_DepthLimit", { leftId: left.id, rightId: right.id });
14553+
error(currentNode, Diagnostics.Spread_expression_produces_a_union_type_that_is_too_complex_to_represent);
14554+
return errorType;
14555+
}
14556+
}
14557+
}
1454714558
}
1454814559

1454914560
/** We approximate own properties as non-methods plus methods that are inside the object literal */

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3048,6 +3048,10 @@
30483048
"category": "Error",
30493049
"code": 2795
30503050
},
3051+
"Spread expression produces a union type that is too complex to represent.": {
3052+
"category": "Error",
3053+
"code": 2796
3054+
},
30513055

30523056
"Import declaration '{0}' is using private name '{1}'.": {
30533057
"category": "Error",
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts(3,12): error TS2796: Spread expression produces a union type that is too complex to represent.
2+
3+
4+
==== tests/cases/conformance/types/spread/objectSpreadRepeatedComplexity.ts (1 errors) ====
5+
function f(cnd: Record<number, boolean>){
6+
// Type is a union of 2^(n-1) members, where n is the number of spread objects
7+
return {
8+
~
9+
// Without this one, it collapses to {} ?
10+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11+
...(cnd[1] &&
12+
~~~~~~~~~~~~~~~~~~~~~
13+
cnd[2] && {
14+
~~~~~~~~~~~~~~~~~~~~~~~
15+
prop0: 0,
16+
~~~~~~~~~~~~~~~~~~~~~
17+
}),
18+
~~~~~~~~~~~
19+
20+
21+
// With one prop each, it collapses to a single object (#34853?)
22+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23+
...(cnd[3] && {
24+
~~~~~~~~~~~~~~~~~~~~~~~
25+
prop3a: 1,
26+
~~~~~~~~~~~~~~~~~~~~~~
27+
prop3b: 1,
28+
~~~~~~~~~~~~~~~~~~~~~~
29+
}),
30+
~~~~~~~~~~~
31+
...(cnd[4] && {
32+
~~~~~~~~~~~~~~~~~~~~~~~
33+
prop4a: 1,
34+
~~~~~~~~~~~~~~~~~~~~~~
35+
prop4b: 1,
36+
~~~~~~~~~~~~~~~~~~~~~~
37+
}),
38+
~~~~~~~~~~~
39+
...(cnd[5] && {
40+
~~~~~~~~~~~~~~~~~~~~~~~
41+
prop5a: 1,
42+
~~~~~~~~~~~~~~~~~~~~~~
43+
prop5b: 1,
44+
~~~~~~~~~~~~~~~~~~~~~~
45+
}),
46+
~~~~~~~~~~~
47+
...(cnd[6] && {
48+
~~~~~~~~~~~~~~~~~~~~~~~
49+
prop6a: 1,
50+
~~~~~~~~~~~~~~~~~~~~~~
51+
prop6b: 1,
52+
~~~~~~~~~~~~~~~~~~~~~~
53+
}),
54+
~~~~~~~~~~~
55+
...(cnd[7] && {
56+
~~~~~~~~~~~~~~~~~~~~~~~
57+
prop7a: 1,
58+
~~~~~~~~~~~~~~~~~~~~~~
59+
prop7b: 1,
60+
~~~~~~~~~~~~~~~~~~~~~~
61+
}),
62+
~~~~~~~~~~~
63+
...(cnd[8] && {
64+
~~~~~~~~~~~~~~~~~~~~~~~
65+
prop8a: 1,
66+
~~~~~~~~~~~~~~~~~~~~~~
67+
prop8b: 1,
68+
~~~~~~~~~~~~~~~~~~~~~~
69+
}),
70+
~~~~~~~~~~~
71+
...(cnd[9] && {
72+
~~~~~~~~~~~~~~~~~~~~~~~
73+
prop9a: 1,
74+
~~~~~~~~~~~~~~~~~~~~~~
75+
prop9b: 1,
76+
~~~~~~~~~~~~~~~~~~~~~~
77+
}),
78+
~~~~~~~~~~~
79+
...(cnd[10] && {
80+
~~~~~~~~~~~~~~~~~~~~~~~~
81+
prop10a: 1,
82+
~~~~~~~~~~~~~~~~~~~~~~~
83+
prop10b: 1,
84+
~~~~~~~~~~~~~~~~~~~~~~~
85+
}),
86+
~~~~~~~~~~~
87+
...(cnd[11] && {
88+
~~~~~~~~~~~~~~~~~~~~~~~~
89+
prop11a: 1,
90+
~~~~~~~~~~~~~~~~~~~~~~~
91+
prop11b: 1,
92+
~~~~~~~~~~~~~~~~~~~~~~~
93+
}),
94+
~~~~~~~~~~~
95+
...(cnd[12] && {
96+
~~~~~~~~~~~~~~~~~~~~~~~~
97+
prop12a: 1,
98+
~~~~~~~~~~~~~~~~~~~~~~~
99+
prop12b: 1,
100+
~~~~~~~~~~~~~~~~~~~~~~~
101+
}),
102+
~~~~~~~~~~~
103+
...(cnd[13] && {
104+
~~~~~~~~~~~~~~~~~~~~~~~~
105+
prop13a: 1,
106+
~~~~~~~~~~~~~~~~~~~~~~~
107+
prop13b: 1,
108+
~~~~~~~~~~~~~~~~~~~~~~~
109+
}),
110+
~~~~~~~~~~~
111+
...(cnd[14] && {
112+
~~~~~~~~~~~~~~~~~~~~~~~~
113+
prop14a: 1,
114+
~~~~~~~~~~~~~~~~~~~~~~~
115+
prop14b: 1,
116+
~~~~~~~~~~~~~~~~~~~~~~~
117+
}),
118+
~~~~~~~~~~~
119+
...(cnd[15] && {
120+
~~~~~~~~~~~~~~~~~~~~~~~~
121+
prop15a: 1,
122+
~~~~~~~~~~~~~~~~~~~~~~~
123+
prop15b: 1,
124+
~~~~~~~~~~~~~~~~~~~~~~~
125+
}),
126+
~~~~~~~~~~~
127+
...(cnd[16] && {
128+
~~~~~~~~~~~~~~~~~~~~~~~~
129+
prop16a: 1,
130+
~~~~~~~~~~~~~~~~~~~~~~~
131+
prop16b: 1,
132+
~~~~~~~~~~~~~~~~~~~~~~~
133+
}),
134+
~~~~~~~~~~~
135+
...(cnd[17] && {
136+
~~~~~~~~~~~~~~~~~~~~~~~~
137+
prop17a: 1,
138+
~~~~~~~~~~~~~~~~~~~~~~~
139+
prop17b: 1,
140+
~~~~~~~~~~~~~~~~~~~~~~~
141+
}),
142+
~~~~~~~~~~~
143+
...(cnd[18] && {
144+
~~~~~~~~~~~~~~~~~~~~~~~~
145+
prop18a: 1,
146+
~~~~~~~~~~~~~~~~~~~~~~~
147+
prop18b: 1,
148+
~~~~~~~~~~~~~~~~~~~~~~~
149+
}),
150+
~~~~~~~~~~~
151+
...(cnd[19] && {
152+
~~~~~~~~~~~~~~~~~~~~~~~~
153+
prop19a: 1,
154+
~~~~~~~~~~~~~~~~~~~~~~~
155+
prop19b: 1,
156+
~~~~~~~~~~~~~~~~~~~~~~~
157+
}),
158+
~~~~~~~~~~~
159+
...(cnd[20] && {
160+
~~~~~~~~~~~~~~~~~~~~~~~~
161+
prop20a: 1,
162+
~~~~~~~~~~~~~~~~~~~~~~~
163+
prop20b: 1,
164+
~~~~~~~~~~~~~~~~~~~~~~~
165+
}),
166+
~~~~~~~~~~~
167+
};
168+
~~~~~
169+
!!! error TS2796: Spread expression produces a union type that is too complex to represent.
170+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//// [objectSpreadRepeatedComplexity.ts]
2+
function f(cnd: Record<number, boolean>){
3+
// Type is a union of 2^(n-1) members, where n is the number of spread objects
4+
return {
5+
// Without this one, it collapses to {} ?
6+
...(cnd[1] &&
7+
cnd[2] && {
8+
prop0: 0,
9+
}),
10+
11+
// With one prop each, it collapses to a single object (#34853?)
12+
...(cnd[3] && {
13+
prop3a: 1,
14+
prop3b: 1,
15+
}),
16+
...(cnd[4] && {
17+
prop4a: 1,
18+
prop4b: 1,
19+
}),
20+
...(cnd[5] && {
21+
prop5a: 1,
22+
prop5b: 1,
23+
}),
24+
...(cnd[6] && {
25+
prop6a: 1,
26+
prop6b: 1,
27+
}),
28+
...(cnd[7] && {
29+
prop7a: 1,
30+
prop7b: 1,
31+
}),
32+
...(cnd[8] && {
33+
prop8a: 1,
34+
prop8b: 1,
35+
}),
36+
...(cnd[9] && {
37+
prop9a: 1,
38+
prop9b: 1,
39+
}),
40+
...(cnd[10] && {
41+
prop10a: 1,
42+
prop10b: 1,
43+
}),
44+
...(cnd[11] && {
45+
prop11a: 1,
46+
prop11b: 1,
47+
}),
48+
...(cnd[12] && {
49+
prop12a: 1,
50+
prop12b: 1,
51+
}),
52+
...(cnd[13] && {
53+
prop13a: 1,
54+
prop13b: 1,
55+
}),
56+
...(cnd[14] && {
57+
prop14a: 1,
58+
prop14b: 1,
59+
}),
60+
...(cnd[15] && {
61+
prop15a: 1,
62+
prop15b: 1,
63+
}),
64+
...(cnd[16] && {
65+
prop16a: 1,
66+
prop16b: 1,
67+
}),
68+
...(cnd[17] && {
69+
prop17a: 1,
70+
prop17b: 1,
71+
}),
72+
...(cnd[18] && {
73+
prop18a: 1,
74+
prop18b: 1,
75+
}),
76+
...(cnd[19] && {
77+
prop19a: 1,
78+
prop19b: 1,
79+
}),
80+
...(cnd[20] && {
81+
prop20a: 1,
82+
prop20b: 1,
83+
}),
84+
};
85+
}
86+
87+
//// [objectSpreadRepeatedComplexity.js]
88+
"use strict";
89+
var __assign = (this && this.__assign) || function () {
90+
__assign = Object.assign || function(t) {
91+
for (var s, i = 1, n = arguments.length; i < n; i++) {
92+
s = arguments[i];
93+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
94+
t[p] = s[p];
95+
}
96+
return t;
97+
};
98+
return __assign.apply(this, arguments);
99+
};
100+
function f(cnd) {
101+
// Type is a union of 2^(n-1) members, where n is the number of spread objects
102+
return __assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign({}, (cnd[1] &&
103+
cnd[2] && {
104+
prop0: 0
105+
})), (cnd[3] && {
106+
prop3a: 1,
107+
prop3b: 1
108+
})), (cnd[4] && {
109+
prop4a: 1,
110+
prop4b: 1
111+
})), (cnd[5] && {
112+
prop5a: 1,
113+
prop5b: 1
114+
})), (cnd[6] && {
115+
prop6a: 1,
116+
prop6b: 1
117+
})), (cnd[7] && {
118+
prop7a: 1,
119+
prop7b: 1
120+
})), (cnd[8] && {
121+
prop8a: 1,
122+
prop8b: 1
123+
})), (cnd[9] && {
124+
prop9a: 1,
125+
prop9b: 1
126+
})), (cnd[10] && {
127+
prop10a: 1,
128+
prop10b: 1
129+
})), (cnd[11] && {
130+
prop11a: 1,
131+
prop11b: 1
132+
})), (cnd[12] && {
133+
prop12a: 1,
134+
prop12b: 1
135+
})), (cnd[13] && {
136+
prop13a: 1,
137+
prop13b: 1
138+
})), (cnd[14] && {
139+
prop14a: 1,
140+
prop14b: 1
141+
})), (cnd[15] && {
142+
prop15a: 1,
143+
prop15b: 1
144+
})), (cnd[16] && {
145+
prop16a: 1,
146+
prop16b: 1
147+
})), (cnd[17] && {
148+
prop17a: 1,
149+
prop17b: 1
150+
})), (cnd[18] && {
151+
prop18a: 1,
152+
prop18b: 1
153+
})), (cnd[19] && {
154+
prop19a: 1,
155+
prop19b: 1
156+
})), (cnd[20] && {
157+
prop20a: 1,
158+
prop20b: 1
159+
}));
160+
}

0 commit comments

Comments
 (0)