quamvoluptatem
Version:
JavaScript Obfuscation Tool.
365 lines (309 loc) • 8.72 kB
text/typescript
import { ok } from "assert";
import { getBlock, isBlock, getBlocks } from "../traverse";
import { Node, Location } from "./gen";
import { getIdentifierInfo, validateChain } from "./identifiers";
/**
* - `FunctionDeclaration`
* - `FunctionExpression`
* - `ArrowFunctionExpression`
* @param object
* @returns
*/
export function isFunction(object: Node): boolean {
return [
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
].includes(object && object.type);
}
/**
* The function context where the object is.
*
* - Determines if async context.
* - Determines variable context.
*
* @param object
* @param parents
*/
export function getFunction(object: Node, parents: Node[]): Node {
return parents.find((x) => isFunction(x));
}
/**
* Refers to the current function or Root node
* @param parents
*/
export function getVarContext(object: Node, parents: Node[]): Node {
var fn = getFunction(object, parents);
if (fn) {
return fn;
}
var top = parents[parents.length - 1] || object;
if (top) {
ok(top.type == "Program", "Root node not program, its " + top.type);
return top;
}
throw new Error("Missing root node");
}
/**
* `Function` or root node
* @param object
* @returns
*/
export function isVarContext(object: Node) {
return (
isFunction(object) ||
object.type == "Program" ||
object.type == "DoExpression"
); // Stage 1
}
/**
* `Block` or root node
* @param object
* @returns
*/
export function isLexContext(object: Node): boolean {
return isBlock(object) || object.type == "Program";
}
/**
* Either a `var context` or `lex context`
* @param object
* @returns
*/
export function isContext(object: Node): boolean {
return isVarContext(object) || isLexContext(object);
}
export function getContexts(object: Node, parents: Node[]): Node[] {
return [object, ...parents].filter((x) => isContext(x));
}
/**
* Refers to the current lexical block or Root node.
* @param parents
*/
export function getLexContext(object: Node, parents: Node[]): Node {
var block = getBlock(object, parents);
if (block) {
return block;
}
var top = parents[parents.length - 1];
if (!top) {
throw new Error("Missing root node");
}
}
export function getDefiningContext(o: Node, p: Node[]): Node {
validateChain(o, p);
ok(o.type == "Identifier");
var info = getIdentifierInfo(o, p);
ok(info.spec.isDefined);
if (info.isVariableDeclaration) {
var variableDeclaration = p.find((x) => x.type == "VariableDeclaration");
ok(variableDeclaration);
if (variableDeclaration.kind === "let") {
return getLexContext(o, p);
}
}
if (info.isFunctionDeclaration) {
return getVarContext(p[0], p.slice(1));
}
return getVarContext(o, p);
}
export function getReferencingContexts(o: Node, p: Node[]): Node[] {
validateChain(o, p);
ok(o.type == "Identifier");
var info = getIdentifierInfo(o, p);
ok(info.spec.isReferenced);
var assignmentPatternIndex = p.findIndex(
(x) => x.type == "AssignmentPattern"
);
if (assignmentPatternIndex != -1) {
if (
p[assignmentPatternIndex].right == (p[assignmentPatternIndex - 1] || o)
) {
var sliced = p.slice(assignmentPatternIndex);
var fnIndex = sliced.findIndex((x) => isFunction(x));
var associatedFn = sliced[fnIndex];
if (
fnIndex !== -1 &&
sliced[fnIndex].params == (sliced[fnIndex - 1] || o)
) {
if (associatedFn == getVarContext(o, p)) {
return isLexContext(associatedFn.body)
? [associatedFn, associatedFn.body]
: [associatedFn];
}
}
}
}
return [getVarContext(o, p), getLexContext(o, p)];
}
export function getBlockBody(block: Node): Node[] {
if (!block) {
throw new Error("no block body");
}
if (Array.isArray(block)) {
return block;
}
return getBlockBody(block.body);
}
export function getIndexDirect(object: Node, parent: Node[]): string {
return Object.keys(parent).find((x) => parent[x] == object);
}
/**
* Attempts to a delete a variable/functions declaration.
* @param object
* @param parents
*/
export function deleteDeclaration(object: Node, parents: Node[]) {
validateChain(object, parents);
// variables
var list = [object, ...parents];
var declaratorIndex = list.findIndex((x) => x.type == "VariableDeclarator");
if (declaratorIndex != -1) {
var declarator = list[declaratorIndex]; // {type: VariableDeclarator, id: Identifier, init: Literal|Expression...}
var declarations = list[declaratorIndex + 1]; // declarator[]
var VariableDeclaration = list[declaratorIndex + 2];
var body = list[declaratorIndex + 3];
deleteDirect(declarator, declarations);
if (VariableDeclaration.declarations.length == 0) {
deleteDirect(VariableDeclaration, body);
}
} else {
if (object.type != "FunctionDeclaration") {
throw new Error("No method to delete: " + object.type);
}
deleteDirect(object, parents[0]);
}
}
/**
* Object must be directly nested in parent
*/
export function deleteDirect(object: Node, parent: Node) {
if (!object) {
throw new Error("object undefined");
}
if (!parent) {
throw new Error("parent undefined");
}
validateChain(object, [parent]);
if (typeof parent === "object") {
if (Array.isArray(parent)) {
var index = parent.indexOf(object);
if (index != -1) {
// delete
parent.splice(index, 1);
} else {
console.log("parent=", parent);
console.log("object=", object);
throw new Error("index -1");
}
} else {
var keyName = Object.keys(parent).find((x) => parent[x] == object);
if (keyName) {
delete parent[keyName];
} else {
throw new Error("keyName undefined");
}
}
}
}
export function prepend(block: Node, ...nodes: Node[]) {
ok(!Array.isArray(block), "block should not be array");
if (block.type == "Program") {
var decs = 0;
block.body.forEach((stmt, i) => {
if (stmt.type == "ImportDeclaration") {
if (decs == i) {
decs++;
}
}
});
block.body.splice(decs, 0, ...nodes);
} else {
getBlockBody(block).unshift(...nodes);
}
}
export function append(block: Node, ...nodes: Node[]) {
ok(!Array.isArray(block), "block should not be array");
getBlockBody(block).push(...nodes);
}
export function clone<T>(object: T): T {
if (typeof object === "object" && object) {
if (Array.isArray(object)) {
var newArray = [] as unknown as any;
object.forEach((element) => {
newArray.push(clone(element));
});
return newArray;
} else {
var newObject = {} as T;
Object.keys(object).forEach((key) => {
if (!(key + "").startsWith("$")) {
newObject[key] = clone(object[key]);
}
});
return newObject;
}
}
return object as any;
}
export function isForInitialize(o, p) {
validateChain(o, p);
var forIndex = p.findIndex(
(x) =>
x.type == "ForStatement" ||
x.type == "ForInStatement" ||
x.type == "ForOfStatement"
);
if (forIndex !== -1) {
if (p[forIndex].type == "ForStatement") {
if (p[forIndex].init == p[forIndex - 1] || o) {
return true;
}
} else {
if (p[forIndex].left == p[forIndex - 1] || o) {
return true;
}
}
}
return false;
}
export function isInBranch(object: Node, parents: Node[], context: Node) {
ok(object);
ok(parents);
ok(context);
ok(parents.includes(context));
var definingContext =
parents[0].type == "FunctionDeclaration" && parents[0].id == object
? getVarContext(parents[0], parents.slice(1))
: getVarContext(object, parents);
var contextIndex = parents.findIndex((x) => x === context);
var slicedParents = parents.slice(0, contextIndex);
ok(!slicedParents.includes(object), "slicedParents includes object");
var slicedTypes = new Set(slicedParents.map((x) => x.type));
var isBranch = definingContext !== context;
if (!isBranch) {
if (
[
"IfStatement",
"ForStatement",
"ForInStatement",
"ForOfStatement",
"WhileStatement",
"DoWhileStatement",
"SwitchStatement",
"ConditionalExpression",
"LogicalExpression",
"TryStatement",
"ChainExpression",
"BinaryExpression",
"FunctionExpression",
"FunctionDeclaration",
"ArrowFunctionExpression",
"ClassExpression",
"ClassDeclaration",
].find((x) => slicedTypes.has(x))
) {
isBranch = true;
}
}
return isBranch;
}