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
27using namespace QV4;
28using QtMiscUtils::toHexUpper;
29using QtMiscUtils::fromHex;
30
31static 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
63static 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
101static const char uriReserved[] = ";/?:@&=+$,#";
102static const char uriUnescaped[] = "-_.!~*'()";
103static const char uriUnescapedReserved[] = "-_.!~*'();/?:@&=+$,#";
104
105static 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
112static 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
181enum DecodeMode {
182 DecodeAll,
183 DecodeNonReserved
184};
185
186static 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
291DEFINE_OBJECT_VTABLE(EvalFunction);
292
293void 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
301ReturnedValue 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
348ReturnedValue 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
355static 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]
372ReturnedValue 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]
453ReturnedValue 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]
480ReturnedValue 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]
494ReturnedValue 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]
508ReturnedValue 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]
527ReturnedValue 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]
546ReturnedValue 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]
565ReturnedValue 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
583ReturnedValue 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
593ReturnedValue 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

source code of qtdeclarative/src/qml/jsruntime/qv4globalobject.cpp