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