Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ Change Log

v5.4.4
---
* Optimized scope identifiers transformer performance
* Optimized identifier renaming performance by reusing scope analysis between transformers
* Fixed `Invalid regular expression` error when obfuscating code that uses ES2025 RegExp pattern modifiers (e.g. `/(?i:abc)/`). Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1410
* Fixed `SyntaxError` when obfuscating a class that extends a boolean literal (e.g. `class C extends true {}`). Fixes https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1131

v5.4.3
---
Expand Down
24 changes: 23 additions & 1 deletion src/analyzers/scope-analyzer/ScopeAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export class ScopeAnalyzer implements IScopeAnalyzer {
*/
private scopeManager: eslintScope.ScopeManager | null = null;

/**
* @type {eslintScope.ScopeManager | null}
*/
private sanitizedScopeManager: eslintScope.ScopeManager | null = null;

/**
* `eslint-scope` reads `ranges` property of a nodes
* Should attach that property to the some custom nodes
Expand Down Expand Up @@ -75,6 +80,9 @@ export class ScopeAnalyzer implements IScopeAnalyzer {
public analyze(astTree: ESTree.Node): void {
const sourceTypeLength: number = ScopeAnalyzer.sourceTypes.length;

this.scopeManager = null;
this.sanitizedScopeManager = null;

ScopeAnalyzer.attachMissingRanges(astTree);

for (let i: number = 0; i < sourceTypeLength; i++) {
Expand Down Expand Up @@ -117,11 +125,25 @@ export class ScopeAnalyzer implements IScopeAnalyzer {
throw new Error('Cannot acquire scope for node');
}

this.sanitizeScopes(scope);
if (this.sanitizedScopeManager !== this.scopeManager) {
this.sanitizeScopes(scope);
this.sanitizedScopeManager = this.scopeManager;
}

return scope;
}

/**
* Checks whether a scope for the given node is already available in the current
* scope manager (i.e. `analyze` has been run for the tree this node belongs to).
*
* @param {Node} node
* @returns {boolean}
*/
public isAnalyzed(node: ESTree.Node): boolean {
return !!this.scopeManager?.acquire(node, ScopeAnalyzer.isRootNode(node));
}

/**
* Fix Annex B function hoisting references.
*
Expand Down
6 changes: 6 additions & 0 deletions src/interfaces/analyzers/scope-analyzer/IScopeAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ export interface IScopeAnalyzer extends IAnalyzer<[ESTree.Node], void> {
* @returns {Scope}
*/
acquireScope(node: ESTree.Node): eslintScope.Scope;

/**
* @param {Node} node
* @returns {boolean}
*/
isAnalyzed(node: ESTree.Node): boolean;
}
5 changes: 5 additions & 0 deletions src/interfaces/node-transformers/INodeTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';

