Skip to content

Large error messages parsed by logbox cause a significant slow down and crash in hermes #32093

Description

@dannyhw

Description

In react native when you log a large error to console.error and logbox attempts to parse it if the message is very large then the following regex throws an exception.

from node_modules/react-native/Libraries/LogBox/Data/parseLogBoxLog.js line 251

const BABEL_CODE_FRAME_ERROR_FORMAT = /^(?:TransformError )?(?:.*):? (?:.*?)(\/.*): ([\s\S]+?)\n([ >]{2}[\d\s]+ \|[\s\S]+|\u{001b}[\s\S]+)/u;

...
const babelCodeFrameError = message.match(BABEL_CODE_FRAME_ERROR_FORMAT);

image

Initially I raised the issue in the hermes repo facebook/hermes#581 and it was suggested to raise it here instead since the regex is seemingly slow and might need some changes.

This starting happening when we recently swapped to hermes on a project where sometimes we get large error messages that return a long graphql query and request body + stack trace. My workaround for now is to just truncate any messages or use a regular log message (not console.error or warn).

React Native version:

Run react-native info in your terminal and copy the results here.

System:
OS: macOS 10.15.6
CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Memory: 253.65 MB / 16.00 GB
Shell: 5.7.1 - /bin/zsh
Binaries:
Node: 14.17.0 - /usr/local/opt/node@14/bin/node
Yarn: 1.22.10 - /usr/local/bin/yarn
npm: 6.14.13 - /usr/local/opt/node@14/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.10.1 - /Users/williamsd2/.rvm/gems/ruby-2.7.0/bin/pod
SDKs:
iOS SDK:
Platforms: iOS 14.4, DriverKit 20.2, macOS 11.1, tvOS 14.3, watchOS 7.2
Android SDK: Not Found
IDEs:
Android Studio: 4.2 AI-202.7660.26.42.7351085
Xcode: 12.4/12D4e - /usr/bin/xcodebuild
Languages:
Java: 15.0.2 - /usr/local/opt/openjdk/bin/javac
npmPackages:
@react-native-community/cli: Not Found
react: 17.0.2 => 17.0.2
react-native: 0.65.1 => 0.65.1
react-native-macos: Not Found
npmGlobalPackages:
react-native: Not Found

Steps To Reproduce

I made a minimal repro here https://github.com/dannyhw/hermes-regex-repro

To recreate that you could do the following

  1. npx react-native init repro --template react-native-template-typescript
  2. follow react native docs to enable hermes
  3. add the code from the code example below into app.tsx (replace existing code)
  4. run the app

code example:

This is the main code in the repro mentioned

import {Text} from 'react-native';
import React from 'react';

const MESSAGE_REPEAT_AMOUNT = 1000;

const MESSAGE_TO_REPEAT = `Labore ad cupidatat dolor dolor officia consectetur voluptate veniam aute nisi fugiat pariatur. Tempor culpa quis non duis cillum. Aliquip enim consectetur commodo occaecat commodo id deserunt exercitation magna dolor elit in duis culpa. Esse aliqua excepteur ipsum non non occaecat voluptate et consequat veniam aliquip qui ipsum.`;
const LONG_MESSAGE = MESSAGE_TO_REPEAT.repeat(MESSAGE_REPEAT_AMOUNT);

export default () => {
  // @ts-ignore (hermes internal global variable not in global type)
  const isHermes = !!global.HermesInternal;
  console.error(LONG_MESSAGE);
  return (
    <Text style={{marginTop: 80}}>is it hermes? {isHermes ? 'yes' : 'no'}</Text>
  );
};

Expected Results

Large messages should not crash the app when parsed by logbox.
I think that the regex could maybe be split up to parts to for example not run the regex if the log doesn't contain the initial "TransformError" string at all.

I could work on it if that would be helpful however I would need guidance.

Snack, code example, screenshot, or link to a repository:

I have created a minimal repro here https://github.com/dannyhw/hermes-regex-repro

  1. clone the repro https://github.com/dannyhw/hermes-regex-repro
  2. yarn install, pod install, gradle clean etc
  3. yarn ios or yarn android
  4. when loading app.tsx it will attempt to log and display the logbox and parse the message which causes the exception in the screenshot

