1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <cstring>
17 #include <vector>
18 #include <string>
19
20 #include "interop-logging.h"
21 #include "convertors-napi.h"
22
23 // NOLINTBEGIN
24
25 // Adapter for NAPI_MODULE
26 #define NODE_API_MODULE_ADAPTER(modname, regfunc) \
27 static napi_value __napi_##regfunc(napi_env env, napi_value exports) \
28 { \
29 /* CC-OFFNXT(G.PRE.05) code generation */ \
30 return Napi::RegisterModule(env, exports, regfunc); \
31 } \
32 NAPI_MODULE(modname, __napi_##regfunc)
33
getValueTypeChecked(napi_env env,napi_value value)34 napi_valuetype getValueTypeChecked(napi_env env, napi_value value)
35 {
36 napi_valuetype type;
37 napi_status status = napi_typeof(env, value, &type);
38 TS_NAPI_THROW_IF_FAILED(env, status, napi_undefined);
39 return type;
40 }
41
isTypedArray(napi_env env,napi_value value)42 bool isTypedArray(napi_env env, napi_value value)
43 {
44 bool result = false;
45 napi_status status = napi_is_typedarray(env, value, &result);
46 TS_NAPI_THROW_IF_FAILED(env, status, false);
47 return result;
48 }
49
getBoolean(napi_env env,napi_value value)50 KBoolean getBoolean(napi_env env, napi_value value)
51 {
52 if (getValueTypeChecked(env, value) == napi_valuetype::napi_boolean) {
53 bool result = false;
54 napi_get_value_bool(env, value, &result);
55 return static_cast<KBoolean>(result);
56 }
57 return static_cast<KBoolean>(getInt32(env, value) != 0);
58 }
59
getInt32(napi_env env,napi_value value)60 KInt getInt32(napi_env env, napi_value value)
61 {
62 if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
63 napi_throw_error(env, nullptr, "Expected Number");
64 return 0;
65 }
66 int32_t result = 0;
67 napi_get_value_int32(env, value, &result);
68 return static_cast<KInt>(result);
69 }
70
getUInt32(napi_env env,napi_value value)71 KUInt getUInt32(napi_env env, napi_value value)
72 {
73 if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
74 napi_throw_error(env, nullptr, "Expected Number");
75 return 0;
76 }
77 uint32_t result = 0U;
78 napi_get_value_uint32(env, value, &result);
79 return static_cast<KUInt>(result);
80 }
81
getFloat32(napi_env env,napi_value value)82 KFloat getFloat32(napi_env env, napi_value value)
83 {
84 if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
85 napi_throw_error(env, nullptr, "Expected Number");
86 return 0.0F;
87 }
88 double result = 0.0;
89 napi_get_value_double(env, value, &result);
90 return static_cast<KFloat>(static_cast<float>(result));
91 }
92
getFloat64(napi_env env,napi_value value)93 KDouble getFloat64(napi_env env, napi_value value)
94 {
95 if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
96 napi_throw_error(env, nullptr, "Expected Number");
97 return 0.0;
98 }
99 double result = 0.0;
100 napi_get_value_double(env, value, &result);
101 return static_cast<KDouble>(result);
102 }
103
getString(napi_env env,napi_value value)104 KStringPtr getString(napi_env env, napi_value value)
105 {
106 KStringPtr result {};
107 napi_valuetype valueType = getValueTypeChecked(env, value);
108 if (valueType == napi_valuetype::napi_null || valueType == napi_valuetype::napi_undefined) {
109 return result;
110 }
111
112 if (valueType != napi_valuetype::napi_string) {
113 napi_throw_error(env, nullptr, "Expected String");
114 return result;
115 }
116
117 size_t length = 0;
118 napi_status status = napi_get_value_string_utf8(env, value, nullptr, 0, &length);
119 if (status != 0) {
120 return result;
121 }
122 result.resize(length);
123 status = napi_get_value_string_utf8(env, value, result.data(), length + 1, nullptr);
124 TS_NAPI_THROW_IF_FAILED(env, status, nullptr);
125 return result;
126 }
127
getPointer(napi_env env,napi_value value)128 KNativePointer getPointer(napi_env env, napi_value value)
129 {
130 napi_valuetype valueType = getValueTypeChecked(env, value);
131 if (valueType == napi_valuetype::napi_external) {
132 KNativePointer result = nullptr;
133 napi_status status = napi_get_value_external(env, value, &result);
134 TS_NAPI_THROW_IF_FAILED(env, status, nullptr);
135 return result;
136 }
137
138 if (valueType != napi_valuetype::napi_bigint) {
139 napi_throw_error(env, nullptr, "cannot be coerced to pointer");
140 return nullptr;
141 }
142
143 bool isWithinRange = true;
144 uint64_t ptrU64 = 0;
145 napi_status status = napi_get_value_bigint_uint64(env, value, &ptrU64, &isWithinRange);
146 TS_NAPI_THROW_IF_FAILED(env, status, nullptr);
147 if (!isWithinRange) {
148 napi_throw_error(env, nullptr, "cannot be coerced to uint64, value is too large");
149 return nullptr;
150 }
151 return reinterpret_cast<KNativePointer>(ptrU64);
152 }
153
getInt64(napi_env env,napi_value value)154 KLong getInt64(napi_env env, napi_value value)
155 {
156 if (getValueTypeChecked(env, value) != napi_valuetype::napi_bigint) {
157 napi_throw_error(env, nullptr, "cannot be coerced to int64");
158 return -1;
159 }
160
161 bool isWithinRange = true;
162 int64_t ptr64 = 0;
163 napi_get_value_bigint_int64(env, value, &ptr64, &isWithinRange);
164 if (!isWithinRange) {
165 napi_throw_error(env, nullptr, "cannot be coerced to int64, value is too large");
166 return -1;
167 }
168 return static_cast<KLong>(ptr64);
169 }
170
makeString(napi_env env,const KStringPtr & value)171 napi_value makeString(napi_env env, const KStringPtr &value)
172 {
173 napi_value result;
174 napi_status status = napi_create_string_utf8(env, value.isNull() ? "" : value.data(), value.length(), &result);
175 TS_NAPI_THROW_IF_FAILED(env, status, result);
176 return result;
177 }
178
makeString(napi_env env,const std::string & value)179 napi_value makeString(napi_env env, const std::string &value)
180 {
181 napi_value result;
182 napi_status status = napi_create_string_utf8(env, value.c_str(), value.length(), &result);
183 TS_NAPI_THROW_IF_FAILED(env, status, result);
184 return result;
185 }
186
makeBoolean(napi_env env,int8_t value)187 napi_value makeBoolean(napi_env env, int8_t value)
188 {
189 napi_value result;
190 napi_status status = napi_get_boolean(env, value != 0, &result);
191 TS_NAPI_THROW_IF_FAILED(env, status, result);
192 return result;
193 }
194
makeInt32(napi_env env,int32_t value)195 napi_value makeInt32(napi_env env, int32_t value)
196 {
197 napi_value result;
198 napi_status status = napi_create_int32(env, value, &result);
199 TS_NAPI_THROW_IF_FAILED(env, status, result);
200 return result;
201 }
202
makeUInt32(napi_env env,uint32_t value)203 napi_value makeUInt32(napi_env env, uint32_t value)
204 {
205 napi_value result;
206 napi_status status = napi_create_uint32(env, value, &result);
207 TS_NAPI_THROW_IF_FAILED(env, status, result);
208 return result;
209 }
210
makeFloat32(napi_env env,float value)211 napi_value makeFloat32(napi_env env, float value)
212 {
213 napi_value result;
214 napi_status status = napi_create_double(env, value, &result);
215 TS_NAPI_THROW_IF_FAILED(env, status, result);
216 return result;
217 }
218
makePointer(napi_env env,void * value)219 napi_value makePointer(napi_env env, void *value)
220 {
221 napi_value result;
222 napi_status status =
223 napi_create_bigint_uint64(env, static_cast<uint64_t>(reinterpret_cast<uintptr_t>(value)), &result);
224 TS_NAPI_THROW_IF_FAILED(env, status, result);
225 return result;
226 }
227
makeVoid(napi_env env)228 napi_value makeVoid(napi_env env)
229 {
230 napi_value result;
231 napi_status status = napi_get_undefined(env, &result);
232 TS_NAPI_THROW_IF_FAILED(env, status, result);
233 return result;
234 }
235
makeObject(napi_env env,napi_value object)236 napi_value makeObject(napi_env env, [[maybe_unused]] napi_value object)
237 {
238 napi_value result;
239 napi_status status = napi_create_object(env, &result);
240 TS_NAPI_THROW_IF_FAILED(env, status, result);
241 return result;
242 }
243
244 #if _MSC_VER >= 1932 // Visual Studio 2022 version 17.2+
245 #pragma comment(linker, "/alternatename:__imp___std_init_once_complete=__imp_InitOnceComplete")
246 #pragma comment(linker, "/alternatename:__imp___std_init_once_begin_initialize=__imp_InitOnceBeginInitialize")
247 #endif
248
getInstance()249 Exports *Exports::getInstance()
250 {
251 static Exports *instance = nullptr;
252 if (instance == nullptr) {
253 instance = new Exports();
254 }
255 return instance;
256 }
257
getModules()258 std::vector<std::string> Exports::getModules()
259 {
260 std::vector<std::string> result;
261 for (auto it = implementations.begin(); it != implementations.end(); ++it) {
262 result.push_back(it->first);
263 }
264 return result;
265 }
266
addMethod(const char * module,const char * name,napi_type_t impl)267 void Exports::addMethod(const char *module, const char *name, napi_type_t impl)
268 {
269 auto it = implementations.find(module);
270 if (it == implementations.end()) {
271 it = implementations.insert(std::make_pair(module, std::vector<std::pair<std::string, napi_type_t>>())).first;
272 }
273 it->second.push_back(std::make_pair(name, impl));
274 }
275
getMethods(const std::string & module)276 const std::vector<std::pair<std::string, napi_type_t>> &Exports::getMethods(const std::string &module)
277 {
278 auto it = implementations.find(module);
279 if (it == implementations.end()) {
280 LOGE("Module %s is not registered", module.c_str());
281 throw "Fatal error";
282 }
283 return it->second;
284 }
285
286 //
287 // Callback dispatcher
288 //
289 // NOTE(khil): Should we get rid of explicit Node_* declrations and hide the naming convention behind the macro
290 // definitions?
291
292 static napi_ref g_koalaNapiCallbackDispatcher = nullptr;
293
294 // NOTE(khil): shall we pass name in globalThis instead of object reference?
Node_SetCallbackDispatcher(napi_env env,napi_callback_info cbinfo)295 napi_value Node_SetCallbackDispatcher(napi_env env, napi_callback_info cbinfo)
296 {
297 CallbackInfo info(env, cbinfo);
298 napi_value dispatcher = info[0];
299 napi_value result = makeVoid(env);
300 napi_status status = napi_create_reference(env, dispatcher, 1, &g_koalaNapiCallbackDispatcher);
301 TS_NAPI_THROW_IF_FAILED(env, status, result);
302
303 return result;
304 }
MAKE_NODE_EXPORT(TS_INTEROP_MODULE,SetCallbackDispatcher)305 MAKE_NODE_EXPORT(TS_INTEROP_MODULE, SetCallbackDispatcher)
306
307 napi_value Node_CleanCallbackDispatcher(napi_env env, [[maybe_unused]] napi_callback_info cbinfo)
308 {
309 napi_value result = makeVoid(env);
310 if (g_koalaNapiCallbackDispatcher) {
311 napi_status status = napi_delete_reference(env, g_koalaNapiCallbackDispatcher);
312 g_koalaNapiCallbackDispatcher = nullptr;
313 TS_NAPI_THROW_IF_FAILED(env, status, result);
314 }
315 return result;
316 }
MAKE_NODE_EXPORT(TS_INTEROP_MODULE,CleanCallbackDispatcher)317 MAKE_NODE_EXPORT(TS_INTEROP_MODULE, CleanCallbackDispatcher)
318
319 napi_value getKoalaNapiCallbackDispatcher(napi_env env)
320 {
321 if (!g_koalaNapiCallbackDispatcher) {
322 abort();
323 }
324 napi_value value;
325 napi_status status = napi_get_reference_value(env, g_koalaNapiCallbackDispatcher, &value);
326 TS_NAPI_THROW_IF_FAILED(env, status, makeVoid(env));
327 return value;
328 }
329
330 // Module initialization
331 using ModuleRegisterCallback = napi_value (*)(napi_env env, napi_value exports);
332
333 /**
334 * Sets a new callback and returns its previous value.
335 */
ProvideModuleRegisterCallback(ModuleRegisterCallback value=nullptr)336 ModuleRegisterCallback ProvideModuleRegisterCallback(ModuleRegisterCallback value = nullptr)
337 {
338 static const ModuleRegisterCallback DEFAULT_CB = []([[maybe_unused]] napi_env env, napi_value exports) {
339 return exports;
340 };
341 static ModuleRegisterCallback curCallback = DEFAULT_CB;
342
343 ModuleRegisterCallback prevCallback = curCallback;
344 curCallback = value != nullptr ? value : DEFAULT_CB;
345 return prevCallback;
346 }
347
348 static constexpr bool SPLIT_MODULES = true;
349
InitModule(napi_env env,napi_value exports)350 static napi_value InitModule(napi_env env, napi_value exports)
351 {
352 Exports *inst = Exports::getInstance();
353 napi_status status;
354 napi_value target = exports;
355 for (const auto &module : inst->getModules()) {
356 if (SPLIT_MODULES) {
357 status = napi_create_object(env, &target);
358 TS_NAPI_THROW_IF_FAILED(env, status, exports);
359 status = napi_set_named_property(env, exports, module.c_str(), target);
360 TS_NAPI_THROW_IF_FAILED(env, status, exports);
361 }
362
363 for (const auto &impl : inst->getMethods(module)) {
364 napi_value implFunc;
365 status = napi_create_function(env, impl.first.c_str(), 0, impl.second, nullptr, &implFunc);
366 TS_NAPI_THROW_IF_FAILED(env, status, exports);
367 status = napi_set_named_property(env, target, impl.first.c_str(), implFunc);
368 TS_NAPI_THROW_IF_FAILED(env, status, exports);
369 }
370 }
371 return ProvideModuleRegisterCallback()(env, exports);
372 }
373
374 NAPI_MODULE(INTEROP_LIBRARY_NAME, InitModule)
375
376 // NOLINTEND
377