From 1df6d567efe32afcec711a48bf36a0343187b8a2 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Thu, 6 Jun 2024 21:45:25 -0400 Subject: [PATCH] Track debugInfo on module state instead of stack Notably with DON'T use try/finally because if something throws we want to observe what it was at the time we threw. Now we can use this when we create the Throw Fiber to associate the Server Components that were part of the parent stack before this error threw. --- .../src/__tests__/ReactFlight-test.js | 6 +- .../react-reconciler/src/ReactChildFiber.js | 244 +++++++++--------- 2 files changed, 126 insertions(+), 124 deletions(-) diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 77fb121f64dc5..cef237fe4aa9f 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -1050,8 +1050,10 @@ describe('ReactFlight', () => { } const expectedStack = __DEV__ - ? // TODO: This should include Throw but it doesn't have a Fiber. - '\n in div' + '\n in ErrorBoundary (at **)' + '\n in App' + ? '\n in Throw' + + '\n in div' + + '\n in ErrorBoundary (at **)' + + '\n in App' : '\n in div' + '\n in ErrorBoundary (at **)'; function App() { diff --git a/packages/react-reconciler/src/ReactChildFiber.js b/packages/react-reconciler/src/ReactChildFiber.js index 60f8dd97468ba..057b62a504b31 100644 --- a/packages/react-reconciler/src/ReactChildFiber.js +++ b/packages/react-reconciler/src/ReactChildFiber.js @@ -76,23 +76,27 @@ import {runWithFiberInDEV} from './ReactCurrentFiber'; let thenableState: ThenableState | null = null; let thenableIndexCounter: number = 0; -function mergeDebugInfo( - outer: ReactDebugInfo | null, - inner: ReactDebugInfo | null | void, -): ReactDebugInfo | null { +// Server Components Meta Data +let currentDebugInfo: null | ReactDebugInfo = null; + +function pushDebugInfo( + debugInfo: null | ReactDebugInfo, +): null | ReactDebugInfo { if (!__DEV__) { return null; } - if (inner == null) { - return outer; - } else if (outer === null) { - return inner; + const previousDebugInfo = currentDebugInfo; + if (debugInfo == null) { + // Leave inplace + } else if (previousDebugInfo === null) { + currentDebugInfo = debugInfo; } else { // If we have two debugInfo, we need to create a new one. This makes the array no longer // live so we'll miss any future updates if we received more so ideally we should always // do this after both have fully resolved/unsuspended. - return outer.concat(inner); + currentDebugInfo = previousDebugInfo.concat(debugInfo); } + return previousDebugInfo; } let didWarnAboutMaps; @@ -497,14 +501,13 @@ function createChildReconciler( current: Fiber | null, textContent: string, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ) { if (current === null || current.tag !== HostText) { // Insert const created = createFiberFromText(textContent, returnFiber.mode, lanes); created.return = returnFiber; if (__DEV__) { - created._debugInfo = debugInfo; + created._debugInfo = currentDebugInfo; } return created; } else { @@ -512,7 +515,7 @@ function createChildReconciler( const existing = useFiber(current, textContent); existing.return = returnFiber; if (__DEV__) { - existing._debugInfo = debugInfo; + existing._debugInfo = currentDebugInfo; } return existing; } @@ -523,7 +526,6 @@ function createChildReconciler( current: Fiber | null, element: ReactElement, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber { const elementType = element.type; if (elementType === REACT_FRAGMENT_TYPE) { @@ -533,7 +535,6 @@ function createChildReconciler( element.props.children, lanes, element.key, - debugInfo, ); validateFragmentProps(element, updated, returnFiber); return updated; @@ -560,7 +561,7 @@ function createChildReconciler( existing.return = returnFiber; if (__DEV__) { existing._debugOwner = element._owner; - existing._debugInfo = debugInfo; + existing._debugInfo = currentDebugInfo; } return existing; } @@ -570,7 +571,7 @@ function createChildReconciler( coerceRef(returnFiber, current, created, element); created.return = returnFiber; if (__DEV__) { - created._debugInfo = debugInfo; + created._debugInfo = currentDebugInfo; } return created; } @@ -580,7 +581,6 @@ function createChildReconciler( current: Fiber | null, portal: ReactPortal, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber { if ( current === null || @@ -592,7 +592,7 @@ function createChildReconciler( const created = createFiberFromPortal(portal, returnFiber.mode, lanes); created.return = returnFiber; if (__DEV__) { - created._debugInfo = debugInfo; + created._debugInfo = currentDebugInfo; } return created; } else { @@ -600,7 +600,7 @@ function createChildReconciler( const existing = useFiber(current, portal.children || []); existing.return = returnFiber; if (__DEV__) { - existing._debugInfo = debugInfo; + existing._debugInfo = currentDebugInfo; } return existing; } @@ -612,7 +612,6 @@ function createChildReconciler( fragment: Iterable, lanes: Lanes, key: null | string, - debugInfo: null | ReactDebugInfo, ): Fiber { if (current === null || current.tag !== Fragment) { // Insert @@ -624,7 +623,7 @@ function createChildReconciler( ); created.return = returnFiber; if (__DEV__) { - created._debugInfo = debugInfo; + created._debugInfo = currentDebugInfo; } return created; } else { @@ -632,7 +631,7 @@ function createChildReconciler( const existing = useFiber(current, fragment); existing.return = returnFiber; if (__DEV__) { - existing._debugInfo = debugInfo; + existing._debugInfo = currentDebugInfo; } return existing; } @@ -642,7 +641,6 @@ function createChildReconciler( returnFiber: Fiber, newChild: any, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber | null { if ( (typeof newChild === 'string' && newChild !== '') || @@ -660,7 +658,7 @@ function createChildReconciler( ); created.return = returnFiber; if (__DEV__) { - created._debugInfo = debugInfo; + created._debugInfo = currentDebugInfo; } return created; } @@ -676,7 +674,9 @@ function createChildReconciler( coerceRef(returnFiber, null, created, newChild); created.return = returnFiber; if (__DEV__) { - created._debugInfo = mergeDebugInfo(debugInfo, newChild._debugInfo); + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + created._debugInfo = currentDebugInfo; + currentDebugInfo = prevDebugInfo; } return created; } @@ -688,11 +688,12 @@ function createChildReconciler( ); created.return = returnFiber; if (__DEV__) { - created._debugInfo = debugInfo; + created._debugInfo = currentDebugInfo; } return created; } case REACT_LAZY_TYPE: { + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); let resolvedChild; if (__DEV__) { resolvedChild = callLazyInitInDEV(newChild); @@ -701,12 +702,9 @@ function createChildReconciler( const init = newChild._init; resolvedChild = init(payload); } - return createChild( - returnFiber, - resolvedChild, - lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), // call merge after init - ); + const created = createChild(returnFiber, resolvedChild, lanes); + currentDebugInfo = prevDebugInfo; + return created; } } @@ -724,7 +722,9 @@ function createChildReconciler( ); created.return = returnFiber; if (__DEV__) { - created._debugInfo = mergeDebugInfo(debugInfo, newChild._debugInfo); + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + created._debugInfo = currentDebugInfo; + currentDebugInfo = prevDebugInfo; } return created; } @@ -734,12 +734,14 @@ function createChildReconciler( // Unwrap the inner value and recursively call this function again. if (typeof newChild.then === 'function') { const thenable: Thenable = (newChild: any); - return createChild( + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + const created = createChild( returnFiber, unwrapThenable(thenable), lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return created; } if (newChild.$$typeof === REACT_CONTEXT_TYPE) { @@ -748,7 +750,6 @@ function createChildReconciler( returnFiber, readContextDuringReconciliation(returnFiber, context, lanes), lanes, - debugInfo, ); } @@ -772,7 +773,6 @@ function createChildReconciler( oldFiber: Fiber | null, newChild: any, lanes: Lanes, - debugInfo: null | ReactDebugInfo, ): Fiber | null { // Update the fiber if the keys match, otherwise return null. const key = oldFiber !== null ? oldFiber.key : null; @@ -794,7 +794,6 @@ function createChildReconciler( // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint '' + newChild, lanes, - debugInfo, ); } @@ -802,31 +801,28 @@ function createChildReconciler( switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { if (newChild.key === key) { - return updateElement( + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + const updated = updateElement( returnFiber, oldFiber, newChild, lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return updated; } else { return null; } } case REACT_PORTAL_TYPE: { if (newChild.key === key) { - return updatePortal( - returnFiber, - oldFiber, - newChild, - lanes, - debugInfo, - ); + return updatePortal(returnFiber, oldFiber, newChild, lanes); } else { return null; } } case REACT_LAZY_TYPE: { + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); let resolvedChild; if (__DEV__) { resolvedChild = callLazyInitInDEV(newChild); @@ -835,13 +831,14 @@ function createChildReconciler( const init = newChild._init; resolvedChild = init(payload); } - return updateSlot( + const updated = updateSlot( returnFiber, oldFiber, resolvedChild, lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return updated; } } @@ -855,14 +852,16 @@ function createChildReconciler( return null; } - return updateFragment( + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + const updated = updateFragment( returnFiber, oldFiber, newChild, lanes, null, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return updated; } // Usable node types @@ -870,13 +869,15 @@ function createChildReconciler( // Unwrap the inner value and recursively call this function again. if (typeof newChild.then === 'function') { const thenable: Thenable = (newChild: any); - return updateSlot( + const prevDebugInfo = pushDebugInfo((thenable: any)._debugInfo); + const updated = updateSlot( returnFiber, oldFiber, unwrapThenable(thenable), lanes, - debugInfo, ); + currentDebugInfo = prevDebugInfo; + return updated; } if (newChild.$$typeof === REACT_CONTEXT_TYPE) { @@ -886,7 +887,6 @@ function createChildReconciler( oldFiber, readContextDuringReconciliation(returnFiber, context, lanes), lanes, - debugInfo, ); } @@ -911,7 +911,6 @@ function createChildReconciler( newIdx: number, newChild: any, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber | null { if ( (typeof newChild === 'string' && newChild !== '') || @@ -927,7 +926,6 @@ function createChildReconciler( // $FlowFixMe[unsafe-addition] Flow doesn't want us to use `+` operator with string and bigint '' + newChild, lanes, - debugInfo, ); } @@ -938,28 +936,25 @@ function createChildReconciler( existingChildren.get( newChild.key === null ? newIdx : newChild.key, ) || null; - return updateElement( + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + const updated = updateElement( returnFiber, matchedFiber, newChild, lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return updated; } case REACT_PORTAL_TYPE: { const matchedFiber = existingChildren.get( newChild.key === null ? newIdx : newChild.key, ) || null; - return updatePortal( - returnFiber, - matchedFiber, - newChild, - lanes, - debugInfo, - ); + return updatePortal(returnFiber, matchedFiber, newChild, lanes); } case REACT_LAZY_TYPE: { + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); let resolvedChild; if (__DEV__) { resolvedChild = callLazyInitInDEV(newChild); @@ -968,14 +963,15 @@ function createChildReconciler( const init = newChild._init; resolvedChild = init(payload); } - return updateFromMap( + const updated = updateFromMap( existingChildren, returnFiber, newIdx, resolvedChild, lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return updated; } } @@ -986,14 +982,16 @@ function createChildReconciler( typeof newChild[ASYNC_ITERATOR] === 'function') ) { const matchedFiber = existingChildren.get(newIdx) || null; - return updateFragment( + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + const updated = updateFragment( returnFiber, matchedFiber, newChild, lanes, null, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return updated; } // Usable node types @@ -1001,14 +999,16 @@ function createChildReconciler( // Unwrap the inner value and recursively call this function again. if (typeof newChild.then === 'function') { const thenable: Thenable = (newChild: any); - return updateFromMap( + const prevDebugInfo = pushDebugInfo((thenable: any)._debugInfo); + const updated = updateFromMap( existingChildren, returnFiber, newIdx, unwrapThenable(thenable), lanes, - debugInfo, ); + currentDebugInfo = prevDebugInfo; + return updated; } if (newChild.$$typeof === REACT_CONTEXT_TYPE) { @@ -1019,7 +1019,6 @@ function createChildReconciler( newIdx, readContextDuringReconciliation(returnFiber, context, lanes), lanes, - debugInfo, ); } @@ -1108,7 +1107,6 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChildren: Array, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber | null { // This algorithm can't optimize by searching from both ends since we // don't have backpointers on fibers. I'm trying to see how far we can get @@ -1150,7 +1148,6 @@ function createChildReconciler( oldFiber, newChildren[newIdx], lanes, - debugInfo, ); if (newFiber === null) { // TODO: This breaks on empty slots like null children. That's @@ -1208,12 +1205,7 @@ function createChildReconciler( // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; newIdx < newChildren.length; newIdx++) { - const newFiber = createChild( - returnFiber, - newChildren[newIdx], - lanes, - debugInfo, - ); + const newFiber = createChild(returnFiber, newChildren[newIdx], lanes); if (newFiber === null) { continue; } @@ -1252,7 +1244,6 @@ function createChildReconciler( newIdx, newChildren[newIdx], lanes, - debugInfo, ); if (newFiber !== null) { if (__DEV__) { @@ -1302,7 +1293,6 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChildrenIterable: Iterable, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber | null { // This is the same implementation as reconcileChildrenArray(), // but using the iterator instead. @@ -1361,7 +1351,6 @@ function createChildReconciler( currentFirstChild, newChildren, lanes, - debugInfo, ); } @@ -1370,7 +1359,6 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChildrenIterable: AsyncIterable, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber | null { const newChildren = newChildrenIterable[ASYNC_ITERATOR](); @@ -1419,7 +1407,6 @@ function createChildReconciler( currentFirstChild, iterator, lanes, - debugInfo, ); } @@ -1428,7 +1415,6 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChildren: ?Iterator, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber | null { if (newChildren == null) { throw new Error('An iterable object provided no iterator.'); @@ -1456,13 +1442,7 @@ function createChildReconciler( } else { nextOldFiber = oldFiber.sibling; } - const newFiber = updateSlot( - returnFiber, - oldFiber, - step.value, - lanes, - debugInfo, - ); + const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes); if (newFiber === null) { // TODO: This breaks on empty slots like null children. That's // unfortunate because it triggers the slow path all the time. We need @@ -1519,7 +1499,7 @@ function createChildReconciler( // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; !step.done; newIdx++, step = newChildren.next()) { - const newFiber = createChild(returnFiber, step.value, lanes, debugInfo); + const newFiber = createChild(returnFiber, step.value, lanes); if (newFiber === null) { continue; } @@ -1558,7 +1538,6 @@ function createChildReconciler( newIdx, step.value, lanes, - debugInfo, ); if (newFiber !== null) { if (__DEV__) { @@ -1632,7 +1611,6 @@ function createChildReconciler( currentFirstChild: Fiber | null, element: ReactElement, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber { const key = element.key; let child = currentFirstChild; @@ -1648,7 +1626,7 @@ function createChildReconciler( existing.return = returnFiber; if (__DEV__) { existing._debugOwner = element._owner; - existing._debugInfo = debugInfo; + existing._debugInfo = currentDebugInfo; } validateFragmentProps(element, existing, returnFiber); return existing; @@ -1675,7 +1653,7 @@ function createChildReconciler( existing.return = returnFiber; if (__DEV__) { existing._debugOwner = element._owner; - existing._debugInfo = debugInfo; + existing._debugInfo = currentDebugInfo; } return existing; } @@ -1698,7 +1676,7 @@ function createChildReconciler( ); created.return = returnFiber; if (__DEV__) { - created._debugInfo = debugInfo; + created._debugInfo = currentDebugInfo; } validateFragmentProps(element, created, returnFiber); return created; @@ -1707,7 +1685,7 @@ function createChildReconciler( coerceRef(returnFiber, currentFirstChild, created, element); created.return = returnFiber; if (__DEV__) { - created._debugInfo = debugInfo; + created._debugInfo = currentDebugInfo; } return created; } @@ -1718,7 +1696,6 @@ function createChildReconciler( currentFirstChild: Fiber | null, portal: ReactPortal, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber { const key = portal.key; let child = currentFirstChild; @@ -1758,7 +1735,6 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChild: any, lanes: Lanes, - debugInfo: ReactDebugInfo | null, ): Fiber | null { // This function is only recursive for Usables/Lazy and not nested arrays. // That's so that using a Lazy wrapper is unobservable to the Fragment @@ -1785,16 +1761,19 @@ function createChildReconciler( // Handle object types if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { - case REACT_ELEMENT_TYPE: - return placeSingleChild( + case REACT_ELEMENT_TYPE: { + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + const firstChild = placeSingleChild( reconcileSingleElement( returnFiber, currentFirstChild, newChild, lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), ), ); + currentDebugInfo = prevDebugInfo; + return firstChild; + } case REACT_PORTAL_TYPE: return placeSingleChild( reconcileSinglePortal( @@ -1802,52 +1781,66 @@ function createChildReconciler( currentFirstChild, newChild, lanes, - debugInfo, ), ); - case REACT_LAZY_TYPE: - const payload = newChild._payload; - const init = newChild._init; - return reconcileChildFibersImpl( + case REACT_LAZY_TYPE: { + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + let result; + if (__DEV__) { + result = callLazyInitInDEV(newChild); + } else { + const payload = newChild._payload; + const init = newChild._init; + result = init(payload); + } + const firstChild = reconcileChildFibersImpl( returnFiber, currentFirstChild, - init(payload), + result, lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return firstChild; + } } if (isArray(newChild)) { - return reconcileChildrenArray( + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + const firstChild = reconcileChildrenArray( returnFiber, currentFirstChild, newChild, lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return firstChild; } if (getIteratorFn(newChild)) { - return reconcileChildrenIteratable( + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + const firstChild = reconcileChildrenIteratable( returnFiber, currentFirstChild, newChild, lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return firstChild; } if ( enableAsyncIterableChildren && typeof newChild[ASYNC_ITERATOR] === 'function' ) { - return reconcileChildrenAsyncIteratable( + const prevDebugInfo = pushDebugInfo(newChild._debugInfo); + const firstChild = reconcileChildrenAsyncIteratable( returnFiber, currentFirstChild, newChild, lanes, - mergeDebugInfo(debugInfo, newChild._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return firstChild; } // Usables are a valid React node type. When React encounters a Usable in @@ -1868,13 +1861,15 @@ function createChildReconciler( // consider as an future improvement. if (typeof newChild.then === 'function') { const thenable: Thenable = (newChild: any); - return reconcileChildFibersImpl( + const prevDebugInfo = pushDebugInfo((thenable: any)._debugInfo); + const firstChild = reconcileChildFibersImpl( returnFiber, currentFirstChild, unwrapThenable(thenable), lanes, - mergeDebugInfo(debugInfo, thenable._debugInfo), ); + currentDebugInfo = prevDebugInfo; + return firstChild; } if (newChild.$$typeof === REACT_CONTEXT_TYPE) { @@ -1884,7 +1879,6 @@ function createChildReconciler( currentFirstChild, readContextDuringReconciliation(returnFiber, context, lanes), lanes, - debugInfo, ); } @@ -1926,6 +1920,8 @@ function createChildReconciler( newChild: any, lanes: Lanes, ): Fiber | null { + const prevDebugInfo = currentDebugInfo; + currentDebugInfo = null; try { // This indirection only exists so we can reset `thenableState` at the end. // It should get inlined by Closure. @@ -1935,7 +1931,6 @@ function createChildReconciler( currentFirstChild, newChild, lanes, - null, // debugInfo ); thenableState = null; // Don't bother to reset `thenableIndexCounter` to 0 because it always gets @@ -1963,7 +1958,12 @@ function createChildReconciler( // the error or suspending if needed. const throwFiber = createFiberFromThrow(x, returnFiber.mode, lanes); throwFiber.return = returnFiber; + if (__DEV__) { + throwFiber._debugInfo = currentDebugInfo; + } return throwFiber; + } finally { + currentDebugInfo = prevDebugInfo; } }