fix: avoid use-after-free for rust instance#2461
Conversation
…etaching event listeners during component teardown
🦋 Changeset detectedLatest commit: 9964d7a The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
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 |
📝 WalkthroughWalkthroughChanged 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Signed-off-by: Haoyang Wang <12288479+PupilTong@users.noreply.github.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
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.tsto 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
📒 Files selected for processing (3)
.changeset/tidy-clubs-wink.mdpackages/web-platform/web-core/ts/client/mainthread/LynxViewInstance.tspackages/web-platform/web-core/ts/client/mainthread/elementAPIs/createElementAPI.ts
Merging this PR will degrade performance by 42.96%
Performance Changes
Comparing Footnotes
|
React External#411 Bundle Size — 582.81KiB (0%).9964d7a(current) vs 7332eb4 main#401(baseline) Bundle metrics
|
| Current #411 |
Baseline #401 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
3 |
3 |
|
17 |
17 |
|
5 |
5 |
|
8.59% |
8.59% |
|
0 |
0 |
|
0 |
0 |
Bundle analysis report Branch PupilTong:p/hw/free-lynx-view-in... Project dashboard
Generated by RelativeCI Documentation Report issue
React MTF Example#426 Bundle Size — 192.85KiB (0%).9964d7a(current) vs 7332eb4 main#416(baseline) Bundle metrics
|
| Current #426 |
Baseline #416 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
3 |
3 |
|
173 |
173 |
|
67 |
67 |
|
45.36% |
45.36% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #426 |
Baseline #416 |
|
|---|---|---|
111.23KiB |
111.23KiB |
|
81.61KiB |
81.61KiB |
Bundle analysis report Branch PupilTong:p/hw/free-lynx-view-in... Project dashboard
Generated by RelativeCI Documentation Report issue
Web Explorer#8867 Bundle Size — 749.55KiB (+0.03%).9964d7a(current) vs 7332eb4 main#8857(baseline) Bundle metrics
Bundle size by type
Bundle analysis report Branch PupilTong:p/hw/free-lynx-view-in... Project dashboard Generated by RelativeCI Documentation Report issue |
React Example#7293 Bundle Size — 223.58KiB (0%).9964d7a(current) vs 7332eb4 main#7283(baseline) Bundle metrics
|
| Current #7293 |
Baseline #7283 |
|
|---|---|---|
0B |
0B |
|
0B |
0B |
|
0% |
0% |
|
0 |
0 |
|
4 |
4 |
|
179 |
179 |
|
70 |
70 |
|
45.76% |
45.76% |
|
2 |
2 |
|
0 |
0 |
Bundle size by type no changes
| Current #7293 |
Baseline #7283 |
|
|---|---|---|
145.76KiB |
145.76KiB |
|
77.82KiB |
77.82KiB |
Bundle analysis report Branch PupilTong:p/hw/free-lynx-view-in... Project dashboard
Generated by RelativeCI Documentation Report issue
…, and WASMJSBinding to ensure proper disposal
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
packages/web-platform/web-core/ts/client/mainthread/Background.tspackages/web-platform/web-core/ts/client/mainthread/ExposureServices.tspackages/web-platform/web-core/ts/client/mainthread/LynxViewInstance.tspackages/web-platform/web-core/ts/client/mainthread/elementAPIs/WASMJSBinding.ts
aff4635 to
9964d7a
Compare
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @lynx-js/react@0.119.0 ### Minor Changes - Simplify hooks for main-thread runtime, which only can run during the first screen. ([#2441](#2441)) ### Patch Changes - Remove stale gestures when gestures are removed ([#2297](#2297)) - Trace refactor ([#2466](#2466)) - Remove `ReactLynx::renderOpcodes` from the trace - Use `ReactLynx::transferRoot` to measure the time spent transferring the root to the background thread - refactor: set state of suspense to render fallback ([#2450](#2450)) - Support rstest for testing library, you can use rstest with RLTL now: ([#2328](#2328)) Create a config file `rstest.config.ts` with the following content: ```ts import { defineConfig } from "@rstest/core"; import { withLynxConfig } from "@lynx-js/react/testing-library/rstest-config"; export default defineConfig({ extends: withLynxConfig(), }); ``` `@lynx-js/react/testing-library/rstest-config` will automatically load your `lynx.config.ts` and apply the same configuration to rstest, so you can keep your test environment consistent with your development environment. And then use rstest as usual: ```bash $ rstest ``` For more usage detail, see <https://rstest.rs/> - Update preact version ([#2456](#2456)) - Add `nodeIndex` to generated FiberElement creation calls and expose React transform debug metadata as `uiSourceMapRecords`. ([#2402](#2402)) ## @lynx-js/react-rsbuild-plugin@0.16.0 ### Minor Changes - Simplify hooks for main-thread runtime, which only can run during the first screen. ([#2441](#2441)) ### Patch Changes - Support rstest for testing library using a dedicated testing loader. ([#2328](#2328)) - Fix `environments.lynx.performance.profile` so it overrides the default profile behavior for the current environment. ([#2468](#2468)) - Add `enableUiSourceMap` option to enable UI source map generation and debug-metadata asset emission. ([#2402](#2402)) - Updated dependencies \[[`a9f8d05`](a9f8d05), [`b1ad1b9`](b1ad1b9), [`f6184f3`](f6184f3), [`f6184f3`](f6184f3), [`a9f8d05`](a9f8d05), [`f542d9c`](f542d9c)]: - @lynx-js/template-webpack-plugin@0.10.9 - @lynx-js/react-webpack-plugin@0.9.1 - @lynx-js/react-alias-rsbuild-plugin@0.16.0 - @lynx-js/css-extract-webpack-plugin@0.7.0 - @lynx-js/react-refresh-webpack-plugin@0.3.5 - @lynx-js/use-sync-external-store@1.5.0 ## @lynx-js/react-alias-rsbuild-plugin@0.16.0 ### Minor Changes - Simplify hooks for main-thread runtime, which only can run during the first screen. ([#2441](#2441)) ### Patch Changes - fix(rstest): add global fallback aliases for `@lynx-js/react/jsx-runtime` and `@lynx-js/react/jsx-dev-runtime` ([#2464](#2464)) `pluginReactAlias` only aliased these entries inside layer-specific rules (`issuerLayer: BACKGROUND/MAIN_THREAD`). In rstest mode there are no layers, so JSX transformed by the testing loader—which emits `import { jsx } from '@lynx-js/react/jsx-runtime'`—could not be resolved, causing a `Cannot find module '@lynx-js/react/jsx-runtime'` error. Added global (non-layer-specific) fallback aliases pointing to the background jsx-runtime. ## @lynx-js/testing-environment@0.2.0 ### Minor Changes - **BREAKING CHANGE**: ([#2328](#2328)) Align the public test-environment API around `LynxEnv`. `LynxTestingEnv` now expects a `{ window }`-shaped environment instead of relying on a concrete `JSDOM` instance or `global.jsdom`. Callers that construct `LynxTestingEnv` manually or initialize the environment through globals should migrate to `new LynxTestingEnv({ window })` or set `global.lynxEnv`. This release also adds the `@lynx-js/testing-environment/env/rstest` entry for running the shared testing-environment suite under rstest. ### Patch Changes - Add `__RemoveGestureDetector` PAPI binding ([#2297](#2297)) ## @lynx-js/rspeedy@0.14.2 ### Patch Changes - Updated dependencies \[]: - @lynx-js/web-rsbuild-server-middleware@0.20.2 ## create-rspeedy@0.14.2 ### Patch Changes - Add Rstest ReactLynx Testing Library template. ([#2328](#2328)) ## @lynx-js/external-bundle-rsbuild-plugin@0.1.1 ### Patch Changes - Updated dependencies \[[`3262ca8`](3262ca8)]: - @lynx-js/externals-loading-webpack-plugin@0.1.1 ## @lynx-js/web-core@0.20.2 ### Patch Changes - fix: map clientX and clientY to x and y in touch event detail ([#2458](#2458)) - fix(web-platform): completely detach event listeners and forcefully free `MainThreadWasmContext` pointer alongside strict FIFO async component disposal to ensure total memory reclamation without use-after-free risks ([#2457](#2457)) - refactor: with WeakRef in element APIs and WASM bindings to improve memory management. ([#2439](#2439)) - fix: preserve CSS variable fallback values when encoding web-core stylesheets so declarations like `var(--token, rgba(...))` are emitted with their fallback intact. ([#2460](#2460)) - fix: avoid to do use-after-free for rust instance ([#2461](#2461)) - fix: Change uniqueId to uid in LynxCrossThreadEventTarget ([#2467](#2467)) - Updated dependencies \[]: - @lynx-js/web-worker-rpc@0.20.2 ## @lynx-js/externals-loading-webpack-plugin@0.1.1 ### Patch Changes - fix: deduplicate `loadScript` calls for externals sharing the same (bundle, section) pair ([#2465](#2465)) When multiple externals had different `libraryName` values but pointed to the same bundle URL and section path, `createLoadExternalSync`/`createLoadExternalAsync` was called once per external, causing `lynx.loadScript` to execute redundantly for the same section. Now only the first external in each `(url, sectionPath)` group triggers the load; subsequent externals in the group are assigned the already-loaded result directly. ## @lynx-js/react-webpack-plugin@0.9.1 ### Patch Changes - Support rstest for testing library using a dedicated testing loader. ([#2328](#2328)) - fix(rstest): normalize partial `compat` options in the testing loader ([#2464](#2464)) The testing loader forwards `compat` directly to `transformReactLynxSync` without normalization. When `compat` is supplied as a partial object, the required `target` field is absent and the WASM transform throws `Error: Missing field 'target'`. Added the same normalization already present in `getCommonOptions` for the background/main-thread loaders: fills in `target: 'MIXED'` and all other required fields with sensible defaults. - Add `enableUiSourceMap` option to enable UI source map generation and debug-metadata asset emission. ([#2402](#2402)) ## @lynx-js/template-webpack-plugin@0.10.9 ### Patch Changes - Introduce `LynxDebugMetadataPlugin` to emit debug-metadata assets. ([#2402](#2402)) - Updated dependencies \[[`24c4c69`](24c4c69), [`7332eb4`](7332eb4), [`fd0cc6e`](fd0cc6e), [`e5b0f66`](e5b0f66), [`5aa97d8`](5aa97d8), [`5c39654`](5c39654)]: - @lynx-js/web-core@0.20.2 ## @lynx-js/react-umd@0.119.0 ## upgrade-rspeedy@0.14.2 ## @lynx-js/web-rsbuild-server-middleware@0.20.2 ## @lynx-js/web-worker-rpc@0.20.2 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary by CodeRabbit
Checklist