Repository navigation
Fixed controlFlowFlattening dropping the ?. short-circuit on optional calls in the middle of a chain - #1479
Merged
sanex3339 merged 2 commits intoNov 11, 2026
Conversation
…onal calls in the middle of a chain `foo?.(x).bar()` short-circuits the whole chain when `foo` is nullish. The replacer decided whether a call was optional by checking if its parent is a `ChainExpression`, which is only true for the last call in the chain. In `foo?.(x).bar()` the call's parent is a `MemberExpression`, so it was hoisted into a regular wrapper without `?.` and threw `TypeError: <X> is not a function` at runtime. Use the `optional` flag on the call node itself, and skip the replacement when the optional call is not the chain tail. A wrapper cannot preserve the semantics on its own: it would return `undefined` to the following member access instead of short-circuiting the whole chain. Follow-up to javascript-obfuscator#1408
# Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
controlFlowFlatteningdrops the?.short-circuit when an optional call is followed by a member access:With
controlFlowFlattening: truethe call is hoisted into an unguarded wrapper and throwsTypeError: _0x... is not a function.This is the gap left by #1409. The replacer treats a call as optional when its parent is a
ChainExpression, but that only covers the last call of the chain. Infn?.(1).map(...)the call's parent is aMemberExpression, so it is hoisted into a regular wrapper without?.. The call node itself hasoptional: true, so this PR uses that flag and leaves the call in place when it is not the chain tail. Hoisting it into a wrapper cannot work here: the wrapper would returnundefinedto.map(), while the original expression short-circuits the whole chain.The tradeoff is that such calls are no longer flattened. I did not find a way to hoist them without changing the chain semantics.
Added a regression test that obfuscates and evaluates the shape with a nullish callee. It fails on master with
TypeError: _0x... is not a functionand passes with the fix. Verified withyarn buildandyarn test:mocha(2119 passing).Follow-up to #1408.