Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Change Log

v5.2.1
---
* Fixed `transformObjectKeys` incorrectly hoisting object literal outside of loop when loop body is a single statement without braces, causing all iterations to share the same object reference. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1300

v5.2.0
---
* Skip obfuscation of `process.env.*`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "javascript-obfuscator",
"version": "5.2.0",
"version": "5.2.1",
"description": "JavaScript obfuscator",
"keywords": [
"obfuscator",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
ObjectExpressionKeysTransformer.isProhibitedSequenceExpression(
objectExpressionNode,
objectExpressionHostStatement
)
) ||
ObjectExpressionKeysTransformer.isProhibitedLoopBody(objectExpressionNode)
) {
return true;
}
Expand All @@ -140,6 +141,39 @@ export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
return hasReferencedIdentifier || hasCallExpression;
}

/**
* @param {ObjectExpression} objectExpressionNode
* @returns {boolean}
*/
private static isProhibitedLoopBody(objectExpressionNode: ESTree.ObjectExpression): boolean {
let currentNode: ESTree.Node | undefined = objectExpressionNode;

while (currentNode) {
const parentNode: ESTree.Node | undefined = currentNode.parentNode;

if (!parentNode || parentNode === currentNode) {
break;
}

const isNonBlockLoopBody: boolean =
NodeGuards.isLoopStatementNode(parentNode) &&
parentNode.body === currentNode &&
!NodeGuards.isBlockStatementNode(currentNode);

if (isNonBlockLoopBody) {
return true;
}

if (NodeGuards.isFunctionNode(parentNode) || NodeGuards.isProgramNode(parentNode)) {
break;
}

currentNode = parentNode;
}

return false;
}

/**
* @param {ObjectExpression} objectExpressionNode
* @param {Node} objectExpressionNodeParentNode
Expand Down
21 changes: 21 additions & 0 deletions src/node/NodeGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,27 @@ export class NodeGuards {
return node.type === NodeType.LogicalExpression;
}

/**
* @param {Node} node
* @returns {boolean}
*/
public static isLoopStatementNode(
node: ESTree.Node
): node is
| ESTree.ForStatement
| ESTree.ForInStatement
| ESTree.ForOfStatement
| ESTree.WhileStatement
| ESTree.DoWhileStatement {
return (
NodeGuards.isForStatementNode(node) ||
NodeGuards.isForInStatementNode(node) ||
NodeGuards.isForOfStatementNode(node) ||
NodeGuards.isWhileStatementNode(node) ||
NodeGuards.isDoWhileStatementNode(node)
);
}

