* 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 "ecmascript/base/json_stringifier.h"
#include "ecmascript/base/dtoa_helper.h"
#include "ecmascript/ecma_string.h"
#include "ecmascript/ecma_string-inl.h"
#include "ecmascript/global_dictionary-inl.h"
#include "ecmascript/interpreter/interpreter.h"
#include "ecmascript/ic/proto_change_details.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<JSTaggedValue> JsonStringifier::Stringify(const JSHandle<JSTaggedValue> &value,
const JSHandle<JSTaggedValue> &replacer,
const JSHandle<JSTaggedValue> &gap)
{
replacer_ = replacer;
if (replacer->IsUndefined() && gap->IsUndefined()) {
return StringifyInternal<true>(value, gap);
}
return StringifyInternal<false>(value, gap);
}
template <bool ReplacerAndGapUndefined>
JSHandle<JSTaggedValue> JsonStringifier::StringifyInternal(const JSHandle<JSTaggedValue> &value,
const JSHandle<JSTaggedValue> &gap)
{
factory_ = thread_->GetEcmaVM()->GetFactory();
handleValue_ = JSMutableHandle<JSTaggedValue>(thread_, JSTaggedValue::Undefined());
handleKey_ = JSMutableHandle<JSTaggedValue>(thread_, JSTaggedValue::Undefined());
if constexpr (!ReplacerAndGapUndefined) {
bool isArray = replacer_->IsArray(thread_);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
if (isArray) {
uint32_t len = 0;
if (replacer_->IsJSArray()) {
JSHandle<JSArray> arr(replacer_);
len = arr->GetArrayLength();
} else if (replacer_->IsJSSharedArray()) {
JSHandle<JSSharedArray> arr(replacer_);
len = arr->GetArrayLength();
} else {
JSHandle<JSTaggedValue> lengthKey = thread_->GlobalConstants()->GetHandledLengthString();
JSHandle<JSTaggedValue> lenResult =
JSTaggedValue::GetProperty(thread_, replacer_, lengthKey).GetValue();
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<JSTaggedValue> propHandle(thread_, JSTaggedValue::Undefined());
for (uint32_t i = 0; i < len; i++) {
JSTaggedValue prop = ObjectFastOperator::FastGetPropertyByIndex(thread_,
replacer_.GetTaggedValue(), i);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
* c. Let item be undefined.
* d. If Type(v) is String, let item be v.
* e. Else if Type(v) is Number, let item be ToString(v).
* f. Else if Type(v) is Object, then
* i. If v has a [[StringData]] or [[NumberData]] internal slot, let item be ToString(v).
* ii. ReturnIfAbrupt(item).
* g. If item is not undefined and item is not currently an element of PropertyList, then
* i. Append item to the end of PropertyList.
* h. Let k be k+1.
*/
propHandle.Update(prop);
if (prop.IsNumber() || prop.IsString()) {
AddDeduplicateProp(propHandle);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
} else if (prop.IsJSPrimitiveRef()) {
JSTaggedValue primitive = JSPrimitiveRef::Cast(prop.GetTaggedObject())->GetValue(thread_);
if (primitive.IsNumber() || primitive.IsString()) {
AddDeduplicateProp(propHandle);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
}
}
}
}
}
if (gap->IsJSPrimitiveRef()) {
JSTaggedValue primitive = JSPrimitiveRef::Cast(gap->GetTaggedObject())->GetValue(thread_);
if (primitive.IsNumber()) {
JSTaggedNumber num = JSTaggedValue::ToNumber(thread_, gap);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
CalculateNumberGap(num);
} else if (primitive.IsString()) {
auto str = JSTaggedValue::ToString(thread_, gap);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
CalculateStringGap(JSHandle<EcmaString>(thread_, str.GetTaggedValue()));
}
} else if (gap->IsNumber()) {
CalculateNumberGap(gap.GetTaggedValue());
} else if (gap->IsString()) {
CalculateStringGap(JSHandle<EcmaString>::Cast(gap));
}
}
JSHandle<JSTaggedValue> key(factory_->GetEmptyString());
JSTaggedValue serializeValue = GetSerializeValue<ReplacerAndGapUndefined>(value, key, value);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
handleValue_.Update(serializeValue);
JSTaggedValue result = SerializeJSONProperty<ReplacerAndGapUndefined>(handleValue_);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
if (!result.IsUndefined()) {
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
return JSHandle<JSTaggedValue>(factory_->NewFromUtf8LiteralCompress(
reinterpret_cast<const uint8_t *>(oneByteResult_.GetBuffer()), oneByteResult_.GetLength()));
} else {
return JSHandle<JSTaggedValue>(factory_->NewFromUtf16Literal(
reinterpret_cast<const uint16_t *>(twoBytesResult_.GetBuffer()), twoBytesResult_.GetLength()));
}
}
return thread_->GlobalConstants()->GetHandledUndefined();
}
void JsonStringifier::AddDeduplicateProp(const JSHandle<JSTaggedValue> &property)
{
JSHandle<EcmaString> primString = JSTaggedValue::ToString(thread_, property);
RETURN_IF_ABRUPT_COMPLETION(thread_);
JSHandle<JSTaggedValue> 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);
if (value > 0) {
int gapLength = static_cast<int>(value);
gap_.append(gapLength, ' ');
}
return true;
}
bool JsonStringifier::CalculateStringGap(const JSHandle<EcmaString> &primString)
{
if (EcmaStringAccessor(primString).IsUtf8()) {
EcmaStringAccessor(primString).AppendToC16String(thread_, gap_);
} else {
ChangeEncoding();
EcmaStringAccessor(primString).AppendToC16String(thread_, gap_);
}
if (gap_.size() > GAP_MAX_LEN) {
gap_.resize(GAP_MAX_LEN);
}
return true;
}
bool JsonStringifier::HasOwnToJsonProperty(const JSHandle<JSObject> &obj) const
{
JSTaggedValue toJsonValue = thread_->GlobalConstants()->GetHandledToJsonString().GetTaggedValue();
JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties(thread_));
if (!propertiesArr->IsDictionaryMode()) {
JSHandle<JSHClass> jsHClass(thread_, obj->GetJSHClass());
int entry = JSHClass::FindPropertyEntry(thread_, *jsHClass, toJsonValue);
if (entry < 0) {
return false;
}
LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHClass->GetLayout(thread_).GetTaggedObject());
PropertyAttributes attr(layoutInfo->GetAttr(thread_, entry));
JSTaggedValue value = attr.IsInlinedProps()
? obj->GetPropertyInlinedPropsWithRep(thread_, static_cast<uint32_t>(entry), attr)
: propertiesArr->Get(thread_, static_cast<uint32_t>(entry) - jsHClass->GetInlinedProperties());
return !value.IsHole();
}
if (obj->IsJSGlobalObject()) {
JSHandle<GlobalDictionary> globalDic(propertiesArr);
int index = globalDic->FindEntry(thread_, toJsonValue);
if (index < 0) {
return false;
}
return !globalDic->GetValue(thread_, index).IsHole();
}
JSHandle<NameDictionary> nameDic(propertiesArr);
int index = nameDic->FindEntry(thread_, toJsonValue);
if (index < 0) {
return false;
}
return !nameDic->GetValue(thread_, index).IsHole();
}
bool JsonStringifier::CheckProtoChainForInterestingProperties(const JSHandle<JSHClass> &receiverHClass,
JSTaggedValue current)
{
while (current.IsECMAObject()) {
JSHClass *hclass = current.GetTaggedObject()->GetClass();
if (hclass->IsJSProxy()) {
receiverHClass->SetMayHaveInterestingProperties(true);
return true;
}
JSHandle<JSObject> currentObj(thread_, current);
if (HasOwnToJsonProperty(currentObj)) {
receiverHClass->SetMayHaveInterestingProperties(true);
return true;
}
JSTaggedValue proto = hclass->GetPrototype(thread_);
if (proto.IsNull() || !proto.IsECMAObject()) {
receiverHClass->SetMayHaveInterestingProperties(false);
JSHClass::EnableProtoChangeMarker(thread_, receiverHClass);
return false;
}
current = proto;
}
return false;
}
bool JsonStringifier::MayHaveInterestingProperties(const JSHandle<JSTaggedValue> &value)
{
if (!value->IsECMAObject()) {
return false;
}
if (UNLIKELY(replacer_->IsCallable()) || value->IsJSPrimitiveRef()) {
return true;
}
JSHandle<JSObject> object(value);
JSHandle<JSHClass> receiverHClass(thread_, object->GetJSHClass());
if (!receiverHClass->MayHaveInterestingProperties()) {
JSTaggedValue receiverProto = receiverHClass->GetPrototype(thread_);
if (!receiverProto.IsECMAObject()) {
return false;
}
JSHClass *protoHClass = receiverProto.GetTaggedObject()->GetClass();
JSTaggedValue markerValue = protoHClass->GetProtoChangeMarker(thread_);
if (markerValue.IsProtoChangeMarker() &&
!ProtoChangeMarker::Cast(markerValue.GetTaggedObject())->GetHasChanged()) {
return false;
}
receiverHClass->SetMayHaveInterestingProperties(true);
}
JSTaggedValue current = value.GetTaggedValue();
if (CheckProtoChainForInterestingProperties(receiverHClass, current)) {
return true;
}
receiverHClass->SetMayHaveInterestingProperties(false);
JSHClass::EnableProtoChangeMarker(thread_, receiverHClass);
return false;
}
template <bool ReplacerAndGapUndefined>
JSTaggedValue JsonStringifier::GetSerializeValue(const JSHandle<JSTaggedValue> &object,
const JSHandle<JSTaggedValue> &key,
const JSHandle<JSTaggedValue> &value)
{
JSTaggedValue tagValue = value.GetTaggedValue();
JSHandle<JSTaggedValue> undefined = thread_->GlobalConstants()->GetHandledUndefined();
bool isBigInt = value->IsBigInt();
bool isJSPrimitiveRef = value->IsJSPrimitiveRef();
bool isECMAObject = value->IsECMAObject();
bool hasInterestingProps = false;
if (isECMAObject) {
hasInterestingProps = MayHaveInterestingProperties(value);
tagValue = value.GetTaggedValue();
isECMAObject = tagValue.IsECMAObject();
} else {
tagValue = value.GetTaggedValue();
}
if (isBigInt || isJSPrimitiveRef || (isECMAObject && hasInterestingProps)) {
JSHandle<JSTaggedValue> toJson = thread_->GlobalConstants()->GetHandledToJsonString();
JSHandle<JSTaggedValue> toJsonFun(
thread_, ObjectFastOperator::FastGetPropertyByValue(thread_, tagValue, toJson.GetTaggedValue()));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
tagValue = value.GetTaggedValue();
if (UNLIKELY(toJsonFun->IsCallable())) {
EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread_, toJsonFun, value, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
info->SetCallArg(key.GetTaggedValue());
tagValue = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
}
}
if constexpr (!ReplacerAndGapUndefined) {
if (UNLIKELY(replacer_->IsCallable())) {
handleValue_.Update(tagValue);
const uint32_t argsLength = 2;
JSHandle<JSTaggedValue> 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);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
}
}
return tagValue;
}
JSHandle<JSTaggedValue> JsonStringifier::SerializeHolder(const JSHandle<JSTaggedValue> &object,
const JSHandle<JSTaggedValue> &value)
{
if (stack_.size() <= 0) {
JSHandle<JSObject> holder = factory_->CreateNullJSObject();
JSHandle<JSTaggedValue> holderKey(factory_->GetEmptyString());
JSObject::CreateDataPropertyOrThrow(thread_, holder, holderKey, value);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
return JSHandle<JSTaggedValue>(holder);
}
return object;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeFastArrayPrimitive(JSTaggedValue tagVal)
{
JSTaggedType rawType = tagVal.GetRawData();
if (rawType == JSTaggedValue::VALUE_NULL || tagVal.IsUndefined() || tagVal.IsHole()) {
AppendLiteral("null");
return true;
}
if (rawType == JSTaggedValue::VALUE_TRUE) {
AppendLiteral("true");
return true;
}
if (rawType == JSTaggedValue::VALUE_FALSE) {
AppendLiteral("false");
return true;
}
if (tagVal.IsInt()) {
EnsureCapacityFor(INT_MAX_LENGTH);
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
AppendIntToFastStringBuilder(oneByteResult_, tagVal.GetInt());
} else {
AppendIntToFastStringBuilder(twoBytesResult_, tagVal.GetInt());
}
return true;
}
if (tagVal.IsDouble()) {
double d = tagVal.GetDouble();
if (std::isfinite(d)) {
EnsureCapacityFor(DOUBLE_MAX_LENGTH);
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
AppendDoubleToFastStringBuilder(oneByteResult_, d);
} else {
AppendDoubleToFastStringBuilder(twoBytesResult_, d);
}
} else {
AppendLiteral("null");
}
return true;
}
return false;
}
template <bool ReplacerAndGapUndefined>
JSTaggedValue JsonStringifier::SerializeJSONProperty(const JSHandle<JSTaggedValue> &value)
{
STACK_LIMIT_CHECK(thread_, JSTaggedValue::Exception());
JSTaggedValue tagValue = value.GetTaggedValue();
if (!tagValue.IsHeapObject()) {
JSTaggedType type = tagValue.GetRawData();
switch (type) {
case JSTaggedValue::VALUE_FALSE:
AppendLiteral("false");
return tagValue;
case JSTaggedValue::VALUE_TRUE:
AppendLiteral("true");
return tagValue;
case JSTaggedValue::VALUE_NULL:
AppendLiteral("null");
return tagValue;
default:
if (tagValue.IsNumber()) {
if (std::isfinite(tagValue.GetNumber())) {
AppendNumberToResult(value);
} else {
AppendLiteral("null");
}
return tagValue;
}
}
} else {
JSType jsType = tagValue.GetTaggedObject()->GetClass()->GetObjectType();
JSHandle<JSTaggedValue> valHandle(thread_, tagValue);
switch (jsType) {
case JSType::JS_ARRAY:
case JSType::JS_SHARED_ARRAY: {
SerializeJSArray<ReplacerAndGapUndefined>(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
return tagValue;
}
case JSType::JS_API_LINKED_LIST: {
JSHandle listHandle = JSHandle<JSAPILinkedList>(thread_, tagValue);
CheckStackPushSameValue(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
valHandle = JSHandle<JSTaggedValue>(thread_, JSAPILinkedList::ConvertToArray(thread_, listHandle));
SerializeJSONObject<ReplacerAndGapUndefined>(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
return tagValue;
}
case JSType::JS_API_LIST: {
JSHandle listHandle = JSHandle<JSAPIList>(thread_, tagValue);
CheckStackPushSameValue(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
valHandle = JSHandle<JSTaggedValue>(thread_, JSAPIList::ConvertToArray(thread_, listHandle));
SerializeJSONObject<ReplacerAndGapUndefined>(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
return tagValue;
}
case JSType::JS_API_FAST_BUFFER: {
JSHandle bufferHandle = JSHandle<JSTaggedValue>(thread_, tagValue);
CheckStackPushSameValue(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
valHandle = JSHandle<JSTaggedValue>(thread_, JSAPIFastBuffer::FromBufferToArray(thread_, bufferHandle));
SerializeJSONObject<ReplacerAndGapUndefined>(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
return tagValue;
}
case JSType::LINE_STRING:
case JSType::TREE_STRING:
case JSType::SLICED_STRING:
case JSType::CACHED_EXTERNAL_STRING: {
JSHandle<EcmaString> strHandle = JSHandle<EcmaString>(valHandle);
auto string = JSHandle<EcmaString>(thread_,
EcmaStringAccessor::Flatten(thread_->GetEcmaVM(), strHandle));
AppendEcmaStringToResult(string);
return tagValue;
}
case JSType::JS_PRIMITIVE_REF: {
SerializePrimitiveRef<ReplacerAndGapUndefined>(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 {
AppendBigIntToResult(valHandle);
return tagValue;
}
}
case JSType::JS_NATIVE_POINTER: {
AppendLiteral("{}");
return tagValue;
}
case JSType::JS_SHARED_MAP: {
if (transformType_ == TransformType::SENDABLE) {
CheckStackPushSameValue(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
SerializeJSONSharedMap<ReplacerAndGapUndefined>(valHandle);
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<ReplacerAndGapUndefined>(valHandle);
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<ReplacerAndGapUndefined>(valHandle);
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<ReplacerAndGapUndefined>(valHandle);
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<ReplacerAndGapUndefined>(valHandle);
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<ReplacerAndGapUndefined>(valHandle);
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<ReplacerAndGapUndefined>(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
} else {
CheckStackPushSameValue(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
SerializeJSONObject<ReplacerAndGapUndefined>(valHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
}
return tagValue;
}
}
}
}
return JSTaggedValue::Undefined();
}
void JsonStringifier::SerializeObjectKey(const JSHandle<JSTaggedValue> &key, bool hasContent)
{
if (hasContent) {
AppendChar(',');
}
if (!gap_.empty()) {
NewLine();
}
if (key->IsString()) {
JSTaggedValue keyValue = key.GetTaggedValue();
auto string = JSHandle<EcmaString>(thread_, key->GetTaggedObject());
if (GetKeyCache()->TryGetCachedKey(keyValue)) {
const EcmaString* strPtr = string.GetObject<EcmaString>();
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
AppendFastPathKeyString(strPtr, oneByteResult_);
} else {
AppendFastPathKeyString(strPtr, twoBytesResult_);
}
} else {
if (AppendEcmaStringToResult(string)) {
GetKeyCache()->CacheKey(keyValue);
}
}
} else if (key->IsInt()) {
AppendChar('\"');
AppendIntToResult(static_cast<int32_t>(key->GetInt()));
AppendChar('\"');
} else {
auto str = JSTaggedValue::ToString(thread_, key);
AppendEcmaStringToResult(str);
}
AppendChar(':');
if (!gap_.empty()) {
AppendChar(' ');
}
}
bool JsonStringifier::PushValue(const JSHandle<JSTaggedValue> &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();
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeJSONObject(const JSHandle<JSTaggedValue> &value)
{
Indent();
AppendChar('{');
bool hasContent = false;
ASSERT(!value->IsAccessor());
JSHandle<JSObject> obj(value);
if constexpr (!ReplacerAndGapUndefined) {
if (!replacer_->IsArray(thread_)) {
hasContent = SerializeObjectProperties<ReplacerAndGapUndefined>(obj, hasContent);
} else {
hasContent = SerializeObjectWithReplacerArray<ReplacerAndGapUndefined>(obj, hasContent);
}
} else {
hasContent = SerializeObjectProperties<ReplacerAndGapUndefined>(obj, hasContent);
}
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
Unindent();
if (hasContent && gap_.length() != 0) {
NewLine();
}
AppendChar('}');
PopValue();
return true;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeObjectProperties(const JSHandle<JSObject> &obj,
bool hasContent)
{
if (UNLIKELY(obj->IsJSProxy() || obj->IsTypedArray())) {
JSHandle<TaggedArray> 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<JSTaggedValue> valueHandle =
JSTaggedValue::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), handleKey_).GetValue();
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
hasContent = ProcessSerializedProperty<ReplacerAndGapUndefined>(JSHandle<JSTaggedValue>(obj),
handleKey_, valueHandle, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
} else {
uint32_t numOfKeys = obj->GetNumberOfKeys(thread_);
uint32_t numOfElements = obj->GetNumberOfElements(thread_);
if (numOfElements > 0) {
hasContent = JsonStringifier::SerializeElements<ReplacerAndGapUndefined>(obj, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
if (numOfKeys > 0) {
hasContent = JsonStringifier::SerializeKeys<ReplacerAndGapUndefined>(obj, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
}
return hasContent;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeObjectWithReplacerArray(const JSHandle<JSObject> &obj,
bool hasContent)
{
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);
hasContent = ProcessSerializedProperty<ReplacerAndGapUndefined>(JSHandle<JSTaggedValue>(obj),
propList_[i], handleValue_, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
return hasContent;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::ProcessSerializedProperty(const JSHandle<JSTaggedValue> &object,
const JSHandle<JSTaggedValue> &key,
const JSHandle<JSTaggedValue> &value,
bool hasContent)
{
JSTaggedValue serializeValue = GetSerializeValue<ReplacerAndGapUndefined>(object, key, value);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
(serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
return hasContent;
}
handleValue_.Update(serializeValue);
SerializeObjectKey(key, hasContent);
JSTaggedValue res = SerializeJSONProperty<ReplacerAndGapUndefined>(handleValue_);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
if (!res.IsUndefined()) {
return true;
}
return hasContent;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeJSONSharedMap(const JSHandle<JSTaggedValue> &value)
{
[[maybe_unused]] ConcurrentApiScope<JSSharedMap> scope(thread_, value);
JSHandle<JSSharedMap> sharedMap(value);
JSHandle<LinkedHashMap> hashMap(thread_, sharedMap->GetLinkedMap(thread_));
return SerializeLinkedHashMap<ReplacerAndGapUndefined>(hashMap);
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeJSONSharedSet(const JSHandle<JSTaggedValue> &value)
{
[[maybe_unused]] ConcurrentApiScope<JSSharedSet> scope(thread_, value);
JSHandle<JSSharedSet> sharedSet(value);
JSHandle<LinkedHashSet> hashSet(thread_, sharedSet->GetLinkedSet(thread_));
return SerializeLinkedHashSet<ReplacerAndGapUndefined>(hashSet);
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeJSONMap(const JSHandle<JSTaggedValue> &value)
{
JSHandle<JSMap> jsMap(value);
JSHandle<LinkedHashMap> hashMap(thread_, jsMap->GetLinkedMap(thread_));
return SerializeLinkedHashMap<ReplacerAndGapUndefined>(hashMap);
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeJSONSet(const JSHandle<JSTaggedValue> &value)
{
JSHandle<JSSet> jsSet(value);
JSHandle<LinkedHashSet> hashSet(thread_, jsSet->GetLinkedSet(thread_));
return SerializeLinkedHashSet<ReplacerAndGapUndefined>(hashSet);
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeJSONHashMap(const JSHandle<JSTaggedValue> &value)
{
AppendChar('{');
JSHandle<JSAPIHashMap> hashMap(value);
JSHandle<TaggedHashArray> table(thread_, hashMap->GetTable(thread_));
uint32_t len = table->GetLength();
ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
JSMutableHandle<TaggedQueue> queue(thread_, factory->NewTaggedQueue(0));
JSMutableHandle<TaggedNode> node(thread_, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> keyHandle(thread_, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> 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())) {
AppendChar('"');
SerializeJSONProperty<ReplacerAndGapUndefined>(keyHandle);
AppendChar('"');
} else {
SerializeJSONProperty<ReplacerAndGapUndefined>(keyHandle);
}
AppendChar(':');
SerializeJSONProperty<ReplacerAndGapUndefined>(valueHandle);
AppendChar(',');
needRemove = true;
}
if (needRemove) {
PopBack();
}
AppendChar('}');
PopValue();
return true;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeJSONHashSet(const JSHandle<JSTaggedValue> &value)
{
AppendChar('[');
JSHandle<JSAPIHashSet> hashSet(value);
JSHandle<TaggedHashArray> table(thread_, hashSet->GetTable(thread_));
uint32_t len = table->GetLength();
ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
JSMutableHandle<TaggedQueue> queue(thread_, factory->NewTaggedQueue(0));
JSMutableHandle<TaggedNode> node(thread_, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> 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<ReplacerAndGapUndefined>(currentKey);
if (res.IsUndefined()) {
AppendLiteral("null");
}
AppendChar(',');
needRemove = true;
}
if (needRemove) {
PopBack();
}
AppendChar(']');
PopValue();
return true;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeLinkedHashMap(const JSHandle<LinkedHashMap> &hashMap)
{
AppendChar('{');
int index = 0;
int totalElements = hashMap->NumberOfElements() + hashMap->NumberOfDeletedElements();
JSMutableHandle<JSTaggedValue> keyHandle(thread_, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> 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())) {
AppendLiteral("\"undefined\"");
} else if (UNLIKELY(!keyHandle->IsString())) {
AppendLiteral("\"");
SerializeJSONProperty<ReplacerAndGapUndefined>(keyHandle);
AppendLiteral("\"");
} else {
SerializeJSONProperty<ReplacerAndGapUndefined>(keyHandle);
}
AppendChar(':');
SerializeJSONProperty<ReplacerAndGapUndefined>(valHandle);
AppendChar(',');
needRemove = true;
}
if (needRemove) {
PopBack();
}
AppendChar('}');
PopValue();
return true;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeLinkedHashSet(const JSHandle<LinkedHashSet> &hashSet)
{
AppendChar('[');
JSMutableHandle<JSTaggedValue> 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<ReplacerAndGapUndefined>(keyHandle);
if (res.IsUndefined()) {
AppendLiteral("null");
}
AppendChar(',');
needRemove = true;
}
if (needRemove) {
PopBack();
}
AppendChar(']');
PopValue();
return true;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeJSProxy(const JSHandle<JSTaggedValue> &object)
{
bool isContain = PushValue(object);
if (isContain) {
THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
}
Indent();
AppendChar('[');
JSHandle<JSProxy> proxy(object);
JSHandle<JSTaggedValue> lengthKey = thread_->GlobalConstants()->GetHandledLengthString();
JSHandle<JSTaggedValue> 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<JSTaggedValue> valHandle = JSProxy::GetProperty(thread_, proxy, handleKey_).GetValue();
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
if (i > 0) {
AppendChar(',');
}
if (!gap_.empty()) {
NewLine();
}
JSTaggedValue serializeValue = GetSerializeValue<ReplacerAndGapUndefined>(object, handleKey_, valHandle);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
handleValue_.Update(serializeValue);
JSTaggedValue res = SerializeJSONProperty<ReplacerAndGapUndefined>(handleValue_);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
if (res.IsUndefined()) {
AppendLiteral("null");
}
}
if (length > 0 && !gap_.empty()) {
NewLine();
}
AppendChar(']');
PopValue();
Unindent();
return true;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeJSArray(const JSHandle<JSTaggedValue> &value)
{
bool isContain = PushValue(value);
if (isContain) {
THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
}
AppendChar('[');
uint32_t len = 0;
if (value->IsJSArray()) {
JSHandle<JSArray> jsArr(value);
len = jsArr->GetArrayLength();
} else if (value->IsJSSharedArray()) {
JSHandle<JSSharedArray> jsArr(value);
len = jsArr->GetArrayLength();
}
if (len > 0) {
Indent();
JSMutableHandle<JSTaggedValue> tagValHandle(thread_, JSTaggedValue::Undefined());
for (uint32_t i = 0; i < len; i++) {
bool useFastArrayPath = ReplacerAndGapUndefined && gap_.empty();
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);
tagValHandle.Update(tagVal);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
if (i > 0) {
AppendChar(',');
}
if (!gap_.empty()) {
NewLine();
}
if (useFastArrayPath && !tagVal.IsHeapObject() &&
SerializeFastArrayPrimitive<ReplacerAndGapUndefined>(tagVal)) {
continue;
}
tagValHandle.Update(tagVal);
handleKey_.Update(JSTaggedValue(i));
handleValue_.Update(tagVal);
JSTaggedValue serializeValue = GetSerializeValue<ReplacerAndGapUndefined>(value, handleKey_, handleValue_);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
handleValue_.Update(serializeValue);
JSTaggedValue res = SerializeJSONProperty<ReplacerAndGapUndefined>(handleValue_);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
if (res.IsUndefined()) {
AppendLiteral("null");
}
}
if (!gap_.empty()) {
Unindent();
NewLine();
}
}
AppendChar(']');
PopValue();
return true;
}
template <bool ReplacerAndGapUndefined>
void JsonStringifier::SerializePrimitiveRef(const JSHandle<JSTaggedValue> &primitiveRef)
{
JSTaggedValue primitive = JSPrimitiveRef::Cast(primitiveRef.GetTaggedValue().GetTaggedObject())->GetValue(thread_);
if (primitive.IsString()) {
auto priStr = JSTaggedValue::ToString(thread_, primitiveRef);
RETURN_IF_ABRUPT_COMPLETION(thread_);
AppendEcmaStringToResult(priStr);
} else if (primitive.IsNumber()) {
auto priNum = JSTaggedValue::ToNumber(thread_, primitiveRef);
RETURN_IF_ABRUPT_COMPLETION(thread_);
if (std::isfinite(priNum.GetNumber())) {
AppendNumberToResult(JSHandle<JSTaggedValue>(thread_, priNum));
} else {
AppendLiteral("null");
}
} else if (primitive.IsBoolean()) {
if (primitive.IsTrue()) {
AppendLiteral("true");
} else {
AppendLiteral("false");
}
} else if (primitive.IsBigInt()) {
if (transformType_ == TransformType::NORMAL) {
THROW_TYPE_ERROR(thread_, "cannot serialize a BigInt");
} else {
auto value = JSHandle<JSTaggedValue>(thread_, primitive);
AppendBigIntToResult(value);
}
} else {
ASSERT(primitive.IsSymbol());
CheckStackPushSameValue(primitiveRef);
RETURN_IF_ABRUPT_COMPLETION(thread_);
SerializeJSONObject<ReplacerAndGapUndefined>(primitiveRef);
}
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeElements(const JSHandle<JSObject> &obj, bool hasContent)
{
bool useFastPath = !ElementAccessor::IsDictionaryMode(thread_, obj);
uint32_t processedIndex = 0;
if (useFastPath) {
uint32_t elementsLen = ElementAccessor::GetElementsLength(thread_, obj);
for (uint32_t i = 0; i < elementsLen; ++i) {
if (ElementAccessor::IsDictionaryMode(thread_, obj) &&
i < ElementAccessor::GetElementsLength(thread_, obj)) {
useFastPath = false;
processedIndex = i;
break;
}
if (!ElementAccessor::Get(thread_, obj, i).IsHole()) {
handleKey_.Update(JSTaggedValue(i));
handleValue_.Update(ElementAccessor::Get(thread_, obj, i));
hasContent = JsonStringifier::AppendJsonString<ReplacerAndGapUndefined>(obj, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
}
}
if (!useFastPath) {
JSHandle<TaggedArray> elementsArr(thread_, obj->GetElements(thread_));
JSHandle<NumberDictionary> numberDic(elementsArr);
CVector<JSHandle<JSTaggedValue>> sortArr;
int size = numberDic->Size();
for (int hashIndex = 0; hashIndex < size; hashIndex++) {
JSTaggedValue key = numberDic->GetKey(thread_, hashIndex);
if (!key.IsUndefined() && !key.IsHole()) {
if (key.IsInt() && static_cast<uint32_t>(key.GetInt()) < processedIndex) {
continue;
}
PropertyAttributes attr = numberDic->GetAttributes(thread_, hashIndex);
if (attr.IsEnumerable()) {
JSTaggedValue numberKey = key.IsInt() ? JSTaggedValue(static_cast<uint32_t>(key.GetInt())) :
JSTaggedValue(key.GetDouble());
sortArr.emplace_back(JSHandle<JSTaggedValue>(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<JSTaggedValue>(obj));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
handleValue_.Update(value);
hasContent = JsonStringifier::AppendJsonString<ReplacerAndGapUndefined>(obj, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
}
return hasContent;
}
bool JsonStringifier::ShouldSerializeKey(bool hasSymbol, JSTaggedValue key)
{
return !hasSymbol || key.IsString();
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::SerializeKeys(const JSHandle<JSObject> &obj, bool hasContent)
{
JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties(thread_));
if (!propertiesArr->IsDictionaryMode()) {
bool hasChangedToDictionaryMode = false;
JSHandle<JSHClass> jsHClass(thread_, obj->GetJSHClass());
bool hasSymbol = jsHClass->HasSymbolProperties();
if (jsHClass->GetEnumCache(thread_).IsEnumCacheOwnValid(thread_)) {
auto cache = JSHClass::GetEnumCacheOwnWithOutCheck(thread_, jsHClass);
uint32_t length = cache->GetLength();
uint32_t dictStart = length;
uint32_t inlinedProps = jsHClass->GetInlinedProperties();
for (uint32_t i = 0; i < length; i++) {
JSTaggedValue key = cache->Get(thread_, i);
if (!ShouldSerializeKey(hasSymbol, key)) {
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<int>(attr.GetOffset()) == index);
value = attr.IsInlinedProps()
? obj->GetPropertyInlinedPropsWithRep(thread_, static_cast<uint32_t>(index), attr)
: propertiesArr->Get(thread_, static_cast<uint32_t>(index) - jsHClass->GetInlinedProperties());
if (attr.IsInlinedProps() && value.IsHole()) {
continue;
}
if (UNLIKELY(value.IsAccessor())) {
value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
JSHandle<JSTaggedValue>(obj));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
if (obj->GetProperties(thread_).IsDictionary()) {
dictStart = i;
handleValue_.Update(value);
hasContent = JsonStringifier::AppendJsonString<ReplacerAndGapUndefined>(obj, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
break;
}
}
handleValue_.Update(value);
hasContent = JsonStringifier::AppendJsonString<ReplacerAndGapUndefined>(obj, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
if (dictStart < length) {
propertiesArr = JSHandle<TaggedArray>(thread_, obj->GetProperties(thread_));
JSHandle<NameDictionary> 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<JSTaggedValue>(obj));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
handleValue_.Update(value);
hasContent = JsonStringifier::AppendJsonString<ReplacerAndGapUndefined>(obj, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
}
return hasContent;
}
int end = static_cast<int>(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<int>(attr.GetOffset()) == i);
if (ShouldSerializeKey(hasSymbol, key) && attr.IsEnumerable()) {
handleKey_.Update(key);
JSTaggedValue value = attr.IsInlinedProps()
? obj->GetPropertyInlinedPropsWithRep(thread_, static_cast<uint32_t>(i), attr)
: propertiesArr->Get(thread_, static_cast<uint32_t>(i) - jsHClass->GetInlinedProperties());
if (attr.IsInlinedProps() && value.IsHole()) {
continue;
}
if (UNLIKELY(value.IsAccessor())) {
value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
JSHandle<JSTaggedValue>(obj));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
handleValue_.Update(value);
hasContent = JsonStringifier::AppendJsonString<ReplacerAndGapUndefined>(obj, hasContent);
if (obj->GetProperties(thread_).IsDictionary()) {
hasChangedToDictionaryMode = true;
propertiesArr = JSHandle<TaggedArray>(thread_, obj->GetProperties(thread_));
}
if (!hasChangedToDictionaryMode) {
JSHClass *currentHClass = obj->GetJSHClass();
if (UNLIKELY(currentHClass != jsHClass.GetTaggedValue().GetTaggedObject())) {
jsHClass = JSHandle<JSHClass>(thread_, currentHClass);
hasSymbol = jsHClass->HasSymbolProperties();
}
}
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
} else {
JSHandle<NameDictionary> 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<JSTaggedValue>(obj));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
jsHClass = JSHandle<JSHClass>(thread_, obj->GetJSHClass());
}
handleValue_.Update(value);
hasContent = JsonStringifier::AppendJsonString<ReplacerAndGapUndefined>(obj, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
}
return hasContent;
}
if (obj->IsJSGlobalObject()) {
JSHandle<GlobalDictionary> globalDic(propertiesArr);
int size = globalDic->Size();
CVector<std::pair<JSHandle<JSTaggedValue>, 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<JSHandle<JSTaggedValue>, PropertyAttributes> pair(JSHandle<JSTaggedValue>(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<JSTaggedValue>(obj));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
handleValue_.Update(value);
hasContent = JsonStringifier::AppendJsonString<ReplacerAndGapUndefined>(obj, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
return hasContent;
}
JSHandle<NameDictionary> nameDic(propertiesArr);
int size = nameDic->Size();
CVector<std::pair<JSHandle<JSTaggedValue>, 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<JSHandle<JSTaggedValue>, PropertyAttributes> pair(JSHandle<JSTaggedValue>(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<JSTaggedValue>(obj));
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
handleValue_.Update(value);
hasContent = JsonStringifier::AppendJsonString<ReplacerAndGapUndefined>(obj, hasContent);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
}
return hasContent;
}
template <bool ReplacerAndGapUndefined>
bool JsonStringifier::AppendJsonString(const JSHandle<JSObject> &obj, bool hasContent)
{
JSTaggedValue serializeValue =
GetSerializeValue<ReplacerAndGapUndefined>(JSHandle<JSTaggedValue>(obj), handleKey_, handleValue_);
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<ReplacerAndGapUndefined>(handleValue_);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
if (!res.IsUndefined()) {
return true;
}
return hasContent;
}
bool JsonStringifier::CheckStackPushSameValue(JSHandle<JSTaggedValue> value)
{
bool isContain = PushValue(value);
if (isContain) {
THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
}
return false;
}
inline void JsonStringifier::PopBack()
{
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
oneByteResult_.PopBack();
} else {
twoBytesResult_.PopBack();
}
}
inline void JsonStringifier::EnsureCapacityFor(size_t size)
{
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
oneByteResult_.EnsureCapacity(size);
} else {
twoBytesResult_.EnsureCapacity(size);
}
}
template <size_t N>
inline void JsonStringifier::AppendLiteral(const char(&src)[N])
{
EnsureCapacityFor(N - 1);
AppendStringInternal(src, N - 1);
}
inline void JsonStringifier::AppendChar(const char src)
{
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
oneByteResult_.EnsureCapacity(1);
oneByteResult_.Append(src);
} else {
twoBytesResult_.EnsureCapacity(1);
twoBytesResult_.Append(static_cast<char16_t>(src));
}
}
void JsonStringifier::ChangeEncoding()
{
encoding_ = Encoding::TWO_BYTE_ENCODING;
size_t length = oneByteResult_.GetLength();
twoBytesResult_.EnsureCapacity(length);
const uint8_t* src = oneByteResult_.GetBuffer();
uint16_t* dst = twoBytesResult_.GetBuffer();
for (size_t i = 0; i < length; ++i) {
dst[i] = static_cast<char16_t>(src[i]);
}
twoBytesResult_.SetLength(length);
}
void JsonStringifier::NewLine()
{
size_t needed = 1 + indent_ * gap_.size();
EnsureCapacityFor(needed);
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
oneByteResult_.Append('\n');
for (int i = 0; i < indent_; ++i) {
for (auto ch : gap_) {
oneByteResult_.Append(static_cast<char>(ch));
}
}
} else {
twoBytesResult_.Append('\n');
for (int i = 0; i < indent_; ++i) {
for (auto ch : gap_) {
twoBytesResult_.Append(ch);
}
}
}
}
inline void JsonStringifier::AppendNumberToResult(const JSHandle<JSTaggedValue> &value)
{
if (value->IsInt()) {
EnsureCapacityFor(INT_MAX_LENGTH);
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
AppendIntToFastStringBuilder(oneByteResult_, value->GetInt());
} else {
AppendIntToFastStringBuilder(twoBytesResult_, value->GetInt());
}
} else {
EnsureCapacityFor(DOUBLE_MAX_LENGTH);
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
AppendDoubleToFastStringBuilder(oneByteResult_, value->GetDouble());
} else {
AppendDoubleToFastStringBuilder(twoBytesResult_, value->GetDouble());
}
}
}
inline void JsonStringifier::AppendIntToResult(int32_t key)
{
EnsureCapacityFor(INT_MAX_LENGTH);
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
AppendIntToFastStringBuilder(oneByteResult_, key);
} else {
AppendIntToFastStringBuilder(twoBytesResult_, key);
}
}
template <typename DestChar>
void JsonStringifier::AppendIntToFastStringBuilder(FastStringBuilder<DestChar> &str, int number)
{
size_t start = str.GetLength();
int64_t n = static_cast<int64_t>(number);
bool isNeg = true;
if (n >= 0) {
n = -n;
isNeg = false;
}
do {
str.Append(static_cast<DestChar>('0' - (n % DEC_BASE)));
n /= DEC_BASE;
} while (n);
if (isNeg) {
str.Append(static_cast<DestChar>('-'));
}
size_t end = str.GetLength();
str.Reverse(start, end);
}
template <typename DestChar>
bool JsonStringifier::AppendSpecialDoubleToFastStringBuilder(FastStringBuilder<DestChar> &str, double d)
{
if (d == 0.0) {
str.AppendCString(base::NumberHelper::ZERO_STR.c_str());
return true;
}
if (std::isnan(d)) {
str.AppendCString(base::NumberHelper::NAN_STR.c_str());
return true;
}
if (std::isinf(d)) {
str.AppendCString((d < 0 ? base::NumberHelper::MINUS_INFINITY_STR : base::NumberHelper::INFINITY_STR).c_str());
return true;
}
return false;
}
template <typename DestChar>
void JsonStringifier::AppendDoubleToFastStringBuilder(FastStringBuilder<DestChar> &str, double d)
{
if (AppendSpecialDoubleToFastStringBuilder(str, d)) {
return;
}
bool isNeg = d < 0;
if (isNeg) {
d = -d;
}
ASSERT(d > 0);
constexpr int startIdx = 8;
char buff[base::JS_DTOA_BUF_SIZE] = {0};
char *result = buff + startIdx;
ASSERT(startIdx < base::JS_DTOA_BUF_SIZE);
int n = 0;
int k = 0;
int len = 0;
DtoaHelper::Dtoa(d, result, &n, &k);
if (n > 0 && n <= base::MAX_DIGITS) {
if (k <= n) {
std::fill(result + k, result + n, '0');
len = n;
} else {
--result;
std::copy(result + 1, result + 1 + n, result);
result[n] = '.';
len = k + 1;
}
} else if (base::MIN_DIGITS < n && n <= 0) {
constexpr int prefixLen = 2;
result -= (-n + prefixLen);
ASSERT(result >= buff);
ASSERT(result + prefixLen - n <= buff + base::JS_DTOA_BUF_SIZE);
result[0] = '0';
result[1] = '.';
std::fill(result + prefixLen, result + prefixLen - n, '0');
len = -n + prefixLen + k;
} else {
int pos = k;
if (k != 1) {
--result;
result[0] = result[1];
result[1] = '.';
++pos;
}
result[pos++] = 'e';
if (n >= 1) {
result[pos++] = '+';
}
auto expo = std::to_string(n - 1);
for (size_t i = 0; i < expo.length(); ++i) {
result[pos++] = expo[i];
}
len = pos;
}
if (isNeg) {
--result;
result[0] = '-';
len += 1;
}
str.AppendString(result, len);
}
inline void JsonStringifier::AppendBigIntToResult(JSHandle<JSTaggedValue> &valHandle)
{
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
AppendBigIntToString(oneByteResult_, valHandle.GetObject<BigInt>());
} else {
AppendBigIntToString(twoBytesResult_, valHandle.GetObject<BigInt>());
}
}
template <typename DestType>
void JsonStringifier::AppendBigIntToString(DestType &str, BigInt *bigint)
{
DISALLOW_GARBAGE_COLLECTION;
constexpr uint32_t conversionToRadix = BigInt::DECIMAL;
CString res = BigIntHelper::Conversion(BigIntHelper::GetBinary(bigint), conversionToRadix, BINARY);
size_t needed = res.size() + 1;
str.EnsureCapacity(needed);
if (bigint->GetSign()) {
str.Append('-');
}
str.AppendString(res.c_str(), res.size());
}
inline bool JsonStringifier::AppendEcmaStringToResult(JSHandle<EcmaString> &string)
{
if (encoding_ == Encoding::ONE_BYTE_ENCODING) {
if (EcmaStringAccessor(*string).IsUtf8()) {
return AppendQuotedStringToFastStringBuilder<uint8_t>(thread_,
oneByteResult_, string.GetObject<EcmaString>());
} else {
ChangeEncoding();
return AppendQuotedStringToFastStringBuilder<uint16_t>(thread_,
twoBytesResult_, string.GetObject<EcmaString>());
}
} else {
return AppendQuotedStringToFastStringBuilder<uint16_t>(thread_,
twoBytesResult_, string.GetObject<EcmaString>());
}
}
template <typename BuilderType>
inline void JsonStringifier::AppendFastPathKeyString(const EcmaString* strPtr, BuilderType &output)
{
EcmaStringAccessor accessor(const_cast<EcmaString*>(strPtr));
uint32_t strLen = accessor.GetLength();
size_t maxAdditionalLength = strLen + 2;
output.EnsureCapacity(maxAdditionalLength);
output.Append('"');
CVector<uint8_t> buf;
const uint8_t* data = accessor.GetUtf8DataFlat(thread_, strPtr, buf);
output.AppendString(reinterpret_cast<const char*>(data), strLen);
output.Append('"');
}
template <typename DestChar>
bool JsonStringifier::AppendQuotedStringToFastStringBuilder(const JSThread *thread,
FastStringBuilder<DestChar> &builder,
const EcmaString *str)
{
uint32_t strLen = EcmaStringAccessor(const_cast<EcmaString *>(str)).GetLength();
size_t maxAdditionalLength = std::max(strLen << 3, static_cast<uint32_t>(8));
builder.EnsureCapacity(maxAdditionalLength);
if (EcmaStringAccessor(const_cast<EcmaString *>(str)).IsUtf8()) {
CVector<uint8_t> buf;
const uint8_t *data = EcmaStringAccessor::GetUtf8DataFlat(thread, str, buf);
const common::Span<const uint8_t> dataSpan(data, strLen);
return base::JsonHelper::AppendValueToQuotedString(dataSpan, builder);
} else {
CVector<uint16_t> buf;
const uint16_t *data = EcmaStringAccessor::GetUtf16DataFlat(thread, str, buf);
const common::Span<const uint16_t> dataSpan(data, strLen);
return base::JsonHelper::AppendValueToQuotedString(dataSpan, builder);
}
}
}