Activity

  1. stale commented on Jan 9, 2022

    @stale

    Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

  2. added
    StaleThere has been a lack of activity on this issue and it may be closed soon.
    on Jan 9, 2022
  3. github-actions commented on Jan 29, 2022

    @github-actions

    This issue was closed because it has been stalled for 7 days with no activity.

  4. locked as resolved and limited conversation to collaborators on Jan 29, 2022
  5. self-assigned this
    on Jun 21, 2023
  6. rickhanlonii commented on Jun 21, 2023

    @rickhanlonii
    Member

    Thanks for reporting - do you know how large the message needs to be to trigger this? Probably depends on the message?

  7. unlocked this conversation on Jun 21, 2023
  8. evelant commented on Jun 21, 2023

    @evelant

    @rickhanlonii I'm experiencing this issue as well. I'm not sure about the exact size of the message that trigger this. It appears to happen if logging any error with more content than a single stack trace. My case may be different than others however since I json stringify all of my logs for pretty printing in the console. Regardless the issue is still quite a drag on DX.

    There's a bit more info in the issue as originally filed on the hermes repo facebook/hermes#581

  9. evelant commented on Jun 21, 2023

    @evelant

    Here's an example of output that crashes the parsing (and hangs the app for 15-20s).

    2023-06-21T16:52:20 ERROR: Error building managed_runtime!
                                causePretty:
    TypeError: Cannot read property '_tag' of undefined
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:140981:21)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122238:27)
        at untraced (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122272:18)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122237:26)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:140975:27)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122182:46)
        at pipe (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:120606:24)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:201074:35)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122367:26)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122371:26)
        at value (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146364:26)
        at _loop2 (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146598:35)
        at runLoop (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146621:28)
        at _loop (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146183:40)
        at evaluateEffect (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146219:18)
        at evaluateMessageWhileSuspended (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146160:34)
        at drainQueueOnCurrentThread (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:145989:118)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:145758:40)
        at starveInternal (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:137918:19)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:137937:40)
        at tryCallOne (/Users/distiller/react-native/sdks/hermes/build_iphonesimulator/lib/InternalBytecode/InternalBytecode.js:53:16)
        at anonymous (/Users/distiller/react-native/sdks/hermes/build_iphonesimulator/lib/InternalBytecode/InternalBytecode.js:139:27)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:29382:26)
        at _callTimer (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:29301:17)
        at _callReactNativeMicrotasksPass (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:29331:17)
        at callReactNativeMicrotasks (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:29494:44)
        at __callReactNativeMicrotasks (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:2413:46)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:2225:45)
        at __guard (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:2397:15)
        at flushedQueue (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:2224:21)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:127593:27)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:127617:93)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:127617:59)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:127335:27)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:127617:59)
    
                                defects[1] > message: Cannot read property '_tag' of undefined
                                defects[1] > name: TypeError
                                defects[1] > stack: TypeError: Cannot read property '_tag' of undefined
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:140981:21)
        at apply (native)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122238:27)
        at untraced (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122272:18)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122237:26)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:140975:27)
        at apply (native)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122182:46)
        at pipe (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:120606:24)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:201074:35)
        at apply (native)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122367:26)
        at apply (native)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:122371:26)
        at value (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146364:26)
        at _loop2 (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146598:35)
        at runLoop (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146621:28)
        at _loop (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146183:40)
        at evaluateEffect (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146219:18)
        at evaluateMessageWhileSuspended (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:146160:34)
        at drainQueueOnCurrentThread (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:145989:118)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:145758:40)
        at starveInternal (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:137918:19)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:137937:40)
        at tryCallOne (/Users/distiller/react-native/sdks/hermes/build_iphonesimulator/lib/InternalBytecode/InternalBytecode.js:53:16)
        at anonymous (/Users/distiller/react-native/sdks/hermes/build_iphonesimulator/lib/InternalBytecode/InternalBytecode.js:139:27)
        at apply (native)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:29382:26)
        at _callTimer (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:29301:17)
        at _callReactNativeMicrotasksPass (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:29331:17)
        at callReactNativeMicrotasks (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:29494:44)
        at __callReactNativeMicrotasks (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:2413:46)
        at anonymous (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:2225:45)
        at __guard (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:2397:15)
        at flushedQueue (http://192.168.1.84:8081/index.bundle//&platform=ios&dev=true&hot=false:2224:21)
    ??? > RangeError: An error was thrown when attempting to render log messages via LogBox.
    ??? >
    ??? > Maximum regex stack depth reached, js engine: hermes
    
  10. evelant commented on Jun 24, 2023

    @evelant

    @motiz88 @rickhanlonii Thanks for the quick fix! Do you know when this will go out in a react-native release?

  11. cortinico commented on Jun 26, 2023

    @cortinico
    Contributor

    @motiz88 @rickhanlonii Thanks for the quick fix! Do you know when this will go out in a react-native release?

    It will land in 0.73.
    If you wish to have it backported to a previous release, please comment on https://github.com/reactwg/react-native-releases/

  12. evelant commented on Sep 18, 2023

    @evelant

    Since it's taking a long time for this to get into a release and it completely breaks android dev mode for some people here's a patch you can apply for now

    diff --git a/Libraries/LogBox/Data/parseLogBoxLog.js b/Libraries/LogBox/Data/parseLogBoxLog.js
    index 7b91d697a880e2660aa63a2d0acb58b1f875e84b..4e4cea4f249f109a6b35395aeb08317864b927cd 100644
    --- a/Libraries/LogBox/Data/parseLogBoxLog.js
    +++ b/Libraries/LogBox/Data/parseLogBoxLog.js
    @@ -14,9 +14,35 @@ import type {LogBoxLogData} from './LogBoxLog';
     import parseErrorStack from '../../Core/Devtools/parseErrorStack';
     import UTFSequence from '../../UTFSequence';
     import stringifySafe from '../../Utilities/stringifySafe';
    +import ansiRegex from 'ansi-regex';
    +
    +const ANSI_REGEX = ansiRegex().source;
     
     const BABEL_TRANSFORM_ERROR_FORMAT =
       /^(?:TransformError )?(?:SyntaxError: |ReferenceError: )(.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/;
    +
    +
    +// https://github.com/babel/babel/blob/33dbb85e9e9fe36915273080ecc42aee62ed0ade/packages/babel-code-frame/src/index.ts#L183-L184
    +const BABEL_CODE_FRAME_MARKER_PATTERN = new RegExp(
    +  [
    +    // Beginning of a line (per 'm' flag)
    +    '^',
    +    // Optional ANSI escapes for colors
    +    `(?:${ANSI_REGEX})*`,
    +    // Marker
    +    '>',
    +    // Optional ANSI escapes for colors
    +    `(?:${ANSI_REGEX})*`,
    +    // Left padding for line number
    +    ' +',
    +    // Line number
    +    '[0-9]+',
    +    // Gutter
    +    ' \\|',
    +  ].join(''),
    +  'm',
    +);
    +
     const BABEL_CODE_FRAME_ERROR_FORMAT =
       // eslint-disable-next-line no-control-regex
       /^(?:TransformError )?(?:.*):? (?:.*?)(\/.*): ([\s\S]+?)\n([ >]{2}[\d\s]+ \|[\s\S]+|\u{001b}[\s\S]+)/u;
    @@ -241,27 +267,31 @@ export function parseLogBoxException(
         };
       }
     
    -  const babelCodeFrameError = message.match(BABEL_CODE_FRAME_ERROR_FORMAT);
    +  // Perform a cheap match first before trying to parse the full message, which
    +  // can get expensive for arbitrary input.
    +  if (BABEL_CODE_FRAME_MARKER_PATTERN.test(message)) {
    +    const babelCodeFrameError = message.match(BABEL_CODE_FRAME_ERROR_FORMAT);
    +    if (babelCodeFrameError) {
    +      // Codeframe errors are thrown from any use of buildCodeFrameError.
    +      const [fileName, content, codeFrame] = babelCodeFrameError.slice(1);
    +      return {
    +        level: 'syntax',
    +        stack: [],
    +        isComponentError: false,
    +        componentStack: [],
    +        codeFrame: {
    +          fileName,
    +          location: null, // We are not given the location.
    +          content: codeFrame,
    +        },
    +        message: {
    +          content,
    +          substitutions: [],
    +        },
    +        category: `${fileName}-${1}-${1}`,
    +      };
    +    }
     
    -  if (babelCodeFrameError) {
    -    // Codeframe errors are thrown from any use of buildCodeFrameError.
    -    const [fileName, content, codeFrame] = babelCodeFrameError.slice(1);
    -    return {
    -      level: 'syntax',
    -      stack: [],
    -      isComponentError: false,
    -      componentStack: [],
    -      codeFrame: {
    -        fileName,
    -        location: null, // We are not given the location.
    -        content: codeFrame,
    -      },
    -      message: {
    -        content,
    -        substitutions: [],
    -      },
    -      category: `${fileName}-${1}-${1}`,
    -    };
       }
     
       if (message.match(/^TransformError /)) {
    diff --git a/package.json b/package.json
    index fcb222fd4c530ce928dae9ff011caa7bd4703d59..3d2618a5607a40e033cb2a7ad717319c64956384 100644
    --- a/package.json
    +++ b/package.json
    @@ -90,6 +90,7 @@
         "@react-native/virtualized-lists": "^0.72.8",
         "abort-controller": "^3.0.0",
         "anser": "^1.4.9",
    +    "ansi-regex": "^5.0.0",
         "base64-js": "^1.1.2",
         "deprecated-react-native-prop-types": "4.1.0",
         "event-target-shim": "^5.0.1",
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Needs: Triage 🔍StaleThere has been a lack of activity on this issue and it may be closed soon.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions