ObjectExpressionCalleeDataExtractor.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { injectable } from 'inversify';
  2. import * as estraverse from '@javascript-obfuscator/estraverse';
  3. import * as ESTree from 'estree';
  4. import { TObjectMembersCallsChain } from '../../../types/analyzers/calls-graph-analyzer/TObjectMembersCallsChain';
  5. import { ICalleeData } from '../../../interfaces/analyzers/calls-graph-analyzer/ICalleeData';
  6. import { AbstractCalleeDataExtractor } from './AbstractCalleeDataExtractor';
  7. import { NodeGuards } from '../../../node/NodeGuards';
  8. import { NodeStatementUtils } from '../../../node/NodeStatementUtils';
  9. @injectable()
  10. export class ObjectExpressionCalleeDataExtractor extends AbstractCalleeDataExtractor {
  11. /**
  12. * @param {Property} propertyNode
  13. * @param {string | number} nextItemInCallsChain
  14. * @returns {boolean}
  15. */
  16. private static isValidTargetPropertyNode (propertyNode: ESTree.Property, nextItemInCallsChain: string | number): boolean {
  17. if (!propertyNode.key) {
  18. return false;
  19. }
  20. const isTargetPropertyNodeWithIdentifierKey: boolean =
  21. NodeGuards.isIdentifierNode(propertyNode.key) && propertyNode.key.name === nextItemInCallsChain;
  22. const isTargetPropertyNodeWithLiteralKey: boolean =
  23. NodeGuards.isLiteralNode(propertyNode.key) &&
  24. Boolean(propertyNode.key.value) &&
  25. propertyNode.key.value === nextItemInCallsChain;
  26. return isTargetPropertyNodeWithIdentifierKey || isTargetPropertyNodeWithLiteralKey;
  27. }
  28. /**
  29. * @param {NodeGuards[]} blockScopeBody
  30. * @param {MemberExpression} callee
  31. * @returns {ICalleeData}
  32. */
  33. public extract (blockScopeBody: ESTree.Node[], callee: ESTree.MemberExpression): ICalleeData | null {
  34. if (!NodeGuards.isMemberExpressionNode(callee)) {
  35. return null;
  36. }
  37. const objectMembersCallsChain: TObjectMembersCallsChain = this.createObjectMembersCallsChain([], callee);
  38. if (!objectMembersCallsChain.length) {
  39. return null;
  40. }
  41. const functionExpressionName: string | number | null = objectMembersCallsChain[objectMembersCallsChain.length - 1];
  42. const calleeBlockStatement: ESTree.BlockStatement | null = this.getCalleeBlockStatement(
  43. NodeStatementUtils.getParentNodeWithStatements(blockScopeBody[0]),
  44. objectMembersCallsChain
  45. );
  46. if (!calleeBlockStatement) {
  47. return null;
  48. }
  49. return {
  50. callee: calleeBlockStatement,
  51. name: functionExpressionName
  52. };
  53. }
  54. /**
  55. * Creates array with MemberExpression calls chain.
  56. *
  57. * Example: object.foo.bar(); // ['object', 'foo', 'bar']
  58. *
  59. * @param {TObjectMembersCallsChain} currentChain
  60. * @param {MemberExpression} memberExpression
  61. * @returns {TObjectMembersCallsChain}
  62. */
  63. private createObjectMembersCallsChain (
  64. currentChain: TObjectMembersCallsChain,
  65. memberExpression: ESTree.MemberExpression
  66. ): TObjectMembersCallsChain {
  67. // first step: processing memberExpression `property` property
  68. if (NodeGuards.isIdentifierNode(memberExpression.property) && !memberExpression.computed) {
  69. currentChain.unshift(memberExpression.property.name);
  70. } else if (
  71. NodeGuards.isLiteralNode(memberExpression.property) &&
  72. (
  73. typeof memberExpression.property.value === 'string' ||
  74. typeof memberExpression.property.value === 'number'
  75. )
  76. ) {
  77. currentChain.unshift(memberExpression.property.value);
  78. } else {
  79. return currentChain;
  80. }
  81. // second step: processing memberExpression `object` property
  82. if (NodeGuards.isMemberExpressionNode(memberExpression.object)) {
  83. return this.createObjectMembersCallsChain(currentChain, memberExpression.object);
  84. } else if (NodeGuards.isIdentifierNode(memberExpression.object)) {
  85. currentChain.unshift(memberExpression.object.name);
  86. }
  87. return currentChain;
  88. }
  89. /**
  90. * @param {NodeGuards} targetNode
  91. * @param {TObjectMembersCallsChain} objectMembersCallsChain
  92. * @returns {BlockStatement}
  93. */
  94. private getCalleeBlockStatement (
  95. targetNode: ESTree.Node,
  96. objectMembersCallsChain: TObjectMembersCallsChain
  97. ): ESTree.BlockStatement | null {
  98. const objectName: string | number | undefined = objectMembersCallsChain.shift();
  99. if (!objectName) {
  100. return null;
  101. }
  102. let calleeBlockStatement: ESTree.BlockStatement | null = null;
  103. estraverse.traverse(targetNode, {
  104. enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
  105. if (
  106. NodeGuards.isVariableDeclaratorNode(node) &&
  107. NodeGuards.isIdentifierNode(node.id) &&
  108. node.init &&
  109. NodeGuards.isObjectExpressionNode(node.init) &&
  110. node.id.name === objectName
  111. ) {
  112. calleeBlockStatement = this.findCalleeBlockStatement(node.init.properties, objectMembersCallsChain);
  113. return estraverse.VisitorOption.Break;
  114. }
  115. }
  116. });
  117. return calleeBlockStatement;
  118. }
  119. /**
  120. * @param {Property[]} objectExpressionProperties
  121. * @param {TObjectMembersCallsChain} objectMembersCallsChain
  122. * @returns {BlockStatement}
  123. */
  124. private findCalleeBlockStatement (
  125. objectExpressionProperties: (ESTree.Property | ESTree.SpreadElement)[],
  126. objectMembersCallsChain: TObjectMembersCallsChain
  127. ): ESTree.BlockStatement | null {
  128. const nextItemInCallsChain: string | number | undefined = objectMembersCallsChain.shift();
  129. if (!nextItemInCallsChain) {
  130. return null;
  131. }
  132. for (const propertyNode of objectExpressionProperties) {
  133. if (!NodeGuards.isPropertyNode(propertyNode)) {
  134. continue;
  135. }
  136. if (!ObjectExpressionCalleeDataExtractor.isValidTargetPropertyNode(propertyNode, nextItemInCallsChain)) {
  137. continue;
  138. }
  139. if (NodeGuards.isObjectExpressionNode(propertyNode.value)) {
  140. return this.findCalleeBlockStatement(propertyNode.value.properties, objectMembersCallsChain);
  141. }
  142. if (NodeGuards.isFunctionExpressionNode(propertyNode.value)) {
  143. return propertyNode.value.body;
  144. }
  145. }
  146. return null;
  147. }
  148. }