/* * Copyright (c) 2021-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/json_stringifier.h" #include "ecmascript/ecma_string.h" #include "ecmascript/global_dictionary-inl.h" #include "ecmascript/interpreter/interpreter.h" #include "ecmascript/js_api/js_api_hashset.h" #include "ecmascript/js_map.h" #include "ecmascript/js_primitive_ref.h" #include "ecmascript/js_set.h" #include "ecmascript/object_fast_operator-inl.h" #include "ecmascript/shared_objects/js_shared_map.h" #include "ecmascript/tagged_hash_array.h" namespace panda::ecmascript::base { constexpr int GAP_MAX_LEN = 10; using TransformType = base::JsonHelper::TransformType; JSHandle JsonStringifier::Stringify(const JSHandle &value, const JSHandle &replacer, const JSHandle &gap) { factory_ = thread_->GetEcmaVM()->GetFactory(); handleValue_ = JSMutableHandle(thread_, JSTaggedValue::Undefined()); handleKey_ = JSMutableHandle(thread_, JSTaggedValue::Undefined()); // Let isArray be IsArray(replacer). bool isArray = replacer->IsArray(thread_); // ReturnIfAbrupt(isArray). RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_); // If isArray is true, then if (isArray) { uint32_t len = 0; if (replacer->IsJSArray()) { // FastPath JSHandle arr(replacer); len = arr->GetArrayLength(); } else if (replacer->IsJSSharedArray()) { JSHandle arr(replacer); len = arr->GetArrayLength(); } else { // Let len be ToLength(Get(replacer, "length")). JSHandle lengthKey = thread_->GlobalConstants()->GetHandledLengthString(); JSHandle lenResult = JSTaggedValue::GetProperty(thread_, replacer, lengthKey).GetValue(); // ReturnIfAbrupt(len). RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_); JSTaggedNumber lenNumber = JSTaggedValue::ToLength(thread_, lenResult); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_); len = lenNumber.ToUint32(); } if (len > 0) { JSMutableHandle propHandle(thread_, JSTaggedValue::Undefined()); // Repeat while kGetValue(thread_); if (primitive.IsNumber() || primitive.IsString()) { AddDeduplicateProp(propHandle); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_); } } } } } // If Type(space) is Object, then if (gap->IsJSPrimitiveRef()) { JSTaggedValue primitive = JSPrimitiveRef::Cast(gap->GetTaggedObject())->GetValue(thread_); // a. If space has a [[NumberData]] internal slot, then if (primitive.IsNumber()) { // i. Let space be ToNumber(space). JSTaggedNumber num = JSTaggedValue::ToNumber(thread_, gap); // ii. ReturnIfAbrupt(space). RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_); CalculateNumberGap(num); } else if (primitive.IsString()) { // b. Else if space has a [[StringData]] internal slot, then // i. Let space be ToString(space). auto str = JSTaggedValue::ToString(thread_, gap); // ii. ReturnIfAbrupt(space). RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_); CalculateStringGap(JSHandle(thread_, str.GetTaggedValue())); } } else if (gap->IsNumber()) { // If Type(space) is Number CalculateNumberGap(gap.GetTaggedValue()); } else if (gap->IsString()) { // Else if Type(space) is String CalculateStringGap(JSHandle::Cast(gap)); } JSHandle key(factory_->GetEmptyString()); JSTaggedValue serializeValue = GetSerializeValue(value, key, value, replacer); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_); handleValue_.Update(serializeValue); JSTaggedValue result = SerializeJSONProperty(handleValue_, replacer); 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(); } void JsonStringifier::AddDeduplicateProp(const JSHandle &property) { JSHandle primString = JSTaggedValue::ToString(thread_, property); RETURN_IF_ABRUPT_COMPLETION(thread_); JSHandle addVal(thread_, *primString); uint32_t propLen = propList_.size(); for (uint32_t i = 0; i < propLen; i++) { if (JSTaggedValue::SameValue(thread_, propList_[i], addVal)) { return; } } propList_.emplace_back(addVal); } bool JsonStringifier::CalculateNumberGap(JSTaggedValue gap) { double value = std::min(gap.GetNumber(), 10.0); // means GAP_MAX_LEN. if (value > 0) { int gapLength = static_cast(value); gap_.append(gapLength, ' '); gap_.append("\0"); } return true; } bool JsonStringifier::CalculateStringGap(const JSHandle &primString) { CString gapString = ConvertToString(thread_, *primString, StringConvertedUsage::LOGICOPERATION); uint32_t gapLen = gapString.length(); if (gapLen > 0) { uint32_t gapLength = gapLen; if (gapLen > GAP_MAX_LEN) { if (gapString.at(GAP_MAX_LEN - 1) == static_cast(common::utf_helper::UTF8_2B_FIRST)) { gapLength = GAP_MAX_LEN + 1; } else { gapLength = GAP_MAX_LEN; } } gap_.append(gapString.c_str(), gapLength); gap_.append("\0"); } return true; } JSTaggedValue JsonStringifier::GetSerializeValue(const JSHandle &object, const JSHandle &key, const JSHandle &value, const JSHandle &replacer) { JSTaggedValue tagValue = value.GetTaggedValue(); JSHandle undefined = thread_->GlobalConstants()->GetHandledUndefined(); // If Type(value) is Object, then if (value->IsECMAObject() || value->IsBigInt()) { // 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_); } } if (UNLIKELY(replacer->IsCallable())) { handleValue_.Update(tagValue); // a. Let value be Call(ReplacerFunction, holder, «key, value»). const uint32_t argsLength = 2; // 2: «key, value» JSHandle holder = SerializeHolder(object, value); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread_, replacer, holder, undefined, argsLength); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); info->SetCallArg(key.GetTaggedValue(), handleValue_.GetTaggedValue()); tagValue = JSFunction::Call(info); // b. ReturnIfAbrupt(value). RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); } return tagValue; } JSHandle JsonStringifier::SerializeHolder(const JSHandle &object, const JSHandle &value) { if (stack_.size() <= 0) { JSHandle holder = factory_->CreateNullJSObject(); JSHandle holderKey(factory_->GetEmptyString()); JSObject::CreateDataPropertyOrThrow(thread_, holder, holderKey, value); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_); return JSHandle(holder); } return object; } JSTaggedValue JsonStringifier::SerializeJSONProperty(const JSHandle &value, const JSHandle &replacer) { STACK_LIMIT_CHECK(thread_, JSTaggedValue::Exception()); 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(thread_, *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, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); return tagValue; } case JSType::JS_API_LINKED_LIST: { JSHandle listHandle = JSHandle(thread_, tagValue); CheckStackPushSameValue(valHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); valHandle = JSHandle(thread_, JSAPILinkedList::ConvertToArray(thread_, listHandle)); SerializeJSONObject(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); return tagValue; } case JSType::JS_API_LIST: { JSHandle listHandle = JSHandle(thread_, tagValue); CheckStackPushSameValue(valHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); valHandle = JSHandle(thread_, JSAPIList::ConvertToArray(thread_, listHandle)); SerializeJSONObject(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); return tagValue; } case JSType::JS_API_FAST_BUFFER: { JSHandle bufferHandle = JSHandle(thread_, tagValue); CheckStackPushSameValue(valHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); valHandle = JSHandle(thread_, JSAPIFastBuffer::FromBufferToArray(thread_, bufferHandle)); SerializeJSONObject(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); return tagValue; } // If Type(value) is String, return QuoteJSONString(value). case JSType::LINE_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(thread_, *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: { if (transformType_ == TransformType::NORMAL) { THROW_TYPE_ERROR_AND_RETURN(thread_, "cannot serialize a BigInt", JSTaggedValue::Exception()); } else { JSHandle thisBigint(thread_, valHandle.GetTaggedValue()); auto bigIntStr = BigInt::ToString(thread_, thisBigint); result_ += ConvertToString(thread_, *bigIntStr); return tagValue; } } case JSType::JS_NATIVE_POINTER: { result_ += "{}"; return tagValue; } case JSType::JS_SHARED_MAP: { if (transformType_ == TransformType::SENDABLE) { CheckStackPushSameValue(valHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); SerializeJSONSharedMap(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); return tagValue; } [[fallthrough]]; } case JSType::JS_MAP: { if (transformType_ == TransformType::SENDABLE) { CheckStackPushSameValue(valHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); SerializeJSONMap(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); return tagValue; } [[fallthrough]]; } case JSType::JS_SET: { if (transformType_ == TransformType::SENDABLE) { CheckStackPushSameValue(valHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); SerializeJSONSet(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); return tagValue; } [[fallthrough]]; } case JSType::JS_SHARED_SET: { if (transformType_ == TransformType::SENDABLE) { CheckStackPushSameValue(valHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); SerializeJSONSharedSet(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); return tagValue; } [[fallthrough]]; } case JSType::JS_API_HASH_MAP: { if (transformType_ == TransformType::SENDABLE) { CheckStackPushSameValue(valHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); SerializeJSONHashMap(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); return tagValue; } [[fallthrough]]; } case JSType::JS_API_HASH_SET: { if (transformType_ == TransformType::SENDABLE) { CheckStackPushSameValue(valHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); SerializeJSONHashSet(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); return tagValue; } [[fallthrough]]; } default: { if (!tagValue.IsCallable()) { JSHClass *jsHclass = tagValue.GetTaggedObject()->GetClass(); if (UNLIKELY(jsHclass->IsJSProxy() && JSProxy::Cast(tagValue.GetTaggedObject())->IsArray(thread_))) { SerializeJSProxy(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); } else { CheckStackPushSameValue(valHandle); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); SerializeJSONObject(valHandle, replacer); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_); } return tagValue; } } } } return JSTaggedValue::Undefined(); } void JsonStringifier::SerializeObjectKey(const JSHandle &key, bool hasContent) { CString stepBegin; CString stepEnd; if (hasContent) { result_ += ","; } if (!gap_.empty()) { stepBegin += "\n"; stepBegin += indent_; stepEnd += " "; } CString str; if (key->IsString()) { str = ConvertToString(thread_, EcmaString::Cast(key->GetTaggedObject()), StringConvertedUsage::LOGICOPERATION); } else if (key->IsInt()) { str = NumberHelper::IntToString(static_cast(key->GetInt())); } else { str = ConvertToString(thread_, *JSTaggedValue::ToString(thread_, key), StringConvertedUsage::LOGICOPERATION); } result_ += stepBegin; JsonHelper::AppendValueToQuotedString(str, result_); result_ += ":"; result_ += stepEnd; } bool JsonStringifier::PushValue(const JSHandle &value) { uint32_t thisLen = stack_.size(); for (uint32_t i = 0; i < thisLen; i++) { bool equal = JSTaggedValue::SameValue(thread_, stack_[i].GetTaggedValue(), value.GetTaggedValue()); if (equal) { return true; } } stack_.emplace_back(value); return false; } void JsonStringifier::PopValue() { stack_.pop_back(); } bool JsonStringifier::SerializeJSONObject(const JSHandle &value, const JSHandle &replacer) { CString stepback = indent_; indent_ += gap_; result_ += "{"; bool hasContent = false; ASSERT(!value->IsAccessor()); JSHandle obj(value); if (!replacer->IsArray(thread_)) { 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(thread_, i)); JSHandle valueHandle = JSTaggedValue::GetProperty(thread_, value, handleKey_).GetValue(); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); JSTaggedValue serializeValue = GetSerializeValue(value, handleKey_, valueHandle, replacer); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() || (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) { continue; } handleValue_.Update(serializeValue); SerializeObjectKey(handleKey_, hasContent); JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); if (!res.IsUndefined()) { hasContent = true; } } } else { uint32_t numOfKeys = obj->GetNumberOfKeys(thread_); uint32_t numOfElements = obj->GetNumberOfElements(thread_); if (numOfElements > 0) { hasContent = JsonStringifier::SerializeElements(obj, replacer, hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } if (numOfKeys > 0) { hasContent = JsonStringifier::SerializeKeys(obj, replacer, hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } } } else { uint32_t propLen = propList_.size(); for (uint32_t i = 0; i < propLen; i++) { JSTaggedValue tagVal = ObjectFastOperator::FastGetPropertyByValue(thread_, obj.GetTaggedValue(), propList_[i].GetTaggedValue()); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); handleValue_.Update(tagVal); JSTaggedValue serializeValue = GetSerializeValue(value, propList_[i], handleValue_, replacer); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() || (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) { continue; } handleValue_.Update(serializeValue); SerializeObjectKey(propList_[i], hasContent); JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); if (!res.IsUndefined()) { hasContent = true; } } } if (hasContent && gap_.length() != 0) { result_ += "\n"; result_ += stepback; } result_ += "}"; PopValue(); indent_ = stepback; return true; } bool JsonStringifier::SerializeJSONSharedMap(const JSHandle &value, const JSHandle &replacer) { [[maybe_unused]] ConcurrentApiScope scope(thread_, value); JSHandle sharedMap(value); JSHandle hashMap(thread_, sharedMap->GetLinkedMap(thread_)); return SerializeLinkedHashMap(hashMap, replacer); } bool JsonStringifier::SerializeJSONSharedSet(const JSHandle &value, const JSHandle &replacer) { [[maybe_unused]] ConcurrentApiScope scope(thread_, value); JSHandle sharedSet(value); JSHandle hashSet(thread_, sharedSet->GetLinkedSet(thread_)); return SerializeLinkedHashSet(hashSet, replacer); } bool JsonStringifier::SerializeJSONMap(const JSHandle &value, const JSHandle &replacer) { JSHandle jsMap(value); JSHandle hashMap(thread_, jsMap->GetLinkedMap(thread_)); return SerializeLinkedHashMap(hashMap, replacer); } bool JsonStringifier::SerializeJSONSet(const JSHandle &value, const JSHandle &replacer) { JSHandle jsSet(value); JSHandle hashSet(thread_, jsSet->GetLinkedSet(thread_)); return SerializeLinkedHashSet(hashSet, replacer); } bool JsonStringifier::SerializeJSONHashMap(const JSHandle &value, const JSHandle &replacer) { CString stepback = indent_; result_ += "{"; JSHandle hashMap(value); JSHandle table(thread_, hashMap->GetTable(thread_)); uint32_t len = table->GetLength(); ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory(); JSMutableHandle queue(thread_, factory->NewTaggedQueue(0)); JSMutableHandle node(thread_, JSTaggedValue::Undefined()); JSMutableHandle keyHandle(thread_, JSTaggedValue::Undefined()); JSMutableHandle valueHandle(thread_, JSTaggedValue::Undefined()); uint32_t index = 0; bool needRemove = false; while (index < len) { node.Update(TaggedHashArray::GetCurrentNode(thread_, queue, table, index)); if (node.GetTaggedValue().IsHole()) { continue; } keyHandle.Update(node->GetKey(thread_)); valueHandle.Update(node->GetValue(thread_)); if (valueHandle->IsUndefined()) { continue; } if (UNLIKELY(!keyHandle->IsString())) { result_ += "\""; SerializeJSONProperty(keyHandle, replacer); result_ += "\""; } else { SerializeJSONProperty(keyHandle, replacer); } result_ += ":"; SerializeJSONProperty(valueHandle, replacer); result_ += ","; needRemove = true; } if (needRemove) { result_.pop_back(); } result_ += "}"; PopValue(); indent_ = stepback; return true; } bool JsonStringifier::SerializeJSONHashSet(const JSHandle &value, const JSHandle &replacer) { CString stepback = indent_; result_ += "["; JSHandle hashSet(value); JSHandle table(thread_, hashSet->GetTable(thread_)); uint32_t len = table->GetLength(); ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory(); JSMutableHandle queue(thread_, factory->NewTaggedQueue(0)); JSMutableHandle node(thread_, JSTaggedValue::Undefined()); JSMutableHandle currentKey(thread_, JSTaggedValue::Undefined()); uint32_t index = 0; bool needRemove = false; while (index < len) { node.Update(TaggedHashArray::GetCurrentNode(thread_, queue, table, index)); if (node.GetTaggedValue().IsHole()) { continue; } currentKey.Update(node->GetKey(thread_)); JSTaggedValue res = SerializeJSONProperty(currentKey, replacer); if (res.IsUndefined()) { result_ += "null"; } result_ += ","; needRemove = true; } if (needRemove) { result_.pop_back(); } result_ += "]"; PopValue(); indent_ = stepback; return true; } bool JsonStringifier::SerializeLinkedHashMap(const JSHandle &hashMap, const JSHandle &replacer) { CString stepback = indent_; result_ += "{"; int index = 0; int totalElements = hashMap->NumberOfElements() + hashMap->NumberOfDeletedElements(); JSMutableHandle keyHandle(thread_, JSTaggedValue::Undefined()); JSMutableHandle valHandle(thread_, JSTaggedValue::Undefined()); bool needRemove = false; while (index < totalElements) { keyHandle.Update(hashMap->GetKey(thread_, index++)); valHandle.Update(hashMap->GetValue(thread_, index - 1)); if (keyHandle->IsHole() || valHandle->IsUndefined()) { continue; } if (UNLIKELY(keyHandle->IsUndefined())) { result_ += "\"undefined\""; } else if (UNLIKELY(!keyHandle->IsString())) { result_ += "\""; SerializeJSONProperty(keyHandle, replacer); result_ += "\""; } else { SerializeJSONProperty(keyHandle, replacer); } result_ += ":"; SerializeJSONProperty(valHandle, replacer); result_ += ","; needRemove = true; } if (needRemove) { result_.pop_back(); } result_ += "}"; PopValue(); indent_ = stepback; return true; } bool JsonStringifier::SerializeLinkedHashSet(const JSHandle &hashSet, const JSHandle &replacer) { CString stepback = indent_; result_ += "["; JSMutableHandle keyHandle(thread_, JSTaggedValue::Undefined()); bool needRemove = false; int index = 0; int totalElements = hashSet->NumberOfElements() + hashSet->NumberOfDeletedElements(); while (index < totalElements) { keyHandle.Update(hashSet->GetKey(thread_, index++)); if (keyHandle->IsHole()) { continue; } JSTaggedValue res = SerializeJSONProperty(keyHandle, replacer); if (res.IsUndefined()) { result_ += "null"; } result_ += ","; needRemove = true; } if (needRemove) { result_.pop_back(); } result_ += "]"; PopValue(); indent_ = stepback; return true; } bool JsonStringifier::SerializeJSProxy(const JSHandle &object, const JSHandle &replacer) { bool isContain = PushValue(object); if (isContain) { THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true); } CString stepback = indent_; CString stepBegin; indent_ += gap_; if (!gap_.empty()) { stepBegin += "\n"; stepBegin += indent_; } 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_ += ","; } result_ += stepBegin; JSTaggedValue serializeValue = GetSerializeValue(object, handleKey_, valHandle, replacer); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); handleValue_.Update(serializeValue); JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); if (res.IsUndefined()) { result_ += "null"; } } if (length > 0 && !gap_.empty()) { result_ += "\n"; result_ += stepback; } result_ += "]"; PopValue(); indent_ = stepback; return true; } bool JsonStringifier::SerializeJSArray(const JSHandle &value, const JSHandle &replacer) { // 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); } CString stepback = indent_; result_ += "["; CString stepBegin; indent_ += gap_; if (!gap_.empty()) { stepBegin += "\n"; stepBegin += indent_; } 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_ += ","; } result_ += stepBegin; JSTaggedValue serializeValue = GetSerializeValue(value, handleKey_, handleValue_, replacer); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); handleValue_.Update(serializeValue); JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); if (res.IsUndefined()) { result_ += "null"; } } if (!gap_.empty()) { result_ += "\n"; result_ += stepback; indent_ = stepback; } } result_ += "]"; PopValue(); indent_ = stepback; return true; } void JsonStringifier::SerializePrimitiveRef(const JSHandle &primitiveRef) { JSTaggedValue primitive = JSPrimitiveRef::Cast(primitiveRef.GetTaggedValue().GetTaggedObject())->GetValue(thread_); if (primitive.IsString()) { auto priStr = JSTaggedValue::ToString(thread_, primitiveRef); RETURN_IF_ABRUPT_COMPLETION(thread_); CString str = ConvertToString(thread_, *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(thread_, *base::NumberHelper::NumberToString(thread_, priNum)); } else { result_ += "null"; } } else if (primitive.IsBoolean()) { result_ += primitive.IsTrue() ? "true" : "false"; } else if (primitive.IsBigInt()) { if (transformType_ == TransformType::NORMAL) { THROW_TYPE_ERROR(thread_, "cannot serialize a BigInt"); } else { JSHandle thisBigint(thread_, primitive); auto bigIntStr = BigInt::ToString(thread_, thisBigint); result_ += ConvertToString(thread_, *bigIntStr); } } } bool JsonStringifier::SerializeElements(const JSHandle &obj, const JSHandle &replacer, bool hasContent) { if (!ElementAccessor::IsDictionaryMode(thread_, obj)) { uint32_t elementsLen = ElementAccessor::GetElementsLength(thread_, obj); for (uint32_t i = 0; i < elementsLen; ++i) { if (!ElementAccessor::Get(thread_, obj, i).IsHole()) { handleKey_.Update(JSTaggedValue(i)); handleValue_.Update(ElementAccessor::Get(thread_, obj, i)); hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } } } else { JSHandle elementsArr(thread_, obj->GetElements(thread_)); JSHandle numberDic(elementsArr); CVector> sortArr; int size = numberDic->Size(); for (int hashIndex = 0; hashIndex < size; hashIndex++) { JSTaggedValue key = numberDic->GetKey(thread_, hashIndex); if (!key.IsUndefined() && !key.IsHole()) { PropertyAttributes attr = numberDic->GetAttributes(thread_, 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(thread_, entryKey); if (index < 0) { continue; } JSTaggedValue value = numberDic->GetValue(thread_, 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 = JsonStringifier::AppendJsonString(obj, replacer, hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } } return hasContent; } bool JsonStringifier::SerializeKeys(const JSHandle &obj, const JSHandle &replacer, bool hasContent) { JSHandle propertiesArr(thread_, obj->GetProperties(thread_)); if (!propertiesArr->IsDictionaryMode()) { bool hasChangedToDictionaryMode = false; JSHandle jsHClass(thread_, obj->GetJSHClass()); if (jsHClass->GetEnumCache(thread_).IsEnumCacheOwnValid(thread_)) { auto cache = JSHClass::GetEnumCacheOwnWithOutCheck(thread_, jsHClass); uint32_t length = cache->GetLength(); uint32_t dictStart = length; for (uint32_t i = 0; i < length; i++) { JSTaggedValue key = cache->Get(thread_, i); if (!key.IsString()) { continue; } handleKey_.Update(key); JSTaggedValue value; LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHClass->GetLayout(thread_).GetTaggedObject()); int index = JSHClass::FindPropertyEntry(thread_, *jsHClass, key); PropertyAttributes attr(layoutInfo->GetAttr(thread_, index)); ASSERT(static_cast(attr.GetOffset()) == index); value = attr.IsInlinedProps() ? obj->GetPropertyInlinedPropsWithRep(thread_, static_cast(index), attr) : propertiesArr->Get(thread_, 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); if (obj->GetProperties(thread_).IsDictionary()) { dictStart = i; handleValue_.Update(value); hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); break; } } handleValue_.Update(value); hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } if (dictStart < length) { propertiesArr = JSHandle(thread_, obj->GetProperties(thread_)); JSHandle nameDic(propertiesArr); for (uint32_t i = dictStart + 1;i < length; i++) { JSTaggedValue key = cache->Get(thread_, i); int hashIndex = nameDic->FindEntry(thread_, key); PropertyAttributes attr = nameDic->GetAttributes(thread_, hashIndex); if (!key.IsString() || hashIndex < 0 || !attr.IsEnumerable()) { continue; } handleKey_.Update(key); JSTaggedValue value = nameDic->GetValue(thread_, hashIndex); 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 = JsonStringifier::AppendJsonString(obj, replacer, 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(thread_).GetTaggedObject()); JSTaggedValue key = layoutInfo->GetKey(thread_, i); if (!hasChangedToDictionaryMode) { PropertyAttributes attr(layoutInfo->GetAttr(thread_, i)); ASSERT(static_cast(attr.GetOffset()) == i); if (key.IsString() && attr.IsEnumerable()) { handleKey_.Update(key); JSTaggedValue value = attr.IsInlinedProps() ? obj->GetPropertyInlinedPropsWithRep(thread_, static_cast(i), attr) : propertiesArr->Get(thread_, static_cast(i) - 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 = JsonStringifier::AppendJsonString(obj, replacer, hasContent); if (obj->GetProperties(thread_).IsDictionary()) { hasChangedToDictionaryMode = true; propertiesArr = JSHandle(thread_, obj->GetProperties(thread_)); } jsHClass = JSHandle(thread_, obj->GetJSHClass()); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } } else { JSHandle nameDic(propertiesArr); int index = nameDic->FindEntry(thread_, key); if (!key.IsString()) { continue; } PropertyAttributes attr = nameDic->GetAttributes(thread_, index); if (!attr.IsEnumerable() || index < 0) { continue; } JSTaggedValue value = nameDic->GetValue(thread_, index); handleKey_.Update(key); if (UNLIKELY(value.IsAccessor())) { value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()), JSHandle(obj)); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); jsHClass = JSHandle(thread_, obj->GetJSHClass()); } handleValue_.Update(value); hasContent = JsonStringifier::AppendJsonString(obj, replacer, 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(thread_, hashIndex); if (!key.IsString()) { continue; } PropertyAttributes attr = globalDic->GetAttributes(thread_, 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(thread_, entryKey); if (index == -1) { continue; } JSTaggedValue value = globalDic->GetValue(thread_, 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 = JsonStringifier::AppendJsonString(obj, replacer, 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(thread_, hashIndex); if (!key.IsString()) { continue; } PropertyAttributes attr = nameDic->GetAttributes(thread_, 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(thread_, entryKey); if (index < 0) { continue; } JSTaggedValue value = nameDic->GetValue(thread_, 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 = JsonStringifier::AppendJsonString(obj, replacer, hasContent); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); } return hasContent; } bool JsonStringifier::AppendJsonString(const JSHandle &obj, const JSHandle &replacer, bool hasContent) { JSTaggedValue serializeValue = GetSerializeValue(JSHandle(obj), handleKey_, handleValue_, replacer); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() || (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) { return hasContent; } handleValue_.Update(serializeValue); SerializeObjectKey(handleKey_, hasContent); JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false); if (!res.IsUndefined()) { return true; } return hasContent; } bool JsonStringifier::CheckStackPushSameValue(JSHandle value) { bool isContain = PushValue(value); if (isContain) { THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true); } return false; } } // namespace panda::ecmascript::base