Skip to content

fix: "Map maximum size exceeded" in deepClone#17142

Merged
JLHwung merged 4 commits intobabel:mainfrom
liuxingbaoyu:fix-clone-size
Mar 2, 2025
Merged

fix: "Map maximum size exceeded" in deepClone#17142
JLHwung merged 4 commits intobabel:mainfrom
liuxingbaoyu:fix-clone-size

Conversation

@liuxingbaoyu
Copy link
Copy Markdown
Member

Q                       A
Fixed Issues? Fixes #16967
Patch: Bug Fix?
Major: Breaking Change?
Minor: New Feature?
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes?
License MIT

In Babel 7, only a workaround was applied, and in Babel 8, a complete fix was made.

@babel-bot
Copy link
Copy Markdown
Collaborator

babel-bot commented Feb 20, 2025

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/58787

@liuxingbaoyu liuxingbaoyu added PR: Bug Fix 🐛 A type of pull request used for our changelog categories pkg: core labels Feb 20, 2025
try {
return deepClone(value, new Map(), true);
} catch (_) {
return structuredClone(value);
Copy link
Copy Markdown
Member Author

@liuxingbaoyu liuxingbaoyu Feb 20, 2025

Choose a reason for hiding this comment

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

This only works in node v17+, but v8.serialize doesn't work in browsers and other runtimes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use structuredClone in Babel 8?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

#16967 (comment)
Unfortunately it is slow, less than half the performance of current implementations.🤦‍♂️

function deepClone(
value: any,
cache: Map<any, any>,
allowCircle: boolean,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The allowCircle is false but the object actually contains a cycle, this function is now going to cause an infinite loop, right? Can we throw in that case instead? We'd only need to keep track of the current "stack", rather than of all nodes in the map.

try {
return deepClone(value, new Map(), true);
} catch (_) {
return structuredClone(value);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use structuredClone in Babel 8?

circleSet.clear();
throw new Error("Babel-deepClone: Cycles are not allowed in AST");
}
circleSet.add(value);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How big is the performance impact of using this set compared to:

  • the old behavior with the map
  • the behavior with no protection against infinite loops

If it's significantly expensive, we could keep track of the depth we are at in the cloning and only start using the cycle detection logic if it's, for example, >100. It means that we'll be slower in error cases, but faster in the (common) successful case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

current: 263 ops/sec ±1.13% (3.809ms)
circle-check: 255 ops/sec ±0.9% (3.924ms)
no-circle-check: 357 ops/sec ±1.55% (2.799ms)
circle-check-100-depth: 355 ops/sec ±0.7% (2.814ms)

I'll add depth detection. :)

if (cache.has(value)) return cache.get(value);
if (allowCircle) {
if (cache.has(value)) return cache.get(value);
} else if (++depth > 100) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was trying to see what are reasonable depth values: both @babel/standalone/babel.js and @babel/standalone/babel.min.js are around 200.

Let's increase this to 200 at least, maybe 250 to be safe? It's unlikely that there are files deeper than that.

(Or maybe we could also just completely remove the cycle-detection logic and just throw an error if depth > 1000 😬)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks for your investigation!
I'd be inclined to set it to 250, since there are probably a few hundred variables being concatenated(auto-generated code) :)

@nicolo-ribaudo nicolo-ribaudo requested a review from JLHwung March 2, 2025 16:30
@JLHwung JLHwung merged commit 985f051 into babel:main Mar 2, 2025
Tobbe added a commit to Tobbe/redwood that referenced this pull request Mar 14, 2025
Tobbe added a commit to redwoodjs/graphql that referenced this pull request Mar 15, 2025
Tobbe added a commit to redwoodjs/graphql that referenced this pull request Mar 25, 2025
@github-actions github-actions Bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Jun 2, 2025
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jun 2, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

outdated A closed issue/PR that is archived due to age. Recommended to make a new issue pkg: core PR: Bug Fix 🐛 A type of pull request used for our changelog categories

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: "Map maximum size exceeded" in deepClone

4 participants