diff --git a/CHANGELOG.md b/CHANGELOG.md index a2442c85d..7133fa025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ v5.4.0 --- * Add support for `import attributes`. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1256 * Fixed `reservedNames` not preserving class method and property names when `stringArray` or `deadCodeInjection` is enabled. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1279 +* Fixed infinite loop / stack overflow when `reservedNames` patterns match all generated identifier names. Now throws a descriptive error instead. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1382 * Fixed `transformObjectKeys` changing evaluation order when object expression is inside a sequence expression with preceding side effects (e.g. `return aux(ys), { min }`). Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1246 * Replaced `mkdirp` dependency with native `fs.mkdirSync({ recursive: true })`. Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1275. Thank you https://github.com/roli-lpci! * Replaced `conf` dependency with custom implementation using `env-paths` and native `fs` diff --git a/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts b/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts index a9655ad1f..329b7f414 100644 --- a/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts +++ b/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts @@ -11,6 +11,11 @@ import { NodeGuards } from '../../node/NodeGuards'; @injectable() export abstract class AbstractIdentifierNamesGenerator implements IIdentifierNamesGenerator { + /** + * @type {number} + */ + private static readonly maxGenerationAttempts: number = 10000; + /** * @type {IOptions} */ @@ -132,6 +137,19 @@ export abstract class AbstractIdentifierNamesGenerator implements IIdentifierNam return !this.allLexicalScopePreservedNames.has(name); } + /** + * @param {number} attempts + */ + protected checkGenerationAttempts(attempts: number): void { + if (attempts > AbstractIdentifierNamesGenerator.maxGenerationAttempts) { + throw new Error( + 'Unable to generate a valid identifier name. ' + + 'This is likely caused by `reservedNames` patterns that match all generated names. ' + + 'Please check your `reservedNames` option.' + ); + } + } + /** * @param {string} name * @returns {boolean} diff --git a/src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts b/src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts index 59df523e5..97020c724 100644 --- a/src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts +++ b/src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts @@ -87,21 +87,31 @@ export class HexadecimalIdentifierNamesGenerator extends AbstractIdentifierNames /** * @param {number} nameLength * @param {(name: string) => boolean} validationFn + * @param {number} attempts * @returns {string} */ - private generateNextName(nameLength: number | undefined, validationFn: (name: string) => boolean): string { + private generateNextName( + nameLength: number | undefined, + validationFn: (name: string) => boolean + ): string { const rangeMinInteger: number = 10000; const rangeMaxInteger: number = 99_999_999; - const randomInteger: number = this.randomGenerator.getRandomInteger(rangeMinInteger, rangeMaxInteger); - const hexadecimalNumber: string = NumberUtils.toHex(randomInteger); const prefixLength: number = Utils.hexadecimalPrefix.length; const baseNameLength: number = (nameLength ?? HexadecimalIdentifierNamesGenerator.baseIdentifierNameLength) + prefixLength; - const baseIdentifierName: string = hexadecimalNumber.slice(0, baseNameLength); - const identifierName: string = `_${baseIdentifierName}`; - if (!validationFn(identifierName)) { - return this.generateNextName(nameLength, validationFn); + let identifierName: string = ''; + let isValid: boolean = false; + + for (let attempts: number = 0; !isValid; attempts++) { + this.checkGenerationAttempts(attempts); + + const randomInteger: number = this.randomGenerator.getRandomInteger(rangeMinInteger, rangeMaxInteger); + const hexadecimalNumber: string = NumberUtils.toHex(randomInteger); + const baseIdentifierName: string = hexadecimalNumber.slice(0, baseNameLength); + + identifierName = `_${baseIdentifierName}`; + isValid = validationFn(identifierName); } this.preserveName(identifierName); diff --git a/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts b/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts index 96d6c3371..f76c71c97 100644 --- a/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts +++ b/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts @@ -282,11 +282,25 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene }; let identifierName: string = previousMangledName; - let isValidIdentifierName: boolean; + let isValidIdentifierName: boolean = false; + let reservedNameAttempts: number = 0; do { identifierName = generateNewMangledName(identifierName); + + if ( + this.preservedNamesSet.has(identifierName) || + MangledIdentifierNamesGenerator.reservedNamesSet.has(identifierName) + ) { + continue; + } + isValidIdentifierName = validationFunction?.(identifierName) ?? this.isValidIdentifierName(identifierName); + + if (!isValidIdentifierName) { + this.checkGenerationAttempts(reservedNameAttempts); + reservedNameAttempts++; + } } while (!isValidIdentifierName); return identifierName; diff --git a/test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/MangledIdentifierNamesGenerator.spec.ts b/test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/MangledIdentifierNamesGenerator.spec.ts index 1dc01f542..28a927821 100644 --- a/test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/MangledIdentifierNamesGenerator.spec.ts +++ b/test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/MangledIdentifierNamesGenerator.spec.ts @@ -368,4 +368,23 @@ describe('MangledIdentifierNamesGenerator', () => { }); }); }); + + describe('`reservedNames` that match all generated names', () => { + let testFunc: () => void; + + before(() => { + const code: string = readFileAsString(__dirname + '/fixtures/reserved-names-match-all.js'); + + testFunc = () => + JavaScriptObfuscator.obfuscate(code, { + ...NO_ADDITIONAL_NODES_PRESET, + identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator, + reservedNames: ['^(?!renameMeOnly$)'] + }); + }); + + it('should throw an error when all generated names match reservedNames', () => { + assert.throws(testFunc, 'Unable to generate a valid identifier name'); + }); + }); }); diff --git a/test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/fixtures/reserved-names-match-all.js b/test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/fixtures/reserved-names-match-all.js new file mode 100644 index 000000000..2ab5e9368 --- /dev/null +++ b/test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/fixtures/reserved-names-match-all.js @@ -0,0 +1 @@ +(function(){function pleasedontrename (){}; function renameMeOnly(){}}) diff --git a/test/unit-tests/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.spec.ts b/test/unit-tests/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.spec.ts index 190edad8c..2fb8c7d8d 100644 --- a/test/unit-tests/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.spec.ts +++ b/test/unit-tests/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.spec.ts @@ -121,4 +121,26 @@ describe('HexadecimalIdentifierNamesGenerator', () => { assert.notEqual(hexadecimalIdentifierName1, hexadecimalIdentifierName2); }); }); + + describe('`reservedNames` that match all generated names', () => { + let testFunc: () => void; + + before(() => { + const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade(); + + inversifyContainerFacade.load('', '', { + reservedNames: ['^(?!renameMeOnly$)'] + }); + const identifierNamesGenerator = inversifyContainerFacade.getNamed( + ServiceIdentifiers.IIdentifierNamesGenerator, + IdentifierNamesGenerator.HexadecimalIdentifierNamesGenerator + ); + + testFunc = () => identifierNamesGenerator.generateNext(); + }); + + it('should throw an error when all generated names match reservedNames', () => { + assert.throws(testFunc, 'Unable to generate a valid identifier name'); + }); + }); }); diff --git a/test/unit-tests/generators/identifier-names-generators/MangledShuffledlIdentifierNamesGenerator.spec.ts b/test/unit-tests/generators/identifier-names-generators/MangledShuffledlIdentifierNamesGenerator.spec.ts index 79519a24a..08286dafd 100644 --- a/test/unit-tests/generators/identifier-names-generators/MangledShuffledlIdentifierNamesGenerator.spec.ts +++ b/test/unit-tests/generators/identifier-names-generators/MangledShuffledlIdentifierNamesGenerator.spec.ts @@ -217,4 +217,26 @@ describe('MangledShuffledIdentifierNamesGenerator', () => { }); }); }); + + describe('`reservedNames` that match all generated names', () => { + let testFunc: () => void; + + beforeEach(() => { + const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade(); + + inversifyContainerFacade.load('', '', { + reservedNames: ['^(?!renameMeOnly$)'] + }); + const identifierNamesGenerator = inversifyContainerFacade.getNamed( + ServiceIdentifiers.IIdentifierNamesGenerator, + IdentifierNamesGenerator.MangledShuffledIdentifierNamesGenerator + ); + + testFunc = () => identifierNamesGenerator.generateNext(); + }); + + it('should throw an error when all generated names match reservedNames', () => { + assert.throws(testFunc, 'Unable to generate a valid identifier name'); + }); + }); }); diff --git a/test/unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec.ts b/test/unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec.ts index e64af8560..773b0421d 100644 --- a/test/unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec.ts +++ b/test/unit-tests/generators/identifier-names-generators/MangledlIdentifierNamesGenerator.spec.ts @@ -342,4 +342,26 @@ describe('MangledIdentifierNamesGenerator', () => { }); }); }); + + describe('`reservedNames` that match all generated names', () => { + let testFunc: () => void; + + beforeEach(() => { + const inversifyContainerFacade: IInversifyContainerFacade = new InversifyContainerFacade(); + + inversifyContainerFacade.load('', '', { + reservedNames: ['^(?!renameMeOnly$)'] + }); + const identifierNamesGenerator = inversifyContainerFacade.getNamed( + ServiceIdentifiers.IIdentifierNamesGenerator, + IdentifierNamesGenerator.MangledIdentifierNamesGenerator + ); + + testFunc = () => identifierNamesGenerator.generateNext(); + }); + + it('should throw an error when all generated names match reservedNames', () => { + assert.throws(testFunc, 'Unable to generate a valid identifier name'); + }); + }); });