diff --git a/static_core/plugins/ets/runtime/CMakeLists.txt b/static_core/plugins/ets/runtime/CMakeLists.txt index 2e414585802c07618aafee6284cbc06b036b4e7c..fcab0ebf91c9a9f1839a2a669990315da38feb2b 100644 --- a/static_core/plugins/ets/runtime/CMakeLists.txt +++ b/static_core/plugins/ets/runtime/CMakeLists.txt @@ -80,6 +80,7 @@ set(ETS_RUNTIME_SOURCES ${ETS_EXT_SOURCES}/intrinsics/helpers/array_buffer_helper.cpp ${ETS_EXT_SOURCES}/intrinsics/helpers/ets_intrinsics_helpers.cpp ${ETS_EXT_SOURCES}/intrinsics/helpers/ets_to_string_cache.cpp + ${ETS_EXT_SOURCES}/intrinsics/helpers/json_helper.cpp ${ETS_EXT_SOURCES}/finalreg/finalization_registry_manager.cpp ${ETS_EXT_SOURCES}/unhandled_manager/unhandled_object_manager.cpp ${ETS_EXT_SOURCES}/mem/ets_reference_processor.cpp diff --git a/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml b/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml index a9342f1d9e435733b4572968261dc2472c4a91a1..fe92e0c5c43054b8c1f858e770a6cf4b4b05af15 100644 --- a/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml +++ b/static_core/plugins/ets/runtime/ets_libbase_runtime.yaml @@ -651,6 +651,20 @@ intrinsics: args: [ std.core.String ] impl: ark::ets::intrinsics::LoadLibrary +################### +# Escompat.JSON # +################### + + - name: EscompatJSONStringifyFast + space: ets + class_name: escompat.JSON + method_name: stringifyFast + static: true + signature: + ret: std.core.String + args: [ std.core.Object ] + impl: ark::ets::intrinsics::EscompatJSONStringifyFast + ################### # std.core.String # ################### diff --git a/static_core/plugins/ets/runtime/ets_panda_file_items.h b/static_core/plugins/ets/runtime/ets_panda_file_items.h index 4a645d3f0f07d87813adacda80272bd6e1105554..481cce53b306855c04bd7d676e3cfde8ee98bc96 100644 --- a/static_core/plugins/ets/runtime/ets_panda_file_items.h +++ b/static_core/plugins/ets/runtime/ets_panda_file_items.h @@ -204,6 +204,8 @@ static constexpr std::string_view ES_ERROR = "Lstd/i static constexpr std::string_view ARRAY = "Lescompat/Array;"; static constexpr std::string_view ARRAY_AS_LIST_INT = "Lstd/containers/ArrayAsListInt;"; +static constexpr std::string_view REG_EXP_EXEC_ARRAY = "Lescompat/RegExpExecArray;"; +static constexpr std::string_view JSON_REPLACER = "Lescompat/JsonReplacer;"; // ANI annotation classes static constexpr std::string_view ANI_UNSAFE_QUICK = "Lstd/annotations/ani/unsafe/Quick;"; diff --git a/static_core/plugins/ets/runtime/ets_platform_types.cpp b/static_core/plugins/ets/runtime/ets_platform_types.cpp index 666d5e8c66141201c49b68dd7e02e8a9800cb947..81ad06cd54fb09e3a88b1c052bcaee9d8eefaa4b 100644 --- a/static_core/plugins/ets/runtime/ets_platform_types.cpp +++ b/static_core/plugins/ets/runtime/ets_platform_types.cpp @@ -192,6 +192,10 @@ EtsPlatformTypes::EtsPlatformTypes([[maybe_unused]] EtsCoroutine *coro) "Lescompat/Array;:V", true); findMethod(&EtsPlatformTypes::escompatProcessListUnhandledPromises, escompatProcess, "listUnhandledPromises", "Lescompat/Array;:V", true); + + findType(&EtsPlatformTypes::coreTuple, TUPLE); + findType(&EtsPlatformTypes::escompatRegExpExecArray, REG_EXP_EXEC_ARRAY); + findType(&EtsPlatformTypes::escompatJsonReplacer, JSON_REPLACER); } } // namespace ark::ets diff --git a/static_core/plugins/ets/runtime/ets_platform_types.h b/static_core/plugins/ets/runtime/ets_platform_types.h index 5f6768b47779b32585c71141145304ea281ee2a5..52f8efb8346810b544b76bb23501246bb15ca399 100644 --- a/static_core/plugins/ets/runtime/ets_platform_types.h +++ b/static_core/plugins/ets/runtime/ets_platform_types.h @@ -110,6 +110,10 @@ public: EtsMethod *escompatProcessListUnhandledJobs {}; EtsMethod *escompatProcessListUnhandledPromises {}; + EtsClass *coreTuple {}; + EtsClass *escompatRegExpExecArray {}; + EtsClass *escompatJsonReplacer {}; + struct Entry { size_t slotIndex {}; }; diff --git a/static_core/plugins/ets/runtime/intrinsics/escompat_JSON.cpp b/static_core/plugins/ets/runtime/intrinsics/escompat_JSON.cpp index 86c598a602b489f81a33619efdd59b69775f59ea..7c0c38d9bdb6d6a9d154c3b64901d337ec3274d2 100644 --- a/static_core/plugins/ets/runtime/intrinsics/escompat_JSON.cpp +++ b/static_core/plugins/ets/runtime/intrinsics/escompat_JSON.cpp @@ -15,6 +15,7 @@ #include "intrinsics.h" #include "plugins/ets/runtime/ets_utils.h" +#include "helpers/json_helper.h" namespace ark::ets::intrinsics { @@ -139,4 +140,16 @@ EtsString *EscompatJSONGetJSONRenameByName(EtsClass *cls, EtsString *name) return retStrHandle.GetPtr(); } -} // namespace ark::ets::intrinsics \ No newline at end of file +extern "C" EtsString *EscompatJSONStringifyFast(EtsObject *value) +{ + auto coro = EtsCoroutine::GetCurrent(); + ASSERT(coro->HasPendingException() == false); + + EtsHandleScope scope(coro); + EtsHandle valueHandle(coro, value); + + helpers::JSONStringifier stringifier; + return stringifier.Stringify(valueHandle); +} + +} // namespace ark::ets::intrinsics diff --git a/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.cpp b/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e36bacb3b319ef8fc02ef502fb3580cca61d93b9 --- /dev/null +++ b/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.cpp @@ -0,0 +1,616 @@ +/* + * Copyright (c) 2025 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 "ets_exceptions.h" +#include "ets_handle.h" +#include "include/thread_scopes.h" +#include "types/ets_escompat_array.h" +#include "types/ets_object.h" +#include "json_helper.h" +#include "ets_to_string_cache.h" +namespace ark::ets::intrinsics::helpers { + +static constexpr int RECORD_HEAD_ENTRY_INDEX = 3; +static constexpr int RECORD_NEXT_FIELD_INDEX = 1; +static constexpr int RECORD_KEY_FIELD_INDEX = 2; +static constexpr int RECORD_VAL_FIELD_INDEX = 3; + +bool JSONStringifier::AppendJSONString(EtsHandle &value, bool hasContent) +{ + if (hasContent) { + buffer_ += ","; + } + buffer_ += "\""; + buffer_ += key_; + buffer_ += "\":"; + return SerializeObject(value); +} + +void JSONStringifier::AppendJSONPrimitive(const PandaString &value, bool hasContent) +{ + if (hasContent) { + buffer_ += ","; + } + + buffer_ += "\""; + buffer_ += key_; + buffer_ += "\":"; + + if ((value == "NaN") || (value == "Infinity") || (value == "-Infinity")) { + buffer_ += "null"; + } else { + buffer_ += value; + } +} + +void JSONStringifier::AppendJSONPointPrimitive(const PandaString &value, bool hasContent) +{ + if (hasContent) { + buffer_ += ","; + } + + buffer_ += "\""; + buffer_ += key_; + buffer_ += "\":"; + + if ((value == "NaN") || (value == "Infinity") || (value == "-Infinity")) { + buffer_ += "null"; + } else { + buffer_ += value; + } +} + +bool JSONStringifier::SerializeFields(EtsHandle &value) +{ + bool hasContent = false; + auto keys = PandaUnorderedSet(); + bool isSuccessful = false; + + value->GetClass()->EnumerateBaseClasses([&](EtsClass *c) { + auto fields = c->GetRuntimeClass()->GetFields(); + for (auto &field : fields) { + if (!HandleField(value, EtsField::FromRuntimeField(&field), hasContent, keys)) { + isSuccessful = false; + return true; + } + isSuccessful = true; + } + return false; + }); + return isSuccessful; +} + +bool JSONStringifier::SerializeJSONObject(EtsHandle &value) +{ + bool isContain = PushValue(value); + if (isContain) { + ThrowEtsException(EtsCoroutine::GetCurrent(), panda_file_items::class_descriptors::TYPE_ERROR, + "cyclic object value"); + return false; + } + buffer_ += "{"; + if (!SerializeFields(value)) { + return false; + }; + buffer_ += "}"; + return true; +} + +bool JSONStringifier::SerializeJSONObjectArray(EtsHandle &value) +{ + auto coro = EtsCoroutine::GetCurrent(); + EtsHandleScope scope(coro); + + bool isSuccessful = false; + buffer_ += "["; + + auto array = EtsHandle>(coro, EtsArrayObject::FromEtsObject(value.GetPtr())); + auto realArray = EtsHandle(coro, array->GetData()); + auto length = array->GetActualLength(); + + for (size_t i = 0; i < length; ++i) { + auto elem = EtsHandle(coro, realArray->Get(i)); + if (elem.GetPtr() == nullptr) { + continue; + } + if (elem->GetClass()->IsFunction()) { + buffer_ += "null"; + isSuccessful = true; + } else { + isSuccessful = SerializeObject(elem); + } + if (!isSuccessful) { + return false; + } + if (i != length - 1) { + buffer_ += ","; + } + } + + buffer_ += "]"; + return isSuccessful; +} + +bool JSONStringifier::DealSpecialCharacter(uint8_t ch) +{ + bool isSpecialCharacter = true; + switch (ch) { + case '\"': + buffer_ += "\\\""; + break; + case '\\': + buffer_ += "\\\\"; + break; + case '\b': + buffer_ += "\\b"; + break; + case '\f': + buffer_ += "\\f"; + break; + case '\n': + buffer_ += "\\n"; + break; + case '\r': + buffer_ += "\\r"; + break; + case '\t': + buffer_ += "\\t"; + break; + case '\0': + buffer_ += "\\u0000"; + break; + default: + if (ch < CODE_SPACE) { + AppendUnicodeEscape(static_cast(ch)); + } else { + isSpecialCharacter = false; + } + } + return isSpecialCharacter; +} + +void JSONStringifier::DealUtf16Character(uint16_t ch0, uint16_t ch1) +{ + if ((IsHighSurrogate(ch0) && !IsLowSurrogate(ch1)) || IsLowSurrogate(ch0)) { + AppendUnicodeEscape(static_cast(ch0)); + return; + } + + utf::Utf8Char uc = utf::ConvertUtf16ToUtf8(ch0, ch1, modify_); + for (size_t j = 0; j < uc.n; ++j) { + if (uc.n == utf::UtfLength::ONE && DealSpecialCharacter(uc.ch[j])) { + break; + } + buffer_ += uc.ch[j]; + } +} + +void JSONStringifier::AppendUtf16ToQuotedString(const Span &sp) +{ + buffer_ += "\""; + uint16_t next16Code = 0; + for (uint32_t i = 0; i < sp.size(); ++i) { + const auto ch = sp[i]; + if (IsHighSurrogate(ch) && (i + 1 < sp.size()) && IsLowSurrogate(sp[i + 1])) { + next16Code = sp[i + 1]; + i++; + } else { + next16Code = 0; + } + DealUtf16Character(ch, next16Code); + } + buffer_ += "\""; +} + +uint32_t JSONStringifier::AppendUtf8Character(const Span &sp, uint32_t index) +{ + uint8_t len {0}; + const auto ch = sp[index]; + if (ch < utf::BIT_MASK_1) { + len = utf::UtfLength::ONE; + } else if ((ch & utf::BIT_MASK_3) == utf::BIT_MASK_2) { + len = utf::UtfLength::TWO; + } else if ((ch & utf::BIT_MASK_4) == utf::BIT_MASK_3) { + len = utf::UtfLength::THREE; + } else if ((ch & utf::BIT_MASK_5) == utf::BIT_MASK_4) { + len = utf::UtfLength::FOUR; + } else { + UNREACHABLE(); + } + ASSERT(index + len <= sp.size()); + for (uint32_t i = 0; i < len; i++) { + buffer_ += sp[index + i]; + } + return len; +} + +void JSONStringifier::AppendUtf8ToQuotedString(const Span &sp) +{ + buffer_ += "\""; + for (uint32_t i = 0; i < sp.size();) { + const auto ch = sp[i]; + if (DealSpecialCharacter(ch)) { + i++; + continue; + } + i += AppendUtf8Character(sp, i); + } + buffer_ += "\""; +} + +bool JSONStringifier::SerializeJSONString(EtsHandle &value) +{ + EtsHandleScope scope(EtsCoroutine::GetCurrent()); + auto stringHandle = EtsHandle(EtsCoroutine::GetCurrent(), EtsString::FromEtsObject(value.GetPtr())); + if (stringHandle->IsEmpty()) { + buffer_ += "\"\""; + } else if (stringHandle->IsUtf16()) { + Span sp(stringHandle->GetDataUtf16(), stringHandle->GetLength()); + AppendUtf16ToQuotedString(sp); + } else { + Span sp(stringHandle->GetDataMUtf8(), stringHandle->GetLength()); + AppendUtf8ToQuotedString(sp); + } + return true; +} + +bool JSONStringifier::SerializeJSONBoxedBoolean(EtsHandle &value) +{ + auto val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + if (val == 0) { + buffer_ += "false"; + } else { + buffer_ += "true"; + } + return true; +} + +bool JSONStringifier::SerializeJSONBoxedDouble(EtsHandle &value) +{ + auto val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + auto cache = PandaEtsVM::GetCurrent()->GetDoubleToStringCache(); + auto coro = EtsCoroutine::GetCurrent(); + PandaString v = cache->GetOrCache(coro, val)->GetMutf8(); + if ((v == "NaN") || (v == "Infinity") || (v == "-Infinity")) { + buffer_ += "null"; + } else { + buffer_ += v; + } + return true; +} + +bool JSONStringifier::SerializeJSONBoxedFloat(EtsHandle &value) +{ + auto val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + auto cache = PandaEtsVM::GetCurrent()->GetFloatToStringCache(); + auto coro = EtsCoroutine::GetCurrent(); + PandaString v = cache->GetOrCache(coro, val)->GetMutf8(); + if ((v == "NaN") || (v == "Infinity") || (v == "-Infinity")) { + buffer_ += "null"; + } else { + buffer_ += v; + } + return true; +} + +bool JSONStringifier::SerializeJSONBoxedLong(EtsHandle &value) +{ + auto val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + auto cache = PandaEtsVM::GetCurrent()->GetLongToStringCache(); + auto coro = EtsCoroutine::GetCurrent(); + PandaString v = cache->GetOrCache(coro, val)->GetMutf8(); + if ((v == "NaN") || (v == "Infinity") || (v == "-Infinity")) { + buffer_ += "null"; + } else { + buffer_ += v; + } + return true; +} + +template +bool JSONStringifier::SerializeJSONBoxedPrimitiveNoCache(EtsHandle &value) +{ + T val = EtsBoxPrimitive::FromCoreType(value.GetPtr())->GetValue(); + if constexpr (std::is_same_v) { + buffer_ += "\""; + buffer_ += static_cast(val); + buffer_ += "\""; + } else { + buffer_ += std::to_string(val); + } + return true; +} + +bool JSONStringifier::SerializeEmptyObject() +{ + buffer_ += "{}"; + return true; +} + +bool JSONStringifier::SerializeJSONNullValue() +{ + buffer_ += "null"; + return true; +} + +bool JSONStringifier::SerializeJSONRecord(EtsHandle &value) +{ + buffer_ += "{"; + auto cls = value->GetClass(); + auto headEntry = cls->GetFieldByIndex(RECORD_HEAD_ENTRY_INDEX); + auto coro = EtsCoroutine::GetCurrent(); + + EtsHandleScope scope(coro); + EtsHandle head(coro, value->GetFieldObject(headEntry)); + + auto hasContent = false; + EtsHandle next(coro, head->GetFieldObject(head->GetClass()->GetFieldByIndex(RECORD_NEXT_FIELD_INDEX))); + do { + EtsHandle key(coro, next->GetFieldObject(next->GetClass()->GetFieldByIndex(RECORD_KEY_FIELD_INDEX))); + EtsHandle val(coro, next->GetFieldObject(next->GetClass()->GetFieldByIndex(RECORD_VAL_FIELD_INDEX))); + next = EtsHandle(coro, + next->GetFieldObject(next->GetClass()->GetFieldByIndex(RECORD_NEXT_FIELD_INDEX))); + if (val.GetPtr() == nullptr) { + continue; + } + if (val->GetClass()->IsFunction()) { + continue; + } + if (key->IsStringClass()) { + key_ = EtsString::FromEtsObject(key.GetPtr())->GetMutf8(); + } else { + auto intVal = EtsBoxPrimitive::FromCoreType(key.GetPtr())->GetValue(); + key_ = std::to_string(intVal); + } + AppendJSONString(val, hasContent); + hasContent = true; + } while (head.GetPtr() != next.GetPtr() && next.GetPtr() != nullptr); + buffer_ += "}"; + return true; +} + +bool JSONStringifier::PushValue(EtsHandle &value) +{ + if (path_.find(value->GetHashCode()) != path_.end()) { + return true; + } + path_.insert(value->GetHashCode()); + return false; +} + +PandaString JSONStringifier::ResolveDisplayName(const EtsField *field) +{ + auto fieldNameData = field->GetCoreType()->GetName(); + auto fieldNameLength = fieldNameData.utf16Length; + std::string_view fieldName(utf::Mutf8AsCString(fieldNameData.data), fieldNameLength); + if (fieldName.rfind(PROPERTY, 0) == 0) { + ASSERT(fieldNameLength > PROPERTY_PREFIX_LENGTH); + return PandaString(reinterpret_cast( + fieldNameData.data + // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + PROPERTY_PREFIX_LENGTH), + fieldNameLength - PROPERTY_PREFIX_LENGTH); + } + return PandaString(reinterpret_cast(fieldNameData.data), fieldNameLength); +} + +bool JSONStringifier::CheckUnsupportedAnnotation(EtsField *field) +{ + auto *runtimeClass = field->GetDeclaringClass()->GetRuntimeClass(); + const panda_file::File &pf = *runtimeClass->GetPandaFile(); + panda_file::FieldDataAccessor fda(pf, field->GetRuntimeField()->GetFileId()); + bool hasAnnotation = false; + fda.EnumerateAnnotations([&hasAnnotation]([[maybe_unused]] panda_file::File::EntityId annId) { + hasAnnotation = true; + return; + }); + return hasAnnotation; +} + +// CC-OFFNXT(huge_method[C++], huge_cyclomatic_complexity[C++], G.FUN.01-CPP) solid logic +// CC-OFFNXT(huge_cca_cyclomatic_complexity[C++]) solid logic +bool JSONStringifier::SerializeObject(EtsHandle &value) +{ + if (value.GetPtr() == nullptr) { + return true; + } + + auto coro = EtsCoroutine::GetCurrent(); + auto platformTypes = PlatformTypes(coro); + bool isSuccessful = false; + + auto desc = value->GetClass()->GetDescriptor(); + if (desc == panda_file_items::class_descriptors::BOX_BOOLEAN) { + isSuccessful = SerializeJSONBoxedBoolean(value); + } else if (desc == panda_file_items::class_descriptors::BOX_DOUBLE) { + isSuccessful = SerializeJSONBoxedDouble(value); + } else if (desc == panda_file_items::class_descriptors::BOX_FLOAT) { + isSuccessful = SerializeJSONBoxedFloat(value); + } else if (desc == panda_file_items::class_descriptors::BOX_LONG) { + isSuccessful = SerializeJSONBoxedLong(value); + } else if (desc == panda_file_items::class_descriptors::BOX_BYTE) { + isSuccessful = SerializeJSONBoxedPrimitiveNoCache(value); + } else if (desc == panda_file_items::class_descriptors::BOX_SHORT) { + isSuccessful = SerializeJSONBoxedPrimitiveNoCache(value); + } else if (desc == panda_file_items::class_descriptors::BOX_CHAR) { + isSuccessful = SerializeJSONBoxedPrimitiveNoCache(value); + } else if (desc == panda_file_items::class_descriptors::BOX_INT) { + isSuccessful = SerializeJSONBoxedPrimitiveNoCache(value); + } else if (desc == panda_file_items::class_descriptors::STRING) { + isSuccessful = SerializeJSONString(value); + } else if (desc == panda_file_items::class_descriptors::RECORD) { + coro->ManagedCodeEnd(); + { + ScopedManagedCodeThread v(coro); + isSuccessful = SerializeJSONRecord(value); + } + coro->ManagedCodeBegin(); + } else if (desc == panda_file_items::class_descriptors::DATE) { + isSuccessful = false; + } else if (desc == panda_file_items::class_descriptors::ARRAY) { + coro->ManagedCodeEnd(); + { + ScopedManagedCodeThread v(coro); + isSuccessful = SerializeJSONObjectArray(value); + } + coro->ManagedCodeBegin(); + } else if (desc == panda_file_items::class_descriptors::PROMISE || + desc == panda_file_items::class_descriptors::SET || desc == panda_file_items::class_descriptors::MAP || + desc == panda_file_items::class_descriptors::MAPENTRY) { + isSuccessful = SerializeEmptyObject(); + } else if (desc == panda_file_items::class_descriptors::NULL_VALUE) { + isSuccessful = SerializeJSONNullValue(); + } else if (desc == panda_file_items::class_descriptors::JS_VALUE) { + isSuccessful = false; + } else { + if (value->IsInstanceOf(platformTypes->escompatRegExpExecArray)) { + coro->ManagedCodeEnd(); + { + ScopedManagedCodeThread v(coro); + auto result = EtsHandle(coro, value->GetFieldObject(value->GetClass()->GetFieldByIndex(1))); + isSuccessful = SerializeJSONObjectArray(result); + } + coro->ManagedCodeBegin(); + } else if (value->IsInstanceOf(platformTypes->escompatJsonReplacer)) { + PandaVector args {Value(value->GetCoreType())}; + auto jsonReplacerMethod = value->GetClass()->GetInstanceMethod("jsonReplacer", nullptr)->GetPandaMethod(); + auto ret = jsonReplacerMethod->Invoke(coro, args.data()); + if (UNLIKELY(coro->HasPendingException())) { + return false; + } + auto retobj = EtsHandle(coro, EtsObject::FromCoreType(ret.GetAs())); + coro->ManagedCodeEnd(); + { + isSuccessful = SerializeJSONRecord(retobj); + } + coro->ManagedCodeBegin(); + } else if (value->IsArrayClass()) { + coro->ManagedCodeEnd(); + { + ScopedManagedCodeThread v(coro); + isSuccessful = SerializeJSONObjectArray(value); + } + coro->ManagedCodeBegin(); + } else if (value->GetClass()->IsFunction()) { + buffer_ += "undefined"; + } else if (value->IsInstanceOf(platformTypes->coreTuple)) { + isSuccessful = false; + } else if (value->GetClass()->IsClass()) { + coro->ManagedCodeEnd(); + { + ScopedManagedCodeThread v(coro); + isSuccessful = SerializeJSONObject(value); + } + coro->ManagedCodeBegin(); + } else { + LOG(ERROR, ETS) << "Unsupported type: " << value->GetClass()->GetDescriptor(); + return false; + } + } + return isSuccessful; +} + +// CC-OFFNXT(huge_method[C++], G.FUN.01-CPP) solid logic +bool JSONStringifier::HandleField(EtsHandle &obj, EtsField *etsField, bool &hasContent, + PandaUnorderedSet &keys) +{ + if (etsField->IsStatic()) { + return true; + } + + if (CheckUnsupportedAnnotation(etsField)) { + return false; + } + key_ = ResolveDisplayName(etsField); + if (keys.find(key_) != keys.end()) { + return false; + } + keys.insert(key_); + auto coro = EtsCoroutine::GetCurrent(); + auto etsType = etsField->GetEtsType(); + + switch (etsType) { + case EtsType::BOOLEAN: { + auto val = obj->GetFieldPrimitive(etsField); + PandaString value = val == 0 ? "false" : "true"; + AppendJSONPrimitive(value, hasContent); + break; + } + case EtsType::BYTE: { + auto val = obj->GetFieldPrimitive(etsField); + AppendJSONPrimitive(PandaString(std::to_string(val)), hasContent); + break; + } + case EtsType::CHAR: { + auto val = obj->GetFieldPrimitive(etsField); + AppendJSONPrimitive(PandaString(std::to_string(val)), hasContent); + break; + } + case EtsType::SHORT: { + auto val = obj->GetFieldPrimitive(etsField); + AppendJSONPrimitive(PandaString(std::to_string(val)), hasContent); + break; + } + case EtsType::INT: { + auto val = obj->GetFieldPrimitive(etsField); + AppendJSONPrimitive(PandaString(std::to_string(val)), hasContent); + break; + } + case EtsType::LONG: { + auto val = obj->GetFieldPrimitive(etsField); + auto cache = PandaEtsVM::GetCurrent()->GetLongToStringCache(); + PandaString v = cache->GetOrCache(coro, val)->GetMutf8(); + AppendJSONPrimitive(v, hasContent); + break; + } + case EtsType::FLOAT: { + auto val = obj->GetFieldPrimitive(etsField); + auto cache = PandaEtsVM::GetCurrent()->GetFloatToStringCache(); + PandaString v = cache->GetOrCache(coro, val)->GetMutf8(); + AppendJSONPrimitive(v, hasContent); + break; + } + case EtsType::DOUBLE: { + auto val = obj->GetFieldPrimitive(etsField); + auto cache = PandaEtsVM::GetCurrent()->GetDoubleToStringCache(); + PandaString v = cache->GetOrCache(coro, val)->GetMutf8(); + AppendJSONPrimitive(v, hasContent); + break; + } + default: + auto fieldObj = EtsHandle(EtsCoroutine::GetCurrent(), obj->GetFieldObject(etsField)); + if (fieldObj.GetPtr() == nullptr || fieldObj->GetClass()->IsFunction()) { + return true; + } + if (!AppendJSONString(fieldObj, hasContent)) { + return false; + } + break; + } + hasContent = true; + return true; +} + +EtsString *JSONStringifier::Stringify(EtsHandle &value) +{ + bool result = false; + auto coro = EtsCoroutine::GetCurrent(); + result = SerializeObject(value); + if (!result || coro->HasPendingException()) { + return nullptr; + } + return EtsString::CreateFromUtf8(buffer_.c_str(), buffer_.length()); +} + +} // namespace ark::ets::intrinsics::helpers diff --git a/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.h b/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.h new file mode 100644 index 0000000000000000000000000000000000000000..2f40433b2caae10358ad89b3e5700ddc1bdf2e50 --- /dev/null +++ b/static_core/plugins/ets/runtime/intrinsics/helpers/json_helper.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025 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 PANDA_PLUGINS_ETS_RUNTIME_JSON_HELPER +#define PANDA_PLUGINS_ETS_RUNTIME_JSON_HELPER + +#include "types/ets_string.h" + +namespace ark::ets::intrinsics::helpers { +class JSONStringifier { +public: + explicit JSONStringifier() = default; + + EtsString *Stringify(EtsHandle &value); + +private: + static inline bool IsHighSurrogate(uint16_t ch) + { + return ch >= utf::DECODE_LEAD_LOW && ch <= utf::DECODE_LEAD_HIGH; + } + + static inline bool IsLowSurrogate(uint16_t ch) + { + return ch >= utf::DECODE_TRAIL_LOW && ch <= utf::DECODE_TRAIL_HIGH; + } + + inline void AppendUnicodeEscape(uint32_t ch) + { + buffer_ += "\\u"; + buffer_ += HEX_DIGIT[(ch >> HEX_SHIFT_THREE) & HEX_DIGIT_MASK]; + buffer_ += HEX_DIGIT[(ch >> HEX_SHIFT_TWO) & HEX_DIGIT_MASK]; + buffer_ += HEX_DIGIT[(ch >> HEX_SHIFT_ONE) & HEX_DIGIT_MASK]; + buffer_ += HEX_DIGIT[ch & HEX_DIGIT_MASK]; + } + +private: + bool AppendJSONString(EtsHandle &value, bool hasContent); + void AppendJSONPrimitive(const PandaString &value, bool hasContent); + void AppendJSONPointPrimitive(const PandaString &value, bool hasContent); + + bool SerializeFields(EtsHandle &value); + + // handling of types + bool SerializeJSONObject(EtsHandle &value); + bool SerializeJSONObjectArray(EtsHandle &value); + + void AppendUtf16ToQuotedString(const Span &sp); + void AppendUtf8ToQuotedString(const Span &sp); + uint32_t AppendUtf8Character(const Span &sp, uint32_t index); + void DealUtf16Character(uint16_t ch0, uint16_t ch1); + bool SerializeJSONString(EtsHandle &value); + bool DealSpecialCharacter(uint8_t ch); + + bool SerializeJSONBoxedBoolean(EtsHandle &value); + bool SerializeJSONBoxedDouble(EtsHandle &value); + bool SerializeJSONBoxedFloat(EtsHandle &value); + bool SerializeJSONBoxedLong(EtsHandle &value); + + template + bool SerializeJSONBoxedPrimitiveNoCache(EtsHandle &value); + + bool SerializeEmptyObject(); + bool SerializeJSONNullValue(); + bool SerializeJSONRecord(EtsHandle &value); + + bool PushValue(EtsHandle &value); + + PandaString ResolveDisplayName(const EtsField *field); + + bool SerializeObject(EtsHandle &value); + bool HandleField(EtsHandle &obj, EtsField *field, bool &hasContent, + PandaUnorderedSet &keys); + + bool CheckUnsupportedAnnotation(EtsField *field); + +private: + PandaString buffer_; + PandaString key_; + PandaUnorderedSet path_; + + const bool modify_ = false; + static constexpr uint8_t CODE_SPACE = 0x20; + static constexpr unsigned HEX_DIGIT_MASK = 0xF; + static constexpr unsigned HEX_SHIFT_THREE = 12; + static constexpr unsigned HEX_SHIFT_TWO = 8; + static constexpr unsigned HEX_SHIFT_ONE = 4; + + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + static constexpr char HEX_DIGIT[] = "0123456789abcdef"; +}; +} // namespace ark::ets::intrinsics::helpers +#endif // PANDA_PLUGINS_ETS_RUNTIME_JSON_HELPER diff --git a/static_core/plugins/ets/stdlib/escompat/json.ets b/static_core/plugins/ets/stdlib/escompat/json.ets index 79b98302cebd28a73192683cd1fbdb3bd0672c45..14a436668944a1aedd10c878675d2553faa90847 100644 --- a/static_core/plugins/ets/stdlib/escompat/json.ets +++ b/static_core/plugins/ets/stdlib/escompat/json.ets @@ -245,9 +245,18 @@ export class JSON { * @returns String - JSON representation of Object */ public static stringify(obj: NullishType): String { + if (obj === undefined) { + return "undefined" + } + let output = JSON.stringifyFast(obj) + if (output !== undefined) { + return output + } return new JSONWriter().write(obj) } + private static native stringifyFast(obj: NullishType): String; + public static stringify(obj: JsonReplacer): String { const record = obj.jsonReplacer(); return JSON.stringify(record); diff --git a/static_core/plugins/ets/subproject_sources.gn b/static_core/plugins/ets/subproject_sources.gn index 278ef1c68d4e76db1c9595b744fdebeb2394a6e5..b1c031d831bbbb8afc3ea6b4504f1c1f239a1b60 100644 --- a/static_core/plugins/ets/subproject_sources.gn +++ b/static_core/plugins/ets/subproject_sources.gn @@ -162,6 +162,7 @@ srcs_runtime = [ "runtime/intrinsics/helpers/array_buffer_helper.cpp", "runtime/intrinsics/helpers/ets_intrinsics_helpers.cpp", "runtime/intrinsics/helpers/ets_to_string_cache.cpp", + "runtime/intrinsics/helpers/json_helper.cpp", "runtime/mem/ets_reference_processor.cpp", "runtime/napi/ets_napi_helpers.cpp", "runtime/napi/ets_napi_invoke_interface.cpp", diff --git a/static_core/runtime/include/class.h b/static_core/runtime/include/class.h index a6121da5975ae474ead8522102d18625d9b97725..d3381150caf96dbf869b8c5626a45e6260991ae8 100644 --- a/static_core/runtime/include/class.h +++ b/static_core/runtime/include/class.h @@ -212,6 +212,16 @@ public: return {fields_, numFields_}; } + Field *GetRawFirstFieldAddr() const + { + return fields_; + } + + uint32_t GetNumFields() const + { + return numFields_; + } + Span GetStaticFields() const { return {fields_, numSfields_};