From c3fbe517da393f6d3bef83d77f8e402b9658a1af Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Fri, 14 Aug 2026 18:06:28 -0300 Subject: [PATCH 1/2] fix: restore native tombstones and record runtime crash breadcrumbs SIG_handler was installed for SIGABRT and SIGSEGV and threw a NativeScriptException from inside the signal handler. Throwing a C++ exception from a signal handler is undefined behaviour: the unwinder cannot cross the kernel-built signal frame, so on arm64 the throw reached std::terminate -> LogAndAbortUncaught -> _Exit(EXIT_FAILURE) and the process died anyway. Installing sa_handler for SIGSEGV also displaced debuggerd, so no tombstone was written - no backtrace, no registers, no fault address. A crash produced four lines of logcat and nothing else. Replace it with a diagnostic-only handler that never throws. It records a pre-rendered breadcrumb with a single write(2) and hands the signal back to the handler that owned it before, so debuggerd still writes the tombstone with the kernel's original siginfo. The breadcrumb names every live runtime by id, tid, main-vs-worker, worker script and the module it last entered, recovering the identity the tombstone truncates to 15 characters of thread name. It is rendered on ordinary threads whenever it changes, so the handler allocates nothing, takes no lock, and calls no JNI, V8 or logging code. android_set_abort_message is deliberately not used: bionic keeps the first message it is given, so it cannot carry state that changes, and claiming the slot would shut out the abort message libc or ART writes for the real fault. std::set_terminate(LogAndAbortUncaught) is unchanged - it is the legitimate handler for genuine uncaught C++ exceptions and already _Exit()s. Fatal signals now surface as real native crashes instead of being converted into JS exceptions. The disabled exceptionHandlingTests spec that asserted a JNI misuse yields a catchable "SIGABRT" exception is removed along with the behaviour it documented. --- .../app/tests/exceptionHandlingTests.js | 15 - test-app/runtime/CMakeLists.txt | 1 + .../runtime/src/main/cpp/CrashBreadcrumbs.cpp | 286 ++++++++++++++++++ .../runtime/src/main/cpp/CrashBreadcrumbs.h | 45 +++ .../runtime/src/main/cpp/ModuleInternal.cpp | 2 + test-app/runtime/src/main/cpp/Runtime.cpp | 39 +-- .../runtime/src/main/cpp/WorkerWrapper.cpp | 2 + 7 files changed, 345 insertions(+), 45 deletions(-) create mode 100644 test-app/runtime/src/main/cpp/CrashBreadcrumbs.cpp create mode 100644 test-app/runtime/src/main/cpp/CrashBreadcrumbs.h diff --git a/test-app/app/src/main/assets/app/tests/exceptionHandlingTests.js b/test-app/app/src/main/assets/app/tests/exceptionHandlingTests.js index bf9aed120..0c5f8cd9c 100644 --- a/test-app/app/src/main/assets/app/tests/exceptionHandlingTests.js +++ b/test-app/app/src/main/assets/app/tests/exceptionHandlingTests.js @@ -319,19 +319,4 @@ describe("Tests exception handling ", function () { expect(errMsg).toContain("SyntaxError: Unexpected token 'class'"); expect(errMsg).toContain("File: (file:///data/data/com.tns.testapplication/files/app/tests/syntaxErrors.js:3:4)"); }); - - // run this test only for API level bigger than 25 as we have handling there - if(android.os.Build.VERSION.SDK_INT > 25 && android.os.Build.CPU_ABI != "x86" && android.os.Build.CPU_ABI != "x86_64") { - xit("Should handle SIGABRT and throw a NativeScript exception when incorrectly calling JNI methods", function () { - let myClassInstance = new com.tns.tests.MyTestBaseClass3(); - // public void callMeWithAString(java.lang.String[] stringArr, Runnable arbitraryInterface) - try { - myClassInstance.callMeWithAString("stringVal", new java.lang.Runnable({ run: () => {} })) - } catch (e) { - android.util.Log.d("~~~~~", "~~~~~~~~ " + e.toString()); - - expect(e.toString()).toContain("SIGABRT"); - } - }); - } }); \ No newline at end of file diff --git a/test-app/runtime/CMakeLists.txt b/test-app/runtime/CMakeLists.txt index a092d3058..0c90d43b8 100644 --- a/test-app/runtime/CMakeLists.txt +++ b/test-app/runtime/CMakeLists.txt @@ -167,6 +167,7 @@ add_library( src/main/cpp/CallbackHandlers.cpp src/main/cpp/ConcurrentQueue.cpp src/main/cpp/Constants.cpp + src/main/cpp/CrashBreadcrumbs.cpp src/main/cpp/DirectBuffer.cpp src/main/cpp/ErrorEvents.cpp src/main/cpp/EventLoop.cpp diff --git a/test-app/runtime/src/main/cpp/CrashBreadcrumbs.cpp b/test-app/runtime/src/main/cpp/CrashBreadcrumbs.cpp new file mode 100644 index 000000000..08b13723a --- /dev/null +++ b/test-app/runtime/src/main/cpp/CrashBreadcrumbs.cpp @@ -0,0 +1,286 @@ +#include "CrashBreadcrumbs.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +constexpr size_t kMaxRuntimes = 16; +constexpr size_t kFieldMax = 160; +constexpr size_t kBufferMax = 8192; +constexpr size_t kHeaderMax = 128; + +struct Slot { + bool used; + bool isWorker; + int runtimeId; + int tid; + char script[kFieldMax]; + char module[kFieldMax]; +}; + +Slot g_slots[kMaxRuntimes]; +std::mutex g_mutex; + +/* + * Rendered in two buffers alternately, so the signal handler never reads the + * one a running thread is part way through writing. + */ +char g_rendered[2][kBufferMax]; +size_t g_renderedLength[2]; +std::atomic g_active{-1}; + +int g_storeFd = -1; +std::atomic_flag g_recorded = ATOMIC_FLAG_INIT; +struct sigaction g_previous[NSIG]; + +thread_local Slot* t_slot = nullptr; + +int CurrentTid() { return static_cast(syscall(__NR_gettid)); } + +void CopyField(char* dst, const char* src) { + if (src == nullptr) { + dst[0] = '\0'; + return; + } + size_t length = strlen(src); + if (length < kFieldMax) { + memcpy(dst, src, length + 1); + return; + } + // Keep the tail: the file name identifies a module, the leading directories + // are shared by every module in the app. + memcpy(dst, "...", 3); + memcpy(dst + 3, src + length - (kFieldMax - 4), kFieldMax - 4); + dst[kFieldMax - 1] = '\0'; +} + +void Append(char* out, size_t& length, const char* format, ...) + __attribute__((format(printf, 3, 4))); + +void Append(char* out, size_t& length, const char* format, ...) { + if (length >= kBufferMax) { + return; + } + va_list args; + va_start(args, format); + int written = vsnprintf(out + length, kBufferMax - length, format, args); + va_end(args); + if (written > 0) { + length += static_cast(written); + if (length > kBufferMax - 1) { + length = kBufferMax - 1; + } + } +} + +void RenderLocked() { + int next = g_active.load(std::memory_order_relaxed) == 0 ? 1 : 0; + char* out = g_rendered[next]; + size_t length = 0; + + Append(out, length, "NativeScript runtime state (pid %d):\n", getpid()); + for (const Slot& slot : g_slots) { + if (!slot.used) { + continue; + } + Append(out, length, " runtime=%d tid=%d %s", slot.runtimeId, slot.tid, + slot.isWorker ? "worker" : "main"); + if (slot.script[0] != '\0') { + Append(out, length, " script=%s", slot.script); + } + Append(out, length, " module=%s\n", + slot.module[0] != '\0' ? slot.module : ""); + } + + g_renderedLength[next] = length; + g_active.store(next, std::memory_order_release); +} + +Slot* FindLocked(int runtimeId) { + for (Slot& slot : g_slots) { + if (slot.used && slot.runtimeId == runtimeId) { + return &slot; + } + } + return nullptr; +} + +/* Async-signal-safe integer formatting; snprintf is not usable here. */ +void AppendRaw(char* out, size_t capacity, size_t& length, const char* text) { + while (*text != '\0' && length < capacity) { + out[length++] = *text++; + } +} + +void AppendRawInt(char* out, size_t capacity, size_t& length, int value) { + char digits[16]; + size_t count = 0; + unsigned int magnitude = static_cast(value); + do { + digits[count++] = static_cast('0' + magnitude % 10); + magnitude /= 10; + } while (magnitude != 0 && count < sizeof(digits)); + while (count > 0 && length < capacity) { + out[length++] = digits[--count]; + } +} + +void Handler(int signalNumber, siginfo_t* info, void* context) { + // Only the first thread to fault records; the rest are already doomed. + if (!g_recorded.test_and_set()) { + int fd = g_storeFd; + if (fd >= 0) { + char header[kHeaderMax]; + size_t length = 0; + AppendRaw(header, sizeof(header), length, "fatal signal "); + AppendRawInt(header, sizeof(header), length, signalNumber); + AppendRaw(header, sizeof(header), length, " on tid "); + AppendRawInt(header, sizeof(header), length, CurrentTid()); + AppendRaw(header, sizeof(header), length, "\n"); + + ssize_t written = pwrite(fd, header, length, 0); + int active = g_active.load(std::memory_order_acquire); + if (written > 0 && active >= 0) { + pwrite(fd, g_rendered[active], g_renderedLength[active], written); + } + } + } + + /* + * Hand the signal to whoever owned it before us -- on Android that is + * debuggerd, which writes the tombstone. + * + * A signal the kernel raised for a real fault arrives again on its own once + * this returns and the faulting instruction re-executes, so debuggerd is + * entered with the kernel's original siginfo instead of anything + * synthesised here. One that was delivered by abort() or kill() (si_code + * <= 0) will not come back, so it has to be re-raised explicitly. + */ + sigaction(signalNumber, &g_previous[signalNumber], nullptr); + if (info == nullptr || info->si_code <= 0) { + raise(signalNumber); + } +} + +} // namespace + +namespace tns { + +void CrashBreadcrumbs::Install() { + static std::once_flag once; + std::call_once(once, [] { + struct sigaction action = {}; + action.sa_sigaction = Handler; + // SA_ONSTACK matters for a stack-overflow SIGSEGV, which has no room left + // on the faulting stack to run a handler. bionic already gives every + // thread an alternate signal stack, so the flag is all that is needed. + action.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigemptyset(&action.sa_mask); + for (int signalNumber : {SIGSEGV, SIGABRT, SIGBUS, SIGILL, SIGFPE}) { + sigaction(signalNumber, &action, &g_previous[signalNumber]); + } + }); +} + +void CrashBreadcrumbs::OpenStore(const std::string& filesRoot) { + static std::once_flag once; + std::call_once(once, [&filesRoot] { + std::string path = filesRoot + "/.ns-crash-breadcrumb"; + int fd = open(path.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600); + if (fd < 0) { + return; + } + + char previous[kBufferMax + kHeaderMax]; + ssize_t length = read(fd, previous, sizeof(previous) - 1); + if (length > 0) { + previous[length] = '\0'; + // Deliberately not ANDROID_LOG_FATAL: liblog feeds a fatal record to + // android_set_abort_message, and bionic keeps the first message it is + // given for the life of the process. Claiming that slot here would + // describe the *previous* process in this one's tombstone, and would + // shut out the abort message libc or ART writes for the real fault. + __android_log_print( + ANDROID_LOG_ERROR, "TNS.Native", + "The previous process was killed by a fatal signal. Runtime state " + "recorded at that moment (match tid against the tombstone in " + "/data/tombstones):\n%s", + previous); + ftruncate(fd, 0); + } + + g_storeFd = fd; + }); +} + +void CrashBreadcrumbs::RegisterRuntime(int runtimeId) { + std::lock_guard lock(g_mutex); + Slot* slot = FindLocked(runtimeId); + if (slot == nullptr) { + for (Slot& candidate : g_slots) { + if (!candidate.used) { + slot = &candidate; + break; + } + } + } + if (slot == nullptr) { + // Table full. Keep the runtimes already tracked rather than evicting one. + return; + } + + slot->used = true; + slot->isWorker = false; + slot->runtimeId = runtimeId; + slot->tid = CurrentTid(); + slot->script[0] = '\0'; + slot->module[0] = '\0'; + t_slot = slot; + RenderLocked(); +} + +void CrashBreadcrumbs::UnregisterRuntime(int runtimeId) { + std::lock_guard lock(g_mutex); + Slot* slot = FindLocked(runtimeId); + if (slot == nullptr) { + return; + } + if (t_slot == slot) { + t_slot = nullptr; + } + slot->used = false; + RenderLocked(); +} + +void CrashBreadcrumbs::SetWorkerScript(int runtimeId, const char* script) { + std::lock_guard lock(g_mutex); + Slot* slot = FindLocked(runtimeId); + if (slot == nullptr) { + return; + } + slot->isWorker = true; + CopyField(slot->script, script); + RenderLocked(); +} + +void CrashBreadcrumbs::SetCurrentModule(const char* modulePath) { + Slot* slot = t_slot; + if (slot == nullptr) { + return; + } + std::lock_guard lock(g_mutex); + CopyField(slot->module, modulePath); + RenderLocked(); +} + +} // namespace tns diff --git a/test-app/runtime/src/main/cpp/CrashBreadcrumbs.h b/test-app/runtime/src/main/cpp/CrashBreadcrumbs.h new file mode 100644 index 000000000..9ac6e62e5 --- /dev/null +++ b/test-app/runtime/src/main/cpp/CrashBreadcrumbs.h @@ -0,0 +1,45 @@ +#ifndef CRASHBREADCRUMBS_H_ +#define CRASHBREADCRUMBS_H_ + +#include + +namespace tns { + +/* + * Records what each runtime thread was doing, so a process killed by a fatal + * signal leaves behind more than a native backtrace. + * + * The state is rendered into a plain byte buffer as it changes, on ordinary + * threads. At crash time the only work left is a write(2) of that buffer, + * which is one of the few calls POSIX permits from a signal handler -- + * anything that allocates, takes a lock or formats has already happened. + */ +class CrashBreadcrumbs { + public: + /* + * Installs SIGSEGV/SIGABRT/SIGBUS/SIGILL/SIGFPE handlers that record the + * breadcrumb and then hand the signal back to the handler installed before + * them, so debuggerd still writes the tombstone. Idempotent. + */ + static void Install(); + + /* + * Points the store at the app's files directory and reports whatever a + * previous process left behind. Idempotent, so every runtime may call it. + */ + static void OpenStore(const std::string& filesRoot); + + /* Binds the calling thread to a runtime for that runtime's lifetime. */ + static void RegisterRuntime(int runtimeId); + static void UnregisterRuntime(int runtimeId); + + /* Marks a registered runtime as a worker started from `script`. */ + static void SetWorkerScript(int runtimeId, const char* script); + + /* Records the module the calling runtime is about to execute. */ + static void SetCurrentModule(const char* modulePath); +}; + +} // namespace tns + +#endif /* CRASHBREADCRUMBS_H_ */ diff --git a/test-app/runtime/src/main/cpp/ModuleInternal.cpp b/test-app/runtime/src/main/cpp/ModuleInternal.cpp index be8ad4024..9b5be4f3a 100644 --- a/test-app/runtime/src/main/cpp/ModuleInternal.cpp +++ b/test-app/runtime/src/main/cpp/ModuleInternal.cpp @@ -13,6 +13,7 @@ #include "V8GlobalHelpers.h" #include "NativeScriptAssert.h" #include "Constants.h" +#include "CrashBreadcrumbs.h" #include "NativeScriptException.h" #include "NsBuiltinModules.h" #include "napi/NapiModules.h" @@ -363,6 +364,7 @@ Local ModuleInternal::LoadImpl(Isolate* isolate, const string& moduleNam Local ModuleInternal::LoadModule(Isolate* isolate, const string& modulePath, const string& moduleCacheKey) { string frameName("LoadModule " + modulePath); tns::instrumentation::Frame frame(frameName); + CrashBreadcrumbs::SetCurrentModule(modulePath.c_str()); Local result; auto context = isolate->GetCurrentContext(); diff --git a/test-app/runtime/src/main/cpp/Runtime.cpp b/test-app/runtime/src/main/cpp/Runtime.cpp index c218dcdf0..f82890693 100644 --- a/test-app/runtime/src/main/cpp/Runtime.cpp +++ b/test-app/runtime/src/main/cpp/Runtime.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -15,6 +14,7 @@ #include "BuiltinLoader.h" #include "CallbackHandlers.h" #include "Constants.h" +#include "CrashBreadcrumbs.h" #include "ErrorEvents.h" #include "Events.h" #include "File.h" @@ -62,26 +62,6 @@ using namespace tns; bool tns::LogEnabled = true; SimpleAllocator g_allocator; -void SIG_handler(int sigNumber) { - stringstream msg; - msg << "JNI Exception occurred ("; - switch (sigNumber) { - case SIGABRT: - msg << "SIGABRT"; - break; - case SIGSEGV: - msg << "SIGSEGV"; - break; - default: - // Shouldn't happen, but for completeness - msg << "Signal #" << sigNumber; - break; - } - msg << ").\n=======\nCheck the 'adb logcat' for additional information about " - "the error.\n=======\n"; - throw NativeScriptException(msg.str()); -} - void LogAndAbortUncaught() { try { throw; // rethrow the current unknown @@ -113,15 +93,8 @@ void Runtime::Init(JavaVM* vm, void* reserved) { JEnv::Init(s_jvm); } - // handle SIGABRT/SIGSEGV only on API level > 20 as the handling is not so - // efficient in older versions - if (m_androidVersion > 20) { - struct sigaction action = {}; - action.sa_handler = SIG_handler; - sigemptyset(&action.sa_mask); - sigaction(SIGABRT, &action, NULL); - sigaction(SIGSEGV, &action, NULL); - } + CrashBreadcrumbs::Install(); + // Set terminate handler for uncaught exceptions std::set_terminate(LogAndAbortUncaught); } @@ -257,6 +230,11 @@ void Runtime::Init(JNIEnv* env, jstring filesPath, jstring nativeLibDir, auto packageNameStr = ArgConverter::jstringToString(packageName); auto callingDirStr = ArgConverter::jstringToString(callingDir); + // Runs on this runtime's own thread, for the main runtime and for workers + // alike, so the calling thread is the one the breadcrumb should name. + CrashBreadcrumbs::OpenStore(filesRoot); + CrashBreadcrumbs::RegisterRuntime(m_id); + Constants::APP_ROOT_FOLDER_PATH = filesRoot + "/app/"; // read config options passed from Java // Indices correspond to positions in the com.tns.AppConfig.KnownKeys enum @@ -928,6 +906,7 @@ double Runtime::PerformanceNowMillis() { } void Runtime::DestroyRuntime() { + CrashBreadcrumbs::UnregisterRuntime(m_id); { std::lock_guard lock(s_runtimeCacheMutex); s_id2RuntimeCache.erase(m_id); diff --git a/test-app/runtime/src/main/cpp/WorkerWrapper.cpp b/test-app/runtime/src/main/cpp/WorkerWrapper.cpp index f576b8484..a8ac7bb6f 100644 --- a/test-app/runtime/src/main/cpp/WorkerWrapper.cpp +++ b/test-app/runtime/src/main/cpp/WorkerWrapper.cpp @@ -7,6 +7,7 @@ #include "ArgConverter.h" #include "CallbackHandlers.h" +#include "CrashBreadcrumbs.h" #include "JEnv.h" #include "JniLocalRef.h" #include "NativeScriptAssert.h" @@ -353,6 +354,7 @@ void WorkerWrapper::BackgroundLooper(std::shared_ptr self) { runtimeId = env.CallStaticIntMethod(RUNTIME_CLASS, INIT_WORKER_RUNTIME_METHOD_ID, workerId_, (jstring) callingDir); runtime_ = Runtime::GetRuntime(runtimeId); + CrashBreadcrumbs::SetWorkerScript(runtimeId, workerPath_.c_str()); { std::lock_guard lock(looperMutex_); From b04666d48ab9942d4ee2cc0e9df9dcaef49e713e Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Fri, 14 Aug 2026 20:15:08 -0300 Subject: [PATCH 2/2] fix: close breadcrumb races and restore enclosing module after nested loads Address review findings: - Stop rendering once a crash is recorded: a thread that kept running could otherwise flip the double buffer twice and rewrite the buffer the handler is copying out. - Make g_storeFd atomic with release/acquire ordering; handlers are installed at JNI_OnLoad, before OpenStore publishes the fd. - Replace SetCurrentModule with a scoped ModuleScope that restores the enclosing module on every return and throw path, so a crash after a nested require no longer blames the inner module. --- .../runtime/src/main/cpp/CrashBreadcrumbs.cpp | 25 ++++++++++++++++--- .../runtime/src/main/cpp/CrashBreadcrumbs.h | 18 +++++++++++-- .../runtime/src/main/cpp/ModuleInternal.cpp | 2 +- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/test-app/runtime/src/main/cpp/CrashBreadcrumbs.cpp b/test-app/runtime/src/main/cpp/CrashBreadcrumbs.cpp index 08b13723a..1e0c4a3b5 100644 --- a/test-app/runtime/src/main/cpp/CrashBreadcrumbs.cpp +++ b/test-app/runtime/src/main/cpp/CrashBreadcrumbs.cpp @@ -39,7 +39,7 @@ char g_rendered[2][kBufferMax]; size_t g_renderedLength[2]; std::atomic g_active{-1}; -int g_storeFd = -1; +std::atomic g_storeFd{-1}; std::atomic_flag g_recorded = ATOMIC_FLAG_INIT; struct sigaction g_previous[NSIG]; @@ -84,6 +84,11 @@ void Append(char* out, size_t& length, const char* format, ...) { } void RenderLocked() { + // Once a crash is recorded the handler may be reading either buffer; a + // second flip after that point would rewrite the one it is copying out. + if (g_recorded.test(std::memory_order_acquire)) { + return; + } int next = g_active.load(std::memory_order_relaxed) == 0 ? 1 : 0; char* out = g_rendered[next]; size_t length = 0; @@ -138,7 +143,7 @@ void AppendRawInt(char* out, size_t capacity, size_t& length, int value) { void Handler(int signalNumber, siginfo_t* info, void* context) { // Only the first thread to fault records; the rest are already doomed. if (!g_recorded.test_and_set()) { - int fd = g_storeFd; + int fd = g_storeFd.load(std::memory_order_acquire); if (fd >= 0) { char header[kHeaderMax]; size_t length = 0; @@ -219,7 +224,7 @@ void CrashBreadcrumbs::OpenStore(const std::string& filesRoot) { ftruncate(fd, 0); } - g_storeFd = fd; + g_storeFd.store(fd, std::memory_order_release); }); } @@ -273,14 +278,26 @@ void CrashBreadcrumbs::SetWorkerScript(int runtimeId, const char* script) { RenderLocked(); } -void CrashBreadcrumbs::SetCurrentModule(const char* modulePath) { +CrashBreadcrumbs::ModuleScope::ModuleScope(const char* modulePath) { Slot* slot = t_slot; if (slot == nullptr) { return; } std::lock_guard lock(g_mutex); + previous_ = slot->module; + restore_ = true; CopyField(slot->module, modulePath); RenderLocked(); } +CrashBreadcrumbs::ModuleScope::~ModuleScope() { + Slot* slot = t_slot; + if (!restore_ || slot == nullptr) { + return; + } + std::lock_guard lock(g_mutex); + CopyField(slot->module, previous_.c_str()); + RenderLocked(); +} + } // namespace tns diff --git a/test-app/runtime/src/main/cpp/CrashBreadcrumbs.h b/test-app/runtime/src/main/cpp/CrashBreadcrumbs.h index 9ac6e62e5..deacfa95a 100644 --- a/test-app/runtime/src/main/cpp/CrashBreadcrumbs.h +++ b/test-app/runtime/src/main/cpp/CrashBreadcrumbs.h @@ -36,8 +36,22 @@ class CrashBreadcrumbs { /* Marks a registered runtime as a worker started from `script`. */ static void SetWorkerScript(int runtimeId, const char* script); - /* Records the module the calling runtime is about to execute. */ - static void SetCurrentModule(const char* modulePath); + /* + * Records the module the calling runtime is executing for the lifetime of + * the scope. Module loads nest (`require` inside a module body), so the + * enclosing module is restored on destruction, on throw paths included. + */ + class ModuleScope { + public: + explicit ModuleScope(const char* modulePath); + ~ModuleScope(); + ModuleScope(const ModuleScope&) = delete; + ModuleScope& operator=(const ModuleScope&) = delete; + + private: + std::string previous_; + bool restore_ = false; + }; }; } // namespace tns diff --git a/test-app/runtime/src/main/cpp/ModuleInternal.cpp b/test-app/runtime/src/main/cpp/ModuleInternal.cpp index 9b5be4f3a..16823c48e 100644 --- a/test-app/runtime/src/main/cpp/ModuleInternal.cpp +++ b/test-app/runtime/src/main/cpp/ModuleInternal.cpp @@ -364,7 +364,7 @@ Local ModuleInternal::LoadImpl(Isolate* isolate, const string& moduleNam Local ModuleInternal::LoadModule(Isolate* isolate, const string& modulePath, const string& moduleCacheKey) { string frameName("LoadModule " + modulePath); tns::instrumentation::Frame frame(frameName); - CrashBreadcrumbs::SetCurrentModule(modulePath.c_str()); + CrashBreadcrumbs::ModuleScope moduleBreadcrumb(modulePath.c_str()); Local result; auto context = isolate->GetCurrentContext();