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. import type {Source} from 'shared/ReactElementType';
    
  11. import type {LazyComponent} from 'react/src/ReactLazy';
    
  12. 
    
  13. import {enableComponentStackLocations} from 'shared/ReactFeatureFlags';
    
  14. 
    
  15. import {
    
  16.   REACT_SUSPENSE_TYPE,
    
  17.   REACT_SUSPENSE_LIST_TYPE,
    
  18.   REACT_FORWARD_REF_TYPE,
    
  19.   REACT_MEMO_TYPE,
    
  20.   REACT_LAZY_TYPE,
    
  21. } from 'shared/ReactSymbols';
    
  22. 
    
  23. import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
    
  24. 
    
  25. import ReactSharedInternals from 'shared/ReactSharedInternals';
    
  26. 
    
  27. const {ReactCurrentDispatcher} = ReactSharedInternals;
    
  28. 
    
  29. let prefix;
    
  30. export function describeBuiltInComponentFrame(
    
  31.   name: string,
    
  32.   source: void | null | Source,
    
  33.   ownerFn: void | null | Function,
    
  34. ): string {
    
  35.   if (enableComponentStackLocations) {
    
  36.     if (prefix === undefined) {
    
  37.       // Extract the VM specific prefix used by each line.
    
  38.       try {
    
  39.         throw Error();
    
  40.       } catch (x) {
    
  41.         const match = x.stack.trim().match(/\n( *(at )?)/);
    
  42.         prefix = (match && match[1]) || '';
    
  43.       }
    
  44.     }
    
  45.     // We use the prefix to ensure our stacks line up with native stack frames.
    
  46.     return '\n' + prefix + name;
    
  47.   } else {
    
  48.     let ownerName = null;
    
  49.     if (__DEV__ && ownerFn) {
    
  50.       ownerName = ownerFn.displayName || ownerFn.name || null;
    
  51.     }
    
  52.     return describeComponentFrame(name, source, ownerName);
    
  53.   }
    
  54. }
    
  55. 
    
  56. let reentry = false;
    
  57. let componentFrameCache;
    
  58. if (__DEV__) {
    
  59.   const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
    
  60.   componentFrameCache = new PossiblyWeakMap<Function, string>();
    
  61. }
    
  62. 
    
  63. export function describeNativeComponentFrame(
    
  64.   fn: Function,
    
  65.   construct: boolean,
    
  66. ): string {
    
  67.   // If something asked for a stack inside a fake render, it should get ignored.
    
  68.   if (!fn || reentry) {
    
  69.     return '';
    
  70.   }
    
  71. 
    
  72.   if (__DEV__) {
    
  73.     const frame = componentFrameCache.get(fn);
    
  74.     if (frame !== undefined) {
    
  75.       return frame;
    
  76.     }
    
  77.   }
    
  78. 
    
  79.   let control;
    
  80. 
    
  81.   reentry = true;
    
  82.   const previousPrepareStackTrace = Error.prepareStackTrace;
    
  83.   // $FlowFixMe[incompatible-type] It does accept undefined.
    
  84.   Error.prepareStackTrace = undefined;
    
  85.   let previousDispatcher;
    
  86.   if (__DEV__) {
    
  87.     previousDispatcher = ReactCurrentDispatcher.current;
    
  88.     // Set the dispatcher in DEV because this might be call in the render function
    
  89.     // for warnings.
    
  90.     ReactCurrentDispatcher.current = null;
    
  91.     disableLogs();
    
  92.   }
    
  93.   try {
    
  94.     // This should throw.
    
  95.     if (construct) {
    
  96.       // Something should be setting the props in the constructor.
    
  97.       const Fake = function () {
    
  98.         throw Error();
    
  99.       };
    
  100.       // $FlowFixMe[prop-missing]
    
  101.       Object.defineProperty(Fake.prototype, 'props', {
    
  102.         set: function () {
    
  103.           // We use a throwing setter instead of frozen or non-writable props
    
  104.           // because that won't throw in a non-strict mode function.
    
  105.           throw Error();
    
  106.         },
    
  107.       });
    
  108.       if (typeof Reflect === 'object' && Reflect.construct) {
    
  109.         // We construct a different control for this case to include any extra
    
  110.         // frames added by the construct call.
    
  111.         try {
    
  112.           Reflect.construct(Fake, []);
    
  113.         } catch (x) {
    
  114.           control = x;
    
  115.         }
    
  116.         Reflect.construct(fn, [], Fake);
    
  117.       } else {
    
  118.         try {
    
  119.           Fake.call();
    
  120.         } catch (x) {
    
  121.           control = x;
    
  122.         }
    
  123.         // $FlowFixMe[prop-missing] found when upgrading Flow
    
  124.         fn.call(Fake.prototype);
    
  125.       }
    
  126.     } else {
    
  127.       try {
    
  128.         throw Error();
    
  129.       } catch (x) {
    
  130.         control = x;
    
  131.       }
    
  132.       // TODO(luna): This will currently only throw if the function component
    
  133.       // tries to access React/ReactDOM/props. We should probably make this throw
    
  134.       // in simple components too
    
  135.       const maybePromise = fn();
    
  136. 
    
  137.       // If the function component returns a promise, it's likely an async
    
  138.       // component, which we don't yet support. Attach a noop catch handler to
    
  139.       // silence the error.
    
  140.       // TODO: Implement component stacks for async client components?
    
  141.       if (maybePromise && typeof maybePromise.catch === 'function') {
    
  142.         maybePromise.catch(() => {});
    
  143.       }
    
  144.     }
    
  145.   } catch (sample) {
    
  146.     // This is inlined manually because closure doesn't do it for us.
    
  147.     if (sample && control && typeof sample.stack === 'string') {
    
  148.       // This extracts the first frame from the sample that isn't also in the control.
    
  149.       // Skipping one frame that we assume is the frame that calls the two.
    
  150.       const sampleLines = sample.stack.split('\n');
    
  151.       const controlLines = control.stack.split('\n');
    
  152.       let s = sampleLines.length - 1;
    
  153.       let c = controlLines.length - 1;
    
  154.       while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
    
  155.         // We expect at least one stack frame to be shared.
    
  156.         // Typically this will be the root most one. However, stack frames may be
    
  157.         // cut off due to maximum stack limits. In this case, one maybe cut off
    
  158.         // earlier than the other. We assume that the sample is longer or the same
    
  159.         // and there for cut off earlier. So we should find the root most frame in
    
  160.         // the sample somewhere in the control.
    
  161.         c--;
    
  162.       }
    
  163.       for (; s >= 1 && c >= 0; s--, c--) {
    
  164.         // Next we find the first one that isn't the same which should be the
    
  165.         // frame that called our sample function and the control.
    
  166.         if (sampleLines[s] !== controlLines[c]) {
    
  167.           // In V8, the first line is describing the message but other VMs don't.
    
  168.           // If we're about to return the first line, and the control is also on the same
    
  169.           // line, that's a pretty good indicator that our sample threw at same line as
    
  170.           // the control. I.e. before we entered the sample frame. So we ignore this result.
    
  171.           // This can happen if you passed a class to function component, or non-function.
    
  172.           if (s !== 1 || c !== 1) {
    
  173.             do {
    
  174.               s--;
    
  175.               c--;
    
  176.               // We may still have similar intermediate frames from the construct call.
    
  177.               // The next one that isn't the same should be our match though.
    
  178.               if (c < 0 || sampleLines[s] !== controlLines[c]) {
    
  179.                 // V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
    
  180.                 let frame = '\n' + sampleLines[s].replace(' at new ', ' at ');
    
  181. 
    
  182.                 // If our component frame is labeled "<anonymous>"
    
  183.                 // but we have a user-provided "displayName"
    
  184.                 // splice it in to make the stack more readable.
    
  185.                 if (fn.displayName && frame.includes('<anonymous>')) {
    
  186.                   frame = frame.replace('<anonymous>', fn.displayName);
    
  187.                 }
    
  188. 
    
  189.                 if (__DEV__) {
    
  190.                   if (typeof fn === 'function') {
    
  191.                     componentFrameCache.set(fn, frame);
    
  192.                   }
    
  193.                 }
    
  194.                 // Return the line we found.
    
  195.                 return frame;
    
  196.               }
    
  197.             } while (s >= 1 && c >= 0);
    
  198.           }
    
  199.           break;
    
  200.         }
    
  201.       }
    
  202.     }
    
  203.   } finally {
    
  204.     reentry = false;
    
  205.     if (__DEV__) {
    
  206.       ReactCurrentDispatcher.current = previousDispatcher;
    
  207.       reenableLogs();
    
  208.     }
    
  209.     Error.prepareStackTrace = previousPrepareStackTrace;
    
  210.   }
    
  211.   // Fallback to just using the name if we couldn't make it throw.
    
  212.   const name = fn ? fn.displayName || fn.name : '';
    
  213.   const syntheticFrame = name ? describeBuiltInComponentFrame(name) : '';
    
  214.   if (__DEV__) {
    
  215.     if (typeof fn === 'function') {
    
  216.       componentFrameCache.set(fn, syntheticFrame);
    
  217.     }
    
  218.   }
    
  219.   return syntheticFrame;
    
  220. }
    
  221. 
    
  222. const BEFORE_SLASH_RE = /^(.*)[\\\/]/;
    
  223. 
    
  224. function describeComponentFrame(
    
  225.   name: null | string,
    
  226.   source: void | null | Source,
    
  227.   ownerName: null | string,
    
  228. ) {
    
  229.   let sourceInfo = '';
    
  230.   if (__DEV__ && source) {
    
  231.     const path = source.fileName;
    
  232.     let fileName = path.replace(BEFORE_SLASH_RE, '');
    
  233.     // In DEV, include code for a common special case:
    
  234.     // prefer "folder/index.js" instead of just "index.js".
    
  235.     if (/^index\./.test(fileName)) {
    
  236.       const match = path.match(BEFORE_SLASH_RE);
    
  237.       if (match) {
    
  238.         const pathBeforeSlash = match[1];
    
  239.         if (pathBeforeSlash) {
    
  240.           const folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, '');
    
  241.           fileName = folderName + '/' + fileName;
    
  242.         }
    
  243.       }
    
  244.     }
    
  245.     sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')';
    
  246.   } else if (ownerName) {
    
  247.     sourceInfo = ' (created by ' + ownerName + ')';
    
  248.   }
    
  249.   return '\n    in ' + (name || 'Unknown') + sourceInfo;
    
  250. }
    
  251. 
    
  252. export function describeClassComponentFrame(
    
  253.   ctor: Function,
    
  254.   source: void | null | Source,
    
  255.   ownerFn: void | null | Function,
    
  256. ): string {
    
  257.   if (enableComponentStackLocations) {
    
  258.     return describeNativeComponentFrame(ctor, true);
    
  259.   } else {
    
  260.     return describeFunctionComponentFrame(ctor, source, ownerFn);
    
  261.   }
    
  262. }
    
  263. 
    
  264. export function describeFunctionComponentFrame(
    
  265.   fn: Function,
    
  266.   source: void | null | Source,
    
  267.   ownerFn: void | null | Function,
    
  268. ): string {
    
  269.   if (enableComponentStackLocations) {
    
  270.     return describeNativeComponentFrame(fn, false);
    
  271.   } else {
    
  272.     if (!fn) {
    
  273.       return '';
    
  274.     }
    
  275.     const name = fn.displayName || fn.name || null;
    
  276.     let ownerName = null;
    
  277.     if (__DEV__ && ownerFn) {
    
  278.       ownerName = ownerFn.displayName || ownerFn.name || null;
    
  279.     }
    
  280.     return describeComponentFrame(name, source, ownerName);
    
  281.   }
    
  282. }
    
  283. 
    
  284. function shouldConstruct(Component: Function) {
    
  285.   const prototype = Component.prototype;
    
  286.   return !!(prototype && prototype.isReactComponent);
    
  287. }
    
  288. 
    
  289. export function describeUnknownElementTypeFrameInDEV(
    
  290.   type: any,
    
  291.   source: void | null | Source,
    
  292.   ownerFn: void | null | Function,
    
  293. ): string {
    
  294.   if (!__DEV__) {
    
  295.     return '';
    
  296.   }
    
  297.   if (type == null) {
    
  298.     return '';
    
  299.   }
    
  300.   if (typeof type === 'function') {
    
  301.     if (enableComponentStackLocations) {
    
  302.       return describeNativeComponentFrame(type, shouldConstruct(type));
    
  303.     } else {
    
  304.       return describeFunctionComponentFrame(type, source, ownerFn);
    
  305.     }
    
  306.   }
    
  307.   if (typeof type === 'string') {
    
  308.     return describeBuiltInComponentFrame(type, source, ownerFn);
    
  309.   }
    
  310.   switch (type) {
    
  311.     case REACT_SUSPENSE_TYPE:
    
  312.       return describeBuiltInComponentFrame('Suspense', source, ownerFn);
    
  313.     case REACT_SUSPENSE_LIST_TYPE:
    
  314.       return describeBuiltInComponentFrame('SuspenseList', source, ownerFn);
    
  315.   }
    
  316.   if (typeof type === 'object') {
    
  317.     switch (type.$$typeof) {
    
  318.       case REACT_FORWARD_REF_TYPE:
    
  319.         return describeFunctionComponentFrame(type.render, source, ownerFn);
    
  320.       case REACT_MEMO_TYPE:
    
  321.         // Memo may contain any component type so we recursively resolve it.
    
  322.         return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn);
    
  323.       case REACT_LAZY_TYPE: {
    
  324.         const lazyComponent: LazyComponent<any, any> = (type: any);
    
  325.         const payload = lazyComponent._payload;
    
  326.         const init = lazyComponent._init;
    
  327.         try {
    
  328.           // Lazy may contain any component type so we recursively resolve it.
    
  329.           return describeUnknownElementTypeFrameInDEV(
    
  330.             init(payload),
    
  331.             source,
    
  332.             ownerFn,
    
  333.           );
    
  334.         } catch (x) {}
    
  335.       }
    
  336.     }
    
  337.   }
    
  338.   return '';
    
  339. }