diff --git a/packages/react-native/ReactCommon/jserrorhandler/CMakeLists.txt b/packages/react-native/ReactCommon/jserrorhandler/CMakeLists.txt index 7015dc360a7b02..71ee2630328601 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/CMakeLists.txt +++ b/packages/react-native/ReactCommon/jserrorhandler/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_VERBOSE_MAKEFILE on) add_compile_options(-std=c++20) -file(GLOB_RECURSE js_error_handler_SRC CONFIGURE_DEPENDS *.cpp) +file(GLOB js_error_handler_SRC CONFIGURE_DEPENDS *.cpp) add_library( jserrorhandler OBJECT diff --git a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp index 6fb7d8705514ca..95578b0dc356b7 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp +++ b/packages/react-native/ReactCommon/jserrorhandler/JsErrorHandler.cpp @@ -8,15 +8,33 @@ #include "JsErrorHandler.h" #include #include -#include -#include +#include #include -#include + +using namespace facebook; namespace { std::string quote(const std::string& view) { return "\"" + view + "\""; } + +int nextExceptionId() { + static int exceptionId = 0; + return exceptionId++; +} + +bool isLooselyNull(const jsi::Value& value) { + return value.isNull() || value.isUndefined(); +} + +bool isEmptyString(jsi::Runtime& runtime, const jsi::Value& value) { + return jsi::Value::strictEquals( + runtime, value, jsi::String::createFromUtf8(runtime, "")); +} + +std::string stringifyToCpp(jsi::Runtime& runtime, const jsi::Value& value) { + return value.toString(runtime).utf8(runtime); +} } // namespace namespace facebook::react { @@ -65,97 +83,6 @@ std::ostream& operator<<( return os; } -// TODO(T198763073): Migrate away from std::regex in this function -static JsErrorHandler::ParsedError parseErrorStack( - jsi::Runtime& runtime, - const jsi::JSError& error, - bool isFatal, - bool isHermes) { - /** - * This parses the different stack traces and puts them into one format - * This borrows heavily from TraceKit (https://github.com/occ/TraceKit) - * This is the same regex from stacktrace-parser.js. - */ - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful - const std::regex REGEX_CHROME( - R"(^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$)"); - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful - const std::regex REGEX_GECKO( - R"(^(?:\s*([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$)"); - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful - const std::regex REGEX_NODE( - R"(^\s*at (?:((?:\[object object\])?\S+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$)"); - - // Capture groups for Hermes (from parseHermesStack.js): - // 1. function name - // 2. is this a native stack frame? - // 3. is this a bytecode address or a source location? - // 4. source URL (filename) - // 5. line number (1 based) - // 6. column number (1 based) or virtual offset (0 based) - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful - const std::regex REGEX_HERMES( - R"(^ {4}at (.+?)(?: \((native)\)?| \((address at )?(.*?):(\d+):(\d+)\))$)"); - - std::string line; - std::stringstream strStream(error.getStack()); - - std::vector frames; - - while (std::getline(strStream, line, '\n')) { - auto searchResults = std::smatch{}; - - if (isHermes) { - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful - if (std::regex_search(line, searchResults, REGEX_HERMES)) { - std::string str2 = std::string(searchResults[2]); - if (str2.compare("native")) { - frames.push_back({ - .file = std::string(searchResults[4]), - .methodName = std::string(searchResults[1]), - .lineNumber = std::stoi(searchResults[5]), - .column = std::stoi(searchResults[6]), - }); - } - } - } else { - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful - if (std::regex_search(line, searchResults, REGEX_GECKO)) { - frames.push_back({ - .file = std::string(searchResults[3]), - .methodName = std::string(searchResults[1]), - .lineNumber = std::stoi(searchResults[4]), - .column = std::stoi(searchResults[5]), - }); - } else if ( - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful - std::regex_search(line, searchResults, REGEX_CHROME) || - // @lint-ignore CLANGTIDY facebook-hte-StdRegexIsAwful - std::regex_search(line, searchResults, REGEX_NODE)) { - frames.push_back({ - .file = std::string(searchResults[2]), - .methodName = std::string(searchResults[1]), - .lineNumber = std::stoi(searchResults[3]), - .column = std::stoi(searchResults[4]), - }); - } else { - continue; - } - } - } - - return { - .message = "EarlyJsError: " + error.getMessage(), - .originalMessage = std::nullopt, - .name = std::nullopt, - .componentStack = std::nullopt, - .stack = std::move(frames), - .id = 0, - .isFatal = isFatal, - .extraData = jsi::Object(runtime), - }; -} - JsErrorHandler::JsErrorHandler(JsErrorHandler::OnJsError onJsError) : _onJsError(std::move(onJsError)), _hasHandledFatalError(false){ @@ -183,8 +110,75 @@ void JsErrorHandler::handleFatalError( << "Original js error: " << error.getMessage() << std::endl; } } - // This is a hacky way to get Hermes stack trace. - ParsedError parsedError = parseErrorStack(runtime, error, true, false); + + auto message = error.getMessage(); + auto errorObj = error.value().getObject(runtime); + auto componentStackValue = errorObj.getProperty(runtime, "componentStack"); + if (!isLooselyNull(componentStackValue)) { + message += "\n" + stringifyToCpp(runtime, componentStackValue); + } + + auto nameValue = errorObj.getProperty(runtime, "name"); + auto name = (isLooselyNull(nameValue) || isEmptyString(runtime, nameValue)) + ? std::nullopt + : std::optional(stringifyToCpp(runtime, nameValue)); + + if (name && !message.starts_with(*name + ": ")) { + message = *name + ": " + message; + } + + auto jsEngineValue = errorObj.getProperty(runtime, "jsEngine"); + + if (!isLooselyNull(jsEngineValue)) { + message += ", js engine: " + stringifyToCpp(runtime, jsEngineValue); + } + + // TODO: What about spreading in decoratedExtraDataKey? + auto extraData = jsi::Object(runtime); + extraData.setProperty(runtime, "jsEngine", jsEngineValue); + extraData.setProperty(runtime, "rawStack", error.getStack()); + + auto cause = errorObj.getProperty(runtime, "cause"); + if (cause.isObject()) { + auto causeObj = cause.asObject(runtime); + // TODO: Consider just forwarding all properties. For now, just forward the + // stack properties to maintain symmetry with js pipeline + auto stackSymbols = causeObj.getProperty(runtime, "stackSymbols"); + extraData.setProperty(runtime, "stackSymbols", stackSymbols); + + auto stackReturnAddresses = + causeObj.getProperty(runtime, "stackReturnAddresses"); + extraData.setProperty( + runtime, "stackReturnAddresses", stackReturnAddresses); + + auto stackElements = causeObj.getProperty(runtime, "stackElements"); + extraData.setProperty(runtime, "stackElements", stackElements); + } + + auto originalMessage = message == error.getMessage() + ? std::nullopt + : std::optional(error.getMessage()); + + auto componentStack = !componentStackValue.isString() + ? std::nullopt + : std::optional(componentStackValue.asString(runtime).utf8(runtime)); + + auto isHermes = runtime.global().hasProperty(runtime, "HermesInternal"); + auto stackFrames = StackTraceParser::parse(isHermes, error.getStack()); + + auto id = nextExceptionId(); + + ParsedError parsedError = { + .message = "EarlyJsError: " + message, + .originalMessage = originalMessage, + .name = name, + .componentStack = componentStack, + .stack = stackFrames, + .id = id, + .isFatal = true, + .extraData = std::move(extraData), + }; + _onJsError(runtime, parsedError); } diff --git a/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec b/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec index 4c8d71179cfa88..c97fb6a292d98d 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec +++ b/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec @@ -22,7 +22,7 @@ folly_version = folly_config[:version] folly_dep_name = folly_config[:dep_name] boost_config = get_boost_config() -boost_compiler_flags = boost_config[:compiler_flags] +boost_compiler_flags = boost_config[:compiler_flags] react_native_path = ".." Pod::Spec.new do |s| @@ -35,7 +35,7 @@ Pod::Spec.new do |s| s.platforms = min_supported_versions s.source = source s.header_dir = "jserrorhandler" - s.source_files = "JsErrorHandler.{cpp,h}" + s.source_files = "JsErrorHandler.{cpp,h}", "StackTraceParser.{cpp,h}" s.pod_target_xcconfig = { "USE_HEADERMAP" => "YES", "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard() @@ -52,7 +52,7 @@ Pod::Spec.new do |s| s.dependency "React-cxxreact" s.dependency "glog" add_dependency(s, "React-debug") - + if ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == "1" s.dependency 'hermes-engine' end diff --git a/packages/react-native/ReactCommon/jserrorhandler/StackTraceParser.cpp b/packages/react-native/ReactCommon/jserrorhandler/StackTraceParser.cpp new file mode 100644 index 00000000000000..2bcaeb19d27632 --- /dev/null +++ b/packages/react-native/ReactCommon/jserrorhandler/StackTraceParser.cpp @@ -0,0 +1,317 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include +#include +#include +#include + +using namespace facebook::react; + +const std::string UNKNOWN_FUNCTION = ""; + +// TODO(T198763073): Migrate away from std::regex in this file +// @lint-ignore-every CLANGTIDY facebook-hte-StdRegexIsAwful + +/** + * Stack trace parsing for other jsvms: + * Port of https://github.com/errwischt/stacktrace-parser + */ +namespace { + +std::optional toInt(std::string_view input) { + int out; + const std::from_chars_result result = + std::from_chars(input.data(), input.data() + input.size(), out); + if (result.ec == std::errc::invalid_argument || + result.ec == std::errc::result_out_of_range) { + return std::nullopt; + } + return out; +} + +JsErrorHandler::ParsedError::StackFrame parseStackFrame( + std::string_view file, + std::string_view methodName, + std::string_view lineStr, + std::string_view columnStr) { + JsErrorHandler::ParsedError::StackFrame frame; + frame.file = file.empty() ? std::nullopt : std::optional(file); + frame.methodName = !methodName.empty() ? methodName : UNKNOWN_FUNCTION; + frame.lineNumber = !lineStr.empty() ? toInt(lineStr) : std::nullopt; + auto columnOpt = !columnStr.empty() ? toInt(columnStr) : std::nullopt; + frame.column = columnOpt ? std::optional(*columnOpt - 1) : std::nullopt; + return frame; +} + +std::optional parseChrome( + const std::string& line) { + static const std::regex chromeRe( + R"(^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/|[a-z]:\\|\\\\).*?)(?::(\d+))?(?::(\d+))?\)?\s*$)", + std::regex::icase); + static const std::regex chromeEvalRe(R"(\((\S*)(?::(\d+))(?::(\d+))\))"); + std::smatch match; + + if (!std::regex_match(line, match, chromeRe)) { + return std::nullopt; + } + std::string methodName = match[1].str(); + std::string file = match[2].str(); + std::string lineStr = match[3].str(); + std::string columnStr = match[4].str(); + + bool isNative = std::regex_search(file, std::regex("^native")); + bool isEval = std::regex_search(file, std::regex("^eval")); + std::string evalFile; + std::string evalLine; + std::string evalColumn; + if (isEval && std::regex_search(file, match, chromeEvalRe)) { + evalFile = match[1].str(); + evalLine = match[2].str(); + evalColumn = match[3].str(); + file = evalFile; + lineStr = evalLine; + columnStr = evalColumn; + } + std::string actualFile = !isNative ? file : ""; + return parseStackFrame(actualFile, methodName, lineStr, columnStr); +} + +std::optional parseWinjs( + const std::string& line) { + static const std::regex winjsRe( + R"(^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$)", + std::regex::icase); + std::smatch match; + if (!std::regex_match(line, match, winjsRe)) { + return std::nullopt; + } + std::string methodName = match[1].str(); + std::string file = match[2].str(); + std::string lineStr = match[3].str(); + std::string columnStr = match[4].str(); + return parseStackFrame(file, methodName, lineStr, columnStr); +} + +std::optional parseGecko( + const std::string& line) { + static const std::regex geckoRe( + R"(^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$)", + std::regex::icase); + static const std::regex geckoEvalRe( + R"((\S+) line (\d+)(?: > eval line \d+)* > eval)", std::regex::icase); + std::smatch match; + if (!std::regex_match(line, match, geckoRe)) { + return std::nullopt; + } + std::string methodName = match[1].str(); + std::string tmpStr = match[2].str(); + std::string file = match[3].str(); + std::string lineStr = match[4].str(); + std::string columnStr = match[5].str(); + bool isEval = std::regex_search(file, std::regex(" > eval")); + std::string evalFile; + std::string evalLine; + if (isEval && std::regex_search(file, match, geckoEvalRe)) { + evalFile = match[1].str(); + evalLine = match[2].str(); + file = evalFile; + lineStr = evalLine; + columnStr = ""; // No column number in eval + } + return parseStackFrame(file, methodName, lineStr, columnStr); +} + +std::optional parseJSC( + const std::string& line) { + static const std::regex javaScriptCoreRe( + R"(^\s*(?:([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$)", + std::regex::icase); + std::smatch match; + if (!std::regex_match(line, match, javaScriptCoreRe)) { + return std::nullopt; + } + std::string methodName = match[1].str(); + std::string tmpStr = + match[2].str(); // This captures any string within parentheses if present + std::string file = match[3].str(); + std::string lineStr = match[4].str(); + std::string columnStr = match[5].str(); + return parseStackFrame(file, methodName, lineStr, columnStr); +} + +std::optional parseNode( + const std::string& line) { + static const std::regex nodeRe( + R"(^\s*at (?:((?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$)", + std::regex::icase); + std::smatch match; + if (!std::regex_match(line, match, nodeRe)) { + return std::nullopt; + } + std::string methodName = match[1].str(); + std::string file = match[2].str(); + std::string lineStr = match[3].str(); + std::string columnStr = match[4].str(); + return parseStackFrame(file, methodName, lineStr, columnStr); +} + +std::vector parseOthers( + const std::string& stackString) { + std::vector stack; + std::istringstream iss(stackString); + std::string line; + + while (std::getline(iss, line)) { + std::optional frame = + parseChrome(line); + + if (!frame) { + frame = parseWinjs(line); + } + if (!frame) { + frame = parseGecko(line); + } + if (!frame) { + frame = parseNode(line); + } + if (!frame) { + frame = parseJSC(line); + } + + if (frame) { + stack.push_back(*frame); + } + } + + return stack; +} + +} // namespace + +/** + * Hermes stack trace parsing logic + */ +namespace { +struct HermesStackLocation { + std::string type; + std::string sourceUrl; + int line1Based{}; + int column1Based{}; + int virtualOffset0Based{}; +}; + +struct HermesStackEntry { + std::string type; + std::string functionName; + HermesStackLocation location; + int count{}; +}; + +bool isInternalBytecodeSourceUrl(const std::string& sourceUrl) { + return sourceUrl == "InternalBytecode.js"; +} + +std::vector convertHermesStack( + const std::vector& stack) { + std::vector frames; + for (const auto& entry : stack) { + if (entry.type != "FRAME") { + continue; + } + if (entry.location.type == "NATIVE" || + entry.location.type == "INTERNAL_BYTECODE") { + continue; + } + JsErrorHandler::ParsedError::StackFrame frame; + frame.methodName = entry.functionName; + frame.file = entry.location.sourceUrl; + frame.lineNumber = entry.location.line1Based; + if (entry.location.type == "SOURCE") { + frame.column = entry.location.column1Based - 1; + } else { + frame.column = entry.location.virtualOffset0Based; + } + frames.push_back(frame); + } + return frames; +} + +HermesStackEntry parseLine(const std::string& line) { + static const std::regex RE_FRAME( + R"(^ {4}at (.+?)(?: \((native)\)?| \((address at )?(.*?):(\d+):(\d+)\))$)"); + static const std::regex RE_SKIPPED(R"(^ {4}... skipping (\d+) frames$)"); + HermesStackEntry entry; + std::smatch match; + if (std::regex_match(line, match, RE_FRAME)) { + entry.type = "FRAME"; + entry.functionName = match[1].str(); + std::string type = match[2].str(); + std::string addressAt = match[3].str(); + std::string sourceUrl = match[4].str(); + if (type == "native") { + entry.location.type = "NATIVE"; + } else { + int line1Based = std::stoi(match[5].str()); + int columnOrOffset = std::stoi(match[6].str()); + if (addressAt == "address at ") { + if (isInternalBytecodeSourceUrl(sourceUrl)) { + entry.location = { + "INTERNAL_BYTECODE", sourceUrl, line1Based, 0, columnOrOffset}; + } else { + entry.location = { + "BYTECODE", sourceUrl, line1Based, 0, columnOrOffset}; + } + } else { + entry.location = {"SOURCE", sourceUrl, line1Based, columnOrOffset, 0}; + } + } + return entry; + } + if (std::regex_match(line, match, RE_SKIPPED)) { + entry.type = "SKIPPED"; + entry.count = std::stoi(match[1].str()); + } + return entry; +} + +std::vector parseHermes( + const std::string& stack) { + static const std::regex RE_COMPONENT_NO_STACK(R"(^ {4}at .*?$)"); + std::istringstream stream(stack); + std::string line; + std::vector entries; + std::smatch match; + while (std::getline(stream, line)) { + if (line.empty()) { + continue; + } + HermesStackEntry entry = parseLine(line); + if (!entry.type.empty()) { + entries.push_back(entry); + continue; + } + + if (std::regex_match(line, match, RE_COMPONENT_NO_STACK)) { + continue; + } + entries.clear(); + } + return convertHermesStack(entries); +} +} // namespace + +std::vector StackTraceParser::parse( + const bool isHermes, + const std::string& stackString) { + std::vector stackFrames = + isHermes ? parseHermes(stackString) : parseOthers(stackString); + return stackFrames; +} diff --git a/packages/react-native/ReactCommon/jserrorhandler/StackTraceParser.h b/packages/react-native/ReactCommon/jserrorhandler/StackTraceParser.h new file mode 100644 index 00000000000000..ab94503a2d5372 --- /dev/null +++ b/packages/react-native/ReactCommon/jserrorhandler/StackTraceParser.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +class StackTraceParser { + public: + static std::vector parse( + bool isHermes, + const std::string& stackString); +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/jserrorhandler/tests/StackTraceParserTest.cpp b/packages/react-native/ReactCommon/jserrorhandler/tests/StackTraceParserTest.cpp new file mode 100644 index 00000000000000..83737775a89056 --- /dev/null +++ b/packages/react-native/ReactCommon/jserrorhandler/tests/StackTraceParserTest.cpp @@ -0,0 +1,1196 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include + +using namespace facebook::react; + +#include +#include + +std::unordered_map CapturedExceptions = { + {"NODE_12", + "Error: Just an Exception\n" + " at promiseMe (/home/xyz/hack/asyncnode.js:11:9)\n" + " at async main (/home/xyz/hack/asyncnode.js:15:13)"}, + {"NODE_ANONYM", + "Error\n" + " at Spect.get (C:\\projects\\spect\\src\\index.js:161:26)\n" + " at Object.get (C:\\projects\\spect\\src\\index.js:43:36)\n" + " at \n" + " at (anonymous function).then (C:\\projects\\spect\\src\\index.js:165:33)\n" + " at process.runNextTicks [as _tickCallback] (internal/process/task_queues.js:52:5)\n" + " at C:\\projects\\spect\\node_modules\\esm\\esm.js:1:34535\n" + " at C:\\projects\\spect\\node_modules\\esm\\esm.js:1:34176\n" + " at process. (C:\\projects\\spect\\node_modules\\esm\\esm.js:1:34506)\n" + " at Function. (C:\\projects\\spect\\node_modules\\esm\\esm.js:1:296856)\n" + " at Function. (C:\\projects\\spect\\node_modules\\esm\\esm.js:1:296555)"}, + {"NODE_SPACE", + "Error\n" + " at Spect.get (C:\\project files\\spect\\src\\index.js:161:26)\n" + " at Object.get (C:\\project files\\spect\\src\\index.js:43:36)\n" + " at \n" + " at (anonymous function).then (C:\\project files\\spect\\src\\index.js:165:33)\n" + " at process.runNextTicks [as _tickCallback] (internal/process/task_queues.js:52:5)\n" + " at C:\\project files\\spect\\node_modules\\esm\\esm.js:1:34535\n" + " at C:\\project files\\spect\\node_modules\\esm\\esm.js:1:34176\n" + " at process. (C:\\project files\\spect\\node_modules\\esm\\esm.js:1:34506)\n" + " at Function. (C:\\project files\\spect\\node_modules\\esm\\esm.js:1:296856)\n" + " at Function. (C:\\project files\\spect\\node_modules\\esm\\esm.js:1:296555)"}, + {"OPERA_25", + "TypeError: Cannot read property 'undef' of null\n" + " at http://path/to/file.js:47:22\n" + " at foo (http://path/to/file.js:52:15)\n" + " at bar (http://path/to/file.js:108:168)"}, + {"CHROME_15", + "TypeError: Object # has no method 'undef'\n" + " at bar (http://path/to/file.js:13:17)\n" + " at bar (http://path/to/file.js:16:5)\n" + " at foo (http://path/to/file.js:20:5)\n" + " at http://path/to/file.js:24:4"}, + {"CHROME_36", + "Error: Default error\n" + " at dumpExceptionError (http://localhost:8080/file.js:41:27)\n" + " at HTMLButtonElement.onclick (http://localhost:8080/file.js:107:146)\n" + " at I.e.fn.(anonymous function) [as index] (http://localhost:8080/file.js:10:3651)"}, + {"CHROME_76", + "Error: BEEP BEEP\n" + " at bar (:8:9)\n" + " at async foo (:2:3)"}, + {"CHROME_XX_WEBPACK", + "TypeError: Cannot read property 'error' of undefined\n" + " at TESTTESTTEST.eval(webpack:///./src/components/test/test.jsx?:295:108)\n" + " at TESTTESTTEST.render(webpack:///./src/components/test/test.jsx?:272:32)\n" + " at TESTTESTTEST.tryRender(webpack:///./~/react-transform-catch-errors/lib/index.js?:34:31)\n" + " at TESTTESTTEST.proxiedMethod(webpack:///./~/react-proxy/modules/createPrototypeProxy.js?:44:30)\n" + " at Module../pages/index.js (C:\\root\\server\\development\\pages\\index.js:182:7)"}, + {"FIREFOX_3", + "()@http://127.0.0.1:8000/js/stacktrace.js:44\n" + "(null)@http://127.0.0.1:8000/js/stacktrace.js:31\n" + "printStackTrace()@http://127.0.0.1:8000/js/stacktrace.js:18\n" + "bar(1)@http://127.0.0.1:8000/js/file.js:13\n" + "bar(2)@http://127.0.0.1:8000/js/file.js:16\n" + "foo()@http://127.0.0.1:8000/js/file.js:20\n" + "@http://127.0.0.1:8000/js/file.js:24\n"}, + {"FIREFOX_7", + "()@file:///G:/js/stacktrace.js:44\n" + "(null)@file:///G:/js/stacktrace.js:31\n" + "printStackTrace()@file:///G:/js/stacktrace.js:18\n" + "bar(1)@file:///G:/js/file.js:13\n" + "bar(2)@file:///G:/js/file.js:16\n" + "foo()@file:///G:/js/file.js:20\n" + "@file:///G:/js/file.js:24\n"}, + {"FIREFOX_14", + "@http://path/to/file.js:48\n" + "dumpException3@http://path/to/file.js:52\n" + "onclick@http://path/to/file.js:1\n"}, + {"FIREFOX_31", + "foo@http://path/to/file.js:41:13\n" + "bar@http://path/to/file.js:1:1\n" + ".plugin/e.fn[c]/<@http://path/to/file.js:1:1\n"}, + {"FIREFOX_43_EVAL", + "baz@http://localhost:8080/file.js line 26 > eval line 2 > eval:1:30\n" + "foo@http://localhost:8080/file.js line 26 > eval:2:96\n" + "@http://localhost:8080/file.js line 26 > eval:4:18\n" + "speak@http://localhost:8080/file.js:26:17\n" + "@http://localhost:8080/file.js:33:9"}, + {"FIREFOX_44_NS_EXCEPTION", + "[2]:1:30)\n" + "at foo (eval at speak (http://localhost:8080/file.js:21:17), :2:96)\n" + "at eval (eval at speak (http://localhost:8080/file.js:21:17), :4:18)\n" + "at Object.speak (http://localhost:8080/file.js:21:17)\n" + "at http://localhost:8080/file.js:31:13\n"}, + {"PHANTOMJS_1_19", + "Error: foo\n" + " at file:///path/to/file.js:878\n" + " at foo (http://path/to/file.js:4283)\n" + " at http://path/to/file.js:4287"}, + {"ANDROID_REACT_NATIVE", + "Error: test\n" + "at render(/home/username/sample-workspace/sampleapp.collect.react/src/components/GpsMonitorScene.js:78:24)\n" + "at _renderValidatedComponentWithoutOwnerOrContext(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:1050:29)\n" + "at _renderValidatedComponent(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:1075:15)\n" + "at renderedElement(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:484:29)\n" + "at _currentElement(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js:346:40)\n" + "at child(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactReconciler.js:68:25)\n" + "at children(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/shared/stack/reconciler/ReactMultiChild.js:264:10)\n" + "at this(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/native/ReactNativeBaseComponent.js:74:41)\n"}, + {"ANDROID_REACT_NATIVE_PROD", + "value@index.android.bundle:12:1917\n" + "onPress@index.android.bundle:12:2336\n" + "touchableHandlePress@index.android.bundle:258:1497\n" + "[native code]\n" + "_performSideEffectsForTransition@index.android.bundle:252:8508\n" + "[native code]\n" + "_receiveSignal@index.android.bundle:252:7291\n" + "[native code]\n" + "touchableHandleResponderRelease@index.android.bundle:252:4735\n" + "[native code]\n" + "u@index.android.bundle:79:142\n" + "invokeGuardedCallback@index.android.bundle:79:459\n" + "invokeGuardedCallbackAndCatchFirstError@index.android.bundle:79:580\n" + "c@index.android.bundle:95:365\n" + "a@index.android.bundle:95:567\n" + "v@index.android.bundle:146:501\n" + "g@index.android.bundle:146:604\n" + "forEach@[native code]\n" + "i@index.android.bundle:149:80\n" + "processEventQueue@index.android.bundle:146:1432\n" + "s@index.android.bundle:157:88\n" + "handleTopLevel@index.android.bundle:157:174\n" + "index.android.bundle:156:572\n" + "a@index.android.bundle:93:276\n" + "c@index.android.bundle:93:60\n" + "perform@index.android.bundle:177:596\n" + "batchedUpdates@index.android.bundle:188:464\n" + "i@index.android.bundle:176:358\n" + "i@index.android.bundle:93:90\n" + "u@index.android.bundle:93:150\n" + "_receiveRootNodeIDEvent@index.android.bundle:156:544\n" + "receiveTouches@index.android.bundle:156:918\n" + "value@index.android.bundle:29:3016\n" + "index.android.bundle:29:955\n" + "value@index.android.bundle:29:2417\n" + "value@index.android.bundle:29:927\n" + "[native code]"}, + {"IOS_REACT_NATIVE_1", + "_exampleFunction@/home/test/project/App.js:125:13\n" + "_depRunCallbacks@/home/test/project/node_modules/dep/index.js:77:45\n" + "tryCallTwo@/home/test/project/node_modules/react-native/node_modules/promise/lib/core.js:45:5\n" + "doResolve@/home/test/project/node_modules/react-native/node_modules/promise/lib/core.js:200:13"}, + {"IOS_REACT_NATIVE_2", + "s@33.js:1:531\n" + "b@1959.js:1:1469\n" + "onSocketClose@2932.js:1:727\n" + "value@81.js:1:1505\n" + "102.js:1:2956\n" + "value@89.js:1:1247\n" + "value@42.js:1:3311\n" + "42.js:1:822\n" + "value@42.js:1:2565\n" + "value@42.js:1:794\n" + "value@[native code]"}, + + {"ANONYMOUS_SOURCES", + "x\n" + "at new (http://www.example.com/test.js:2:1\n" + "at :1:2\n"}, + {"NODE_JS_TEST_1", + "ReferenceError: test is not defined\n" + "at repl:1:2\n" + "at REPLServer.self.eval (repl.js:110:21)\n" + "at Interface. (repl.js:239:12)\n" + "at Interface.EventEmitter.emit (events.js:95:17)\n" + "at emitKey (readline.js:1095:12)\n"}, + {"NODE_JS_TEST_2", + "ReferenceError: breakDown is not defined\n" + "at null._onTimeout (repl:1:25)\n" + "at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)\n"}, + {"IO_JS", + "ReferenceError: test is not defined\n" + "at repl:1:1\n" + "at REPLServer.defaultEval (repl.js:154:27)\n" + "at bound (domain.js:254:14)\n" + "at REPLServer.runBound [as eval] (domain.js:267:12)\n" + "at REPLServer. (repl.js:308:12)\n" + "at emitOne (events.js:77:13)\n" + "at REPLServer.emit (events.js:169:7)\n" + "at REPLServer.Interface._onLine (readline.js:210:10)\n" + "at REPLServer.Interface._line (readline.js:549:8)\n" + "at REPLServer.Interface._ttyWrite (readline.js:826:14)\n"}}; + +TEST(StackTraceParser, nodeWithSpaceInPath) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["NODE_SPACE"]); + EXPECT_EQ(actualStackFrames.size(), 9); + + std::vector expectedStackFrames = { + {R"(C:\project files\spect\src\index.js)", "Spect.get", 161, 25}, + {R"(C:\project files\spect\src\index.js)", "Object.get", 43, 35}, + {R"(C:\project files\spect\src\index.js)", + "(anonymous function).then", + 165, + 32}, + {"internal/process/task_queues.js", + "process.runNextTicks [as _tickCallback]", + 52, + 4}, + {R"(C:\project files\spect\node_modules\esm\esm.js)", + "", + 1, + 34534}, + {R"(C:\project files\spect\node_modules\esm\esm.js)", + "", + 1, + 34175}, + {R"(C:\project files\spect\node_modules\esm\esm.js)", + "process.", + 1, + 34505}, + {R"(C:\project files\spect\node_modules\esm\esm.js)", + "Function.", + 1, + 296855}, + {R"(C:\project files\spect\node_modules\esm\esm.js)", + "Function.", + 1, + 296554}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, javaScriptCore) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["IOS_REACT_NATIVE_1"]); + EXPECT_EQ(actualStackFrames.size(), 4); + + std::vector expectedStackFrames = { + {"/home/test/project/App.js", "_exampleFunction", 125, 12}, + {"/home/test/project/node_modules/dep/index.js", + "_depRunCallbacks", + 77, + 44}, + {"/home/test/project/node_modules/react-native/node_modules/promise/lib/core.js", + "tryCallTwo", + 45, + 4}, + {"/home/test/project/node_modules/react-native/node_modules/promise/lib/core.js", + "doResolve", + 200, + 12}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, errorInReactNative) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["IOS_REACT_NATIVE_2"]); + EXPECT_EQ(actualStackFrames.size(), 11); + + std::vector expectedStackFrames = { + {"33.js", "s", 1, 530}, + {"1959.js", "b", 1, 1468}, + {"2932.js", "onSocketClose", 1, 726}, + {"81.js", "value", 1, 1504}, + {"102.js", "", 1, 2955}, + {"89.js", "value", 1, 1246}, + {"42.js", "value", 1, 3310}, + {"42.js", "", 1, 821}, + {"42.js", "value", 1, 2564}, + {"42.js", "value", 1, 793}, + {"[native code]", "value", std::nullopt, std::nullopt}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, simpleJavaScriptCoreErrors) { + auto actualStackFrames = + StackTraceParser::parse(false, "global code@stack_traces/test:83:55"); + EXPECT_EQ(actualStackFrames.size(), 1); + + std::vector expectedStackFrames = { + {"stack_traces/test", "global code", 83, 54}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, safari6Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["SAFARI_6"]); + EXPECT_EQ(actualStackFrames.size(), 4); + + std::vector expectedStackFrames = { + {"http://path/to/file.js", "", 48, std::nullopt}, + {"http://path/to/file.js", "dumpException3", 52, std::nullopt}, + {"http://path/to/file.js", "onclick", 82, std::nullopt}, + {"[native code]", "", std::nullopt, std::nullopt}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, safari7Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["SAFARI_7"]); + EXPECT_EQ(actualStackFrames.size(), 3); + + std::vector expectedStackFrames = { + {"http://path/to/file.js", "", 48, 21}, + {"http://path/to/file.js", "foo", 52, 14}, + {"http://path/to/file.js", "bar", 108, 106}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, safari8Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["SAFARI_8"]); + EXPECT_EQ(actualStackFrames.size(), 3); + + std::vector expectedStackFrames = { + {"http://path/to/file.js", "", 47, 21}, + {"http://path/to/file.js", "foo", 52, 14}, + {"http://path/to/file.js", "bar", 108, 22}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, safari8EvalError) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["SAFARI_8_EVAL"]); + EXPECT_EQ(actualStackFrames.size(), 3); + + std::vector expectedStackFrames = { + {"[native code]", "eval", std::nullopt, std::nullopt}, + {"http://path/to/file.js", "foo", 58, 20}, + {"http://path/to/file.js", "bar", 109, 90}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, firefox3Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["FIREFOX_3"]); + EXPECT_EQ(actualStackFrames.size(), 7); + + std::vector expectedStackFrames = { + {"http://127.0.0.1:8000/js/stacktrace.js", "", 44, std::nullopt}, + {"http://127.0.0.1:8000/js/stacktrace.js", "", 31, std::nullopt}, + {"http://127.0.0.1:8000/js/stacktrace.js", + "printStackTrace", + 18, + std::nullopt}, + {"http://127.0.0.1:8000/js/file.js", "bar", 13, std::nullopt}, + {"http://127.0.0.1:8000/js/file.js", "bar", 16, std::nullopt}, + {"http://127.0.0.1:8000/js/file.js", "foo", 20, std::nullopt}, + {"http://127.0.0.1:8000/js/file.js", "", 24, std::nullopt}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, firefox7Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["FIREFOX_7"]); + EXPECT_EQ(actualStackFrames.size(), 7); + + std::vector expectedStackFrames = { + {"file:///G:/js/stacktrace.js", "", 44, std::nullopt}, + {"file:///G:/js/stacktrace.js", "", 31, std::nullopt}, + {"file:///G:/js/stacktrace.js", "printStackTrace", 18, std::nullopt}, + {"file:///G:/js/file.js", "bar", 13, std::nullopt}, + {"file:///G:/js/file.js", "bar", 16, std::nullopt}, + {"file:///G:/js/file.js", "foo", 20, std::nullopt}, + {"file:///G:/js/file.js", "", 24, std::nullopt}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, firefox14Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["FIREFOX_14"]); + EXPECT_EQ(actualStackFrames.size(), 3); + + std::vector expectedStackFrames = { + {"http://path/to/file.js", "", 48, std::nullopt}, + {"http://path/to/file.js", "dumpException3", 52, std::nullopt}, + {"http://path/to/file.js", "onclick", 1, std::nullopt}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, firefox31Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["FIREFOX_31"]); + EXPECT_EQ(actualStackFrames.size(), 3); + + std::vector expectedStackFrames = { + {"http://path/to/file.js", "foo", 41, 12}, + {"http://path/to/file.js", "bar", 1, 0}, + {"http://path/to/file.js", ".plugin/e.fn[c]/<", 1, 0}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, firefox44) { + auto actualStackFrames = StackTraceParser::parse( + false, CapturedExceptions["FIREFOX_44_NS_EXCEPTION"]); + EXPECT_EQ(actualStackFrames.size(), 4); + + std::vector expectedStackFrames = { + {"http://path/to/file.js", "[2]", 23, 0}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, chromeErrorWithNoLocation) { + auto actualStackFrames = + StackTraceParser::parse(false, "error\n at Array.forEach (native)"); + EXPECT_EQ(actualStackFrames.size(), 1); + + std::vector expectedStackFrames = { + {std::nullopt, "Array.forEach", std::nullopt, std::nullopt}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, chrome15Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["CHROME_15"]); + EXPECT_EQ(actualStackFrames.size(), 4); + + std::vector expectedStackFrames = { + {"http://path/to/file.js", "bar", 13, 16}, + {"http://path/to/file.js", "bar", 16, 4}, + {"http://path/to/file.js", "foo", 20, 4}, + {"http://path/to/file.js", "", 24, 3}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, chrome36Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["CHROME_36"]); + EXPECT_EQ(actualStackFrames.size(), 3); + + std::vector expectedStackFrames = { + {"http://localhost:8080/file.js", "dumpExceptionError", 41, 26}, + {"http://localhost:8080/file.js", "HTMLButtonElement.onclick", 107, 145}, + {"http://localhost:8080/file.js", + "I.e.fn.(anonymous function) [as index]", + 10, + 3650}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, chrome76Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["CHROME_76"]); + EXPECT_EQ(actualStackFrames.size(), 2); + + std::vector expectedStackFrames = { + {"", "bar", 8, 8}, {"", "async foo", 2, 2}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, chromeErrorWithWebpackURLS) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["CHROME_XX_WEBPACK"]); + EXPECT_EQ(actualStackFrames.size(), 5); + + std::vector expectedStackFrames = { + {"webpack:///./src/components/test/test.jsx?", + "TESTTESTTEST.eval", + 295, + 107}, + {"webpack:///./src/components/test/test.jsx?", + "TESTTESTTEST.render", + 272, + 31}, + {"webpack:///./~/react-transform-catch-errors/lib/index.js?", + "TESTTESTTEST.tryRender", + 34, + 30}, + {"webpack:///./~/react-proxy/modules/createPrototypeProxy.js?", + "TESTTESTTEST.proxiedMethod", + 44, + 29}, + {R"(C:\root\server\development\pages\index.js)", + "Module../pages/index.js", + 182, + 6}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, nestedEvalsFromChrome) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["CHROME_48_EVAL"]); + EXPECT_EQ(actualStackFrames.size(), 5); + + std::vector expectedStackFrames = { + {"http://localhost:8080/file.js", "baz", 21, 16}, + {"http://localhost:8080/file.js", "foo", 21, 16}, + {"http://localhost:8080/file.js", "eval", 21, 16}, + {"http://localhost:8080/file.js", "Object.speak", 21, 16}, + {"http://localhost:8080/file.js", "", 31, 12}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, chromeErrorWithBlobURLs) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["CHROME_48_BLOB"]); + EXPECT_EQ(actualStackFrames.size(), 7); + + std::vector expectedStackFrames = { + {std::nullopt, "Error", std::nullopt, std::nullopt}, + {"blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379", + "s", + 31, + 29145}, + {"blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379", + "Object.d [as add]", + 31, + 30038}, + {"blob:http%3A//localhost%3A8080/d4eefe0f-361a-4682-b217-76587d9f712a", + "", + 15, + 10977}, + {"blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379", + "", + 1, + 6910}, + {"blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379", + "n.fire", + 7, + 3018}, + {"blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379", + "n.handle", + 7, + 2862}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, ie10Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["IE_10"]); + EXPECT_EQ(actualStackFrames.size(), 3); + + std::vector expectedStackFrames = { + {"http://path/to/file.js", "Anonymous function", 48, 12}, + {"http://path/to/file.js", "foo", 46, 8}, + {"http://path/to/file.js", "bar", 82, 0}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, ie11Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["IE_11"]); + EXPECT_EQ(actualStackFrames.size(), 3); + + std::vector expectedStackFrames = { + {"http://path/to/file.js", "Anonymous function", 47, 20}, + {"http://path/to/file.js", "foo", 45, 12}, + {"http://path/to/file.js", "bar", 108, 0}}; + + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, ie11EvalError) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["IE_11_EVAL"]); + EXPECT_EQ(actualStackFrames.size(), 3); + std::vector expectedStackFrames = { + {"eval code", "eval code", 1, 0}, + {"http://path/to/file.js", "foo", 58, 16}, + {"http://path/to/file.js", "bar", 109, 0}}; + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, Opera25Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["OPERA_25"]); + EXPECT_EQ(actualStackFrames.size(), 3); + std::vector expectedStackFrames = { + {"http://path/to/file.js", "", 47, 21}, + {"http://path/to/file.js", "foo", 52, 14}, + {"http://path/to/file.js", "bar", 108, 167}}; + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, PhantomJS119Error) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["PHANTOMJS_1_19"]); + EXPECT_EQ(actualStackFrames.size(), 3); + std::vector expectedStackFrames = { + {"file:///path/to/file.js", "", 878, std::nullopt}, + {"http://path/to/file.js", "foo", 4283, std::nullopt}, + {"http://path/to/file.js", "", 4287, std::nullopt}}; + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, FirefoxResourceUrlError) { + auto actualStackFrames = StackTraceParser::parse( + false, CapturedExceptions["FIREFOX_50_RESOURCE_URL"]); + EXPECT_EQ(actualStackFrames.size(), 3); + std::vector expectedStackFrames = { + {"resource://path/data/content/bundle.js", "render", 5529, 15}}; + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, FirefoxEvalUrlError) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["FIREFOX_43_EVAL"]); + EXPECT_EQ(actualStackFrames.size(), 5); + std::vector expectedStackFrames = { + {"http://localhost:8080/file.js", "baz", 26, std::nullopt}, + {"http://localhost:8080/file.js", "foo", 26, std::nullopt}, + {"http://localhost:8080/file.js", "", 26, std::nullopt}, + {"http://localhost:8080/file.js", "speak", 26, 16}, + {"http://localhost:8080/file.js", "", 33, 8}}; + for (auto i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, ReactNativeAndroidError) { + auto actualStackFrames = StackTraceParser::parse( + false, CapturedExceptions["ANDROID_REACT_NATIVE"]); + EXPECT_EQ(actualStackFrames.size(), 8); + std::vector expectedStackFrames = { + {"/home/username/sample-workspace/sampleapp.collect.react/src/components/GpsMonitorScene.js", + "render", + 78, + 23}, + {"/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/native/ReactNativeBaseComponent.js", + "this", + 74, + 40}}; + + EXPECT_EQ(actualStackFrames[0].column, expectedStackFrames[0].column); + EXPECT_EQ(actualStackFrames[0].file, expectedStackFrames[0].file); + EXPECT_EQ(actualStackFrames[0].lineNumber, expectedStackFrames[0].lineNumber); + EXPECT_EQ(actualStackFrames[0].methodName, expectedStackFrames[0].methodName); + + EXPECT_EQ(actualStackFrames[7].column, expectedStackFrames[1].column); + EXPECT_EQ(actualStackFrames[7].file, expectedStackFrames[1].file); + EXPECT_EQ(actualStackFrames[7].lineNumber, expectedStackFrames[1].lineNumber); + EXPECT_EQ(actualStackFrames[7].methodName, expectedStackFrames[1].methodName); +} + +TEST(StackTraceParser, ReactNativeAndroidProdError) { + auto actualStackFrames = StackTraceParser::parse( + false, CapturedExceptions["ANDROID_REACT_NATIVE_PROD"]); + EXPECT_EQ(actualStackFrames.size(), 37); + std::vector expectedStackFrames = { + {"index.android.bundle", "value", 12, 1916}, + {"index.android.bundle", "value", 29, 926}, + {"[native code]", "", std::nullopt, std::nullopt}}; + EXPECT_EQ(actualStackFrames[0].column, expectedStackFrames[0].column); + EXPECT_EQ(actualStackFrames[0].file, expectedStackFrames[0].file); + EXPECT_EQ(actualStackFrames[0].lineNumber, expectedStackFrames[0].lineNumber); + EXPECT_EQ(actualStackFrames[0].methodName, expectedStackFrames[0].methodName); + + EXPECT_EQ(actualStackFrames[35].column, expectedStackFrames[1].column); + EXPECT_EQ(actualStackFrames[35].file, expectedStackFrames[1].file); + EXPECT_EQ( + actualStackFrames[35].lineNumber, expectedStackFrames[1].lineNumber); + EXPECT_EQ( + actualStackFrames[35].methodName, expectedStackFrames[1].methodName); + + EXPECT_EQ(actualStackFrames[36].column, expectedStackFrames[2].column); + EXPECT_EQ(actualStackFrames[36].file, expectedStackFrames[2].file); + EXPECT_EQ( + actualStackFrames[36].lineNumber, expectedStackFrames[2].lineNumber); + EXPECT_EQ( + actualStackFrames[36].methodName, expectedStackFrames[2].methodName); +} + +TEST(StackTraceParser, NodeJsAsyncErrorsVersion12) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["NODE_12"]); + EXPECT_EQ(actualStackFrames.size(), 2); + std::vector expectedStackFrames = { + {"/home/xyz/hack/asyncnode.js", "promiseMe", 11, 8}, + {"/home/xyz/hack/asyncnode.js", "async main", 15, 12}}; + + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, NodeJsErrorsWithAnonymousCalls) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["NODE_ANONYM"]); + EXPECT_EQ(actualStackFrames.size(), 9); + std::vector expectedStackFrames = { + {R"(C:\projects\spect\src\index.js)", "Spect.get", 161, 25}, + {R"(C:\projects\spect\src\index.js)", + "(anonymous function).then", + 165, + 32}, + {R"(C:\projects\spect\node_modules\esm\esm.js)", "", 1, 34534}, + {R"(C:\projects\spect\node_modules\esm\esm.js)", + "process.", + 1, + 34505}}; + // Check specific stack frames as per the JavaScript test + EXPECT_EQ(actualStackFrames[0].column, expectedStackFrames[0].column); + EXPECT_EQ(actualStackFrames[0].file, expectedStackFrames[0].file); + EXPECT_EQ(actualStackFrames[0].lineNumber, expectedStackFrames[0].lineNumber); + EXPECT_EQ(actualStackFrames[0].methodName, expectedStackFrames[0].methodName); + + EXPECT_EQ(actualStackFrames[2].column, expectedStackFrames[1].column); + EXPECT_EQ(actualStackFrames[2].file, expectedStackFrames[1].file); + EXPECT_EQ(actualStackFrames[2].lineNumber, expectedStackFrames[1].lineNumber); + EXPECT_EQ(actualStackFrames[2].methodName, expectedStackFrames[1].methodName); + + EXPECT_EQ(actualStackFrames[4].column, expectedStackFrames[2].column); + EXPECT_EQ(actualStackFrames[4].file, expectedStackFrames[2].file); + EXPECT_EQ(actualStackFrames[4].lineNumber, expectedStackFrames[2].lineNumber); + EXPECT_EQ(actualStackFrames[4].methodName, expectedStackFrames[2].methodName); + + EXPECT_EQ(actualStackFrames[6].column, expectedStackFrames[3].column); + EXPECT_EQ(actualStackFrames[6].file, expectedStackFrames[3].file); + EXPECT_EQ(actualStackFrames[6].lineNumber, expectedStackFrames[3].lineNumber); + EXPECT_EQ(actualStackFrames[6].methodName, expectedStackFrames[3].methodName); +} + +TEST(StackTraceParser, AnonymousSources) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["ANONYMOUS_SOURCES"]); + EXPECT_EQ(actualStackFrames.size(), 2); + std::vector expectedStackFrames = { + {"http://www.example.com/test.js", "new ", 2, 0}, + {"", "", 1, 1}}; + + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, NodeJsTest1) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["NODE_JS_TEST_1"]); + EXPECT_EQ(actualStackFrames.size(), 5); + std::vector expectedStackFrames = { + {"repl", "", 1, 1}, + {"repl.js", "REPLServer.self.eval", 110, 20}, + {"repl.js", "Interface.", 239, 11}, + {"events.js", "Interface.EventEmitter.emit", 95, 16}, + {"readline.js", "emitKey", 1095, 11}}; + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, NodeJsTest2) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["NODE_JS_TEST_2"]); + EXPECT_EQ(actualStackFrames.size(), 2); + std::vector expectedStackFrames = { + {"repl", "null._onTimeout", 1, 24}, + {"timers.js", "Timer.listOnTimeout [as ontimeout]", 110, 14}}; + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, IoJs) { + auto actualStackFrames = + StackTraceParser::parse(false, CapturedExceptions["IO_JS"]); + EXPECT_EQ(actualStackFrames.size(), 10); + std::vector expectedStackFrames = { + {"repl", "", 1, 0}, + {"repl.js", "REPLServer.defaultEval", 154, 26}, + {"domain.js", "bound", 254, 13}, + {"domain.js", "REPLServer.runBound [as eval]", 267, 11}, + {"repl.js", "REPLServer.", 308, 11}, + {"events.js", "emitOne", 77, 12}, + {"events.js", "REPLServer.emit", 169, 6}, + {"readline.js", "REPLServer.Interface._onLine", 210, 9}, + {"readline.js", "REPLServer.Interface._line", 549, 7}, + {"readline.js", "REPLServer.Interface._ttyWrite", 826, 13}}; + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +/** + * Hermes tests + */ +TEST(StackTraceParser, hermesBytecodeLocation) { + auto actualStackFrames = StackTraceParser::parse( + true, + "TypeError: undefined is not a function\n" + " at global (address at unknown:1:9)\n" + " at foo$bar (address at /js/foo.hbc:10:1234)"); + + EXPECT_EQ(actualStackFrames.size(), 2); + std::vector expectedStackFrames = { + {"unknown", "global", 1, 9}, {"/js/foo.hbc", "foo$bar", 10, 1234}}; + + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, internalBytecodeLocation) { + auto actualStackFrames = StackTraceParser::parse( + true, + "TypeError: undefined is not a function\n" + " at internal (address at InternalBytecode.js:1:9)\n" + " at notInternal (address at /js/InternalBytecode.js:10:1234)"); + EXPECT_EQ(actualStackFrames.size(), 1); + std::vector expectedStackFrames = { + {"/js/InternalBytecode.js", "notInternal", 10, 1234}}; + + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, sourceLocation) { + auto actualStackFrames = StackTraceParser::parse( + true, + "TypeError: undefined is not a function\n" + " at global (unknown:1:9)\n" + " at foo$bar (/js/foo.js:10:1234)"); + EXPECT_EQ(actualStackFrames.size(), 2); + std::vector expectedStackFrames = { + {"unknown", "global", 1, 8}, {"/js/foo.js", "foo$bar", 10, 1233}}; + + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, tolerateEmptyFilename) { + auto actualStackFrames = StackTraceParser::parse( + true, + "TypeError: undefined is not a function\n" + " at global (unknown:1:9)\n" + " at foo$bar (:10:1234)"); + EXPECT_EQ(actualStackFrames.size(), 2); + std::vector expectedStackFrames = { + {"unknown", "global", 1, 8}, {"", "foo$bar", 10, 1233}}; + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, skippedFrames) { + auto actualStackFrames = StackTraceParser::parse( + true, + "TypeError: undefined is not a function\n" + " at global (unknown:1:9)\n" + " ... skipping 50 frames\n" + " at foo$bar (/js/foo.js:10:1234)"); + EXPECT_EQ(actualStackFrames.size(), 2); + std::vector expectedStackFrames = { + {"unknown", "global", 1, 8}, {"/js/foo.js", "foo$bar", 10, 1233}}; + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} + +TEST(StackTraceParser, handleNonStandardLines) { + auto actualStackFrames = StackTraceParser::parse( + true, + "The next line is not a stack frame\n" + " at bogus (filename:1:2)\n" + " but the real stack trace follows below.\n" + " at foo$bar (/js/foo.js:10:1234)"); + EXPECT_EQ(actualStackFrames.size(), 1); + std::vector expectedStackFrames = { + {"/js/foo.js", "foo$bar", 10, 1233}}; + for (size_t i = 0; i < expectedStackFrames.size(); i++) { + EXPECT_EQ(actualStackFrames[i].column, expectedStackFrames[i].column); + EXPECT_EQ(actualStackFrames[i].file, expectedStackFrames[i].file); + EXPECT_EQ( + actualStackFrames[i].lineNumber, expectedStackFrames[i].lineNumber); + EXPECT_EQ( + actualStackFrames[i].methodName, expectedStackFrames[i].methodName); + } +} diff --git a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp index 78eccf35f9073f..66155b271114c6 100644 --- a/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp +++ b/packages/react-native/ReactCommon/react/runtime/ReactInstance.cpp @@ -365,6 +365,17 @@ bool isTruthy(jsi::Runtime& runtime, const jsi::Value& value) { return Boolean.call(runtime, value).getBool(); } +jsi::Value wrapInErrorIfNecessary( + jsi::Runtime& runtime, + const jsi::Value& value) { + auto Error = runtime.global().getPropertyAsFunction(runtime, "Error"); + auto isError = + value.isObject() && value.asObject(runtime).instanceOf(runtime, Error); + auto error = isError ? value.getObject(runtime) + : Error.callAsConstructor(runtime, value); + return jsi::Value(runtime, error); +} + } // namespace void ReactInstance::initializeRuntime( @@ -412,8 +423,10 @@ void ReactInstance::initializeRuntime( } if (isFatal) { - auto jsError = - jsi::JSError(runtime, jsi::Value(runtime, args[0])); + auto jsError = jsi::JSError( + runtime, + jsi::Value( + runtime, wrapInErrorIfNecessary(runtime, args[0]))); jsErrorHandler->handleFatalError(runtime, jsError); return jsi::Value(true); }