Skip to content

Commit 0ba8998

Browse files
authored
Fix stack overflow in merge symbol (microsoft#24134)
* JS initializers use original valueDecl, not mutated target's valueDeclaration is set to the source's if it is not present. This makes it incorrect to use getJSInitializerSymbol because it relies on the symbol's valueDeclaration. This fix just saves the original valueDeclaration before mutating and uses that. * Compare merged targetInitializer to target Instead of the unmerged one * Add test of stack overflow
1 parent 7e515af commit 0ba8998

4 files changed

Lines changed: 58 additions & 5 deletions

File tree

src/compiler/checker.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,7 @@ namespace ts {
892892
function mergeSymbol(target: Symbol, source: Symbol) {
893893
if (!(target.flags & getExcludedSymbolFlags(source.flags)) ||
894894
(source.flags | target.flags) & SymbolFlags.JSContainer) {
895+
const targetValueDeclaration = target.valueDeclaration;
895896
Debug.assert(!!(target.flags & SymbolFlags.Transient));
896897
// Javascript static-property-assignment declarations always merge, even though they are also values
897898
if (source.flags & SymbolFlags.ValueModule && target.flags & SymbolFlags.ValueModule && target.constEnumOnlyModule && !source.constEnumOnlyModule) {
@@ -916,12 +917,13 @@ namespace ts {
916917
}
917918
if ((source.flags | target.flags) & SymbolFlags.JSContainer) {
918919
const sourceInitializer = getJSInitializerSymbol(source);
919-
let targetInitializer = getJSInitializerSymbol(target);
920+
const init = getDeclaredJavascriptInitializer(targetValueDeclaration) || getAssignedJavascriptInitializer(targetValueDeclaration);
921+
let targetInitializer = init && init.symbol ? init.symbol : target;
922+
if (!(targetInitializer.flags & SymbolFlags.Transient)) {
923+
const mergedInitializer = getMergedSymbol(targetInitializer);
924+
targetInitializer = mergedInitializer === targetInitializer ? cloneSymbol(targetInitializer) : mergedInitializer;
925+
}
920926
if (sourceInitializer !== source || targetInitializer !== target) {
921-
if (!(targetInitializer.flags & SymbolFlags.Transient)) {
922-
const mergedInitializer = getMergedSymbol(targetInitializer);
923-
targetInitializer = mergedInitializer === targetInitializer ? cloneSymbol(targetInitializer) : mergedInitializer;
924-
}
925927
mergeSymbol(targetInitializer, sourceInitializer);
926928
}
927929
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
=== tests/cases/conformance/salsa/a.js ===
2+
// #24131
3+
const a = {};
4+
>a : Symbol(a, Decl(a.js, 1, 5), Decl(a.js, 1, 13), Decl(b.js, 0, 0))
5+
6+
a.d = function() {};
7+
>a.d : Symbol(d, Decl(a.js, 1, 13), Decl(b.js, 0, 2))
8+
>a : Symbol(a, Decl(a.js, 1, 5), Decl(a.js, 1, 13), Decl(b.js, 0, 0))
9+
>d : Symbol(d, Decl(a.js, 1, 13), Decl(b.js, 0, 2))
10+
11+
=== tests/cases/conformance/salsa/b.js ===
12+
a.d.prototype = {};
13+
>a.d.prototype : Symbol(d.prototype, Decl(b.js, 0, 0))
14+
>a.d : Symbol(d, Decl(a.js, 1, 13), Decl(b.js, 0, 2))
15+
>a : Symbol(a, Decl(a.js, 1, 5), Decl(a.js, 1, 13), Decl(b.js, 0, 0))
16+
>d : Symbol(d, Decl(a.js, 1, 13), Decl(b.js, 0, 2))
17+
>prototype : Symbol(d.prototype, Decl(b.js, 0, 0))
18+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
=== tests/cases/conformance/salsa/a.js ===
2+
// #24131
3+
const a = {};
4+
>a : { [x: string]: any; d: typeof d; }
5+
>{} : { [x: string]: any; d: typeof d; }
6+
7+
a.d = function() {};
8+
>a.d = function() {} : typeof d
9+
>a.d : typeof d
10+
>a : { [x: string]: any; d: typeof d; }
11+
>d : typeof d
12+
>function() {} : typeof d
13+
14+
=== tests/cases/conformance/salsa/b.js ===
15+
a.d.prototype = {};
16+
>a.d.prototype = {} : { [x: string]: any; }
17+
>a.d.prototype : { [x: string]: any; }
18+
>a.d : typeof d
19+
>a : { [x: string]: any; d: typeof d; }
20+
>d : typeof d
21+
>prototype : { [x: string]: any; }
22+
>{} : { [x: string]: any; }
23+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @noEmit: true
2+
// @allowJs: true
3+
// @checkJs: true
4+
5+
// #24131
6+
// @Filename: a.js
7+
const a = {};
8+
a.d = function() {};
9+
// @Filename: b.js
10+
a.d.prototype = {};

0 commit comments

Comments
 (0)