1# JSVM-API Development Specifications 2 3## Lifecycle Management 4 5**[Rule]** Properly use **OH_JSVM_OpenHandleScope** and **OH_JSVM_CloseHandleScope** to minimize the lifecycle of **JSVM_Value** and prevent memory leaks. 6 7Each **JSVM_Value** belongs to a specific **HandleScope** instance, which is created by **OH_JSVM_OpenHandleScope** and closed by **OH_JSVM_CloseHandleScope**. After a **HandleScope** instance is closed, the corresponding **JSVM_Value** will be automatically released. 8 9**NOTE** 10 11- **JSVM_Value** can be created only after **HandleScope** is opened; otherwise, the application may crash. Node-API does not have this restriction. 12- **JSVM_Value** cannot be used after the corresponding **HandleScope** is closed. To hold **JSVM_Value** persistently, call **OH_JSVM_CreateReference** to convert **JSVM_Value** to **JSVM_Ref**. 13- The scopes (including **JSVM_VMScope**, **JSVM_EnvScope**, and **JSVM_HandleScope**) must be closed in reverse order. The scope opened first must be closed last. Otherwise, the application may crash. 14 15**Example (scope closing error)** 16``` 17// If JSVM_VMScope is not closed in reverse order, the application may crash. 18JSVM_VM vm; 19JSVM_CreateVMOptions options; 20OH_JSVM_CreateVM(&options, &vm); 21 22JSVM_VMScope vmScope1, vmScope2; 23OH_JSVM_OpenVMScope(vm, &vmScope1); 24OH_JSVM_OpenVMScope(vm, &vmScope2); 25 26// You need to close vmScope2 and then vmScope1. 27OH_JSVM_CloseVMScope(vm, vmScope1); 28OH_JSVM_CloseVMScope(vm, vmScope2); 29OH_JSVM_DestroyVM(vm); 30``` 31 32 33**Encapsulation in C++** 34 35``` 36class HandleScopeWrapper { 37 public: 38 HandleScopeWrapper(JSVM_Env env) : env(env) { 39 OH_JSVM_OpenHandleScope(env, &handleScope); 40 } 41 42 ~HandleScopeWrapper() { 43 OH_JSVM_CloseHandleScope(env, handleScope); 44 } 45 46 HandleScopeWrapper(const HandleScopeWrapper&) = delete; 47 HandleScopeWrapper& operator=(const HandleScopeWrapper&) = delete; 48 HandleScopeWrapper(HandleScopeWrapper&&) = delete; 49 void* operator new(size_t) = delete; 50 void* operator new[](size_t) = delete; 51 52 protected: 53 JSVM_Env env; 54 JSVM_HandleScope handleScope; 55}; 56``` 57 58**Example** 59 60```c++ 61// When JSVM-API is frequently called to create JS objects in the for loop, use handle_scope to release resources in a timely manner when they are no longer used. 62// In the following example, the lifecycle of the local variable res ends at the end of each loop. To prevent memory leaks, scope is used to release the JS object in a timely manner. 63// After each for loop ends, trigger the destructor function of HandleScopeWrapper to release the JS object held by scope. 64for (int i = 0; i < 100000; i++) 65{ 66 HandleScopeWrapper scope(env); 67 JSVM_Value res; 68 OH_JSVM_CreateObject(env, &res); 69 if (i == 1000) { 70 // After the loop exits, the HandleScopeWrapper destructor is automatically called to release resources. 71 break; 72 } 73} 74``` 75 76## Context Sensitive in Multiple JSVM Instances 77 78**[Rule]** Do not use JSVM-API to access JS objects across JSVM instances. 79 80A JSVM instance is an independent running environment. Operations such as creating and accessing a JS object must be performed in the same JSVM instance. If an object is operated in different JSVM instances, the application may crash. A JSVM instance is represented as a **JSVM_Env** in APIs. 81 82**Example (incorrect)** 83 84```c++ 85// Create a string object with value of "bar" in env1. 86OH_JSVM_CreateStringUtf8(env1, "value1", JSVM_AUTO_LENGTH , &string); 87// Create an object in env2 and set the string object to this object. 88JSVM_Status status = OH_JSVM_CreateObject(env2, &object); 89if (status != JSVM_OK) 90{ 91 return; 92} 93 94status = OH_JSVM_SetNamedProperty(env2, object, "string1", string); 95if (status != JSVM_OK) 96{ 97 return; 98} 99``` 100 101A JS object belongs to a specific **JSVM_Env**. Therefore, you cannot set an object of env1 to an object of env2. If the object of env1 is accessed in env2, the application may crash. 102 103## JSVM Instance Shared by Multiple Threads 104 105[**Rule**] When multiple threads use the same JSVM instance, use a lock to ensure that the JSVM instance can be executed in only one thread at a time. If multiple threads use the JSVM instance at the same time, the application may crash. 106 107> **NOTE** 108> 109> - You can use **OH_JSVM_IsLocked** to check whether the calling thread holds the lock of the JSVM instance instead of setting a loop to wait for other threads to release the lock. 110> - Nested use of **OH_JSVM_AcquireLock** in the same thread will not cause deadlock. 111> - When using **OH_JSVM_ReleaseLock**, you need to check whether it is at the outermost layer to prevent the inner layer from releasing the lock of the entire thread when **OH_JSVM_AcquireLock** is nested in the same thread. 112> - After **OH_JSVM_AcquireLock** is called, use **OH_JSVM_OpenHandleScope** to enable the JSVM instance to enter the thread. After **OH_JSVM_ReleaseLock** is called, use **OH_JSVM_ReleaseLock** to enable the JSVM instance to exit the thread. 113> - A JSVM instance cannot be nested across threads. If you need to temporarily change the thread that uses the JSVM instance, ensure that **JSVM_Value** is saved as **JSVM_Ref**. After the lock is released, **JSVM_Value** cannot be accessed. 114> - The sequence of obtaining resources is as follows: Lock -> VMScope -> EnvScope -> HandleScope. The sequence of releasing resources is the opposite. An incorrect sequence may cause the application to crash. 115 116 117 118**Encapsulation in C++** 119 120``` 121class LockWrapper { 122 public: 123 // Constructor, lock acquisition, VMScope, and EnvScope. 124 LockWrapper(JSVM_Env env) : env(env) { 125 OH_JSVM_IsLocked(env, &isLocked); 126 if (!isLocked) { 127 OH_JSVM_AcquireLock(env); 128 OH_JSVM_GetVM(env, &vm); 129 OH_JSVM_OpenVMScope(vm, &vmScope); 130 OH_JSVM_OpenEnvScope(env, &envScope); 131 } 132 } 133 134 // Destructor used to release EnvScope, VMScope, and lock. 135 ~LockWrapper() { 136 if (!isLocked) { 137 OH_JSVM_CloseEnvScope(env, envScope); 138 OH_JSVM_CloseVMScope(vm, vmScope); 139 OH_JSVM_ReleaseLock(env); 140 } 141 } 142 143 LockWrapper(const LockWrapper&) = delete; 144 LockWrapper& operator=(const LockWrapper&) = delete; 145 LockWrapper(LockWrapper&&) = delete; 146 void* operator new(size_t) = delete; 147 void* operator new[](size_t) = delete; 148 149 private: 150 JSVM_Env env; 151 JSVM_EnvScope envScope; 152 JSVM_VMScope vmScope; 153 JSVM_VM vm; 154 bool isLocked; 155}; 156``` 157 158 159 160**Example (correct)** 161 162``` 163// This example demonstrates how to use a JSVM across threads. 164// Thread t1 obtains the lock and call JSVM-API. 165// Thread t2 is blocked when thread t1 obtained the lock, continues to call JSVM-API when t1 releases the lock. 166static napi_value Add([[maybe_unused]] napi_env _env, [[maybe_unused]] napi_callback_info _info) { 167 static JSVM_VM vm; 168 static JSVM_Env env; 169 if (aa == 0) { 170 OH_JSVM_Init(nullptr); 171 aa++; 172 // create vm 173 JSVM_CreateVMOptions options; 174 memset(&options, 0, sizeof(options)); 175 OH_JSVM_CreateVM(&options, &vm); 176 // Create env. 177 OH_JSVM_CreateEnv(vm, 0, nullptr, &env); 178 } 179 180 std::thread t1([]() { 181 LockWrapper lock(env); 182 JSVM_HandleScope handleScope; 183 OH_JSVM_OpenHandleScope(env, &handleScope); 184 JSVM_Value value; 185 JSVM_Status rst = OH_JSVM_CreateInt32(env, 32, &value); // 32: numerical value 186 if (rst == JSVM_OK) { 187 OH_LOG_INFO(LOG_APP, "JSVM:t1 OH_JSVM_CreateInt32 suc"); 188 } else { 189 OH_LOG_ERROR(LOG_APP, "JSVM:t1 OH_JSVM_CreateInt32 fail"); 190 } 191 int32_t num1; 192 OH_JSVM_GetValueInt32(env, value, &num1); 193 OH_LOG_INFO(LOG_APP, "JSVM:t1 num1 = %{public}d", num1); 194 OH_JSVM_CloseHandleScope(env, handleScope); 195 }); 196 std::thread t2([]() { 197 LockWrapper lock(env); 198 JSVM_HandleScope handleScope; 199 OH_JSVM_OpenHandleScope(env, &handleScope); 200 JSVM_Value value; 201 JSVM_Status rst = OH_JSVM_CreateInt32(env, 32, &value); // 32: numerical value 202 if (rst == JSVM_OK) { 203 OH_LOG_INFO(LOG_APP, "JSVM:t2 OH_JSVM_CreateInt32 suc"); 204 } else { 205 OH_LOG_ERROR(LOG_APP, "JSVM:t2 OH_JSVM_CreateInt32 fail"); 206 } 207 int32_t num1; 208 OH_JSVM_GetValueInt32(env, value, &num1); 209 OH_LOG_INFO(LOG_APP, "JSVM:t2 num1 = %{public}d", num1); 210 OH_JSVM_CloseHandleScope(env, handleScope); 211 }); 212 t1.detach(); 213 t2.detach(); 214 return nullptr; 215} 216``` 217 218## Obtaining Arguments Passed from JS 219 220**[Rule]** When **argv** in **OH_JSVM_GetCbInfo** is not **nullptr**, the length of **argv** must be greater than or equal to **argc**. 221 222If **argv** is not **nullptr**, the arguments actually passed from JS will be written to **argv** in **OH_JSVM_GetCbInfo** based on the value of **argc**. If there are more arguments than the provided count, only the requested number of arguments are copied. If there are fewer arguments provided than the claimed, the rest of **argv** is filled with values that represent **undefined**. 223 224**Example (incorrect)** 225 226```cpp 227static JSVM_Value IncorrectDemo1(JSVM_Env env, JSVM_CallbackInfo info) { 228 // argc is not correctly initialized and is set to a random value. If the length of argv is less than the number of arguments specified by argc, data overwriting occurs. 229 size_t argc; 230 JSVM_Value argv[10] = {nullptr}; 231 OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr); 232 return nullptr; 233} 234 235static JSVM_Value IncorrectDemo2(JSVM_Env env, JSVM_CallbackInfo info) { 236 // If the number of arguments specified by argc is greater than the length of argv, out-of-bounds write will occur when data is written to argv in napi_get_cb_info. 237 size_t argc = 5; 238 JSVM_Value argv[3] = {nullptr}; 239 OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr); 240 return nullptr; 241} 242``` 243 244**Example (correct)** 245 246```cpp 247static JSVM_Value GetArgvDemo1(napi_env env, JSVM_CallbackInfo info) { 248 size_t argc = 0; 249 // Pass in nullptr to argv to obtain the actual number of arguments passed by JS. 250 OH_JSVM_GetCbInfo(env, info, &argc, nullptr, nullptr, nullptr); 251 // If 0 is passed by JS, the subsequent logic is not executed. 252 if (argc == 0) { 253 return nullptr; 254 } 255 // Create an array to obtain the arguments passed by JS. 256 JSVM_Value* argv = new JSVM_Value[argc]; 257 OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr); 258 // Service code. 259 // ... ... 260 // argv is an object created by new and must be manually released when it is not required. 261 delete argv; 262 return nullptr; 263} 264 265static JSVM_Value GetArgvDemo2(napi_env env, JSVM_CallbackInfo info) { 266 size_t argc = 2; 267 JSVM_Value* argv[2] = {nullptr}; 268 // The arguments (of the quantity specified by argc) passed from JS or undefined will be written to argv of OH_JSVM_GetCbInfo. 269 OH_JSVM_GetCbInfo(env, info, &argc, nullptr, nullptr, nullptr); 270 // Service code. 271 // ... ... 272 return nullptr; 273} 274``` 275 276## Exception Handling 277 278**[Suggestion]** Any exception occurred in a JSVM-API call should be handled in a timely manner. Otherwise, unexpected behavior may occur. 279 280 Exception handling can be classified into the following types based on the primary/secondary relationship between the JS and native code: 281 282- If an exception occurs in the native code of a callback (JS is primary and native is secondary), throw an exception to the JSVM. 283 ```c++ 284 // Native is primary, and JSVM is secondary. 285 void ThrowError() { 286 bool isPending = false; 287 if (JSVM_OK == OH_JSVM_IsExceptionPending((env), &isPending) && isPending) { 288 JSVM_Value error; 289 // Obtain and clear the JSVM exception. 290 if (JSVM_OK == OH_JSVM_GetAndClearLastException((env), &error)) { 291 // Obtain the exception stack. 292 JSVM_Value stack = nullptr; 293 CALL_JSVM(env, OH_JSVM_GetNamedProperty((env), error, "stack", &stack)); 294 295 JSVM_Value message = nullptr; 296 CALL_JSVM(env, OH_JSVM_GetNamedProperty((env), error, "message", &message)); 297 298 // Convert the JS string to the std::string of C++. 299 std::string stackstr = stack? GetValueString(stack) : ""; 300 std::string messagestr = message? GetValueString(message) : ""; 301 // Throw an exception. JSError needs to be implemented. 302 throw JSError(*this, messagestr, stackstr); 303 } 304 } 305 // Throw an exception. JSError needs to be implemented. 306 throw JSError(*this, "JSVM Runtime: unkown execption"); 307 } 308 309 status = OH_JSVM_SetNamedProperty(env, object, "foo", string); 310 // If the JSVM-API call fails, clear the pending exception of the JSVM instance and throw a C++ exception. 311 if (status != JSVM_OK) { 312 ThrowError(); 313 } 314 ``` 315 316- If JSVM-API (native is primary and JS is secondary) fails to be called from C++, clear the pending exceptions in JSVM to prevent the impact on subsequent JSVM-API execution and set a branch to handling C++ exception (or thrown a C++ exception). 317 ``` 318 // JSVM is primary, and native is secondary. 319 void DoSomething() { 320 throw("Do something failed"); 321 } 322 323 // Demo 1: Capture a C++ exception and throw the exception to the JSVM. 324 JSVM_Value NativeFunctionExceptionDemo1(JSVM_Env env, JSVM_CallbackInfo info) { 325 try { 326 DoSomething(); 327 } catch (const char *ex) { 328 OH_JSVM_ThrowError(env, nullptr, ex); 329 return nullptr; 330 } 331 return nullptr; 332 } 333 334 // Demo 2: The JSVM-API call fails, and an exception is thrown to the JSVM. 335 JSVM_Value NativeFunctionExceptionDemo2(JSVM_Env env, JSVM_CallbackInfo info) { 336 JSVM_Value JSBool = nullptr; 337 bool value = false; 338 auto status = OH_JSVM_GetValueBool(env, JSBool, &value); 339 if (status != JSVM_OK) { 340 OH_JSVM_ThrowError(env, nullptr, "Get bool value failed"); 341 return nullptr; 342 } 343 return nullptr; 344 } 345 346 // Demo 3: The JSVM-API call fails, and an exception to be handled is added to the JSVM during the invocation. In this case, no exception needs to be thrown to the JSVM. 347 JSVM_Value NativeFunctionExceptionDemo3(JSVM_Env env, JSVM_CallbackInfo info) { 348 std::string sourcecodestr = R"JS( 349 throw Error('Error throw from js'); 350 )JS"; 351 JSVM_Value sourcecodevalue = nullptr; 352 OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue); 353 JSVM_Script script; 354 auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script); 355 JSVM_Value result; 356 // Execute the JS script. During the execution, a JS exception is thrown. 357 status = OH_JSVM_RunScript(env, script, &result); 358 if (status != JSVM_OK) { 359 bool isPending = false; 360 // If an exception already exists, do not throw the exception to the JSVM. 361 // If a new exception needs to be thrown, handle the pending exception in the JSVM first. 362 if (JSVM_OK == OH_JSVM_IsExceptionPending((env), &isPending) && isPending) { 363 return nullptr; 364 } 365 OH_JSVM_ThrowError(env, nullptr, "Runscript failed"); 366 return nullptr; 367 } 368 return nullptr; 369 } 370 371 // Bind NativeFunction to the JSVM. The process is omitted here. 372 std::string sourcecodestr = R"JS( 373 // The console log needs to be implemented. 374 try { 375 // Call the native function. 376 NativeFunction() 377 } catch (e) { 378 // Handle the exception in C/C++. 379 consolelog(e.message) 380 } 381 )JS"; 382 JSVM_Value sourcecodevalue = nullptr; 383 OH_JSVM_CreateStringUtf8(env, sourcecodestr.c_str(), sourcecodestr.size(), &sourcecodevalue); 384 JSVM_Script script; 385 auto status = OH_JSVM_CompileScript(env, sourcecodevalue, nullptr, 0, true, nullptr, &script); 386 OH_LOG_INFO(LOG_APP, "JSVM API TEST: %{public}d", (uint32_t)status); 387 JSVM_Value result; 388 // Execute the JS script to call native methods from JS. 389 status = OH_JSVM_RunScript(env, script, &result); 390 ``` 391 392> **NOTE** 393> 394> If JSVM-API fails to be called in a callback, you can determine whether to throw an exception to the JSVM. If an exception needs to be thrown to the JSVM, ensure that there is no pending exception in the JSVM. If no exception is thrown, JS **try-catch** can capture such JS exception. For details, see "NativeFunctionException Demo 3." 395 396## Binding Object Context 397 398**[Rule]**: The JS function and object generated by JSVM-API can be accessed from JS only after they are bound to the context. The **const char *** parameter in **OH_JSVM_CreateFunction** is the property **name** of the created function, without pointing to the function in the context. This rule also applies to the class and object generated by JSVM-API. 399 400**Example** 401 402``` 403JSVM_Value JSFunc = nullptr; 404const char *name = "NativeFunction"; 405JSVM_CallbackStruct cb = {NativeFunction, nullptr}; 406// Create a JS function, whose property "name" is set to "NativeFunction". 407OH_JSVM_CreateFunction(env, name, strlen(name), &cb, &JSFunc); 408// Bind the function to the context. 409// Obtain the global object of the context. 410JSVM_Value global = nullptr; 411OH_JSVM_GetGlobal(env, &global); 412// Create the JS string "FunctionNameInJSContext." 413JSVM_Value key = nullptr; 414OH_JSVM_CreateStringLatin1(env, "FunctionNameInJSContext", JSVM_AUTO_LENGTH, &key); 415// Set the global property "FunctionNameInJSContext" to "JSFunc". This binds the function to the context. 416OH_JSVM_SetProperty(env, global, key, JSFunc); 417// Call the function from JS. 418std::string sourcecodestr = R"JS( 419 // The console log needs to be implemented. 420 FunctionNameInJSContext() // The call is successful. 421 consolelog(FunctionNameInJSContext.name) // Print "NativeFunction." 422 try { 423 NativeFunction() // If the function cannot be found, throw an exception. 424 } catch (e) { 425 consolelog(e.message) 426 } 427)JS"; 428``` 429