|
8 | 8 | #include "JsErrorHandler.h" |
9 | 9 | #include <cxxreact/ErrorUtils.h> |
10 | 10 | #include <glog/logging.h> |
11 | | -#include <regex> |
12 | | -#include <sstream> |
13 | 11 | #include <string> |
14 | | -#include <vector> |
| 12 | +#include "StackTraceParser.h" |
| 13 | + |
| 14 | +using namespace facebook; |
15 | 15 |
|
16 | 16 | namespace { |
17 | 17 | std::string quote(const std::string& view) { |
18 | 18 | return "\"" + view + "\""; |
19 | 19 | } |
| 20 | + |
| 21 | +int nextExceptionId() { |
| 22 | + static int exceptionId = 0; |
| 23 | + return exceptionId++; |
| 24 | +} |
| 25 | + |
| 26 | +bool isLooselyNull(const jsi::Value& value) { |
| 27 | + return value.isNull() || value.isUndefined(); |
| 28 | +} |
| 29 | + |
| 30 | +bool isEmptyString(jsi::Runtime& runtime, const jsi::Value& value) { |
| 31 | + return jsi::Value::strictEquals( |
| 32 | + runtime, value, jsi::String::createFromUtf8(runtime, "")); |
| 33 | +} |
| 34 | + |
| 35 | +std::string stringifyToCpp(jsi::Runtime& runtime, const jsi::Value& value) { |
| 36 | + return value.toString(runtime).utf8(runtime); |
| 37 | +} |
20 | 38 | } // namespace |
21 | 39 |
|
22 | 40 | namespace facebook::react { |
@@ -65,97 +83,6 @@ std::ostream& operator<<( |
65 | 83 | return os; |
66 | 84 | } |
67 | 85 |
|
68 | | -// TODO(T198763073): Migrate away from std::regex in this function |
69 | | -static JsErrorHandler::ParsedError parseErrorStack( |
70 | | - jsi::Runtime& runtime, |
71 | | - const jsi::JSError& error, |
72 | | - bool isFatal, |
73 | | - bool isHermes) { |
74 | | - /** |
75 | | - * This parses the different stack traces and puts them into one format |
76 | | - * This borrows heavily from TraceKit (https://github.com/occ/TraceKit) |
77 | | - * This is the same regex from stacktrace-parser.js. |
78 | | - */ |
79 | | - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful |
80 | | - const std::regex REGEX_CHROME( |
81 | | - R"(^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$)"); |
82 | | - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful |
83 | | - const std::regex REGEX_GECKO( |
84 | | - R"(^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$)"); |
85 | | - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful |
86 | | - const std::regex REGEX_NODE( |
87 | | - R"(^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$)"); |
88 | | - |
89 | | - // Capture groups for Hermes (from parseHermesStack.js): |
90 | | - // 1. function name |
91 | | - // 2. is this a native stack frame? |
92 | | - // 3. is this a bytecode address or a source location? |
93 | | - // 4. source URL (filename) |
94 | | - // 5. line number (1 based) |
95 | | - // 6. column number (1 based) or virtual offset (0 based) |
96 | | - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful |
97 | | - const std::regex REGEX_HERMES( |
98 | | - R"(^ {4}at (.+?)(?: \((native)\)?| \((address at )?(.*?):(\d+):(\d+)\))$)"); |
99 | | - |
100 | | - std::string line; |
101 | | - std::stringstream strStream(error.getStack()); |
102 | | - |
103 | | - std::vector<JsErrorHandler::ParsedError::StackFrame> frames; |
104 | | - |
105 | | - while (std::getline(strStream, line, '\n')) { |
106 | | - auto searchResults = std::smatch{}; |
107 | | - |
108 | | - if (isHermes) { |
109 | | - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful |
110 | | - if (std::regex_search(line, searchResults, REGEX_HERMES)) { |
111 | | - std::string str2 = std::string(searchResults[2]); |
112 | | - if (str2.compare("native")) { |
113 | | - frames.push_back({ |
114 | | - .file = std::string(searchResults[4]), |
115 | | - .methodName = std::string(searchResults[1]), |
116 | | - .lineNumber = std::stoi(searchResults[5]), |
117 | | - .column = std::stoi(searchResults[6]), |
118 | | - }); |
119 | | - } |
120 | | - } |
121 | | - } else { |
122 | | - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful |
123 | | - if (std::regex_search(line, searchResults, REGEX_GECKO)) { |
124 | | - frames.push_back({ |
125 | | - .file = std::string(searchResults[3]), |
126 | | - .methodName = std::string(searchResults[1]), |
127 | | - .lineNumber = std::stoi(searchResults[4]), |
128 | | - .column = std::stoi(searchResults[5]), |
129 | | - }); |
130 | | - } else if ( |
131 | | - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful |
132 | | - std::regex_search(line, searchResults, REGEX_CHROME) || |
133 | | - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful |
134 | | - std::regex_search(line, searchResults, REGEX_NODE)) { |
135 | | - frames.push_back({ |
136 | | - .file = std::string(searchResults[2]), |
137 | | - .methodName = std::string(searchResults[1]), |
138 | | - .lineNumber = std::stoi(searchResults[3]), |
139 | | - .column = std::stoi(searchResults[4]), |
140 | | - }); |
141 | | - } else { |
142 | | - continue; |
143 | | - } |
144 | | - } |
145 | | - } |
146 | | - |
147 | | - return { |
148 | | - .message = "EarlyJsError: " + error.getMessage(), |
149 | | - .originalMessage = std::nullopt, |
150 | | - .name = std::nullopt, |
151 | | - .componentStack = std::nullopt, |
152 | | - .stack = std::move(frames), |
153 | | - .id = 0, |
154 | | - .isFatal = isFatal, |
155 | | - .extraData = jsi::Object(runtime), |
156 | | - }; |
157 | | -} |
158 | | - |
159 | 86 | JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError) |
160 | 87 | : _onJsError(std::move(onJsError)), |
161 | 88 | _hasHandledFatalError(false){ |
@@ -183,8 +110,75 @@ void JsErrorHandler::handleFatalError( |
183 | 110 | << "Original js error: " << error.getMessage() << std::endl; |
184 | 111 | } |
185 | 112 | } |
186 | | - // This is a hacky way to get Hermes stack trace. |
187 | | - ParsedError parsedError = parseErrorStack(runtime, error, true, false); |
| 113 | + |
| 114 | + auto message = error.getMessage(); |
| 115 | + auto errorObj = error.value().getObject(runtime); |
| 116 | + auto componentStackValue = errorObj.getProperty(runtime, "componentStack"); |
| 117 | + if (!isLooselyNull(componentStackValue)) { |
| 118 | + message += "\n" + stringifyToCpp(runtime, componentStackValue); |
| 119 | + } |
| 120 | + |
| 121 | + auto nameValue = errorObj.getProperty(runtime, "name"); |
| 122 | + auto name = (isLooselyNull(nameValue) || isEmptyString(runtime, nameValue)) |
| 123 | + ? std::nullopt |
| 124 | + : std::optional(stringifyToCpp(runtime, nameValue)); |
| 125 | + |
| 126 | + if (name && !message.starts_with(*name + ": ")) { |
| 127 | + message = *name + ": " + message; |
| 128 | + } |
| 129 | + |
| 130 | + auto jsEngineValue = errorObj.getProperty(runtime, "jsEngine"); |
| 131 | + |
| 132 | + if (!isLooselyNull(jsEngineValue)) { |
| 133 | + message += ", js engine: " + stringifyToCpp(runtime, jsEngineValue); |
| 134 | + } |
| 135 | + |
| 136 | + // TODO: What about spreading in decoratedExtraDataKey? |
| 137 | + auto extraData = jsi::Object(runtime); |
| 138 | + extraData.setProperty(runtime, "jsEngine", jsEngineValue); |
| 139 | + extraData.setProperty(runtime, "rawStack", error.getStack()); |
| 140 | + |
| 141 | + auto cause = errorObj.getProperty(runtime, "cause"); |
| 142 | + if (cause.isObject()) { |
| 143 | + auto causeObj = cause.asObject(runtime); |
| 144 | + // TODO: Consider just forwarding all properties. For now, just forward the |
| 145 | + // stack properties to maintain symmetry with js pipeline |
| 146 | + auto stackSymbols = causeObj.getProperty(runtime, "stackSymbols"); |
| 147 | + extraData.setProperty(runtime, "stackSymbols", stackSymbols); |
| 148 | + |
| 149 | + auto stackReturnAddresses = |
| 150 | + causeObj.getProperty(runtime, "stackReturnAddresses"); |
| 151 | + extraData.setProperty( |
| 152 | + runtime, "stackReturnAddresses", stackReturnAddresses); |
| 153 | + |
| 154 | + auto stackElements = causeObj.getProperty(runtime, "stackElements"); |
| 155 | + extraData.setProperty(runtime, "stackElements", stackElements); |
| 156 | + } |
| 157 | + |
| 158 | + auto originalMessage = message == error.getMessage() |
| 159 | + ? std::nullopt |
| 160 | + : std::optional(error.getMessage()); |
| 161 | + |
| 162 | + auto componentStack = !componentStackValue.isString() |
| 163 | + ? std::nullopt |
| 164 | + : std::optional(componentStackValue.asString(runtime).utf8(runtime)); |
| 165 | + |
| 166 | + auto isHermes = runtime.global().hasProperty(runtime, "HermesInternal"); |
| 167 | + auto stackFrames = StackTraceParser::parse(isHermes, error.getStack()); |
| 168 | + |
| 169 | + auto id = nextExceptionId(); |
| 170 | + |
| 171 | + ParsedError parsedError = { |
| 172 | + .message = "EarlyJsError: " + message, |
| 173 | + .originalMessage = originalMessage, |
| 174 | + .name = name, |
| 175 | + .componentStack = componentStack, |
| 176 | + .stack = stackFrames, |
| 177 | + .id = id, |
| 178 | + .isFatal = true, |
| 179 | + .extraData = std::move(extraData), |
| 180 | + }; |
| 181 | + |
188 | 182 | _onJsError(runtime, parsedError); |
189 | 183 | } |
190 | 184 |
|
|
0 commit comments