Fixed reservedNames not preserving class method and property names when stringArray or deadCodeInjection is enabled (#1389)

This commit is contained in:
Timofey Kachalov
2026-03-22 09:41:56 +04:00
committed by GitHub
parent 1385963ecb
commit 2246099bb6
6 changed files with 67 additions and 23 deletions
+1
View File
@@ -3,6 +3,7 @@ Change Log
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
* 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!
v5.3.1
@@ -12,6 +12,7 @@ import { NodeTransformationStage } from '../../enums/node-transformers/NodeTrans
import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
import { NodeFactory } from '../../node/NodeFactory';
import { NodeGuards } from '../../node/NodeGuards';
import { IdentifierReplacer } from '../rename-identifiers-transformers/replacer/IdentifierReplacer';
/**
* replaces:
@@ -89,6 +90,18 @@ export class ClassFieldTransformer extends AbstractNodeTransformer {
return classFieldNode;
}
/**
* @param {string} name
* @returns {boolean}
*/
private isIgnoredName(name: string): boolean {
if (name === ClassFieldTransformer.ignoredName) {
return true;
}
return IdentifierReplacer.isReservedName(name, this.options.reservedNames);
}
/**
* @param {MethodDefinition | PropertyDefinition} classFieldNode
* @param {Identifier} keyNode
@@ -98,7 +111,7 @@ export class ClassFieldTransformer extends AbstractNodeTransformer {
classFieldNode: ESTree.MethodDefinition | ESTree.PropertyDefinition,
keyNode: ESTree.Identifier
): ESTree.MethodDefinition | ESTree.PropertyDefinition {
if (keyNode.name !== ClassFieldTransformer.ignoredName && !classFieldNode.computed) {
if (!this.isIgnoredName(keyNode.name) && !classFieldNode.computed) {
classFieldNode.computed = true;
classFieldNode.key = NodeFactory.literalNode(keyNode.name);
}
@@ -115,11 +128,7 @@ export class ClassFieldTransformer extends AbstractNodeTransformer {
classFieldNode: ESTree.MethodDefinition | ESTree.PropertyDefinition,
keyNode: ESTree.Literal
): ESTree.MethodDefinition | ESTree.PropertyDefinition {
if (
typeof keyNode.value === 'string' &&
keyNode.value !== ClassFieldTransformer.ignoredName &&
!classFieldNode.computed
) {
if (typeof keyNode.value === 'string' && !this.isIgnoredName(keyNode.value) && !classFieldNode.computed) {
classFieldNode.computed = true;
}
@@ -52,6 +52,21 @@ export class IdentifierReplacer implements IIdentifierReplacer {
this.identifierNamesGenerator = identifierNamesGeneratorFactory(options);
}
/**
* @param {string} name
* @param {string} reservedNames
* @returns {boolean}
*/
public static isReservedName(name: string, reservedNames: string[]): boolean {
if (!reservedNames.length) {
return false;
}
return reservedNames.some((reservedName: string) => {
return new RegExp(reservedName, 'g').exec(name) !== null;
});
}
/**
* Store identifier node `name` of global identifiers as key in map with random name as value.
* Reserved name will be ignored.
@@ -62,7 +77,7 @@ export class IdentifierReplacer implements IIdentifierReplacer {
public storeGlobalName(identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void {
const identifierName: string = identifierNode.name;
if (this.isReservedName(identifierName)) {
if (IdentifierReplacer.isReservedName(identifierName, this.options.reservedNames)) {
return;
}
@@ -89,7 +104,7 @@ export class IdentifierReplacer implements IIdentifierReplacer {
public storeLocalName(identifierNode: ESTree.Identifier, lexicalScopeNode: TNodeWithLexicalScope): void {
const identifierName: string = identifierNode.name;
if (this.isReservedName(identifierName)) {
if (IdentifierReplacer.isReservedName(identifierName, this.options.reservedNames)) {
return;
}
@@ -142,18 +157,4 @@ export class IdentifierReplacer implements IIdentifierReplacer {
): void {
this.identifierNamesGenerator.preserveNameForLexicalScope(identifierNode.name, lexicalScopeNode);
}
/**
* @param {string} name
* @returns {boolean}
*/
private isReservedName(name: string): boolean {
if (!this.options.reservedNames.length) {
return false;
}
return this.options.reservedNames.some((reservedName: string) => {
return new RegExp(reservedName, 'g').exec(name) !== null;
});
}
}
@@ -273,4 +273,31 @@ describe('ClassFieldTransformer', () => {
});
});
});
describe('Variant #3: class method not obfuscated if option `reservedNames` is set', () => {
const samplesCount: number = 100;
let obfuscations: string[];
beforeEach(() => {
obfuscations = [];
const code: string = readFileAsString(__dirname + '/fixtures/class-with-function.js');
for (let i = 0; i < samplesCount; i++) {
const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(code, {
controlFlowFlattening: true,
deadCodeInjection: true,
reservedNames: ['bar']
}).getObfuscatedCode();
obfuscations.push(obfuscatedCode);
}
});
it('class should always have `bar` method not obfuscated', () => {
for (const codeAsString of obfuscations) {
assert.match(codeAsString, /\bbar\s*\(/);
}
});
});
});
@@ -0,0 +1,6 @@
class Foo {
bar() {
console.log(1);
baz();
}
}
@@ -25,7 +25,7 @@ describe('IdentifierReplacer', () => {
});
});
describe('Variant #1: ignore global reserved names', () => {
describe('Variant #2: ignore global reserved names', () => {
let obfuscatedCode: string;
before(() => {