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