/* * Copyright (c) 2023-2024 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/json_helper.h" #include "ecmascript/global_dictionary-inl.h" #include "ecmascript/interpreter/interpreter.h" #include "ecmascript/js_primitive_ref.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: case JSType::JS_SHARED_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: case JSType::SLICED_STRING: { JSHandle strHandle = JSHandle(valHandle); auto string = JSHandle(thread_, EcmaStringAccessor::Flatten(thread_->GetEcmaVM(), strHandle)); CString str = ConvertToString(*string, StringConvertedUsage::LOGICOPERATION); JsonHelper::AppendValueToQuotedString(str, result_); 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); } JsonHelper::AppendValueToQuotedString(str, result_); 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, usually caused by circular structure", 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, usually caused by circular structure", 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, usually caused by circular structure", true); } result_ += "["; uint32_t len = 0; if (value->IsJSArray()) { JSHandle jsArr(value); len = jsArr->GetArrayLength(); } else if (value->IsJSSharedArray()) { JSHandle jsArr(value); 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); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } 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); JsonHelper::AppendValueToQuotedString(str, result_); } 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) { if (!ElementAccessor::IsDictionaryMode(obj)) { uint32_t elementsLen = ElementAccessor::GetElementsLength(obj); for (uint32_t i = 0; i < elementsLen; ++i) { if (!ElementAccessor::Get(obj, i).IsHole()) { handleKey_.Update(JSTaggedValue(i)); handleValue_.Update(ElementAccessor::Get(obj, i)); hasContent = AppendJsonString(hasContent, strCache, i); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } } } else { JSHandle elementsArr(thread_, obj->GetElements()); 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } 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) { if (!ElementAccessor::IsDictionaryMode(obj)) { uint32_t elementsLen = ElementAccessor::GetElementsLength(obj); for (uint32_t i = 0; i < elementsLen; ++i) { if (!ElementAccessor::Get(obj, i).IsHole()) { CString key = strCache[cacheIndex++].first; handleValue_.Update(ElementAccessor::Get(obj, i)); hasContent = FastAppendJsonString(hasContent, key); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } } } else { JSHandle elementsArr(thread_, obj->GetElements()); 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } 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()) { return TryCacheSerializeKeysFromPropertiesArray(obj, hasContent, strCache); } if (obj->IsJSGlobalObject()) { return TryCacheSerializeKeysFromGlobalObject(obj, hasContent, strCache); } return TryCacheSerializeKeysFromNameDictionary(obj, hasContent, strCache); } bool FastJsonStringifier::TryCacheSerializeKeysFromPropertiesArray(const JSHandle &obj, bool hasContent, CVector> &strCache) { JSHandle propertiesArr(thread_, obj->GetProperties()); JSHandle jsHclass(thread_, obj->GetJSHClass()); JSTaggedValue enumCache = jsHclass->GetEnumCache(); if (JSObject::GetEnumCacheKind(thread_, enumCache) == EnumCacheKind::ONLY_OWN_KEYS) { return TryCacheSerializeKeysFromEnumCache(obj, hasContent, strCache); } 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } handleValue_.Update(value); hasContent = AppendJsonString(hasContent, strCache, index); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } } return hasContent; } bool FastJsonStringifier::TryCacheSerializeKeysFromEnumCache(const JSHandle &obj, bool hasContent, CVector> &strCache) { JSHandle propertiesArr(thread_, obj->GetProperties()); JSHandle jsHclass(thread_, obj->GetJSHClass()); JSHandle cache(thread_, jsHclass->GetEnumCache()); 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } handleValue_.Update(value); hasContent = AppendJsonString(hasContent, strCache, index); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } return hasContent; } bool FastJsonStringifier::TryCacheSerializeKeysFromGlobalObject(const JSHandle &obj, bool hasContent, CVector> &strCache) { JSHandle propertiesArr(thread_, obj->GetProperties()); 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } handleValue_.Update(value); hasContent = AppendJsonString(hasContent, strCache, index); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } return hasContent; } bool FastJsonStringifier::TryCacheSerializeKeysFromNameDictionary(const JSHandle &obj, bool hasContent, CVector> &strCache) { JSHandle propertiesArr(thread_, obj->GetProperties()); 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } 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) { if (!ElementAccessor::IsDictionaryMode(obj)) { uint32_t elementsLen = ElementAccessor::GetElementsLength(obj); for (uint32_t i = 0; i < elementsLen; ++i) { if (!ElementAccessor::Get(obj, i).IsHole()) { handleKey_.Update(JSTaggedValue(i)); handleValue_.Update(ElementAccessor::Get(obj, i)); hasContent = AppendJsonString(hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } } } else { JSHandle elementsArr(thread_, obj->GetElements()); 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } 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 (JSObject::GetEnumCacheKind(thread_, enumCache) == EnumCacheKind::ONLY_OWN_KEYS) { return SerializeKeysFromCache(obj, enumCache, propertiesArr, hasContent); } else { return SerializeKeysFromLayout(obj, jsHclass, propertiesArr, hasContent); } } else if (obj->IsJSGlobalObject()) { return SerializeKeysFromGlobalDictionary(obj, propertiesArr, hasContent); } else { return SerializeKeysFromNameDictionary(obj, propertiesArr, hasContent); } } bool FastJsonStringifier::SerializeKeysFromCache(const JSHandle &obj, JSTaggedValue enumCache, const JSHandle &propertiesArr, bool hasContent) { 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; } hasContent = SerializeKeyValue(obj, key, propertiesArr, hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } return hasContent; } bool FastJsonStringifier::SerializeKeysFromLayout(const JSHandle &obj, const JSHandle &jsHclass, const JSHandle &propertiesArr, bool 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()) { hasContent = SerializeKeyValue(obj, key, propertiesArr, hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } } return hasContent; } bool FastJsonStringifier::SerializeKeysFromGlobalDictionary(const JSHandle &obj, const JSHandle &propertiesArr, bool hasContent) { 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } handleValue_.Update(value); hasContent = AppendJsonString(hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } return hasContent; } bool FastJsonStringifier::SerializeKeysFromNameDictionary(const JSHandle &obj, const JSHandle &propertiesArr, bool 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)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } handleValue_.Update(value); hasContent = AppendJsonString(hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } return hasContent; } bool FastJsonStringifier::SerializeKeyValue(const JSHandle &obj, JSTaggedValue key, const JSHandle &propertiesArr, bool hasContent) { handleKey_.Update(key); JSHandle jsHclass(thread_, obj->GetJSHClass()); int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key); LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject()); PropertyAttributes attr(layoutInfo->GetAttr(index)); ASSERT(static_cast(attr.GetOffset()) == index); JSTaggedValue value = attr.IsInlinedProps() ? obj->GetPropertyInlinedPropsWithRep(static_cast(index), attr) : propertiesArr->Get(static_cast(index) - jsHclass->GetInlinedProperties()); if (attr.IsInlinedProps() && value.IsHole()) { return hasContent; } if (UNLIKELY(value.IsAccessor())) { value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()), JSHandle(obj)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } 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, usually caused by circular structure", 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