Skip to content

minifier: Annex B block-function alias invalidates cached var facts #24659

Description

@Dunqing

In sloppy JavaScript, an Annex B block-function declaration can assign its function object to an existing same-name var binding when the block executes. The compressor does not see that runtime alias write when semantic keeps the block function and the existing var as separate symbols, so it can inline a stale value and change program behavior.

Reproduction

function outer(c) {
  var f = 1;
  if (c) {
    function f() {}
  }
  return () => f;
}

console.log(typeof outer(true)(), typeof outer(false)());

The original prints:

function number

Compress-only minification on current main (cf628ac433) produces:

function outer(c){if(c){function f(){}}return()=>1}

The minified program prints:

number number

The same divergence remains after #24650 and #24658. On that stack, running compression twice also changes the output again by removing the now-unused block, although both compressed outputs are already behaviorally wrong.

Cause

When the enclosing var scope already contains f, the Annex B binder path does not move the block function into that scope. The outer var f and block function f therefore have separate symbols, and symbol_redeclarations for the outer symbol is empty. At runtime, Annex B still copies the function object into the outer binding, but the compressor's symbol facts see no write and treat f = 1 as constant.

This is related to #24331, which demonstrates the same var-first Annex B alias blind spot in the mangler, but the failure here happens during compression without mangling. #20728 fixed the general hoisting model; this existing-binding edge remains.

The fix likely needs semantic metadata that records the runtime alias even when the symbols stay separate, or an equivalent conservative gate in consumers of symbol value facts.

Reported with AI assistance (Claude Code and OpenAI Codex). The behavior and Node output were independently reproduced on current main and the #24658 stack before filing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions