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 — controlFlowFlattening evaluates the skipped operand of `&&` / `||` / `??` (LogicalExpressionControlFlowReplacer) · Issue #1456 · javascript-obfuscator/javascript-obfuscator

controlFlowFlattening evaluates the skipped operand of && / || / ?? (LogicalExpressionControlFlowReplacer) #1456

New issue
Copy link
New issue
Copy link
Closed
#1459
Closed
controlFlowFlattening evaluates the skipped operand of && / || / ?? (LogicalExpressionControlFlowReplacer)#1456
#1459
Copy link
Labels
bug

Description

@righteousgambit
righteousgambit
opened on Sep 29, 2026
Issue body actions

Expected Behavior

Obfuscated code keeps short-circuit evaluation: in a && b the right operand is not evaluated when a is falsy (and likewise for || and ??).

Current Behavior

With controlFlowFlattening: true, inside a block-bodied function, LogicalExpressionControlFlowReplacer rewrites a && b into a call to a control-flow storage helper, S.fn(a, b), whenever both operands are a literal, an identifier or an object literal (optionally under unary operators) and the right operand contains no call and no computed member expression. Call arguments are evaluated before the call, so the right operand now always runs:

  • img && { width: img.width } throws TypeError when img is null
  • ready && late throws ReferenceError while late (a let) is in its temporal dead zone, or when late is an undeclared global
  • seen || { seen, n: ++count } increments on every call; getters and valueOf run

Each eligible site is replaced with probability controlFlowFlatteningThreshold. Without a seed, which sites break changes from run to run.

checkForProhibitedExpressions treats identifiers and object literals as side-effect free, but evaluating them early can throw or have effects. The helper would need to take the right operand lazily (for example S.fn(a, () => b)), or the replacer would need to be limited to operands that are safe to evaluate early (literals only).

Steps to Reproduce

// node repro.js
const JavaScriptObfuscator = require('javascript-obfuscator');
const code = `
function size(img) { return img && { width: img.width }; }
function pick(ready) { return ready && late; }
var count = 0;
function next(seen) { return seen || { seen: seen, n: ++count }; }
function report(name, fn) { try { console.log(name, JSON.stringify(fn())); } catch (e) { console.log(name, 'throws', e.constructor.name + ': ' + e.message); } }
report('size(null)  ', function () { return size(null); });
report('pick(false) ', function () { return pick(false); });
report('next(true)  ', function () { next(true); return count; });
let late = 1;
`;
const options = { controlFlowFlattening: true, controlFlowFlatteningThreshold: 1, seed: 1 };
console.log('--- plain');
new Function(code)();
console.log('--- obfuscated');
new Function(JavaScriptObfuscator.obfuscate(code, options).getObfuscatedCode())();

Output (5.5.0 and 5.8.0):

--- plain
size(null)   null
pick(false)  false
next(true)   0
--- obfuscated
size(null)   throws TypeError: Cannot read properties of null (reading 'width')
pick(false)  throws ReferenceError: Cannot access 'late' before initialization
next(true)   1

size after obfuscation (stringArray: false, compact: false):

function size(_0x3f434e) {
    var _0x2c7393 = {
        'PSBht': function (_0x26868c, _0x4eb5be) {
            return _0x26868c && _0x4eb5be;
        }
    };
    return _0x2c7393['PSBht'](_0x3f434e, { 'width': _0x3f434e['width'] });
}

The same happens in methods, getters, class field initialisers inside functions, async functions, generators and block-bodied arrows. Expression-bodied arrows, parameter defaults and top-level code are not affected (FunctionControlFlowTransformer only transforms block bodies).

JavaScript Obfuscator Edition

  • JavaScript Obfuscator Open Source (npm javascript-obfuscator)

Your Environment

  • Obfuscator version used: 5.5.0, 5.8.0
  • Node version used: 25.4.0

Workaround

Before obfuscating, rewrite each such right operand as (0, b). The replacer declines sequence expressions.

Activity

  1. sanex3339
    added
    bug
    on Sep 30, 2026
  2. sanex3339 commented on Sep 30, 2026

    @sanex3339
    sanex3339
    on Sep 30, 2026
    Member
    More actions

    Will be released later today as 5.8.1

  3. sanex3339
    closed this as completedon Sep 30, 2026
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 01402503 — 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.