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