Skip to content

fix: avoid use-after-free for rust instance#2461

Merged
PupilTong merged 6 commits intolynx-family:mainfrom
PupilTong:p/hw/free-lynx-view-instance
Apr 15, 2026
Merged

fix: avoid use-after-free for rust instance#2461
PupilTong merged 6 commits intolynx-family:mainfrom
PupilTong:p/hw/free-lynx-view-instance

Conversation

@PupilTong
Copy link
Copy Markdown
Collaborator

@PupilTong PupilTong commented Apr 14, 2026

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a memory-safety use-after-free during teardown of native bindings.
    • Improved disposal ordering to avoid premature release of background resources.
    • Ensured background message ports are closed when stopping workers.
    • Added proper cleanup for exposure/observer tracking and timers.
    • Deferred final native binding disposal to idle to reduce teardown contention.
    • Corrected event listener removal behavior.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

@PupilTong PupilTong requested a review from Sherry-hue as a code owner April 14, 2026 09:57
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 14, 2026

🦋 Changeset detected

Latest commit: 9964d7a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@lynx-js/web-core Patch
upgrade-rspeedy Patch
@lynx-js/web-rsbuild-server-middleware Patch
@lynx-js/template-webpack-plugin Patch
@lynx-js/react-rsbuild-plugin Patch
create-rspeedy Patch
@lynx-js/web-worker-rpc Patch
@lynx-js/react-alias-rsbuild-plugin Patch
@lynx-js/rspeedy Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

Changed disposal and cleanup sequencing across web-core: deferred wasm disposal, explicit exposure services disposal, safe wasmContext nullification, background MessagePort closure, and event-listener removal signature adjustments to prevent a Rust use-after-free.

Changes

