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 | |
4 | #include "qv4globalobject_p.h" |
5 | |
6 | #include <private/qv4alloca_p.h> |
7 | #include <private/qv4codegen_p.h> |
8 | #include <private/qv4context_p.h> |
9 | #include <private/qv4function_p.h> |
10 | #include <private/qv4mm_p.h> |
11 | #include <private/qv4scopedvalue_p.h> |
12 | #include <private/qv4script_p.h> |
13 | #include <private/qv4stackframe_p.h> |
14 | #include <private/qv4string_p.h> |
15 | #include <private/qv4value_p.h> |
16 | |
17 | #include <wtf/MathExtras.h> |
18 | |
19 | #include <QtCore/private/qlocale_tools_p.h> |
20 | #include <QtCore/private/qtools_p.h> |
21 | |
22 | #include <QtCore/qdebug.h> |
23 | #include <QtCore/qstring.h> |
24 | |
25 | #include <iostream> |
26 | |
27 | using namespace QV4; |
28 | using QtMiscUtils::toHexUpper; |
29 | using QtMiscUtils::fromHex; |
30 | |
31 | static QString escape(const QString &input) |
32 | { |
33 | QString output; |
34 | output.reserve(asize: input.size() * 3); |
35 | const int length = input.size(); |
36 | for (int i = 0; i < length; ++i) { |
37 | ushort uc = input.at(i).unicode(); |
38 | if (uc < 0x100) { |
39 | if ( (uc > 0x60 && uc < 0x7B) |
40 | || (uc > 0x3F && uc < 0x5B) |
41 | || (uc > 0x2C && uc < 0x3A) |
42 | || (uc == 0x2A) |
43 | || (uc == 0x2B) |
44 | || (uc == 0x5F)) { |
45 | output.append(c: QChar(uc)); |
46 | } else { |
47 | output.append(c: u'%'); |
48 | output.append(c: QLatin1Char(toHexUpper(value: uc >> 4))); |
49 | output.append(c: QLatin1Char(toHexUpper(value: uc))); |
50 | } |
51 | } else { |
52 | output.append(c: u'%'); |
53 | output.append(c: u'u'); |
54 | output.append(c: QLatin1Char(toHexUpper(value: uc >> 12))); |
55 | output.append(c: QLatin1Char(toHexUpper(value: uc >> 8))); |
56 | output.append(c: QLatin1Char(toHexUpper(value: uc >> 4))); |
57 | output.append(c: QLatin1Char(toHexUpper(value: uc))); |
58 | } |
59 | } |
60 | return output; |
61 | } |
62 | |
63 | static QString unescape(const QString &input) |
64 | { |
65 | QString result; |
66 | result.reserve(asize: input.size()); |
67 | int i = 0; |
68 | const int length = input.size(); |
69 | while (i < length) { |
70 | QChar c = input.at(i: i++); |
71 | if ((c == u'%') && (i + 1 < length)) { |
72 | QChar a = input.at(i); |
73 | if ((a == u'u') && (i + 4 < length)) { |
74 | int d3 = fromHex(c: input.at(i: i+1).unicode()); |
75 | int d2 = fromHex(c: input.at(i: i+2).unicode()); |
76 | int d1 = fromHex(c: input.at(i: i+3).unicode()); |
77 | int d0 = fromHex(c: input.at(i: i+4).unicode()); |
78 | if ((d3 != -1) && (d2 != -1) && (d1 != -1) && (d0 != -1)) { |
79 | ushort uc = ushort((d3 << 12) | (d2 << 8) | (d1 << 4) | d0); |
80 | result.append(c: QChar(uc)); |
81 | i += 5; |
82 | } else { |
83 | result.append(c); |
84 | } |
85 | } else { |
86 | int d1 = fromHex(c: a.unicode()); |
87 | int d0 = fromHex(c: input.at(i: i+1).unicode()); |
88 | if ((d1 != -1) && (d0 != -1)) { |
89 | c = QChar((d1 << 4) | d0); |
90 | i += 2; |
91 | } |
92 | result.append(c); |
93 | } |
94 | } else { |
95 | result.append(c); |
96 | } |
97 | } |
98 | return result; |
99 | } |
100 | |
101 | static const char uriReserved[] = ";/?:@&=+$,#" ; |
102 | static const char uriUnescaped[] = "-_.!~*'()" ; |
103 | static const char uriUnescapedReserved[] = "-_.!~*'();/?:@&=+$,#" ; |
104 | |
105 | static void addEscapeSequence(QString &output, uchar ch) |
106 | { |
107 | output.append(c: QLatin1Char('%')); |
108 | output.append(c: QLatin1Char(toHexUpper(value: ch >> 4))); |
109 | output.append(c: QLatin1Char(toHexUpper(value: ch & 0xf))); |
110 | } |
111 | |
112 | static QString encode(const QString &input, const char *unescapedSet, bool *ok) |
113 | { |
114 | *ok = true; |
115 | QString output; |
116 | const int length = input.size(); |
117 | int i = 0; |
118 | while (i < length) { |
119 | const QChar c = input.at(i); |
120 | bool escape = true; |
121 | if ((c.unicode() >= 'a' && c.unicode() <= 'z') || |
122 | (c.unicode() >= 'A' && c.unicode() <= 'Z') || |
123 | (c.unicode() >= '0' && c.unicode() <= '9')) { |
124 | escape = false; |
125 | } else { |
126 | const char *r = unescapedSet; |
127 | while (*r) { |
128 | if (*r == c.unicode()) { |
129 | escape = false; |
130 | break; |
131 | } |
132 | ++r; |
133 | } |
134 | } |
135 | if (escape) { |
136 | uint uc = c.unicode(); |
137 | if ((uc >= 0xDC00) && (uc <= 0xDFFF)) { |
138 | *ok = false; |
139 | break; |
140 | } |
141 | if (!((uc < 0xD800) || (uc > 0xDBFF))) { |
142 | ++i; |
143 | if (i == length) { |
144 | *ok = false; |
145 | break; |
146 | } |
147 | const uint uc2 = input.at(i).unicode(); |
148 | if ((uc2 < 0xDC00) || (uc2 > 0xDFFF)) { |
149 | *ok = false; |
150 | break; |
151 | } |
152 | uc = ((uc - 0xD800) * 0x400) + (uc2 - 0xDC00) + 0x10000; |
153 | } |
154 | if (uc < 0x80) { |
155 | addEscapeSequence(output, ch: (uchar)uc); |
156 | } else { |
157 | if (uc < 0x0800) { |
158 | addEscapeSequence(output, ch: 0xc0 | ((uchar) (uc >> 6))); |
159 | } else { |
160 | |
161 | if (QChar::requiresSurrogates(ucs4: uc)) { |
162 | addEscapeSequence(output, ch: 0xf0 | ((uchar) (uc >> 18))); |
163 | addEscapeSequence(output, ch: 0x80 | (((uchar) (uc >> 12)) & 0x3f)); |
164 | } else { |
165 | addEscapeSequence(output, ch: 0xe0 | (((uchar) (uc >> 12)) & 0x3f)); |
166 | } |
167 | addEscapeSequence(output, ch: 0x80 | (((uchar) (uc >> 6)) & 0x3f)); |
168 | } |
169 | addEscapeSequence(output, ch: 0x80 | ((uchar) (uc&0x3f))); |
170 | } |
171 | } else { |
172 | output.append(c); |
173 | } |
174 | ++i; |
175 | } |
176 | if (i != length) |
177 | *ok = false; |
178 | return output; |
179 | } |
180 | |
181 | enum DecodeMode { |
182 | DecodeAll, |
183 | DecodeNonReserved |
184 | }; |
185 | |
186 | static QString decode(const QString &input, DecodeMode decodeMode, bool *ok) |
187 | { |
188 | *ok = true; |
189 | QString output; |
190 | output.reserve(asize: input.size()); |
191 | const int length = input.size(); |
192 | int i = 0; |
193 | const QChar percent = QLatin1Char('%'); |
194 | while (i < length) { |
195 | const QChar ch = input.at(i); |
196 | if (ch == percent) { |
197 | int start = i; |
198 | if (i + 2 >= length) |
199 | goto error; |
200 | |
201 | int d1 = fromHex(c: input.at(i: i+1).unicode()); |
202 | int d0 = fromHex(c: input.at(i: i+2).unicode()); |
203 | if ((d1 == -1) || (d0 == -1)) |
204 | goto error; |
205 | |
206 | int b = (d1 << 4) | d0; |
207 | i += 2; |
208 | if (b & 0x80) { |
209 | int uc; |
210 | int min_uc; |
211 | int need; |
212 | if ((b & 0xe0) == 0xc0) { |
213 | uc = b & 0x1f; |
214 | need = 1; |
215 | min_uc = 0x80; |
216 | } else if ((b & 0xf0) == 0xe0) { |
217 | uc = b & 0x0f; |
218 | need = 2; |
219 | min_uc = 0x800; |
220 | } else if ((b & 0xf8) == 0xf0) { |
221 | uc = b & 0x07; |
222 | need = 3; |
223 | min_uc = 0x10000; |
224 | } else { |
225 | goto error; |
226 | } |
227 | |
228 | if (i + (3 * need) >= length) |
229 | goto error; |
230 | |
231 | for (int j = 0; j < need; ++j) { |
232 | ++i; |
233 | if (input.at(i) != percent) |
234 | goto error; |
235 | |
236 | d1 = fromHex(c: input.at(i: i+1).unicode()); |
237 | d0 = fromHex(c: input.at(i: i+2).unicode()); |
238 | if ((d1 == -1) || (d0 == -1)) |
239 | goto error; |
240 | |
241 | b = (d1 << 4) | d0; |
242 | if ((b & 0xC0) != 0x80) |
243 | goto error; |
244 | |
245 | i += 2; |
246 | uc = (uc << 6) + (b & 0x3f); |
247 | } |
248 | if (uc < min_uc) |
249 | goto error; |
250 | |
251 | if (uc < 0x10000) { |
252 | output.append(c: QChar(uc)); |
253 | } else { |
254 | if (uc > 0x10FFFF) |
255 | goto error; |
256 | |
257 | ushort l = ushort(((uc - 0x10000) & 0x3FF) + 0xDC00); |
258 | ushort h = ushort((((uc - 0x10000) >> 10) & 0x3FF) + 0xD800); |
259 | output.append(c: QChar(h)); |
260 | output.append(c: QChar(l)); |
261 | } |
262 | } else { |
263 | if (decodeMode == DecodeNonReserved && b <= 0x40) { |
264 | const char *r = uriReserved; |
265 | while (*r) { |
266 | if (*r == b) |
267 | break; |
268 | ++r; |
269 | } |
270 | if (*r) |
271 | output.append(v: QStringView{input}.mid(pos: start, n: i - start + 1)); |
272 | else |
273 | output.append(c: QChar(b)); |
274 | } else { |
275 | output.append(c: QChar(b)); |
276 | } |
277 | } |
278 | } else { |
279 | output.append(c: ch); |
280 | } |
281 | ++i; |
282 | } |
283 | if (i != length) |
284 | *ok = false; |
285 | return output; |
286 | error: |
287 | *ok = false; |
288 | return QString(); |
289 | } |
290 | |
291 | DEFINE_OBJECT_VTABLE(EvalFunction); |
292 | |
293 | void Heap::EvalFunction::init(QV4::ExecutionContext *scope) |
294 | { |
295 | Scope s(scope); |
296 | Heap::FunctionObject::init(scope, name: s.engine->id_eval()); |
297 | ScopedFunctionObject f(s, this); |
298 | f->defineReadonlyConfigurableProperty(name: s.engine->id_length(), value: Value::fromInt32(i: 1)); |
299 | } |
300 | |
301 | ReturnedValue EvalFunction::evalCall(const Value *, const Value *argv, int argc, bool directCall) const |
302 | { |
303 | if (argc < 1) |
304 | return Encode::undefined(); |
305 | |
306 | ExecutionEngine *v4 = engine(); |
307 | bool isStrict = v4->currentStackFrame->v4Function->isStrict(); |
308 | |
309 | Scope scope(v4); |
310 | ScopedContext ctx(scope, v4->currentContext()); |
311 | |
312 | if (!directCall) { |
313 | // the context for eval should be the global scope |
314 | ctx = v4->scriptContext(); |
315 | } |
316 | |
317 | String *scode = argv[0].stringValue(); |
318 | if (!scode) |
319 | return argv[0].asReturnedValue(); |
320 | |
321 | const QString code = scode->toQString(); |
322 | bool inheritContext = !isStrict; |
323 | |
324 | Script script(ctx, QV4::Compiler::ContextType::Eval, code, QStringLiteral("eval code" )); |
325 | script.strictMode = (directCall && isStrict); |
326 | script.inheritContext = inheritContext; |
327 | script.parse(); |
328 | if (v4->hasException) |
329 | return Encode::undefined(); |
330 | |
331 | Function *function = script.function(); |
332 | if (!function) |
333 | return Encode::undefined(); |
334 | function->kind = Function::Eval; |
335 | |
336 | if (function->isStrict() || isStrict) { |
337 | ScopedFunctionObject e(scope, FunctionObject::createScriptFunction(scope: ctx, function)); |
338 | ScopedValue thisObject(scope, directCall ? scope.engine->currentStackFrame->thisObject() : scope.engine->globalObject->asReturnedValue()); |
339 | return checkedResult(v4, result: e->call(thisObject, argv: nullptr, argc: 0)); |
340 | } |
341 | |
342 | ScopedValue thisObject(scope, scope.engine->currentStackFrame->thisObject()); |
343 | |
344 | return checkedResult(v4, result: function->call(thisObject, argv: nullptr, argc: 0, context: ctx)); |
345 | } |
346 | |
347 | |
348 | ReturnedValue EvalFunction::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
349 | { |
350 | // indirect call |
351 | return static_cast<const EvalFunction *>(f)->evalCall(thisObject, argv, argc, directCall: false); |
352 | } |
353 | |
354 | |
355 | static inline int toInt(const QChar &qc, int R) |
356 | { |
357 | ushort c = qc.unicode(); |
358 | int v = -1; |
359 | if (c >= '0' && c <= '9') |
360 | v = c - '0'; |
361 | else if (c >= 'A' && c <= 'Z') |
362 | v = c - 'A' + 10; |
363 | else if (c >= 'a' && c <= 'z') |
364 | v = c - 'a' + 10; |
365 | if (v >= 0 && v < R) |
366 | return v; |
367 | else |
368 | return -1; |
369 | } |
370 | |
371 | // parseInt [15.1.2.2] |
372 | ReturnedValue GlobalFunctions::method_parseInt(const FunctionObject *b, const Value *, const Value *argv, int argc) |
373 | { |
374 | Scope scope(b); |
375 | ScopedValue inputString(scope, argc ? argv[0] : Value::undefinedValue()); |
376 | ScopedValue radix(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
377 | int R = radix->isUndefined() ? 0 : radix->toInt32(); |
378 | |
379 | // [15.1.2.2] step by step: |
380 | QString trimmed = inputString->toQString().trimmed(); // 1 + 2 |
381 | CHECK_EXCEPTION(); |
382 | |
383 | const QChar *pos = trimmed.constData(); |
384 | const QChar *end = pos + trimmed.size(); |
385 | |
386 | int sign = 1; // 3 |
387 | if (pos != end) { |
388 | if (*pos == QLatin1Char('-')) |
389 | sign = -1; // 4 |
390 | if (*pos == QLatin1Char('-') || *pos == QLatin1Char('+')) |
391 | ++pos; // 5 |
392 | } |
393 | bool stripPrefix = true; // 7 |
394 | if (R) { // 8 |
395 | if (R < 2 || R > 36) |
396 | RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); // 8a |
397 | if (R != 16) |
398 | stripPrefix = false; // 8b |
399 | } else { // 9 |
400 | R = 10; // 9a |
401 | } |
402 | if (stripPrefix) { // 10 |
403 | if ((end - pos >= 2) |
404 | && (pos[0] == QLatin1Char('0')) |
405 | && (pos[1] == QLatin1Char('x') || pos[1] == QLatin1Char('X'))) { // 10a |
406 | pos += 2; |
407 | R = 16; |
408 | } |
409 | } |
410 | // 11: Z is progressively built below |
411 | // 13: this is handled by the toInt function |
412 | if (pos == end) // 12 |
413 | RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); |
414 | bool overflow = false; |
415 | qint64 v_overflow = 0; |
416 | unsigned overflow_digit_count = 0; |
417 | int d = toInt(qc: *pos++, R); |
418 | if (d == -1) |
419 | RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); |
420 | qint64 v = d; |
421 | while (pos != end) { |
422 | d = toInt(qc: *pos++, R); |
423 | if (d == -1) |
424 | break; |
425 | if (overflow) { |
426 | if (overflow_digit_count == 0) { |
427 | v_overflow = v; |
428 | v = 0; |
429 | } |
430 | ++overflow_digit_count; |
431 | v = v * R + d; |
432 | } else { |
433 | qint64 vNew = v * R + d; |
434 | if (vNew < v) { |
435 | overflow = true; |
436 | --pos; |
437 | } else { |
438 | v = vNew; |
439 | } |
440 | } |
441 | } |
442 | |
443 | if (overflow) { |
444 | double result = (double) v_overflow * pow(x: static_cast<double>(R), y: static_cast<double>(overflow_digit_count)); |
445 | result += v; |
446 | RETURN_RESULT(Encode(sign * result)); |
447 | } else { |
448 | RETURN_RESULT(Encode(sign * (double) v)); // 15 |
449 | } |
450 | } |
451 | |
452 | // parseFloat [15.1.2.3] |
453 | ReturnedValue GlobalFunctions::method_parseFloat(const FunctionObject *b, const Value *, const Value *argv, int argc) |
454 | { |
455 | Scope scope(b); |
456 | // [15.1.2.3] step by step: |
457 | ScopedString inputString(scope, argc ? argv[0] : Value::undefinedValue(), ScopedString::Convert); |
458 | CHECK_EXCEPTION(); |
459 | |
460 | QString trimmed = inputString->toQString().trimmed(); // 2 |
461 | |
462 | // 4: |
463 | if (trimmed.startsWith(s: QLatin1String("Infinity" )) |
464 | || trimmed.startsWith(s: QLatin1String("+Infinity" ))) |
465 | RETURN_RESULT(Encode(Q_INFINITY)); |
466 | if (trimmed.startsWith(s: QLatin1String("-Infinity" ))) |
467 | RETURN_RESULT(Encode(-Q_INFINITY)); |
468 | QByteArray ba = trimmed.toLatin1(); |
469 | bool ok; |
470 | const char *begin = ba.constData(); |
471 | const char *end = nullptr; |
472 | double d = qstrtod(s00: begin, se: &end, ok: &ok); |
473 | if (end - begin == 0) |
474 | RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); // 3 |
475 | else |
476 | RETURN_RESULT(Encode(d)); |
477 | } |
478 | |
479 | /// isNaN [15.1.2.4] |
480 | ReturnedValue GlobalFunctions::method_isNaN(const FunctionObject *, const Value *, const Value *argv, int argc) |
481 | { |
482 | if (!argc) |
483 | // undefined gets converted to NaN |
484 | RETURN_RESULT(Encode(true)); |
485 | |
486 | if (argv[0].integerCompatible()) |
487 | RETURN_RESULT(Encode(false)); |
488 | |
489 | double d = argv[0].toNumber(); |
490 | RETURN_RESULT(Encode((bool)std::isnan(d))); |
491 | } |
492 | |
493 | /// isFinite [15.1.2.5] |
494 | ReturnedValue GlobalFunctions::method_isFinite(const FunctionObject *, const Value *, const Value *argv, int argc) |
495 | { |
496 | if (!argc) |
497 | // undefined gets converted to NaN |
498 | RETURN_RESULT(Encode(false)); |
499 | |
500 | if (argv[0].integerCompatible()) |
501 | RETURN_RESULT(Encode(true)); |
502 | |
503 | double d = argv[0].toNumber(); |
504 | RETURN_RESULT(Encode((bool)std::isfinite(d))); |
505 | } |
506 | |
507 | /// decodeURI [15.1.3.1] |
508 | ReturnedValue GlobalFunctions::method_decodeURI(const FunctionObject *b, const Value *, const Value *argv, int argc) |
509 | { |
510 | if (argc == 0) |
511 | RETURN_UNDEFINED(); |
512 | |
513 | ExecutionEngine *v4 = b->engine(); |
514 | QString uriString = argv[0].toQString(); |
515 | bool ok; |
516 | QString out = decode(input: uriString, decodeMode: DecodeNonReserved, ok: &ok); |
517 | if (!ok) { |
518 | Scope scope(v4); |
519 | ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence" ))); |
520 | RETURN_RESULT(scope.engine->throwURIError(s)); |
521 | } |
522 | |
523 | RETURN_RESULT(v4->newString(out)); |
524 | } |
525 | |
526 | /// decodeURIComponent [15.1.3.2] |
527 | ReturnedValue GlobalFunctions::method_decodeURIComponent(const FunctionObject *b, const Value *, const Value *argv, int argc) |
528 | { |
529 | if (argc == 0) |
530 | RETURN_UNDEFINED(); |
531 | |
532 | ExecutionEngine *v4 = b->engine(); |
533 | QString uriString = argv[0].toQString(); |
534 | bool ok; |
535 | QString out = decode(input: uriString, decodeMode: DecodeAll, ok: &ok); |
536 | if (!ok) { |
537 | Scope scope(v4); |
538 | ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence" ))); |
539 | RETURN_RESULT(scope.engine->throwURIError(s)); |
540 | } |
541 | |
542 | RETURN_RESULT(v4->newString(out)); |
543 | } |
544 | |
545 | /// encodeURI [15.1.3.3] |
546 | ReturnedValue GlobalFunctions::method_encodeURI(const FunctionObject *b, const Value *, const Value *argv, int argc) |
547 | { |
548 | if (argc == 0) |
549 | RETURN_UNDEFINED(); |
550 | |
551 | ExecutionEngine *v4 = b->engine(); |
552 | QString uriString = argv[0].toQString(); |
553 | bool ok; |
554 | QString out = encode(input: uriString, unescapedSet: uriUnescapedReserved, ok: &ok); |
555 | if (!ok) { |
556 | Scope scope(v4); |
557 | ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence" ))); |
558 | RETURN_RESULT(scope.engine->throwURIError(s)); |
559 | } |
560 | |
561 | RETURN_RESULT(v4->newString(out)); |
562 | } |
563 | |
564 | /// encodeURIComponent [15.1.3.4] |
565 | ReturnedValue GlobalFunctions::method_encodeURIComponent(const FunctionObject *b, const Value *, const Value *argv, int argc) |
566 | { |
567 | if (argc == 0) |
568 | RETURN_UNDEFINED(); |
569 | |
570 | ExecutionEngine *v4 = b->engine(); |
571 | QString uriString = argv[0].toQString(); |
572 | bool ok; |
573 | QString out = encode(input: uriString, unescapedSet: uriUnescaped, ok: &ok); |
574 | if (!ok) { |
575 | Scope scope(v4); |
576 | ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence" ))); |
577 | RETURN_RESULT(scope.engine->throwURIError(s)); |
578 | } |
579 | |
580 | RETURN_RESULT(v4->newString(out)); |
581 | } |
582 | |
583 | ReturnedValue GlobalFunctions::method_escape(const FunctionObject *b, const Value *, const Value *argv, int argc) |
584 | { |
585 | ExecutionEngine *v4 = b->engine(); |
586 | if (!argc) |
587 | RETURN_RESULT(v4->newString(QStringLiteral("undefined" ))); |
588 | |
589 | QString str = argv[0].toQString(); |
590 | RETURN_RESULT(v4->newString(escape(str))); |
591 | } |
592 | |
593 | ReturnedValue GlobalFunctions::method_unescape(const FunctionObject *b, const Value *, const Value *argv, int argc) |
594 | { |
595 | ExecutionEngine *v4 = b->engine(); |
596 | if (!argc) |
597 | RETURN_RESULT(v4->newString(QStringLiteral("undefined" ))); |
598 | |
599 | QString str = argv[0].toQString(); |
600 | RETURN_RESULT(v4->newString(unescape(str))); |
601 | } |
602 | |