Skip to content

Fix NPE in InlineObjectLiterals when block-scoped function shadows hoisted var (fixes #4200) - #4333

Open
itzabhishekgour wants to merge 1 commit into
google:masterfrom
itzabhishekgour:fix/issue-4200-npe
Open

Fix NPE in InlineObjectLiterals when block-scoped function shadows hoisted var (fixes #4200)#4333
itzabhishekgour wants to merge 1 commit into
google:masterfrom
itzabhishekgour:fix/issue-4200-npe

Conversation

@itzabhishekgour

Copy link
Copy Markdown

Summary

Fixes #4200

InlineObjectLiterals.InliningBehavior.afterExitScope dereferences the ReferenceCollection returned by referenceMap.getReferences(v) without checking for null:

// Before (bug)
ReferenceCollection referenceInfo = referenceMap.getReferences(v);
if (isInlinableObject(referenceInfo.references)) {

// After (fix)
ReferenceCollection referenceInfo = referenceMap.getReferences(v);
if (referenceInfo == null) {
  continue;
}
if (isInlinableObject(referenceInfo.references)) {

Root Cause

t.getScope().getVarIterable() returns every Var registered by SyntacticScopeCreator for a scope, but referenceMap only contains an entry for a Var if at least one NAME reference to it was actually visited during traversal.

When a block-scoped function declaration shadows a hoisted var of the same name — e.g. var x; function x() {} inside the same non-hoist block — the two declarations are registered as separate Var objects in separate scopes. Every NAME "x" encountered inside the block resolves to the closer (block-scope) binding, so the hoist-scope Var never gets a matching reference collected via addReference(). referenceMap.getReferences(v) then returns null for it, and the subsequent .references field access throws the NPE.

InlineVariables already guards against exactly this case (InlineVariables.java:687); InlineObjectLiterals was missing the equivalent check.

Reachability

This precondition — an AST holding this shadowing pattern un-normalized — does not occur in the standard Compiler.compile() pipeline, since Normalize (via MakeDeclaredNamesUnique) always runs before InlineObjectLiterals and eliminates this kind of shadowing beforehand. The original crash was found by a JQF fuzzer harness exercising this pass directly on a non-normalized AST. This fix is defense-in-depth: it makes InlineObjectLiterals robust against a violated precondition rather than closing a crash reachable through normal compilation.

Verification

  • Added testNoCrash_issue4200_blockScopedFunctionDeclarationShadowsHoistedVar to InlineObjectLiteralsTest.java, using disableNormalize() to hold the AST in the vulnerable (non-normalized) state — this shadowing pattern is otherwise eliminated by Normalize before this pass ever runs.
  • Red/green verified locally: with the fix removed, the new test reproduces the exact reported NullPointerException; with the fix restored, it passes.
  • Full InlineObjectLiteralsTest suite passes with no regressions.
  • Build: bazel test //:test/com/google/javascript/jscomp/InlineObjectLiteralsTest

@google-cla

google-cla Bot commented Aug 14, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@itzabhishekgour

Copy link
Copy Markdown
Author

@google-cla check

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.

ICE: Cannot read field "references" because "referenceInfo" is null

1 participant