diff options
author | Minteck <contact@minteck.org> | 2021-12-21 16:52:28 +0100 |
---|---|---|
committer | Minteck <contact@minteck.org> | 2021-12-21 16:52:28 +0100 |
commit | 46e43f4bde4a35785b4997b81e86cd19f046b69b (patch) | |
tree | c53c2f826f777f9d6b2d249dab556feb72a6c3a6 /src/node_modules/javascript-obfuscator/src/analyzers | |
download | langdetect-46e43f4bde4a35785b4997b81e86cd19f046b69b.tar.gz langdetect-46e43f4bde4a35785b4997b81e86cd19f046b69b.tar.bz2 langdetect-46e43f4bde4a35785b4997b81e86cd19f046b69b.zip |
Commit
Diffstat (limited to 'src/node_modules/javascript-obfuscator/src/analyzers')
9 files changed, 969 insertions, 0 deletions
diff --git a/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/CallsGraphAnalyzer.ts b/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/CallsGraphAnalyzer.ts new file mode 100644 index 0000000..68f22ec --- /dev/null +++ b/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/CallsGraphAnalyzer.ts @@ -0,0 +1,170 @@ +import { inject, injectable, } from 'inversify'; +import { ServiceIdentifiers } from '../../container/ServiceIdentifiers'; + +import * as estraverse from 'estraverse'; +import * as ESTree from 'estree'; + +import { TCalleeDataExtractorFactory } from '../../types/container/calls-graph-analyzer/TCalleeDataExtractorFactory'; + +import { ICalleeData } from '../../interfaces/analyzers/calls-graph-analyzer/ICalleeData'; +import { ICallsGraphAnalyzer } from '../../interfaces/analyzers/calls-graph-analyzer/ICallsGraphAnalyzer'; +import { ICallsGraphData } from '../../interfaces/analyzers/calls-graph-analyzer/ICallsGraphData'; + +import { CalleeDataExtractor } from '../../enums/analyzers/calls-graph-analyzer/CalleeDataExtractor'; + +import { NodeGuards } from '../../node/NodeGuards'; +import { NodeStatementUtils } from '../../node/NodeStatementUtils'; + +/** + * This class generates a data with a graph of functions calls + * + * For example: + * + * function Foo () { + * var baz = function () { + * + * } + * + * baz(); + * } + * + * foo(); + * + * Will generate a structure like: + * + * [ + * { + * callee: FOO_FUNCTION_NODE + * name: 'Foo', + * trace: [ + * { + * callee: BAZ_FUNCTION_NODE, + * name: 'baz, + * trace: [] + * } + * ] + * } + * ] + */ +@injectable() +export class CallsGraphAnalyzer implements ICallsGraphAnalyzer { + /** + * @type {CalleeDataExtractor[]} + */ + private static readonly calleeDataExtractorsList: CalleeDataExtractor[] = [ + CalleeDataExtractor.FunctionDeclarationCalleeDataExtractor, + CalleeDataExtractor.FunctionExpressionCalleeDataExtractor, + CalleeDataExtractor.ObjectExpressionCalleeDataExtractor + ]; + + /** + * @type {number} + */ + private static readonly limitThresholdActivationLength: number = 25; + + /** + * @type {number} + */ + private static readonly limitThreshold: number = 0.002; + + /** + * @type {TCalleeDataExtractorFactory} + */ + private readonly calleeDataExtractorFactory: TCalleeDataExtractorFactory; + + public constructor ( + @inject(ServiceIdentifiers.Factory__ICalleeDataExtractor) calleeDataExtractorFactory: TCalleeDataExtractorFactory + ) { + this.calleeDataExtractorFactory = calleeDataExtractorFactory; + } + + /** + * @param {number} blockScopeBodyLength + * @returns {number} + */ + public static getLimitIndex (blockScopeBodyLength: number): number { + const lastIndex: number = blockScopeBodyLength - 1; + const limitThresholdActivationIndex: number = CallsGraphAnalyzer.limitThresholdActivationLength - 1; + + let limitIndex: number = lastIndex; + + if (lastIndex > limitThresholdActivationIndex) { + limitIndex = Math.round( + limitThresholdActivationIndex + (lastIndex * CallsGraphAnalyzer.limitThreshold) + ); + + if (limitIndex > lastIndex) { + limitIndex = lastIndex; + } + } + + return limitIndex; + } + + /** + * @param {Program} astTree + * @returns {ICallsGraphData[]} + */ + public analyze (astTree: ESTree.Program): ICallsGraphData[] { + return this.analyzeRecursive(astTree.body); + } + + /** + * @param {NodeGuards[]} blockScopeBody + * @returns {ICallsGraphData[]} + */ + private analyzeRecursive (blockScopeBody: ESTree.Node[]): ICallsGraphData[] { + const limitIndex: number = CallsGraphAnalyzer.getLimitIndex(blockScopeBody.length); + const callsGraphData: ICallsGraphData[] = []; + const blockScopeBodyLength: number = blockScopeBody.length; + + for (let index: number = 0; index < blockScopeBodyLength; index++) { + if (index > limitIndex) { + break; + } + + const blockScopeBodyNode: ESTree.Node = blockScopeBody[index]; + + estraverse.traverse(blockScopeBodyNode, { + enter: (node: ESTree.Node): estraverse.VisitorOption | void => { + if (!NodeGuards.isCallExpressionNode(node)) { + return; + } + + if (blockScopeBodyNode.parentNode !== NodeStatementUtils.getParentNodeWithStatements(node)) { + return estraverse.VisitorOption.Skip; + } + + this.analyzeCallExpressionNode(callsGraphData, blockScopeBody, node); + } + }); + } + + return callsGraphData; + } + + /** + * @param {ICallsGraphData[]} callsGraphData + * @param {NodeGuards[]} blockScopeBody + * @param {CallExpression} callExpressionNode + */ + private analyzeCallExpressionNode ( + callsGraphData: ICallsGraphData[], + blockScopeBody: ESTree.Node[], + callExpressionNode: ESTree.CallExpression + ): void { + CallsGraphAnalyzer.calleeDataExtractorsList.forEach((calleeDataExtractorName: CalleeDataExtractor) => { + const calleeData: ICalleeData | null = this.calleeDataExtractorFactory(calleeDataExtractorName) + .extract(blockScopeBody, callExpressionNode.callee); + + if (!calleeData) { + return; + } + + callsGraphData.push({ + ...calleeData, + callsGraph: this.analyzeRecursive(calleeData.callee.body) + }); + }); + } +} diff --git a/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/AbstractCalleeDataExtractor.ts b/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/AbstractCalleeDataExtractor.ts new file mode 100644 index 0000000..34381f6 --- /dev/null +++ b/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/AbstractCalleeDataExtractor.ts @@ -0,0 +1,16 @@ +import { injectable } from 'inversify'; + +import * as ESTree from 'estree'; + +import { ICalleeData } from '../../../interfaces/analyzers/calls-graph-analyzer/ICalleeData'; +import { ICalleeDataExtractor } from '../../../interfaces/analyzers/calls-graph-analyzer/ICalleeDataExtractor'; + +@injectable() +export abstract class AbstractCalleeDataExtractor implements ICalleeDataExtractor { + /** + * @param {Node[]} blockScopeBody + * @param {Node} callee + * @returns {ICalleeData} + */ + public abstract extract (blockScopeBody: ESTree.Node[], callee: ESTree.Node): ICalleeData | null; +} diff --git a/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/FunctionDeclarationCalleeDataExtractor.ts b/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/FunctionDeclarationCalleeDataExtractor.ts new file mode 100644 index 0000000..aeb89cf --- /dev/null +++ b/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/FunctionDeclarationCalleeDataExtractor.ts @@ -0,0 +1,59 @@ +import { injectable } from 'inversify'; + +import * as estraverse from 'estraverse'; +import * as ESTree from 'estree'; + +import { ICalleeData } from '../../../interfaces/analyzers/calls-graph-analyzer/ICalleeData'; + +import { AbstractCalleeDataExtractor } from './AbstractCalleeDataExtractor'; +import { NodeGuards } from '../../../node/NodeGuards'; +import { NodeStatementUtils } from '../../../node/NodeStatementUtils'; + +@injectable() +export class FunctionDeclarationCalleeDataExtractor extends AbstractCalleeDataExtractor { + /** + * @param {NodeGuards[]} blockScopeBody + * @param {Identifier} callee + * @returns {ICalleeData} + */ + public extract (blockScopeBody: ESTree.Node[], callee: ESTree.Identifier): ICalleeData | null { + if (!NodeGuards.isIdentifierNode(callee)) { + return null; + } + + const calleeBlockStatement: ESTree.BlockStatement | null = this.getCalleeBlockStatement( + NodeStatementUtils.getParentNodeWithStatements(blockScopeBody[0]), + callee.name + ); + + if (!calleeBlockStatement) { + return null; + } + + return { + callee: calleeBlockStatement, + name: callee.name + }; + } + + /** + * @param {NodeGuards} targetNode + * @param {string} name + * @returns {BlockStatement} + */ + private getCalleeBlockStatement (targetNode: ESTree.Node, name: string): ESTree.BlockStatement | null { + let calleeBlockStatement: ESTree.BlockStatement | null = null; + + estraverse.traverse(targetNode, { + enter: (node: ESTree.Node): estraverse.VisitorOption | void => { + if (NodeGuards.isFunctionDeclarationNode(node) && node.id.name === name) { + calleeBlockStatement = node.body; + + return estraverse.VisitorOption.Break; + } + } + }); + + return calleeBlockStatement; + } +} diff --git a/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/FunctionExpressionCalleeDataExtractor.ts b/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/FunctionExpressionCalleeDataExtractor.ts new file mode 100644 index 0000000..42ac074 --- /dev/null +++ b/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/FunctionExpressionCalleeDataExtractor.ts @@ -0,0 +1,70 @@ +import { injectable } from 'inversify'; + +import * as estraverse from 'estraverse'; +import * as ESTree from 'estree'; + +import { ICalleeData } from '../../../interfaces/analyzers/calls-graph-analyzer/ICalleeData'; + +import { AbstractCalleeDataExtractor } from './AbstractCalleeDataExtractor'; +import { NodeGuards } from '../../../node/NodeGuards'; +import { NodeStatementUtils } from '../../../node/NodeStatementUtils'; + +@injectable() +export class FunctionExpressionCalleeDataExtractor extends AbstractCalleeDataExtractor { + /** + * @param {NodeGuards[]} blockScopeBody + * @param {Identifier} callee + * @returns {ICalleeData} + */ + public extract (blockScopeBody: ESTree.Node[], callee: ESTree.Identifier | ESTree.FunctionExpression): ICalleeData | null { + let calleeName: string | null = null; + let calleeBlockStatement: ESTree.BlockStatement | null = null; + + if (NodeGuards.isIdentifierNode(callee)) { + calleeName = callee.name; + calleeBlockStatement = this.getCalleeBlockStatement( + NodeStatementUtils.getParentNodeWithStatements(blockScopeBody[0]), + callee.name + ); + } else if (NodeGuards.isFunctionExpressionNode(callee)) { + calleeName = null; + calleeBlockStatement = callee.body; + } + + if (!calleeBlockStatement) { + return null; + } + + return { + callee: calleeBlockStatement, + name: calleeName + }; + } + + /** + * @param {NodeGuards} targetNode + * @param {string} name + * @returns {BlockStatement} + */ + private getCalleeBlockStatement (targetNode: ESTree.Node, name: string): ESTree.BlockStatement | null { + let calleeBlockStatement: ESTree.BlockStatement | null = null; + + estraverse.traverse(targetNode, { + enter: (node: ESTree.Node, parentNode: ESTree.Node | null): estraverse.VisitorOption | void => { + if ( + NodeGuards.isFunctionExpressionNode(node) && + parentNode && + NodeGuards.isVariableDeclaratorNode(parentNode) && + NodeGuards.isIdentifierNode(parentNode.id) && + parentNode.id.name === name + ) { + calleeBlockStatement = node.body; + + return estraverse.VisitorOption.Break; + } + } + }); + + return calleeBlockStatement; + } +} diff --git a/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/ObjectExpressionCalleeDataExtractor.ts b/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/ObjectExpressionCalleeDataExtractor.ts new file mode 100644 index 0000000..6937589 --- /dev/null +++ b/src/node_modules/javascript-obfuscator/src/analyzers/calls-graph-analyzer/callee-data-extractors/ObjectExpressionCalleeDataExtractor.ts @@ -0,0 +1,177 @@ +import { injectable } from 'inversify'; + +import * as estraverse from 'estraverse'; +import * as ESTree from 'estree'; + +import { TObjectMembersCallsChain } from '../../../types/analyzers/calls-graph-analyzer/TObjectMembersCallsChain'; + +import { ICalleeData } from '../../../interfaces/analyzers/calls-graph-analyzer/ICalleeData'; + +import { AbstractCalleeDataExtractor } from './AbstractCalleeDataExtractor'; +import { NodeGuards } from '../../../node/NodeGuards'; +import { NodeStatementUtils } from '../../../node/NodeStatementUtils'; + +@injectable() +export class ObjectExpressionCalleeDataExtractor extends AbstractCalleeDataExtractor { + /** + * @param {Property} propertyNode + * @param {string | number} nextItemInCallsChain + * @returns {boolean} + */ + private static isValidTargetPropertyNode (propertyNode: ESTree.Property, nextItemInCallsChain: string | number): boolean { + if (!propertyNode.key) { + return false; + } + + const isTargetPropertyNodeWithIdentifierKey: boolean = + NodeGuards.isIdentifierNode(propertyNode.key) && propertyNode.key.name === nextItemInCallsChain; + const isTargetPropertyNodeWithLiteralKey: boolean = + NodeGuards.isLiteralNode(propertyNode.key) && + Boolean(propertyNode.key.value) && + propertyNode.key.value === nextItemInCallsChain; + + return isTargetPropertyNodeWithIdentifierKey || isTargetPropertyNodeWithLiteralKey; + } + + /** + * @param {NodeGuards[]} blockScopeBody + * @param {MemberExpression} callee + * @returns {ICalleeData} + */ + public extract (blockScopeBody: ESTree.Node[], callee: ESTree.MemberExpression): ICalleeData | null { + if (!NodeGuards.isMemberExpressionNode(callee)) { + return null; + } + + const objectMembersCallsChain: TObjectMembersCallsChain = this.createObjectMembersCallsChain([], callee); + + if (!objectMembersCallsChain.length) { + return null; + } + + const functionExpressionName: string | number | null = objectMembersCallsChain[objectMembersCallsChain.length - 1]; + const calleeBlockStatement: ESTree.BlockStatement | null = this.getCalleeBlockStatement( + NodeStatementUtils.getParentNodeWithStatements(blockScopeBody[0]), + objectMembersCallsChain + ); + + if (!calleeBlockStatement) { + return null; + } + + return { + callee: calleeBlockStatement, + name: functionExpressionName + }; + } + + /** + * Creates array with MemberExpression calls chain. + * + * Example: object.foo.bar(); // ['object', 'foo', 'bar'] + * + * @param {TObjectMembersCallsChain} currentChain + * @param {MemberExpression} memberExpression + * @returns {TObjectMembersCallsChain} + */ + private createObjectMembersCallsChain ( + currentChain: TObjectMembersCallsChain, + memberExpression: ESTree.MemberExpression + ): TObjectMembersCallsChain { + // first step: processing memberExpression `property` property + if (NodeGuards.isIdentifierNode(memberExpression.property) && !memberExpression.computed) { + currentChain.unshift(memberExpression.property.name); + } else if ( + NodeGuards.isLiteralNode(memberExpression.property) && + ( + typeof memberExpression.property.value === 'string' || + typeof memberExpression.property.value === 'number' + ) + ) { + currentChain.unshift(memberExpression.property.value); + } else { + return currentChain; + } + + // second step: processing memberExpression `object` property + if (NodeGuards.isMemberExpressionNode(memberExpression.object)) { + return this.createObjectMembersCallsChain(currentChain, memberExpression.object); + } else if (NodeGuards.isIdentifierNode(memberExpression.object)) { + currentChain.unshift(memberExpression.object.name); + } + + return currentChain; + } + + /** + * @param {NodeGuards} targetNode + * @param {TObjectMembersCallsChain} objectMembersCallsChain + * @returns {BlockStatement} + */ + private getCalleeBlockStatement ( + targetNode: ESTree.Node, + objectMembersCallsChain: TObjectMembersCallsChain + ): ESTree.BlockStatement | null { + const objectName: string | number | undefined = objectMembersCallsChain.shift(); + + if (!objectName) { + return null; + } + + let calleeBlockStatement: ESTree.BlockStatement | null = null; + + estraverse.traverse(targetNode, { + enter: (node: ESTree.Node): estraverse.VisitorOption | void => { + if ( + NodeGuards.isVariableDeclaratorNode(node) && + NodeGuards.isIdentifierNode(node.id) && + node.init && + NodeGuards.isObjectExpressionNode(node.init) && + node.id.name === objectName + ) { + calleeBlockStatement = this.findCalleeBlockStatement(node.init.properties, objectMembersCallsChain); + + return estraverse.VisitorOption.Break; + } + } + }); + + return calleeBlockStatement; + } + + /** + * @param {Property[]} objectExpressionProperties + * @param {TObjectMembersCallsChain} objectMembersCallsChain + * @returns {BlockStatement} + */ + private findCalleeBlockStatement ( + objectExpressionProperties: (ESTree.Property | ESTree.SpreadElement)[], + objectMembersCallsChain: TObjectMembersCallsChain + ): ESTree.BlockStatement | null { + const nextItemInCallsChain: string | number | undefined = objectMembersCallsChain.shift(); + + if (!nextItemInCallsChain) { + return null; + } + + for (const propertyNode of objectExpressionProperties) { + if (!NodeGuards.isPropertyNode(propertyNode)) { + continue; + } + + if (!ObjectExpressionCalleeDataExtractor.isValidTargetPropertyNode(propertyNode, nextItemInCallsChain)) { + continue; + } + + if (NodeGuards.isObjectExpressionNode(propertyNode.value)) { + return this.findCalleeBlockStatement(propertyNode.value.properties, objectMembersCallsChain); + } + + if (NodeGuards.isFunctionExpressionNode(propertyNode.value)) { + return propertyNode.value.body; + } + } + + return null; + } +} diff --git a/src/node_modules/javascript-obfuscator/src/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.ts b/src/node_modules/javascript-obfuscator/src/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.ts new file mode 100644 index 0000000..483f817 --- /dev/null +++ b/src/node_modules/javascript-obfuscator/src/analyzers/number-numerical-expression-analyzer/NumberNumericalExpressionAnalyzer.ts @@ -0,0 +1,130 @@ +import { injectable, inject } from 'inversify'; + +import { TNumberNumericalExpressionData } from '../../types/analyzers/number-numerical-expression-analyzer/TNumberNumericalExpressionData'; + +import { INumberNumericalExpressionAnalyzer } from '../../interfaces/analyzers/number-numerical-expression-analyzer/INumberNumericalExpressionAnalyzer'; +import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator'; + +import { ServiceIdentifiers } from '../../container/ServiceIdentifiers'; + +import { NumberUtils } from '../../utils/NumberUtils'; + +/** + * Based on https://gist.github.com/da411d/0e59f79dcf4603cdabf0024a10eeb6fe + */ +@injectable() +export class NumberNumericalExpressionAnalyzer implements INumberNumericalExpressionAnalyzer { + /** + * @type {number} + */ + private static readonly additionalParts: number = 3; + + /** + * @type {Map<number, number[]>} + */ + private readonly numberFactorsMap: Map<number, number[]> = new Map(); + + /** + * @type {IRandomGenerator} + */ + private readonly randomGenerator: IRandomGenerator; + + /** + * @param {IRandomGenerator} randomGenerator + */ + public constructor ( + @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator + ) { + this.randomGenerator = randomGenerator; + } + + /** + * @param {number} number + * @returns {TNumberNumericalExpressionData} + */ + public analyze (number: number): TNumberNumericalExpressionData { + if (isNaN(number)) { + throw new Error('Given value is NaN'); + } + + if (NumberUtils.isUnsafeNumber(number)) { + return [number]; + } + + const additionParts: number[] = this.generateAdditionParts(number); + + return additionParts.map((addition: number) => this.mixWithMultiplyParts(addition)); + } + + /** + * @param {number} number + * @returns {number[]} + */ + private generateAdditionParts (number: number): number[] { + const additionParts = []; + + const upperNumberLimit: number = Math.min(Math.abs(number * 2), Number.MAX_SAFE_INTEGER); + + const from: number = Math.min(-10000, -upperNumberLimit); + const to: number = Math.max(10000, upperNumberLimit); + + let temporarySum = 0; + + for (let i = 0; i < NumberNumericalExpressionAnalyzer.additionalParts; i++) { + if (i < NumberNumericalExpressionAnalyzer.additionalParts - 1) { + // trailing parts + + let addition: number = this.randomGenerator.getRandomInteger(from, to); + const isUnsafeCombination: boolean = NumberUtils.isUnsafeNumber(temporarySum + addition); + + // we have to flip sign if total expression sum overflows over safe integer limits + if (isUnsafeCombination) { + addition = -addition; + } + + additionParts.push(addition); + temporarySum += addition; + } else { + const combination: number = number - temporarySum; + const isUnsafeCombination: boolean = NumberUtils.isUnsafeNumber(combination); + + // last part + if (isUnsafeCombination) { + additionParts.push(0 - temporarySum); + additionParts.push(number); + } else { + additionParts.push(combination); + } + } + } + + return additionParts; + } + + /** + * @param {number} number + * @returns {number | number[]} + */ + private mixWithMultiplyParts (number: number): number | number[] { + const shouldMixWithMultiplyParts: boolean = this.randomGenerator.getMathRandom() > 0.5; + + if (!shouldMixWithMultiplyParts || number === 0) { + return number; + } + + let factors: number[] | null = this.numberFactorsMap.get(number) ?? null; + + if (!factors) { + factors = NumberUtils.getFactors(number); + this.numberFactorsMap.set(number, factors); + } + + if (!factors.length) { + return number; + } + + const factor: number = factors[this.randomGenerator.getRandomInteger(0, factors.length - 1)]; + + return [factor, number / factor]; + } +} diff --git a/src/node_modules/javascript-obfuscator/src/analyzers/prevailing-kind-of-variables-analyzer/PrevailingKindOfVariablesAnalyzer.ts b/src/node_modules/javascript-obfuscator/src/analyzers/prevailing-kind-of-variables-analyzer/PrevailingKindOfVariablesAnalyzer.ts new file mode 100644 index 0000000..f01f9f1 --- /dev/null +++ b/src/node_modules/javascript-obfuscator/src/analyzers/prevailing-kind-of-variables-analyzer/PrevailingKindOfVariablesAnalyzer.ts @@ -0,0 +1,61 @@ +import { inject, injectable, } from 'inversify'; +import { ServiceIdentifiers } from '../../container/ServiceIdentifiers'; + +import * as estraverse from 'estraverse'; +import * as ESTree from 'estree'; + +import { IArrayUtils } from '../../interfaces/utils/IArrayUtils'; +import { IPrevailingKindOfVariablesAnalyzer } from '../../interfaces/analyzers/calls-graph-analyzer/IPrevailingKindOfVariablesAnalyzer'; + +import { NodeGuards } from '../../node/NodeGuards'; + +@injectable() +export class PrevailingKindOfVariablesAnalyzer implements IPrevailingKindOfVariablesAnalyzer { + /** + * @type {ESTree.VariableDeclaration['kind']} + */ + private static readonly defaultKindOfVariables: ESTree.VariableDeclaration['kind'] = 'var'; + + /** + * @type {IArrayUtils} + */ + private readonly arrayUtils: IArrayUtils; + + /** + * @type {ESTree.VariableDeclaration['kind']} + */ + private prevailingKindOfVariables: ESTree.VariableDeclaration['kind'] = PrevailingKindOfVariablesAnalyzer.defaultKindOfVariables; + + public constructor ( + @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils + ) { + this.arrayUtils = arrayUtils; + } + + /** + * @param {Program} astTree + */ + public analyze (astTree: ESTree.Program): void { + const variableKinds: ESTree.VariableDeclaration['kind'][] = []; + + estraverse.traverse(astTree, { + enter: (node: ESTree.Node): estraverse.VisitorOption | void => { + if (!NodeGuards.isVariableDeclarationNode(node)) { + return; + } + + variableKinds.push(node.kind); + } + }); + + this.prevailingKindOfVariables = this.arrayUtils.findMostOccurringElement(variableKinds) + ?? PrevailingKindOfVariablesAnalyzer.defaultKindOfVariables; + } + + /** + * @returns {VariableDeclaration["kind"]} + */ + public getPrevailingKind (): ESTree.VariableDeclaration['kind'] { + return this.prevailingKindOfVariables; + } +} diff --git a/src/node_modules/javascript-obfuscator/src/analyzers/scope-analyzer/ScopeAnalyzer.ts b/src/node_modules/javascript-obfuscator/src/analyzers/scope-analyzer/ScopeAnalyzer.ts new file mode 100644 index 0000000..7641e7a --- /dev/null +++ b/src/node_modules/javascript-obfuscator/src/analyzers/scope-analyzer/ScopeAnalyzer.ts @@ -0,0 +1,152 @@ +import { injectable, } from 'inversify'; + +import * as eslintScope from 'eslint-scope'; +import * as estraverse from 'estraverse'; +import * as ESTree from 'estree'; + +import { IScopeAnalyzer } from '../../interfaces/analyzers/scope-analyzer/IScopeAnalyzer'; + +import { ecmaVersion } from '../../constants/EcmaVersion'; + +import { NodeGuards } from '../../node/NodeGuards'; + +@injectable() +export class ScopeAnalyzer implements IScopeAnalyzer { + /** + * @type {eslintScope.AnalysisOptions} + */ + private static readonly eslintScopeOptions: eslintScope.AnalysisOptions = { + ecmaVersion, + optimistic: true + }; + + /** + * @type {acorn.Options['sourceType'][]} + */ + private static readonly sourceTypes: acorn.Options['sourceType'][] = [ + 'script', + 'module' + ]; + + /** + * @type {number} + */ + private static readonly emptyRangeValue: number = 0; + + /** + * @type {eslintScope.ScopeManager | null} + */ + private scopeManager: eslintScope.ScopeManager | null = null; + + /** + * `eslint-scope` reads `ranges` property of a nodes + * Should attach that property to the some custom nodes + * + * @param {Node} astTree + */ + private static attachMissingRanges (astTree: ESTree.Node): void { + estraverse.replace(astTree, { + enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node => { + if (!node.range) { + node.range = [ + parentNode?.range?.[0] ?? ScopeAnalyzer.emptyRangeValue, + parentNode?.range?.[1] ?? ScopeAnalyzer.emptyRangeValue + ]; + } + + return node; + } + }); + } + + /** + * @param {Node} node + * @returns {boolean} + */ + private static isRootNode (node: ESTree.Node): boolean { + return NodeGuards.isProgramNode(node) || node.parentNode === node; + } + + /** + * @param {Program} astTree + */ + public analyze (astTree: ESTree.Node): void { + const sourceTypeLength: number = ScopeAnalyzer.sourceTypes.length; + + ScopeAnalyzer.attachMissingRanges(astTree); + + for (let i: number = 0; i < sourceTypeLength; i++) { + try { + this.scopeManager = eslintScope.analyze(astTree, { + ...ScopeAnalyzer.eslintScopeOptions, + sourceType: ScopeAnalyzer.sourceTypes[i] + }); + + return; + } catch (error) { + if (i < sourceTypeLength - 1) { + continue; + } + + throw new Error(error); + } + } + + throw new Error('Scope analyzing error'); + } + + /** + * @param {Node} node + * @returns {Scope} + */ + public acquireScope (node: ESTree.Node): eslintScope.Scope { + if (!this.scopeManager) { + throw new Error('Scope manager is not defined'); + } + + const scope: eslintScope.Scope | null = this.scopeManager.acquire( + node, + ScopeAnalyzer.isRootNode(node) + ); + + if (!scope) { + throw new Error('Cannot acquire scope for node'); + } + + this.sanitizeScopes(scope); + + return scope; + } + + /** + * @param {Scope} scope + */ + private sanitizeScopes (scope: eslintScope.Scope): void { + scope.childScopes.forEach((childScope: eslintScope.Scope) => { + // fix of class scopes + // trying to move class scope references to the parent scope + if (childScope.type === 'class' && childScope.upper) { + if (!childScope.variables.length) { + return; + } + + // class name variable is always first + const classNameVariable: eslintScope.Variable = childScope.variables[0]; + + const upperVariable: eslintScope.Variable | undefined = childScope.upper.variables + .find((variable: eslintScope.Variable) => { + const isValidClassNameVariable: boolean = classNameVariable.defs + .some((definition: eslintScope.Definition) => definition.type === 'ClassName'); + + return isValidClassNameVariable && variable.name === classNameVariable.name; + }); + + upperVariable?.references.push(...childScope.variables[0].references); + } + }); + + for (const childScope of scope.childScopes) { + this.sanitizeScopes(childScope); + } + } +} diff --git a/src/node_modules/javascript-obfuscator/src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts b/src/node_modules/javascript-obfuscator/src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts new file mode 100644 index 0000000..3b850bd --- /dev/null +++ b/src/node_modules/javascript-obfuscator/src/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.ts @@ -0,0 +1,134 @@ +import { inject, injectable, } from 'inversify'; +import { ServiceIdentifiers } from '../../container/ServiceIdentifiers'; + +import * as estraverse from 'estraverse'; +import * as ESTree from 'estree'; + +import { IOptions } from '../../interfaces/options/IOptions'; +import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator'; +import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage'; +import { IStringArrayStorageAnalyzer } from '../../interfaces/analyzers/string-array-storage-analyzer/IStringArrayStorageAnalyzer'; +import { IStringArrayStorageItemData } from '../../interfaces/storages/string-array-transformers/IStringArrayStorageItem'; + +import { NodeGuards } from '../../node/NodeGuards'; +import { NodeLiteralUtils } from '../../node/NodeLiteralUtils'; +import { NodeMetadata } from '../../node/NodeMetadata'; + +/** + * Adds values of literal nodes to the string array storage + */ +@injectable() +export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer { + /** + * @type {number} + */ + private static readonly minimumLengthForStringArray: number = 3; + + /** + * @type {IOptions} + */ + private readonly options: IOptions; + + /** + * @type {randomGenerator} + */ + private readonly randomGenerator: IRandomGenerator; + + /** + * @type {IStringArrayStorage} + */ + private readonly stringArrayStorage: IStringArrayStorage; + + /** + * @type {Map<ESTree.Literal, IStringArrayStorageItemData>} + */ + private readonly stringArrayStorageData: Map<ESTree.Literal, IStringArrayStorageItemData> = new Map(); + + /** + * @param {IStringArrayStorage} stringArrayStorage + * @param {IRandomGenerator} randomGenerator + * @param {IOptions} options + */ + public constructor ( + @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage, + @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator, + @inject(ServiceIdentifiers.IOptions) options: IOptions, + ) { + this.stringArrayStorage = stringArrayStorage; + this.randomGenerator = randomGenerator; + this.options = options; + } + + /** + * @param {Program} astTree + */ + public analyze (astTree: ESTree.Program): void { + if (!this.options.stringArray) { + return; + } + + estraverse.traverse(astTree, { + enter: (node: ESTree.Node, parentNode: ESTree.Node | null): estraverse.VisitorOption | void => { + if (!parentNode) { + return; + } + + if (NodeMetadata.isIgnoredNode(node)) { + return estraverse.VisitorOption.Skip; + } + + if (!NodeGuards.isLiteralNode(node)) { + return; + } + + this.analyzeLiteralNode(node, parentNode); + } + }); + } + + /** + * @param {Literal} literalNode + * @returns {IStringArrayStorageItemData | undefined} + */ + public getItemDataForLiteralNode (literalNode: ESTree.Literal): IStringArrayStorageItemData | undefined { + return this.stringArrayStorageData.get(literalNode); + } + + /** + * @param {Literal} literalNode + * @param {Node} parentNode + */ + private analyzeLiteralNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): void { + if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) { + return; + } + + if (NodeLiteralUtils.isProhibitedLiteralNode(literalNode, parentNode)) { + return; + } + + if (!this.shouldAddValueToStringArray(literalNode)) { + return; + } + + this.stringArrayStorageData.set( + literalNode, + this.stringArrayStorage.getOrThrow(literalNode.value) + ); + } + + /** + * @param {(SimpleLiteral & {value: string})} literalNode + * @returns {boolean} + */ + private shouldAddValueToStringArray (literalNode: ESTree.Literal & {value: string}): boolean { + const isForceTransformNode: boolean = NodeMetadata.isForceTransformNode(literalNode); + + if (isForceTransformNode) { + return true; + } + + return literalNode.value.length >= StringArrayStorageAnalyzer.minimumLengthForStringArray + && this.randomGenerator.getMathRandom() <= this.options.stringArrayThreshold; + } +} |