1# Analyzing Error Logs and Crashes Triggered by Using Node-API 2 3The maintenance and debugging measures mentioned in this topic rely on the Ark runtime multi-thread check. Therefore, you are advised to enable this feature before debugging. For details about how to enable Ark runtime multi-thread check, see [Analyzing CPP Crash](https://developer.huawei.com/consumer/en/doc/harmonyos-guides/ide-multi-thread-check). 4 5Unless otherwise specified, the maintenance and debugging measures used in this topic will interrupt the process once the Ark runtime multi-thread check is enabled. 6 7## Inconsistent napi_env Between the Data Being Used and the Data Being Created 8 9### Logs 10 11The inconsistency in **napi_env** occurs in the following scenarios: 12 131. When Node-API is called to use a created struct, the **napi_env** of the struct is inconsistent with that used to create the struct. 14 15 > **Log**<br> 16 > param env not equal to its owner. 17 > 18 192. When Node-API is called to use a created struct, the **napi_env** of the struct has the same address as the **napi_env** used to create the struct but the original **napi_env** has been destroyed. 20 21 > **Log**<br> 22 > 23 > - For the methods other than thread-safe functions, the log information is as follows: 24 > 25 > owner env has been destroyed, owner id: <owner id> , current env id: <current id>. 26 > 27 > - For thread-safe functions, the log information is as follows: 28 > 29 > current tsfn was created by dead env, owner id: <owner id>, current env id: <current id> 30 31The following APIs may trigger this error: 32 331. napi_get_reference_value 342. napi_delete_reference* 353. napi_queue_async_work 364. napi_queue_async_work_with_qos 375. napi_cancel_async_work 386. napi_call_threadsafe_function* 397. napi_release_threadsafe_function* 40 41> **NOTE**<br>The APIs with an asterisk (*) can only trigger the log information in the second scenario. 42 43### Example 44 45> **NOTE**<br>The following code is intended only to create exception scenarios and trigger DFX error logs. Do not apply it to your code before you fully understand its purpose. 46 47#### Utility Class 48 49Define a utility class to construct the two exception scenarios. 50 51```cpp 52#define CHECK(cond) \ 53 do { \ 54 if (cond) { \ 55 OH_LOG_FATAL(LOG_APP, "Failed to check `" #cond "`"); \ 56 std::abort(); \ 57 } \ 58 } while(0) 59#define CHECK_EQ(lhs, rhs) CHECK(lhs == rhs) 60#define CHECK_NE(lhs, rhs) CHECK(lhs != rhs) 61#define CHECK_NOT_NULL(val) CHECK(val != nullptr) 62 63#define STRICT_NAPI_CALL(call) \ 64 do { \ 65 napi_status ret = (call); \ 66 if (ret != napi_ok) { \ 67 OH_LOG_FATAL(LOG_APP, "Failed to execute `" #call "`, " \ 68 "return code is: %{public}d", ret); \ 69 std::abort(); \ 70 } \ 71 } while(0) 72 73 74class CallbackInfo { 75public: 76 CallbackInfo(napi_env env, napi_callback_info info) 77 : env_(env) 78 { 79 napi_get_cb_info(env, info, &argc_, nullptr, &thisVar_, &data_); 80 if (argc_ > 0) { 81 argv_ = new napi_value[argc_]; 82 CHECK_NOT_NULL(argv_); 83 memset(argv_, nullptr, sizeof(argv_)); 84 napi_get_cb_info(env, info, &argc_, argv_, nullptr, nullptr); 85 } 86 } 87 ~CallbackInfo() 88 { 89 if (argc_ > 0) { 90 delete[] argv_; 91 argv_ = nullptr; 92 } 93 } 94 95 inline size_t GetArgc() const { return argc_; } 96 inline napi_value* GetArgs() const { return argv_; } 97 98 inline napi_value GetArg(size_t index) const 99 { 100 if (index >= argc_) { 101 napi_value undefined = nullptr; 102 napi_get_undefined(env_, &undefined); 103 return undefined; 104 } 105 return argv_[index]; 106 } 107 inline napi_value operator[](size_t index) const 108 { 109 return GetArg(index); 110 } 111 112private: 113 napi_env env_ { nullptr }; 114 size_t argc_ { 0 }; 115 napi_value* argv_ { nullptr }; 116 napi_value thisVar_ { nullptr }; 117 void* data_ { nullptr }; 118}; 119 120// Construct napi_env with the same address (or different addresses) to trigger different DFX information. 121class EngineProxy { 122public: 123 EngineProxy() 124 { 125 STRICT_NAPI_CALL(napi_create_ark_runtime(&env_)); 126 // 5: makes easier reuse of the napi_env address. 127 for (int i = 0; i < 5; i++) { 128 RecreateOnce(); 129 } 130 } 131 132 ~EngineProxy() 133 { 134 STRICT_NAPI_CALL(napi_destroy_ark_runtime(&env_)); 135 } 136 137 inline bool RecreateSame() 138 { 139 return Recreate(true); 140 } 141 142 inline bool RecreateDiff() 143 { 144 return Recreate(false); 145 } 146 147 inline operator napi_env() const 148 { 149 return env_; 150 } 151 152 // Re-create napi_env until the address is the same as (or different from) the original napi_env. 153 bool Recreate(bool requireSame) 154 { 155 const char* recreateTypeTag = requireSame ? "same" : "different"; 156 napi_env old = env_; 157 for (int i = 0; i < MAX_RETRY_TIMES; i++) { 158 if (RecreateOnce(old) == requireSame) { 159 OH_LOG_INFO(LOG_APP, "Succeed to recreate env with %{public}s pointer " 160 "address after retried %{public}d times.", recreateTypeTag, i); 161 return true; 162 } 163 } 164 OH_LOG_ERROR(LOG_APP, "Failed to recreate env with %{public}s pointer " 165 "address after retried %{public}d times.", recreateTypeTag, MAX_RETRY_TIMES); 166 return false; 167 } 168 169private: 170 // Re-create napi_env and return whether the new address is the same as the original address. 171 bool RecreateOnce(napi_env old = nullptr) 172 { 173 STRICT_NAPI_CALL(napi_destroy_ark_runtime(&env_)); 174 STRICT_NAPI_CALL(napi_create_ark_runtime(&env_)); 175 return env_ == old; 176 } 177 178 napi_env env_ {nullptr}; 179 180 constexpr static int MAX_RETRY_TIMES = 1 << 8; 181}; 182``` 183 184#### napi_ref APIs 185 186Sample code of **napi_get_reference_value** and **napi_delete_reference**: 187 188```cpp 189/* 190 * API declaration index.d.ts 191 * const triggerDFXGetRef: (samePtr: boolean) => void; 192 */ 193napi_value TriggerDFXGetRef(napi_env env, napi_callback_info cbinfo) 194{ 195 CallbackInfo info(env, cbinfo); 196 bool same = true; 197 STRICT_NAPI_CALL(napi_get_value_bool(env, info[0], &same)); 198 std::thread([](bool same) { 199 EngineProxy localEnv; 200 napi_value obj = nullptr; 201 STRICT_NAPI_CALL(napi_create_object(localEnv, &obj)); 202 napi_ref ref = nullptr; 203 napi_create_reference(localEnv, obj, 1, &ref); 204 if (!localEnv.Recreate(same)) { 205 return; 206 }; 207 napi_value result = nullptr; 208 napi_get_reference_value(localEnv, ref, &result); 209 }, same).detach(); 210 return nullptr; 211} 212 213/* 214 * API declaration index.d.ts 215 * const triggerDFXDelRef: () => void; 216 */ 217napi_value TriggerDFXDelRef(napi_env, napi_callback_info) 218{ 219 std::thread([]() { 220 EngineProxy localEnv; 221 napi_value obj = nullptr; 222 STRICT_NAPI_CALL(napi_create_object(localEnv, &obj)); 223 napi_ref ref = nullptr; 224 napi_create_reference(localEnv, obj, 1, &ref); 225 if (!localEnv.RecreateSame()) { 226 return; 227 }; 228 napi_delete_reference(localEnv, ref); 229 }).detach(); 230 return nullptr; 231} 232``` 233 234#### napi_async_work APIs 235 236Sample code of **napi_queue_async_work**, **napi_queue_async_work_with_qos**, and **napi_cancel_async_work**: 237 238```cpp 239/* 240 * The macro EXPAND_ASYNC_WORK_CASE provides the following variables for op: 241 * @variable napi_env localEnv 242 * @variable napi_async_work work 243 */ 244#define EXPAND_ASYNC_WORK_CASE(name, op) \ 245napi_value name(napi_env env, napi_callback_info cbinfo) \ 246{ \ 247 CallbackInfo info(env, cbinfo); \ 248 bool same = true; \ 249 STRICT_NAPI_CALL(napi_get_value_bool(env, info[0], &same)); \ 250 std::thread([](bool same) { \ 251 EngineProxy localEnv; \ 252 napi_async_work work = nullptr; \ 253 { \ 254 napi_value taskName = nullptr; \ 255 napi_create_string_utf8(localEnv, #name, NAPI_AUTO_LENGTH, &taskName); \ 256 /* Do not use an empty execute callback to create napi_async_work. */ \ 257 napi_create_async_work(localEnv, nullptr, taskName, \ 258 [](napi_env, void*) {}, [](napi_env, napi_status, void* ) {}, \ 259 nullptr, &work); \ 260 if (!localEnv.Recreate(same)) { \ 261 return; \ 262 } \ 263 } \ 264 (op); \ 265 }, same).detach(); \ 266 return nullptr; \ 267} 268 269/* 270 * API declaration index.d.ts 271 * const triggerDFXQueueWork: (samePtr: boolean) => void; 272 * const triggerDFXQueueWorkWithQos: (samePtr: boolean) => void; 273 * const triggerDFXCancelWork: (samePtr: boolean) => void; 274 */ 275EXPAND_ASYNC_WORK_CASE(TriggerDFXQueueWork, 276 napi_queue_async_work(localEnv, work)) 277EXPAND_ASYNC_WORK_CASE(TriggerDFXQueueWorkWithQos, 278 napi_queue_async_work_with_qos(localEnv, work, napi_qos_default)) 279EXPAND_ASYNC_WORK_CASE(TriggerDFXCancelWork, 280 napi_cancel_async_work(localEnv, work)) 281 282#undef EXPAND_ASYNC_WORK_CASE 283``` 284 285#### napi_threadsafe_function APIs 286 287Sample code of **napi_call_threadsafe_function** and **napi_release_threadsafe_function**: 288 289```cpp 290/* 291 * The macro EXPAND_THREADSAFE_FUNCTION_CASE provides the following variables for op: 292 * @variable napi_env localEnv 293 * @variable napi_threadsafe_function tsfn 294 */ 295#define EXPAND_THREADSAFE_FUNCTION_CASE(name, op) \ 296 napi_value name(napi_env, napi_callback_info) { \ 297 std::thread([]() { \ 298 EngineProxy localEnv; \ 299 napi_threadsafe_function tsfn = nullptr; \ 300 { \ 301 napi_value taskName = nullptr; \ 302 napi_create_string_utf8(localEnv, "Test", NAPI_AUTO_LENGTH, &taskName); \ 303 napi_create_threadsafe_function( \ 304 localEnv, nullptr, nullptr, taskName, 0, 1, nullptr, \ 305 [](napi_env, void *, void *) {}, nullptr, \ 306 [](napi_env, napi_value, void *, void *) {}, &tsfn); \ 307 if (!localEnv.RecreateSame()) { \ 308 return; \ 309 }; \ 310 } \ 311 (op); \ 312 }).detach(); \ 313 return nullptr; \ 314 } 315 316/* 317 * API declaration index.d.ts 318 * const triggerDFXTsfnCall: () => void; 319 * const triggerDFXTsfnRelease: () => void; 320 */ 321EXPAND_THREADSAFE_FUNCTION_CASE(TriggerDFXTsfnCall, 322 napi_call_threadsafe_function(tsfn, nullptr, napi_tsfn_nonblocking)) 323EXPAND_THREADSAFE_FUNCTION_CASE(TriggerDFXTsfnRelease, 324 napi_release_threadsafe_function(tsfn, napi_tsfn_release)) 325 326#undef EXPAND_THREADSAFE_FUNCTION_CASE 327``` 328 329## Cross-Thread Calls 330 331### Logs 332 333Most Node-API interfaces are not thread-safe. Additional measures are used to locate the errors caused by improper use of Node-API interfaces. 334 335Unless otherwise specified, the maintenance and debugging measures used in this topic will interrupt the process once the Ark runtime multi-thread check is enabled. 336 337> **Log**<br> 338> 339> current napi interface cannot run in multi-thread, thread id: <env tid>, current thread id: <current tid> 340 341The following APIs may trigger this type of error: 342 3431. napi_add_env_cleanup_hook* 3442. napi_remove_env_cleanup_hook* 3453. napi_add_async_cleanup_hook 3464. napi_set_instance_data 3475. napi_get_instance_data 348 349> **NOTE** 350> 351> When the triggering conditions are met in the debugging process, the APIs with an asterisk (*) can print the ERROR log with call stack information instead of interrupting the process. 352 353### Example 354 355> **NOTE**<br>The following code is intended only to create exception scenarios and trigger DFX error logs. Do not apply it to your code before you fully understand its purpose. 356 357#### env_cleanup_hook APIs 358 359Sample code of **napi_add_env_cleanup_hook** and **napi_remove_env_cleanup_hook**: 360 361```cpp 362static void EnvCLeanUpCallback(void *arg) { 363 char* data = reinterpret_cast<char *>(arg); 364 delete data; 365} 366 367/* 368 * API declaration index.d.ts 369 * const triggerDFXClnAddXT: () => void; 370 */ 371napi_value TriggerDFXClnAddXT(napi_env env, napi_callback_info) 372{ 373 char* data = new char; 374 CHECK_NOT_NULL(data); 375 *data = nullptr; 376 std::thread([](napi_env env, char* data) { 377 napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data)); 378 }, env, data).join(); 379 napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data)); 380 delete data; 381 return nullptr; 382} 383 384/* 385 * API declaration index.d.ts 386 * const triggerDFXClnAddMT: () => void; 387 */ 388napi_value TriggerDFXClnAddMT(napi_env env, napi_callback_info) 389{ 390 char* data = new char; 391 CHECK_NOT_NULL(data); 392 *data = nullptr; 393 napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data)); 394 napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data)); 395 napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data)); 396 delete data; 397 return nullptr; 398} 399 400/* 401 * API declaration index.d.ts 402 * const triggerDFXClnRmXT: () => void; 403 */ 404napi_value TriggerDFXClnRmXT(napi_env env, napi_callback_info) 405{ 406 char* data = new char; 407 CHECK_NOT_NULL(data); 408 *data = nullptr; 409 napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data)); 410 std::thread([](napi_env env, char* data) { 411 napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data)); 412 delete data; 413 }, env, data).join(); 414 return nullptr; 415} 416 417/* 418 * API declaration index.d.ts 419 * const triggerDFXClnRmMT: () => void; 420 */ 421napi_value TriggerDFXClnRmMT(napi_env env, napi_callback_info) 422{ 423 char* data = new char; 424 CHECK_NOT_NULL(data); 425 *data = nullptr; 426 napi_add_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data)); 427 napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data)); 428 // Ensure consistency in parameters used for registering and deregistering cleanup hooks. It is more important than the errors caused by repeated deregistration. 429 napi_remove_env_cleanup_hook(env, EnvCLeanUpCallback, reinterpret_cast<void *>(data)); 430 delete data; 431 return nullptr; 432} 433``` 434 435#### async_cleanup_hook APIs 436 437Sample code of **napi_add_async_cleanup_hook**: 438 439```cpp 440static void AsyncCleanupCallback(napi_async_cleanup_hook_handle handle, void *) 441{ 442 napi_remove_async_cleanup_hook(handle); 443} 444 445/* 446 * API declaration index.d.ts 447 * const triggerDFXAsyncAddXT: () => void; 448 */ 449napi_value TriggerDFXAsyncAddXT(napi_env env, napi_callback_info) 450{ 451 std::thread([](napi_env env) { 452 napi_add_async_cleanup_hook(env, AsyncCleanupCallback, nullptr, nullptr); 453 }, env).join(); 454 return nullptr; 455} 456``` 457 458#### instance_data APIs 459 460Sample code of **napi_set_instance_data** and **napi_get_instance_data**: 461 462```cpp 463/* 464 * API declaration index.d.ts 465 * const triggerDFXInsSetXT: () => void; 466 */ 467napi_value TriggerDFXInsSetXT(napi_env env, napi_callback_info) 468{ 469 std::thread([](napi_env env) { 470 napi_set_instance_data(env, nullptr, [](napi_env, void *, void *) {}, nullptr); 471 }, env).join(); 472 return nullptr; 473} 474 475/* 476 * API declaration index.d.ts 477 * const triggerDFXInsGetXT: () => void; 478 */ 479napi_value TriggerDFXInsGetXT(napi_env env, napi_callback_info) 480{ 481 std::thread([](napi_env env) { 482 void *data = nullptr; 483 napi_get_instance_data(env, &data); 484 }, env).join(); 485 return nullptr; 486} 487``` 488