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_IsArrayBuffer(JSContext * context,JSValue value)233 bool JS_IsArrayBuffer(JSContext* context, JSValue value)
234 {
235 bool result = false;
236 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
237 JSValue name = JS_GetPropertyStr(context, constructor, "name");
238 const char* cName = JS_ToCString(context, name);
239 result = !strcmp("ArrayBuffer", cName ? cName : "");
240 JS_FreeCString(context, cName);
241 JS_FreeValue(context, name);
242 JS_FreeValue(context, constructor);
243
244 JSAtom key = JS_NewAtom(context, "napi_buffer");
245 bool hasPro = JS_HasProperty(context, value, key);
246 JS_FreeAtom(context, key);
247 return (result && !hasPro);
248 }
249
JS_IsBuffer(JSContext * context,JSValue value)250 bool JS_IsBuffer(JSContext* context, JSValue value)
251 {
252 bool result = false;
253 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
254 JSValue name = JS_GetPropertyStr(context, constructor, "name");
255 const char* cName = JS_ToCString(context, name);
256 result = !strcmp("ArrayBuffer", cName ? cName : "");
257 JS_FreeValue(context, name);
258 JS_FreeCString(context, cName);
259 JS_FreeValue(context, constructor);
260
261 JSAtom key = JS_NewAtom(context, "napi_buffer");
262 bool hasPro = JS_HasProperty(context, value, key);
263 JS_FreeAtom(context, key);
264
265 return (result && hasPro);
266 }
267
JS_IsDate(JSContext * context,JSValue value)268 bool JS_IsDate(JSContext* context, JSValue value)
269 {
270 bool result = false;
271 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
272 JSValue name = JS_GetPropertyStr(context, constructor, "name");
273 const char* cName = JS_ToCString(context, name);
274 result = !strcmp("Date", cName ? cName : "");
275 JS_FreeCString(context, cName);
276 JS_FreeValue(context, name);
277 JS_FreeValue(context, constructor);
278 return result;
279 }
280
JS_IsDataView(JSContext * context,JSValue value)281 bool JS_IsDataView(JSContext* context, JSValue value)
282 {
283 bool result = false;
284 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
285 JSValue name = JS_GetPropertyStr(context, constructor, "name");
286 const char* cName = JS_ToCString(context, name);
287 result = !strcmp("DataView", cName ? cName : "");
288 JS_FreeCString(context, cName);
289 JS_FreeValue(context, name);
290 JS_FreeValue(context, constructor);
291 return result;
292 }
293
JS_IsTypedArray(JSContext * context,JSValue value)294 bool JS_IsTypedArray(JSContext* context, JSValue value)
295 {
296 bool result = false;
297 JSValue constructor = JS_GetPropertyStr(context, value, "constructor");
298 JSValue name = JS_GetPropertyStr(context, constructor, "name");
299 const char* cName = JS_ToCString(context, name);
300 result = !strcmp("Uint8ClampedArray", cName ? cName : "") || !strcmp("Int8Array", cName ? cName : "") ||
301 !strcmp("Uint8Array", cName ? cName : "") || !strcmp("Int16Array", cName ? cName : "") ||
302 !strcmp("Uint16Array", cName ? cName : "") || !strcmp("Int32Array", cName ? cName : "") ||
303 !strcmp("Uint32Array", cName ? cName : "") || !strcmp("BigInt64Array", cName ? cName : "") ||
304 !strcmp("BigUint64Array", cName ? cName : "") || !strcmp("Float32Array", cName ? cName : "") ||
305 !strcmp("Float64Array", cName ? cName : "");
306
307 JS_FreeCString(context, cName);
308 JS_FreeValue(context, name);
309 JS_FreeValue(context, constructor);
310 return result;
311 }
312
JS_StrictEquals(JSContext * context,JSValue v1,JSValue v2)313 bool JS_StrictEquals(JSContext* context, JSValue v1, JSValue v2)
314 {
315 JSValue thisVar = JS_UNDEFINED;
316 JSValue argv[2] = { v1, v2 };
317 const char script[] = "(v1, v2) => v1 === v2;";
318 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
319 JSValue ret = JS_Call(context, func, thisVar, 2, (JSValue*)&argv);
320 bool result = JS_ToBool(context, ret);
321 JS_FreeValue(context, func);
322 JS_FreeValue(context, ret);
323 js_std_loop(context);
324
325 return result;
326 }
327
JS_StrictDate(JSContext * context,double time)328 JSValue JS_StrictDate(JSContext* context, double time)
329 {
330 JSValue thisVar = JS_UNDEFINED;
331 JSValue v1 = JS_NewInt64(context, (int64_t)time);
332 if (JS_IsException(v1)) {
333 JS_FreeValue(context, v1);
334 return JS_GetException(context);
335 }
336
337 JSValue argv[1] = { v1 };
338 const char script[] = " (v1) => new Date(v1); ";
339 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
340 JSValue ret = JS_Call(context, func, thisVar, 1, (JSValue*)&argv);
341 JS_FreeValue(context, func);
342
343 if (JS_IsException(ret)) {
344 JS_FreeValue(context, ret);
345 return JS_GetException(context);
346 }
347 return ret;
348 }
349
JS_CreateBigIntWords(JSContext * context,int signBit,size_t wordCount,const uint64_t * words)350 JSValue JS_CreateBigIntWords(JSContext* context, int signBit, size_t wordCount, const uint64_t* words)
351 {
352 JSValue thisVar = JS_UNDEFINED;
353 constexpr size_t Count = 20;
354 constexpr size_t Two = 2;
355 if (wordCount <= 0 || wordCount > Count || words == nullptr) {
356 HILOG_INFO("%{public}s called. Params is invalid or the BigInt too big.", __func__);
357 return JS_EXCEPTION;
358 }
359
360 JSValue signValue = JS_NewInt32(context, (signBit % Two));
361 if (JS_IsException(signValue)) {
362 HILOG_INFO("%{public}s called. Invoke JS_NewInt32 error.", __func__);
363 return JS_EXCEPTION;
364 }
365
366 JSValue wordsValue = JS_NewArray(context);
367 if (JS_IsException(wordsValue)) {
368 HILOG_INFO("%{public}s called. Invoke JS_NewArray error.", __func__);
369 return JS_EXCEPTION;
370 }
371
372 for (size_t i = 0; i < wordCount; i++) {
373 // shift 32 bits right to get high bit
374 JSValue idxValueHigh = JS_NewUint32(context, (uint32_t)(words[i] >> 32));
375 // gets lower 32 bits
376 JSValue idxValueLow = JS_NewUint32(context, (uint32_t)(words[i] & 0xFFFFFFFF));
377 if (!(JS_IsException(idxValueHigh)) && !(JS_IsException(idxValueLow))) {
378 JS_SetPropertyUint32(context, wordsValue, (i * Two), idxValueHigh);
379 JS_SetPropertyUint32(context, wordsValue, (i * Two + 1), idxValueLow);
380 JS_FreeValue(context, idxValueHigh);
381 JS_FreeValue(context, idxValueLow);
382 }
383 }
384
385 JSValue argv[2] = { signValue, wordsValue };
386 const char script[] = "(sign, word) => { "
387 " const max_v = BigInt(2 ** 64 - 1);"
388 " var bg = 0n;"
389 " for (var i=0; i<word.length/2; i++) {"
390 " bg = bg + (BigInt(word[i*2]) * 2n**32n + BigInt(word[i*2 +1])) * (max_v ** BigInt(i));"
391 " }"
392 " if (sign != 0) {"
393 " bg = bg * (-1n);"
394 " }"
395 " return bg;"
396 "};";
397
398 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
399 JSValue ret = JS_Call(context, func, thisVar, 2, (JSValue*)&argv);
400 JS_FreeValue(context, func);
401 JS_FreeValue(context, signValue);
402 JS_FreeValue(context, wordsValue);
403 return ret;
404 }
405
ParseBigIntWordsInternal(JSContext * context,JSValue value,int * signBit,size_t * wordCount,uint64_t * words)406 bool ParseBigIntWordsInternal(JSContext* context, JSValue value, int* signBit, size_t* wordCount, uint64_t* words)
407 {
408 int cntValue = 0;
409 if (wordCount == nullptr) {
410 return false;
411 }
412
413 JSValue jsValue = JS_GetPropertyStr(context, value, "count");
414 if (!JS_IsException(jsValue)) {
415 JS_ToInt32(context, &cntValue, jsValue);
416 JS_FreeValue(context, jsValue);
417 } else {
418 return false;
419 }
420
421 if (signBit == nullptr && words == nullptr) {
422 *wordCount = cntValue;
423 return true;
424 } else if (signBit != nullptr && words != nullptr) {
425 cntValue = (cntValue > *wordCount) ? *wordCount : cntValue;
426 jsValue = JS_GetPropertyStr(context, value, "sign");
427 if (!JS_IsException(jsValue)) {
428 int sigValue = 0;
429 JS_ToInt32(context, &sigValue, jsValue);
430 *signBit = sigValue;
431 JS_FreeValue(context, jsValue);
432 }
433
434 jsValue = JS_GetPropertyStr(context, value, "words");
435 if (!JS_IsException(jsValue)) {
436 JSValue element;
437 int64_t cValue = 0;
438 for (uint32_t i = 0; i < (uint32_t)cntValue; i++) {
439 element = JS_GetPropertyUint32(context, jsValue, i);
440 JS_ToInt64Ext(context, &cValue, element);
441 JS_FreeValue(context, element);
442 words[i] = (uint64_t)cValue;
443 }
444 JS_FreeValue(context, jsValue);
445 *wordCount = cntValue;
446 return true;
447 }
448 }
449 return false;
450 }
451
JS_GetBigIntWords(JSContext * context,JSValue value,int * signBit,size_t * wordCount,uint64_t * words)452 bool JS_GetBigIntWords(JSContext* context, JSValue value, int* signBit, size_t* wordCount, uint64_t* words)
453 {
454 HILOG_INFO("%{public}s called. ", __func__);
455
456 bool rev = false;
457 JSValue thisVar = JS_UNDEFINED;
458 if (wordCount == nullptr) {
459 HILOG_INFO("%{public}s called. Params are invalid.", __func__);
460 return false;
461 }
462
463 const char script[] = "(big) => {"
464 "const max_v = BigInt(2 ** 64 - 1);"
465 "var rev = {};"
466 "rev.sign = 0;"
467 "rev.count = 0;"
468 "rev.words = [];"
469 "if (big < 0n) {"
470 " rev.sign = 1;"
471 " big = big * (-1n);"
472 "}"
473 "while (big >= max_v) {"
474 " rev.words[rev.count] = big % max_v;"
475 " big = big / max_v;"
476 " rev.count++;"
477 "}"
478 "rev.words[rev.count] = big % max_v;"
479 "rev.count++;"
480 "return rev;"
481 "}";
482
483 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
484 JSValue bigObj = JS_Call(context, func, thisVar, 1, &value);
485 if (!JS_IsException(bigObj)) {
486 if (JS_IsObject(bigObj)) {
487 rev = ParseBigIntWordsInternal(context, bigObj, signBit, wordCount, words);
488 } else {
489 HILOG_INFO("%{public}s called. JSValue is not Object. ", __func__);
490 }
491 }
492
493 JS_FreeValue(context, func);
494 JS_FreeValue(context, bigObj);
495 return rev;
496 }
497
JS_Freeze(JSContext * context,JSValue value)498 JSValue JS_Freeze(JSContext* context, JSValue value)
499 {
500 JSValue thisVar = JS_UNDEFINED;
501 const char script[] = "(obj) => Object.freeze(obj);";
502 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
503 JSValue ret = JS_Call(context, func, thisVar, 1, &value);
504 JS_FreeValue(context, func);
505 JS_FreeValue(context, ret);
506 return JS_DupValue(context, value);
507 }
508
JS_Seal(JSContext * context,JSValue value)509 JSValue JS_Seal(JSContext* context, JSValue value)
510 {
511 JSValue thisVar = JS_UNDEFINED;
512 const char script[] = "(obj) => Object.seal(obj);";
513 JSValue func = JS_Eval(context, script, strlen(script), "<input>", JS_EVAL_TYPE_GLOBAL);
514 JSValue ret = JS_Call(context, func, thisVar, 1, &value);
515 JS_FreeValue(context, func);
516 JS_FreeValue(context, ret);
517 return JS_DupValue(context, value);
518 }
519
520 struct JS_BigFloatExt {
521 JSRefCountHeader header;
522 bf_t num;
523 };
524
JS_ToInt64WithBigInt(JSContext * context,JSValueConst value,int64_t * pres,bool * lossless)525 bool JS_ToInt64WithBigInt(JSContext* context, JSValueConst value, int64_t* pres, bool* lossless)
526 {
527 if (pres == nullptr || lossless == nullptr) {
528 HILOG_INFO("%{public}s called. Params are invalid.", __func__);
529 return false;
530 }
531
532 bool rev = false;
533 JSValue val = JS_DupValue(context, value);
534 JS_BigFloatExt* p = (JS_BigFloatExt*)JS_VALUE_GET_PTR(val);
535 if (p) {
536 int opFlag = bf_get_int64(pres, &p->num, BF_GET_INT_MOD);
537 if (lossless != nullptr) {
538 *lossless = (opFlag == 0);
539 }
540 rev = true;
541 }
542 JS_FreeValue(context, val);
543 return rev;
544 }
545
JS_ToUInt64WithBigInt(JSContext * context,JSValueConst value,uint64_t * pres,bool * lossless)546 bool JS_ToUInt64WithBigInt(JSContext* context, JSValueConst value, uint64_t* pres, bool* lossless)
547 {
548 if (pres == nullptr || lossless == nullptr) {
549 HILOG_INFO("%{public}s called. Params are invalid.", __func__);
550 return false;
551 }
552
553 bool rev = false;
554 JSValue val = JS_DupValue(context, value);
555 JS_BigFloatExt* p = (JS_BigFloatExt*)JS_VALUE_GET_PTR(val);
556 if (p) {
557 int opFlag = bf_get_uint64(pres, &p->num);
558 if (lossless != nullptr) {
559 *lossless = (opFlag == 0);
560 }
561 rev = true;
562 }
563 JS_FreeValue(context, val);
564 return rev;
565 }