1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. // This is a DevTools fork of ReactComponentStackFrame.
    
  11. // This fork enables DevTools to use the same "native" component stack format,
    
  12. // while still maintaining support for multiple renderer versions
    
  13. // (which use different values for ReactTypeOfWork).
    
  14. 
    
  15. import type {LazyComponent} from 'react/src/ReactLazy';
    
  16. import type {CurrentDispatcherRef} from './types';
    
  17. 
    
  18. import {
    
  19.   FORWARD_REF_NUMBER,
    
  20.   FORWARD_REF_SYMBOL_STRING,
    
  21.   LAZY_NUMBER,
    
  22.   LAZY_SYMBOL_STRING,
    
  23.   MEMO_NUMBER,
    
  24.   MEMO_SYMBOL_STRING,
    
  25.   SUSPENSE_NUMBER,
    
  26.   SUSPENSE_SYMBOL_STRING,
    
  27.   SUSPENSE_LIST_NUMBER,
    
  28.   SUSPENSE_LIST_SYMBOL_STRING,
    
  29. } from './ReactSymbols';
    
  30. 
    
  31. // The shared console patching code is DEV-only.
    
  32. // We can't use it since DevTools only ships production builds.
    
  33. import {disableLogs, reenableLogs} from './DevToolsConsolePatching';
    
  34. 
    
  35. let prefix;
    
  36. export function describeBuiltInComponentFrame(
    
  37.   name: string,
    
  38.   ownerFn: void | null | Function,
    
  39. ): string {
    
  40.   if (prefix === undefined) {
    
  41.     // Extract the VM specific prefix used by each line.
    
  42.     try {
    
  43.       throw Error();
    
  44.     } catch (x) {
    
  45.       const match = x.stack.trim().match(/\n( *(at )?)/);
    
  46.       prefix = (match && match[1]) || '';
    
  47.     }
    
  48.   }
    
  49.   // We use the prefix to ensure our stacks line up with native stack frames.
    
  50.   return '\n' + prefix + name;
    
  51. }
    
  52. 
    
  53. let reentry = false;
    
  54. let componentFrameCache;
    
  55. if (__DEV__) {
    
  56.   const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
    
  57.   componentFrameCache = new PossiblyWeakMap<$FlowFixMe, string>();
    
  58. }
    
  59. 
    
  60. export function describeNativeComponentFrame(
    
  61.   fn: Function,
    
  62.   construct: boolean,
    
  63.   currentDispatcherRef: CurrentDispatcherRef,
    
  64. ): string {
    
  65.   // If something asked for a stack inside a fake render, it should get ignored.
    
  66.   if (!fn || reentry) {
    
  67.     return '';
    
  68.   }
    
  69. 
    
  70.   if (__DEV__) {
    
  71.     const frame = componentFrameCache.get(fn);
    
  72.     if (frame !== undefined) {
    
  73.       return frame;
    
  74.     }
    
  75.   }
    
  76. 
    
  77.   let control;
    
  78. 
    
  79.   const previousPrepareStackTrace = Error.prepareStackTrace;
    
  80.   // $FlowFixMe[incompatible-type] It does accept undefined.
    
  81.   Error.prepareStackTrace = undefined;
    
  82. 
    
  83.   reentry = true;
    
  84. 
    
  85.   // Override the dispatcher so effects scheduled by this shallow render are thrown away.
    
  86.   //
    
  87.   // Note that unlike the code this was forked from (in ReactComponentStackFrame)
    
  88.   // DevTools should override the dispatcher even when DevTools is compiled in production mode,
    
  89.   // because the app itself may be in development mode and log errors/warnings.
    
  90.   const previousDispatcher = currentDispatcherRef.current;
    
  91.   currentDispatcherRef.current = null;
    
  92.   disableLogs();
    
  93. 
    
  94.   try {
    
  95.     // This should throw.
    
  96.     if (construct) {
    
  97.       // Something should be setting the props in the constructor.
    
  98.       const Fake = function () {
    
  99.         throw Error();
    
  100.       };
    
  101.       // $FlowFixMe[prop-missing]
    
  102.       Object.defineProperty(Fake.prototype, 'props', {
    
  103.         set: function () {
    
  104.           // We use a throwing setter instead of frozen or non-writable props
    
  105.           // because that won't throw in a non-strict mode function.
    
  106.           throw Error();
    
  107.         },
    
  108.       });
    
  109.       if (typeof Reflect === 'object' && Reflect.construct) {
    
  110.         // We construct a different control for this case to include any extra
    
  111.         // frames added by the construct call.
    
  112.         try {
    
  113.           Reflect.construct(Fake, []);
    
  114.         } catch (x) {
    
  115.           control = x;
    
  116.         }
    
  117.         Reflect.construct(fn, [], Fake);
    
  118.       } else {
    
  119.         try {
    
  120.           Fake.call();
    
  121.         } catch (x) {
    
  122.           control = x;
    
  123.         }
    
  124.         // $FlowFixMe[prop-missing] found when upgrading Flow
    
  125.         fn.call(Fake.prototype);
    
  126.       }
    
  127.     } else {
    
  128.       try {
    
  129.         throw Error();
    
  130.       } catch (x) {
    
  131.         control = x;
    
  132.       }
    
  133.       fn();
    
  134.     }
    
  135.   } catch (sample) {
    
  136.     // This is inlined manually because closure doesn't do it for us.
    
  137.     if (sample && control && typeof sample.stack === 'string') {
    
  138.       // This extracts the first frame from the sample that isn't also in the control.
    
  139.       // Skipping one frame that we assume is the frame that calls the two.
    
  140.       const sampleLines = sample.stack.split('\n');
    
  141.       const controlLines = control.stack.split('\n');
    
  142.       let s = sampleLines.length - 1;
    
  143.       let c = controlLines.length - 1;
    
  144.       while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
    
  145.         // We expect at least one stack frame to be shared.
    
  146.         // Typically this will be the root most one. However, stack frames may be
    
  147.         // cut off due to maximum stack limits. In this case, one maybe cut off
    
  148.         // earlier than the other. We assume that the sample is longer or the same
    
  149.         // and there for cut off earlier. So we should find the root most frame in
    
  150.         // the sample somewhere in the control.
    
  151.         c--;
    
  152.       }
    
  153.       for (; s >= 1 && c >= 0; s--, c--) {
    
  154.         // Next we find the first one that isn't the same which should be the
    
  155.         // frame that called our sample function and the control.
    
  156.         if (sampleLines[s] !== controlLines[c]) {
    
  157.           // In V8, the first line is describing the message but other VMs don't.
    
  158.           // If we're about to return the first line, and the control is also on the same
    
  159.           // line, that's a pretty good indicator that our sample threw at same line as
    
  160.           // the control. I.e. before we entered the sample frame. So we ignore this result.
    
  161.           // This can happen if you passed a class to function component, or non-function.
    
  162.           if (s !== 1 || c !== 1) {
    
  163.             do {
    
  164.               s--;
    
  165.               c--;
    
  166.               // We may still have similar intermediate frames from the construct call.
    
  167.               // The next one that isn't the same should be our match though.
    
  168.               if (c < 0 || sampleLines[s] !== controlLines[c]) {
    
  169.                 // V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
    
  170.                 const frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
    
  171.                 if (__DEV__) {
    
  172.                   if (typeof fn === 'function') {
    
  173.                     componentFrameCache.set(fn, frame);
    
  174.                   }
    
  175.                 }
    
  176.                 // Return the line we found.
    
  177.                 return frame;
    
  178.               }
    
  179.             } while (s >= 1 && c >= 0);
    
  180.           }
    
  181.           break;
    
  182.         }
    
  183.       }
    
  184.     }
    
  185.   } finally {
    
  186.     reentry = false;
    
  187. 
    
  188.     Error.prepareStackTrace = previousPrepareStackTrace;
    
  189. 
    
  190.     currentDispatcherRef.current = previousDispatcher;
    
  191.     reenableLogs();
    
  192.   }
    
  193.   // Fallback to just using the name if we couldn't make it throw.
    
  194.   const name = fn ? fn.displayName || fn.name : '';
    
  195.   const syntheticFrame = name ? describeBuiltInComponentFrame(name) : '';
    
  196.   if (__DEV__) {
    
  197.     if (typeof fn === 'function') {
    
  198.       componentFrameCache.set(fn, syntheticFrame);
    
  199.     }
    
  200.   }
    
  201.   return syntheticFrame;
    
  202. }
    
  203. 
    
  204. export function describeClassComponentFrame(
    
  205.   ctor: Function,
    
  206.   ownerFn: void | null | Function,
    
  207.   currentDispatcherRef: CurrentDispatcherRef,
    
  208. ): string {
    
  209.   return describeNativeComponentFrame(ctor, true, currentDispatcherRef);
    
  210. }
    
  211. 
    
  212. export function describeFunctionComponentFrame(
    
  213.   fn: Function,
    
  214.   ownerFn: void | null | Function,
    
  215.   currentDispatcherRef: CurrentDispatcherRef,
    
  216. ): string {
    
  217.   return describeNativeComponentFrame(fn, false, currentDispatcherRef);
    
  218. }
    
  219. 
    
  220. function shouldConstruct(Component: Function) {
    
  221.   const prototype = Component.prototype;
    
  222.   return !!(prototype && prototype.isReactComponent);
    
  223. }
    
  224. 
    
  225. export function describeUnknownElementTypeFrameInDEV(
    
  226.   type: any,
    
  227.   ownerFn: void | null | Function,
    
  228.   currentDispatcherRef: CurrentDispatcherRef,
    
  229. ): string {
    
  230.   if (!__DEV__) {
    
  231.     return '';
    
  232.   }
    
  233.   if (type == null) {
    
  234.     return '';
    
  235.   }
    
  236.   if (typeof type === 'function') {
    
  237.     return describeNativeComponentFrame(
    
  238.       type,
    
  239.       shouldConstruct(type),
    
  240.       currentDispatcherRef,
    
  241.     );
    
  242.   }
    
  243.   if (typeof type === 'string') {
    
  244.     return describeBuiltInComponentFrame(type, ownerFn);
    
  245.   }
    
  246.   switch (type) {
    
  247.     case SUSPENSE_NUMBER:
    
  248.     case SUSPENSE_SYMBOL_STRING:
    
  249.       return describeBuiltInComponentFrame('Suspense', ownerFn);
    
  250.     case SUSPENSE_LIST_NUMBER:
    
  251.     case SUSPENSE_LIST_SYMBOL_STRING:
    
  252.       return describeBuiltInComponentFrame('SuspenseList', ownerFn);
    
  253.   }
    
  254.   if (typeof type === 'object') {
    
  255.     switch (type.$$typeof) {
    
  256.       case FORWARD_REF_NUMBER:
    
  257.       case FORWARD_REF_SYMBOL_STRING:
    
  258.         return describeFunctionComponentFrame(
    
  259.           type.render,
    
  260.           ownerFn,
    
  261.           currentDispatcherRef,
    
  262.         );
    
  263.       case MEMO_NUMBER:
    
  264.       case MEMO_SYMBOL_STRING:
    
  265.         // Memo may contain any component type so we recursively resolve it.
    
  266.         return describeUnknownElementTypeFrameInDEV(
    
  267.           type.type,
    
  268.           ownerFn,
    
  269.           currentDispatcherRef,
    
  270.         );
    
  271.       case LAZY_NUMBER:
    
  272.       case LAZY_SYMBOL_STRING: {
    
  273.         const lazyComponent: LazyComponent<any, any> = (type: any);
    
  274.         const payload = lazyComponent._payload;
    
  275.         const init = lazyComponent._init;
    
  276.         try {
    
  277.           // Lazy may contain any component type so we recursively resolve it.
    
  278.           return describeUnknownElementTypeFrameInDEV(
    
  279.             init(payload),
    
  280.             ownerFn,
    
  281.             currentDispatcherRef,
    
  282.           );
    
  283.         } catch (x) {}
    
  284.       }
    
  285.     }
    
  286.   }
    
  287.   return '';
    
  288. }