Skip to content

fix: clear staticNode reference on Static unmount#905

Merged
sindresorhus merged 4 commits into
vadimdemedes:masterfrom
costajohnt:fix/static-unmount-oom
Mar 22, 2026
Merged

fix: clear staticNode reference on Static unmount#905
sindresorhus merged 4 commits into
vadimdemedes:masterfrom
costajohnt:fix/static-unmount-oom

Conversation

@costajohnt

Copy link
Copy Markdown
Contributor

Summary

When a <Static> component unmounts, rootNode.staticNode was not cleared, leaving a dangling reference to a freed Yoga WASM node. Subsequent renders called getComputedWidth()/getComputedHeight() on the freed node, returning garbage values that caused OOM when allocating the output grid.

This clears the staticNode reference in both removeChild and removeChildFromContainer when an internal_static node is removed.

Fixes #904

Test plan

  • Added regression test that mounts <Static>, unmounts it via rerender, and verifies subsequent renders succeed without crashing

@costajohnt costajohnt marked this pull request as ready for review March 19, 2026 04:26
@sindresorhus

Copy link
Copy Markdown
Collaborator

CI is failing

When a <Static> component unmounts, rootNode.staticNode was not cleared,
leaving a dangling reference to a freed Yoga WASM node. Subsequent renders
would call getComputedWidth/Height on the freed node, returning garbage
values that caused OOM when allocating the output grid.

Clear the staticNode reference in both removeChild and
removeChildFromContainer when an internal_static node is removed.

Fixes vadimdemedes#904
Switch boolean short-circuit to ternary for jsx-no-leaked-render
and extract any-typed value before chaining .includes() to satisfy
no-unsafe-call.
@costajohnt costajohnt force-pushed the fix/static-unmount-oom branch from 86fd9e0 to d8f7516 Compare March 19, 2026 15:42
@sindresorhus

Copy link
Copy Markdown
Collaborator

The new regression test does not actually prove #904 is fixed. It passes on current master without this patch, because it only checks the immediate rerender for Dynamic. The reported failure happens later, after more Yoga churn and rerenders when the freed WASM memory gets reused, so this test would stay green even if the original bug were still present.

Replace the simple unmount-and-check test with one that:
- Rerenders multiple times after unmounting Static
- Verifies output length stays stable (detects if stale
  staticNode causes fullStaticOutput to grow)
- References issue vadimdemedes#904 in the test name

The underlying use-after-free is nondeterministic (depends on
WASM memory reuse timing), so this test functions as a smoke
test rather than a guaranteed reproduction.
@costajohnt

Copy link
Copy Markdown
Contributor Author

You're right, the previous test passed on master without the patch. I've updated it to verify that fullStaticOutput doesn't grow after <Static> unmounts — it rerenders 10 times after unmount and asserts the output length stays stable.

To be transparent: the underlying use-after-free is nondeterministic (depends on WASM allocator reusing the freed node's memory), so a fully deterministic unit test isn't possible without exposing reconciler internals. This test serves as a smoke test. If you'd prefer a more direct assertion, I could export a test helper from the reconciler to verify rootNode.staticNode === undefined after unmount — happy to go that route if you'd like.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rootNode.staticNode dangling reference causes OOM after <Static> unmount

2 participants