Cohort / File(s) Summary
Changeset
/.changeset/tidy-clubs-wink.md
Added a patch changeset entry announcing a fix for a Rust use-after-free.
Lynx view disposal
packages/web-platform/web-core/ts/client/mainthread/LynxViewInstance.ts
Reordered async disposal: await background thread disposal, dispose ExposureServices, and defer mtsWasmBinding.dispose() via requestIdleCallbackImpl.
Wasm context nullification
packages/web-platform/web-core/ts/client/mainthread/elementAPIs/createElementAPI.ts
After freeing wasmContext, set wasmContext = null immediately (with // @ts-expect-error``).
Background thread port cleanup
packages/web-platform/web-core/ts/client/mainthread/Background.ts
Store MessagePort in a private field and close/clear it during async disposal to release the port.
Exposure lifecycle
packages/web-platform/web-core/ts/client/mainthread/ExposureServices.ts
Added dispose() method to disconnect observers, clear maps/sets, cancel timers, and reset exposure caches.
WASM JS binding listeners
packages/web-platform/web-core/ts/client/mainthread/elementAPIs/WASMJSBinding.ts
Changed removeEventListener options argument from an options object to boolean true when removing rootDom listeners.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • Sherry-hue

Poem

🐰 I nudged a thread and cleared a map,
Freed a context, closed a gap,
Idle callback hums a tune,
Ports tucked in beneath the moon,
No more ghosts—cleanup naps soon. 🎩✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: avoid use-after-free for rust instance' accurately describes the main objective of the changeset, which implements resource cleanup and deferred disposal to prevent use-after-free conditions in the Rust/WASM layer.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Signed-off-by: Haoyang Wang <12288479+PupilTong@users.noreply.github.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 14, 2026

Codecov Report

❌ Patch coverage is 16.66667% with 20 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../web-core/ts/client/mainthread/ExposureServices.ts 0.00% 14 Missing ⚠️
.../web-core/ts/client/mainthread/LynxViewInstance.ts 0.00% 4 Missing ⚠️
.../ts/client/mainthread/elementAPIs/WASMJSBinding.ts 0.00% 1 Missing ⚠️
.../client/mainthread/elementAPIs/createElementAPI.ts 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

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: 2

🧹 Nitpick comments (1)
packages/web-platform/web-core/ts/client/mainthread/elementAPIs/createElementAPI.ts (1)

86-87: Add a regression test around post-dispose PAPI calls.

These lines are the fail-closed guard for the UAF fix, but there’s no coverage proving a late flush/PAPI call now stops in JS instead of touching freed WASM state.

Based on learnings: In TypeScript test files, use WASMJSBinding.ts to mock the Wasm binding for testing DOM manipulation logic without loading the actual Wasm binary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-core/ts/client/mainthread/elementAPIs/createElementAPI.ts`
around lines 86 - 87, Add a regression test that verifies PAPI calls are no-ops
after the Wasm context is disposed: write a TypeScript test (using
WASMJSBinding.ts to mock the Wasm binding) that initializes the module via the
createElementAPI entrypoint, simulates disposal by triggering the same code path
that sets wasmContext = null, then attempts a representative post-dispose PAPI
call (the same public API you expect to guard, e.g., any create/manipulate/flush
method exported by createElementAPI) and assert it does not throw and that the
mocked WASM binding methods were not invoked; ensure the test imports
WASMJSBinding.ts, spies/stubs the underlying wasm methods, calls the dispose
path that nulls wasmContext, then calls the PAPI and asserts a safe no-op
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/tidy-clubs-wink.md:
- Line 5: Update the changelog entry text in .changeset/tidy-clubs-wink.md to
rephrase the awkward wording; replace the existing line "fix: avoid to do
use-after-free for rust instance" with a clearer phrase such as "fix: avoid
use-after-free for the Rust instance" so the generated changelog reads
naturally.

In `@packages/web-platform/web-core/ts/client/mainthread/LynxViewInstance.ts`:
- Around line 303-305: The idle callback currently calls
this.mtsWasmBinding.dispose() which duplicates an earlier dispose path
(disposeWasmContext -> wasmContext.free()) and frees the Rust context too early;
instead, remove the second dispose call and split the teardown into two phases:
1) in the idle callback only run listener/JS cleanup (e.g., unregister event
listeners and release JS-side handles) using a new method like cleanupListeners
or cleanupJSResources on LynxViewInstance, and 2) ensure wasmContext.free() is
invoked only after awaiting this.backgroundThread[Symbol.asyncDispose]() (move
the wasm free call out of disposeWasmContext or add a separate method
freeWasmContext that is called after backgroundThread async dispose). Update
references to requestIdleCallbackImpl, this.mtsWasmBinding.dispose(),
disposeWasmContext, and wasmContext.free() accordingly so there is no duplicate
dispose and the Rust context is freed after the background thread finishes.

---

Nitpick comments:
In
`@packages/web-platform/web-core/ts/client/mainthread/elementAPIs/createElementAPI.ts`:
- Around line 86-87: Add a regression test that verifies PAPI calls are no-ops
after the Wasm context is disposed: write a TypeScript test (using
WASMJSBinding.ts to mock the Wasm binding) that initializes the module via the
createElementAPI entrypoint, simulates disposal by triggering the same code path
that sets wasmContext = null, then attempts a representative post-dispose PAPI
call (the same public API you expect to guard, e.g., any create/manipulate/flush
method exported by createElementAPI) and assert it does not throw and that the
mocked WASM binding methods were not invoked; ensure the test imports
WASMJSBinding.ts, spies/stubs the underlying wasm methods, calls the dispose
path that nulls wasmContext, then calls the PAPI and asserts a safe no-op
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a18ab21e-79d0-45fb-9420-f23614396d76

📥 Commits

Reviewing files that changed from the base of the PR and between 7332eb4 and 00cc290.

📒 Files selected for processing (3)
  • .changeset/tidy-clubs-wink.md
  • packages/web-platform/web-core/ts/client/mainthread/LynxViewInstance.ts
  • packages/web-platform/web-core/ts/client/mainthread/elementAPIs/createElementAPI.ts

Comment thread .changeset/tidy-clubs-wink.md
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Apr 14, 2026

Merging this PR will degrade performance by 42.96%

⚡ 1 improved benchmark
❌ 1 regressed benchmark
✅ 79 untouched benchmarks
⏩ 21 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
basic-performance-text-200 11.8 ms 20.8 ms -42.96%
basic-performance-scroll-view-100 8.6 ms 8 ms +6.7%

Comparing PupilTong:p/hw/free-lynx-view-instance (9964d7a) with main (7332eb4)

Open in CodSpeed

Footnotes

  1. 21 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@relativeci
Copy link
Copy Markdown

relativeci bot commented Apr 14, 2026

React External

#411 Bundle Size — 582.81KiB (0%).

9964d7a(current) vs 7332eb4 main#401(baseline)

Bundle metrics  no changes
                 Current
#411
     Baseline
#401
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 17 17
No change  Duplicate Modules 5 5
No change  Duplicate Code 8.59% 8.59%
No change  Packages 0 0
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#411
     Baseline
#401
No change  Other 582.81KiB 582.81KiB

Bundle analysis reportBranch PupilTong:p/hw/free-lynx-view-in...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci bot commented Apr 14, 2026

React MTF Example

#426 Bundle Size — 192.85KiB (0%).

9964d7a(current) vs 7332eb4 main#416(baseline)

Bundle metrics  no changes
                 Current
#426
     Baseline
#416
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 173 173
No change  Duplicate Modules 67 67
No change  Duplicate Code 45.36% 45.36%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#426
     Baseline
#416
No change  IMG 111.23KiB 111.23KiB
No change  Other 81.61KiB 81.61KiB

Bundle analysis reportBranch PupilTong:p/hw/free-lynx-view-in...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci bot commented Apr 14, 2026

Web Explorer

#8867 Bundle Size — 749.55KiB (+0.03%).

9964d7a(current) vs 7332eb4 main#8857(baseline)

Bundle metrics  Change 3 changes
                 Current
#8867
     Baseline
#8857
No change  Initial JS 44.45KiB 44.45KiB
No change  Initial CSS 2.16KiB 2.16KiB
Change  Cache Invalidation 9.49% 0%
No change  Chunks 8 8
No change  Assets 10 10
Change  Modules 148(-1.33%) 150
No change  Duplicate Modules 11 11
Change  Duplicate Code 34.99%(-0.03%) 35%
No change  Packages 3 3
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Regression 1 regression
                 Current
#8867
     Baseline
#8857
No change  Other 401.63KiB 401.63KiB
Regression  JS 345.76KiB (+0.07%) 345.51KiB
No change  CSS 2.16KiB 2.16KiB

Bundle analysis reportBranch PupilTong:p/hw/free-lynx-view-in...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci bot commented Apr 14, 2026

React Example

#7293 Bundle Size — 223.58KiB (0%).

9964d7a(current) vs 7332eb4 main#7283(baseline)

Bundle metrics  no changes
                 Current
#7293
     Baseline
#7283
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 179 179
No change  Duplicate Modules 70 70
No change  Duplicate Code 45.76% 45.76%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#7293
     Baseline
#7283
No change  IMG 145.76KiB 145.76KiB
No change  Other 77.82KiB 77.82KiB

Bundle analysis reportBranch PupilTong:p/hw/free-lynx-view-in...Project dashboard


Generated by RelativeCIDocumentationReport issue

…, and WASMJSBinding to ensure proper disposal
@PupilTong PupilTong self-assigned this Apr 14, 2026
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web-platform/web-core/ts/client/mainthread/LynxViewInstance.ts`:
- Around line 300-306: The async disposal currently schedules
mtsWasmBinding.dispose via requestIdleCallbackImpl and returns immediately,
violating the async disposal contract; change async [Symbol.asyncDispose]()
(which calls exposureServices.dispose()) to await a Promise that wraps
requestIdleCallbackImpl so the function only resolves after
mtsWasmBinding.dispose() completes; use the same requestIdleCallbackImpl call
(including its 16ms fallback) but resolve/reject the Promise after calling await
this.mtsWasmBinding.dispose() (or handling any errors) so callers in LynxView.ts
can safely assume resources are fully released when the returned Promise
resolves.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d88492f4-65ef-4828-9ec9-666890afc31b

📥 Commits

Reviewing files that changed from the base of the PR and between 00cc290 and 9964d7a.

📒 Files selected for processing (4)
  • packages/web-platform/web-core/ts/client/mainthread/Background.ts
  • packages/web-platform/web-core/ts/client/mainthread/ExposureServices.ts
  • packages/web-platform/web-core/ts/client/mainthread/LynxViewInstance.ts
  • packages/web-platform/web-core/ts/client/mainthread/elementAPIs/WASMJSBinding.ts

@PupilTong PupilTong force-pushed the p/hw/free-lynx-view-instance branch from aff4635 to 9964d7a Compare April 14, 2026 10:49
@PupilTong PupilTong merged commit 5aa97d8 into lynx-family:main Apr 15, 2026
103 of 108 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants