From 9092a1ba1e7ccc92d0dfbb83eab3450cec279397 Mon Sep 17 00:00:00 2001 From: Rui Date: Sat, 22 Apr 2023 17:22:11 -0300 Subject: [PATCH] perf(linker): Fixes brute force chunk cycle detection (#3069) --- internal/linker/linker.go | 43 +++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/internal/linker/linker.go b/internal/linker/linker.go index cd116a4d4ef..4042689b968 100644 --- a/internal/linker/linker.go +++ b/internal/linker/linker.go @@ -452,28 +452,41 @@ func (c *linkerContext) mangleProps(mangleCache map[string]interface{}) { // can cause initialization bugs. So let's forbid these cycles for now to guard // against code splitting bugs that could cause us to generate buggy chunks. func (c *linkerContext) enforceNoCyclicChunkImports() { - var validate func(int, []int) - validate = func(chunkIndex int, path []int) { - for _, otherChunkIndex := range path { - if chunkIndex == otherChunkIndex { - c.log.AddError(nil, logger.Range{}, "Internal error: generated chunks contain a circular import") - return - } + var validate func(int, map[int]int) bool + + // DFS memoization with 3-colors, more space efficient + // 0: white (unvisited), 1: gray (visiting), 2: black (visited) + colors := make(map[int]int) + validate = func(chunkIndex int, colors map[int]int) bool { + if colors[chunkIndex] == 1 { + c.log.AddError(nil, logger.Range{}, "Internal error: generated chunks contain a circular import") + return true } - path = append(path, chunkIndex) + + if colors[chunkIndex] == 2 { + return false + } + + colors[chunkIndex] = 1 + for _, chunkImport := range c.chunks[chunkIndex].crossChunkImports { - // Ignore cycles caused by dynamic "import()" expressions. These are fine - // because they don't necessarily cause initialization order issues and - // they don't indicate a bug in our chunk generation algorithm. They arise - // normally in real code (e.g. two files that import each other). if chunkImport.importKind != ast.ImportDynamic { - validate(int(chunkImport.chunkIndex), path) + + // Recursively validate otherChunkIndex + if validate(int(chunkImport.chunkIndex), colors) { + return true + } } } + + colors[chunkIndex] = 2 + return false } - path := make([]int, 0, len(c.chunks)) + for i := range c.chunks { - validate(i, path) + if validate(i, colors) { + break + } } }