mirror of
https://github.com/javascript-obfuscator/javascript-obfuscator
synced 2026-08-09 12:42:29 +00:00
Fixed controlFlowFlattening occasionally dropping the ?. short-circuit on foo?.(arg) calls, causing TypeError: <X> is not a function
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
Change Log
|
||||
|
||||
v5.4.3
|
||||
---
|
||||
* Fixed `controlFlowFlattening` occasionally dropping the `?.` short-circuit on `foo?.(arg)` calls, causing `TypeError: <X> is not a function`. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1408
|
||||
|
||||
v5.4.2
|
||||
---
|
||||
* Fixed obfuscated code hanging in Bun when `selfDefending` is enabled. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1404
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "javascript-obfuscator",
|
||||
"version": "5.4.2",
|
||||
"version": "5.4.3",
|
||||
"description": "JavaScript obfuscator",
|
||||
"keywords": [
|
||||
"obfuscator",
|
||||
|
||||
+5
-1
@@ -64,7 +64,11 @@ export class CallExpressionControlFlowReplacer extends AbstractControlFlowReplac
|
||||
|
||||
const isChainExpressionParent = NodeGuards.isChainExpressionNode(parentNode);
|
||||
|
||||
const replacerId: number = callExpressionNode.arguments.length;
|
||||
// Bucket reuse-eligible wrappers by both arg count AND optional-ness, so an
|
||||
// optional `foo?.(arg)` call never reuses the wrapper of a non-optional `bar(arg)`
|
||||
// that happens to share `arguments.length` — which would drop the `?.` short-circuit
|
||||
// and crash on undefined callees (issue #1408).
|
||||
const replacerId: string = `${callExpressionNode.arguments.length}-${isChainExpressionParent ? 'optional' : 'standard'}`;
|
||||
const callExpressionFunctionCustomNode: ICustomNode<TInitialData<CallExpressionFunctionNode>> =
|
||||
this.controlFlowCustomNodeFactory(ControlFlowCustomNode.CallExpressionFunctionNode);
|
||||
const expressionArguments: (ESTree.Expression | ESTree.SpreadElement)[] = callExpressionNode.arguments;
|
||||
|
||||
+28
@@ -248,5 +248,33 @@ describe('CallExpressionControlFlowReplacer', function () {
|
||||
assert.match(obfuscatedCode, controlFlowStorageNodeRegExp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Variant #8 - optional `?.()` call must not reuse a non-optional wrapper (issue #1408)', () => {
|
||||
const samplesCount: number = 200;
|
||||
|
||||
it('should preserve the `?.` short-circuit on every obfuscation when an optional call and a non-optional call share the same arity', () => {
|
||||
const code: string = readFileAsString(
|
||||
__dirname + '/fixtures/issue-1408-mixed-optional-and-plain-calls.js'
|
||||
);
|
||||
|
||||
for (let i = 0; i < samplesCount; i++) {
|
||||
const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(code, {
|
||||
...NO_ADDITIONAL_NODES_PRESET,
|
||||
controlFlowFlattening: true,
|
||||
controlFlowFlatteningThreshold: 1,
|
||||
deadCodeInjection: true,
|
||||
deadCodeInjectionThreshold: 1
|
||||
}).getObfuscatedCode();
|
||||
|
||||
const result: unknown = eval(obfuscatedCode);
|
||||
|
||||
assert.strictEqual(
|
||||
result,
|
||||
'ok',
|
||||
`iteration ${i}: optional ?.() call lost its short-circuit (obfuscated code threw or returned wrong value)`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
(function () {
|
||||
function noise (n) {
|
||||
var acc = 0;
|
||||
for (var i = 0; i < n; i++) {
|
||||
if (i % 2 === 0) {
|
||||
acc += i;
|
||||
} else {
|
||||
acc ^= i;
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
function bump (v) {
|
||||
return v + 1;
|
||||
}
|
||||
|
||||
function outer () {
|
||||
var captured = undefined;
|
||||
var inner = function (msg) {
|
||||
captured?.(msg);
|
||||
return 'ok';
|
||||
};
|
||||
return inner('payload');
|
||||
}
|
||||
|
||||
noise(50);
|
||||
bump(7);
|
||||
noise(25);
|
||||
bump(13);
|
||||
return outer();
|
||||
})();
|
||||
Reference in New Issue
Block a user