/**
* @param {Node} node
* @returns {boolean}
Expand Down
9 changes: 9 additions & 0 deletions test/functional-tests/issues/fixtures/issue1300-do-while.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(function() {
let arr = [];
let i = 0;
do
arr.push({value: 0});
while (++i < 3);
arr[0].value = 1;
return arr[0] === arr[1];
})();
8 changes: 8 additions & 0 deletions test/functional-tests/issues/fixtures/issue1300-for-in.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(function() {
let arr = [];
let obj = {a: 1, b: 2, c: 3};
for (let key in obj)
arr.push({value: 0});
arr[0].value = 1;
return arr[0] === arr[1];
})();
7 changes: 7 additions & 0 deletions test/functional-tests/issues/fixtures/issue1300-for-of.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(function() {
let arr = [];
for (let x of [1, 2, 3])
arr.push({value: 0});
arr[0].value = 1;
return arr[0] === arr[1];
})();
8 changes: 8 additions & 0 deletions test/functional-tests/issues/fixtures/issue1300-while.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(function() {
let arr = [];
let i = 0;
while (i++ < 3)
arr.push({value: 0});
arr[0].value = 1;
return arr[0] === arr[1];
})();
8 changes: 8 additions & 0 deletions test/functional-tests/issues/fixtures/issue1300.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Object inside for loop should create new object each iteration
(function() {
let arr = [];
for (let i = 0; i < 3; i++)
arr.push({value: 0});
arr[0].value = 1;
return arr[0] === arr[1]; // should be false
})();
79 changes: 79 additions & 0 deletions test/functional-tests/issues/issue1300.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { assert } from 'chai';
import { NO_ADDITIONAL_NODES_PRESET } from '../../../src/options/presets/NoCustomNodes';
import { readFileAsString } from '../../helpers/readFileAsString';
import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscatorFacade';

//
// https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1300
//
describe('Issue #1300', () => {
describe('Object inside loop should create new object each iteration', () => {
const samplesCount = 50;

it('does not break object creation semantics with transformObjectKeys', () => {
const code: string = readFileAsString(__dirname + '/fixtures/issue1300.js');

for (let i = 0; i < samplesCount; i++) {
const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET,
transformObjectKeys: true,
seed: i
}).getObfuscatedCode();

const originalResult = eval(code);
const obfuscatedResult = eval(obfuscatedCode);

assert.equal(originalResult, false, 'Original code should return false');
assert.equal(obfuscatedResult, false, `Obfuscated code should return false (seed: ${i})`);
}
});

it('does not break with for-in loop', () => {
const code: string = readFileAsString(__dirname + '/fixtures/issue1300-for-in.js');

const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET,
transformObjectKeys: true
}).getObfuscatedCode();

assert.equal(eval(code), false);
assert.equal(eval(obfuscatedCode), false);
});

it('does not break with for-of loop', () => {
const code: string = readFileAsString(__dirname + '/fixtures/issue1300-for-of.js');

const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET,
transformObjectKeys: true
}).getObfuscatedCode();

assert.equal(eval(code), false);
assert.equal(eval(obfuscatedCode), false);
});

it('does not break with while loop', () => {
const code: string = readFileAsString(__dirname + '/fixtures/issue1300-while.js');

const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET,
transformObjectKeys: true
}).getObfuscatedCode();

assert.equal(eval(code), false);
assert.equal(eval(obfuscatedCode), false);
});

it('does not break with do-while loop', () => {
const code: string = readFileAsString(__dirname + '/fixtures/issue1300-do-while.js');

const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(code, {
...NO_ADDITIONAL_NODES_PRESET,
transformObjectKeys: true
}).getObfuscatedCode();

assert.equal(eval(code), false);
assert.equal(eval(obfuscatedCode), false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -1092,13 +1092,13 @@ describe('ObjectExpressionKeysTransformer', () => {
});

describe('Variant #2: without block statement', () => {
// Object should NOT be transformed when inside loop without block statement
// to prevent all iterations sharing the same object reference (issue #1300)
const match: string =
`` +
`var ${variableMatch};` +
`var ${variableMatch} *= *{};` +
`${variableMatch}\\['bar'] *= *'bar';` +
`for *\\(var ${variableMatch} *= *0x0; *${variableMatch} *< *0xa; *${variableMatch}\\+\\+\\) *` +
`${variableMatch} *= *${variableMatch};` +
`${variableMatch} *= *\\{'bar': *'bar'\\};` +
``;
const regExp: RegExp = new RegExp(match);

Expand Down Expand Up @@ -1151,13 +1151,13 @@ describe('ObjectExpressionKeysTransformer', () => {
});

describe('Variant #2: without block statement', () => {
// Object should NOT be transformed when inside loop without block statement
// to prevent all iterations sharing the same object reference (issue #1300)
const match: string =
`` +
`var ${variableMatch} *= *{};` +
`var ${variableMatch} *= *{};` +
`${variableMatch}\\['bar'] *= *'bar';` +
`for *\\(var ${variableMatch} in *${variableMatch}\\) *` +
`${variableMatch} *= *${variableMatch};` +
`${variableMatch} *= *\\{'bar': *'bar'\\};` +
``;
const regExp: RegExp = new RegExp(match);

Expand Down Expand Up @@ -1210,13 +1210,13 @@ describe('ObjectExpressionKeysTransformer', () => {
});

describe('Variant #2: without block statement', () => {
// Object should NOT be transformed when inside loop without block statement
// to prevent all iterations sharing the same object reference (issue #1300)
const match: string =
`` +
`var ${variableMatch} *= *\\[];` +
`var ${variableMatch} *= *{};` +
`${variableMatch}\\['bar'] *= *'bar';` +
`for *\\(var ${variableMatch} of *${variableMatch}\\) *` +
`${variableMatch} *= *${variableMatch};` +
`${variableMatch} *= *\\{'bar': *'bar'\\};` +
``;
const regExp: RegExp = new RegExp(match);

Expand Down Expand Up @@ -1268,13 +1268,13 @@ describe('ObjectExpressionKeysTransformer', () => {
});

describe('Variant #2: without block statement', () => {
// Object should NOT be transformed when inside loop without block statement
// to prevent all iterations sharing the same object reference (issue #1300)
const match: string =
`` +
`var ${variableMatch};` +
`var ${variableMatch} *= *{};` +
`${variableMatch}\\['bar'] *= *'bar';` +
`while *\\(!!\\[]\\)` +
`${variableMatch} *= *${variableMatch};` +
`${variableMatch} *= *\\{'bar': *'bar'\\};` +
``;
const regExp: RegExp = new RegExp(match);

Expand Down