Files
javascript-obfuscator-javas…/src/node-transformers/converting-transformers/ObjectExpressionKeysTransformer.ts
T
2026-01-27 21:19:27 +04:00

309 lines
11 KiB
TypeScript

import { inject, injectable } from 'inversify';
import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
import * as estraverse from '@javascript-obfuscator/estraverse';
import * as ESTree from 'estree';
import { TObjectExpressionExtractorFactory } from '../../types/container/node-transformers/TObjectExpressionExtractorFactory';
import { IOptions } from '../../interfaces/options/IOptions';
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
import { ObjectExpressionExtractor } from '../../enums/node-transformers/converting-transformers/properties-extractors/ObjectExpressionExtractor';
import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
import { NodeGuards } from '../../node/NodeGuards';
import { NodeStatementUtils } from '../../node/NodeStatementUtils';
@injectable()
export class ObjectExpressionKeysTransformer extends AbstractNodeTransformer {
/**
* @type {string}
*/
private static readonly thisIdentifierName: string = 'this';
/**
* @type {ObjectExpressionExtractor[]}
*/
private static readonly objectExpressionExtractorNames: ObjectExpressionExtractor[] = [
ObjectExpressionExtractor.ObjectExpressionToVariableDeclarationExtractor,
ObjectExpressionExtractor.BasePropertiesExtractor
];
/**
* @type {TObjectExpressionExtractorFactory}
*/
private readonly objectExpressionExtractorFactory: TObjectExpressionExtractorFactory;
/**
* @param {TObjectExpressionExtractorFactory} objectExpressionExtractorFactory
* @param {IRandomGenerator} randomGenerator
* @param {IOptions} options
*/
public constructor(
@inject(ServiceIdentifiers.Factory__IObjectExpressionExtractor)
objectExpressionExtractorFactory: TObjectExpressionExtractorFactory,
@inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
@inject(ServiceIdentifiers.IOptions) options: IOptions
) {
super(randomGenerator, options);
this.objectExpressionExtractorFactory = objectExpressionExtractorFactory;
}
/**
* Combined prohibition check result
*/
private static checkProhibitedPatterns(
objectExpressionNode: ESTree.ObjectExpression,
objectExpressionHostNode: ESTree.Node
): { hasReferencedIdentifier: boolean; hasCallExpression: boolean } {
const identifierNamesSet: Set<string> = new Set();
let hasReferencedIdentifier: boolean = false;
let hasCallExpression: boolean = false;
let isInsideObjectExpression: boolean = false;
estraverse.traverse(objectExpressionHostNode, {
// eslint-disable-next-line complexity
enter: (node: ESTree.Node): void | estraverse.VisitorOption => {
if (node === objectExpressionNode) {
isInsideObjectExpression = true;
}
if (isInsideObjectExpression && !hasCallExpression) {
if (NodeGuards.isCallExpressionNode(node) || NodeGuards.isNewExpressionNode(node)) {
hasCallExpression = true;
}
}
if (NodeGuards.isIdentifierNode(node) || NodeGuards.isThisExpressionNode(node)) {
const identifierName: string = NodeGuards.isIdentifierNode(node)
? node.name
: ObjectExpressionKeysTransformer.thisIdentifierName;
if (!isInsideObjectExpression) {
identifierNamesSet.add(identifierName);
} else if (identifierNamesSet.has(identifierName)) {
hasReferencedIdentifier = true;
}
}
if (hasReferencedIdentifier && hasCallExpression) {
return estraverse.VisitorOption.Break;
}
},
leave: (node: ESTree.Node): void | estraverse.VisitorOption => {
if (node === objectExpressionNode) {
isInsideObjectExpression = false;
if (hasReferencedIdentifier || hasCallExpression) {
return estraverse.VisitorOption.Break;
}
}
}
});
return { hasReferencedIdentifier, hasCallExpression };
}
/**
* @param {ObjectExpression} objectExpressionNode
* @param {Node} objectExpressionParentNode
* @param {Statement} objectExpressionHostStatement
* @returns {boolean}
*/
private static isProhibitedObjectExpressionNode(
objectExpressionNode: ESTree.ObjectExpression,
objectExpressionParentNode: ESTree.Node,
objectExpressionHostStatement: ESTree.Statement
): boolean {
if (
ObjectExpressionKeysTransformer.isProhibitedArrowFunctionExpression(
objectExpressionNode,
objectExpressionParentNode
) ||
ObjectExpressionKeysTransformer.isProhibitedSequenceExpression(
objectExpressionNode,
objectExpressionHostStatement
) ||
ObjectExpressionKeysTransformer.isProhibitedLoopBody(objectExpressionNode)
) {
return true;
}
const { hasReferencedIdentifier, hasCallExpression } = ObjectExpressionKeysTransformer.checkProhibitedPatterns(
objectExpressionNode,
objectExpressionHostStatement
);
return hasReferencedIdentifier || hasCallExpression;
}
/**
* @param {ObjectExpression} objectExpressionNode
* @returns {boolean}
*/
private static isProhibitedLoopBody(objectExpressionNode: ESTree.ObjectExpression): boolean {
let currentNode: ESTree.Node | undefined = objectExpressionNode;
while (currentNode) {
const parentNode: ESTree.Node | undefined = currentNode.parentNode;
if (!parentNode || parentNode === currentNode) {
break;
}
const isNonBlockLoopBody: boolean =
NodeGuards.isLoopStatementNode(parentNode) &&
parentNode.body === currentNode &&
!NodeGuards.isBlockStatementNode(currentNode);
if (isNonBlockLoopBody) {
return true;
}
if (NodeGuards.isFunctionNode(parentNode) || NodeGuards.isProgramNode(parentNode)) {
break;
}
currentNode = parentNode;
}
return false;
}
/**
* @param {ObjectExpression} objectExpressionNode
* @param {Node} objectExpressionNodeParentNode
* @returns {boolean}
*/
private static isProhibitedArrowFunctionExpression(
objectExpressionNode: ESTree.ObjectExpression,
objectExpressionNodeParentNode: ESTree.Node
): boolean {
return (
NodeGuards.isArrowFunctionExpressionNode(objectExpressionNodeParentNode) &&
objectExpressionNodeParentNode.body === objectExpressionNode
);
}
/**
* @param {ObjectExpression} objectExpressionNode
* @param {Node} objectExpressionHostNode
* @returns {boolean}
*/
private static isProhibitedSequenceExpression(
objectExpressionNode: ESTree.ObjectExpression,
objectExpressionHostNode: ESTree.Node
): boolean {
return (
NodeGuards.isExpressionStatementNode(objectExpressionHostNode) &&
NodeGuards.isSequenceExpressionNode(objectExpressionHostNode.expression) &&
objectExpressionHostNode.expression.expressions.some(
(expressionNode: ESTree.Expression) =>
NodeGuards.isCallExpressionNode(expressionNode) && NodeGuards.isSuperNode(expressionNode.callee)
)
);
}
/**
* @param {NodeTransformationStage} nodeTransformationStage
* @returns {IVisitor | null}
*/
public getVisitor(nodeTransformationStage: NodeTransformationStage): IVisitor | null {
if (!this.options.transformObjectKeys) {
return null;
}
switch (nodeTransformationStage) {
case NodeTransformationStage.Converting:
return {
leave: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
if (parentNode && NodeGuards.isObjectExpressionNode(node)) {
return this.transformNode(node, parentNode);
}
}
};
default:
return null;
}
}
/**
* replaces:
* var object = {
* foo: 1,
* bar: 2
* };
*
* on:
* var _0xabc123 = {};
* _0xabc123['foo'] = 1;
* _0xabc123['bar'] = 2;
* var object = _0xabc123;
*
* @param {ObjectExpression} objectExpressionNode
* @param {Node} parentNode
* @returns {NodeGuards}
*/
public transformNode(objectExpressionNode: ESTree.ObjectExpression, parentNode: ESTree.Node): ESTree.Node {
if (!objectExpressionNode.properties.length) {
return objectExpressionNode;
}
const hostStatement: ESTree.Statement = NodeStatementUtils.getRootStatementOfNode(objectExpressionNode);
if (
ObjectExpressionKeysTransformer.isProhibitedObjectExpressionNode(
objectExpressionNode,
parentNode,
hostStatement
)
) {
return objectExpressionNode;
}
return this.applyObjectExpressionKeysExtractorsRecursive(objectExpressionNode, hostStatement, 0);
}
/**
* @param {ObjectExpression} objectExpressionNode
* @param {Statement} hostStatement
* @param {number} extractorIndex
* @returns {Node}
*/
private applyObjectExpressionKeysExtractorsRecursive(
objectExpressionNode: ESTree.ObjectExpression,
hostStatement: ESTree.Statement,
extractorIndex: number
): ESTree.Node {
const objectExpressionExtractorNames = ObjectExpressionKeysTransformer.objectExpressionExtractorNames;
if (extractorIndex >= objectExpressionExtractorNames.length) {
return objectExpressionNode;
}
const objectExpressionExtractor: ObjectExpressionExtractor = objectExpressionExtractorNames[extractorIndex];
const {
nodeToReplace,
objectExpressionHostStatement: newObjectExpressionHostStatement,
objectExpressionNode: newObjectExpressionNode
} = this.objectExpressionExtractorFactory(objectExpressionExtractor).extract(
objectExpressionNode,
hostStatement
);
this.applyObjectExpressionKeysExtractorsRecursive(
newObjectExpressionNode,
newObjectExpressionHostStatement,
extractorIndex + 1
);
return nodeToReplace;
}
}