Skip to content

Fix leak when a module is linked again after a failed link - #1846

Open
wleeaf wants to merge 1 commit into
quickjs-ng:masterfrom
wleeaf:fix-module-link-failure-leak
Open

wleeaf wants to merge 1 commit into
quickjs-ng:masterfrom
wleeaf:fix-module-link-failure-leak

Conversation

@wleeaf

@wleeaf wleeaf commented Oct 24, 2026

Copy link
Copy Markdown

When linking fails, js_link_module() resets the modules left on the DFS stack to unlinked, but keeps the variable references the partial link took: the import slots of the module function (a counted reference to the exported variable, or a new variable for a namespace re-export) and the var_ref of each local export. The next import of the same module links it again and overwrites those pointers without releasing the old references. They leak in release builds, and debug builds fail the list_empty(&rt->gc_obj_list) assertion in JS_FreeRuntime().

This change releases and clears those references when a module is reset after a failed link, so the module goes back to the state it had before linking. Namespace imports are variables of the module itself and are kept, as are the exports of C modules, which linking does not create. The cleared slots are the same ones that js_bytecode_function_finalizer() and js_free_module_def() release later, so nothing is freed twice.

tests/bug1833.js imports a two-module cycle that fails to link, twice. It covers an import slot, a local export and a namespace re-export (export * as ns from). Without the fix, the test aborts on the assertion in a Debug build (three leaked var_refs) and LeakSanitizer reports a leak under ASan. With the fix, the test and both reproductions from the issue pass in Debug, ASan+UBSan and GC stress builds. The rest of make test, and the test262 module-code, dynamic-import and import directories under ASan+UBSan, show no new failures.

When linking fails, js_link_module() resets the modules left on the DFS
stack to unlinked but keeps the variable references the partial link
took: the import slots of the module function and the var_ref of each
local export. The next import of the same module links it again and
overwrites them without releasing the old references, so they leak in
release builds and JS_FreeRuntime() fails the `list_empty(&rt->gc_obj_list)`
assertion in debug builds.

Release and clear those references when a module is reset, so it goes
back to the state it had before linking. Namespace imports are variables
of the module itself and are kept, as are the exports of C modules,
which linking does not create.

Fixes: quickjs-ng#1833
Copilot AI balanced review requested due to automatic review settings October 24, 2026 13:51

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

This branch has not been deployed

No deployments
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.

2 participants