diff --git a/BUILD.gn b/BUILD.gn
index fee6ad88af7026b60e5ae697a343153985905969..51e758f443f695266e35256ea9144e5719099677 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -376,8 +376,7 @@ config("ark_jsruntime_common_config") {
}
}
- if (!is_mac && target_os != "ios" && !use_libfuzzer &&
- !(ark_standalone_build && !enable_lto)) {
+ if (!is_mac && target_os != "ios" && !use_libfuzzer && !enable_lto_O0) {
cflags_cc += [ "-flto=thin" ]
ldflags += [ "-flto=thin" ]
}
@@ -454,7 +453,7 @@ config("ark_jsruntime_common_config") {
}
}
- if (enable_leak_check) {
+ if (enable_leak_check || is_asan) {
defines += [ "ECMASCRIPT_ENABLE_HANDLE_LEAK_CHECK" ]
}
@@ -466,6 +465,11 @@ config("ark_jsruntime_common_config") {
defines += [ "HOOK_ENABLE" ]
}
}
+
+ # is_asan: skynet config; run_with_asan: est_runtime enable asan config
+ if (is_asan) {
+ defines += [ "ECMASCRIPT_ENABLE_DFX_CONFIG" ]
+ }
}
# ecmascript unit testcase config
@@ -480,7 +484,7 @@ config("ecma_test_config") {
"$js_root:ark_jsruntime_common_config",
]
- if (enable_leak_check) {
+ if (enable_leak_check || is_asan) {
defines = [ "ECMASCRIPT_ENABLE_HANDLE_LEAK_CHECK" ]
}
@@ -510,9 +514,12 @@ ecma_source = [
"ecmascript/base/atomic_helper.cpp",
"ecmascript/base/builtins_base.cpp",
"ecmascript/base/error_helper.cpp",
+ "ecmascript/base/fast_json_stringifier.cpp",
+ "ecmascript/base/json_helper.cpp",
"ecmascript/base/json_parser.cpp",
"ecmascript/base/json_stringifier.cpp",
"ecmascript/base/number_helper.cpp",
+ "ecmascript/base/path_helper.cpp",
"ecmascript/base/string_helper.cpp",
"ecmascript/base/typed_array_helper.cpp",
"ecmascript/base/utf_helper.cpp",
@@ -598,6 +605,7 @@ ecma_source = [
"ecmascript/ecma_string.cpp",
"ecmascript/ecma_string_table.cpp",
"ecmascript/ecma_vm.cpp",
+ "ecmascript/elements.cpp",
"ecmascript/frames.cpp",
"ecmascript/free_object.cpp",
"ecmascript/generator_helper.cpp",
@@ -725,17 +733,24 @@ ecma_source = [
"ecmascript/module/js_module_namespace.cpp",
"ecmascript/module/js_module_record.cpp",
"ecmascript/module/js_module_source_text.cpp",
+ "ecmascript/module/js_module_deregister.cpp",
"ecmascript/module/module_data_extractor.cpp",
+ "ecmascript/module/module_path_helper.cpp",
"ecmascript/napi/jsnapi.cpp",
"ecmascript/object_factory.cpp",
"ecmascript/object_operator.cpp",
"ecmascript/patch/patch_loader.cpp",
"ecmascript/patch/quick_fix_manager.cpp",
+ "ecmascript/pgo_profiler/ap_file/pgo_file_info.cpp",
"ecmascript/pgo_profiler/pgo_profiler.cpp",
"ecmascript/pgo_profiler/pgo_profiler_decoder.cpp",
"ecmascript/pgo_profiler/pgo_profiler_encoder.cpp",
"ecmascript/pgo_profiler/pgo_profiler_info.cpp",
"ecmascript/pgo_profiler/pgo_profiler_layout.cpp",
+ "ecmascript/pgo_profiler/pgo_profiler_manager.cpp",
+ "ecmascript/pgo_profiler/pgo_utils.cpp",
+ "ecmascript/pgo_profiler/ap_file/pgo_method_type_set.cpp",
+ "ecmascript/pgo_profiler/types/pgo_profile_type.cpp",
"ecmascript/stackmap/ark_stackmap_builder.cpp",
"ecmascript/stackmap/ark_stackmap_parser.cpp",
"ecmascript/stackmap/llvm_stackmap_parser.cpp",
@@ -784,6 +799,7 @@ if (is_ohos && is_standard_system && enable_hitrace) {
ecma_debugger_source = [
"ecmascript/debugger/debugger_api.cpp",
"ecmascript/debugger/js_debugger.cpp",
+ "ecmascript/debugger/dropframe_manager.cpp",
"ecmascript/debugger/hot_reload_manager.cpp",
]
@@ -1038,7 +1054,7 @@ ohos_source_set("libark_jsruntime_test_set") {
deps += [ "$js_root/ecmascript/compiler:libark_mock_stub_set" ]
}
- if (enable_leak_check) {
+ if (enable_leak_check || is_asan) {
defines += [ "ECMASCRIPT_ENABLE_HANDLE_LEAK_CHECK" ]
}
diff --git a/docs/README_zh.md b/docs/README_zh.md
index 037e4173ea5c8db25e61fbfcb653668e65dd96ed..a7bf762b8c4d55f22ac459954167ac5bb74339d6 100644
--- a/docs/README_zh.md
+++ b/docs/README_zh.md
@@ -106,17 +106,9 @@ print("Hello World!!!");
1. 通过方舟前端生成hello-world.abc文件,编译命令:
```
- node --expose-gc /your_code_path/out/rk3568/clang_x64/arkcompiler/ets_frontend/build/src/index.js hello-world.js
+ /your_code_path/out/rk3568/clang_x64/arkcompiler/ets_frontend/es2abc hello-world.js
```
- **注意**:使用node编译abc过程遇到ENOENT错误,运行如下命令进行修复
-
- ```
- npm cache clean --force
- cd /your_code_path/arkcompiler/ets_frontend/ts2panda
- npm install
- cd /your_code_path/out/rk3568/clang_x64/arkcompiler/ets_frontend/build
- npm install
- ```
+
2. 执行hello-world.abc文件:
1. 设置搜索路径:
@@ -400,7 +392,7 @@ print('Hello World!!!')
1. 通过方舟前端生成hello-world.abc文件,编译命令:
```
- node --expose-gc /your_code_path/out/rk3568/clang_x64/arkcompiler/ets_frontend/build/src/index.js -m --merge-abc test1/test.ts
+ /your_code_path/out/rk3568/clang_x64/arkcompiler/ets_frontend/es2abc --module --merge-abc test1/test.ts
```
2. 执行hello-world.abc文件:
@@ -439,136 +431,151 @@ print('Hello World!!!')
构建编译:
```
-$ ./build.sh --product-name rk3568 --build-target ark_ts2abc_build
+$ ./build.sh --product-name rk3568 --build-target ets_frontend_build
```
-安装 `node`和 `npm`后, 使用前端工具:
-
```
-$ cd out/rk3568/clang_x64/arkcompiler/ets_frontend/build
-$ npm install
-$ node --expose-gc src/index.js [选项] file.js
+$ cd out/rk3568/clang_x64/arkcompiler/ets_frontend/
+$ ./es2abc [options] file.js
```
-
选项
- |
-缩写
+
+
+选项
|
-描述
+ | 描述
|
-取值范围
+ | 取值范围
|
-默认值
+ | 默认值
|
---modules
+ | --debug-info
|
--m
+ | 携带debug信息
|
-按照Module方式编译
+ | -
|
--
- |
--
+ | -
|
---debug-log
- |
--l
+ | --debugger-evaluate-expression
|
-使能log信息
+ | debugger下对输入的base64形式的表达式求值
|
--
+ | -
|
--
+ | -
|
---dump-assembly
- |
--a
+ | --dump-assembly
|
-输出为可读文本格式的字节码文件
+ | 输出为汇编文件
|
--
+ | -
|
--
+ | -
|
---debug
+ | --dump-ast
|
--d
+ | 打印解析得到的ast(抽象语法树)
|
-携带debug信息
+ | -
|
--
- |
--
+ | -
|
---show-statistics
- |
--s
+ | --dump-debug-info
|
-显示字节码相关的统计信息
+ | 打印debug信息
|
--
+ | -
|
--
+ | -
|
---output
+ | --dump-literal-buffer
+ |
+打印literal buffer内容
|
--o
+ | -
+ |
+-
+ |
+
+--dump-size-stat
|
-输出文件路径
+ | 显示字节码相关的统计信息
|
--
+ | -
|
--
+ | -
|
---timeout
+ | --extension
|
--t
+ | 指定输入类型
|
-超时门限
+ | ['js', 'ts', 'as']
|
--
+ | -
|
--
+ |
+--help
+ |
+帮助提示
+ |
+-
+ |
+-
|
---help
+ | --module
+ |
+按照ESM模式编译
+ |
+-
|
--h
+ | -
|
-帮助提示
+ |
+--opt-level
+ |
+指定编译优化等级
|
--
+ | ['0', '1', '2']
|
--
+ | 0
|
---bc-version
+ | --output
+ |
+
+输出文件路径
|
--v
+ | -
+ |
+-
+ |
+
+--parse-only
|
-输出当前字节码版本
+ | 只对输入文件做解析动作
|
--
+ | -
|
--
+ | -
|
---bc-min-version
+ | --thread
|
- |
-输出支持的最低字节码版本
+ | 指定生成字节码时所用的线程数目
|
--
+ | 0-机器支持的线程数目
|
--
+ | 0
|
diff --git a/docs/development-example.md b/docs/development-example.md
index 4fc6e27681fcaf0f7d541c05706645bcde2ff848..c8ee078d03e02cc03cb720233b94e7ae1a988f6c 100644
--- a/docs/development-example.md
+++ b/docs/development-example.md
@@ -48,7 +48,7 @@ Run the **hello-world.js** file.
1. Use the ARK frontend to create the **hello-world.abc** file.
```
- node --expose-gc /your_code_path/out/rk3568/clang_x64/arkcompiler/ets_frontend/build/src/index.js hello-world.js
+ /your_code_path/out/rk3568/clang_x64/arkcompiler/ets_frontend/es2abc hello-world.js
```
2. Run the **hello-world.abc** file.
diff --git a/docs/using-the-toolchain.md b/docs/using-the-toolchain.md
index d487322f813bd9890ac8a9b75f25af36e467eef1..2b0f65c3dfe5d80e4eeb96bc105aef73c121747b 100644
--- a/docs/using-the-toolchain.md
+++ b/docs/using-the-toolchain.md
@@ -9,137 +9,153 @@ Front-end tools, converting JS source code into ARK bytecode, can be built by sp
Build tools:
```
-$ $ ./build.sh --product-name rk3568 --build-target ark_ts2abc_build
+$ $ ./build.sh --product-name rk3568 --build-target ets_frontend_build
```
-Install `node` and `npm`, then use tools:
-
```
-$ cd out/rk3568/clang_x64/arkcompiler/ets_frontend/build
-$ npm install
-$ node --expose-gc src/index.js [option] file.js
+$ cd out/rk3568/clang_x64/arkcompiler/ets_frontend/
+$ ./es2abc [options] file.js
```
-
-Option
- |
-Abbreviation
+If no parameter is specified for **\[options\]**, an ARK binary file is generated by default.
+
+
+
+Option
|
-Description
+ | Description
|
-Value Range
+ | Value Range
|
-Default Value
+ | Default Value
|
---modules
+ | --debug-info
|
--m
+ | Provides debug information.
|
-Compiles JS files based on the module.
+ | -
|
--
- |
--
+ | -
|
---debug-log
- |
--l
+ | --debugger-evaluate-expression
|
-Enables the log function.
+ | Evaluates base64 style expression in debugger
|
--
+ | -
|
--
+ | -
|
---dump-assembly
- |
--a
+ | --dump-assembly
|
-Outputs a text ARK bytecode file.
+ | Outputs an assembly file.
|
--
+ | -
|
--
+ | -
|
---debug
+ | --dump-ast
|
--d
+ | Prints the parsed AST(Abstract Syntax Tree)
|
-Provides debug information.
+ | -
|
--
- |
--
+ | -
|
---show-statistics
- |
--s
+ | --dump-debug-info
|
-Displays statistics about bytecodes.
+ | Prints debug Info
|
--
+ | -
|
--
+ | -
|
---output
+ | --dump-literal-buffer
+ |
+Prints the content of literal buffer
|
--o
+ | -
+ |
+-
+ |
+
+--dump-size-stat
|
-Specifies the path of the output file.
+ | Displays statistics about bytecodes.
|
--
+ | -
|
--
+ | -
|
---timeout
+ | --extension
|
--t
+ | Specifies input file type
|
-Specifies the timeout threshold.
+ | ['js', 'ts', 'as']
|
--
+ | -
|
--
+ |
+--help
+ |
+Displays help information.
+ |
+-
+ |
+-
|
---help
+ | --module
+ |
+Compiles the code based on the ecmascript standard module.
+ |
+-
|
--h
+ | -
|
-Displays help information.
+ |
+--opt-level
+ |
+Specifies the level for compilation optimization.
|
--
+ | ['0', '1', '2']
|
--
+ | 0
|
---bc-version
+ | --output
+ |
+
+Specifies the path of the output file.
|
--v
+ | -
+ |
+-
+ |
+
+--parse-only
|
-Outputs the current bytecode version.
+ | Parse the input file only
|
--
+ | -
|
--
+ | -
|
---bc-min-version
+ | --thread
|
- |
-Outputs the lowest bytecode version supported.
+ | Specifies the number of threads used to generate bytecode
|
--
+ | 0-Number of threads supported by your machine
|
--
+ | 0
|
diff --git a/ecmascript/base/array_helper.cpp b/ecmascript/base/array_helper.cpp
index 68fe58c692b56d0886fa3f07851d500696037065..8d85084f274d0a7da10c7790f4ffed93c0395d43 100644
--- a/ecmascript/base/array_helper.cpp
+++ b/ecmascript/base/array_helper.cpp
@@ -24,8 +24,104 @@
#include "ecmascript/js_hclass.h"
#include "ecmascript/js_tagged_number.h"
#include "ecmascript/js_tagged_value-inl.h"
+#include "ecmascript/object_fast_operator-inl.h"
namespace panda::ecmascript::base {
+int64_t ArrayHelper::GetStartIndex(JSThread *thread, const JSHandle &startIndexHandle,
+ int64_t length)
+{
+ // Common procedure to clamp fromIndexValue to the range [0, length].
+ // For integer case, conditional selection instructions (csel in ARM, cmov in x86, etc.)
+ // may be utilized by the compiler to minimize branching.
+ auto doClamp = [length](auto fromIndexValue) -> int64_t {
+ if (LIKELY(fromIndexValue >= 0)) {
+ // Including the case where fromIndexValue == Infinity
+ return (fromIndexValue >= length) ? length : static_cast(fromIndexValue);
+ }
+ auto plusLength = fromIndexValue + length;
+ if (plusLength >= 0) {
+ return static_cast(plusLength);
+ }
+ return 0; // Including the case where fromIndexValue == -Infinity
+ };
+ if (LIKELY(startIndexHandle->IsInt())) {
+ // Fast path: startIndexHandle is tagged int32.
+ return doClamp(startIndexHandle->GetInt());
+ }
+ // Slow path: startIndexHandle is targged double, or type conversion is involved.
+ JSTaggedNumber fromIndexTemp = JSTaggedValue::ToNumber(thread, startIndexHandle);
+ if (UNLIKELY(thread->HasPendingException())) {
+ return length;
+ }
+ double fromIndexValue = base::NumberHelper::TruncateDouble(fromIndexTemp.GetNumber()); // NaN -> 0
+ return doClamp(fromIndexValue);
+}
+
+int64_t ArrayHelper::GetStartIndexFromArgs(JSThread *thread, EcmaRuntimeCallInfo *argv,
+ uint32_t argIndex, int64_t length)
+{
+ uint32_t argc = argv->GetArgsNumber();
+ if (argc <= argIndex) {
+ return 0;
+ }
+ JSHandle arg = base::BuiltinsBase::GetCallArg(argv, argIndex);
+ return GetStartIndex(thread, arg, length);
+}
+
+int64_t ArrayHelper::GetLastStartIndex(JSThread *thread, const JSHandle &startIndexHandle,
+ int64_t length)
+{
+ // Common procedure to clamp fromIndexValue to the range [-1, length-1].
+ auto doClamp = [length](auto fromIndexValue) -> int64_t {
+ if (LIKELY(fromIndexValue >= 0)) {
+ // Including the case where fromIndexValue == Infinity
+ return (length - 1 < fromIndexValue) ? (length - 1) : static_cast(fromIndexValue);
+ }
+ auto plusLength = fromIndexValue + length;
+ if (plusLength >= 0) {
+ return static_cast(plusLength);
+ }
+ return -1; // Including the case where fromIndexValue == -Infinity
+ };
+ if (LIKELY(startIndexHandle->IsInt())) {
+ // Fast path: startIndexHandle is tagged int32.
+ return doClamp(startIndexHandle->GetInt());
+ }
+ // Slow path: startIndexHandle is targged double, or type conversion is involved.
+ JSTaggedNumber fromIndexTemp = JSTaggedValue::ToNumber(thread, startIndexHandle);
+ if (UNLIKELY(thread->HasPendingException())) {
+ return -1;
+ }
+ double fromIndexValue = base::NumberHelper::TruncateDouble(fromIndexTemp.GetNumber()); // NaN -> 0
+ return doClamp(fromIndexValue);
+}
+
+int64_t ArrayHelper::GetLastStartIndexFromArgs(JSThread *thread, EcmaRuntimeCallInfo *argv,
+ uint32_t argIndex, int64_t length)
+{
+ uint32_t argc = argv->GetArgsNumber();
+ if (argc <= argIndex) {
+ return length - 1;
+ }
+ JSHandle arg = base::BuiltinsBase::GetCallArg(argv, argIndex);
+ return GetLastStartIndex(thread, arg, length);
+}
+
+bool ArrayHelper::ElementIsStrictEqualTo(JSThread *thread, const JSHandle &thisObjVal,
+ const JSHandle &keyHandle,
+ const JSHandle &target)
+{
+ bool exists = thisObjVal->IsTypedArray() || JSTaggedValue::HasProperty(thread, thisObjVal, keyHandle);
+ if (thread->HasPendingException() || !exists) {
+ return false;
+ }
+ JSHandle valueHandle = JSArray::FastGetPropertyByValue(thread, thisObjVal, keyHandle);
+ if (thread->HasPendingException()) {
+ return false;
+ }
+ return JSTaggedValue::StrictEqual(thread, target, valueHandle);
+}
+
bool ArrayHelper::IsConcatSpreadable(JSThread *thread, const JSHandle &obj)
{
// 1. If Type(O) is not Object, return false.
@@ -37,19 +133,22 @@ bool ArrayHelper::IsConcatSpreadable(JSThread *thread, const JSHandleGetEcmaVM();
JSHandle env = ecmaVm->GetGlobalEnv();
JSHandle isConcatsprKey = env->GetIsConcatSpreadableSymbol();
- JSHandle spreadable = JSTaggedValue::GetProperty(thread, obj, isConcatsprKey).GetValue();
+ JSTaggedValue spreadable = ObjectFastOperator::FastGetPropertyByValue(thread, obj.GetTaggedValue(),
+ isConcatsprKey.GetTaggedValue());
// 3. ReturnIfAbrupt(spreadable).
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
// 4. If spreadable is not undefined, return ToBoolean(spreadable).
- if (!spreadable->IsUndefined()) {
- return spreadable->ToBoolean();
+ if (!spreadable.IsUndefined()) {
+ return spreadable.ToBoolean();
}
// 5. Return IsArray(O).
return obj->IsArray(thread);
}
+// must use 'double' as return type, for sort result may double.
+// let arr = [1,2,3,4,5,6]; arr.sort(() => Math.random() - 0.5);
double ArrayHelper::SortCompare(JSThread *thread, const JSHandle &callbackfnHandle,
const JSHandle &valueX, const JSHandle &valueY)
{
@@ -86,10 +185,10 @@ double ArrayHelper::SortCompare(JSThread *thread, const JSHandle
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
info->SetCallArg(valueX.GetTaggedValue(), valueY.GetTaggedValue());
JSTaggedValue callResult = JSFunction::Call(info);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
if (callResult.IsInt()) {
return callResult.GetInt();
}
- RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
JSHandle testResult(thread, callResult);
JSTaggedNumber v = JSTaggedValue::ToNumber(thread, testResult);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
@@ -111,7 +210,33 @@ double ArrayHelper::SortCompare(JSThread *thread, const JSHandle
JSHandle yValueHandle(JSTaggedValue::ToString(thread, valueY));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, 0);
ComparisonResult compareResult = JSTaggedValue::Compare(thread, xValueHandle, yValueHandle);
- return compareResult == ComparisonResult::GREAT ? 1 : 0;
+ if (compareResult == ComparisonResult::GREAT) {
+ return 1;
+ }
+ if (compareResult == ComparisonResult::LESS) {
+ return -1;
+ }
+ return 0;
+}
+
+double ArrayHelper::StringSortCompare(JSThread *thread, const JSHandle &valueX,
+ const JSHandle &valueY)
+{
+ ASSERT(valueX->IsString());
+ ASSERT(valueY->IsString());
+ // 9. If xString < yString, return -1.
+ // 10. If xString > yString, return 1.
+ // 11. Return +0.
+ auto xHandle = JSHandle(valueX);
+ auto yHandle = JSHandle(valueY);
+ int result = EcmaStringAccessor::Compare(thread->GetEcmaVM(), xHandle, yHandle);
+ if (result < 0) {
+ return -1;
+ }
+ if (result > 0) {
+ return 1;
+ }
+ return 0;
}
int64_t ArrayHelper::GetLength(JSThread *thread, const JSHandle &thisHandle)
@@ -187,6 +312,7 @@ JSTaggedValue ArrayHelper::FlattenIntoArray(JSThread *thread, const JSHandle &thisObj,
+ int64_t len, const JSHandle &callbackFnHandle,
+ HolesType holes)
+{
+ // 1. Let items be a new empty List.
+ JSHandle items(thread->GetEcmaVM()->GetFactory()->NewTaggedArray(len));
+ // 2. Let k be 0.
+ int64_t k = 0;
+ // 3. Repeat, while k < len,
+ // a. Let Pk be ! ToString(𝔽(k)).
+ // b. If holes is skip-holes, then
+ // i. Let kRead be ? HasProperty(obj, Pk).
+ // c. Else,
+ // i. Assert: holes is read-through-holes.
+ // ii. Let kRead be true.
+ // d. If kRead is true, then
+ // i. Let kValue be ? Get(obj, Pk).
+ // ii. Append kValue to items.
+ // e. Set k to k + 1.
+ bool kRead = false;
+ JSHandle thisObjVal(thisObj);
+ JSMutableHandle pk(thread, JSTaggedValue::Undefined());
+
+ while (k < len) {
+ if (holes == HolesType::SKIP_HOLES) {
+ pk.Update(JSTaggedValue(k));
+ kRead = JSTaggedValue::HasProperty(thread, thisObjVal, pk);
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
+ } else {
+ ASSERT(holes == HolesType::READ_THROUGH_HOLES);
+ kRead = true;
+ }
+ if (kRead) {
+ JSHandle kValue = JSArray::FastGetPropertyByValue(thread, thisObjVal, k);
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
+ items->Set(thread, k, kValue.GetTaggedValue());
+ }
+ ++k;
+ }
+ JSHandle array(JSArray::CreateArrayFromList(thread, items));
+ JSHandle arrayObj = JSHandle::Cast(array);
+ // 4. Sort items using an implementation-defined sequence of calls to SortCompare.
+ // If any such call returns an abrupt completion,
+ // stop before performing any further calls to SortCompare and return that Completion Record.
+ JSArray::Sort(thread, arrayObj, callbackFnHandle);
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
+ // 5. Return items.
+ return arrayObj.GetTaggedValue();
+}
} // namespace panda::ecmascript::base
diff --git a/ecmascript/base/array_helper.h b/ecmascript/base/array_helper.h
index 1461c246cd9d96ff6829d1cdd799e26fe4402377..cf12a07a821a831178ab8c728dd8fb2961e388e6 100644
--- a/ecmascript/base/array_helper.h
+++ b/ecmascript/base/array_helper.h
@@ -19,6 +19,8 @@
#include
#include "ecmascript/base/builtins_base.h"
+#include "ecmascript/ecma_runtime_call_info.h"
+#include "ecmascript/js_tagged_value.h"
namespace panda::ecmascript::base {
struct FlattenArgs {
@@ -26,17 +28,51 @@ struct FlattenArgs {
int64_t start = 0;
double depth = 0;
};
+
+enum class HolesType {
+ SKIP_HOLES,
+ READ_THROUGH_HOLES,
+};
class ArrayHelper {
public:
+ // Common subprocedure for Array.prototype.at, Array.prototype.indexOf, Array.prototype.slice, etc.
+ // Gets start index that falls in range [0, length].
+ // length is returned on pending exception.
+ static int64_t GetStartIndex(JSThread *thread, const JSHandle &startIndexHandle,
+ int64_t length);
+ // If argIndex is out of range [0, argc), then start index = 0 by default.
+ // Otherwise, let startIndexHandle = GetCallArg(argv, argIndex) and call GetStartIndex.
+ static int64_t GetStartIndexFromArgs(JSThread *thread, EcmaRuntimeCallInfo *argv,
+ uint32_t argIndex, int64_t length);
+ // Common subprocedure for Array.prototype.lastIndexOf, etc.
+ // Gets last start index that falls in range [-1, length - 1].
+ // -1 is returned on pending exception.
+ static int64_t GetLastStartIndex(JSThread *thread, const JSHandle &startIndexHandle,
+ int64_t length);
+ // If argIndex is out of range [0, argc), then start index = length - 1 by default.
+ // Otherwise, let startIndexHandle = GetCallArg(argv, argIndex) and call GetLastStartIndex.
+ static int64_t GetLastStartIndexFromArgs(JSThread *thread, EcmaRuntimeCallInfo *argv,
+ uint32_t argIndex, int64_t length);
+ // Let thisHandle be the array object. Checks whether array[key] (if exists) is strictly equal to target.
+ // Returns false on pending exception.
+ static bool ElementIsStrictEqualTo(JSThread *thread, const JSHandle &thisHandle,
+ const JSHandle &keyHandle,
+ const JSHandle &target);
+
static bool IsConcatSpreadable(JSThread *thread, const JSHandle &obj);
static double SortCompare(JSThread *thread, const JSHandle &callbackfnHandle,
const JSHandle &valueX, const JSHandle &valueY);
+ static double StringSortCompare(JSThread *thread, const JSHandle &valueX,
+ const JSHandle &valueY);
static int64_t GetLength(JSThread *thread, const JSHandle &thisHandle);
static int64_t GetArrayLength(JSThread *thread, const JSHandle &thisHandle);
static JSTaggedValue FlattenIntoArray(JSThread *thread, const JSHandle &newArrayHandle,
const JSHandle &thisObjVal, const FlattenArgs &args,
const JSHandle &mapperFunctionHandle,
const JSHandle &thisArg);
+ static JSTaggedValue SortIndexedProperties(JSThread *thread, const JSHandle &thisObj,
+ int64_t len, const JSHandle &callbackFnHandle,
+ HolesType holes);
};
} // namespace panda::ecmascript::base
diff --git a/ecmascript/base/atomic_helper.h b/ecmascript/base/atomic_helper.h
index bc9ef981f7f3f9877ae7539033370eb24266fe6a..c180bc7e3bdfeb98b94e01cd9c6064e725578d3c 100644
--- a/ecmascript/base/atomic_helper.h
+++ b/ecmascript/base/atomic_helper.h
@@ -19,7 +19,7 @@
#include "ecmascript/js_dataview.h"
namespace panda::ecmascript::base {
-enum class BytesSize : int32_t {ONEBYTES = 1, TWOBYTES = 2, FOURBYTES = 4, EIGHTBYTES = 8};
+enum class BytesSize : uint32_t {ONEBYTES = 1, TWOBYTES = 2, FOURBYTES = 4, EIGHTBYTES = 8};
class AtomicHelper final {
public:
@@ -101,4 +101,4 @@ public:
};
} // namespace panda::ecmascript::base
-#endif // ECMASCRIPT_BASE_ATOMIC_HELPER_H
\ No newline at end of file
+#endif // ECMASCRIPT_BASE_ATOMIC_HELPER_H
diff --git a/ecmascript/base/bit_helper.h b/ecmascript/base/bit_helper.h
index 1bc058b0981b8e4297937afcd481feb810033884..fe9ffbeb10865a14984f7ec88ad96fe478c0e33e 100644
--- a/ecmascript/base/bit_helper.h
+++ b/ecmascript/base/bit_helper.h
@@ -22,6 +22,7 @@
#include
namespace panda::ecmascript::base {
+constexpr uint64_t pureNaN = 0x7FF8ULL << 48U; // Be sure return the NaN that is safe.
template
union Data {
S src;
diff --git a/ecmascript/base/builtins_base.h b/ecmascript/base/builtins_base.h
index be50bb83cc00234aa705531d52f3a8d29a487dd1..632fed2213f73d55a1035c32e03eedfa15942993 100644
--- a/ecmascript/base/builtins_base.h
+++ b/ecmascript/base/builtins_base.h
@@ -63,6 +63,11 @@ public:
return JSTaggedValue(value);
}
+ static inline JSTaggedValue GetTaggedInt64(int64_t value)
+ {
+ return JSTaggedValue(value);
+ }
+
static inline JSTaggedValue GetTaggedDouble(double value)
{
return JSTaggedValue(value);
diff --git a/ecmascript/base/config.h b/ecmascript/base/config.h
index 0dc57ae708d531f90a8d068366a75f103fc10793..0f9baeb51b3489697bd87cb10744b3777a505ae6 100644
--- a/ecmascript/base/config.h
+++ b/ecmascript/base/config.h
@@ -64,6 +64,15 @@ namespace panda::ecmascript {
#define ECMASCRIPT_ENABLE_HEAP_VERIFY 1
#define ECMASCRIPT_ENABLE_BARRIER_CHECK 1
#define ECMASCRIPT_ENABLE_NAPI_SPECIAL_CHECK 1
+#elif defined(ECMASCRIPT_ENABLE_DFX_CONFIG)
+ #define ECMASCRIPT_ENABLE_IC 1
+ #define ECMASCRIPT_ENABLE_ZAP_MEM 0
+ #define ECMASCRIPT_SWITCH_GC_MODE_TO_FULL_GC 0
+ #define ECMASCRIPT_ENABLE_CAST_CHECK 0
+ #define ECMASCRIPT_ENABLE_NEW_HANDLE_CHECK 0
+ #define ECMASCRIPT_ENABLE_HEAP_VERIFY 1
+ #define ECMASCRIPT_ENABLE_BARRIER_CHECK 0
+ #define ECMASCRIPT_ENABLE_NAPI_SPECIAL_CHECK 1
#else
#define ECMASCRIPT_ENABLE_IC 1
#define ECMASCRIPT_ENABLE_ZAP_MEM 0
diff --git a/ecmascript/base/error_helper.cpp b/ecmascript/base/error_helper.cpp
index 78a0422f16d797efe39bff410f97d9565b904aa3..489997efc09ccf23448cc1109e252e5b45d20243 100644
--- a/ecmascript/base/error_helper.cpp
+++ b/ecmascript/base/error_helper.cpp
@@ -128,7 +128,7 @@ JSHandle ErrorHelper::GetErrorName(JSThread *thread, const JSHand
}
JSTaggedValue ErrorHelper::ErrorCommonConstructor(EcmaRuntimeCallInfo *argv,
- [[maybe_unused]] const ErrorType &errorType)
+ const ErrorType &errorType)
{
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
@@ -166,7 +166,23 @@ JSTaggedValue ErrorHelper::ErrorCommonConstructor(EcmaRuntimeCallInfo *argv,
[[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread, nativeInstanceObj, msgKey, msgDesc);
ASSERT_PRINT(status == true, "return result exception!");
}
-
+ // InstallErrorCause
+ JSHandle options = BuiltinsBase::GetCallArg(argv, 1);
+ // If options is an Object and ? HasProperty(options, "cause") is true, then
+ // a. Let cause be ? Get(options, "cause").
+ // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause).
+ if (options->IsECMAObject()) {
+ JSHandle causeKey = globalConst->GetHandledCauseString();
+ bool causePresent = JSTaggedValue::HasProperty(thread, options, causeKey);
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
+ if (causePresent) {
+ JSHandle cause = JSObject::GetProperty(thread, options, causeKey).GetValue();
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
+ PropertyDescriptor causeDesc(thread, cause, true, false, true);
+ [[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread, nativeInstanceObj, causeKey, causeDesc);
+ ASSERT_PRINT(status == true, "return result exception!");
+ }
+ }
JSHandle errorFunc = GetErrorJSFunction(thread);
if (!errorFunc->IsUndefined()) {
JSHandle errorFunckey = globalConst->GetHandledErrorFuncString();
@@ -176,7 +192,8 @@ JSTaggedValue ErrorHelper::ErrorCommonConstructor(EcmaRuntimeCallInfo *argv,
ASSERT_PRINT(status == true, "return result exception!");
}
- JSHandle handleStack = BuildEcmaStackTrace(thread);
+ bool isOOMError = errorType == ErrorType::OOM_ERROR;
+ JSHandle handleStack = BuildEcmaStackTrace(thread, isOOMError);
JSHandle stackkey = globalConst->GetHandledStackString();
PropertyDescriptor stackDesc(thread, JSHandle::Cast(handleStack), true, false, true);
[[maybe_unused]] bool status = JSObject::DefineOwnProperty(thread, nativeInstanceObj, stackkey, stackDesc);
@@ -205,9 +222,12 @@ JSHandle ErrorHelper::GetErrorJSFunction(JSThread *thread)
return thread->GlobalConstants()->GetHandledUndefined();
}
-JSHandle ErrorHelper::BuildEcmaStackTrace(JSThread *thread)
+JSHandle ErrorHelper::BuildEcmaStackTrace(JSThread *thread, bool isOOMError)
{
std::string data = JsStackInfo::BuildJsStackTrace(thread, false);
+ if (isOOMError) {
+ data = data.substr(0, MAX_ERROR_SIZE);
+ }
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
LOG_ECMA(DEBUG) << data;
return factory->NewFromStdString(data);
diff --git a/ecmascript/base/error_helper.h b/ecmascript/base/error_helper.h
index 5a1deeab3ade84bede2f86ff0bcbb16ea4c7d55b..aedf378e641f6844fa52921971da955da3aa5f7f 100644
--- a/ecmascript/base/error_helper.h
+++ b/ecmascript/base/error_helper.h
@@ -35,10 +35,11 @@ public:
private:
static JSHandle GetErrorJSFunction(JSThread *thread);
- static JSHandle BuildEcmaStackTrace(JSThread *thread);
+ static JSHandle BuildEcmaStackTrace(JSThread *thread, bool isOOMError);
static JSHandle GetErrorName(JSThread *thread, const JSHandle &name,
const ErrorType &errorType);
+ static constexpr uint32_t MAX_ERROR_SIZE = 128_KB;
};
} // namespace panda::ecmascript::base
diff --git a/ecmascript/base/fast_json_stringifier.cpp b/ecmascript/base/fast_json_stringifier.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..54677c54fdbabf04948d2f9315e2370e52e3d237
--- /dev/null
+++ b/ecmascript/base/fast_json_stringifier.cpp
@@ -0,0 +1,938 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ecmascript/base/fast_json_stringifier.h"
+
+#include "ecmascript/base/builtins_base.h"
+#include "ecmascript/base/json_helper.h"
+#include "ecmascript/base/number_helper.h"
+#include "ecmascript/builtins/builtins_errors.h"
+#include "ecmascript/ecma_runtime_call_info.h"
+#include "ecmascript/ecma_string-inl.h"
+#include "ecmascript/ecma_vm.h"
+#include "ecmascript/global_dictionary-inl.h"
+#include "ecmascript/js_array.h"
+#include "ecmascript/js_function.h"
+#include "ecmascript/js_handle.h"
+#include "ecmascript/js_object-inl.h"
+#include "ecmascript/js_primitive_ref.h"
+#include "ecmascript/js_tagged_value-inl.h"
+#include "ecmascript/js_tagged_value.h"
+#include "ecmascript/object_fast_operator-inl.h"
+
+namespace panda::ecmascript::base {
+JSHandle FastJsonStringifier::Stringify(const JSHandle &value)
+{
+ factory_ = thread_->GetEcmaVM()->GetFactory();
+ JSHandle jsonCache = thread_->GetEcmaVM()->GetGlobalEnv()->GetJsonObjectHclassCache();
+ if (jsonCache->IsHole()) {
+ hclassCache_ = factory_->NewTaggedArray(JSON_CACHE_SIZE);
+ } else {
+ hclassCache_ = JSHandle::Cast(jsonCache);
+ }
+ JSTaggedValue tagValue = value.GetTaggedValue();
+ handleValue_ = JSMutableHandle(thread_, tagValue);
+ handleKey_ = JSMutableHandle(thread_, factory_->GetEmptyString());
+
+ if (handleValue_->IsECMAObject() || handleValue_->IsBigInt()) {
+ JSTaggedValue serializeValue = GetSerializeValue(handleKey_, handleValue_);
+ RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
+ handleValue_.Update(serializeValue);
+ }
+
+ JSTaggedValue result = SerializeJSONProperty(handleValue_);
+
+ RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
+ if (!result.IsUndefined()) {
+ return JSHandle(
+ factory_->NewFromUtf8Literal(reinterpret_cast(result_.c_str()), result_.size()));
+ }
+ return thread_->GlobalConstants()->GetHandledUndefined();
+}
+
+JSTaggedValue FastJsonStringifier::GetSerializeValue(const JSHandle &key,
+ const JSHandle &value)
+{
+ JSTaggedValue tagValue = value.GetTaggedValue();
+ JSHandle undefined = thread_->GlobalConstants()->GetHandledUndefined();
+ // a. Let toJSON be Get(value, "toJSON").
+ JSHandle toJson = thread_->GlobalConstants()->GetHandledToJsonString();
+ JSHandle toJsonFun(
+ thread_, ObjectFastOperator::FastGetPropertyByValue(thread_, tagValue, toJson.GetTaggedValue()));
+ // b. ReturnIfAbrupt(toJSON).
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
+ tagValue = value.GetTaggedValue();
+ // c. If IsCallable(toJSON) is true
+ if (UNLIKELY(toJsonFun->IsCallable())) {
+ // Let value be Call(toJSON, value, «key»).
+ EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread_, toJsonFun, value, undefined, 1);
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
+ info->SetCallArg(key.GetTaggedValue());
+ tagValue = JSFunction::Call(info);
+ // ii. ReturnIfAbrupt(value).
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
+ }
+ return tagValue;
+}
+
+JSTaggedValue FastJsonStringifier::SerializeJSONProperty(const JSHandle &value)
+{
+ JSTaggedValue tagValue = value.GetTaggedValue();
+ if (!tagValue.IsHeapObject()) {
+ JSTaggedType type = tagValue.GetRawData();
+ switch (type) {
+ // If value is false, return "false".
+ case JSTaggedValue::VALUE_FALSE:
+ result_ += "false";
+ return tagValue;
+ // If value is true, return "true".
+ case JSTaggedValue::VALUE_TRUE:
+ result_ += "true";
+ return tagValue;
+ // If value is null, return "null".
+ case JSTaggedValue::VALUE_NULL:
+ result_ += "null";
+ return tagValue;
+ default:
+ // If Type(value) is Number, then
+ if (tagValue.IsNumber()) {
+ // a. If value is finite, return ToString(value).
+ if (std::isfinite(tagValue.GetNumber())) {
+ result_ += ConvertToString(*base::NumberHelper::NumberToString(thread_, tagValue));
+ } else {
+ // b. Else, return "null".
+ result_ += "null";
+ }
+ return tagValue;
+ }
+ }
+ } else {
+ JSType jsType = tagValue.GetTaggedObject()->GetClass()->GetObjectType();
+ JSHandle valHandle(thread_, tagValue);
+ switch (jsType) {
+ case JSType::JS_ARRAY: {
+ SerializeJSArray(valHandle);
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
+ return tagValue;
+ }
+ // If Type(value) is String, return QuoteJSONString(value).
+ case JSType::LINE_STRING:
+ case JSType::CONSTANT_STRING:
+ case JSType::TREE_STRING: {
+ JSHandle strHandle = JSHandle(valHandle);
+ auto string = JSHandle(thread_,
+ EcmaStringAccessor::Flatten(thread_->GetEcmaVM(), strHandle));
+ CString str = ConvertToString(*string, StringConvertedUsage::LOGICOPERATION);
+ str = JsonHelper::ValueToQuotedString(str);
+ result_ += str;
+ return tagValue;
+ }
+ case JSType::JS_PRIMITIVE_REF: {
+ SerializePrimitiveRef(valHandle);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, JSTaggedValue::Exception());
+ return tagValue;
+ }
+ case JSType::SYMBOL:
+ return JSTaggedValue::Undefined();
+ case JSType::BIGINT: {
+ THROW_TYPE_ERROR_AND_RETURN(thread_, "cannot serialize a BigInt", JSTaggedValue::Exception());
+ }
+ default: {
+ if (!tagValue.IsCallable()) {
+ JSHClass *jsHclass = tagValue.GetTaggedObject()->GetClass();
+ if (UNLIKELY(jsHclass->IsJSProxy() &&
+ JSProxy::Cast(tagValue.GetTaggedObject())->IsArray(thread_))) {
+ SerializeJSProxy(valHandle);
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
+ } else {
+ SerializeJSONObject(valHandle);
+ RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
+ }
+ return tagValue;
+ }
+ }
+ }
+ }
+ return JSTaggedValue::Undefined();
+}
+
+CString FastJsonStringifier::SerializeObjectKey(const JSHandle &key, bool hasContent)
+{
+ if (hasContent) {
+ result_ += ",";
+ }
+
+ CString str;
+ if (key->IsString()) {
+ str = ConvertToString(EcmaString::Cast(key->GetTaggedObject()), StringConvertedUsage::LOGICOPERATION);
+ } else if (key->IsInt()) {
+ str = NumberHelper::IntToString(static_cast(key->GetInt()));
+ } else {
+ str = ConvertToString(*JSTaggedValue::ToString(thread_, key), StringConvertedUsage::LOGICOPERATION);
+ }
+ str = JsonHelper::ValueToQuotedString(str);
+ result_ += str;
+ result_ += ":";
+
+ return str;
+}
+
+bool FastJsonStringifier::PushValue(const JSHandle &value)
+{
+ uint32_t thisLen = stack_.size();
+
+ for (uint32_t i = 0; i < thisLen; i++) {
+ bool equal = JSTaggedValue::SameValue(stack_[i].GetTaggedValue(), value.GetTaggedValue());
+ if (equal) {
+ return true;
+ }
+ }
+
+ stack_.emplace_back(value);
+ return false;
+}
+
+void FastJsonStringifier::PopValue()
+{
+ stack_.pop_back();
+}
+
+bool FastJsonStringifier::SerializeJSONObject(const JSHandle &value)
+{
+ bool isContain = PushValue(value);
+ if (isContain) {
+ THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value", true);
+ }
+
+ result_ += "{";
+ bool hasContent = false;
+
+ ASSERT(!value->IsAccessor());
+ JSHandle obj(value);
+ if (UNLIKELY(value->IsJSProxy() || value->IsTypedArray())) { // serialize proxy and typedArray
+ JSHandle propertyArray = JSObject::EnumerableOwnNames(thread_, obj);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ uint32_t arrLength = propertyArray->GetLength();
+ for (uint32_t i = 0; i < arrLength; i++) {
+ handleKey_.Update(propertyArray->Get(i));
+ JSHandle valueHandle = JSTaggedValue::GetProperty(thread_, value, handleKey_).GetValue();
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (UNLIKELY(valueHandle->IsECMAObject() || valueHandle->IsBigInt())) {
+ JSTaggedValue serializeValue = GetSerializeValue(handleKey_, valueHandle);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
+ (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
+ continue;
+ }
+ handleValue_.Update(serializeValue);
+ } else {
+ handleValue_.Update(valueHandle);
+ }
+ SerializeObjectKey(handleKey_, hasContent);
+ JSTaggedValue res = SerializeJSONProperty(handleValue_);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (!res.IsUndefined()) {
+ hasContent = true;
+ }
+ }
+ } else {
+ uint32_t numOfKeys = obj->GetNumberOfKeys();
+ uint32_t numOfElements = obj->GetNumberOfElements();
+ if (numOfKeys + numOfElements < CACHE_MINIMUN_SIZIE || !cacheable_) {
+ if (numOfElements > 0) {
+ hasContent = DefaultSerializeElements(obj, hasContent);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ if (numOfKeys > 0) {
+ hasContent = DefaultSerializeKeys(obj, hasContent);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ } else {
+ JSHClass *jsHclass = value->GetTaggedObject()->GetClass();
+ int32_t index = FindCache(jsHclass, numOfKeys + numOfElements);
+ if (index != INVALID_INDEX) {
+ auto strCache = thread_->GetCurrentEcmaContext()->GetJsonStringifyCache(index);
+ uint32_t cacheIndex = 0;
+ if (numOfElements > 0) {
+ hasContent = SerializeElementsWithCache(obj, hasContent, strCache, cacheIndex, numOfElements);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ if (numOfKeys > 0) {
+ hasContent = SerializeKeysWithCache(obj, hasContent, strCache, cacheIndex);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ } else {
+ CVector> strCache;
+ if (numOfElements > 0) {
+ hasContent = TryCacheSerializeElements(obj, hasContent, strCache);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ if (numOfKeys > 0) {
+ hasContent = TryCacheSerializeKeys(obj, hasContent, strCache);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ if (cacheable_) {
+ SetCache(value->GetTaggedObject()->GetClass(), numOfElements + numOfKeys, strCache);
+ }
+ }
+ }
+ }
+
+ result_ += "}";
+ PopValue();
+ return true;
+}
+
+bool FastJsonStringifier::SerializeJSProxy(const JSHandle &object)
+{
+ bool isContain = PushValue(object);
+ if (isContain) {
+ THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value", true);
+ }
+
+ result_ += "[";
+ JSHandle proxy(object);
+ JSHandle lengthKey = thread_->GlobalConstants()->GetHandledLengthString();
+ JSHandle lenghHandle = JSProxy::GetProperty(thread_, proxy, lengthKey).GetValue();
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ JSTaggedNumber lenNumber = JSTaggedValue::ToLength(thread_, lenghHandle);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ uint32_t length = lenNumber.ToUint32();
+ for (uint32_t i = 0; i < length; i++) {
+ handleKey_.Update(JSTaggedValue(i));
+ JSHandle valHandle = JSProxy::GetProperty(thread_, proxy, handleKey_).GetValue();
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (i > 0) {
+ result_ += ",";
+ }
+ if (UNLIKELY(valHandle->IsECMAObject() || valHandle->IsBigInt())) {
+ JSTaggedValue serializeValue = GetSerializeValue(handleKey_, valHandle);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ handleValue_.Update(serializeValue);
+ } else {
+ handleValue_.Update(valHandle);
+ }
+ JSTaggedValue res = SerializeJSONProperty(handleValue_);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (res.IsUndefined()) {
+ result_ += "null";
+ }
+ }
+
+ result_ += "]";
+ PopValue();
+ return true;
+}
+
+bool FastJsonStringifier::SerializeJSArray(const JSHandle &value)
+{
+ // If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
+ bool isContain = PushValue(value);
+ if (isContain) {
+ THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value", true);
+ }
+
+ result_ += "[";
+ JSHandle jsArr(value);
+ uint32_t len = jsArr->GetArrayLength();
+ if (len > 0) {
+ for (uint32_t i = 0; i < len; i++) {
+ JSTaggedValue tagVal = ObjectFastOperator::FastGetPropertyByIndex(thread_, value.GetTaggedValue(), i);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (UNLIKELY(tagVal.IsAccessor())) {
+ tagVal = JSObject::CallGetter(thread_, AccessorData::Cast(tagVal.GetTaggedObject()), value);
+ }
+ handleKey_.Update(JSTaggedValue(i));
+ handleValue_.Update(tagVal);
+
+ if (i > 0) {
+ result_ += ",";
+ }
+ if (handleValue_->IsECMAObject() || handleValue_->IsBigInt()) {
+ JSTaggedValue serializeValue = GetSerializeValue(handleKey_, handleValue_);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ handleValue_.Update(serializeValue);
+ }
+ JSTaggedValue res = SerializeJSONProperty(handleValue_);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (res.IsUndefined()) {
+ result_ += "null";
+ }
+ }
+ }
+
+ result_ += "]";
+ PopValue();
+ return true;
+}
+
+void FastJsonStringifier::SerializePrimitiveRef(const JSHandle &primitiveRef)
+{
+ JSTaggedValue primitive = JSPrimitiveRef::Cast(primitiveRef.GetTaggedValue().GetTaggedObject())->GetValue();
+ if (primitive.IsString()) {
+ auto priStr = JSTaggedValue::ToString(thread_, primitiveRef);
+ RETURN_IF_ABRUPT_COMPLETION(thread_);
+ CString str = ConvertToString(*priStr, StringConvertedUsage::LOGICOPERATION);
+ str = JsonHelper::ValueToQuotedString(str);
+ result_ += str;
+ } else if (primitive.IsNumber()) {
+ auto priNum = JSTaggedValue::ToNumber(thread_, primitiveRef);
+ RETURN_IF_ABRUPT_COMPLETION(thread_);
+ if (std::isfinite(priNum.GetNumber())) {
+ result_ += ConvertToString(*base::NumberHelper::NumberToString(thread_, priNum));
+ } else {
+ result_ += "null";
+ }
+ } else if (primitive.IsBoolean()) {
+ result_ += primitive.IsTrue() ? "true" : "false";
+ } else if (primitive.IsBigInt()) {
+ THROW_TYPE_ERROR(thread_, "cannot serialize a BigInt");
+ }
+}
+
+bool FastJsonStringifier::TryCacheSerializeElements(const JSHandle &obj, bool hasContent,
+ CVector> &strCache)
+{
+ JSHandle elementsArr(thread_, obj->GetElements());
+ if (!elementsArr->IsDictionaryMode()) {
+ uint32_t elementsLen = elementsArr->GetLength();
+ for (uint32_t i = 0; i < elementsLen; ++i) {
+ if (!elementsArr->Get(i).IsHole()) {
+ handleKey_.Update(JSTaggedValue(i));
+ handleValue_.Update(elementsArr->Get(i));
+ hasContent = AppendJsonString(hasContent, strCache, i);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ }
+ } else {
+ JSHandle numberDic(elementsArr);
+ CVector> sortArr;
+ int size = numberDic->Size();
+ for (int hashIndex = 0; hashIndex < size; hashIndex++) {
+ JSTaggedValue key = numberDic->GetKey(hashIndex);
+ if (!key.IsUndefined() && !key.IsHole()) {
+ PropertyAttributes attr = numberDic->GetAttributes(hashIndex);
+ if (attr.IsEnumerable()) {
+ JSTaggedValue numberKey = JSTaggedValue(static_cast(key.GetInt()));
+ sortArr.emplace_back(JSHandle(thread_, numberKey));
+ }
+ }
+ }
+ std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareNumber);
+ for (const auto &entry : sortArr) {
+ JSTaggedValue entryKey = entry.GetTaggedValue();
+ handleKey_.Update(entryKey);
+ int index = numberDic->FindEntry(entryKey);
+ JSTaggedValue value = numberDic->GetValue(index);
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = AppendJsonString(hasContent, strCache, index);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ }
+ return hasContent;
+}
+
+bool FastJsonStringifier::SerializeElementsWithCache(const JSHandle &obj, bool hasContent,
+ CVector> &strCache, uint32_t &cacheIndex, uint32_t elementSize)
+{
+ JSHandle elementsArr(thread_, obj->GetElements());
+ if (!elementsArr->IsDictionaryMode()) {
+ uint32_t elementsLen = elementsArr->GetLength();
+ for (uint32_t i = 0; i < elementsLen; ++i) {
+ if (!elementsArr->Get(i).IsHole()) {
+ CString key = strCache[cacheIndex++].first;
+ handleValue_.Update(elementsArr->Get(i));
+ hasContent = FastAppendJsonString(hasContent, key);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ }
+ } else {
+ JSHandle numberDic(elementsArr);
+ for (; cacheIndex < elementSize; cacheIndex++) {
+ CString key = strCache[cacheIndex].first;
+ int index = strCache[cacheIndex].second;
+ JSTaggedValue value = numberDic->GetValue(index);
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = FastAppendJsonString(hasContent, key);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ }
+ return hasContent;
+}
+
+bool FastJsonStringifier::TryCacheSerializeKeys(const JSHandle &obj, bool hasContent,
+ CVector> &strCache)
+{
+ JSHandle propertiesArr(thread_, obj->GetProperties());
+ if (!propertiesArr->IsDictionaryMode()) {
+ JSHandle jsHclass(thread_, obj->GetJSHClass());
+ JSTaggedValue enumCache = jsHclass->GetEnumCache();
+ if (!enumCache.IsNull()) {
+ JSHandle cache(thread_, enumCache);
+ uint32_t length = cache->GetLength();
+ for (uint32_t i = 0; i < length; i++) {
+ JSTaggedValue key = cache->Get(i);
+ if (!key.IsString()) {
+ continue;
+ }
+ handleKey_.Update(key);
+ JSTaggedValue value;
+ LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
+ int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
+ PropertyAttributes attr(layoutInfo->GetAttr(index));
+ ASSERT(static_cast(attr.GetOffset()) == index);
+ value = attr.IsInlinedProps()
+ ? obj->GetPropertyInlinedPropsWithRep(static_cast(index), attr)
+ : propertiesArr->Get(static_cast(index) - jsHclass->GetInlinedProperties());
+ if (attr.IsInlinedProps() && value.IsHole()) {
+ continue;
+ }
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = AppendJsonString(hasContent, strCache, index);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ return hasContent;
+ }
+ int end = static_cast(jsHclass->NumberOfProps());
+ if (end <= 0) {
+ return hasContent;
+ }
+ for (int i = 0; i < end; i++) {
+ LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
+ JSTaggedValue key = layoutInfo->GetKey(i);
+ if (key.IsString() && layoutInfo->GetAttr(i).IsEnumerable()) {
+ handleKey_.Update(key);
+ JSTaggedValue value;
+ int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
+ PropertyAttributes attr(layoutInfo->GetAttr(index));
+ ASSERT(static_cast(attr.GetOffset()) == index);
+ value = attr.IsInlinedProps()
+ ? obj->GetPropertyInlinedPropsWithRep(static_cast(index), attr)
+ : propertiesArr->Get(static_cast(index) - jsHclass->GetInlinedProperties());
+ if (attr.IsInlinedProps() && value.IsHole()) {
+ continue;
+ }
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = AppendJsonString(hasContent, strCache, index);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ }
+ return hasContent;
+ }
+ if (obj->IsJSGlobalObject()) {
+ JSHandle globalDic(propertiesArr);
+ int size = globalDic->Size();
+ CVector, PropertyAttributes>> sortArr;
+ for (int hashIndex = 0; hashIndex < size; hashIndex++) {
+ JSTaggedValue key = globalDic->GetKey(hashIndex);
+ if (!key.IsString()) {
+ continue;
+ }
+ PropertyAttributes attr = globalDic->GetAttributes(hashIndex);
+ if (!attr.IsEnumerable()) {
+ continue;
+ }
+ std::pair, PropertyAttributes> pair(JSHandle(thread_, key), attr);
+ sortArr.emplace_back(pair);
+ }
+ std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
+ for (const auto &entry : sortArr) {
+ JSTaggedValue entryKey = entry.first.GetTaggedValue();
+ handleKey_.Update(entryKey);
+ int index = globalDic->FindEntry(entryKey);
+ JSTaggedValue value = globalDic->GetValue(index);
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = AppendJsonString(hasContent, strCache, index);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ return hasContent;
+ }
+ JSHandle nameDic(propertiesArr);
+ int size = nameDic->Size();
+ CVector, PropertyAttributes>> sortArr;
+ for (int hashIndex = 0; hashIndex < size; hashIndex++) {
+ JSTaggedValue key = nameDic->GetKey(hashIndex);
+ if (!key.IsString()) {
+ continue;
+ }
+ PropertyAttributes attr = nameDic->GetAttributes(hashIndex);
+ if (!attr.IsEnumerable()) {
+ continue;
+ }
+ std::pair, PropertyAttributes> pair(JSHandle(thread_, key), attr);
+ sortArr.emplace_back(pair);
+ }
+ std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
+ for (const auto &entry : sortArr) {
+ JSTaggedValue entryKey = entry.first.GetTaggedValue();
+ handleKey_.Update(entryKey);
+ int index = nameDic->FindEntry(entryKey);
+ JSTaggedValue value = nameDic->GetValue(index);
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = AppendJsonString(hasContent, strCache, index);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ return hasContent;
+}
+
+bool FastJsonStringifier::SerializeKeysWithCache(const JSHandle &obj, bool hasContent,
+ CVector> &strCache, uint32_t &cacheIndex)
+{
+ JSHandle jsHclass(thread_, obj->GetJSHClass());
+ JSHandle propertiesArr(thread_, obj->GetProperties());
+ if (!propertiesArr->IsDictionaryMode()) {
+ for (; cacheIndex < strCache.size(); cacheIndex++) {
+ auto cacheValue = strCache[cacheIndex];
+ CString str = cacheValue.first;
+ int index = cacheValue.second;
+ LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
+ PropertyAttributes attr(layoutInfo->GetAttr(index));
+ JSTaggedValue value = attr.IsInlinedProps()
+ ? obj->GetPropertyInlinedPropsWithRep(static_cast(index), attr)
+ : propertiesArr->Get(static_cast(index) - jsHclass->GetInlinedProperties());
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = FastAppendJsonString(hasContent, str);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ return hasContent;
+ }
+ if (obj->IsJSGlobalObject()) {
+ JSHandle globalDic(propertiesArr);
+ for (; cacheIndex < strCache.size(); cacheIndex++) {
+ auto cacheValue = strCache[cacheIndex];
+ CString str = cacheValue.first;
+ int index = cacheValue.second;
+ JSTaggedValue value = globalDic->GetValue(index);
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = FastAppendJsonString(hasContent, str);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ return hasContent;
+ }
+ JSHandle nameDic(propertiesArr);
+ for (; cacheIndex < strCache.size(); cacheIndex++) {
+ auto cacheValue = strCache[cacheIndex];
+ CString str = cacheValue.first;
+ int index = cacheValue.second;
+ JSTaggedValue value = nameDic->GetValue(index);
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = FastAppendJsonString(hasContent, str);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ return hasContent;
+}
+
+bool FastJsonStringifier::AppendJsonString(bool hasContent, CVector> &strCache, int index)
+{
+ if (handleValue_->IsECMAObject() || handleValue_->IsBigInt()) {
+ JSTaggedValue serializeValue = GetSerializeValue(handleKey_, handleValue_);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
+ (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
+ return hasContent;
+ }
+ handleValue_.Update(serializeValue);
+ }
+ CString keyStr = SerializeObjectKey(handleKey_, hasContent);
+ strCache.emplace_back(std::pair(keyStr, index));
+ JSTaggedValue res = SerializeJSONProperty(handleValue_);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (!res.IsUndefined()) {
+ return true;
+ }
+ EraseKeyString(keyStr, hasContent);
+ return hasContent;
+}
+
+bool FastJsonStringifier::FastAppendJsonString(bool hasContent, CString &key)
+{
+ if (handleValue_->IsECMAObject() || handleValue_->IsBigInt()) {
+ JSTaggedValue serializeValue = GetSerializeValue(handleKey_, handleValue_);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
+ (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
+ return hasContent;
+ }
+ handleValue_.Update(serializeValue);
+ }
+ FastSerializeObjectKey(key, hasContent);
+ JSTaggedValue res = SerializeJSONProperty(handleValue_);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (!res.IsUndefined()) {
+ return true;
+ }
+ EraseKeyString(key, hasContent);
+ return hasContent;
+}
+
+bool FastJsonStringifier::DefaultSerializeElements(const JSHandle &obj, bool hasContent)
+{
+ JSHandle elementsArr(thread_, obj->GetElements());
+ if (!elementsArr->IsDictionaryMode()) {
+ uint32_t elementsLen = elementsArr->GetLength();
+ for (uint32_t i = 0; i < elementsLen; ++i) {
+ if (!elementsArr->Get(i).IsHole()) {
+ handleKey_.Update(JSTaggedValue(i));
+ handleValue_.Update(elementsArr->Get(i));
+ hasContent = AppendJsonString(hasContent);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ }
+ } else {
+ JSHandle numberDic(elementsArr);
+ CVector> sortArr;
+ int size = numberDic->Size();
+ for (int hashIndex = 0; hashIndex < size; hashIndex++) {
+ JSTaggedValue key = numberDic->GetKey(hashIndex);
+ if (!key.IsUndefined() && !key.IsHole()) {
+ PropertyAttributes attr = numberDic->GetAttributes(hashIndex);
+ if (attr.IsEnumerable()) {
+ JSTaggedValue numberKey = JSTaggedValue(static_cast(key.GetInt()));
+ sortArr.emplace_back(JSHandle(thread_, numberKey));
+ }
+ }
+ }
+ std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareNumber);
+ for (const auto &entry : sortArr) {
+ JSTaggedValue entryKey = entry.GetTaggedValue();
+ handleKey_.Update(entryKey);
+ int index = numberDic->FindEntry(entryKey);
+ JSTaggedValue value = numberDic->GetValue(index);
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = AppendJsonString(hasContent);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ }
+ return hasContent;
+}
+
+bool FastJsonStringifier::DefaultSerializeKeys(const JSHandle &obj, bool hasContent)
+{
+ JSHandle propertiesArr(thread_, obj->GetProperties());
+ if (!propertiesArr->IsDictionaryMode()) {
+ JSHandle jsHclass(thread_, obj->GetJSHClass());
+ JSTaggedValue enumCache = jsHclass->GetEnumCache();
+ if (!enumCache.IsNull()) {
+ JSHandle cache(thread_, enumCache);
+ uint32_t length = cache->GetLength();
+ for (uint32_t i = 0; i < length; i++) {
+ JSTaggedValue key = cache->Get(i);
+ if (!key.IsString()) {
+ continue;
+ }
+ handleKey_.Update(key);
+ JSTaggedValue value;
+ LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
+ int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
+ PropertyAttributes attr(layoutInfo->GetAttr(index));
+ ASSERT(static_cast(attr.GetOffset()) == index);
+ value = attr.IsInlinedProps()
+ ? obj->GetPropertyInlinedPropsWithRep(static_cast(index), attr)
+ : propertiesArr->Get(static_cast(index) - jsHclass->GetInlinedProperties());
+ if (attr.IsInlinedProps() && value.IsHole()) {
+ continue;
+ }
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = AppendJsonString(hasContent);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ return hasContent;
+ }
+ int end = static_cast(jsHclass->NumberOfProps());
+ if (end <= 0) {
+ return hasContent;
+ }
+ for (int i = 0; i < end; i++) {
+ LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
+ JSTaggedValue key = layoutInfo->GetKey(i);
+ if (key.IsString() && layoutInfo->GetAttr(i).IsEnumerable()) {
+ handleKey_.Update(key);
+ JSTaggedValue value;
+ int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
+ PropertyAttributes attr(layoutInfo->GetAttr(index));
+ ASSERT(static_cast(attr.GetOffset()) == index);
+ value = attr.IsInlinedProps()
+ ? obj->GetPropertyInlinedPropsWithRep(static_cast(index), attr)
+ : propertiesArr->Get(static_cast(index) - jsHclass->GetInlinedProperties());
+ if (attr.IsInlinedProps() && value.IsHole()) {
+ continue;
+ }
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = AppendJsonString(hasContent);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ }
+ return hasContent;
+ }
+ if (obj->IsJSGlobalObject()) {
+ JSHandle globalDic(propertiesArr);
+ int size = globalDic->Size();
+ CVector, PropertyAttributes>> sortArr;
+ for (int hashIndex = 0; hashIndex < size; hashIndex++) {
+ JSTaggedValue key = globalDic->GetKey(hashIndex);
+ if (!key.IsString()) {
+ continue;
+ }
+ PropertyAttributes attr = globalDic->GetAttributes(hashIndex);
+ if (!attr.IsEnumerable()) {
+ continue;
+ }
+ std::pair, PropertyAttributes> pair(JSHandle(thread_, key), attr);
+ sortArr.emplace_back(pair);
+ }
+ std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
+ for (const auto &entry : sortArr) {
+ JSTaggedValue entryKey = entry.first.GetTaggedValue();
+ handleKey_.Update(entryKey);
+ int index = globalDic->FindEntry(entryKey);
+ JSTaggedValue value = globalDic->GetValue(index);
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = AppendJsonString(hasContent);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ return hasContent;
+ }
+ JSHandle nameDic(propertiesArr);
+ int size = nameDic->Size();
+ CVector, PropertyAttributes>> sortArr;
+ for (int hashIndex = 0; hashIndex < size; hashIndex++) {
+ JSTaggedValue key = nameDic->GetKey(hashIndex);
+ if (!key.IsString()) {
+ continue;
+ }
+ PropertyAttributes attr = nameDic->GetAttributes(hashIndex);
+ if (!attr.IsEnumerable()) {
+ continue;
+ }
+ std::pair, PropertyAttributes> pair(JSHandle(thread_, key), attr);
+ sortArr.emplace_back(pair);
+ }
+ std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
+ for (const auto &entry : sortArr) {
+ JSTaggedValue entryKey = entry.first.GetTaggedValue();
+ handleKey_.Update(entryKey);
+ int index = nameDic->FindEntry(entryKey);
+ JSTaggedValue value = nameDic->GetValue(index);
+ if (UNLIKELY(value.IsAccessor())) {
+ value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
+ JSHandle(obj));
+ }
+ handleValue_.Update(value);
+ hasContent = AppendJsonString(hasContent);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ return hasContent;
+}
+
+bool FastJsonStringifier::AppendJsonString(bool hasContent)
+{
+ if (handleValue_->IsECMAObject() || handleValue_->IsBigInt()) {
+ JSTaggedValue serializeValue = GetSerializeValue(handleKey_, handleValue_);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
+ (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
+ return hasContent;
+ }
+ handleValue_.Update(serializeValue);
+ }
+ CString keyStr = SerializeObjectKey(handleKey_, hasContent);
+ JSTaggedValue res = SerializeJSONProperty(handleValue_);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ if (!res.IsUndefined()) {
+ return true;
+ }
+ EraseKeyString(keyStr, hasContent);
+ return hasContent;
+}
+
+bool FastJsonStringifier::DefaultSerializeObject(const JSTaggedValue &object, uint32_t numOfKeys,
+ uint32_t numOfElements)
+{
+ JSHandle value(thread_, object);
+ bool isContain = PushValue(value);
+ if (isContain) {
+ THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value", true);
+ }
+
+ result_ += "{";
+ bool hasContent = false;
+
+ JSHandle obj(value);
+ if (numOfElements > 0) {
+ hasContent = DefaultSerializeElements(obj, hasContent);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+ if (numOfKeys > 0) {
+ hasContent = DefaultSerializeKeys(obj, hasContent);
+ RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
+ }
+
+ result_ += "}";
+ PopValue();
+ return true;
+}
+} // namespace panda::ecmascript::base
diff --git a/ecmascript/base/fast_json_stringifier.h b/ecmascript/base/fast_json_stringifier.h
new file mode 100644
index 0000000000000000000000000000000000000000..32528ed402fc4d81e486f9917e96e7417196514d
--- /dev/null
+++ b/ecmascript/base/fast_json_stringifier.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ECMASCRIPT_BASE_FAST_JSON_STRINGIFY_H
+#define ECMASCRIPT_BASE_FAST_JSON_STRINGIFY_H
+
+#include "ecmascript/js_tagged_value.h"
+#include "ecmascript/js_handle.h"
+#include "ecmascript/object_factory.h"
+#include "ecmascript/global_env.h"
+#include "ecmascript/mem/c_containers.h"
+
+namespace panda::ecmascript::base {
+class FastJsonStringifier {
+public:
+ static constexpr int32_t INVALID_INDEX = -1;
+ static constexpr int32_t JSON_CACHE_MASK = 62;
+ static constexpr int32_t JSON_CACHE_SIZE = 64;
+ static constexpr int32_t CACHE_MINIMUN_SIZIE = 5;
+ FastJsonStringifier() = default;
+
+ explicit FastJsonStringifier(JSThread *thread) : thread_(thread) {}
+
+ ~FastJsonStringifier() = default;
+ NO_COPY_SEMANTIC(FastJsonStringifier);
+ NO_MOVE_SEMANTIC(FastJsonStringifier);
+
+ JSHandle Stringify(const JSHandle &value);
+
+private:
+ JSTaggedValue SerializeJSONProperty(const JSHandle &value);
+ JSTaggedValue GetSerializeValue(const JSHandle &key, const JSHandle &value);
+ CString SerializeObjectKey(const JSHandle &key, bool hasContent);
+
+ bool SerializeJSONObject(const JSHandle &value);
+
+ bool SerializeJSArray(const JSHandle &value);
+ bool SerializeJSProxy(const JSHandle &object);
+
+ void SerializePrimitiveRef(const JSHandle &primitiveRef);
+
+ bool PushValue(const JSHandle &value);
+
+ void PopValue();
+
+ bool AppendJsonString(bool hasContent, CVector> &strCache, int index);
+ bool FastAppendJsonString(bool hasContent, CString &key);
+ bool TryCacheSerializeElements(const JSHandle &obj, bool hasContent,
+ CVector> &strCache);
+ bool SerializeElementsWithCache(const JSHandle &obj, bool hasContent,
+ CVector> &strCache, uint32_t &cacheIndex,
+ uint32_t elementSize);
+ bool TryCacheSerializeKeys(const JSHandle &obj, bool hasContent,
+ CVector> &strCache);
+ bool SerializeKeysWithCache(const JSHandle &obj, bool hasContent,
+ CVector> &strCache, uint32_t &cacheIndex);
+ bool AppendJsonString(bool hasContent);
+ bool DefaultSerializeKeys(const JSHandle &obj, bool hasContent);
+ bool DefaultSerializeElements(const JSHandle &obj, bool hasContent);
+ bool DefaultSerializeObject(const JSTaggedValue &object, uint32_t numOfKeys, uint32_t numOfElements);
+
+ inline void EraseKeyString(CString &keyStr, bool hasContent)
+ {
+ size_t keyLength = keyStr.length() + (hasContent ? 1 : 0) + 1;
+ result_.erase(result_.end() - keyLength, result_.end());
+ }
+
+ inline void FastSerializeObjectKey(CString &key, bool hasContent)
+ {
+ if (hasContent) {
+ result_ += ",";
+ }
+
+ result_ += key;
+ result_ += ":";
+ }
+
+ inline int32_t FindCache(JSHClass *hclass, size_t numOfKeys)
+ {
+ size_t index = GetHash(hclass, numOfKeys);
+ JSTaggedValue cacheHclass = hclassCache_->Get(index);
+ if (cacheHclass != JSTaggedValue::Hole()) {
+ if (JSHClass::Cast(cacheHclass.GetTaggedObject()) == hclass) {
+ return index;
+ } else {
+ cacheHclass = hclassCache_->Get(++index);
+ if (JSHClass::Cast(cacheHclass.GetTaggedObject()) == hclass) {
+ return index;
+ } else {
+ return INVALID_INDEX;
+ }
+ }
+ }
+ return INVALID_INDEX;
+ }
+
+ inline void SetCache(JSHClass *hclass, size_t numOfKeys, CVector> &value)
+ {
+ size_t index = GetHash(hclass, numOfKeys);
+ JSTaggedValue cacheHclass = hclassCache_->Get(index);
+ if (cacheHclass != JSTaggedValue::Hole()) {
+ cacheHclass = hclassCache_->Get(++index);
+ if (cacheHclass != JSTaggedValue::Hole()) {
+ --index;
+ }
+ }
+ hclassCache_->Set(thread_, index, JSTaggedValue(hclass));
+ thread_->GetCurrentEcmaContext()->SetJsonStringifyCache(index, value);
+ }
+
+ inline size_t GetHash(JSHClass *hclass, size_t numOfKeys)
+ {
+ uintptr_t ptr = reinterpret_cast(hclass);
+ size_t hash = (ptr + numOfKeys) & JSON_CACHE_MASK;
+ return hash;
+ }
+
+ CString result_;
+ JSThread *thread_ {nullptr};
+ ObjectFactory *factory_ {nullptr};
+ CVector> stack_;
+ JSMutableHandle handleKey_ {};
+ JSMutableHandle handleValue_ {};
+ bool cacheable_ {true};
+ JSHandle hclassCache_ {};
+};
+} // namespace panda::ecmascript::basekey
+#endif // ECMASCRIPT_BASE_FAST_JSON_STRINGIFY_H
diff --git a/ecmascript/base/file_header.h b/ecmascript/base/file_header.h
index a34ce20ead1f27bbd9926656135e228b97d2961e..1d70e938419f522e5834eed87c6d05d13ca1100d 100644
--- a/ecmascript/base/file_header.h
+++ b/ecmascript/base/file_header.h
@@ -19,15 +19,18 @@
#include "ecmascript/base/string_helper.h"
#include "ecmascript/log_wrapper.h"
#include "utils/bit_utils.h"
+#include "zlib.h"
+
#include
#include
#include
namespace panda::ecmascript::base {
-class FileHeader {
+class FileHeaderBase {
public:
static constexpr size_t MAGIC_SIZE = 8;
static constexpr size_t VERSION_SIZE = 4;
+ static constexpr uint32_t CHECKSUM_END_OFFSET = MAGIC_SIZE + VERSION_SIZE + sizeof(uint32_t);
static constexpr std::array MAGIC = {'P', 'A', 'N', 'D', 'A', '\0', '\0', '\0'};
using VersionType = std::array;
@@ -59,23 +62,7 @@ public:
return ret;
}
-protected:
- explicit FileHeader(const VersionType &lastVersion) : magic_(MAGIC), version_(lastVersion) {}
-
- static bool VerifyVersion(const char *fileDesc, const VersionType &currVersion, const VersionType &lastVersion,
- bool strictMatch)
- {
- bool matched = strictMatch ? currVersion == lastVersion : currVersion <= lastVersion;
- if (!matched) {
- LOG_HOST_TOOL_ERROR << fileDesc << " version error, expected version should be "
- << (strictMatch ? "equal to " : "less or equal than ") << ConvToStr(lastVersion)
- << ", but got " << ConvToStr(currVersion);
- return false;
- }
- return true;
- }
-
- bool InternalVerify(const char *fileDesc, const VersionType &lastVersion, bool strictMatch) const
+ bool VerifyVersion(const char *fileDesc, const VersionType &lastVersion, bool strictMatch) const
{
if (magic_ != MAGIC) {
LOG_HOST_TOOL_ERROR << "Magic mismatch, please make sure " << fileDesc
@@ -91,11 +78,37 @@ protected:
return true;
}
- bool InternalVerifyVersion(const VersionType &expectVersion) const
+ bool CompatibleVerify(const VersionType &expectVersion) const
{
return version_ >= expectVersion;
}
+ VersionType GetVersion() const
+ {
+ return version_;
+ }
+
+ void SetVersion(VersionType version)
+ {
+ version_ = version;
+ }
+
+protected:
+ explicit FileHeaderBase(const VersionType &lastVersion) : magic_(MAGIC), version_(lastVersion) {}
+
+ static bool VerifyVersion(const char *fileDesc, const VersionType &currVersion, const VersionType &lastVersion,
+ bool strictMatch)
+ {
+ bool matched = strictMatch ? (currVersion == lastVersion) : (currVersion <= lastVersion);
+ if (!matched) {
+ LOG_HOST_TOOL_ERROR << fileDesc << " version error, expected version should be "
+ << (strictMatch ? "equal to " : "less or equal than ") << ConvToStr(lastVersion)
+ << ", but got " << ConvToStr(currVersion);
+ return false;
+ }
+ return true;
+ }
+
std::string InternalGetVersion() const
{
return ConvToStr(version_);
@@ -124,5 +137,52 @@ private:
VersionType version_;
};
+class FileHeaderElastic : public FileHeaderBase {
+public:
+ static constexpr uint32_t ENDIAN_VALUE = 0x12345678;
+ void SetChecksum(uint32_t checksum)
+ {
+ checksum_ = checksum;
+ }
+
+ uint32_t GetChecksum() const
+ {
+ return checksum_;
+ }
+
+ void SetHeaderSize(uint32_t size)
+ {
+ headerSize_ = size;
+ }
+
+ uint32_t GetHeaderSize() const
+ {
+ return headerSize_;
+ }
+
+ void SetFileSize(uint32_t size)
+ {
+ fileSize_ = size;
+ }
+
+ uint32_t GetFileSize() const
+ {
+ return fileSize_;
+ }
+
+ uint32_t GetEndianTag() const
+ {
+ return endianTag_;
+ }
+
+protected:
+ explicit FileHeaderElastic(const VersionType &lastVersion) : FileHeaderBase(lastVersion) {}
+
+private:
+ uint32_t checksum_ {0};
+ uint32_t fileSize_ {0};
+ uint32_t headerSize_ {0};
+ uint32_t endianTag_ {ENDIAN_VALUE};
+};
} // namespace panda::ecmascript::base
#endif // ECMASCRIPT_BASE_FILE_HEADER_H
diff --git a/ecmascript/base/json_helper.cpp b/ecmascript/base/json_helper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c0fb8de8bd83dc65c3d352c95bfd24d0ced7449d
--- /dev/null
+++ b/ecmascript/base/json_helper.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ecmascript/base/json_helper.h"
+
+#include
+#include
+#include
+
+namespace panda::ecmascript::base {
+constexpr unsigned char CODE_SPACE = 0x20;
+constexpr int FOUR_HEX = 4;
+constexpr char ZERO_FIRST = static_cast(0xc0); // \u0000 => c0 80
+
+bool JsonHelper::IsFastValueToQuotedString(const char *value)
+{
+ if (strpbrk(value, "\"\\\b\f\n\r\t") != nullptr) {
+ return false;
+ }
+ while (*value != '\0') {
+ if ((*value > 0 && *value < CODE_SPACE) || *value == ZERO_FIRST) {
+ return false;
+ }
+ value++;
+ }
+ return true;
+}
+
+CString JsonHelper::ValueToQuotedString(CString str)
+{
+ CString product;
+ const char *value = str.c_str();
+ // fast mode
+ bool isFast = IsFastValueToQuotedString(value);
+ if (isFast) {
+ product += "\"";
+ product += str;
+ product += "\"";
+ return product;
+ }
+ // 1. Let product be code unit 0x0022 (QUOTATION MARK).
+ product += "\"";
+ // 2. For each code unit C in value
+ for (const char *c = value; *c != 0; ++c) {
+ switch (*c) {
+ /*
+ * a. If C is 0x0022 (QUOTATION MARK) or 0x005C (REVERSE SOLIDUS), then
+ * i. Let product be the concatenation of product and code unit 0x005C (REVERSE SOLIDUS).
+ * ii. Let product be the concatenation of product and C.
+ */
+ case '\"':
+ product += "\\\"";
+ break;
+ case '\\':
+ product += "\\\\";
+ break;
+ /*
+ * b. Else if C is 0x0008 (BACKSPACE), 0x000C (FORM FEED), 0x000A (LINE FEED), 0x000D (CARRIAGE RETURN),
+ * or 0x000B (LINE TABULATION), then
+ * i. Let product be the concatenation of product and code unit 0x005C (REVERSE SOLIDUS).
+ * ii. Let abbrev be the String value corresponding to the value of C as follows:
+ * BACKSPACE "b"
+ * FORM FEED (FF) "f"
+ * LINE FEED (LF) "n"
+ * CARRIAGE RETURN (CR) "r"
+ * LINE TABULATION "t"
+ * iii. Let product be the concatenation of product and abbrev.
+ */
+ case '\b':
+ product += "\\b";
+ break;
+ case '\f':
+ product += "\\f";
+ break;
+ case '\n':
+ product += "\\n";
+ break;
+ case '\r':
+ product += "\\r";
+ break;
+ case '\t':
+ product += "\\t";
+ break;
+ case ZERO_FIRST:
+ product += "\\u0000";
+ ++c;
+ break;
+ default:
+ // c. Else if C has a code unit value less than 0x0020 (SPACE), then
+ if (*c > 0 && *c < CODE_SPACE) {
+ /*
+ * i. Let product be the concatenation of product and code unit 0x005C (REVERSE SOLIDUS).
+ * ii. Let product be the concatenation of product and "u".
+ * iii. Let hex be the string result of converting the numeric code unit value of C to a String of
+ * four hexadecimal digits. Alphabetic hexadecimal digits are presented as lowercase Latin letters.
+ * iv. Let product be the concatenation of product and hex.
+ */
+ std::ostringstream oss;
+ oss << "\\u" << std::hex << std::setfill('0') << std::setw(FOUR_HEX) << static_cast(*c);
+ product += oss.str();
+ } else {
+ // Else,
+ // i. Let product be the concatenation of product and C.
+ product += *c;
+ }
+ }
+ }
+ // 3. Let product be the concatenation of product and code unit 0x0022 (QUOTATION MARK).
+ product += "\"";
+ // Return product.
+ return product;
+}
+} // namespace panda::ecmascript::base
\ No newline at end of file
diff --git a/ecmascript/base/json_helper.h b/ecmascript/base/json_helper.h
new file mode 100644
index 0000000000000000000000000000000000000000..7cd093f9c5c82a50df600704058b09af1e2e5e34
--- /dev/null
+++ b/ecmascript/base/json_helper.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ECMASCRIPT_BASE_JSON_HELPER_H
+#define ECMASCRIPT_BASE_JSON_HELPER_H
+
+#include "ecmascript/js_handle.h"
+#include "ecmascript/mem/c_string.h"
+#include "ecmascript/property_attributes.h"
+
+namespace panda::ecmascript::base {
+
+class JsonHelper {
+public:
+ static CString ValueToQuotedString(CString str);
+
+ static bool IsFastValueToQuotedString(const char *value);
+
+ static inline bool CompareKey(const std::pair, PropertyAttributes> &a,
+ const std::pair, PropertyAttributes> &b)
+ {
+ return a.second.GetDictionaryOrder() < b.second.GetDictionaryOrder();
+ }
+
+ static inline bool CompareNumber(const JSHandle &a, const JSHandle &b)
+ {
+ return a->GetNumber() < b->GetNumber();
+ }
+};
+
+} // namespace panda::ecmascript::base
+
+#endif // ECMASCRIPT_BASE_UTF_JSON_H
\ No newline at end of file
diff --git a/ecmascript/base/json_parser.cpp b/ecmascript/base/json_parser.cpp
index 69dbf3405e6f4a5a46f76f64072f10b80e5a2d5a..6cd134cbfa696edc3e58138cf0c2e15c92727d78 100644
--- a/ecmascript/base/json_parser.cpp
+++ b/ecmascript/base/json_parser.cpp
@@ -22,9 +22,11 @@ JSHandle Internalize::InternalizeJsonProperty(JSThread *thread, c
{
JSHandle objHandle(holder);
JSHandle val = JSTaggedValue::GetProperty(thread, objHandle, name).GetValue();
+ RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
JSHandle lengthKey = thread->GlobalConstants()->GetHandledLengthString();
if (val->IsECMAObject()) {
JSHandle obj = JSTaggedValue::ToObject(thread, val);
+ RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
bool isArray = val->IsArray(thread);
if (isArray) {
JSHandle lenResult = JSTaggedValue::GetProperty(thread, val, lengthKey).GetValue();
@@ -38,6 +40,7 @@ JSHandle Internalize::InternalizeJsonProperty(JSThread *thread, c
// Let prop be ! ToString((I)).
keyUnknow.Update(JSTaggedValue(i));
keyName.Update(JSTaggedValue::ToString(thread, keyUnknow).GetTaggedValue());
+ RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
RecurseAndApply(thread, obj, keyName, receiver);
}
} else {
@@ -54,12 +57,13 @@ JSHandle
|
|---|
|
|---|