• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
23 namespace panda::ecmascript::base {
24 constexpr int GAP_MAX_LEN = 10;
25 using TransformType = base::JsonHelper::TransformType;
26 
Stringify(const JSHandle<JSTaggedValue> & value,const JSHandle<JSTaggedValue> & replacer,const JSHandle<JSTaggedValue> & gap)27 JSHandle<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 
AddDeduplicateProp(const JSHandle<JSTaggedValue> & property)131 void 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 
CalculateNumberGap(JSTaggedValue gap)146 bool 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         for (int i = 0; i < gapLength; i++) {
153             gap_ += " ";
154         }
155         gap_.append("\0");
156     }
157     return true;
158 }
159 
CalculateStringGap(const JSHandle<EcmaString> & primString)160 bool JsonStringifier::CalculateStringGap(const JSHandle<EcmaString> &primString)
161 {
162     CString gapString = ConvertToString(*primString, StringConvertedUsage::LOGICOPERATION);
163     uint32_t gapLen = gapString.length();
164     if (gapLen > 0) {
165         uint32_t gapLength = gapLen;
166         if (gapLen > GAP_MAX_LEN) {
167             if (gapString.at(GAP_MAX_LEN - 1) == static_cast<char>(utf_helper::UTF8_2B_FIRST)) {
168                 gapLength = GAP_MAX_LEN + 1;
169             } else {
170                 gapLength = GAP_MAX_LEN;
171             }
172         }
173         CString str = gapString.substr(0, gapLength);
174         gap_ += str;
175         gap_.append("\0");
176     }
177     return true;
178 }
179 
GetSerializeValue(const JSHandle<JSTaggedValue> & object,const JSHandle<JSTaggedValue> & key,const JSHandle<JSTaggedValue> & value,const JSHandle<JSTaggedValue> & replacer)180 JSTaggedValue JsonStringifier::GetSerializeValue(const JSHandle<JSTaggedValue> &object,
181                                                  const JSHandle<JSTaggedValue> &key,
182                                                  const JSHandle<JSTaggedValue> &value,
183                                                  const JSHandle<JSTaggedValue> &replacer)
184 {
185     JSTaggedValue tagValue = value.GetTaggedValue();
186     JSHandle<JSTaggedValue> undefined = thread_->GlobalConstants()->GetHandledUndefined();
187     // If Type(value) is Object, then
188     if (value->IsECMAObject() || value->IsBigInt()) {
189         // a. Let toJSON be Get(value, "toJSON").
190         JSHandle<JSTaggedValue> toJson = thread_->GlobalConstants()->GetHandledToJsonString();
191         JSHandle<JSTaggedValue> toJsonFun(
192             thread_, ObjectFastOperator::FastGetPropertyByValue(thread_, tagValue, toJson.GetTaggedValue()));
193         // b. ReturnIfAbrupt(toJSON).
194         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
195         tagValue = value.GetTaggedValue();
196         // c. If IsCallable(toJSON) is true
197         if (UNLIKELY(toJsonFun->IsCallable())) {
198             // Let value be Call(toJSON, value, «key»).
199             EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread_, toJsonFun, value, undefined, 1);
200             RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
201             info->SetCallArg(key.GetTaggedValue());
202             tagValue = JSFunction::Call(info);
203             // ii. ReturnIfAbrupt(value).
204             RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
205         }
206     }
207 
208     if (UNLIKELY(replacer->IsCallable())) {
209         handleValue_.Update(tagValue);
210         // a. Let value be Call(ReplacerFunction, holder, «key, value»).
211         const uint32_t argsLength = 2; // 2: «key, value»
212         JSHandle<JSTaggedValue> holder = SerializeHolder(object, value);
213         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
214         EcmaRuntimeCallInfo *info =
215             EcmaInterpreter::NewRuntimeCallInfo(thread_, replacer, holder, undefined, argsLength);
216         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
217         info->SetCallArg(key.GetTaggedValue(), handleValue_.GetTaggedValue());
218         tagValue = JSFunction::Call(info);
219         // b. ReturnIfAbrupt(value).
220         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
221     }
222     return tagValue;
223 }
224 
SerializeHolder(const JSHandle<JSTaggedValue> & object,const JSHandle<JSTaggedValue> & value)225 JSHandle<JSTaggedValue> JsonStringifier::SerializeHolder(const JSHandle<JSTaggedValue> &object,
226                                                          const JSHandle<JSTaggedValue> &value)
227 {
228     if (stack_.size() <= 0) {
229         JSHandle<JSObject> holder = factory_->CreateNullJSObject();
230         JSHandle<JSTaggedValue> holderKey(factory_->GetEmptyString());
231         JSObject::CreateDataPropertyOrThrow(thread_, holder, holderKey, value);
232         RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread_);
233         return JSHandle<JSTaggedValue>(holder);
234     }
235     return object;
236 }
237 
SerializeJSONProperty(const JSHandle<JSTaggedValue> & value,const JSHandle<JSTaggedValue> & replacer)238 JSTaggedValue JsonStringifier::SerializeJSONProperty(const JSHandle<JSTaggedValue> &value,
239                                                      const JSHandle<JSTaggedValue> &replacer)
240 {
241     JSTaggedValue tagValue = value.GetTaggedValue();
242     if (!tagValue.IsHeapObject()) {
243         JSTaggedType type = tagValue.GetRawData();
244         switch (type) {
245             // If value is false, return "false".
246             case JSTaggedValue::VALUE_FALSE:
247                 result_ += "false";
248                 return tagValue;
249             // If value is true, return "true".
250             case JSTaggedValue::VALUE_TRUE:
251                 result_ += "true";
252                 return tagValue;
253             // If value is null, return "null".
254             case JSTaggedValue::VALUE_NULL:
255                 result_ += "null";
256                 return tagValue;
257             default:
258                 // If Type(value) is Number, then
259                 if (tagValue.IsNumber()) {
260                     // a. If value is finite, return ToString(value).
261                     if (std::isfinite(tagValue.GetNumber())) {
262                         result_ += ConvertToString(*base::NumberHelper::NumberToString(thread_, tagValue));
263                     } else {
264                         // b. Else, return "null".
265                         result_ += "null";
266                     }
267                     return tagValue;
268                 }
269         }
270     } else {
271         JSType jsType = tagValue.GetTaggedObject()->GetClass()->GetObjectType();
272         JSHandle<JSTaggedValue> valHandle(thread_, tagValue);
273         switch (jsType) {
274             case JSType::JS_ARRAY:
275             case JSType::JS_SHARED_ARRAY: {
276                 SerializeJSArray(valHandle, replacer);
277                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
278                 return tagValue;
279             }
280             case JSType::JS_API_LINKED_LIST: {
281                 JSHandle listHandle = JSHandle<JSAPILinkedList>(thread_, tagValue);
282                 CheckStackPushSameValue(valHandle);
283                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
284                 valHandle = JSHandle<JSTaggedValue>(thread_, JSAPILinkedList::ConvertToArray(thread_, listHandle));
285                 SerializeJSONObject(valHandle, replacer);
286                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
287                 return tagValue;
288             }
289             case JSType::JS_API_LIST: {
290                 JSHandle listHandle = JSHandle<JSAPIList>(thread_, tagValue);
291                 CheckStackPushSameValue(valHandle);
292                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
293                 valHandle = JSHandle<JSTaggedValue>(thread_, JSAPIList::ConvertToArray(thread_, listHandle));
294                 SerializeJSONObject(valHandle, replacer);
295                 RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
296                 return tagValue;
297             }
298             // If Type(value) is String, return QuoteJSONString(value).
299             case JSType::LINE_STRING:
300             case JSType::CONSTANT_STRING:
301             case JSType::TREE_STRING:
302             case JSType::SLICED_STRING: {
303                 JSHandle<EcmaString> strHandle = JSHandle<EcmaString>(valHandle);
304                 auto string = JSHandle<EcmaString>(thread_,
305                     EcmaStringAccessor::Flatten(thread_->GetEcmaVM(), strHandle));
306                 CString str = ConvertToString(*string, StringConvertedUsage::LOGICOPERATION);
307                 str = JsonHelper::ValueToQuotedString(str);
308                 result_ += str;
309                 return tagValue;
310             }
311             case JSType::JS_PRIMITIVE_REF: {
312                 SerializePrimitiveRef(valHandle);
313                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, JSTaggedValue::Exception());
314                 return tagValue;
315             }
316             case JSType::SYMBOL:
317                 return JSTaggedValue::Undefined();
318             case JSType::BIGINT: {
319                 if (transformType_ == TransformType::NORMAL) {
320                     THROW_TYPE_ERROR_AND_RETURN(thread_, "cannot serialize a BigInt", JSTaggedValue::Exception());
321                 } else {
322                     JSHandle<BigInt> thisBigint(thread_, valHandle.GetTaggedValue());
323                     auto bigIntStr = BigInt::ToString(thread_, thisBigint);
324                     result_ += ConvertToString(*bigIntStr);
325                     return tagValue;
326                 }
327             }
328             case JSType::JS_NATIVE_POINTER: {
329                 result_ += "{}";
330                 return tagValue;
331             }
332             default: {
333                 if (!tagValue.IsCallable()) {
334                     JSHClass *jsHclass = tagValue.GetTaggedObject()->GetClass();
335                     if (UNLIKELY(jsHclass->IsJSProxy() &&
336                         JSProxy::Cast(tagValue.GetTaggedObject())->IsArray(thread_))) {
337                         SerializeJSProxy(valHandle, replacer);
338                         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
339                     } else {
340                         CheckStackPushSameValue(valHandle);
341                         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
342                         SerializeJSONObject(valHandle, replacer);
343                         RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread_);
344                     }
345                     return tagValue;
346                 }
347             }
348         }
349     }
350     return JSTaggedValue::Undefined();
351 }
352 
SerializeObjectKey(const JSHandle<JSTaggedValue> & key,bool hasContent)353 void JsonStringifier::SerializeObjectKey(const JSHandle<JSTaggedValue> &key, bool hasContent)
354 {
355     CString stepBegin;
356     CString stepEnd;
357     if (hasContent) {
358         result_ += ",";
359     }
360     if (!gap_.empty()) {
361         stepBegin += "\n";
362         stepBegin += indent_;
363         stepEnd += " ";
364     }
365     CString str;
366     if (key->IsString()) {
367         str = ConvertToString(EcmaString::Cast(key->GetTaggedObject()), StringConvertedUsage::LOGICOPERATION);
368     } else if (key->IsInt()) {
369         str = NumberHelper::IntToString(static_cast<int32_t>(key->GetInt()));
370     } else {
371         str = ConvertToString(*JSTaggedValue::ToString(thread_, key), StringConvertedUsage::LOGICOPERATION);
372     }
373     result_ += stepBegin;
374     str = JsonHelper::ValueToQuotedString(str);
375     result_ += str;
376     result_ += ":";
377     result_ += stepEnd;
378 }
379 
PushValue(const JSHandle<JSTaggedValue> & value)380 bool JsonStringifier::PushValue(const JSHandle<JSTaggedValue> &value)
381 {
382     uint32_t thisLen = stack_.size();
383 
384     for (uint32_t i = 0; i < thisLen; i++) {
385         bool equal = JSTaggedValue::SameValue(stack_[i].GetTaggedValue(), value.GetTaggedValue());
386         if (equal) {
387             return true;
388         }
389     }
390 
391     stack_.emplace_back(value);
392     return false;
393 }
394 
PopValue()395 void JsonStringifier::PopValue()
396 {
397     stack_.pop_back();
398 }
399 
SerializeJSONObject(const JSHandle<JSTaggedValue> & value,const JSHandle<JSTaggedValue> & replacer)400 bool JsonStringifier::SerializeJSONObject(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer)
401 {
402     CString stepback = indent_;
403     indent_ += gap_;
404 
405     result_ += "{";
406     bool hasContent = false;
407 
408     ASSERT(!value->IsAccessor());
409     JSHandle<JSObject> obj(value);
410     if (!replacer->IsArray(thread_)) {
411         if (UNLIKELY(value->IsJSProxy() || value->IsTypedArray())) {  // serialize proxy and typedArray
412             JSHandle<TaggedArray> propertyArray = JSObject::EnumerableOwnNames(thread_, obj);
413             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
414             uint32_t arrLength = propertyArray->GetLength();
415             for (uint32_t i = 0; i < arrLength; i++) {
416                 handleKey_.Update(propertyArray->Get(i));
417                 JSHandle<JSTaggedValue> valueHandle = JSTaggedValue::GetProperty(thread_, value, handleKey_).GetValue();
418                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
419                 JSTaggedValue serializeValue = GetSerializeValue(value, handleKey_, valueHandle, replacer);
420                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
421                 if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
422                     (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
423                     continue;
424                 }
425                 handleValue_.Update(serializeValue);
426                 SerializeObjectKey(handleKey_, hasContent);
427                 JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer);
428                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
429                 if (!res.IsUndefined()) {
430                     hasContent = true;
431                 }
432             }
433         } else {
434             uint32_t numOfKeys = obj->GetNumberOfKeys();
435             uint32_t numOfElements = obj->GetNumberOfElements();
436             if (numOfElements > 0) {
437                 hasContent = JsonStringifier::SerializeElements(obj, replacer, hasContent);
438                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
439             }
440             if (numOfKeys > 0) {
441                 hasContent = JsonStringifier::SerializeKeys(obj, replacer, hasContent);
442                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
443             }
444         }
445     } else {
446         uint32_t propLen = propList_.size();
447         for (uint32_t i = 0; i < propLen; i++) {
448             JSTaggedValue tagVal =
449                 ObjectFastOperator::FastGetPropertyByValue(thread_, obj.GetTaggedValue(),
450                                                            propList_[i].GetTaggedValue());
451             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
452             handleValue_.Update(tagVal);
453             JSTaggedValue serializeValue = GetSerializeValue(value, propList_[i], handleValue_, replacer);
454             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
455             if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
456                 (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
457                 continue;
458             }
459             handleValue_.Update(serializeValue);
460             SerializeObjectKey(propList_[i], hasContent);
461             JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer);
462             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
463             if (!res.IsUndefined()) {
464                 hasContent = true;
465             }
466         }
467     }
468     if (hasContent && gap_.length() != 0) {
469         result_ += "\n";
470         result_ += stepback;
471     }
472     result_ += "}";
473     PopValue();
474     indent_ = stepback;
475     return true;
476 }
477 
SerializeJSProxy(const JSHandle<JSTaggedValue> & object,const JSHandle<JSTaggedValue> & replacer)478 bool JsonStringifier::SerializeJSProxy(const JSHandle<JSTaggedValue> &object, const JSHandle<JSTaggedValue> &replacer)
479 {
480     bool isContain = PushValue(object);
481     if (isContain) {
482         THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
483     }
484 
485     CString stepback = indent_;
486     CString stepBegin;
487     indent_ += gap_;
488 
489     if (!gap_.empty()) {
490         stepBegin += "\n";
491         stepBegin += indent_;
492     }
493     result_ += "[";
494     JSHandle<JSProxy> proxy(object);
495     JSHandle<JSTaggedValue> lengthKey = thread_->GlobalConstants()->GetHandledLengthString();
496     JSHandle<JSTaggedValue> lenghHandle = JSProxy::GetProperty(thread_, proxy, lengthKey).GetValue();
497     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
498     JSTaggedNumber lenNumber = JSTaggedValue::ToLength(thread_, lenghHandle);
499     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
500     uint32_t length = lenNumber.ToUint32();
501     for (uint32_t i = 0; i < length; i++) {
502         handleKey_.Update(JSTaggedValue(i));
503         JSHandle<JSTaggedValue> valHandle = JSProxy::GetProperty(thread_, proxy, handleKey_).GetValue();
504         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
505         if (i > 0) {
506             result_ += ",";
507         }
508         result_ += stepBegin;
509         JSTaggedValue serializeValue = GetSerializeValue(object, handleKey_, valHandle, replacer);
510         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
511         handleValue_.Update(serializeValue);
512         JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer);
513         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
514         if (res.IsUndefined()) {
515             result_ += "null";
516         }
517     }
518 
519     if (length > 0 && !gap_.empty()) {
520         result_ += "\n";
521         result_ += stepback;
522     }
523     result_ += "]";
524     PopValue();
525     indent_ = stepback;
526     return true;
527 }
528 
SerializeJSArray(const JSHandle<JSTaggedValue> & value,const JSHandle<JSTaggedValue> & replacer)529 bool JsonStringifier::SerializeJSArray(const JSHandle<JSTaggedValue> &value, const JSHandle<JSTaggedValue> &replacer)
530 {
531     // If state.[[Stack]] contains value, throw a TypeError exception because the structure is cyclical.
532     bool isContain = PushValue(value);
533     if (isContain) {
534         THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", 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     uint32_t len = 0;
547     if (value->IsJSArray()) {
548         JSHandle<JSArray> jsArr(value);
549         len = jsArr->GetArrayLength();
550     } else if (value->IsJSSharedArray()) {
551         JSHandle<JSSharedArray> jsArr(value);
552         len = jsArr->GetArrayLength();
553     }
554     if (len > 0) {
555         for (uint32_t i = 0; i < len; i++) {
556             JSTaggedValue tagVal = ObjectFastOperator::FastGetPropertyByIndex(thread_, value.GetTaggedValue(), i);
557             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
558             if (UNLIKELY(tagVal.IsAccessor())) {
559                 tagVal = JSObject::CallGetter(thread_, AccessorData::Cast(tagVal.GetTaggedObject()), value);
560                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
561             }
562             handleKey_.Update(JSTaggedValue(i));
563             handleValue_.Update(tagVal);
564 
565             if (i > 0) {
566                 result_ += ",";
567             }
568             result_ += stepBegin;
569             JSTaggedValue serializeValue = GetSerializeValue(value, handleKey_, handleValue_, replacer);
570             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
571             handleValue_.Update(serializeValue);
572             JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer);
573             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
574             if (res.IsUndefined()) {
575                 result_ += "null";
576             }
577         }
578 
579         if (!gap_.empty()) {
580             result_ += "\n";
581             result_ += stepback;
582         }
583     }
584 
585     result_ += "]";
586     PopValue();
587     indent_ = stepback;
588     return true;
589 }
590 
SerializePrimitiveRef(const JSHandle<JSTaggedValue> & primitiveRef)591 void JsonStringifier::SerializePrimitiveRef(const JSHandle<JSTaggedValue> &primitiveRef)
592 {
593     JSTaggedValue primitive = JSPrimitiveRef::Cast(primitiveRef.GetTaggedValue().GetTaggedObject())->GetValue();
594     if (primitive.IsString()) {
595         auto priStr = JSTaggedValue::ToString(thread_, primitiveRef);
596         RETURN_IF_ABRUPT_COMPLETION(thread_);
597         CString str = ConvertToString(*priStr, StringConvertedUsage::LOGICOPERATION);
598         str = JsonHelper::ValueToQuotedString(str);
599         result_ += str;
600     } else if (primitive.IsNumber()) {
601         auto priNum = JSTaggedValue::ToNumber(thread_, primitiveRef);
602         RETURN_IF_ABRUPT_COMPLETION(thread_);
603         if (std::isfinite(priNum.GetNumber())) {
604             result_ += ConvertToString(*base::NumberHelper::NumberToString(thread_, priNum));
605         } else {
606             result_ += "null";
607         }
608     } else if (primitive.IsBoolean()) {
609         result_ += primitive.IsTrue() ? "true" : "false";
610     } else if (primitive.IsBigInt()) {
611         if (transformType_ == TransformType::NORMAL) {
612             THROW_TYPE_ERROR(thread_, "cannot serialize a BigInt");
613         } else {
614             JSHandle<BigInt> thisBigint(thread_, primitive);
615             auto bigIntStr = BigInt::ToString(thread_, thisBigint);
616             result_ += ConvertToString(*bigIntStr);
617         }
618     }
619 }
620 
SerializeElements(const JSHandle<JSObject> & obj,const JSHandle<JSTaggedValue> & replacer,bool hasContent)621 bool JsonStringifier::SerializeElements(const JSHandle<JSObject> &obj, const JSHandle<JSTaggedValue> &replacer,
622                                         bool hasContent)
623 {
624     if (!ElementAccessor::IsDictionaryMode(obj)) {
625         uint32_t elementsLen = ElementAccessor::GetElementsLength(obj);
626         for (uint32_t i = 0; i < elementsLen; ++i) {
627             if (!ElementAccessor::Get(obj, i).IsHole()) {
628                 handleKey_.Update(JSTaggedValue(i));
629                 handleValue_.Update(ElementAccessor::Get(obj, i));
630                 hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
631                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
632             }
633         }
634     } else {
635         JSHandle<TaggedArray> elementsArr(thread_, obj->GetElements());
636         JSHandle<NumberDictionary> numberDic(elementsArr);
637         CVector<JSHandle<JSTaggedValue>> sortArr;
638         int size = numberDic->Size();
639         for (int hashIndex = 0; hashIndex < size; hashIndex++) {
640             JSTaggedValue key = numberDic->GetKey(hashIndex);
641             if (!key.IsUndefined() && !key.IsHole()) {
642                 PropertyAttributes attr = numberDic->GetAttributes(hashIndex);
643                 if (attr.IsEnumerable()) {
644                     JSTaggedValue numberKey = JSTaggedValue(static_cast<uint32_t>(key.GetInt()));
645                     sortArr.emplace_back(JSHandle<JSTaggedValue>(thread_, numberKey));
646                 }
647             }
648         }
649         std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareNumber);
650         for (const auto &entry : sortArr) {
651             JSTaggedValue entryKey = entry.GetTaggedValue();
652             handleKey_.Update(entryKey);
653             int index = numberDic->FindEntry(entryKey);
654             if (index < 0) {
655                 continue;
656             }
657             JSTaggedValue value = numberDic->GetValue(index);
658             if (UNLIKELY(value.IsAccessor())) {
659                 value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
660                                              JSHandle<JSTaggedValue>(obj));
661                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
662             }
663             handleValue_.Update(value);
664             hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
665             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
666         }
667     }
668     return hasContent;
669 }
670 
SerializeKeys(const JSHandle<JSObject> & obj,const JSHandle<JSTaggedValue> & replacer,bool hasContent)671 bool JsonStringifier::SerializeKeys(const JSHandle<JSObject> &obj, const JSHandle<JSTaggedValue> &replacer,
672                                     bool hasContent)
673 {
674     JSHandle<TaggedArray> propertiesArr(thread_, obj->GetProperties());
675     if (!propertiesArr->IsDictionaryMode()) {
676         bool hasChangedToDictionaryMode = false;
677         JSHandle<JSHClass> jsHclass(thread_, obj->GetJSHClass());
678         JSTaggedValue enumCache = jsHclass->GetEnumCache();
679         if (JSObject::GetEnumCacheKind(thread_, enumCache) == EnumCacheKind::ONLY_OWN_KEYS) {
680             JSHandle<TaggedArray> cache(thread_, enumCache);
681             uint32_t length = cache->GetLength();
682             uint32_t dictStart = length;
683             for (uint32_t i = 0; i < length; i++) {
684                 JSTaggedValue key = cache->Get(i);
685                 if (!key.IsString()) {
686                     continue;
687                 }
688                 handleKey_.Update(key);
689                 JSTaggedValue value;
690                 LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
691                 int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
692                 PropertyAttributes attr(layoutInfo->GetAttr(index));
693                 ASSERT(static_cast<int>(attr.GetOffset()) == index);
694                 value = attr.IsInlinedProps()
695                         ? obj->GetPropertyInlinedPropsWithRep(static_cast<uint32_t>(index), attr)
696                         : propertiesArr->Get(static_cast<uint32_t>(index) - jsHclass->GetInlinedProperties());
697                 if (attr.IsInlinedProps() && value.IsHole()) {
698                     continue;
699                 }
700                 if (UNLIKELY(value.IsAccessor())) {
701                     value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
702                                                  JSHandle<JSTaggedValue>(obj));
703                     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
704                     if (obj->GetProperties().IsDictionary()) {
705                         dictStart = i;
706                         handleValue_.Update(value);
707                         hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
708                         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
709                         break;
710                     }
711                 }
712                 handleValue_.Update(value);
713                 hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
714                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
715             }
716             if (dictStart < length) {
717                 propertiesArr = JSHandle<TaggedArray>(thread_, obj->GetProperties());
718                 JSHandle<NameDictionary> nameDic(propertiesArr);
719                 for (uint32_t i = dictStart + 1;i < length; i++) {
720                     JSTaggedValue key = cache->Get(i);
721                     int hashIndex = nameDic->FindEntry(key);
722                     PropertyAttributes attr = nameDic->GetAttributes(hashIndex);
723                     if (!key.IsString() || hashIndex < 0 || !attr.IsEnumerable()) {
724                         continue;
725                     }
726                     handleKey_.Update(key);
727                     JSTaggedValue value = nameDic->GetValue(hashIndex);
728                     if (UNLIKELY(value.IsAccessor())) {
729                         value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
730                             JSHandle<JSTaggedValue>(obj));
731                         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
732                     }
733                     handleValue_.Update(value);
734                     hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
735                     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
736                 }
737             }
738             return hasContent;
739         }
740         int end = static_cast<int>(jsHclass->NumberOfProps());
741         if (end <= 0) {
742             return hasContent;
743         }
744         for (int i = 0; i < end; i++) {
745             LayoutInfo *layoutInfo = LayoutInfo::Cast(jsHclass->GetLayout().GetTaggedObject());
746             JSTaggedValue key = layoutInfo->GetKey(i);
747             if (!hasChangedToDictionaryMode) {
748                 if (key.IsString() && layoutInfo->GetAttr(i).IsEnumerable()) {
749                     handleKey_.Update(key);
750                     JSTaggedValue value;
751                     int index = JSHClass::FindPropertyEntry(thread_, *jsHclass, key);
752                     PropertyAttributes attr(layoutInfo->GetAttr(index));
753                     ASSERT(static_cast<int>(attr.GetOffset()) == index);
754                     value = attr.IsInlinedProps()
755                             ? obj->GetPropertyInlinedPropsWithRep(static_cast<uint32_t>(index), attr)
756                             : propertiesArr->Get(static_cast<uint32_t>(index) - jsHclass->GetInlinedProperties());
757                     if (attr.IsInlinedProps() && value.IsHole()) {
758                         continue;
759                     }
760                     if (UNLIKELY(value.IsAccessor())) {
761                         value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
762                             JSHandle<JSTaggedValue>(obj));
763                         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
764                     }
765                     handleValue_.Update(value);
766                     hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
767                     if (obj->GetProperties().IsDictionary()) {
768                         hasChangedToDictionaryMode = true;
769                         propertiesArr = JSHandle<TaggedArray>(thread_, obj->GetProperties());
770                     }
771                     jsHclass = JSHandle<JSHClass>(thread_, obj->GetJSHClass());
772                     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
773                 }
774             } else {
775                     JSHandle<NameDictionary> nameDic(propertiesArr);
776                     int index = nameDic->FindEntry(key);
777                     if (!key.IsString()) {
778                         continue;
779                     }
780                     PropertyAttributes attr = nameDic->GetAttributes(index);
781                     if (!attr.IsEnumerable() || index < 0) {
782                         continue;
783                     }
784                     JSTaggedValue value = nameDic->GetValue(index);
785                     handleKey_.Update(key);
786                     if (UNLIKELY(value.IsAccessor())) {
787                         value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
788                             JSHandle<JSTaggedValue>(obj));
789                         jsHclass = JSHandle<JSHClass>(thread_, obj->GetJSHClass());
790                     }
791                     handleValue_.Update(value);
792                     hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
793                     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
794             }
795         }
796         return hasContent;
797     }
798     if (obj->IsJSGlobalObject()) {
799         JSHandle<GlobalDictionary> globalDic(propertiesArr);
800         int size = globalDic->Size();
801         CVector<std::pair<JSHandle<JSTaggedValue>, PropertyAttributes>> sortArr;
802         for (int hashIndex = 0; hashIndex < size; hashIndex++) {
803             JSTaggedValue key = globalDic->GetKey(hashIndex);
804             if (!key.IsString()) {
805                 continue;
806             }
807             PropertyAttributes attr = globalDic->GetAttributes(hashIndex);
808             if (!attr.IsEnumerable()) {
809                 continue;
810             }
811             std::pair<JSHandle<JSTaggedValue>, PropertyAttributes> pair(JSHandle<JSTaggedValue>(thread_, key), attr);
812             sortArr.emplace_back(pair);
813         }
814         std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
815         for (const auto &entry : sortArr) {
816             JSTaggedValue entryKey = entry.first.GetTaggedValue();
817             handleKey_.Update(entryKey);
818             int index = globalDic->FindEntry(entryKey);
819             if (index == -1) {
820                 continue;
821             }
822             JSTaggedValue value = globalDic->GetValue(index);
823             if (UNLIKELY(value.IsAccessor())) {
824                 value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
825                                              JSHandle<JSTaggedValue>(obj));
826                 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
827             }
828             handleValue_.Update(value);
829             hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
830             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
831         }
832         return hasContent;
833     }
834     JSHandle<NameDictionary> nameDic(propertiesArr);
835     int size = nameDic->Size();
836     CVector<std::pair<JSHandle<JSTaggedValue>, PropertyAttributes>> sortArr;
837     for (int hashIndex = 0; hashIndex < size; hashIndex++) {
838         JSTaggedValue key = nameDic->GetKey(hashIndex);
839         if (!key.IsString()) {
840             continue;
841         }
842         PropertyAttributes attr = nameDic->GetAttributes(hashIndex);
843         if (!attr.IsEnumerable()) {
844             continue;
845         }
846         std::pair<JSHandle<JSTaggedValue>, PropertyAttributes> pair(JSHandle<JSTaggedValue>(thread_, key), attr);
847         sortArr.emplace_back(pair);
848     }
849     std::sort(sortArr.begin(), sortArr.end(), JsonHelper::CompareKey);
850     for (const auto &entry : sortArr) {
851         JSTaggedValue entryKey = entry.first.GetTaggedValue();
852         handleKey_.Update(entryKey);
853         int index = nameDic->FindEntry(entryKey);
854         if (index < 0) {
855             continue;
856         }
857         JSTaggedValue value = nameDic->GetValue(index);
858         if (UNLIKELY(value.IsAccessor())) {
859             value = JSObject::CallGetter(thread_, AccessorData::Cast(value.GetTaggedObject()),
860                                          JSHandle<JSTaggedValue>(obj));
861             RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
862         }
863         handleValue_.Update(value);
864         hasContent = JsonStringifier::AppendJsonString(obj, replacer, hasContent);
865         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
866     }
867     return hasContent;
868 }
869 
AppendJsonString(const JSHandle<JSObject> & obj,const JSHandle<JSTaggedValue> & replacer,bool hasContent)870 bool JsonStringifier::AppendJsonString(const JSHandle<JSObject> &obj, const JSHandle<JSTaggedValue> &replacer,
871                                        bool hasContent)
872 {
873     JSTaggedValue serializeValue = GetSerializeValue(JSHandle<JSTaggedValue>(obj), handleKey_, handleValue_, replacer);
874     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
875     if (UNLIKELY(serializeValue.IsUndefined() || serializeValue.IsSymbol() ||
876         (serializeValue.IsECMAObject() && serializeValue.IsCallable()))) {
877         return hasContent;
878     }
879     handleValue_.Update(serializeValue);
880     SerializeObjectKey(handleKey_, hasContent);
881     JSTaggedValue res = SerializeJSONProperty(handleValue_, replacer);
882     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread_, false);
883     if (!res.IsUndefined()) {
884         return true;
885     }
886     return hasContent;
887 }
888 
CheckStackPushSameValue(JSHandle<JSTaggedValue> value)889 bool JsonStringifier::CheckStackPushSameValue(JSHandle<JSTaggedValue> value)
890 {
891     bool isContain = PushValue(value);
892     if (isContain) {
893         THROW_TYPE_ERROR_AND_RETURN(thread_, "stack contains value, usually caused by circular structure", true);
894     }
895     return false;
896 }
897 
898 }  // namespace panda::ecmascript::base
899