GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Identifiers inside functions nested in class field initializers are neither renamed nor preserved (`stringArray` produces `X is not a function`) · Issue #1462 · javascript-obfuscator/javascript-obfuscator

Identifiers inside functions nested in class field initializers are neither renamed nor preserved (stringArray produces X is not a function) #1462

New issue
Copy link
New issue
Copy link
Closed
#1467
Closed
Identifiers inside functions nested in class field initializers are neither renamed nor preserved (stringArray produces X is not a function)#1462
#1467
Copy link
Labels
bug

Description

@NotYoojun
NotYoojun
opened on Oct 7, 2026
Issue body actions

Expected Behavior

Identifiers declared in a function nested inside a class field initializer, such as the parameter b in static staticField = register({ onChange: (b) => { ... } }), are renamed like identifiers in any other function, and the obfuscated code runs correctly. The minimal example below should print foobar.

Current Behavior

Those identifiers are neither renamed nor preserved, so a generated name can take the same name. In the example below, the string array calls wrapper is also named b. The string array scope calls wrapper j inserted into the nested function therefore receives the shadowing parameter instead of the calls wrapper, and the code throws TypeError: j is not a function:

class Foo
{
    static [i(0xc0)] = register
    ({ 
        'onChange': (b) => 
        { 
            var j = b; 
            return b[j(0xb6)]+b[j(0xb5)]; 
        } 
    }); 
}

// ...
function b(c,d){c=c-0xb4;var e=a();var f=e[c];return f;}

The same function written inside a class method instead of a class field initializer has its parameter renamed correctly.

This is easy to hit with minified input: minifiers commonly name the first parameter e, and with mangled or mangled-shuffled identifier names the string array calls wrapper can get the same single-letter name. Our production build crashed this way on code shaped like static someProperty = register({ onChanged: e => { ... } }).

Steps to Reproduce

  1. npm install [email protected]
  2. Save the minimal working example below as repro.js
  3. Run node repro.js

JavaScript Obfuscator Edition

  • JavaScript Obfuscator Open Source

Your Environment

  • Obfuscator version used: 5.9.0 (also reproduced with 5.7.0)
  • Node version used: 24.18.0

Stack trace

TypeError: j is not a function
    at Object.onChange (eval at <anonymous> (repro.js:28:13), <anonymous>:1:758)
    at eval (eval at <anonymous> (repro.js:28:13), <anonymous>:1:804)
    at Object.<anonymous> (repro.js:28:13)
    at Module._compile (node:internal/modules/cjs/loader:1871:14)
    at Object..js (node:internal/modules/cjs/loader:2002:10)
    at Module.load (node:internal/modules/cjs/loader:1594:32)
    at Module._load (node:internal/modules/cjs/loader:1396:12)
    at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
    at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)
    at node:internal/main/run_main_module:33:47

Minimal working example that will help to reproduce issue

const JavaScriptObfuscator = require('javascript-obfuscator');

const code = `
function register (options) {
    return options;
}

class Foo {
    static staticField = register({
        onChange: (b) => {
            return b['value'] + b['suffix'];
        }
    });
}

Foo.staticField.onChange({ value: 'foo', suffix: 'bar' });
`;

const obfuscatedCode = JavaScriptObfuscator.obfuscate(code, {
    identifierNamesGenerator: 'mangled',
    stringArray: true,
    stringArrayThreshold: 1,
    stringArrayWrappersCount: 1,
    stringArrayWrappersChainedCalls: false
}).getObfuscatedCode();

console.log(obfuscatedCode);
console.log(eval(obfuscatedCode));

Cause

ScopeIdentifiersTraverser.traverseScopeIdentifiersRecursive and traverseScopeThroughIdentifiersRecursive return early when currentScope.variableScope.block is not a node with a block lexical scope:

javascript-obfuscator/src/node/ScopeIdentifiersTraverser.ts

Lines 90 to 92 in 67005e2

if (!variableLexicalScopeNode) {
return;
}

eslint-scope 8 creates a class-field-initializer scope for every class field value. That scope is its own variable scope, and its block is the initializer expression itself. When the initializer is not a function, the early return also skips all child scopes, including functions nested inside the initializer. class-static-block scopes are not affected because StaticBlock is accepted by isNodeWithBlockLexicalScope.

I have a fix with tests ready and will open a PR referencing this issue.

Workaround

identifiersPrefix avoids the collision with the string array calls wrapper because generated global names get the prefix, but the identifiers inside the initializer are still not renamed, and generated local names can still collide with them.

Activity

  1. sanex3339
    added
    bug
    on Oct 13, 2026
  2. sanex3339
    closed this as completedin #1467on Nov 1, 2026
  3. sanex3339 commented on Nov 2, 2026

    @sanex3339
    sanex3339
    on Nov 2, 2026
    Member
    More actions

    Released as 5.9.1

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402520 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.