1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | #include <qv4jsonobject_p.h> |
4 | #include <qv4objectproto_p.h> |
5 | #include <qv4numberobject_p.h> |
6 | #include <qv4stringobject_p.h> |
7 | #include <qv4booleanobject_p.h> |
8 | #include <qv4objectiterator_p.h> |
9 | #include <qv4scopedvalue_p.h> |
10 | #include <qv4runtime_p.h> |
11 | #include <qv4variantobject_p.h> |
12 | #include "qv4jscall_p.h" |
13 | #include <qv4symbol_p.h> |
14 | |
15 | #include <qstack.h> |
16 | #include <qstringlist.h> |
17 | |
18 | #include <wtf/MathExtras.h> |
19 | |
20 | using namespace QV4; |
21 | |
22 | //#define PARSER_DEBUG |
23 | #ifdef PARSER_DEBUG |
24 | static int indent = 0; |
25 | #define BEGIN qDebug() << QByteArray(4*indent++, ' ').constData() |
26 | #define END --indent |
27 | #define DEBUG qDebug() << QByteArray(4*indent, ' ').constData() |
28 | #else |
29 | #define BEGIN if (1) ; else qDebug() |
30 | #define END do {} while (0) |
31 | #define DEBUG if (1) ; else qDebug() |
32 | #endif |
33 | |
34 | |
35 | DEFINE_OBJECT_VTABLE(JsonObject); |
36 | |
37 | static const int nestingLimit = 1024; |
38 | |
39 | |
40 | JsonParser::JsonParser(ExecutionEngine *engine, const QChar *json, int length) |
41 | : engine(engine), head(json), json(json), nestingLevel(0), lastError(QJsonParseError::NoError) |
42 | { |
43 | end = json + length; |
44 | } |
45 | |
46 | |
47 | |
48 | /* |
49 | |
50 | begin-array = ws %x5B ws ; [ left square bracket |
51 | |
52 | begin-object = ws %x7B ws ; { left curly bracket |
53 | |
54 | end-array = ws %x5D ws ; ] right square bracket |
55 | |
56 | end-object = ws %x7D ws ; } right curly bracket |
57 | |
58 | name-separator = ws %x3A ws ; : colon |
59 | |
60 | value-separator = ws %x2C ws ; , comma |
61 | |
62 | Insignificant whitespace is allowed before or after any of the six |
63 | structural characters. |
64 | |
65 | ws = *( |
66 | %x20 / ; Space |
67 | %x09 / ; Horizontal tab |
68 | %x0A / ; Line feed or New line |
69 | %x0D ; Carriage return |
70 | ) |
71 | |
72 | */ |
73 | |
74 | enum { |
75 | Space = 0x20, |
76 | Tab = 0x09, |
77 | LineFeed = 0x0a, |
78 | Return = 0x0d, |
79 | BeginArray = 0x5b, |
80 | BeginObject = 0x7b, |
81 | EndArray = 0x5d, |
82 | EndObject = 0x7d, |
83 | NameSeparator = 0x3a, |
84 | ValueSeparator = 0x2c, |
85 | Quote = 0x22 |
86 | }; |
87 | |
88 | bool JsonParser::eatSpace() |
89 | { |
90 | while (json < end) { |
91 | const char16_t ch = json->unicode(); |
92 | if (ch > Space) |
93 | break; |
94 | if (ch != Space && |
95 | ch != Tab && |
96 | ch != LineFeed && |
97 | ch != Return) |
98 | break; |
99 | ++json; |
100 | } |
101 | return (json < end); |
102 | } |
103 | |
104 | QChar JsonParser::nextToken() |
105 | { |
106 | if (!eatSpace()) |
107 | return u'\0'; |
108 | QChar token = *json++; |
109 | switch (token.unicode()) { |
110 | case BeginArray: |
111 | case BeginObject: |
112 | case NameSeparator: |
113 | case ValueSeparator: |
114 | case EndArray: |
115 | case EndObject: |
116 | eatSpace(); |
117 | case Quote: |
118 | break; |
119 | default: |
120 | token = u'\0'; |
121 | break; |
122 | } |
123 | return token; |
124 | } |
125 | |
126 | /* |
127 | JSON-text = object / array |
128 | */ |
129 | ReturnedValue JsonParser::parse(QJsonParseError *error) |
130 | { |
131 | #ifdef PARSER_DEBUG |
132 | indent = 0; |
133 | qDebug() << ">>>>> parser begin" ; |
134 | #endif |
135 | |
136 | eatSpace(); |
137 | |
138 | Scope scope(engine); |
139 | ScopedValue v(scope); |
140 | if (!parseValue(val: v)) { |
141 | #ifdef PARSER_DEBUG |
142 | qDebug() << ">>>>> parser error" ; |
143 | #endif |
144 | if (lastError == QJsonParseError::NoError) |
145 | lastError = QJsonParseError::IllegalValue; |
146 | error->offset = json - head; |
147 | error->error = lastError; |
148 | return Encode::undefined(); |
149 | } |
150 | |
151 | // some input left... |
152 | if (eatSpace()) { |
153 | lastError = QJsonParseError::IllegalValue; |
154 | error->offset = json - head; |
155 | error->error = lastError; |
156 | return Encode::undefined(); |
157 | } |
158 | |
159 | END; |
160 | error->offset = 0; |
161 | error->error = QJsonParseError::NoError; |
162 | return v->asReturnedValue(); |
163 | } |
164 | |
165 | /* |
166 | object = begin-object [ member *( value-separator member ) ] |
167 | end-object |
168 | */ |
169 | |
170 | ReturnedValue JsonParser::parseObject() |
171 | { |
172 | if (++nestingLevel > nestingLimit) { |
173 | lastError = QJsonParseError::DeepNesting; |
174 | return Encode::undefined(); |
175 | } |
176 | |
177 | BEGIN << "parseObject pos=" << json; |
178 | Scope scope(engine); |
179 | |
180 | ScopedObject o(scope, engine->newObject()); |
181 | |
182 | QChar token = nextToken(); |
183 | while (token.unicode() == Quote) { |
184 | if (!parseMember(o)) |
185 | return Encode::undefined(); |
186 | token = nextToken(); |
187 | if (token.unicode() != ValueSeparator) |
188 | break; |
189 | token = nextToken(); |
190 | if (token.unicode() == EndObject) { |
191 | lastError = QJsonParseError::MissingObject; |
192 | return Encode::undefined(); |
193 | } |
194 | } |
195 | |
196 | DEBUG << "end token=" << token; |
197 | if (token.unicode() != EndObject) { |
198 | lastError = QJsonParseError::UnterminatedObject; |
199 | return Encode::undefined(); |
200 | } |
201 | |
202 | END; |
203 | |
204 | --nestingLevel; |
205 | return o.asReturnedValue(); |
206 | } |
207 | |
208 | /* |
209 | member = string name-separator value |
210 | */ |
211 | bool JsonParser::parseMember(Object *o) |
212 | { |
213 | BEGIN << "parseMember" ; |
214 | Scope scope(engine); |
215 | |
216 | QString key; |
217 | if (!parseString(string: &key)) |
218 | return false; |
219 | QChar token = nextToken(); |
220 | if (token.unicode() != NameSeparator) { |
221 | lastError = QJsonParseError::MissingNameSeparator; |
222 | return false; |
223 | } |
224 | ScopedValue val(scope); |
225 | if (!parseValue(val)) |
226 | return false; |
227 | |
228 | ScopedString s(scope, engine->newString(s: key)); |
229 | PropertyKey skey = s->toPropertyKey(); |
230 | if (skey.isArrayIndex()) { |
231 | o->put(idx: skey.asArrayIndex(), v: val); |
232 | } else { |
233 | // avoid trouble with properties named __proto__ |
234 | o->insertMember(s, v: val); |
235 | } |
236 | |
237 | END; |
238 | return true; |
239 | } |
240 | |
241 | /* |
242 | array = begin-array [ value *( value-separator value ) ] end-array |
243 | */ |
244 | ReturnedValue JsonParser::parseArray() |
245 | { |
246 | Scope scope(engine); |
247 | BEGIN << "parseArray" ; |
248 | ScopedArrayObject array(scope, engine->newArrayObject()); |
249 | |
250 | if (++nestingLevel > nestingLimit) { |
251 | lastError = QJsonParseError::DeepNesting; |
252 | return Encode::undefined(); |
253 | } |
254 | |
255 | if (!eatSpace()) { |
256 | lastError = QJsonParseError::UnterminatedArray; |
257 | return Encode::undefined(); |
258 | } |
259 | if (json->unicode() == EndArray) { |
260 | nextToken(); |
261 | } else { |
262 | uint index = 0; |
263 | while (1) { |
264 | ScopedValue val(scope); |
265 | if (!parseValue(val)) |
266 | return Encode::undefined(); |
267 | array->arraySet(index, value: val); |
268 | QChar token = nextToken(); |
269 | if (token.unicode() == EndArray) |
270 | break; |
271 | else if (token.unicode() != ValueSeparator) { |
272 | if (!eatSpace()) |
273 | lastError = QJsonParseError::UnterminatedArray; |
274 | else |
275 | lastError = QJsonParseError::MissingValueSeparator; |
276 | return Encode::undefined(); |
277 | } |
278 | ++index; |
279 | } |
280 | } |
281 | |
282 | DEBUG << "size =" << array->getLength(); |
283 | END; |
284 | |
285 | --nestingLevel; |
286 | return array.asReturnedValue(); |
287 | } |
288 | |
289 | /* |
290 | value = false / null / true / object / array / number / string |
291 | |
292 | */ |
293 | |
294 | bool JsonParser::parseValue(Value *val) |
295 | { |
296 | BEGIN << "parse Value" << *json; |
297 | |
298 | switch ((json++)->unicode()) { |
299 | case u'n': |
300 | if (end - json < 3) { |
301 | lastError = QJsonParseError::IllegalValue; |
302 | return false; |
303 | } |
304 | if (*json++ == u'u' && |
305 | *json++ == u'l' && |
306 | *json++ == u'l') { |
307 | *val = Value::nullValue(); |
308 | DEBUG << "value: null" ; |
309 | END; |
310 | return true; |
311 | } |
312 | lastError = QJsonParseError::IllegalValue; |
313 | return false; |
314 | case u't': |
315 | if (end - json < 3) { |
316 | lastError = QJsonParseError::IllegalValue; |
317 | return false; |
318 | } |
319 | if (*json++ == u'r' && |
320 | *json++ == u'u' && |
321 | *json++ == u'e') { |
322 | *val = Value::fromBoolean(b: true); |
323 | DEBUG << "value: true" ; |
324 | END; |
325 | return true; |
326 | } |
327 | lastError = QJsonParseError::IllegalValue; |
328 | return false; |
329 | case u'f': |
330 | if (end - json < 4) { |
331 | lastError = QJsonParseError::IllegalValue; |
332 | return false; |
333 | } |
334 | if (*json++ == u'a' && |
335 | *json++ == u'l' && |
336 | *json++ == u's' && |
337 | *json++ == u'e') { |
338 | *val = Value::fromBoolean(b: false); |
339 | DEBUG << "value: false" ; |
340 | END; |
341 | return true; |
342 | } |
343 | lastError = QJsonParseError::IllegalValue; |
344 | return false; |
345 | case Quote: { |
346 | QString value; |
347 | if (!parseString(string: &value)) |
348 | return false; |
349 | DEBUG << "value: string" ; |
350 | END; |
351 | *val = Value::fromHeapObject(m: engine->newString(s: value)); |
352 | return true; |
353 | } |
354 | case BeginArray: { |
355 | *val = parseArray(); |
356 | if (val->isUndefined()) |
357 | return false; |
358 | DEBUG << "value: array" ; |
359 | END; |
360 | return true; |
361 | } |
362 | case BeginObject: { |
363 | *val = parseObject(); |
364 | if (val->isUndefined()) |
365 | return false; |
366 | DEBUG << "value: object" ; |
367 | END; |
368 | return true; |
369 | } |
370 | case EndArray: |
371 | lastError = QJsonParseError::MissingObject; |
372 | return false; |
373 | default: |
374 | --json; |
375 | if (!parseNumber(val)) |
376 | return false; |
377 | DEBUG << "value: number" ; |
378 | END; |
379 | } |
380 | |
381 | return true; |
382 | } |
383 | |
384 | |
385 | |
386 | |
387 | |
388 | /* |
389 | number = [ minus ] int [ frac ] [ exp ] |
390 | decimal-point = %x2E ; . |
391 | digit1-9 = %x31-39 ; 1-9 |
392 | e = %x65 / %x45 ; e E |
393 | exp = e [ minus / plus ] 1*DIGIT |
394 | frac = decimal-point 1*DIGIT |
395 | int = zero / ( digit1-9 *DIGIT ) |
396 | minus = %x2D ; - |
397 | plus = %x2B ; + |
398 | zero = %x30 ; 0 |
399 | |
400 | */ |
401 | |
402 | bool JsonParser::parseNumber(Value *val) |
403 | { |
404 | BEGIN << "parseNumber" << *json; |
405 | |
406 | const QChar *start = json; |
407 | bool isInt = true; |
408 | |
409 | // minus |
410 | if (json < end && *json == u'-') |
411 | ++json; |
412 | |
413 | // int = zero / ( digit1-9 *DIGIT ) |
414 | if (json < end && *json == u'0') { |
415 | ++json; |
416 | } else { |
417 | while (json < end && *json >= u'0' && *json <= u'9') |
418 | ++json; |
419 | } |
420 | |
421 | // frac = decimal-point 1*DIGIT |
422 | if (json < end && *json == u'.') { |
423 | isInt = false; |
424 | ++json; |
425 | while (json < end && *json >= u'0' && *json <= u'9') |
426 | ++json; |
427 | } |
428 | |
429 | // exp = e [ minus / plus ] 1*DIGIT |
430 | if (json < end && (*json == u'e' || *json == u'E')) { |
431 | isInt = false; |
432 | ++json; |
433 | if (json < end && (*json == u'-' || *json == u'+')) |
434 | ++json; |
435 | while (json < end && *json >= u'0' && *json <= u'9') |
436 | ++json; |
437 | } |
438 | |
439 | QString number(start, json - start); |
440 | DEBUG << "numberstring" << number; |
441 | |
442 | if (isInt) { |
443 | bool ok; |
444 | int n = number.toInt(ok: &ok); |
445 | if (ok && n < (1<<25) && n > -(1<<25)) { |
446 | *val = Value::fromInt32(i: n); |
447 | END; |
448 | return true; |
449 | } |
450 | } |
451 | |
452 | bool ok; |
453 | double d; |
454 | d = number.toDouble(ok: &ok); |
455 | |
456 | if (!ok) { |
457 | lastError = QJsonParseError::IllegalNumber; |
458 | return false; |
459 | } |
460 | |
461 | * val = Value::fromDouble(d); |
462 | |
463 | END; |
464 | return true; |
465 | } |
466 | |
467 | /* |
468 | |
469 | string = quotation-mark *char quotation-mark |
470 | |
471 | char = unescaped / |
472 | escape ( |
473 | %x22 / ; " quotation mark U+0022 |
474 | %x5C / ; \ reverse solidus U+005C |
475 | %x2F / ; / solidus U+002F |
476 | %x62 / ; b backspace U+0008 |
477 | %x66 / ; f form feed U+000C |
478 | %x6E / ; n line feed U+000A |
479 | %x72 / ; r carriage return U+000D |
480 | %x74 / ; t tab U+0009 |
481 | %x75 4HEXDIG ) ; uXXXX U+XXXX |
482 | |
483 | escape = %x5C ; \ |
484 | |
485 | quotation-mark = %x22 ; " |
486 | |
487 | unescaped = %x20-21 / %x23-5B / %x5D-10FFFF |
488 | */ |
489 | static inline bool addHexDigit(QChar digit, uint *result) |
490 | { |
491 | ushort d = digit.unicode(); |
492 | *result <<= 4; |
493 | if (d >= u'0' && d <= u'9') |
494 | *result |= (d - u'0'); |
495 | else if (d >= u'a' && d <= u'f') |
496 | *result |= (d - u'a') + 10; |
497 | else if (d >= u'A' && d <= u'F') |
498 | *result |= (d - u'A') + 10; |
499 | else |
500 | return false; |
501 | return true; |
502 | } |
503 | |
504 | static inline bool scanEscapeSequence(const QChar *&json, const QChar *end, uint *ch) |
505 | { |
506 | ++json; |
507 | if (json >= end) |
508 | return false; |
509 | |
510 | DEBUG << "scan escape" ; |
511 | uint escaped = (json++)->unicode(); |
512 | switch (escaped) { |
513 | case u'"': |
514 | *ch = '"'; break; |
515 | case u'\\': |
516 | *ch = '\\'; break; |
517 | case u'/': |
518 | *ch = '/'; break; |
519 | case u'b': |
520 | *ch = 0x8; break; |
521 | case u'f': |
522 | *ch = 0xc; break; |
523 | case u'n': |
524 | *ch = 0xa; break; |
525 | case u'r': |
526 | *ch = 0xd; break; |
527 | case u't': |
528 | *ch = 0x9; break; |
529 | case u'u': { |
530 | *ch = 0; |
531 | if (json > end - 4) |
532 | return false; |
533 | for (int i = 0; i < 4; ++i) { |
534 | if (!addHexDigit(digit: *json, result: ch)) |
535 | return false; |
536 | ++json; |
537 | } |
538 | return true; |
539 | } |
540 | default: |
541 | return false; |
542 | } |
543 | return true; |
544 | } |
545 | |
546 | |
547 | bool JsonParser::parseString(QString *string) |
548 | { |
549 | BEGIN << "parse string stringPos=" << json; |
550 | |
551 | while (json < end) { |
552 | if (*json == u'"') |
553 | break; |
554 | else if (*json == u'\\') { |
555 | uint ch = 0; |
556 | if (!scanEscapeSequence(json, end, ch: &ch)) { |
557 | lastError = QJsonParseError::IllegalEscapeSequence; |
558 | return false; |
559 | } |
560 | if (QChar::requiresSurrogates(ucs4: ch)) { |
561 | *string += QChar(QChar::highSurrogate(ucs4: ch)) + QChar(QChar::lowSurrogate(ucs4: ch)); |
562 | } else { |
563 | *string += QChar(ch); |
564 | } |
565 | } else { |
566 | if (json->unicode() <= 0x1f) { |
567 | lastError = QJsonParseError::IllegalEscapeSequence; |
568 | return false; |
569 | } |
570 | *string += *json; |
571 | ++json; |
572 | } |
573 | } |
574 | ++json; |
575 | |
576 | if (json > end) { |
577 | lastError = QJsonParseError::UnterminatedString; |
578 | return false; |
579 | } |
580 | |
581 | END; |
582 | return true; |
583 | } |
584 | |
585 | |
586 | struct Stringify |
587 | { |
588 | ExecutionEngine *v4; |
589 | FunctionObject *replacerFunction; |
590 | QV4::String *propertyList; |
591 | int propertyListSize; |
592 | QString gap; |
593 | QString indent; |
594 | QStack<Object *> stack; |
595 | |
596 | bool stackContains(Object *o) { |
597 | for (int i = 0; i < stack.size(); ++i) |
598 | if (stack.at(i)->d() == o->d()) |
599 | return true; |
600 | return false; |
601 | } |
602 | |
603 | Stringify(ExecutionEngine *e) : v4(e), replacerFunction(nullptr), propertyList(nullptr), propertyListSize(0) {} |
604 | |
605 | QString Str(const QString &key, const Value &v); |
606 | QString JA(Object *a); |
607 | QString JO(Object *o); |
608 | |
609 | QString makeMember(const QString &key, const Value &v); |
610 | }; |
611 | |
612 | class [[nodiscard]] CallDepthAndCycleChecker |
613 | { |
614 | Q_DISABLE_COPY_MOVE(CallDepthAndCycleChecker); |
615 | |
616 | public: |
617 | CallDepthAndCycleChecker(Stringify *stringify, Object *o) |
618 | : m_callDepthRecorder(stringify->v4) |
619 | { |
620 | if (stringify->stackContains(o)) { |
621 | stringify->v4->throwTypeError( |
622 | QStringLiteral("Cannot convert circular structure to JSON" )); |
623 | } |
624 | |
625 | stringify->v4->checkStackLimits(); |
626 | } |
627 | |
628 | bool foundProblem() const { return m_callDepthRecorder.ee->hasException; } |
629 | |
630 | private: |
631 | ExecutionEngineCallDepthRecorder<1> m_callDepthRecorder; |
632 | }; |
633 | |
634 | static QString quote(const QString &str) |
635 | { |
636 | QString product; |
637 | const int length = str.size(); |
638 | product.reserve(asize: length + 2); |
639 | product += u'"'; |
640 | for (int i = 0; i < length; ++i) { |
641 | QChar c = str.at(i); |
642 | switch (c.unicode()) { |
643 | case u'"': |
644 | product += QLatin1String("\\\"" ); |
645 | break; |
646 | case u'\\': |
647 | product += QLatin1String("\\\\" ); |
648 | break; |
649 | case u'\b': |
650 | product += QLatin1String("\\b" ); |
651 | break; |
652 | case u'\f': |
653 | product += QLatin1String("\\f" ); |
654 | break; |
655 | case u'\n': |
656 | product += QLatin1String("\\n" ); |
657 | break; |
658 | case u'\r': |
659 | product += QLatin1String("\\r" ); |
660 | break; |
661 | case u'\t': |
662 | product += QLatin1String("\\t" ); |
663 | break; |
664 | default: |
665 | if (c.unicode() <= 0x1f) { |
666 | product += QLatin1String("\\u00" ); |
667 | product += (c.unicode() > 0xf ? u'1' : u'0') + |
668 | QLatin1Char("0123456789abcdef" [c.unicode() & 0xf]); |
669 | } else { |
670 | product += c; |
671 | } |
672 | } |
673 | } |
674 | product += u'"'; |
675 | return product; |
676 | } |
677 | |
678 | QString Stringify::Str(const QString &key, const Value &v) |
679 | { |
680 | Scope scope(v4); |
681 | |
682 | ScopedValue value(scope, v); |
683 | ScopedObject o(scope, value); |
684 | if (o) { |
685 | ScopedString s(scope, v4->newString(QStringLiteral("toJSON" ))); |
686 | ScopedFunctionObject toJSON(scope, o->get(name: s)); |
687 | if (!!toJSON) { |
688 | JSCallArguments jsCallData(scope, 1); |
689 | *jsCallData.thisObject = value; |
690 | jsCallData.args[0] = v4->newString(s: key); |
691 | value = toJSON->call(data: jsCallData); |
692 | if (v4->hasException) |
693 | return QString(); |
694 | } |
695 | } |
696 | |
697 | if (replacerFunction) { |
698 | JSCallArguments jsCallData(scope, 2); |
699 | jsCallData.args[0] = v4->newString(s: key); |
700 | jsCallData.args[1] = value; |
701 | |
702 | if (stack.isEmpty()) { |
703 | ScopedObject holder(scope, v4->newObject()); |
704 | holder->put(name: scope.engine->id_empty(), v); |
705 | *jsCallData.thisObject = holder; |
706 | } else { |
707 | *jsCallData.thisObject = stack.top(); |
708 | } |
709 | |
710 | value = replacerFunction->call(data: jsCallData); |
711 | if (v4->hasException) |
712 | return QString(); |
713 | } |
714 | |
715 | o = value->asReturnedValue(); |
716 | if (o) { |
717 | if (NumberObject *n = o->as<NumberObject>()) |
718 | value = Encode(n->value()); |
719 | else if (StringObject *so = o->as<StringObject>()) |
720 | value = so->d()->string; |
721 | else if (BooleanObject *b = o->as<BooleanObject>()) |
722 | value = Encode(b->value()); |
723 | } |
724 | |
725 | if (value->isNull()) |
726 | return QStringLiteral("null" ); |
727 | if (value->isBoolean()) |
728 | return value->booleanValue() ? QStringLiteral("true" ) : QStringLiteral("false" ); |
729 | if (value->isString()) |
730 | return quote(str: value->stringValue()->toQString()); |
731 | |
732 | if (value->isNumber()) { |
733 | double d = value->toNumber(); |
734 | return std::isfinite(x: d) ? value->toQString() : QStringLiteral("null" ); |
735 | } |
736 | |
737 | if (const QV4::VariantObject *v = value->as<QV4::VariantObject>()) { |
738 | return quote(str: v->d()->data().toString()); |
739 | } |
740 | |
741 | o = value->asReturnedValue(); |
742 | if (o) { |
743 | if (!o->as<FunctionObject>()) { |
744 | if (o->isArrayLike()) { |
745 | return JA(a: o.getPointer()); |
746 | } else { |
747 | return JO(o); |
748 | } |
749 | } |
750 | } |
751 | |
752 | return QString(); |
753 | } |
754 | |
755 | QString Stringify::makeMember(const QString &key, const Value &v) |
756 | { |
757 | QString strP = Str(key, v); |
758 | if (!strP.isEmpty()) { |
759 | QString member = quote(str: key) + u':'; |
760 | if (!gap.isEmpty()) |
761 | member += u' '; |
762 | member += strP; |
763 | return member; |
764 | } |
765 | return QString(); |
766 | } |
767 | |
768 | QString Stringify::JO(Object *o) |
769 | { |
770 | CallDepthAndCycleChecker check(this, o); |
771 | if (check.foundProblem()) |
772 | return QString(); |
773 | |
774 | Scope scope(v4); |
775 | |
776 | QString result; |
777 | stack.push(t: o); |
778 | QString stepback = indent; |
779 | indent += gap; |
780 | |
781 | QStringList partial; |
782 | if (!propertyListSize) { |
783 | ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); |
784 | ScopedValue name(scope); |
785 | |
786 | ScopedValue val(scope); |
787 | while (1) { |
788 | name = it.nextPropertyNameAsString(value: val); |
789 | if (name->isNull()) |
790 | break; |
791 | QString key = name->toQString(); |
792 | QString member = makeMember(key, v: val); |
793 | if (!member.isEmpty()) |
794 | partial += member; |
795 | } |
796 | } else { |
797 | ScopedValue v(scope); |
798 | for (int i = 0; i < propertyListSize; ++i) { |
799 | bool exists; |
800 | String *s = propertyList + i; |
801 | if (!s) |
802 | continue; |
803 | v = o->get(name: s, hasProperty: &exists); |
804 | if (!exists) |
805 | continue; |
806 | QString member = makeMember(key: s->toQString(), v); |
807 | if (!member.isEmpty()) |
808 | partial += member; |
809 | } |
810 | } |
811 | |
812 | if (partial.isEmpty()) { |
813 | result = QStringLiteral("{}" ); |
814 | } else if (gap.isEmpty()) { |
815 | result = u'{' + partial.join(sep: u',') + u'}'; |
816 | } else { |
817 | QString separator = QLatin1String(",\n" ) + indent; |
818 | result = QLatin1String("{\n" ) + indent + partial.join(sep: separator) + u'\n' |
819 | + stepback + u'}'; |
820 | } |
821 | |
822 | indent = stepback; |
823 | stack.pop(); |
824 | return result; |
825 | } |
826 | |
827 | QString Stringify::JA(Object *a) |
828 | { |
829 | CallDepthAndCycleChecker check(this, a); |
830 | if (check.foundProblem()) |
831 | return QString(); |
832 | |
833 | Scope scope(a->engine()); |
834 | |
835 | QString result; |
836 | stack.push(t: a); |
837 | QString stepback = indent; |
838 | indent += gap; |
839 | |
840 | QStringList partial; |
841 | uint len = a->getLength(); |
842 | ScopedValue v(scope); |
843 | for (uint i = 0; i < len; ++i) { |
844 | bool exists; |
845 | v = a->get(idx: i, hasProperty: &exists); |
846 | if (!exists) { |
847 | partial += QStringLiteral("null" ); |
848 | continue; |
849 | } |
850 | QString strP = Str(key: QString::number(i), v); |
851 | if (!strP.isEmpty()) |
852 | partial += strP; |
853 | else |
854 | partial += QStringLiteral("null" ); |
855 | } |
856 | |
857 | if (partial.isEmpty()) { |
858 | result = QStringLiteral("[]" ); |
859 | } else if (gap.isEmpty()) { |
860 | result = u'[' + partial.join(sep: u',') + u']'; |
861 | } else { |
862 | QString separator = QLatin1String(",\n" ) + indent; |
863 | result = QLatin1String("[\n" ) + indent + partial.join(sep: separator) + u'\n' + stepback + u']'; |
864 | } |
865 | |
866 | indent = stepback; |
867 | stack.pop(); |
868 | return result; |
869 | } |
870 | |
871 | |
872 | void Heap::JsonObject::init() |
873 | { |
874 | Object::init(); |
875 | Scope scope(internalClass->engine); |
876 | ScopedObject o(scope, this); |
877 | |
878 | o->defineDefaultProperty(QStringLiteral("parse" ), code: QV4::JsonObject::method_parse, argumentCount: 2); |
879 | o->defineDefaultProperty(QStringLiteral("stringify" ), code: QV4::JsonObject::method_stringify, argumentCount: 3); |
880 | ScopedString json(scope, scope.engine->newString(QStringLiteral("JSON" ))); |
881 | o->defineReadonlyConfigurableProperty(name: scope.engine->symbol_toStringTag(), value: json); |
882 | } |
883 | |
884 | |
885 | ReturnedValue JsonObject::method_parse(const FunctionObject *b, const Value *, const Value *argv, int argc) |
886 | { |
887 | ExecutionEngine *v4 = b->engine(); |
888 | QString jtext; |
889 | if (argc > 0) |
890 | jtext = argv[0].toQString(); |
891 | |
892 | DEBUG << "parsing source = " << jtext; |
893 | JsonParser parser(v4, jtext.constData(), jtext.size()); |
894 | QJsonParseError error; |
895 | ReturnedValue result = parser.parse(error: &error); |
896 | if (error.error != QJsonParseError::NoError) { |
897 | DEBUG << "parse error" << error.errorString(); |
898 | RETURN_RESULT(v4->throwSyntaxError(QStringLiteral("JSON.parse: Parse error" ))); |
899 | } |
900 | |
901 | return result; |
902 | } |
903 | |
904 | ReturnedValue JsonObject::method_stringify(const FunctionObject *b, const Value *, const Value *argv, int argc) |
905 | { |
906 | Scope scope(b); |
907 | Stringify stringify(scope.engine); |
908 | |
909 | ScopedObject o(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
910 | if (o) { |
911 | stringify.replacerFunction = o->as<FunctionObject>(); |
912 | if (o->isArrayObject()) { |
913 | int arrayLen = scope.engine->safeForAllocLength(len64: o->getLength()); |
914 | CHECK_EXCEPTION(); |
915 | stringify.propertyList = static_cast<QV4::String *>(scope.alloc(nValues: arrayLen)); |
916 | for (int i = 0; i < arrayLen; ++i) { |
917 | Value *v = stringify.propertyList + i; |
918 | *v = o->get(idx: i); |
919 | if (v->as<NumberObject>() || v->as<StringObject>() || v->isNumber()) |
920 | *v = v->toString(e: scope.engine); |
921 | if (!v->isString()) { |
922 | v->setM(nullptr); |
923 | } else { |
924 | for (int j = 0; j <i; ++j) { |
925 | if (stringify.propertyList[j].m() == v->m()) { |
926 | v->setM(nullptr); |
927 | break; |
928 | } |
929 | } |
930 | } |
931 | } |
932 | } |
933 | } |
934 | |
935 | ScopedValue s(scope, argc > 2 ? argv[2] : Value::undefinedValue()); |
936 | if (NumberObject *n = s->as<NumberObject>()) |
937 | s = Encode(n->value()); |
938 | else if (StringObject *so = s->as<StringObject>()) |
939 | s = so->d()->string; |
940 | |
941 | if (s->isNumber()) { |
942 | stringify.gap = QString(qMin(a: 10, b: (int)s->toInteger()), u' '); |
943 | } else if (String *str = s->stringValue()) { |
944 | stringify.gap = str->toQString().left(n: 10); |
945 | } |
946 | |
947 | |
948 | ScopedValue arg0(scope, argc ? argv[0] : Value::undefinedValue()); |
949 | QString result = stringify.Str(key: QString(), v: arg0); |
950 | if (result.isEmpty() || scope.hasException()) |
951 | RETURN_UNDEFINED(); |
952 | return Encode(scope.engine->newString(s: result)); |
953 | } |
954 | |
955 | |
956 | |
957 | ReturnedValue JsonObject::fromJsonValue(ExecutionEngine *engine, const QJsonValue &value) |
958 | { |
959 | if (value.isString()) |
960 | return engine->newString(s: value.toString())->asReturnedValue(); |
961 | else if (value.isDouble()) |
962 | return Encode(value.toDouble()); |
963 | else if (value.isBool()) |
964 | return Encode(value.toBool()); |
965 | else if (value.isArray()) |
966 | return fromJsonArray(engine, array: value.toArray()); |
967 | else if (value.isObject()) |
968 | return fromJsonObject(engine, object: value.toObject()); |
969 | else if (value.isNull()) |
970 | return Encode::null(); |
971 | else |
972 | return Encode::undefined(); |
973 | } |
974 | |
975 | QJsonValue JsonObject::toJsonValue(const Value &value, V4ObjectSet &visitedObjects) |
976 | { |
977 | if (value.isNumber()) |
978 | return QJsonValue(value.toNumber()); |
979 | else if (value.isBoolean()) |
980 | return QJsonValue((bool)value.booleanValue()); |
981 | else if (value.isNull()) |
982 | return QJsonValue(QJsonValue::Null); |
983 | else if (value.isUndefined()) |
984 | return QJsonValue(QJsonValue::Undefined); |
985 | else if (String *s = value.stringValue()) |
986 | return QJsonValue(s->toQString()); |
987 | |
988 | Q_ASSERT(value.isObject()); |
989 | Scope scope(value.as<Object>()->engine()); |
990 | ScopedArrayObject a(scope, value); |
991 | if (a) |
992 | return toJsonArray(a, visitedObjects); |
993 | ScopedObject o(scope, value); |
994 | if (o) |
995 | return toJsonObject(o, visitedObjects); |
996 | return QJsonValue(value.toQString()); |
997 | } |
998 | |
999 | QV4::ReturnedValue JsonObject::fromJsonObject(ExecutionEngine *engine, const QJsonObject &object) |
1000 | { |
1001 | Scope scope(engine); |
1002 | ScopedObject o(scope, engine->newObject()); |
1003 | ScopedString s(scope); |
1004 | ScopedValue v(scope); |
1005 | for (QJsonObject::const_iterator it = object.begin(), cend = object.end(); it != cend; ++it) { |
1006 | v = fromJsonValue(engine, value: it.value()); |
1007 | o->put(name: (s = engine->newString(s: it.key())), v); |
1008 | } |
1009 | return o.asReturnedValue(); |
1010 | } |
1011 | |
1012 | QJsonObject JsonObject::toJsonObject(const Object *o, V4ObjectSet &visitedObjects) |
1013 | { |
1014 | QJsonObject result; |
1015 | if (!o || o->as<FunctionObject>()) |
1016 | return result; |
1017 | |
1018 | Scope scope(o->engine()); |
1019 | |
1020 | if (visitedObjects.contains(value: ObjectItem(o))) { |
1021 | // Avoid recursion. |
1022 | // For compatibility with QVariant{List,Map} conversion, we return an |
1023 | // empty object (and no error is thrown). |
1024 | return result; |
1025 | } |
1026 | |
1027 | visitedObjects.insert(value: ObjectItem(o)); |
1028 | |
1029 | ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); |
1030 | ScopedValue name(scope); |
1031 | QV4::ScopedValue val(scope); |
1032 | while (1) { |
1033 | name = it.nextPropertyNameAsString(value: val); |
1034 | if (name->isNull()) |
1035 | break; |
1036 | |
1037 | QString key = name->toQStringNoThrow(); |
1038 | if (!val->as<FunctionObject>()) |
1039 | result.insert(key, value: toJsonValue(value: val, visitedObjects)); |
1040 | } |
1041 | |
1042 | visitedObjects.remove(value: ObjectItem(o)); |
1043 | |
1044 | return result; |
1045 | } |
1046 | |
1047 | QV4::ReturnedValue JsonObject::fromJsonArray(ExecutionEngine *engine, const QJsonArray &array) |
1048 | { |
1049 | Scope scope(engine); |
1050 | int size = array.size(); |
1051 | ScopedArrayObject a(scope, engine->newArrayObject()); |
1052 | a->arrayReserve(n: size); |
1053 | ScopedValue v(scope); |
1054 | for (int i = 0; i < size; i++) |
1055 | a->arrayPut(index: i, value: (v = fromJsonValue(engine, value: array.at(i)))); |
1056 | a->setArrayLengthUnchecked(size); |
1057 | return a.asReturnedValue(); |
1058 | } |
1059 | |
1060 | QJsonArray JsonObject::toJsonArray(const ArrayObject *a, V4ObjectSet &visitedObjects) |
1061 | { |
1062 | QJsonArray result; |
1063 | if (!a) |
1064 | return result; |
1065 | |
1066 | Scope scope(a->engine()); |
1067 | |
1068 | if (visitedObjects.contains(value: ObjectItem(a))) { |
1069 | // Avoid recursion. |
1070 | // For compatibility with QVariant{List,Map} conversion, we return an |
1071 | // empty array (and no error is thrown). |
1072 | return result; |
1073 | } |
1074 | |
1075 | visitedObjects.insert(value: ObjectItem(a)); |
1076 | |
1077 | ScopedValue v(scope); |
1078 | quint32 length = a->getLength(); |
1079 | for (quint32 i = 0; i < length; ++i) { |
1080 | v = a->get(idx: i); |
1081 | if (v->as<FunctionObject>()) |
1082 | v = Encode::null(); |
1083 | result.append(value: toJsonValue(value: v, visitedObjects)); |
1084 | } |
1085 | |
1086 | visitedObjects.remove(value: ObjectItem(a)); |
1087 | |
1088 | return result; |
1089 | } |
1090 | |