Skip to content

Add regression test for rope string slice performance (issue #26682)#26683

Open
robobun wants to merge 1 commit intomainfrom
claude/fix-rope-string-slice-issue-26682
Open

Add regression test for rope string slice performance (issue #26682)#26683
robobun wants to merge 1 commit intomainfrom
claude/fix-rope-string-slice-issue-26682

Conversation

@robobun
Copy link
Copy Markdown
Collaborator

@robobun robobun commented Feb 2, 2026

Summary

Fixes #26682

String.prototype.slice() currently has O(n) complexity on rope strings (strings built by concatenation), causing O(n²) overall complexity when iterating through a concatenated string with slice operations.

Reproduction:

let s = '';
for (let i = 0; i < 50000; i++) s += 'A';

let start = Date.now();
let m = new Map();
for (let i = 0; i < 50000; i++) {
    let k = s.slice(i, i+1);
    m.set(k, 1);
}
console.log(`Time: ${Date.now() - start}ms`);

Results:

  • Node.js: ~2ms
  • Bun 1.3.8: ~2800ms (~1400x slower)

Root Cause

The issue is in JavaScriptCore's tryJSSubstringImpl function in JSString.h. When slicing across rope fiber boundaries, the implementation resolves the entire rope instead of extracting substrings from each fiber.

Fix

The fix is in the WebKit fork: oven-sh/WebKit#154

The fix modifies tryJSSubstringImpl to handle cross-fiber slices by extracting substrings from each relevant fiber and concatenating them into a new rope, avoiding the need to resolve the entire original rope.

This PR

This PR adds regression tests that are marked as test.todo() until the WebKit fix is merged and the commit hash is updated in cmake/tools/SetupWebKit.cmake.

Test plan

  • Tests pass locally with USE_SYSTEM_BUN=1 bun test test/regression/issue/026682.test.ts
  • After WebKit PR is merged, update WEBKIT_VERSION in cmake/tools/SetupWebKit.cmake
  • Change test.todo() to test() and verify tests pass

🤖 Generated with Claude Code

String.prototype.slice() currently has O(n) complexity on rope strings,
causing O(n²) overall complexity when iterating through a concatenated
string with slice operations.

The tests are marked as todo until the WebKit fix is merged:
oven-sh/WebKit#154

Once the WebKit PR is merged and the commit hash is updated in
cmake/tools/SetupWebKit.cmake, these tests should be enabled.

Fixes #26682

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@robobun
Copy link
Copy Markdown
Collaborator Author

robobun commented Feb 2, 2026

Updated 10:57 PM PT - Feb 1st, 2026

❌ Your commit 35e9ca91 has 2 failures in Build #36365 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 26683

That installs a local version of the PR into your bun-26683 executable, so you can run:

bun-26683 --bun

@github-actions github-actions bot added the claude label Feb 2, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 2, 2026

Walkthrough

Adds a regression test file containing three performance test cases for string operations on concatenated strings. The tests verify slice, charAt, and bracket access performance to detect quadratic versus linear time complexity behavior, referencing upstream WebKit fixes.

Changes

Cohort / File(s) Summary
String Performance Tests
test/regression/issue/026682.test.ts
New test file with three test.todos verifying rope string performance for slice, charAt, and bracket access operations with explicit timing thresholds. Tests simulate O(n²) behavior detection on concatenated large strings.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a regression test for rope string slice performance related to issue #26682.
Description check ✅ Passed The description is well-structured and comprehensive, covering the problem statement, root cause analysis, the upstream fix, and the test plan. It exceeds the basic template requirements.
Linked Issues check ✅ Passed The PR adds regression tests that directly address the O(n²) performance problem documented in issue #26682 for String.prototype.slice() on rope strings.
Out of Scope Changes check ✅ Passed The PR only adds a test file (test/regression/issue/026682.test.ts) with no production code changes, keeping the scope tightly focused on regression testing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@test/regression/issue/026682.test.ts`:
- Around line 34-41: The inner loop builds each chunk one character at a time
causing inefficiency; replace the per-char concatenation in the test (the
variables chunk and the inner for-loop that creates chunk using
String.fromCharCode) with a single allocation using Buffer.alloc(chunkSize,
fillChar).toString() (where fillChar is String.fromCharCode(65 + i)), then
append that chunk to s as before to preserve the rope behavior.
- Around line 65-67: Replace the manual loop that builds the repetitive rope
string (variables iterations and s) with a chunked construction using
Buffer.alloc(count, 'A').toString(): choose a chunkSize (e.g., 1024), compute
fullChunks = Math.floor(iterations / chunkSize) and remainder = iterations %
chunkSize, create the chunk string once via Buffer.alloc(chunkSize,
'A').toString(), push that chunk fullChunks times (and a final
Buffer.alloc(remainder, 'A').toString() when remainder > 0) into an array, then
join to produce s; this keeps setup cost predictable and follows the guideline
to use Buffer.alloc instead of repeated concatenation or 'A'.repeat.
- Around line 12-16: The test currently builds a long repetitive string by
appending one character at a time using the iterations variable and s string
(for loop that does s += "A"), which is O(n²); replace that loop by creating the
string in one allocation using Buffer.alloc(iterations, "A").toString() and
assign it to s so the test still produces a long rope but avoids char-by-char
concatenation.

Comment on lines +12 to +16
// Using 50,000 iterations - enough to detect O(n²) behavior
const iterations = 50000;
let s = "";
for (let i = 0; i < iterations; i++) s += "A";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use Buffer.alloc(...).toString() for repetitive strings (avoid char-by-char concat).

The current loop builds a repetitive string one character at a time. Use chunked Buffer.alloc to comply with test guidelines and avoid O(n²) setup costs while still creating a rope via concatenation.

Suggested update
-  const iterations = 50000;
-  let s = "";
-  for (let i = 0; i < iterations; i++) s += "A";
+  const iterations = 50000;
+  const chunkSize = 1000;
+  const chunk = Buffer.alloc(chunkSize, "A").toString();
+  let s = "";
+  for (let i = 0; i < iterations; i += chunkSize) s += chunk;

As per coding guidelines: "Use Buffer.alloc(count, fill).toString() instead of 'A'.repeat(count) to create repetitive strings in tests."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Using 50,000 iterations - enough to detect O(n²) behavior
const iterations = 50000;
let s = "";
for (let i = 0; i < iterations; i++) s += "A";
// Using 50,000 iterations - enough to detect O(n²) behavior
const iterations = 50000;
const chunkSize = 1000;
const chunk = Buffer.alloc(chunkSize, "A").toString();
let s = "";
for (let i = 0; i < iterations; i += chunkSize) s += chunk;
🤖 Prompt for AI Agents
In `@test/regression/issue/026682.test.ts` around lines 12 - 16, The test
currently builds a long repetitive string by appending one character at a time
using the iterations variable and s string (for loop that does s += "A"), which
is O(n²); replace that loop by creating the string in one allocation using
Buffer.alloc(iterations, "A").toString() and assign it to s so the test still
produces a long rope but avoids char-by-char concatenation.

Comment on lines +34 to +41
const chunkSize = 10000;
let s = "";
for (let i = 0; i < 5; i++) {
let chunk = "";
for (let j = 0; j < chunkSize; j++) {
chunk += String.fromCharCode(65 + i); // A, B, C, D, E
}
s += chunk;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Create chunks via Buffer.alloc instead of per-char concatenation.

This inner loop is building a repetitive chunk one character at a time. Use Buffer.alloc(chunkSize, fill).toString() and keep the rope by concatenating chunks.

Suggested update
-  const chunkSize = 10000;
-  let s = "";
-  for (let i = 0; i < 5; i++) {
-    let chunk = "";
-    for (let j = 0; j < chunkSize; j++) {
-      chunk += String.fromCharCode(65 + i); // A, B, C, D, E
-    }
-    s += chunk;
-  }
+  const chunkSize = 10000;
+  let s = "";
+  for (let i = 0; i < 5; i++) {
+    const chunk = Buffer.alloc(chunkSize, String.fromCharCode(65 + i)).toString(); // A, B, C, D, E
+    s += chunk;
+  }

As per coding guidelines: "Use Buffer.alloc(count, fill).toString() instead of 'A'.repeat(count) to create repetitive strings in tests."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const chunkSize = 10000;
let s = "";
for (let i = 0; i < 5; i++) {
let chunk = "";
for (let j = 0; j < chunkSize; j++) {
chunk += String.fromCharCode(65 + i); // A, B, C, D, E
}
s += chunk;
const chunkSize = 10000;
let s = "";
for (let i = 0; i < 5; i++) {
const chunk = Buffer.alloc(chunkSize, String.fromCharCode(65 + i)).toString(); // A, B, C, D, E
s += chunk;
}
🤖 Prompt for AI Agents
In `@test/regression/issue/026682.test.ts` around lines 34 - 41, The inner loop
builds each chunk one character at a time causing inefficiency; replace the
per-char concatenation in the test (the variables chunk and the inner for-loop
that creates chunk using String.fromCharCode) with a single allocation using
Buffer.alloc(chunkSize, fillChar).toString() (where fillChar is
String.fromCharCode(65 + i)), then append that chunk to s as before to preserve
the rope behavior.

Comment on lines +65 to +67
const iterations = 50000;
let s = "";
for (let i = 0; i < iterations; i++) s += "A";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use chunked Buffer.alloc for the repetitive rope string.

Build the rope by concatenating chunks created via Buffer.alloc to comply with test guidelines and keep setup cost predictable.

Suggested update
-  const iterations = 50000;
-  let s = "";
-  for (let i = 0; i < iterations; i++) s += "A";
+  const iterations = 50000;
+  const chunkSize = 1000;
+  const chunk = Buffer.alloc(chunkSize, "A").toString();
+  let s = "";
+  for (let i = 0; i < iterations; i += chunkSize) s += chunk;

As per coding guidelines: "Use Buffer.alloc(count, fill).toString() instead of 'A'.repeat(count) to create repetitive strings in tests."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const iterations = 50000;
let s = "";
for (let i = 0; i < iterations; i++) s += "A";
const iterations = 50000;
const chunkSize = 1000;
const chunk = Buffer.alloc(chunkSize, "A").toString();
let s = "";
for (let i = 0; i < iterations; i += chunkSize) s += chunk;
🤖 Prompt for AI Agents
In `@test/regression/issue/026682.test.ts` around lines 65 - 67, Replace the
manual loop that builds the repetitive rope string (variables iterations and s)
with a chunked construction using Buffer.alloc(count, 'A').toString(): choose a
chunkSize (e.g., 1024), compute fullChunks = Math.floor(iterations / chunkSize)
and remainder = iterations % chunkSize, create the chunk string once via
Buffer.alloc(chunkSize, 'A').toString(), push that chunk fullChunks times (and a
final Buffer.alloc(remainder, 'A').toString() when remainder > 0) into an array,
then join to produce s; this keeps setup cost predictable and follows the
guideline to use Buffer.alloc instead of repeated concatenation or 'A'.repeat.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bad performance of string.slice() on concatenated strings

1 participant