Skip to content

Commit

Permalink
fix: don't inline objects into function etc
Browse files Browse the repository at this point in the history
  • Loading branch information
j4k0xb committed Dec 28, 2024
1 parent c643d43 commit d44b47c
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 3 deletions.
29 changes: 26 additions & 3 deletions packages/webcrack/src/deobfuscate/merge-object-assignments.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Binding } from '@babel/traverse';
import type { Binding, NodePath } from '@babel/traverse';
import * as t from '@babel/types';
import * as m from '@codemod/matchers';
import type { Transform } from '../ast-utils';
import { constObjectProperty, safeLiteral } from '../ast-utils';
import { constObjectProperty, findParent, safeLiteral } from '../ast-utils';

/**
* Merges object assignments into the object expression.
Expand Down Expand Up @@ -78,7 +78,8 @@ export default {
// Example: const obj = { foo: 'bar' }; return obj; -> return { foo: 'bar' };
if (
binding.references === 1 &&
inlineableObject.match(object.current)
inlineableObject.match(object.current) &&
!isRepeatedCallReference(binding, binding.referencePaths[0])
) {
binding.referencePaths[0].replaceWith(object.current);
path.remove();
Expand All @@ -103,6 +104,28 @@ function hasCircularReference(node: t.Node, binding: Binding) {
);
}

const repeatedCallMatcher = m.or(
m.forStatement(),
m.forOfStatement(),
m.forInStatement(),
m.whileStatement(),
m.doWhileStatement(),
m.function(),
m.objectMethod(),
m.classBody(),
);

/**
* Returns true when the reference can be evaluated multiple times.
* In that case, the object should not be inlined to avoid creating multiple instances.
* Structure: Block{ binding, Repeatable{reference} }
*/
function isRepeatedCallReference(binding: Binding, reference: NodePath) {
const block = binding.scope.getBlockParent().path;
const repeatable = findParent(reference, repeatedCallMatcher);
return repeatable?.isDescendant(block);
}

/**
* Only literals, arrays and objects are allowed because variable values
* might be different in the place the object will be inlined.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,143 @@ test('ignore call with possible circular reference', () =>
const obj = {};
obj.foo = fn();
`));

test('do not inline object into function', () =>
expectJS(`
const obj = {};
obj.foo = 1;
function f() {
return obj;
}
`).toMatchInlineSnapshot(`
const obj = {
foo: 1
};
function f() {
return obj;
}
`));

test('do not inline object into arrow function', () =>
expectJS(`
const obj = {};
obj.foo = 1;
const f = () => obj;
`).toMatchInlineSnapshot(`
const obj = {
foo: 1
};
const f = () => obj;
`));

test('do not inline object into method', () =>
expectJS(`
const obj = {};
obj.foo = 1;
const obj2 = { f() { return obj; } };
`).toMatchInlineSnapshot(`
const obj = {
foo: 1
};
const obj2 = {
f() {
return obj;
}
};
`));

test('do not inline object into class', () =>
expectJS(`
const obj = {};
obj.foo = 1;
class C {
f = obj;
}
`).toMatchInlineSnapshot(`
const obj = {
foo: 1
};
class C {
f = obj;
}
`));

test('do not inline object into while-loop', () =>
expectJS(`
const obj = {};
obj.foo = 1;
while (i < 2) {
arr.push(obj);
}
`).toMatchInlineSnapshot(`
const obj = {
foo: 1
};
while (i < 2) {
arr.push(obj);
}
`));

test('do not inline object into do-while-loop', () =>
expectJS(`
const obj = {};
obj.foo = 1;
do {
arr.push(obj);
} while (i < 2);
`).toMatchInlineSnapshot(`
const obj = {
foo: 1
};
do {
arr.push(obj);
} while (i < 2);
`));

test('do not inline object into for-loop', () =>
expectJS(`
const obj = {};
obj.foo = 1;
for (let i = 0; i < 2; i++) {
arr.push(obj);
}
`).toMatchInlineSnapshot(`
const obj = {
foo: 1
};
for (let i = 0; i < 2; i++) {
arr.push(obj);
}
`));

test('do not inline object into for-of-loop', () =>
expectJS(`
const obj = {};
obj.foo = 1;
for (const item of items) {
arr.push(obj);
}
`).toMatchInlineSnapshot(`
const obj = {
foo: 1
};
for (const item of items) {
arr.push(obj);
}
`));

test('do not inline object into for-in-loop', () =>
expectJS(`
const obj = {};
obj.foo = 1;
for (const key in [1, 2]) {
arr.push(obj);
}
`).toMatchInlineSnapshot(`
const obj = {
foo: 1
};
for (const key in [1, 2]) {
arr.push(obj);
}
`));

0 comments on commit d44b47c

Please sign in to comment.