export interface INodeTransformer extends ITransformer<NodeTransformer> {
/**
* @type {boolean}
*/
runOnProgramNodeOnly?: boolean;

/**
* @param {NodeTransformationStage} nodeTransformationStage
* @returns {IVisitor | null}
Expand Down
12 changes: 6 additions & 6 deletions src/interfaces/node/IScopeIdentifiersTraverser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ import { IScopeThroughIdentifiersTraverserCallbackData } from './IScopeThroughId
export interface IScopeIdentifiersTraverser {
/**
* @param {Program} programNode
* @param {Node | null} parentNode
* @param {TScopeIdentifiersTraverserCallback<IScopeIdentifiersTraverserCallbackData>} callback
* @param {boolean} analyzeScope
*/
traverseScopeIdentifiers(
programNode: ESTree.Program,
parentNode: ESTree.Node | null,
callback: TScopeIdentifiersTraverserCallback<IScopeIdentifiersTraverserCallbackData>
callback: TScopeIdentifiersTraverserCallback<IScopeIdentifiersTraverserCallbackData>,
analyzeScope?: boolean
): void;

/**
* @param {Node} node
* @param {Node | null} parentNode
* @param {TScopeIdentifiersTraverserCallback<IScopeThroughIdentifiersTraverserCallbackData>} callback
* @param {boolean} analyzeScope
*/
traverseScopeThroughIdentifiers(
node: ESTree.Node,
parentNode: ESTree.Node | null,
callback: TScopeIdentifiersTraverserCallback<IScopeThroughIdentifiersTraverserCallbackData>
callback: TScopeIdentifiersTraverserCallback<IScopeThroughIdentifiersTraverserCallbackData>,
analyzeScope?: boolean
): void;
}
5 changes: 5 additions & 0 deletions src/node-transformers/AbstractNodeTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export abstract class AbstractNodeTransformer implements INodeTransformer {
*/
public readonly runAfter: NodeTransformer[] | undefined;

/**
* @type {boolean}
*/
public readonly runOnProgramNodeOnly: boolean | undefined;

/**
* @type {IOptions}
*/
Expand Down
101 changes: 83 additions & 18 deletions src/node-transformers/NodeTransformersRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import { VisitorDirection } from '../enums/node-transformers/VisitorDirection';
import { NodeGuards } from '../node/NodeGuards';
import { NodeMetadata } from '../node/NodeMetadata';

interface IVisitorsData {
enterVisitors: IVisitor[];
leaveVisitors: IVisitor[];
runOnProgramNodeOnly: boolean;
}

@injectable()
export class NodeTransformersRunner implements INodeTransformersRunner {
/**
Expand Down Expand Up @@ -75,27 +81,20 @@ export class NodeTransformersRunner implements INodeTransformersRunner {
this.nodeTransformerNamesGroupsBuilder.build(normalizedNodeTransformers);

for (const nodeTransformerNamesGroup of nodeTransformerNamesGroups) {
const enterVisitors: IVisitor[] = [];
const leaveVisitors: IVisitor[] = [];

for (const nodeTransformerName of nodeTransformerNamesGroup) {
const nodeTransformer: INodeTransformer = normalizedNodeTransformers[nodeTransformerName];
const visitor: IVisitor | null = nodeTransformer.getVisitor(nodeTransformationStage);

if (!visitor) {
continue;
}
const visitorsData: IVisitorsData = this.buildVisitorsData(
nodeTransformerNamesGroup,
normalizedNodeTransformers,
nodeTransformationStage
);
const { enterVisitors, leaveVisitors } = visitorsData;

if (visitor.enter) {
enterVisitors.push({ enter: visitor.enter });
}

if (visitor.leave) {
leaveVisitors.push({ leave: visitor.leave });
}
if (!enterVisitors.length && !leaveVisitors.length) {
continue;
}

if (!enterVisitors.length && !leaveVisitors.length) {
if (this.canRunOnProgramNodeOnly(visitorsData, astTree)) {
astTree = this.runOnProgramNodeOnly(astTree, enterVisitors);

continue;
}

Expand All @@ -108,6 +107,72 @@ export class NodeTransformersRunner implements INodeTransformersRunner {
return astTree;
}

/**
* @param {NodeTransformer[]} nodeTransformerNamesGroup
* @param {TDictionary<INodeTransformer>} normalizedNodeTransformers
* @param {NodeTransformationStage} nodeTransformationStage
* @returns {IVisitorsData}
*/
private buildVisitorsData(
nodeTransformerNamesGroup: NodeTransformer[],
normalizedNodeTransformers: TDictionary<INodeTransformer>,
nodeTransformationStage: NodeTransformationStage
): IVisitorsData {
const enterVisitors: IVisitor[] = [];
const leaveVisitors: IVisitor[] = [];
let runOnProgramNodeOnly: boolean = true;

for (const nodeTransformerName of nodeTransformerNamesGroup) {
const nodeTransformer: INodeTransformer = normalizedNodeTransformers[nodeTransformerName];
const visitor: IVisitor | null = nodeTransformer.getVisitor(nodeTransformationStage);

if (!visitor) {
continue;
}

if (!nodeTransformer.runOnProgramNodeOnly) {
runOnProgramNodeOnly = false;
}

if (visitor.enter) {
enterVisitors.push({ enter: visitor.enter });
}

if (visitor.leave) {
leaveVisitors.push({ leave: visitor.leave });
}
}

return { enterVisitors, leaveVisitors, runOnProgramNodeOnly };
}

/**
* @param {IVisitorsData} visitorsData
* @param {Node} astTree
* @returns {boolean}
*/
private canRunOnProgramNodeOnly(visitorsData: IVisitorsData, astTree: ESTree.Node): boolean {
return (
visitorsData.runOnProgramNodeOnly &&
!visitorsData.leaveVisitors.length &&
NodeGuards.isProgramNode(astTree)
);
}

/**
* @param {T} astTree
* @param {IVisitor[]} enterVisitors
* @returns {T}
*/
private runOnProgramNodeOnly<T extends ESTree.Node>(astTree: T, enterVisitors: IVisitor[]): T {
const visitorResult: TVisitorResult = this.mergeVisitorsForDirection(
enterVisitors,
VisitorDirection.Enter
)(astTree, astTree.parentNode ?? null);

return visitorResult && NodeGuards.isNode(visitorResult) ? <T>visitorResult : astTree;
}

/**
* @param {NodeTransformer[]} nodeTransformerNames
* @param {NodeTransformationStage} nodeTransformationStage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ export class BooleanLiteralTransformer extends AbstractNodeTransformer {
return literalNode;
}

/**
* A class heritage (`extends`) must be a `LeftHandSideExpression`, so replacing a
* boolean literal there with a unary expression (`class Foo extends ![] {}`) would
* produce a `SyntaxError`. Keep the original literal in that position.
*/
if ('superClass' in parentNode && parentNode.superClass === literalNode) {
return literalNode;
}

const literalValue: ESTree.SimpleLiteral['value'] = literalNode.value;

const unaryExpressionNode: ESTree.UnaryExpression = literalValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class DeadCodeInjectionIdentifiersTransformer extends AbstractNodeTransfo
return {
enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
if (parentNode && NodeGuards.isProgramNode(node)) {
return this.transformNode(node, parentNode);
return this.transformNode(node);
}
}
};
Expand All @@ -74,13 +74,11 @@ export class DeadCodeInjectionIdentifiersTransformer extends AbstractNodeTransfo

/**
* @param {VariableDeclaration} programNode
* @param {NodeGuards} parentNode
* @returns {NodeGuards}
*/
public transformNode(programNode: ESTree.Program, parentNode: ESTree.Node): ESTree.Node {
public transformNode(programNode: ESTree.Program): ESTree.Node {
this.scopeIdentifiersTraverser.traverseScopeThroughIdentifiers(
programNode,
parentNode,
(data: IScopeThroughIdentifiersTraverserCallbackData) => {
const { reference, variableLexicalScopeNode } = data;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export class VariablePreserveTransformer extends AbstractNodeTransformer {
*/
public override readonly runAfter: NodeTransformer[] = [NodeTransformer.ParentificationTransformer];

/**
* @type {boolean}
*/
public override readonly runOnProgramNodeOnly: boolean = true;

/**
* @type {IIdentifierReplacer}
*/
Expand Down Expand Up @@ -64,17 +69,21 @@ export class VariablePreserveTransformer extends AbstractNodeTransformer {
* @returns {IVisitor | null}
*/
public getVisitor(nodeTransformationStage: NodeTransformationStage): IVisitor | null {
const visitor: IVisitor = {
enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
if (parentNode && NodeGuards.isProgramNode(node)) {
return this.transformNode(node);
}
}
};

switch (nodeTransformationStage) {
case NodeTransformationStage.Preparing:
case NodeTransformationStage.Converting:
case NodeTransformationStage.RenameIdentifiers:
return {
enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
if (parentNode && NodeGuards.isProgramNode(node)) {
return this.transformNode(node, parentNode);
}
}
};
return visitor;

case NodeTransformationStage.Converting:
return this.shouldPreserveConvertingStageIdentifiers() ? visitor : null;

default:
return null;
Expand All @@ -83,13 +92,11 @@ export class VariablePreserveTransformer extends AbstractNodeTransformer {

/**
* @param {VariableDeclaration} programNode
* @param {NodeGuards} parentNode
* @returns {NodeGuards}
*/
public transformNode(programNode: ESTree.Program, parentNode: ESTree.Node): ESTree.Node {
public transformNode(programNode: ESTree.Program): ESTree.Node {
this.scopeIdentifiersTraverser.traverseScopeIdentifiers(
programNode,
parentNode,
this.preserveScopeVariableIdentifiers
);

Expand Down Expand Up @@ -136,4 +143,16 @@ export class VariablePreserveTransformer extends AbstractNodeTransformer {

this.identifierReplacer.preserveNameForLexicalScope(identifierNode, lexicalScopeNode);
}

/**
* @returns {boolean}
*/
private shouldPreserveConvertingStageIdentifiers(): boolean {
return (
this.options.controlFlowFlattening ||
this.options.deadCodeInjection ||
this.options.stringArray ||
this.options.transformObjectKeys
);
}
}
Loading
Loading