StackTraceAnalyzer.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import { inject, injectable, } from 'inversify';
  2. import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
  3. import * as estraverse from 'estraverse';
  4. import * as ESTree from 'estree';
  5. import { TCalleeDataExtractorFactory } from '../../types/container/stack-trace-analyzer/TCalleeDataExtractorFactory';
  6. import { ICalleeData } from '../../interfaces/analyzers/stack-trace-analyzer/ICalleeData';
  7. import { IStackTraceAnalyzer } from '../../interfaces/analyzers/stack-trace-analyzer/IStackTraceAnalyzer';
  8. import { IStackTraceData } from '../../interfaces/analyzers/stack-trace-analyzer/IStackTraceData';
  9. import { CalleeDataExtractor } from '../../enums/analyzers/stack-trace-analyzer/CalleeDataExtractor';
  10. import { NodeGuards } from '../../node/NodeGuards';
  11. import { NodeUtils } from '../../node/NodeUtils';
  12. /**
  13. * This class generates a data with a stack trace of functions calls
  14. *
  15. * For example:
  16. *
  17. * function Foo () {
  18. * var baz = function () {
  19. *
  20. * }
  21. *
  22. * baz();
  23. * }
  24. *
  25. * foo();
  26. *
  27. * Will generate a structure like:
  28. *
  29. * [
  30. * {
  31. * callee: FOO_FUNCTION_NODE
  32. * name: 'Foo',
  33. * trace: [
  34. * {
  35. * callee: BAZ_FUNCTION_NODE,
  36. * name: 'baz,
  37. * trace: []
  38. * }
  39. * ]
  40. * }
  41. * ]
  42. */
  43. @injectable()
  44. export class StackTraceAnalyzer implements IStackTraceAnalyzer {
  45. /**
  46. * @type {CalleeDataExtractor[]}
  47. */
  48. private static readonly calleeDataExtractorsList: CalleeDataExtractor[] = [
  49. CalleeDataExtractor.FunctionDeclarationCalleeDataExtractor,
  50. CalleeDataExtractor.FunctionExpressionCalleeDataExtractor,
  51. CalleeDataExtractor.ObjectExpressionCalleeDataExtractor
  52. ];
  53. /**
  54. * @type {number}
  55. */
  56. private static readonly limitThresholdActivationLength: number = 25;
  57. /**
  58. * @type {number}
  59. */
  60. private static readonly limitThreshold: number = 0.002;
  61. /**
  62. * @type {TCalleeDataExtractorFactory}
  63. */
  64. private readonly calleeDataExtractorFactory: TCalleeDataExtractorFactory;
  65. constructor (
  66. @inject(ServiceIdentifiers.Factory__ICalleeDataExtractor) calleeDataExtractorFactory: TCalleeDataExtractorFactory
  67. ) {
  68. this.calleeDataExtractorFactory = calleeDataExtractorFactory;
  69. }
  70. /**
  71. * @param {number} blockScopeBodyLength
  72. * @returns {number}
  73. */
  74. public static getLimitIndex (blockScopeBodyLength: number): number {
  75. const lastIndex: number = blockScopeBodyLength - 1;
  76. const limitThresholdActivationIndex: number = StackTraceAnalyzer.limitThresholdActivationLength - 1;
  77. let limitIndex: number = lastIndex;
  78. if (lastIndex > limitThresholdActivationIndex) {
  79. limitIndex = Math.round(
  80. limitThresholdActivationIndex + (lastIndex * StackTraceAnalyzer.limitThreshold)
  81. );
  82. if (limitIndex > lastIndex) {
  83. limitIndex = lastIndex;
  84. }
  85. }
  86. return limitIndex;
  87. }
  88. /**
  89. * @param {Program} astTree
  90. * @returns {IStackTraceData[]}
  91. */
  92. public analyze (astTree: ESTree.Program): IStackTraceData[] {
  93. return this.analyzeRecursive(astTree.body);
  94. }
  95. /**
  96. * @param {NodeGuards[]} blockScopeBody
  97. * @returns {IStackTraceData[]}
  98. */
  99. private analyzeRecursive (blockScopeBody: ESTree.Node[]): IStackTraceData[] {
  100. const limitIndex: number = StackTraceAnalyzer.getLimitIndex(blockScopeBody.length);
  101. const stackTraceData: IStackTraceData[] = [];
  102. const blockScopeBodyLength: number = blockScopeBody.length;
  103. for (let index: number = 0; index < blockScopeBodyLength; index++) {
  104. if (index > limitIndex) {
  105. break;
  106. }
  107. const blockScopeBodyNode: ESTree.Node = blockScopeBody[index];
  108. estraverse.traverse(blockScopeBodyNode, {
  109. enter: (node: ESTree.Node): estraverse.VisitorOption | void => {
  110. if (!NodeGuards.isCallExpressionNode(node)) {
  111. return;
  112. }
  113. if (blockScopeBodyNode.parentNode !== NodeUtils.getBlockScopeOfNode(node)) {
  114. return estraverse.VisitorOption.Skip;
  115. }
  116. this.analyzeCallExpressionNode(stackTraceData, blockScopeBody, node);
  117. }
  118. });
  119. }
  120. return stackTraceData;
  121. }
  122. /**
  123. * @param {IStackTraceData[]} stackTraceData
  124. * @param {NodeGuards[]} blockScopeBody
  125. * @param {CallExpression} callExpressionNode
  126. */
  127. private analyzeCallExpressionNode (
  128. stackTraceData: IStackTraceData[],
  129. blockScopeBody: ESTree.Node[],
  130. callExpressionNode: ESTree.CallExpression
  131. ): void {
  132. StackTraceAnalyzer.calleeDataExtractorsList.forEach((calleeDataExtractorName: CalleeDataExtractor) => {
  133. const calleeData: ICalleeData | null = this.calleeDataExtractorFactory(calleeDataExtractorName)
  134. .extract(blockScopeBody, callExpressionNode.callee);
  135. if (!calleeData) {
  136. return;
  137. }
  138. stackTraceData.push({
  139. ...calleeData,
  140. stackTrace: this.analyzeRecursive(calleeData.callee.body)
  141. });
  142. });
  143. }
  144. }