diff --git a/CHANGELOG.md b/CHANGELOG.md index 35118b467..9e4ac0062 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ Change Log +v5.3.1 +--- +* 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 + v5.3.0 --- * Add Pro API support to CLI diff --git a/package.json b/package.json index 2519534eb..005f5a90d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "javascript-obfuscator", - "version": "5.3.0", + "version": "5.3.1", "description": "JavaScript obfuscator", "keywords": [ "obfuscator", diff --git a/src/analyzers/scope-analyzer/ScopeAnalyzer.ts b/src/analyzers/scope-analyzer/ScopeAnalyzer.ts index b4001cf6d..1c001e376 100644 --- a/src/analyzers/scope-analyzer/ScopeAnalyzer.ts +++ b/src/analyzers/scope-analyzer/ScopeAnalyzer.ts @@ -238,7 +238,11 @@ export class ScopeAnalyzer implements IScopeAnalyzer { (definition: eslintScope.Definition) => definition.type === 'ClassName' ); - return isValidClassNameVariable && variable.name === classNameVariable.name; + const isImportBinding: boolean = variable.defs.some( + (definition: eslintScope.Definition) => definition.type === 'ImportBinding' + ); + + return isValidClassNameVariable && variable.name === classNameVariable.name && !isImportBinding; } ); diff --git a/test/functional-tests/analyzers/scope-analyzer/ScopeAnalyzer.spec.ts b/test/functional-tests/analyzers/scope-analyzer/ScopeAnalyzer.spec.ts index 188f6d0cc..b5539fe26 100644 --- a/test/functional-tests/analyzers/scope-analyzer/ScopeAnalyzer.spec.ts +++ b/test/functional-tests/analyzers/scope-analyzer/ScopeAnalyzer.spec.ts @@ -6,6 +6,7 @@ import { evalLocal } from '../../../helpers/evalLocal'; import { readFileAsString } from '../../../helpers/readFileAsString'; import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade'; +import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes'; describe('ScopeAnalyzer', () => { describe('analyze', () => { @@ -121,5 +122,64 @@ describe('ScopeAnalyzer', () => { }); }); }); + + describe('Variant #3: class expression name shadowing import binding', () => { + const samplesCount: number = 50; + + let obfuscatedCode: string; + let importBindingName: string | null; + let classExpressionName: string | null; + + beforeEach(() => { + const code: string = readFileAsString( + __dirname + '/fixtures/class-expression-name-shadowing-import.js' + ); + + importBindingName = null; + classExpressionName = null; + + for (let i = 0; i < samplesCount; i++) { + obfuscatedCode = JavaScriptObfuscator.obfuscate(code, { + ...NO_ADDITIONAL_NODES_PRESET, + compact: false, + stringArray: false, + seed: i + }).getObfuscatedCode(); + + const importMatch = obfuscatedCode.match(/import\s*\{\s*i\s+as\s+(\w+)\s*\}/); + const classMatch = obfuscatedCode.match(/=\s*class\s+(\w+)\s*\{/); + + if (importMatch && classMatch) { + importBindingName = importMatch[1]; + classExpressionName = classMatch[1]; + + if (importBindingName !== classExpressionName) { + break; + } + } + } + }); + + it('should not rename class expression self-references to the import binding', () => { + assert.isNotNull(importBindingName, 'should find import binding name'); + assert.isNotNull(classExpressionName, 'should find class expression name'); + + const getInstanceMatch = obfuscatedCode.match(/getInstance[^}]*\{([^}]*)\}/s); + assert.isNotNull(getInstanceMatch, 'should find getInstance body'); + + const getInstanceBody: string = getInstanceMatch![1]; + + assert.include( + getInstanceBody, + `new ${classExpressionName!}()`, + 'new () inside getInstance should reference the class expression name' + ); + assert.notInclude( + getInstanceBody, + `new ${importBindingName!}()`, + 'new () inside getInstance should NOT reference the import binding' + ); + }); + }); }); }); diff --git a/test/functional-tests/analyzers/scope-analyzer/fixtures/class-expression-name-shadowing-import.js b/test/functional-tests/analyzers/scope-analyzer/fixtures/class-expression-name-shadowing-import.js new file mode 100644 index 000000000..3d14de717 --- /dev/null +++ b/test/functional-tests/analyzers/scope-analyzer/fixtures/class-expression-name-shadowing-import.js @@ -0,0 +1,17 @@ +import { i as e } from './runtime.js'; + +function factory() { + return 'factory-result'; +} + +var P = class e { + static instance; + + static getInstance() { + return ((e.instance ||= new e()), e.instance); + } +}; + +var x = e(factory(), 1); + +export { P, x };