From bd6d89fabcb2964f19e9968b0653f321e05c7854 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sun, 22 Mar 2026 12:44:30 +0700 Subject: [PATCH 1/4] Fixed infinite loop / stack overflow when `reservedNames` patterns match all generated identifier names --- CHANGELOG.md | 1 + .../AbstractIdentifierNamesGenerator.ts | 18 +++++++++++++++ .../HexadecimalIdentifierNamesGenerator.ts | 11 ++++++++-- .../MangledIdentifierNamesGenerator.ts | 3 +++ .../MangledIdentifierNamesGenerator.spec.ts | 19 ++++++++++++++++ .../fixtures/reserved-names-match-all.js | 1 + ...exadecimalIdentifierNamesGenerator.spec.ts | 22 +++++++++++++++++++ ...dShuffledlIdentifierNamesGenerator.spec.ts | 22 +++++++++++++++++++ .../MangledlIdentifierNamesGenerator.spec.ts | 22 +++++++++++++++++++ 9 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 test/functional-tests/generators/identifier-names-generators/mangled-identifier-names-generator/fixtures/reserved-names-match-all.js 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..e756d4a8b 100644 --- a/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts +++ b/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts @@ -21,6 +21,11 @@ export abstract class AbstractIdentifierNamesGenerator implements IIdentifierNam */ protected readonly randomGenerator: IRandomGenerator; + /** + * @type {number} + */ + private static readonly maxGenerationAttempts: number = 1000; + /** * @type {Set} */ @@ -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..807f2721f 100644 --- a/src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts +++ b/src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts @@ -87,9 +87,16 @@ 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, + attempts: number = 0 + ): string { + this.checkGenerationAttempts(attempts); + const rangeMinInteger: number = 10000; const rangeMaxInteger: number = 99_999_999; const randomInteger: number = this.randomGenerator.getRandomInteger(rangeMinInteger, rangeMaxInteger); @@ -101,7 +108,7 @@ export class HexadecimalIdentifierNamesGenerator extends AbstractIdentifierNames const identifierName: string = `_${baseIdentifierName}`; if (!validationFn(identifierName)) { - return this.generateNextName(nameLength, validationFn); + return this.generateNextName(nameLength, validationFn, attempts + 1); } this.preserveName(identifierName); diff --git a/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts b/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts index 96d6c3371..de3784bad 100644 --- a/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts +++ b/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts @@ -283,10 +283,13 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene let identifierName: string = previousMangledName; let isValidIdentifierName: boolean; + let attempts: number = 0; do { + this.checkGenerationAttempts(attempts); identifierName = generateNewMangledName(identifierName); isValidIdentifierName = validationFunction?.(identifierName) ?? this.isValidIdentifierName(identifierName); + attempts++; } 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'); + }); + }); }); From a33325d675acffe94bf4acc5f4c193ca591f8f97 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sun, 22 Mar 2026 12:55:14 +0700 Subject: [PATCH 2/4] Fix lint --- .../AbstractIdentifierNamesGenerator.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts b/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts index e756d4a8b..5b0320886 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 = 1000; + /** * @type {IOptions} */ @@ -21,11 +26,6 @@ export abstract class AbstractIdentifierNamesGenerator implements IIdentifierNam */ protected readonly randomGenerator: IRandomGenerator; - /** - * @type {number} - */ - private static readonly maxGenerationAttempts: number = 1000; - /** * @type {Set} */ @@ -143,9 +143,9 @@ export abstract class AbstractIdentifierNamesGenerator implements IIdentifierNam 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.' + 'Unable to generate a valid identifier name. ' + + 'This is likely caused by `reservedNames` patterns that match all generated names. ' + + 'Please check your `reservedNames` option.' ); } } From 1d171091375529ab7fa796035227228fd22ea400 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sun, 22 Mar 2026 13:28:20 +0700 Subject: [PATCH 3/4] Adjust logic --- .../AbstractIdentifierNamesGenerator.ts | 2 +- .../MangledIdentifierNamesGenerator.ts | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts b/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts index 5b0320886..329b7f414 100644 --- a/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts +++ b/src/generators/identifier-names-generators/AbstractIdentifierNamesGenerator.ts @@ -14,7 +14,7 @@ export abstract class AbstractIdentifierNamesGenerator implements IIdentifierNam /** * @type {number} */ - private static readonly maxGenerationAttempts: number = 1000; + private static readonly maxGenerationAttempts: number = 10000; /** * @type {IOptions} diff --git a/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts b/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts index de3784bad..f76c71c97 100644 --- a/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts +++ b/src/generators/identifier-names-generators/MangledIdentifierNamesGenerator.ts @@ -282,14 +282,25 @@ export class MangledIdentifierNamesGenerator extends AbstractIdentifierNamesGene }; let identifierName: string = previousMangledName; - let isValidIdentifierName: boolean; - let attempts: number = 0; + let isValidIdentifierName: boolean = false; + let reservedNameAttempts: number = 0; do { - this.checkGenerationAttempts(attempts); identifierName = generateNewMangledName(identifierName); + + if ( + this.preservedNamesSet.has(identifierName) || + MangledIdentifierNamesGenerator.reservedNamesSet.has(identifierName) + ) { + continue; + } + isValidIdentifierName = validationFunction?.(identifierName) ?? this.isValidIdentifierName(identifierName); - attempts++; + + if (!isValidIdentifierName) { + this.checkGenerationAttempts(reservedNameAttempts); + reservedNameAttempts++; + } } while (!isValidIdentifierName); return identifierName; From 4c1463469b3603ef5e432a3552b19be5b7f03507 Mon Sep 17 00:00:00 2001 From: sanex3339 Date: Sun, 22 Mar 2026 13:55:04 +0700 Subject: [PATCH 4/4] Fix HexadecimalIdentifierNamesGenerator.ts --- .../HexadecimalIdentifierNamesGenerator.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts b/src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts index 807f2721f..97020c724 100644 --- a/src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts +++ b/src/generators/identifier-names-generators/HexadecimalIdentifierNamesGenerator.ts @@ -92,23 +92,26 @@ export class HexadecimalIdentifierNamesGenerator extends AbstractIdentifierNames */ private generateNextName( nameLength: number | undefined, - validationFn: (name: string) => boolean, - attempts: number = 0 + validationFn: (name: string) => boolean ): string { - this.checkGenerationAttempts(attempts); - 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, attempts + 1); + 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);