Skip to content

Commit 25b49c2

Browse files
committed
refactor: replace assert() with NS_CHECK/NS_DCHECK
assert() never runs in a build we ship. assembleRelease maps to the RelWithDebInfo CMake config, whose stock CMAKE_CXX_FLAGS_RELWITHDEBINFO carries -DNDEBUG, and CMakeLists appends -O3 to that variable rather than replacing it, so nothing removes the define. All 123 first-party asserts were therefore diagnostics that existed only in debug and test runs -- the two places the invariants were least likely to be violated. Two macros replace them, both in NativeScriptAssert.h: NS_CHECK evaluates and aborts in every configuration. NS_DCHECK evaluates and aborts in debug builds only. 61 sites become NS_CHECK: the JNIEnv/JavaVM handles and the jclass, jmethodID and jfieldID lookups resolved once during runtime initialisation from fixed class names, plus the per-isolate V8StringConstants block. Every one of them is used unconditionally a statement or two later, so a null there is undefined behaviour today and surfaces as a tombstone pointing at whatever ran next. JEnv::GetMethodID and friends already call CheckForJavaException, so these fire only when a lookup returns null with no pending Java exception; they are backstops, not the primary error path. The remaining 62 sites keep debug-only semantics as NS_DCHECK. Notably MethodCache and FieldAccessor check the result of JEnv::FindClass, which deliberately returns nullptr with a pending Java exception for a class that is genuinely missing and lets the caller raise a NativeScriptException. Aborting there would turn a handled, recoverable path into a crash. A failed NS_CHECK records the expression and source location through CrashBreadcrumbs::RecordFatal and logs it at ANDROID_LOG_FATAL, which claims the bionic abort message slot, so the check names itself in the tombstone and in the breadcrumb file the next launch reports. RecordFatal takes no lock and writes a buffer the signal handler already knows how to emit, so it is safe on a thread that is aborting from under one of the runtime's own locks. NS_DCHECK still compiles its expression when NDEBUG is defined, in a branch that is never taken, so an expression that stops making sense is a build failure instead of something only a debug build notices. It follows that the expression must stay free of side effects, exactly as with assert().
1 parent e2ede45 commit 25b49c2

34 files changed

Lines changed: 261 additions & 139 deletions

