Skip to content

Fix -commonjs compilation crash and empty output (#1990) - #2117

Open
tangtaizong666 wants to merge 1 commit into
facebook:static_hfrom
tangtaizong666:fix-cjs-module-irgen-crash
Open

Fix -commonjs compilation crash and empty output (#1990)#2117
tangtaizong666 wants to merge 1 commit into
facebook:static_hfrom
tangtaizong666:fix-cjs-module-irgen-crash

Conversation

@tangtaizong666

Copy link
Copy Markdown

Summary

Fixes #1990.

hermes -commonjs segfaulted on any input. The crash is a null dereference in genBasicFunction: its enqueued compilation lambda captures curFunction()->typedClassContext / legacyClassContext at enqueue time, and doCJSModule was the only genBasicFunction entry point that did not establish an active FunctionContext first (doIt, doItInScope and doLazyFunction all do). In a release build curFunction() returns the null functionContext_, which matches the ASan trace in the issue (ESTreeIRGen-func.cpp:390:43, READ at offset 0x148). The fix creates a top-level FunctionContext, mirroring doLazyFunction.

With the crash fixed, -commonjs still silently produced empty bytecode (CommonJS module count: 0, nothing executed) at the default optimization level, because two optimizer passes do not treat CJS module functions as roots — they have no IR users, they are only referenced from the module's CJS module table and called by the runtime's require machinery:

  • deleteUnusedFunctionsAndVariables deleted them as unused (on main, DCE explicitly skips M->findCJSModule(&F); that check was lost in this helper on static_h).
  • analyzeFunctionCallsites marked them allCallsitesKnownInStrictMode/unreachable and their bodies were gutted, producing a segfault at -O; only the global function was exempted as "called by the runtime".

FuncSigOpts already special-cases CJS module functions (F.isGlobalScope() || M->findCJSModule(&F)), so this brings the other two passes in line with it. These are the only two isGlobalScope() special cases in lib/Optimizer without the CJS check.

Test Plan

New regression test test/hermes/cjs-module.js compiles and runs a CommonJS module at the default optimization level, -O0 and -O. TDD verified against each fix:

  • Unpatched: hermes -commonjs test.jsSegmentation fault (exit 139) at any opt level.
  • With only the IRGen fix: -O0 runs correctly, but default/-O produce no output (module deleted as unused), and after also fixing deleteUnusedFunctionsAndVariables, -O segfaults on the gutted unreachable body — each optimizer fix addresses an observed failure mode.
  • With all three fixes:
$ hermes -commonjs test/hermes/cjs-module.js     # same with -O0 / -O
cjs module start
object function object
42

Multi-module require() round trip (also with -O and -O -fstatic-require):

$ hermes -commonjs main.js dep.js
hello world
$ hermes -commonjs -dump-bytecode main.js dep.js | grep 'CommonJS module count'
  CommonJS module count: 2

lit suites for the affected components (test/hermes, test/IRGen, test/Optimizer, test/BCGen, test/Sema, test/AST, test/Parser, release Linux build): 1046 passes, 0 failures caused by this change (the 640 pre-existing failures in my environment are all missing shermes/hbcdump binaries, verified by grepping every failure log).

Code formatted with clang-format (no diff).

Compiling anything with -commonjs crashed with a null dereference:
doCJSModule ran genBasicFunction without an active FunctionContext.
The enqueued compilation lambda in genBasicFunction captures
curFunction()->typedClassContext / legacyClassContext at enqueue time,
and doCJSModule was the only genBasicFunction entry point that did not
establish a context first. Create a top-level FunctionContext,
mirroring doLazyFunction.

With the crash fixed, CJS modules were still compiled to empty
bytecode (CommonJS module count: 0) at the default optimization level,
because the optimizer does not treat CJS module functions as roots:

- deleteUnusedFunctionsAndVariables removed them: they have no IR
  users, as they are only referenced from the module's CJS module
  table.
- analyzeFunctionCallsites marked them
  allCallsitesKnownInStrictMode/unreachable, gutting their bodies,
  since only the global function was exempted as "called by the
  runtime". FuncSigOpts already handles CJS module functions, so this
  brings the other two passes in line with it.

Add a regression test that compiles and runs a CommonJS module at the
default optimization level, -O0, and -O.

Fixes facebook#1990
@meta-cla meta-cla Bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Jul 26, 2026
@tmikov

tmikov commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Hmm. CommonJS isn't supported or maintained, it is just we haven't deleted it yet. But this is a small change that fixes something that does exist. We will discuss whether we want to take it. Thanks!

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

Labels

CLA Signed Do not delete this pull request or issue due to inactivity.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--commonjs option is broken

2 participants