Skip to content

Warn when user code reads Module['ctx'] in ASSERTIONS builds - #28178

Open
rosalbito wants to merge 1 commit into
emscripten-core:mainfrom
rosalbito:module-ctx-deprecation-warning
Open

rosalbito wants to merge 1 commit into
emscripten-core:mainfrom
rosalbito:module-ctx-deprecation-warning

Conversation

@rosalbito

Copy link
Copy Markdown
Contributor

This is the nudge discussed in #28081, and the warning step on that issue's deprecation checklist. Module['ctx'] keeps working, but reading it from user code now warns once in ASSERTIONS builds:

`Module.ctx` is deprecated. Use `GLctx` in JS libraries, EM_ASM and EM_JS code (see https://github.com/emscripten-core/emscripten/issues/28081)

It removes nothing, so it can land before #28079 or instead of it. If #28081 goes ahead with the removal, #28079 can follow once the deprecation period is over. It uses the getter/setter suggested in the survey on #28081.

Questions for @sbc100 and @kripken:

  1. Is a warning on its own the nudge you had in mind, or would you rather land it together with Remove undocumented Module['ctx'] property #28079?
  2. Deprecation proposal for Module['ctx'] #28081 hasn't settled on removal. Is "deprecated" the right word for the warning, or should it be softer until that's decided?
  3. The write costs 6 to 17 bytes in release builds (GL and GLFW tests, measured against Remove undocumented Module['ctx'] property #28079). If you want those bytes back sooner, emcc already scans EM_ASM, EM_JS, --pre-js and JS libraries at link time, so it could keep the write only for programs that mention Module.ctx. JS on the page itself wouldn't be seen, so those programs would get undefined in release builds. Is that worth adding, or is it too much compiler logic for so few bytes? I haven't built it, but I can as a follow-up.
  • Release builds don't change: the hook and its reads exist only under ASSERTIONS. .js and .wasm are byte-identical to main for GL, GLFW, SDL1 and main loop builds at -O2, and with --closure 1, -sMODULARIZE, -pthread and -sMINIMAL_RUNTIME on the GL build (--closure 1 on GLFW too).
  • Writes stay silent: the runtime writes this property, and so do some projects, so only reads warn.
  • The runtime's own reads don't warn: Browser.createContext, glfwCreateWindow and the main loop's requestAnimationFrame check read it through readModuleCtx.
  • Those reads stay on Module['ctx']: Remove undocumented Module['ctx'] property #28079 moves them to Browser.ctx, but Defold's delete Module.ctx relies on resetting the context cached in Browser.createContext. A private field breaks that, and the new test fails if I add one.
  • The hook is linked only with GL or Browser: other builds pay only for the main loop check.
-O2 -sASSERTIONS main This PR
GL test, .js bytes / gzip 38351 / 12346 38711 / 12488
GLFW test, .js bytes / gzip 122727 / 35823 123251 / 36025
main loop, no GL, .js bytes / gzip 36401 / 11845 36439 / 11857

Limits:

  • A project that assigns its own Module.ctx, such as the 2D context or AudioContext users in the survey, also gets the warning if it links GL or Browser. The setter can't tell who wrote the value.
  • delete Module.ctx removes the hook, so later reads don't warn. That includes the runtime's own delete in glfwDestroyWindow and glutDestroyWindow.
  • src/cpuprofiler.js still reads Module['ctx'] as a fallback when GLctx doesn't exist, so a --cpuprofiler build with Browser but no GL can warn once. Remove undocumented Module['ctx'] property #28079 removes that fallback.

Tests: the new browser.test_module_ctx_deprecated runs with and without Closure on Chrome 151 headless, and it fails on main. It checks that creating a GLFW window doesn't warn, that a read warns once and returns the live context, that GLctx works from EM_ASM, and that deleting the property still resets the context.

I ran 185 browser tests (GLFW, GLUT, WebGL, SDL, OpenAL, main loop) on the same Chrome: 175 passed, 9 skipped, and test_html5_webgl_api failed on its desynchronized assertion, as it does on main. 42 other tests covering code size, JS libraries and assertions passed, with no codesize changes needed. I also checked -pthread and a context proxied from PROXY_TO_PTHREAD in Chrome, and that the main loop's requestAnimationFrame warning still fires.

Not run: the full test suite, Firefox, Safari, and -sMINIMAL_RUNTIME in a browser (the test harness fails there on main too, so that build only ran under Node with a fake context).

AI-generated with Claude, reviewed by me before posting.

This is the warning step from the deprecation checklist in emscripten-core#28081.
Module['ctx'] keeps working, and release builds don't change.

The runtime's own reads go through readModuleCtx so that they don't warn.
They stay on Module['ctx'] because code such as Defold deletes it to reset
the context cached by Browser.createContext.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rosalbito
rosalbito marked this pull request as ready for review November 5, 2026 13:21

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request deprecates the undocumented Module['ctx'] property, triggering a warning in ASSERTIONS builds when read by user code while keeping it functional. It introduces internal helpers and hooks to distinguish between user-space and runtime reads of the context, and adds corresponding tests. The feedback recommends explicitly declaring $hookModuleCtx as a dependency of $readModuleCtx to ensure correct dependency resolution by the compiler.

Comment thread src/lib/libcore.js
Comment on lines +1525 to +1526
$readModuleCtx__internal: true,
$readModuleCtx: () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To ensure robust dependency resolution and prevent potential issues with the compiler's automatic dependency scanner, it is highly recommended to explicitly declare $hookModuleCtx as a dependency of $readModuleCtx using __deps.

  $readModuleCtx__deps: ['$hookModuleCtx'],
  $readModuleCtx__internal: true,
  $readModuleCtx: () => {

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.

1 participant