Skip to content

Commit 7792fb0

Browse files
authored
Fixed class expression name references inside class body being incorrectly resolved to an import binding with the same name, causing broken code at runtime (javascript-obfuscator#1387)
1 parent 0a06d29 commit 7792fb0

5 files changed

Lines changed: 87 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
Change Log
22

3+
v5.3.1
4+
---
5+
* Fixed class expression name references inside class body being incorrectly resolved to an import binding with the same name, causing broken code at runtime. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1386
6+
37
v5.3.0
48
---
59
* Add Pro API support to CLI

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "javascript-obfuscator",
3-
"version": "5.3.0",
3+
"version": "5.3.1",
44
"description": "JavaScript obfuscator",
55
"keywords": [
66
"obfuscator",

src/analyzers/scope-analyzer/ScopeAnalyzer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,11 @@ export class ScopeAnalyzer implements IScopeAnalyzer {
238238
(definition: eslintScope.Definition) => definition.type === 'ClassName'
239239
);
240240

241-
return isValidClassNameVariable && variable.name === classNameVariable.name;
241+
const isImportBinding: boolean = variable.defs.some(
242+
(definition: eslintScope.Definition) => definition.type === 'ImportBinding'
243+
);
244+
245+
return isValidClassNameVariable && variable.name === classNameVariable.name && !isImportBinding;
242246
}
243247
);
244248

test/functional-tests/analyzers/scope-analyzer/ScopeAnalyzer.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { evalLocal } from '../../../helpers/evalLocal';
66
import { readFileAsString } from '../../../helpers/readFileAsString';
77

88
import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade';
9+
import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
910

1011
describe('ScopeAnalyzer', () => {
1112
describe('analyze', () => {
@@ -121,5 +122,64 @@ describe('ScopeAnalyzer', () => {
121122
});
122123
});
123124
});
125+
126+
describe('Variant #3: class expression name shadowing import binding', () => {
127+
const samplesCount: number = 50;
128+
129+
let obfuscatedCode: string;
130+
let importBindingName: string | null;
131+
let classExpressionName: string | null;
132+
133+
beforeEach(() => {
134+
const code: string = readFileAsString(
135+
__dirname + '/fixtures/class-expression-name-shadowing-import.js'
136+
);
137+
138+
importBindingName = null;
139+
classExpressionName = null;
140+
141+
for (let i = 0; i < samplesCount; i++) {
142+
obfuscatedCode = JavaScriptObfuscator.obfuscate(code, {
143+
...NO_ADDITIONAL_NODES_PRESET,
144+
compact: false,
145+
stringArray: false,
146+
seed: i
147+
}).getObfuscatedCode();
148+
149+
const importMatch = obfuscatedCode.match(/import\s*\{\s*i\s+as\s+(\w+)\s*\}/);
150+
const classMatch = obfuscatedCode.match(/=\s*class\s+(\w+)\s*\{/);
151+
152+
if (importMatch && classMatch) {
153+
importBindingName = importMatch[1];
154+
classExpressionName = classMatch[1];
155+
156+
if (importBindingName !== classExpressionName) {
157+
break;
158+
}
159+
}
160+
}
161+
});
162+
163+
it('should not rename class expression self-references to the import binding', () => {
164+
assert.isNotNull(importBindingName, 'should find import binding name');
165+
assert.isNotNull(classExpressionName, 'should find class expression name');
166+
167+
const getInstanceMatch = obfuscatedCode.match(/getInstance[^}]*\{([^}]*)\}/s);
168+
assert.isNotNull(getInstanceMatch, 'should find getInstance body');
169+
170+
const getInstanceBody: string = getInstanceMatch![1];
171+
172+
assert.include(
173+
getInstanceBody,
174+
`new ${classExpressionName!}()`,
175+
'new <class>() inside getInstance should reference the class expression name'
176+
);
177+
assert.notInclude(
178+
getInstanceBody,
179+
`new ${importBindingName!}()`,
180+
'new <import>() inside getInstance should NOT reference the import binding'
181+
);
182+
});
183+
});
124184
});
125185
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { i as e } from './runtime.js';
2+
3+
function factory() {
4+
return 'factory-result';
5+
}
6+
7+
var P = class e {
8+
static instance;
9+
10+
static getInstance() {
11+
return ((e.instance ||= new e()), e.instance);
12+
}
13+
};
14+
15+
var x = e(factory(), 1);
16+
17+
export { P, x };

0 commit comments

Comments
 (0)