Repository: facebook/react
Pull request: [Flight] Pass line/column to `filterStackFrame`

Stacked on https://github.com/facebook/react/pull/33706

`filterStackFrame` implementations can fast-path ignore-listing by checking if the corresponding source map ignore-lists everything. However, this check requires checking every section for [Index Source Maps](https://tc39.es/ecma426/#sec-index-source-map) which deopts the fast-path check from a constant check (comparing `.length`) to looping through every section (i.e. `O(N)`).

By passing in the relevant line and column, a `filterStackFrame` implementation can find the relevant section with binary search (`O(log N)`) e.g. https://github.com/vercel/next.js/blob/cc27c243af15703ee4f1bce2dd0afa6960a0f011/packages/next/src/server/lib/source-maps.ts#L60-L82

Changed files:
filename: packages/react-client/src/__tests__/ReactFlight-test.js
patch: @@ -1309,6 +1309,8 @@ describe('ReactFlight', () => {
         // third-party RSC frame
         // Ideally this would be a real frame produced by React not a mocked one.
         '    at ThirdParty (rsc://React/ThirdParty/file:///code/%5Broot%2520of%2520the%2520server%5D.js?42:1:1)',
+        // We'll later filter this out based on line/column in `filterStackFrame`.
+        '    at ThirdPartyModule (file:///file-with-index-source-map.js:52656:16374)',
         // host component in parent stack
         '    at div (<anonymous>)',
         ...originalStackLines.slice(2),
@@ -1357,7 +1359,10 @@ describe('ReactFlight', () => {
         }
         return `digest(${String(x)})`;
       },
-      filterStackFrame(filename, functionName) {
+      filterStackFrame(filename, functionName, lineNumber, columnNumber) {
+        if (lineNumber === 52656 && columnNumber === 16374) {
+          return false;
+        }
         if (!filename) {
           // Allow anonymous
           return functionName === 'div';
@@ -3682,7 +3687,7 @@ describe('ReactFlight', () => {
         onError(x) {
           return `digest("${x.message}")`;
         },
-        filterStackFrame(url, functionName) {
+        filterStackFrame(url, functionName, lineNumber, columnNumber) {
           return functionName !== 'intermediate';
         },
       },

filename: packages/react-server/src/ReactFlightServer.js
patch: @@ -204,11 +204,13 @@ function findCalledFunctionNameFromStackTrace(
     const callsite = stack[i];
     const functionName = callsite[0];
     const url = devirtualizeURL(callsite[1]);
+    const lineNumber = callsite[2];
+    const columnNumber = callsite[3];
     if (functionName === 'new Promise') {
       // Ignore Promise constructors.
     } else if (url === 'node:internal/async_hooks') {
       // Ignore the stack frames from the async hooks themselves.
-    } else if (filterStackFrame(url, functionName)) {
+    } else if (filterStackFrame(url, functionName, lineNumber, columnNumber)) {
       if (bestMatch === '') {
         // If we had no good stack frames for internal calls, just use the last
         // first party function name.
@@ -236,7 +238,9 @@ function filterStackTrace(
     const callsite = stack[i];
     const functionName = callsite[0];
     const url = devirtualizeURL(callsite[1]);
-    if (filterStackFrame(url, functionName)) {
+    const lineNumber = callsite[2];
+    const columnNumber = callsite[3];
+    if (filterStackFrame(url, functionName, lineNumber, columnNumber)) {
       // Use a clone because the Flight protocol isn't yet resilient to deduping
       // objects in the debug info. TODO: Support deduping stacks.
       const clone: ReactCallSite = (callsite.slice(0): any);
@@ -466,7 +470,12 @@ export type Request = {
   // DEV-only
   completedDebugChunks: Array<Chunk | BinaryChunk>,
   environmentName: () => string,
-  filterStackFrame: (url: string, functionName: string) => boolean,
+  filterStackFrame: (
+    url: string,
+    functionName: string,
+    lineNumber: number,
+    columnNumber: number,
+  ) => boolean,
   didWarnForKey: null | WeakSet<ReactComponentInfo>,
   writtenDebugObjects: WeakMap<Reference, string>,
   deferredDebugObjects: null | DeferredDebugStore,
@@ -2180,7 +2189,14 @@ function visitAsyncNode(
               const callsite = fullStack[firstFrame];
               const functionName = callsite[0];
               const url = devirtualizeURL(callsite[1]);
-              isAwaitInUserspace = filterStackFrame(url, functionName);
+              const lineNumber = callsite[2];
+              const columnNumber = callsite[3];
+              isAwaitInUserspace = filterStackFrame(
+                url,
+                functionName,
+                lineNumber,
+                columnNumber,
+              );
             }
             if (!isAwaitInUserspace) {
               // If this await was fully filtered out, then it was inside third party code