1 /*
2 * Copyright (c) 2021 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
18 #include "native_engine/native_value.h"
19 #include "quickjs_headers.h"
20 #include "utils/log.h"
21
22 struct JSObjectInfo {
23 JSContext* context = nullptr;
24 JSFinalizer finalizer = nullptr;
25 void* data = nullptr;
26 void* hint = nullptr;
27 };
28
29 JSClassID g_baseClassId = 1;
30
AddIntrinsicExternal(JSContext * context)31 void AddIntrinsicExternal(JSContext* context)
32 {
33 const char* className = "External";
34 JSValue global = JS_GetGlobalObject(context);
35 JSValue external = JS_NewCFunction2(
36 context,
37 [](JSContext* ctx, JSValueConst newTarget, int argc, JSValueConst* argv) {
38 JSValue proto = JS_GetPropertyStr(ctx, newTarget, "prototype");
39 JSValue result = JS_NewObjectProtoClass(ctx, proto, GetBaseClassID());
40 JS_FreeValue(ctx, proto);
41 return result;
42 },
43 className, strlen(className), JS_CFUNC_constructor, 0);
44 JSValue proto = JS_NewObject(context);
45
46 JS_DefinePropertyValueStr(context, external, "prototype", JS_DupValue(context, proto), 0);
47 JS_DefinePropertyValueStr(
48 context, proto, "constructor", JS_DupValue(context, external), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
49
50 JS_SetPropertyStr(context, global, "External", external);
51 JS_FreeValue(context, proto);
52 JS_FreeValue(context, global);
53 }
54
GetBaseClassID()55 JSClassID GetBaseClassID()
56 {
57 return g_baseClassId;
58 }
59
JS_NewExternal(JSContext * context,void * value,JSFinalizer finalizer,void * hint)60 JSValue JS_NewExternal(JSContext* context, void* value, JSFinalizer finalizer, void* hint)
61 {
62 JSValue global = JS_GetGlobalObject(context);
63 JSValue external = JS_GetPropertyStr(context, global, "External");
64 JSValue result = JS_CallConstructor(context, external, 0, nullptr);
65
66 auto info = new JSObjectInfo();
67 if (info != nullptr) {
68 info->context = context;
69 info->finalizer = finalizer;
70 info->data = value;
71 info->hint = hint;
72 JS_SetOpaque(result, info);
73 }
74
75 JS_FreeValue(context, external);
76 JS_FreeValue(context, global);
77 return result;
78 }
79
JS_ExternalToNativeObject(JSContext * context,JSValue value)80 void* JS_ExternalToNativeObject(JSContext* context, JSValue value)
81 {
82 auto* info = reinterpret_cast<JSObjectInfo*>(JS_GetOpaque(value, GetBaseClassID()));
83 return (info != nullptr) ? info->data : nullptr;
84 }
85
JS_IsExternal(JSContext * context,JSValue value)86 bool JS_IsExternal(JSContext* context, JSValue value)
87 {
88 bool result = false;
89 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
90 JSValue name = JS_GetPropertyStr(context, constructor, "name");
91 const char* cName = JS_ToCString(context, name);
92 result = !strcmp("External", cName ? cName : "");
93 JS_FreeCString(context, cName);
94 JS_FreeValue(context, name);
95 JS_FreeValue(context, constructor);
96 return result;
97 }
98
AddIntrinsicBaseClass(JSContext * context)99 void AddIntrinsicBaseClass(JSContext* context)
100 {
101 const JSClassDef baseClassDef = {
102 .class_name = "BaseClass",
103 .finalizer =
104 [](JSRuntime* rt, JSValue val) {
105 auto* info = reinterpret_cast<JSObjectInfo*>(JS_GetOpaque(val, GetBaseClassID()));
106 if (info != nullptr) {
107 info->finalizer(info->context, info->data, info->hint);
108 delete info;
109 }
110 },
111 };
112
113 JS_NewClassID(&g_baseClassId);
114 JS_NewClass(JS_GetRuntime(context), g_baseClassId, &baseClassDef);
115 }
116
JS_AddFinalizer(JSContext * context,JSValue value,void * pointer,JSFinalizer finalizer,void * hint)117 void JS_AddFinalizer(JSContext* context, JSValue value, void* pointer, JSFinalizer finalizer, void* hint)
118 {
119 auto* info = reinterpret_cast<JSObjectInfo*>(JS_GetOpaque(value, GetBaseClassID()));
120 if (info == nullptr) {
121 info = new JSObjectInfo();
122 if (info != nullptr) {
123 info->context = context;
124 info->finalizer = finalizer;
125 info->data = pointer;
126 info->hint = hint;
127 }
128 }
129
130 if (info) {
131 JS_SetOpaque(value, info);
132 }
133 }
134
JS_FreeFinalizer(JSValue value)135 void JS_FreeFinalizer(JSValue value)
136 {
137 auto* info = reinterpret_cast<JSObjectInfo*>(JS_GetOpaque(value, GetBaseClassID()));
138 if (info != nullptr) {
139 delete info;
140 info = nullptr;
141 JS_SetOpaque(value, info);
142 }
143 }
144
JS_SetNativePointer(JSContext * context,JSValue value,void * pointer,JSFinalizer finalizer,void * hint)145 void JS_SetNativePointer(JSContext* context, JSValue value, void* pointer, JSFinalizer finalizer, void* hint)
146 {
147 auto* info = reinterpret_cast<JSObjectInfo*>(JS_GetOpaque(value, GetBaseClassID()));
148 if (info == nullptr) {
149 info = new JSObjectInfo();
150 if (info != nullptr) {
151 info->context = context;
152 info->finalizer = finalizer;
153 info->data = pointer;
154 info->hint = hint;
155 }
156 } else if (pointer == nullptr) {
157 delete info;
158 info = nullptr;
159 }
160
161 JS_SetOpaque(value, info);
162 }
163
JS_GetNativePointer(JSContext * context,JSValue value)164 void* JS_GetNativePointer(JSContext* context, JSValue value)
165 {
166 auto* info = reinterpret_cast<JSObjectInfo*>(JS_GetOpaque(value, GetBaseClassID()));
167 return (info != nullptr) ? info->data : nullptr;
168 }
169
JS_IsPromise(JSContext * context,JSValue value)170 bool JS_IsPromise(JSContext* context, JSValue value)
171 {
172 bool result = false;
173 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
174 JSValue name = JS_GetPropertyStr(context, constructor, "name");
175 const char* cName = JS_ToCString(context, name);
176 result = !strcmp("Promise", cName ? cName : "");
177 JS_FreeCString(context, cName);
178 JS_FreeValue(context, name);
179 JS_FreeValue(context, constructor);
180 return result;
181 }
182
JS_IsMapIterator(JSContext * context,JSValue value)183 bool JS_IsMapIterator(JSContext* context, JSValue value)
184 {
185 return false;
186 }
187
JS_IsSetIterator(JSContext * context,JSValue value)188 bool JS_IsSetIterator(JSContext* context, JSValue value)
189 {
190 return false;
191 }
192
JS_IsGeneratorObject(JSContext * context,JSValue value)193 bool JS_IsGeneratorObject(JSContext* context, JSValue value)
194 {
195 return false;
196 }
197
JS_IsModuleNamespaceObject(JSContext * context,JSValue value)198 bool JS_IsModuleNamespaceObject(JSContext* context, JSValue value)
199 {
200 return false;
201 }
202
JS_IsProxy(JSContext * context,JSValue value)203 bool JS_IsProxy(JSContext* context, JSValue value)
204 {
205 return false;
206 }
207
JS_IsRegExp(JSContext * context,JSValue value)208 bool JS_IsRegExp(JSContext* context, JSValue value)
209 {
210 return false;
211 }
212
JS_IsNumberObject(JSContext * context,JSValue value)213 bool JS_IsNumberObject(JSContext* context, JSValue value)
214 {
215 return false;
216 }
217
JS_IsMap(JSContext * context,JSValue value)218 bool JS_IsMap(JSContext* context, JSValue value)
219 {
220 return false;
221 }
222
JS_IsSet(JSContext * context,JSValue value)223 bool JS_IsSet(JSContext* context, JSValue value)
224 {
225 return false;
226 }
227
JS_IsKeyObject(JSContext * context,JSValue value)228 bool JS_IsKeyObject(JSContext* context, JSValue value)
229 {
230 return false;
231 }
232
JS_IsBigInt64Array(JSContext * context,JSValue value)233 bool JS_IsBigInt64Array(JSContext* context, JSValue value)
234 {
235 return false;
236 }
237
JS_IsBigUint64Array(JSContext * context,JSValue value)238 bool JS_IsBigUint64Array(JSContext* context, JSValue value)
239 {
240 return false;
241 }
JS_IsSharedArrayBuffer(JSContext * context,JSValue value)242 bool JS_IsSharedArrayBuffer(JSContext* context, JSValue value)
243 {
244 return false;
245 }
246
JS_IsArrayBuffer(JSContext * context,JSValue value)247 bool JS_IsArrayBuffer(JSContext* context, JSValue value)
248 {
249 bool result = false;
250 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
251 JSValue name = JS_GetPropertyStr(context, constructor, "name");
252 const char* cName = JS_ToCString(context, name);
253 result = !strcmp("ArrayBuffer", cName ? cName : "");
254 JS_FreeCString(context, cName);
255 JS_FreeValue(context, name);
256 JS_FreeValue(context, constructor);
257
258 JSAtom key = JS_NewAtom(context, "napi_buffer");
259 bool hasPro = JS_HasProperty(context, value, key);
260 JS_FreeAtom(context, key);
261 return (result && !hasPro);
262 }
263
JS_IsBuffer(JSContext * context,JSValue value)264 bool JS_IsBuffer(JSContext* context, JSValue value)
265 {
266 bool result = false;
267 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
268 JSValue name = JS_GetPropertyStr(context, constructor, "name");
269 const char* cName = JS_ToCString(context, name);
270 result = !strcmp("ArrayBuffer", cName ? cName : "");
271 JS_FreeValue(context, name);
272 JS_FreeCString(context, cName);
273 JS_FreeValue(context, constructor);
274
275 JSAtom key = JS_NewAtom(context, "napi_buffer");
276 bool hasPro = JS_HasProperty(context, value, key);
277 JS_FreeAtom(context, key);
278
279 return (result && hasPro);
280 }
281
JS_IsDate(JSContext * context,JSValue value)282 bool JS_IsDate(JSContext* context, JSValue value)
283 {
284 bool result = false;
285 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
286 JSValue name = JS_GetPropertyStr(context, constructor, "name");
287 const char* cName = JS_ToCString(context, name);
288 result = !strcmp("Date", cName ? cName : "");
289 JS_FreeCString(context, cName);
290 JS_FreeValue(context, name);
291 JS_FreeValue(context, constructor);
292 return result;
293 }
294
JS_IsDataView(JSContext * context,JSValue value)295 bool JS_IsDataView(JSContext* context, JSValue value)
296 {
297 bool result = false;
298 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
299 JSValue name = JS_GetPropertyStr(context, constructor, "name");
300 const char* cName = JS_ToCString(context, name);
301 result = !strcmp("DataView", cName ? cName : "");
302 JS_FreeCString(context, cName);
303 JS_FreeValue(context, name);
304 JS_FreeValue(context, constructor);
305 return result;
306 }
307
JS_IsTypedArray(JSContext * context,JSValue value)308 bool JS_IsTypedArray(JSContext* context, JSValue value)
309 {
310 bool result = false;
311 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
312 JSValue name = JS_GetPropertyStr(context, constructor, "name");
313 const char* cName = JS_ToCString(context, name);
314 result = !strcmp("Uint8ClampedArray", cName ? cName : "") || !strcmp("Int8Array", cName ? cName : "") ||
315 !strcmp("Uint8Array", cName ? cName : "") || !strcmp("Int16Array", cName ? cName : "") ||
316 !strcmp("Uint16Array", cName ? cName : "") || !strcmp("Int32Array", cName ? cName : "") ||
317 !strcmp("Uint32Array", cName ? cName : "") || !strcmp("BigInt64Array", cName ? cName : "") ||
318 !strcmp("BigUint64Array", cName ? cName : "") || !strcmp("Float32Array", cName ? cName : "") ||
319 !strcmp("Float64Array", cName ? cName : "");
320
321 JS_FreeCString(context, cName);
322 JS_FreeValue(context, name);
323 JS_FreeValue(context, constructor);
324 return result;
325 }
326
JS_StrictEquals(JSContext * context,JSValue v1,JSValue v2)327 bool JS_StrictEquals(JSContext* context, JSValue v1, JSValue v2)
328 {
329 JSValue thisVar = JS_UNDEFINED;
330 JSValue argv[2] = { v1, v2 };
331 const char script[] = "(v1, v2) => v1 === v2;";
332 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
333 JSValue ret = JS_Call(context, func, thisVar, 2, (JSValue*)&argv);
334 bool result = JS_ToBool(context, ret);
335 JS_FreeValue(context, func);
336 JS_FreeValue(context, ret);
337 js_std_loop(context);
338
339 return result;
340 }
341
JS_StrictDate(JSContext * context,double time)342 JSValue JS_StrictDate(JSContext* context, double time)
343 {
344 JSValue thisVar = JS_UNDEFINED;
345 JSValue v1 = JS_NewInt64(context, (int64_t)time);
346 if (JS_IsException(v1)) {
347 JS_FreeValue(context, v1);
348 return JS_GetException(context);
349 }
350
351 JSValue argv[1] = { v1 };
352 const char script[] = " (v1) => new Date(v1); ";
353 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
354 JSValue ret = JS_Call(context, func, thisVar, 1, (JSValue*)&argv);
355 JS_FreeValue(context, func);
356
357 if (JS_IsException(ret)) {
358 JS_FreeValue(context, ret);
359 return JS_GetException(context);
360 }
361 return ret;
362 }
363
JS_CreateBigIntWords(JSContext * context,int signBit,size_t wordCount,const uint64_t * words)364 JSValue JS_CreateBigIntWords(JSContext* context, int signBit, size_t wordCount, const uint64_t* words)
365 {
366 JSValue thisVar = JS_UNDEFINED;
367 constexpr size_t Count = 20;
368 constexpr size_t Two = 2;
369 if (wordCount <= 0 || wordCount > Count || words == nullptr) {
370 HILOG_INFO("%{public}s called. Params is invalid or the BigInt too big.", __func__);
371 return JS_EXCEPTION;
372 }
373
374 JSValue signValue = JS_NewInt32(context, (signBit % Two));
375 if (JS_IsException(signValue)) {
376 HILOG_INFO("%{public}s called. Invoke JS_NewInt32 error.", __func__);
377 return JS_EXCEPTION;
378 }
379
380 JSValue wordsValue = JS_NewArray(context);
381 if (JS_IsException(wordsValue)) {
382 HILOG_INFO("%{public}s called. Invoke JS_NewArray error.", __func__);
383 return JS_EXCEPTION;
384 }
385
386 for (size_t i = 0; i < wordCount; i++) {
387 // shift 32 bits right to get high bit
388 JSValue idxValueHigh = JS_NewUint32(context, (uint32_t)(words[i] >> 32));
389 // gets lower 32 bits
390 JSValue idxValueLow = JS_NewUint32(context, (uint32_t)(words[i] & 0xFFFFFFFF));
391 if (!(JS_IsException(idxValueHigh)) && !(JS_IsException(idxValueLow))) {
392 JS_SetPropertyUint32(context, wordsValue, (i * Two), idxValueHigh);
393 JS_SetPropertyUint32(context, wordsValue, (i * Two + 1), idxValueLow);
394 JS_FreeValue(context, idxValueHigh);
395 JS_FreeValue(context, idxValueLow);
396 }
397 }
398
399 JSValue argv[2] = { signValue, wordsValue };
400 const char script[] = "(sign, word) => { "
401 " const max_v = BigInt(2 ** 64 - 1);"
402 " var bg = 0n;"
403 " for (var i=0; i<word.length/2; i++) {"
404 " bg = bg + (BigInt(word[i*2]) * 2n**32n + BigInt(word[i*2 +1])) * (max_v ** BigInt(i));"
405 " }"
406 " if (sign != 0) {"
407 " bg = bg * (-1n);"
408 " }"
409 " return bg;"
410 "};";
411
412 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
413 JSValue ret = JS_Call(context, func, thisVar, 2, (JSValue*)&argv);
414 JS_FreeValue(context, func);
415 JS_FreeValue(context, signValue);
416 JS_FreeValue(context, wordsValue);
417 return ret;
418 }
419
ParseBigIntWordsInternal(JSContext * context,JSValue value,int * signBit,size_t * wordCount,uint64_t * words)420 bool ParseBigIntWordsInternal(JSContext* context, JSValue value, int* signBit, size_t* wordCount, uint64_t* words)
421 {
422 int cntValue = 0;
423 if (wordCount == nullptr) {
424 return false;
425 }
426
427 JSValue jsValue = JS_GetPropertyStr(context, value, "count");
428 if (!JS_IsException(jsValue)) {
429 JS_ToInt32(context, &cntValue, jsValue);
430 JS_FreeValue(context, jsValue);
431 } else {
432 return false;
433 }
434
435 if (signBit == nullptr && words == nullptr) {
436 *wordCount = cntValue;
437 return true;
438 } else if (signBit != nullptr && words != nullptr) {
439 cntValue = (cntValue > static_cast<int>(*wordCount)) ? *wordCount : cntValue;
440 jsValue = JS_GetPropertyStr(context, value, "sign");
441 if (!JS_IsException(jsValue)) {
442 int sigValue = 0;
443 JS_ToInt32(context, &sigValue, jsValue);
444 *signBit = sigValue;
445 JS_FreeValue(context, jsValue);
446 }
447
448 jsValue = JS_GetPropertyStr(context, value, "words");
449 if (!JS_IsException(jsValue)) {
450 JSValue element;
451 int64_t cValue = 0;
452 for (uint32_t i = 0; i < (uint32_t)cntValue; i++) {
453 element = JS_GetPropertyUint32(context, jsValue, i);
454 JS_ToInt64Ext(context, &cValue, element);
455 JS_FreeValue(context, element);
456 words[i] = (uint64_t)cValue;
457 }
458 JS_FreeValue(context, jsValue);
459 *wordCount = cntValue;
460 return true;
461 }
462 }
463 return false;
464 }
465
JS_GetBigIntWords(JSContext * context,JSValue value,int * signBit,size_t * wordCount,uint64_t * words)466 bool JS_GetBigIntWords(JSContext* context, JSValue value, int* signBit, size_t* wordCount, uint64_t* words)
467 {
468 HILOG_INFO("%{public}s called. ", __func__);
469
470 bool rev = false;
471 JSValue thisVar = JS_UNDEFINED;
472 if (wordCount == nullptr) {
473 HILOG_INFO("%{public}s called. Params are invalid.", __func__);
474 return false;
475 }
476
477 const char script[] = "(big) => {"
478 "const max_v = BigInt(2 ** 64 - 1);"
479 "var rev = {};"
480 "rev.sign = 0;"
481 "rev.count = 0;"
482 "rev.words = [];"
483 "if (big < 0n) {"
484 " rev.sign = 1;"
485 " big = big * (-1n);"
486 "}"
487 "while (big >= max_v) {"
488 " rev.words[rev.count] = big % max_v;"
489 " big = big / max_v;"
490 " rev.count++;"
491 "}"
492 "rev.words[rev.count] = big % max_v;"
493 "rev.count++;"
494 "return rev;"
495 "}";
496
497 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
498 JSValue bigObj = JS_Call(context, func, thisVar, 1, &value);
499 if (!JS_IsException(bigObj)) {
500 if (JS_IsObject(bigObj)) {
501 rev = ParseBigIntWordsInternal(context, bigObj, signBit, wordCount, words);
502 } else {
503 HILOG_INFO("%{public}s called. JSValue is not Object. ", __func__);
504 }
505 }
506
507 JS_FreeValue(context, func);
508 JS_FreeValue(context, bigObj);
509 return rev;
510 }
511
JS_Freeze(JSContext * context,JSValue value)512 JSValue JS_Freeze(JSContext* context, JSValue value)
513 {
514 JSValue thisVar = JS_UNDEFINED;
515 const char script[] = "(obj) => Object.freeze(obj);";
516 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
517 JSValue ret = JS_Call(context, func, thisVar, 1, &value);
518 JS_FreeValue(context, func);
519 JS_FreeValue(context, ret);
520 return JS_DupValue(context, value);
521 }
522
JS_Seal(JSContext * context,JSValue value)523 JSValue JS_Seal(JSContext* context, JSValue value)
524 {
525 JSValue thisVar = JS_UNDEFINED;
526 const char script[] = "(obj) => Object.seal(obj);";
527 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
528 JSValue ret = JS_Call(context, func, thisVar, 1, &value);
529 JS_FreeValue(context, func);
530 JS_FreeValue(context, ret);
531 return JS_DupValue(context, value);
532 }
533
534 struct JS_BigFloatExt {
535 JSRefCountHeader header;
536 bf_t num;
537 };
538
JS_ToInt64WithBigInt(JSContext * context,JSValueConst value,int64_t * pres,bool * lossless)539 bool JS_ToInt64WithBigInt(JSContext* context, JSValueConst value, int64_t* pres, bool* lossless)
540 {
541 if (pres == nullptr || lossless == nullptr) {
542 HILOG_INFO("%{public}s called. Params are invalid.", __func__);
543 return false;
544 }
545
546 bool rev = false;
547 JSValue val = JS_DupValue(context, value);
548 JS_BigFloatExt* p = (JS_BigFloatExt*)JS_VALUE_GET_PTR(val);
549 if (p) {
550 int opFlag = bf_get_int64(pres, &p->num, 0);
551 if (lossless != nullptr) {
552 *lossless = (opFlag == 0);
553 }
554 rev = true;
555 }
556 JS_FreeValue(context, val);
557 return rev;
558 }
559
JS_ToUInt64WithBigInt(JSContext * context,JSValueConst value,uint64_t * pres,bool * lossless)560 bool JS_ToUInt64WithBigInt(JSContext* context, JSValueConst value, uint64_t* pres, bool* lossless)
561 {
562 if (pres == nullptr || lossless == nullptr) {
563 HILOG_INFO("%{public}s called. Params are invalid.", __func__);
564 return false;
565 }
566
567 bool rev = false;
568 JSValue val = JS_DupValue(context, value);
569 JS_BigFloatExt* p = (JS_BigFloatExt*)JS_VALUE_GET_PTR(val);
570 if (p) {
571 int opFlag = bf_get_uint64(pres, &p->num);
572 if (lossless != nullptr) {
573 *lossless = (opFlag == 0);
574 }
575 rev = true;
576 }
577 JS_FreeValue(context, val);
578 return rev;
579 }
580