From 95b73f37888cd8756dae3b5a8aba6b1a9f96787a Mon Sep 17 00:00:00 2001 From: Dino0204 Date: Thu, 18 Dec 2025 01:15:16 +0000 Subject: [PATCH 1/3] Fix infinite loop in symbolWalker during extract function refactoring --- src/compiler/symbolWalker.ts | 80 ++++++++++++------- ...extractFunctionSymbolWalkerInfiniteLoop.ts | 16 ++++ 2 files changed, 65 insertions(+), 31 deletions(-) create mode 100644 tests/cases/fourslash/extractFunctionSymbolWalkerInfiniteLoop.ts diff --git a/src/compiler/symbolWalker.ts b/src/compiler/symbolWalker.ts index 3f1181236f90a..c09e1eedaa5f0 100644 --- a/src/compiler/symbolWalker.ts +++ b/src/compiler/symbolWalker.ts @@ -45,6 +45,9 @@ export function createGetSymbolWalker( const visitedTypes: Type[] = []; // Sparse array from id to type const visitedSymbols: Symbol[] = []; // Sparse array from id to symbol + const maxRecursionDepth = 100; + let level = 0; + return { walkType: type => { try { @@ -76,41 +79,56 @@ export function createGetSymbolWalker( if (visitedTypes[type.id]) { return; } - visitedTypes[type.id] = type; - - // Reuse visitSymbol to visit the type's symbol, - // but be sure to bail on recuring into the type if accept declines the symbol. - const shouldBail = visitSymbol(type.symbol); - if (shouldBail) return; - - // Visit the type's related types, if any - if (type.flags & TypeFlags.Object) { - const objectType = type as ObjectType; - const objectFlags = objectType.objectFlags; - if (objectFlags & ObjectFlags.Reference) { - visitTypeReference(type as TypeReference); + + // Prevent infinite recursion by limiting depth + if (level > maxRecursionDepth) { + return; + } + + // Increment recursion level + level++; + + // Wrap logic in try-finally to ensure level is decremented even on early return or error + try { + visitedTypes[type.id] = type; + + // Reuse visitSymbol to visit the type's symbol, + // but be sure to bail on recuring into the type if accept declines the symbol. + const shouldBail = visitSymbol(type.symbol); + if (shouldBail) return; + + // Visit the type's related types, if any + if (type.flags & TypeFlags.Object) { + const objectType = type as ObjectType; + const objectFlags = objectType.objectFlags; + if (objectFlags & ObjectFlags.Reference) { + visitTypeReference(type as TypeReference); + } + if (objectFlags & ObjectFlags.Mapped) { + visitMappedType(type as MappedType); + } + if (objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)) { + visitInterfaceType(type as InterfaceType); + } + if (objectFlags & (ObjectFlags.Tuple | ObjectFlags.Anonymous)) { + visitObjectType(objectType); + } } - if (objectFlags & ObjectFlags.Mapped) { - visitMappedType(type as MappedType); + if (type.flags & TypeFlags.TypeParameter) { + visitTypeParameter(type as TypeParameter); } - if (objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)) { - visitInterfaceType(type as InterfaceType); + if (type.flags & TypeFlags.UnionOrIntersection) { + visitUnionOrIntersectionType(type as UnionOrIntersectionType); } - if (objectFlags & (ObjectFlags.Tuple | ObjectFlags.Anonymous)) { - visitObjectType(objectType); + if (type.flags & TypeFlags.Index) { + visitIndexType(type as IndexType); } - } - if (type.flags & TypeFlags.TypeParameter) { - visitTypeParameter(type as TypeParameter); - } - if (type.flags & TypeFlags.UnionOrIntersection) { - visitUnionOrIntersectionType(type as UnionOrIntersectionType); - } - if (type.flags & TypeFlags.Index) { - visitIndexType(type as IndexType); - } - if (type.flags & TypeFlags.IndexedAccess) { - visitIndexedAccessType(type as IndexedAccessType); + if (type.flags & TypeFlags.IndexedAccess) { + visitIndexedAccessType(type as IndexedAccessType); + } + } finally { + // Decrement recursion level when exiting + level--; } } diff --git a/tests/cases/fourslash/extractFunctionSymbolWalkerInfiniteLoop.ts b/tests/cases/fourslash/extractFunctionSymbolWalkerInfiniteLoop.ts new file mode 100644 index 0000000000000..17156b4bc4594 --- /dev/null +++ b/tests/cases/fourslash/extractFunctionSymbolWalkerInfiniteLoop.ts @@ -0,0 +1,16 @@ +/// +// @lib: esnext,dom +// @strict: true + +////function func(param: ProblematicType) {} +////type ProblematicType = { +//// prop: ProblematicType; +////}; +////class TestRefactoring { +//// createElement() { +//// [|document.createElement('span');|] +//// } +////} + +goTo.selectRange(test.ranges()[0]); +verify.not.refactorAvailable("Extract function"); \ No newline at end of file From a41bd6ef25edcd2489af7e2c15cc1ee75831aec5 Mon Sep 17 00:00:00 2001 From: Dino0204 Date: Thu, 18 Dec 2025 03:06:38 +0000 Subject: [PATCH 2/3] Fix off-by-one error in recursion limit --- src/compiler/symbolWalker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/symbolWalker.ts b/src/compiler/symbolWalker.ts index c09e1eedaa5f0..5436b69b8b363 100644 --- a/src/compiler/symbolWalker.ts +++ b/src/compiler/symbolWalker.ts @@ -81,7 +81,7 @@ export function createGetSymbolWalker( } // Prevent infinite recursion by limiting depth - if (level > maxRecursionDepth) { + if (level >= maxRecursionDepth) { return; } From 8305d70e86acacba888e7740f6717e83d12c176d Mon Sep 17 00:00:00 2001 From: Dino0204 Date: Thu, 18 Dec 2025 03:23:25 +0000 Subject: [PATCH 3/3] Fix CI format error --- src/compiler/symbolWalker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/symbolWalker.ts b/src/compiler/symbolWalker.ts index 5436b69b8b363..d09c8bd8878dd 100644 --- a/src/compiler/symbolWalker.ts +++ b/src/compiler/symbolWalker.ts @@ -126,7 +126,8 @@ export function createGetSymbolWalker( if (type.flags & TypeFlags.IndexedAccess) { visitIndexedAccessType(type as IndexedAccessType); } - } finally { + } + finally { // Decrement recursion level when exiting level--; }