From 3bf36bc8489360e69c9a9b1ce8d0775918d383a9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:30:07 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Prevent=20Call=20Stack=20Ex?= =?UTF-8?q?ceeded=20on=20large=20array=20push?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the use of the array spread operator (`push(...items)`) with a manual `for` loop in performance-critical paths dealing with potentially unbounded arrays (e.g., search results, file discovery). This safely appends items regardless of array size and prevents the V8 engine from throwing a `RangeError: Maximum call stack size exceeded` when processing large workspaces. Co-authored-by: AhmmedSamier <17784876+AhmmedSamier@users.noreply.github.com> --- .jules/bolt.md | 3 +++ language-server/src/core/ripgrep-service.ts | 6 +++++- language-server/src/core/search-engine.ts | 19 ++++++++++++++++--- language-server/src/core/workspace-indexer.ts | 6 +++++- language-server/src/indexer-client.ts | 12 ++++++++++-- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index b8cd366..0261fba 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -9,3 +9,6 @@ ## 2026-04-08 - [Fast Dense Integer Set Tracking] **Learning:** When keeping track of seen integer IDs that are dense and bounded (e.g. from 0 to N), using `new Set()` incurs heavy allocation and insertion overhead compared to a fixed-size byte array. **Action:** Replace `Set` with `new Uint8Array(maxIndex)` and use `array[id] = 1` to track presence, which is ~15x faster and avoids garbage collection pauses in hot paths. (Benchmark context: `N=100,000` IDs, `bun` version 1.2.14, Linux x86_64, Intel Xeon 2.30GHz, 4 cores, 8GB RAM, averaged over 100 iterations comparing `Set` addition vs `new Uint8Array(maxIndex)` indexed assignment `array[id] = 1`). +## 2026-04-30 - [Fast Safe Array Push] +**Learning:** Using the array spread operator (`array.push(...items)`) to merge arrays is convenient but dangerous when the source array is potentially unbounded (e.g., parsing results, large file system traversals). Node.js passes each element as an individual argument to the underlying function, which can trigger a `RangeError: Maximum call stack size exceeded` if the array exceeds ~65,535 items in V8. +**Action:** Replace `array.push(...items)` with a manual `for` loop (`for (let i = 0; i < items.length; i++) array.push(items[i]);`) in paths dealing with dynamically generated or unconstrained array sizes. This safely handles arrays of any length with consistent memory overhead. diff --git a/language-server/src/core/ripgrep-service.ts b/language-server/src/core/ripgrep-service.ts index a2df023..734b045 100644 --- a/language-server/src/core/ripgrep-service.ts +++ b/language-server/src/core/ripgrep-service.ts @@ -129,7 +129,11 @@ export class RipgrepService { try { const batchResults = await this.runRgBatch(baseArgs, batches[batchIndex], remaining, token); if (batchResults.length > 0) { - allResults.push(...batchResults); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < batchResults.length; i++) { + allResults.push(batchResults[i]); + } } } catch { // Ignore batch failure diff --git a/language-server/src/core/search-engine.ts b/language-server/src/core/search-engine.ts index 39e03d7..51dbde3 100644 --- a/language-server/src/core/search-engine.ts +++ b/language-server/src/core/search-engine.ts @@ -282,7 +282,11 @@ export class SearchEngine implements ISearchProvider { } // Append items - this.items.push(...items); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < items.length; i++) { + this.items.push(items[i]); + } const CHUNK_SIZE = 500; for (let i = 0; i < items.length; i += CHUNK_SIZE) { @@ -392,7 +396,11 @@ export class SearchEngine implements ISearchProvider { if (indices && indices.length > 0) { if (!normalizedRemovedPaths.has(normalized)) { normalizedRemovedPaths.add(normalized); - indicesToRemove.push(...indices); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < indices.length; i++) { + indicesToRemove.push(indices[i]); + } } } } @@ -974,7 +982,12 @@ export class SearchEngine implements ISearchProvider { return; } - allResults.push(...providerResults); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < providerResults.length; i++) { + allResults.push(providerResults[i]); + } + if (onResult) { for (const result of providerResults) { if (token?.isCancellationRequested) { diff --git a/language-server/src/core/workspace-indexer.ts b/language-server/src/core/workspace-indexer.ts index e37b158..92c7509 100644 --- a/language-server/src/core/workspace-indexer.ts +++ b/language-server/src/core/workspace-indexer.ts @@ -227,7 +227,11 @@ export class WorkspaceIndexer { if (this.cancellationToken.cancelled) throw new CancellationError(); await this.indexFiles((items) => { - fileItems.push(...items); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < items.length; i++) { + fileItems.push(items[i]); + } this.fireItemsAdded(items); }); diff --git a/language-server/src/indexer-client.ts b/language-server/src/indexer-client.ts index 3ac688c..bbecf4b 100644 --- a/language-server/src/indexer-client.ts +++ b/language-server/src/indexer-client.ts @@ -46,7 +46,11 @@ export class LspIndexerEnvironment implements IndexerEnvironment { nodir: true, }); - results.push(...files); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < files.length; i++) { + results.push(files[i]); + } } return results; } @@ -58,7 +62,11 @@ export class LspIndexerEnvironment implements IndexerEnvironment { for (const folder of this.workspaceFolders) { try { const files = await this.execRgFiles(folder, excludes); - results.push(...files); + // ⚡ Bolt: Fast safe array push + // Prevents 'Maximum Call Stack Size Exceeded' for very large arrays. + for (let i = 0; i < files.length; i++) { + results.push(files[i]); + } } catch { return []; }