test-app/runtime/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ add_library(
195195
src/main/cpp/ModuleBinding.cpp
196196
src/main/cpp/ModuleInternal.cpp
197197
src/main/cpp/ModuleInternalCallbacks.cpp
198+
src/main/cpp/NativeScriptAssert.cpp
198199
src/main/cpp/NativeScriptException.cpp
199200
src/main/cpp/NativeScriptPlatform.cpp
200201
src/main/cpp/NsBuiltinModules.cpp

test-app/runtime/src/main/cpp/ArgConverter.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,16 +177,16 @@ Local<Value> ArgConverter::ConvertFromJavaLong(Isolate* isolate, jlong value) {
177177
}
178178

179179
int64_t ArgConverter::ConvertToJavaLong(Isolate* isolate, const Local<Value>& value) {
180-
assert(!value.IsEmpty());
180+
NS_DCHECK(!value.IsEmpty());
181181

182182
auto obj = Local<Object>::Cast(value);
183183

184-
assert(!obj.IsEmpty());
184+
NS_DCHECK(!obj.IsEmpty());
185185

186186
auto context = isolate->GetCurrentContext();
187187
Local<Value> temp;
188188
bool success = obj->Get(context, V8StringConstants::GetValue(isolate)).ToLocal(&temp);
189-
assert(success && !temp.IsEmpty());
189+
NS_DCHECK(success && !temp.IsEmpty());
190190
auto valueProp = temp.As<Object>();
191191

192192
string num = ConvertToString(valueProp->ToString(context).ToLocalChecked());

test-app/runtime/src/main/cpp/ArrayBufferHelper.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "ArrayBufferHelper.h"
2+
#include "NativeScriptAssert.h"
23
#include "ArgConverter.h"
34
#include "NativeScriptException.h"
45
#include <sstream>
@@ -65,7 +66,7 @@ void ArrayBufferHelper::CreateFromCallbackImpl(const FunctionCallbackInfo<Value>
6566

6667
if (m_ByteBufferClass == nullptr) {
6768
m_ByteBufferClass = env.FindClass("java/nio/ByteBuffer");
68-
assert(m_ByteBufferClass != nullptr);
69+
NS_CHECK(m_ByteBufferClass != nullptr);
6970
}
7071

7172
auto isByteBuffer = env.IsInstanceOf(obj, m_ByteBufferClass);
@@ -76,7 +77,7 @@ void ArrayBufferHelper::CreateFromCallbackImpl(const FunctionCallbackInfo<Value>
7677

7778
if (m_isDirectMethodID == nullptr) {
7879
m_isDirectMethodID = env.GetMethodID(m_ByteBufferClass, "isDirect", "()Z");
79-
assert(m_isDirectMethodID != nullptr);
80+
NS_CHECK(m_isDirectMethodID != nullptr);
8081
}
8182

8283
auto ret = env.CallBooleanMethod(obj, m_isDirectMethodID);
@@ -99,14 +100,14 @@ void ArrayBufferHelper::CreateFromCallbackImpl(const FunctionCallbackInfo<Value>
99100
} else {
100101
if (m_remainingMethodID == nullptr) {
101102
m_remainingMethodID = env.GetMethodID(m_ByteBufferClass, "remaining", "()I");
102-
assert(m_remainingMethodID != nullptr);
103+
NS_CHECK(m_remainingMethodID != nullptr);
103104
}
104105

105106
int bufferRemainingSize = env.CallIntMethod(obj, m_remainingMethodID);
106107

107108
if (m_getMethodID == nullptr) {
108109
m_getMethodID = env.GetMethodID(m_ByteBufferClass, "get", "([BII)Ljava/nio/ByteBuffer;");
109-
assert(m_getMethodID != nullptr);
110+
NS_CHECK(m_getMethodID != nullptr);
110111
}
111112

112113
jbyteArray byteArray = env.NewByteArray(bufferRemainingSize);

test-app/runtime/src/main/cpp/ArrayHelper.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "ArrayHelper.h"
2+
#include "NativeScriptAssert.h"
23
#include "ArgConverter.h"
34
#include "NativeScriptException.h"
45
#include "Runtime.h"
@@ -15,10 +16,10 @@ void ArrayHelper::Init(const Local<Context>& context) {
1516
JEnv env;
1617

1718
RUNTIME_CLASS = env.FindClass("com/tns/Runtime");
18-
assert(RUNTIME_CLASS != nullptr);
19+
NS_CHECK(RUNTIME_CLASS != nullptr);
1920

2021
CREATE_ARRAY_HELPER = env.GetStaticMethodID(RUNTIME_CLASS, "createArrayHelper", "(Ljava/lang/String;I)Ljava/lang/Object;");
21-
assert(CREATE_ARRAY_HELPER != nullptr);
22+
NS_CHECK(CREATE_ARRAY_HELPER != nullptr);
2223

2324
auto isolate = v8::Isolate::GetCurrent();
2425
auto global = context->Global();

test-app/runtime/src/main/cpp/AssetExtractor.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "jni.h"
22
#include "zip.h"
3-
#include <assert.h>
3+
#include "NativeScriptAssert.h"
44
#include <libgen.h>
55
#include <utime.h>
66
#include <sys/stat.h>
@@ -22,7 +22,7 @@ void AssetExtractor::ExtractAssets(JNIEnv* env, jobject obj, jstring apk, jstrin
2222
int err = 0;
2323
auto z = zip_open(strApk.c_str(), 0, &err);
2424

25-
assert(z != nullptr);
25+
NS_DCHECK(z != nullptr);
2626
zip_int64_t num = zip_get_num_entries(z, 0);
2727
struct zip_stat sb;
2828
struct zip_file* zf;
@@ -53,15 +53,15 @@ void AssetExtractor::ExtractAssets(JNIEnv* env, jobject obj, jstring apk, jstrin
5353
mkdir_rec(dirFullname.c_str());
5454

5555
zf = zip_fopen_index(z, i, 0);
56-
assert(zf != nullptr);
56+
NS_DCHECK(zf != nullptr);
5757

5858
auto fd = fopen(assetFullname.c_str(), "w");
5959

6060
if (fd != nullptr) {
6161
zip_int64_t sum = 0;
6262
while (sum != sb.size) {
6363
zip_int64_t len = zip_fread(zf, buf, sizeof(buf));
64-
assert(len > 0);
64+
NS_DCHECK(len > 0);
6565

6666
fwrite(buf, 1, len, fd);
6767
sum += len;

test-app/runtime/src/main/cpp/CallbackHandlers.cpp

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "CallbackHandlers.h"
2+
#include "NativeScriptAssert.h"
23
#include "MetadataNode.h"
34
#include "Util.h"
45
#include "V8GlobalHelpers.h"
@@ -30,33 +31,33 @@ void CallbackHandlers::Init(Isolate *isolate) {
3031
JEnv env;
3132

3233
JAVA_LANG_STRING = env.FindClass("java/lang/String");
33-
assert(JAVA_LANG_STRING != nullptr);
34+
NS_CHECK(JAVA_LANG_STRING != nullptr);
3435

3536
RUNTIME_CLASS = env.FindClass("com/tns/Runtime");
36-
assert(RUNTIME_CLASS != nullptr);
37+
NS_CHECK(RUNTIME_CLASS != nullptr);
3738

3839
RESOLVE_CLASS_METHOD_ID = env.GetMethodID(RUNTIME_CLASS, "resolveClass",
3940
"(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;Z)Ljava/lang/Class;");
40-
assert(RESOLVE_CLASS_METHOD_ID != nullptr);
41+
NS_CHECK(RESOLVE_CLASS_METHOD_ID != nullptr);
4142

4243
CURRENT_OBJECTID_FIELD_ID = env.GetFieldID(RUNTIME_CLASS, "currentObjectId", "I");
43-
assert(CURRENT_OBJECTID_FIELD_ID != nullptr);
44+
NS_CHECK(CURRENT_OBJECTID_FIELD_ID != nullptr);
4445

4546
MAKE_INSTANCE_STRONG_ID = env.GetMethodID(RUNTIME_CLASS, "makeInstanceStrong",
4647
"(Ljava/lang/Object;I)V");
47-
assert(MAKE_INSTANCE_STRONG_ID != nullptr);
48+
NS_CHECK(MAKE_INSTANCE_STRONG_ID != nullptr);
4849

4950
GET_TYPE_METADATA = env.GetStaticMethodID(RUNTIME_CLASS, "getTypeMetadata",
5051
"(Ljava/lang/String;I)[Ljava/lang/String;");
51-
assert(GET_TYPE_METADATA != nullptr);
52+
NS_CHECK(GET_TYPE_METADATA != nullptr);
5253

5354
ENABLE_VERBOSE_LOGGING_METHOD_ID = env.GetMethodID(RUNTIME_CLASS, "enableVerboseLogging",
5455
"()V");
55-
assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr);
56+
NS_CHECK(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr);
5657

5758
DISABLE_VERBOSE_LOGGING_METHOD_ID = env.GetMethodID(RUNTIME_CLASS, "disableVerboseLogging",
5859
"()V");
59-
assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr);
60+
NS_CHECK(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr);
6061

6162
MetadataNode::Init(isolate);
6263

@@ -559,7 +560,7 @@ void CallbackHandlers::CallJavaMethod(const Local<Object> &caller, const string
559560
break;
560561
}
561562
default: {
562-
assert(false);
563+
NS_DCHECK(false);
563564
break;
564565
}
565566
}
@@ -674,7 +675,7 @@ CallbackHandlers::GetMethodOverrides(JEnv &env, const Local<Object> &implementat
674675
}
675676

676677
void CallbackHandlers::RunOnMainThreadCallback(const FunctionCallbackInfo<v8::Value> &args) {
677-
assert(args[0]->IsFunction());
678+
NS_DCHECK(args[0]->IsFunction());
678679
Isolate *isolate = args.GetIsolate();
679680

680681
v8::Locker locker(isolate);
@@ -695,7 +696,7 @@ void CallbackHandlers::RunOnMainThreadCallback(const FunctionCallbackInfo<v8::Va
695696
std::lock_guard<std::mutex> lock(cacheMutex_);
696697
bool inserted;
697698
std::tie(std::ignore, inserted) = cache_.try_emplace(key, isolate, callback);
698-
assert(inserted && "Main thread callback ID should not be duplicated");
699+
NS_DCHECK(inserted && "Main thread callback ID should not be duplicated");
699700
}
700701

701702
// bare entry: the closure locks the CALLER's isolate (possibly a
@@ -957,7 +958,7 @@ vector<string> CallbackHandlers::GetTypeMetadata(const string &name, int index)
957958

958959
jsize length = env.GetArrayLength(pubApi);
959960

960-
assert(length > 0);
961+
NS_DCHECK(length > 0);
961962

962963
vector<string> result;
963964

test-app/runtime/src/main/cpp/CrashBreadcrumbs.cpp

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ constexpr size_t kMaxRuntimes = 16;
1818
constexpr size_t kFieldMax = 160;
1919
constexpr size_t kBufferMax = 8192;
2020
constexpr size_t kHeaderMax = 128;
21+
constexpr size_t kFatalMax = 256;
2122

2223
struct Slot {
2324
bool used;
@@ -43,6 +44,14 @@ std::atomic<int> g_storeFd{-1};
4344
std::atomic_flag g_recorded = ATOMIC_FLAG_INIT;
4445
struct sigaction g_previous[NSIG];
4546

47+
/*
48+
* Written by whichever thread is on its way to abort(), read by the signal
49+
* handler. Kept out of the rendered buffers so that recording it needs no
50+
* lock -- the thread may be aborting from under one.
51+
*/
52+
char g_fatalMessage[kFatalMax];
53+
std::atomic<size_t> g_fatalLength{0};
54+
4655
thread_local Slot* t_slot = nullptr;
4756

4857
int CurrentTid() { return static_cast<int>(syscall(__NR_gettid)); }
@@ -154,9 +163,22 @@ void Handler(int signalNumber, siginfo_t* info, void* context) {
154163
AppendRaw(header, sizeof(header), length, "\n");
155164

156165
ssize_t written = pwrite(fd, header, length, 0);
157-
int active = g_active.load(std::memory_order_acquire);
158-
if (written > 0 && active >= 0) {
159-
pwrite(fd, g_rendered[active], g_renderedLength[active], written);
166+
if (written > 0) {
167+
off_t offset = written;
168+
169+
size_t fatalLength = g_fatalLength.load(std::memory_order_acquire);
170+
if (fatalLength > 0) {
171+
ssize_t fatalWritten =
172+
pwrite(fd, g_fatalMessage, fatalLength, offset);
173+
if (fatalWritten > 0) {
174+
offset += fatalWritten;
175+
}
176+
}
177+
178+
int active = g_active.load(std::memory_order_acquire);
179+
if (active >= 0) {
180+
pwrite(fd, g_rendered[active], g_renderedLength[active], offset);
181+
}
160182
}
161183
}
162184
}
@@ -278,6 +300,18 @@ void CrashBreadcrumbs::SetWorkerScript(int runtimeId, const char* script) {
278300
RenderLocked();
279301
}
280302

303+
void CrashBreadcrumbs::RecordFatal(const char* message) {
304+
if (message == nullptr) {
305+
return;
306+
}
307+
// Room is reserved for the newline and the terminator.
308+
size_t length = strnlen(message, kFatalMax - 2);
309+
memcpy(g_fatalMessage, message, length);
310+
g_fatalMessage[length] = '\n';
311+
g_fatalMessage[length + 1] = '\0';
312+
g_fatalLength.store(length + 1, std::memory_order_release);
313+
}
314+
281315
CrashBreadcrumbs::ModuleScope::ModuleScope(const char* modulePath) {
282316
Slot* slot = t_slot;
283317
if (slot == nullptr) {

test-app/runtime/src/main/cpp/CrashBreadcrumbs.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ class CrashBreadcrumbs {
3636
/* Marks a registered runtime as a worker started from `script`. */
3737
static void SetWorkerScript(int runtimeId, const char* script);
3838

39+
/*
40+
* Records a line to be written ahead of the runtime state should the process
41+
* die. Takes no lock, so it stays usable from a thread that is about to
42+
* abort and may already hold any of the runtime's own locks.
43+
*/
44+
static void RecordFatal(const char* message);
45+
3946
/*
4047
* Records the module the calling runtime is executing for the lifetime of
4148
* the scope. Module loads nest (`require` inside a module body), so the

test-app/runtime/src/main/cpp/EventLoop.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
#include <unistd.h>
77

88
#include <algorithm>
9-
#include <cassert>
109
#include <cerrno>
1110
#include <cmath>
1211
#include <cstring>
@@ -98,7 +97,7 @@ void EventLoop::BindToCurrentThread() {
9897
if (EVENT_LOOP_HANDLER_CLASS == nullptr) {
9998
// JEnv::FindClass caches a global ref to the class
10099
EVENT_LOOP_HANDLER_CLASS = env.FindClass("com/tns/EventLoopHandler");
101-
assert(EVENT_LOOP_HANDLER_CLASS != nullptr);
100+
NS_CHECK(EVENT_LOOP_HANDLER_CLASS != nullptr);
102101
EVENT_LOOP_HANDLER_CTOR = env.GetMethodID(EVENT_LOOP_HANDLER_CLASS, "<init>", "(J)V");
103102
EVENT_LOOP_HANDLER_POST = env.GetMethodID(EVENT_LOOP_HANDLER_CLASS, "post", "(J)V");
104103
EVENT_LOOP_HANDLER_POST_TOKEN =
@@ -128,7 +127,7 @@ void EventLoop::BindToCurrentThread() {
128127
}
129128
JniLocalRef handler(env.NewObject(EVENT_LOOP_HANDLER_CLASS, EVENT_LOOP_HANDLER_CTOR,
130129
reinterpret_cast<jlong>(this)));
131-
assert(!handler.IsNull());
130+
NS_DCHECK(!handler.IsNull());
132131
handler_ = env.NewGlobalRef(handler);
133132

134133
// flush work buffered before the home thread was known

test-app/runtime/src/main/cpp/FieldAccessor.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "FieldAccessor.h"
2+
#include "NativeScriptAssert.h"
23
#include "ArgConverter.h"
34
#include "NativeScriptException.h"
45
#include "Runtime.h"
@@ -214,14 +215,14 @@ void FieldAccessor::SetJavaField(Isolate* isolate, const Local<Object>& target,
214215

215216
if (isStatic) {
216217
fieldData->clazz = env.FindClass(fieldMetadata.getDeclaringType());
217-
assert(fieldData->clazz != nullptr);
218+
NS_DCHECK(fieldData->clazz != nullptr);
218219
fieldData->fid = env.GetStaticFieldID(fieldData->clazz, fieldMetadata.name, fieldJniSig);
219-
assert(fieldData->fid != nullptr);
220+
NS_DCHECK(fieldData->fid != nullptr);
220221
} else {
221222
fieldData->clazz = env.FindClass(fieldMetadata.getDeclaringType());
222-
assert(fieldData->clazz != nullptr);
223+
NS_DCHECK(fieldData->clazz != nullptr);
223224
fieldData->fid = env.GetFieldID(fieldData->clazz, fieldMetadata.name, fieldJniSig);
224-
assert(fieldData->fid != nullptr);
225+
NS_DCHECK(fieldData->fid != nullptr);
225226
}
226227
}
227228

0 commit comments

Comments
 (0)