1/*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "ecmascript/base/json_stringifier.h"
17
18#include "ecmascript/global_dictionary-inl.h"
19#include "ecmascript/interpreter/interpreter.h"
20#include "ecmascript/js_primitive_ref.h"
21#include "ecmascript/object_fast_operator-inl.h"
22
23namespace panda::ecmascript::base {
24constexpr int GAP_MAX_LEN = 10;
25using TransformType = base::JsonHelper::TransformType;
26
27JSHandle<JSTaggedValue> JsonStringifier::Stringify(const JSHandle<JSTaggedValue> &value,
28                                                   const JSHandle<JSTaggedValue> &replacer,
29                                                   const JSHandle<JSTaggedValue> &gap)
30{
31    factory_ = thread_->GetEcmaVM()->GetFactory();
32    handleValue_ = JSMutableHandle<JSTaggedValue>(thread_, JSTaggedValue::Undefined());
33    handleKey_ = JSMutableHandle<JSTaggedValue>(thread_, JSTaggedValue::Undefined());
34    // Let isArray be IsArray(replacer).
35    bool isArray = replacer->IsArray(thread_);
36    // ReturnIfAbrupt(isArray).
37    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
38    // If isArray is true, then
39    if (isArray) {
40        uint32_t len = 0;
41        if (replacer->IsJSArray()) {
42            // FastPath
43            JSHandle<JSArray> arr(replacer);
44            len = arr->GetArrayLength();
45        } else if (replacer->IsJSSharedArray()) {
46            JSHandle<JSSharedArray> arr(replacer);
47            len = arr->GetArrayLength();
48        } else {
49            // Let len be ToLength(Get(replacer, "length")).
50            JSHandle<JSTaggedValue> lengthKey = thread_->GlobalConstants()->GetHandledLengthString();
51            JSHandle<JSTaggedValue> lenResult = JSTaggedValue::GetProperty(thread_, replacer, lengthKey).GetValue();
52            // ReturnIfAbrupt(len).
53            RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
54            JSTaggedNumber lenNumber = JSTaggedValue::ToLength(thread_, lenResult);
55            RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
56            len = lenNumber.ToUint32();
57        }
58        if (len > 0) {
59            JSMutableHandle<JSTaggedValue> propHandle(thread_, JSTaggedValue::Undefined());
60            // Repeat while k<len.
61            for (uint32_t i = 0; i < len; i++) {
62                // a. Let v be Get(replacer, ToString(k)).
63                JSTaggedValue prop = ObjectFastOperator::FastGetPropertyByIndex(thread_, replacer.GetTaggedValue(), i);
64                // b. ReturnIfAbrupt(v).
65                RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
66                /*
67                 * c. Let item be undefined.
68                 * d. If Type(v) is String, let item be v.
69                 * e. Else if Type(v) is Number, let item be ToString(v).
70                 * f. Else if Type(v) is Object, then
71                 * i. If v has a [[StringData]] or [[NumberData]] internal slot, let item be ToString(v).
72                 * ii. ReturnIfAbrupt(item).
73                 * g. If item is not undefined and item is not currently an element of PropertyList, then
74                 * i. Append item to the end of PropertyList.
75                 * h. Let k be k+1.
76                 */
77                propHandle.Update(prop);
78                if (prop.IsNumber() || prop.IsString()) {
79                    AddDeduplicateProp(propHandle);
80                    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
81                } else if (prop.IsJSPrimitiveRef()) {
82                    JSTaggedValue primitive = JSPrimitiveRef::Cast(prop.GetTaggedObject())->GetValue();
83                    if (primitive.IsNumber() || primitive.IsString()) {
84                        AddDeduplicateProp(propHandle);
85                        RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
86                    }
87                }
88            }
89        }
90    }
91
92    // If Type(space) is Object, then
93    if (gap->IsJSPrimitiveRef()) {
94        JSTaggedValue primitive = JSPrimitiveRef::Cast(gap->GetTaggedObject())->GetValue();
95        // a. If space has a [[NumberData]] internal slot, then
96        if (primitive.IsNumber()) {
97            // i. Let space be ToNumber(space).
98            JSTaggedNumber num = JSTaggedValue::ToNumber(thread_, gap);
99            // ii. ReturnIfAbrupt(space).
100            RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
101            CalculateNumberGap(num);
102        } else if (primitive.IsString()) {
103            // b. Else if space has a [[StringData]] internal slot, then
104            // i. Let space be ToString(space).
105            auto str = JSTaggedValue::ToString(thread_, gap);
106            // ii. ReturnIfAbrupt(space).
107            RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
108            CalculateStringGap(JSHandle<EcmaString>(thread_, str.GetTaggedValue()));
109        }
110    } else if (gap->IsNumber()) {
111        // If Type(space) is Number
112        CalculateNumberGap(gap.GetTaggedValue());
113    } else if (gap->IsString()) {
114        // Else if Type(space) is String
115        CalculateStringGap(JSHandle<EcmaString>::Cast(gap));
116    }
117
118    JSHandle<JSTaggedValue> key(factory_->GetEmptyString());
119    JSTaggedValue serializeValue = GetSerializeValue(value, key, value, replacer);
120    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
121    handleValue_.Update(serializeValue);
122    JSTaggedValue result = SerializeJSONProperty(handleValue_, replacer);
123    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
124    if (!result.IsUndefined()) {
125        return JSHandle<JSTaggedValue>(
126            factory_->NewFromUtf8Literal(reinterpret_cast<const uint8_t *>(result_.c_str()), result_.size()));
127    }
128    return thread_->GlobalConstants()->GetHandledUndefined();
129}
130
131void JsonStringifier::AddDeduplicateProp(const JSHandle<JSTaggedValue> &property)
132{
133    JSHandle<EcmaString> primString = JSTaggedValue::ToString(thread_, property);
134    RETURN_IF_ABRUPT_COMPLETION(thread_);
135    JSHandle<JSTaggedValue> addVal(thread_, *primString);
136
137    uint32_t propLen = propList_.size();
138    for (uint32_t i = 0; i < propLen; i++) {
139        if (JSTaggedValue::SameValue(propList_[i], addVal)) {
140            return;
141        }
142    }
143    propList_.emplace_back(addVal);
144}
145
146bool JsonStringifier::CalculateNumberGap(JSTaggedValue gap)
147{
148    double numValue = gap.GetNumber();
149    int num = static_cast<int>(numValue);
150    if (num > 0) {
151        int gapLength = std::min(num, GAP_MAX_LEN);
152        gap_.append(gapLength, ' ');
153        gap_.append("\0");
154    }
155    return true;
156}
157
158bool JsonStringifier::CalculateStringGap(const JSHandle<EcmaString> &primString)
159{
160    CString gapString = ConvertToString(*primString, StringConvertedUsage::LOGICOPERATION);
161    uint32_t gapLen = gapString.length();
162    if (gapLen > 0) {
163        uint32_t gapLength = gapLen;
164        if (gapLen > GAP_MAX_LEN) {
165            if (gapString.at(GAP_MAX_LEN - 1) == static_cast<char>(utf_helper::UTF8_2B_FIRST)) {
166                gapLength = GAP_MAX_LEN + 1;
167            } else {
168                gapLength = GAP_MAX_LEN;
169            }
170        }
171        gap_.append(gapString.c_str(), gapLength);
172        gap_.append("\0");
173    }
174    return true;
175}
176
177JSTaggedValue JsonStringifier::GetSerializeValue(const JSHandle<JSTaggedValue> &object,
178                                                 const JSHandle<JSTaggedValue> &key,
179                                                 const JSHandle<JSTaggedValue> &value,
180                                                 const JSHandle<JSTaggedValue> &replacer)
181{
182    JSTaggedValue tagValue = value.GetTaggedValue();
183    JSHandle<JSTaggedValue> undefined = thread_->GlobalConstants()->GetHandledUndefined();
184    // If Type(value) is Object, then
185    if (value->IsECMAObject() || value->IsBigInt()) {
186        // a. Let toJSON be Get(value, "toJSON").
187        JSHandle<JSTaggedValue> toJson = thread_->GlobalConstants()->GetHandledToJsonString();
188        JSHandle<JSTaggedValue> toJsonFun(
189            thread_, ObjectFastOperator::FastGetPropertyByValue(thread_, tagValue, toJson.GetTaggedValue()));
190        // b. ReturnIfAbrupt(toJSON).
191        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
192        tagValue = value.GetTaggedValue();
193        // c. If IsCallable(toJSON) is true
194        if (UNLIKELY(toJsonFun->IsCallable())) {
195            // Let value be Call(toJSON, value, «key»).
196            EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread_, toJsonFun, value, undefined, 1);
197            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
198            info->SetCallArg(key.GetTaggedValue());
199            tagValue = JSFunction::Call(info);
200            // ii. ReturnIfAbrupt(value).
201            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
202        }
203    }
204
205    if (UNLIKELY(replacer->IsCallable())) {
206        handleValue_.Update(tagValue);
207        // a. Let value be Call(ReplacerFunction, holder, «key, value»).
208        const uint32_t argsLength = 2; // 2: «key, value»
209        JSHandle<JSTaggedValue> holder = SerializeHolder(object, value);
210        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
211        EcmaRuntimeCallInfo *info =
212            EcmaInterpreter::NewRuntimeCallInfo(thread_, replacer, holder, undefined, argsLength);
213        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
214        info->SetCallArg(key.GetTaggedValue(), handleValue_.GetTaggedValue());
215        tagValue = JSFunction::Call(info);
216        // b. ReturnIfAbrupt(value).
217        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
218    }
219    return tagValue;
220}
221
222JSHandle<JSTaggedValue> JsonStringifier::SerializeHolder(const JSHandle<JSTaggedValue> &object,
223                                                         const JSHandle<JSTaggedValue> &value)
224{
225    if (stack_.size() <= 0) {
226        JSHandle<JSObject> holder = factory_->CreateNullJSObject();
227        JSHandle<JSTaggedValue> holderKey(factory_->GetEmptyString());
228        JSObject::CreateDataPropertyOrThrow(thread_, holder, holderKey, value);
229        RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
230        return JSHandle<JSTaggedValue>(holder);
231    }
232    return object;
233}
234
235JSTaggedValue JsonStringifier::SerializeJSONProperty(const JSHandle<JSTaggedValue> &value,
236                                                     const JSHandle<JSTaggedValue> &replacer)
237{
238    JSTaggedValue tagValue = value.GetTaggedValue();
239    if (!tagValue.IsHeapObject()) {
240        JSTaggedType type = tagValue.GetRawData();
241        switch (type) {
242            // If value is false, return "false".
243            case JSTaggedValue::VALUE_FALSE:
244                result_ += "false";
245                return tagValue;
246            // If value is true, return "true".
247            case JSTaggedValue::VALUE_TRUE:
248                result_ += "true";
249                return tagValue;
250            // If value is null, return "null".
251            case JSTaggedValue::VALUE_NULL:
252                result_ += "null";
253                return tagValue;
254            default:
255                // If Type(value) is Number, then
256                if (tagValue.IsNumber()) {
257                    // a. If value is finite, return ToString(value).
258                    if (std::isfinite(tagValue.GetNumber())) {
259                        result_ += ConvertToString(*base::NumberHelper::NumberToString(thread_, tagValue));
260                    } else {
261                        // b. Else, return "null".
262                        result_ += "null";
263                    }
264                    return tagValue;
265                }
266        }
267    } else {
268        JSType jsType = tagValue.GetTaggedObject()->GetClass()->GetObjectType();
269        JSHandle<JSTaggedValue> valHandle(thread_, tagValue);
270        switch (jsType) {
271            case JSType::JS_ARRAY:
272            case JSType::JS_SHARED_ARRAY: {
273                SerializeJSArray(valHandle, replacer);
274                RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
275                return tagValue;
276            }
277            case JSType::JS_API_LINKED_LIST: {
278                JSHandle listHandle = JSHandle<JSAPILinkedList>(thread_, tagValue);
279                CheckStackPushSameValue(valHandle);
280                RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
281                valHandle = JSHandle<JSTaggedValue>(thread_, JSAPILinkedList::ConvertToArray(thread_, listHandle));
282                SerializeJSONObject(valHandle, replacer);
283                RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
284                return tagValue;
285            }
286            case JSType::JS_API_LIST: {
287                JSHandle listHandle = JSHandle<JSAPIList>(thread_, tagValue);
288                CheckStackPushSameValue(valHandle);
289                RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
290                valHandle = JSHandle<JSTaggedValue>(thread_, JSAPIList::ConvertToArray(thread_, listHandle));
291                SerializeJSONObject(valHandle, replacer);
292                RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
293                return tagValue;
294            }
295            // If Type(value) is String, return QuoteJSONString(value).
296            case JSType::LINE_STRING:
297            case JSType::CONSTANT_STRING:
298            case JSType::TREE_STRING:
299            case JSType::SLICED_STRING: {
300                JSHandle<EcmaString> strHandle = JSHandle<EcmaString>(valHandle);
301                auto string = JSHandle<EcmaString>(thread_,
302                    EcmaStringAccessor::Flatten(thread_->GetEcmaVM(), strHandle));
303                CString str = ConvertToString(*string, StringConvertedUsage::LOGICOPERATION);
304                JsonHelper::AppendValueToQuotedString(str, result_);
305                return tagValue;
306            }
307            case JSType::JS_PRIMITIVE_REF: {
308                SerializePrimitiveRef(valHandle);
309                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, JSTaggedValue::Exception());
310                return tagValue;
311            }
312            case JSType::SYMBOL:
313                return JSTaggedValue::Undefined();
314            case JSType::BIGINT: {
315                if (transformType_ == TransformType::NORMAL) {
316                    THROW_TYPE_ERROR_AND_RETURN(thread_, "cannot serialize a BigInt", JSTaggedValue::Exception());
317                } else {
318                    JSHandle<BigInt> thisBigint(thread_, valHandle.GetTaggedValue());
319                    auto bigIntStr = BigInt::ToString(thread_, thisBigint);
320                    result_ += ConvertToString(*bigIntStr);
321                    return tagValue;
322                }
323            }
324            case JSType::JS_NATIVE_POINTER: {
325                result_ += "{}";
326                return tagValue;
327            }
328            default: {
329                if (!tagValue.IsCallable()) {
330                    JSHClass *jsHclass = tagValue.GetTaggedObject()->GetClass();
331                    if (UNLIKELY(jsHclass->IsJSProxy() &&
332                        JSProxy::Cast(tagValue.GetTaggedObject())->IsArray(thread_))) {
333                        SerializeJSProxy(valHandle, replacer);
334                        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
335                    } else {
336                        CheckStackPushSameValue(valHandle);
337                        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
338                        SerializeJSONObject(valHandle, replacer);
339                        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
340                    }
341                    return tagValue;
342                }
343            }
344        }
345    }
346    return JSTaggedValue::Undefined();
347}
348
349void JsonStringifier::SerializeObjectKey(const JSHandle<JSTaggedValue> &key, bool hasContent)
350{
351    CString stepBegin;
352    CString stepEnd;
353    if (hasContent) {
354        result_ += ",";
355    }
356    if (!gap_.empty()) {
357        stepBegin += "\n";
358        stepBegin += indent_;
359        stepEnd += " ";
360    }
361    CString str;
362    if (key->IsString()) {
363        str = ConvertToString(EcmaString::Cast(key->GetTaggedObject()), StringConvertedUsage::LOGICOPERATION);
364    } else if (key->IsInt()) {
365        str = NumberHelper::IntToString(static_cast<int32_t>(key->GetInt()));
366    } else {
367        str = ConvertToString(*JSTaggedValue::ToString(thread_, key), StringConvertedUsage::LOGICOPERATION);
368    }
369    result_ += stepBegin;
370    JsonHelper::AppendValueToQuotedString(str, result_);
371    result_ += ":";
372    result_ += stepEnd;
373}
374
375bool JsonStringifier::PushValue(const JSHandle<JSTaggedValue> &value)
376{
377    uint32_t thisLen = stack_.size();
378
379    for (uint32_t i = 0; i < thisLen; i++) {
380        bool equal = JSTaggedValue::SameValue(stack_[i].GetTaggedValue(), value.GetTaggedValue());
381        if (equal) {
382            return true;
383        }
384    }
385
386    stack_.emplace_back(value);
387    return false;
388}
389
390void JsonStringifier::PopValue()
391{
392    stack_.pop_back();
393}
394
395bool JsonStringifier::SerializeJSONObject(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer)
396{
397    CString stepback = indent_;
398    indent_ += gap_;
399
400    result_ += "{";
401    bool hasContent = false;
402
403    ASSERT(!value->IsAccessor());
404    JSHandle<JSObject> obj(value);
405    if (!replacer->IsArray(thread_)) {
406        if (UNLIKELY(value->IsJSProxy() || value->IsTypedArray())) {  // serialize proxy and typedArray
407            JSHandle<TaggedArray> propertyArray = JSObject::EnumerableOwnNames(thread_, obj);
408            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
409            uint32_t arrLength = propertyArray->GetLength();
410            for (uint32_t i = 0; i < arrLength; i++) {
411                handleKey_.Update(propertyArray->Get(i));
412                JSHandle<JSTaggedValue> valueHandle = JSTaggedValue::GetProperty(thread_, value, handleKey_).GetValue();
413                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
414                JSTaggedValue serializeValue = GetSerializeValue(value, handleKey_, valueHandle, replacer);
415                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
416                if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
417                    (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
418                    continue;
419                }
420                handleValue_.Update(serializeValue);
421                SerializeObjectKey(handleKey_, hasContent);
422                JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer);
423                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
424                if (!res.IsUndefined()) {
425                    hasContent = true;
426                }
427            }
428        } else {
429            uint32_t numOfKeys = obj->GetNumberOfKeys();
430            uint32_t numOfElements = obj->GetNumberOfElements();
431            if (numOfElements > 0) {
432                hasContent = JsonStringifier::SerializeElements(obj, replacer, hasContent);
433                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
434            }
435            if (numOfKeys > 0) {
436                hasContent = JsonStringifier::SerializeKeys(obj, replacer, hasContent);
437                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
438            }
439        }
440    } else {
441        uint32_t propLen = propList_.size();
442        for (uint32_t i = 0; i < propLen; i++) {
443            JSTaggedValue tagVal =
444                ObjectFastOperator::FastGetPropertyByValue(thread_, obj.GetTaggedValue(),
445                                                           propList_[i].GetTaggedValue());
446            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
447            handleValue_.Update(tagVal);
448            JSTaggedValue serializeValue = GetSerializeValue(value, propList_[i], handleValue_, replacer);
449            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
450            if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
451                (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
452                continue;
453            }
454            handleValue_.Update(serializeValue);
455            SerializeObjectKey(propList_[i], hasContent);
456            JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer);
457            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
458            if (!res.IsUndefined()) {
459                hasContent = true;
460            }
461        }
462    }
463    if (hasContent && gap_.length() != 0) {
464        result_ += "\n";
465        result_ += stepback;
466    }
467    result_ += "}";
468    PopValue();
469    indent_ = stepback;
470    return true;
471}
472
473bool JsonStringifier::SerializeJSProxy(const JSHandle<JSTaggedValue> &object, const JSHandle<JSTaggedValue> &replacer)
474{
475    bool isContain = PushValue(object);
476    if (isContain) {
477        THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
478    }
479
480    CString stepback = indent_;
481    CString stepBegin;
482    indent_ += gap_;
483
484    if (!gap_.empty()) {
485        stepBegin += "\n";
486        stepBegin += indent_;
487    }
488    result_ += "[";
489    JSHandle<JSProxy> proxy(object);
490    JSHandle<JSTaggedValue> lengthKey = thread_->GlobalConstants()->GetHandledLengthString();
491    JSHandle<JSTaggedValue> lenghHandle = JSProxy::GetProperty(thread_, proxy, lengthKey).GetValue();
492    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
493    JSTaggedNumber lenNumber = JSTaggedValue::ToLength(thread_, lenghHandle);
494    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
495    uint32_t length = lenNumber.ToUint32();
496    for (uint32_t i = 0; i < length; i++) {
497        handleKey_.Update(JSTaggedValue(i));
498        JSHandle<JSTaggedValue> valHandle = JSProxy::GetProperty(thread_, proxy, handleKey_).GetValue();
499        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
500        if (i > 0) {
501            result_ += ",";
502        }
503        result_ += stepBegin;
504        JSTaggedValue serializeValue = GetSerializeValue(object, handleKey_, valHandle, replacer);
505        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
506        handleValue_.Update(serializeValue);
507        JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer);
508        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
509        if (res.IsUndefined()) {
510            result_ += "null";
511        }
512    }
513
514    if (length > 0 && !gap_.empty()) {
515        result_ += "\n";
516        result_ += stepback;
517    }
518    result_ += "]";
519    PopValue();
520    indent_ = stepback;
521    return true;
522}
523
524bool JsonStringifier::SerializeJSArray(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer)
525{
526    // If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
527    bool isContain = PushValue(value);
528    if (isContain) {
529        THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
530    }
531
532    CString stepback = indent_;
533    CString stepBegin;
534    indent_ += gap_;
535
536    if (!gap_.empty()) {
537        stepBegin += "\n";
538        stepBegin += indent_;
539    }
540    result_ += "[";
541    uint32_t len = 0;
542    if (value->IsJSArray()) {
543        JSHandle<JSArray> jsArr(value);
544        len = jsArr->GetArrayLength();
545    } else if (value->IsJSSharedArray()) {
546        JSHandle<JSSharedArray> jsArr(value);
547        len = jsArr->GetArrayLength();
548    }
549    if (len > 0) {
550        for (uint32_t i = 0; i < len; i++) {
551            JSTaggedValue tagVal = ObjectFastOperator::FastGetPropertyByIndex(thread_, value.GetTaggedValue(), i);
552            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
553            if (UNLIKELY(tagVal.IsAccessor())) {
554                tagVal = JSObject::CallGetter(thread_, AccessorData::Cast(tagVal.GetTaggedObject()), value);
555                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
556            }
557            handleKey_.Update(JSTaggedValue(i));
558            handleValue_.Update(tagVal);
559
560            if (i > 0) {
561                result_ += ",";
562            }
563            result_ += stepBegin;
564            JSTaggedValue serializeValue = GetSerializeValue(value, handleKey_, handleValue_, replacer);
565            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
566            handleValue_.Update(serializeValue);
567            JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer);
568            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
569            if (res.IsUndefined()) {
570                result_ += "null";
571            }
572        }
573
574        if (!gap_.empty()) {
575            result_ += "\n";
576            result_ += stepback;
577        }
578    }
579
580    result_ += "]";
581    PopValue();
582    indent_ = stepback;
583    return true;
584}
585
586void JsonStringifier::SerializePrimitiveRef(const JSHandle<JSTaggedValue> &primitiveRef)
587{
588    JSTaggedValue primitive = JSPrimitiveRef::Cast(primitiveRef.GetTaggedValue().GetTaggedObject())->GetValue();
589    if (primitive.IsString()) {
590        auto priStr = JSTaggedValue::ToString(thread_, primitiveRef);
591        RETURN_IF_ABRUPT_COMPLETION(thread_);
592        CString str = ConvertToString(*priStr, StringConvertedUsage::LOGICOPERATION);
593        JsonHelper::AppendValueToQuotedString(str, result_);
594    } else if (primitive.IsNumber()) {
595        auto priNum = JSTaggedValue::ToNumber(thread_, primitiveRef);
596        RETURN_IF_ABRUPT_COMPLETION(thread_);
597        if (std::isfinite(priNum.GetNumber())) {
598            result_ += ConvertToString(*base::NumberHelper::NumberToString(thread_, priNum));
599        } else {
600            result_ += "null";
601        }
602    } else if (primitive.IsBoolean()) {
603        result_ += primitive.IsTrue() ? "true" : "false";
604    } else if (primitive.IsBigInt()) {
605        if (transformType_ == TransformType::NORMAL) {
606            THROW_TYPE_ERROR(thread_, "cannot serialize a BigInt");
607        } else {
608            JSHandle<BigInt> thisBigint(thread_, primitive);
609            auto bigIntStr = BigInt::ToString(thread_, thisBigint);
610            result_ += ConvertToString(*bigIntStr);
611        }
612    }
613}
614
615bool JsonStringifier::SerializeElements(const JSHandle<JSObject> &obj, const JSHandle<JSTaggedValue> &replacer,
616                                        bool hasContent)
617{
618    if (!ElementAccessor::IsDictionaryMode(obj)) {
619        uint32_t elementsLen = ElementAccessor::GetElementsLength(obj);
620        for (uint32_t i = 0; i < elementsLen; ++i) {
621            if (!ElementAccessor::Get(obj, i).IsHole()) {
622                handleKey_.Update(JSTaggedValue(i));
623                handleValue_.Update(ElementAccessor::Get(obj, i));
624                hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
625                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
626            }
627        }
628    } else {
629        JSHandle<TaggedArray> elementsArr(thread_, obj->GetElements());
630        JSHandle<NumberDictionary> numberDic(elementsArr);
631        CVector<JSHandle<JSTaggedValue>> sortArr;
632        int size = numberDic->Size();
633        for (int hashIndex = 0; hashIndex < size; hashIndex++) {
634            JSTaggedValue key = numberDic->GetKey(hashIndex);
635            if (!key.IsUndefined() && !key.IsHole()) {
636                PropertyAttributes attr = numberDic->GetAttributes(hashIndex);
637                if (attr.IsEnumerable()) {
638                    JSTaggedValue numberKey = JSTaggedValue(static_cast<uint32_t>(key.GetInt()));
639                    sortArr.emplace_back(JSHandle<JSTaggedValue>(thread_, numberKey));
640                }
641            }
642        }
643        std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareNumber);
644        for (const auto &entry : sortArr) {
645            JSTaggedValue entryKey = entry.GetTaggedValue();
646            handleKey_.Update(entryKey);
647            int index = numberDic->FindEntry(entryKey);
648            if (index < 0) {
649                continue;
650            }
651            JSTaggedValue value = numberDic->GetValue(index);
652            if (UNLIKELY(value.IsAccessor())) {
653                value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
654                                             JSHandle<JSTaggedValue>(obj));
655                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
656            }
657            handleValue_.Update(value);
658            hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
659            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
660        }
661    }
662    return hasContent;
663}
664
665bool JsonStringifier::SerializeKeys(const JSHandle<JSObject> &obj, const JSHandle<JSTaggedValue> &replacer,
666                                    bool hasContent)
667{
668    JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties());
669    if (!propertiesArr->IsDictionaryMode()) {
670        bool hasChangedToDictionaryMode = false;
671        JSHandle<JSHClass> jsHclass(thread_, obj->GetJSHClass());
672        JSTaggedValue enumCache = jsHclass->GetEnumCache();
673        if (JSObject::GetEnumCacheKind(thread_, enumCache) == EnumCacheKind::ONLY_OWN_KEYS) {
674            JSHandle<TaggedArray> cache(thread_, enumCache);
675            uint32_t length = cache->GetLength();
676            uint32_t dictStart = length;
677            for (uint32_t i = 0; i < length; i++) {
678                JSTaggedValue key = cache->Get(i);
679                if (!key.IsString()) {
680                    continue;
681                }
682                handleKey_.Update(key);
683                JSTaggedValue value;
684                LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
685                int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
686                PropertyAttributes attr(layoutInfo->GetAttr(index));
687                ASSERT(static_cast<int>(attr.GetOffset()) == index);
688                value = attr.IsInlinedProps()
689                        ? obj->GetPropertyInlinedPropsWithRep(static_cast<uint32_t>(index), attr)
690                        : propertiesArr->Get(static_cast<uint32_t>(index) - jsHclass->GetInlinedProperties());
691                if (attr.IsInlinedProps() && value.IsHole()) {
692                    continue;
693                }
694                if (UNLIKELY(value.IsAccessor())) {
695                    value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
696                                                 JSHandle<JSTaggedValue>(obj));
697                    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
698                    if (obj->GetProperties().IsDictionary()) {
699                        dictStart = i;
700                        handleValue_.Update(value);
701                        hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
702                        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
703                        break;
704                    }
705                }
706                handleValue_.Update(value);
707                hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
708                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
709            }
710            if (dictStart < length) {
711                propertiesArr = JSHandle<TaggedArray>(thread_, obj->GetProperties());
712                JSHandle<NameDictionary> nameDic(propertiesArr);
713                for (uint32_t i = dictStart + 1;i < length; i++) {
714                    JSTaggedValue key = cache->Get(i);
715                    int hashIndex = nameDic->FindEntry(key);
716                    PropertyAttributes attr = nameDic->GetAttributes(hashIndex);
717                    if (!key.IsString() || hashIndex < 0 || !attr.IsEnumerable()) {
718                        continue;
719                    }
720                    handleKey_.Update(key);
721                    JSTaggedValue value = nameDic->GetValue(hashIndex);
722                    if (UNLIKELY(value.IsAccessor())) {
723                        value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
724                            JSHandle<JSTaggedValue>(obj));
725                        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
726                    }
727                    handleValue_.Update(value);
728                    hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
729                    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
730                }
731            }
732            return hasContent;
733        }
734        int end = static_cast<int>(jsHclass->NumberOfProps());
735        if (end <= 0) {
736            return hasContent;
737        }
738        for (int i = 0; i < end; i++) {
739            LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
740            JSTaggedValue key = layoutInfo->GetKey(i);
741            if (!hasChangedToDictionaryMode) {
742                if (key.IsString() && layoutInfo->GetAttr(i).IsEnumerable()) {
743                    handleKey_.Update(key);
744                    JSTaggedValue value;
745                    int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
746                    PropertyAttributes attr(layoutInfo->GetAttr(index));
747                    ASSERT(static_cast<int>(attr.GetOffset()) == index);
748                    value = attr.IsInlinedProps()
749                            ? obj->GetPropertyInlinedPropsWithRep(static_cast<uint32_t>(index), attr)
750                            : propertiesArr->Get(static_cast<uint32_t>(index) - jsHclass->GetInlinedProperties());
751                    if (attr.IsInlinedProps() && value.IsHole()) {
752                        continue;
753                    }
754                    if (UNLIKELY(value.IsAccessor())) {
755                        value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
756                            JSHandle<JSTaggedValue>(obj));
757                        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
758                    }
759                    handleValue_.Update(value);
760                    hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
761                    if (obj->GetProperties().IsDictionary()) {
762                        hasChangedToDictionaryMode = true;
763                        propertiesArr = JSHandle<TaggedArray>(thread_, obj->GetProperties());
764                    }
765                    jsHclass = JSHandle<JSHClass>(thread_, obj->GetJSHClass());
766                    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
767                }
768            } else {
769                    JSHandle<NameDictionary> nameDic(propertiesArr);
770                    int index = nameDic->FindEntry(key);
771                    if (!key.IsString()) {
772                        continue;
773                    }
774                    PropertyAttributes attr = nameDic->GetAttributes(index);
775                    if (!attr.IsEnumerable() || index < 0) {
776                        continue;
777                    }
778                    JSTaggedValue value = nameDic->GetValue(index);
779                    handleKey_.Update(key);
780                    if (UNLIKELY(value.IsAccessor())) {
781                        value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
782                            JSHandle<JSTaggedValue>(obj));
783                        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
784                        jsHclass = JSHandle<JSHClass>(thread_, obj->GetJSHClass());
785                    }
786                    handleValue_.Update(value);
787                    hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
788                    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
789            }
790        }
791        return hasContent;
792    }
793    if (obj->IsJSGlobalObject()) {
794        JSHandle<GlobalDictionary> globalDic(propertiesArr);
795        int size = globalDic->Size();
796        CVector<std::pair<JSHandle<JSTaggedValue>, PropertyAttributes>> sortArr;
797        for (int hashIndex = 0; hashIndex < size; hashIndex++) {
798            JSTaggedValue key = globalDic->GetKey(hashIndex);
799            if (!key.IsString()) {
800                continue;
801            }
802            PropertyAttributes attr = globalDic->GetAttributes(hashIndex);
803            if (!attr.IsEnumerable()) {
804                continue;
805            }
806            std::pair<JSHandle<JSTaggedValue>, PropertyAttributes> pair(JSHandle<JSTaggedValue>(thread_, key), attr);
807            sortArr.emplace_back(pair);
808        }
809        std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
810        for (const auto &entry : sortArr) {
811            JSTaggedValue entryKey = entry.first.GetTaggedValue();
812            handleKey_.Update(entryKey);
813            int index = globalDic->FindEntry(entryKey);
814            if (index == -1) {
815                continue;
816            }
817            JSTaggedValue value = globalDic->GetValue(index);
818            if (UNLIKELY(value.IsAccessor())) {
819                value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
820                                             JSHandle<JSTaggedValue>(obj));
821                RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
822            }
823            handleValue_.Update(value);
824            hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
825            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
826        }
827        return hasContent;
828    }
829    JSHandle<NameDictionary> nameDic(propertiesArr);
830    int size = nameDic->Size();
831    CVector<std::pair<JSHandle<JSTaggedValue>, PropertyAttributes>> sortArr;
832    for (int hashIndex = 0; hashIndex < size; hashIndex++) {
833        JSTaggedValue key = nameDic->GetKey(hashIndex);
834        if (!key.IsString()) {
835            continue;
836        }
837        PropertyAttributes attr = nameDic->GetAttributes(hashIndex);
838        if (!attr.IsEnumerable()) {
839            continue;
840        }
841        std::pair<JSHandle<JSTaggedValue>, PropertyAttributes> pair(JSHandle<JSTaggedValue>(thread_, key), attr);
842        sortArr.emplace_back(pair);
843    }
844    std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
845    for (const auto &entry : sortArr) {
846        JSTaggedValue entryKey = entry.first.GetTaggedValue();
847        handleKey_.Update(entryKey);
848        int index = nameDic->FindEntry(entryKey);
849        if (index < 0) {
850            continue;
851        }
852        JSTaggedValue value = nameDic->GetValue(index);
853        if (UNLIKELY(value.IsAccessor())) {
854            value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
855                                         JSHandle<JSTaggedValue>(obj));
856            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
857        }
858        handleValue_.Update(value);
859        hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
860        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
861    }
862    return hasContent;
863}
864
865bool JsonStringifier::AppendJsonString(const JSHandle<JSObject> &obj, const JSHandle<JSTaggedValue> &replacer,
866                                       bool hasContent)
867{
868    JSTaggedValue serializeValue = GetSerializeValue(JSHandle<JSTaggedValue>(obj), handleKey_, handleValue_, replacer);
869    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
870    if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
871        (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
872        return hasContent;
873    }
874    handleValue_.Update(serializeValue);
875    SerializeObjectKey(handleKey_, hasContent);
876    JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer);
877    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
878    if (!res.IsUndefined()) {
879        return true;
880    }
881    return hasContent;
882}
883
884bool JsonStringifier::CheckStackPushSameValue(JSHandle<JSTaggedValue> value)
885{
886    bool isContain = PushValue(value);
887    if (isContain) {
888        THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
889    }
890    return false;
891}
892
893}  // namespace panda::ecmascript::base
894