source: webkit/trunk/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp@ 63120

Last change on this file since 63120 was 63120, checked in by Darin Adler, 15 years ago

2010-07-09 Darin Adler <Darin Adler>

Reviewed by Geoffrey Garen.

String to number coercion is not spec compliant
https://bugs.webkit.org/show_bug.cgi?id=31349

ToNumber should ignore NBSP (\u00a0)
https://bugs.webkit.org/show_bug.cgi?id=25490

  • runtime/JSGlobalObjectFunctions.cpp: (JSC::parseIntOverflow): Added a version that works on UChar.
  • runtime/JSGlobalObjectFunctions.h: Ditto.
  • runtime/UString.cpp: (JSC::isInfinity): Added helper functions. (JSC::UString::toDouble): Use isStrWhiteSpace instead of isSASCIISpace to define what we should skip. Got rid of the code that used CString and UTF8String, instead processing the UChar of the string directly, except for when we call strtod. For strtod, use our own home-grown conversion function that does not try to do any UTF-16 processing. Tidied up the logic a bit as well.

2010-07-09 Darin Adler <Darin Adler>

Reviewed by Geoffrey Garen.

String to number coercion is not spec compliant
https://bugs.webkit.org/show_bug.cgi?id=31349

  • fast/js/ToNumber-expected.txt: Updated to expect more tests to pass.
  • fast/js/parseFloat-expected.txt: Ditto.
  • fast/js/sputnik/Conformance/09_Type_Conversion/9.3_ToNumber/9.3.1_ToNumber_from_String/S9.3.1_A2-expected.txt: Ditto.
  • fast/js/sputnik/Conformance/09_Type_Conversion/9.3_ToNumber/9.3.1_ToNumber_from_String/S9.3.1_A3_T1-expected.txt: Ditto.
  • fast/js/sputnik/Conformance/09_Type_Conversion/9.3_ToNumber/9.3.1_ToNumber_from_String/S9.3.1_A3_T2-expected.txt: Ditto.
  • fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T10-expected.txt: Ditto.
  • fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T3-expected.txt: Ditto.
  • fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T8-expected.txt: Ditto.
  • fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A2_T9-expected.txt: Ditto.
  • fast/js/sputnik/Conformance/15_Native_Objects/15.1_The_Global_Object/15.1.2/15.1.2.3_parseFloat/S15.1.2.3_A6-expected.txt: Ditto.
  • Property svn:eol-style set to native
File size: 14.2 KB
Line 
1/*
2 * Copyright (C) 1999-2002 Harri Porten ([email protected])
3 * Copyright (C) 2001 Peter Kelly ([email protected])
4 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
5 * Copyright (C) 2007 Cameron Zwarich ([email protected])
6 * Copyright (C) 2007 Maks Orlovich
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 *
23 */
24
25#include "config.h"
26#include "JSGlobalObjectFunctions.h"
27
28#include "CallFrame.h"
29#include "GlobalEvalFunction.h"
30#include "Interpreter.h"
31#include "JSGlobalObject.h"
32#include "JSString.h"
33#include "JSStringBuilder.h"
34#include "Lexer.h"
35#include "LiteralParser.h"
36#include "Nodes.h"
37#include "Parser.h"
38#include "StringBuilder.h"
39#include "dtoa.h"
40#include <stdio.h>
41#include <stdlib.h>
42#include <wtf/ASCIICType.h>
43#include <wtf/Assertions.h>
44#include <wtf/MathExtras.h>
45#include <wtf/StringExtras.h>
46#include <wtf/unicode/UTF8.h>
47
48using namespace WTF;
49using namespace Unicode;
50
51namespace JSC {
52
53static JSValue encode(ExecState* exec, const char* doNotEscape)
54{
55 UString str = exec->argument(0).toString(exec);
56 CString cstr = str.UTF8String(true);
57 if (!cstr.data())
58 return throwError(exec, createURIError(exec, "String contained an illegal UTF-16 sequence."));
59
60 JSStringBuilder builder;
61 const char* p = cstr.data();
62 for (size_t k = 0; k < cstr.length(); k++, p++) {
63 char c = *p;
64 if (c && strchr(doNotEscape, c))
65 builder.append(c);
66 else {
67 char tmp[4];
68 snprintf(tmp, sizeof(tmp), "%%%02X", static_cast<unsigned char>(c));
69 builder.append(tmp);
70 }
71 }
72 return builder.build(exec);
73}
74
75static JSValue decode(ExecState* exec, const char* doNotUnescape, bool strict)
76{
77 JSStringBuilder builder;
78 UString str = exec->argument(0).toString(exec);
79 int k = 0;
80 int len = str.size();
81 const UChar* d = str.data();
82 UChar u = 0;
83 while (k < len) {
84 const UChar* p = d + k;
85 UChar c = *p;
86 if (c == '%') {
87 int charLen = 0;
88 if (k <= len - 3 && isASCIIHexDigit(p[1]) && isASCIIHexDigit(p[2])) {
89 const char b0 = Lexer::convertHex(p[1], p[2]);
90 const int sequenceLen = UTF8SequenceLength(b0);
91 if (sequenceLen != 0 && k <= len - sequenceLen * 3) {
92 charLen = sequenceLen * 3;
93 char sequence[5];
94 sequence[0] = b0;
95 for (int i = 1; i < sequenceLen; ++i) {
96 const UChar* q = p + i * 3;
97 if (q[0] == '%' && isASCIIHexDigit(q[1]) && isASCIIHexDigit(q[2]))
98 sequence[i] = Lexer::convertHex(q[1], q[2]);
99 else {
100 charLen = 0;
101 break;
102 }
103 }
104 if (charLen != 0) {
105 sequence[sequenceLen] = 0;
106 const int character = decodeUTF8Sequence(sequence);
107 if (character < 0 || character >= 0x110000)
108 charLen = 0;
109 else if (character >= 0x10000) {
110 // Convert to surrogate pair.
111 builder.append(static_cast<UChar>(0xD800 | ((character - 0x10000) >> 10)));
112 u = static_cast<UChar>(0xDC00 | ((character - 0x10000) & 0x3FF));
113 } else
114 u = static_cast<UChar>(character);
115 }
116 }
117 }
118 if (charLen == 0) {
119 if (strict)
120 return throwError(exec, createURIError(exec, "URI error"));
121 // The only case where we don't use "strict" mode is the "unescape" function.
122 // For that, it's good to support the wonky "%u" syntax for compatibility with WinIE.
123 if (k <= len - 6 && p[1] == 'u'
124 && isASCIIHexDigit(p[2]) && isASCIIHexDigit(p[3])
125 && isASCIIHexDigit(p[4]) && isASCIIHexDigit(p[5])) {
126 charLen = 6;
127 u = Lexer::convertUnicode(p[2], p[3], p[4], p[5]);
128 }
129 }
130 if (charLen && (u == 0 || u >= 128 || !strchr(doNotUnescape, u))) {
131 c = u;
132 k += charLen - 1;
133 }
134 }
135 k++;
136 builder.append(c);
137 }
138 return builder.build(exec);
139}
140
141bool isStrWhiteSpace(UChar c)
142{
143 switch (c) {
144 // ECMA-262-5th 7.2 & 7.3
145 case 0x0009:
146 case 0x000A:
147 case 0x000B:
148 case 0x000C:
149 case 0x000D:
150 case 0x0020:
151 case 0x00A0:
152 case 0x2028:
153 case 0x2029:
154 case 0xFEFF:
155 return true;
156 default:
157 return c > 0xff && isSeparatorSpace(c);
158 }
159}
160
161static int parseDigit(unsigned short c, int radix)
162{
163 int digit = -1;
164
165 if (c >= '0' && c <= '9')
166 digit = c - '0';
167 else if (c >= 'A' && c <= 'Z')
168 digit = c - 'A' + 10;
169 else if (c >= 'a' && c <= 'z')
170 digit = c - 'a' + 10;
171
172 if (digit >= radix)
173 return -1;
174 return digit;
175}
176
177double parseIntOverflow(const char* s, int length, int radix)
178{
179 double number = 0.0;
180 double radixMultiplier = 1.0;
181
182 for (const char* p = s + length - 1; p >= s; p--) {
183 if (radixMultiplier == Inf) {
184 if (*p != '0') {
185 number = Inf;
186 break;
187 }
188 } else {
189 int digit = parseDigit(*p, radix);
190 number += digit * radixMultiplier;
191 }
192
193 radixMultiplier *= radix;
194 }
195
196 return number;
197}
198
199double parseIntOverflow(const UChar* s, int length, int radix)
200{
201 double number = 0.0;
202 double radixMultiplier = 1.0;
203
204 for (const UChar* p = s + length - 1; p >= s; p--) {
205 if (radixMultiplier == Inf) {
206 if (*p != '0') {
207 number = Inf;
208 break;
209 }
210 } else {
211 int digit = parseDigit(*p, radix);
212 number += digit * radixMultiplier;
213 }
214
215 radixMultiplier *= radix;
216 }
217
218 return number;
219}
220
221static double parseInt(const UString& s, int radix)
222{
223 int length = s.size();
224 const UChar* data = s.data();
225 int p = 0;
226
227 while (p < length && isStrWhiteSpace(data[p]))
228 ++p;
229
230 double sign = 1;
231 if (p < length) {
232 if (data[p] == '+')
233 ++p;
234 else if (data[p] == '-') {
235 sign = -1;
236 ++p;
237 }
238 }
239
240 if ((radix == 0 || radix == 16) && length - p >= 2 && data[p] == '0' && (data[p + 1] == 'x' || data[p + 1] == 'X')) {
241 radix = 16;
242 p += 2;
243 } else if (radix == 0) {
244 if (p < length && data[p] == '0')
245 radix = 8;
246 else
247 radix = 10;
248 }
249
250 if (radix < 2 || radix > 36)
251 return NaN;
252
253 int firstDigitPosition = p;
254 bool sawDigit = false;
255 double number = 0;
256 while (p < length) {
257 int digit = parseDigit(data[p], radix);
258 if (digit == -1)
259 break;
260 sawDigit = true;
261 number *= radix;
262 number += digit;
263 ++p;
264 }
265
266 if (number >= mantissaOverflowLowerBound) {
267 if (radix == 10)
268 number = WTF::strtod(s.substr(firstDigitPosition, p - firstDigitPosition).UTF8String().data(), 0);
269 else if (radix == 2 || radix == 4 || radix == 8 || radix == 16 || radix == 32)
270 number = parseIntOverflow(s.substr(firstDigitPosition, p - firstDigitPosition).UTF8String().data(), p - firstDigitPosition, radix);
271 }
272
273 if (!sawDigit)
274 return NaN;
275
276 return sign * number;
277}
278
279static double parseFloat(const UString& s)
280{
281 // Check for 0x prefix here, because toDouble allows it, but we must treat it as 0.
282 // Need to skip any whitespace and then one + or - sign.
283 int length = s.size();
284 const UChar* data = s.data();
285 int p = 0;
286 while (p < length && isStrWhiteSpace(data[p]))
287 ++p;
288
289 if (p < length && (data[p] == '+' || data[p] == '-'))
290 ++p;
291
292 if (length - p >= 2 && data[p] == '0' && (data[p + 1] == 'x' || data[p + 1] == 'X'))
293 return 0;
294
295 return s.toDouble(true /*tolerant*/, false /* NaN for empty string */);
296}
297
298EncodedJSValue JSC_HOST_CALL globalFuncEval(ExecState* exec)
299{
300 JSObject* thisObject = exec->hostThisValue().toThisObject(exec);
301 JSObject* unwrappedObject = thisObject->unwrappedObject();
302 if (!unwrappedObject->isGlobalObject() || static_cast<JSGlobalObject*>(unwrappedObject)->evalFunction() != exec->callee())
303 return throwVMError(exec, createEvalError(exec, "The \"this\" value passed to eval must be the global object from which eval originated"));
304
305 JSValue x = exec->argument(0);
306 if (!x.isString())
307 return JSValue::encode(x);
308
309 UString s = x.toString(exec);
310
311 LiteralParser preparser(exec, s, LiteralParser::NonStrictJSON);
312 if (JSValue parsedObject = preparser.tryLiteralParse())
313 return JSValue::encode(parsedObject);
314
315 RefPtr<EvalExecutable> eval = EvalExecutable::create(exec, makeSource(s));
316 JSObject* error = eval->compile(exec, static_cast<JSGlobalObject*>(unwrappedObject)->globalScopeChain().node());
317 if (error)
318 return throwVMError(exec, error);
319
320 return JSValue::encode(exec->interpreter()->execute(eval.get(), exec, thisObject, static_cast<JSGlobalObject*>(unwrappedObject)->globalScopeChain().node(), exec->exceptionSlot()));
321}
322
323EncodedJSValue JSC_HOST_CALL globalFuncParseInt(ExecState* exec)
324{
325 JSValue value = exec->argument(0);
326 int32_t radix = exec->argument(1).toInt32(exec);
327
328 if (radix != 0 && radix != 10)
329 return JSValue::encode(jsNumber(exec, parseInt(value.toString(exec), radix)));
330
331 if (value.isInt32())
332 return JSValue::encode(value);
333
334 if (value.isDouble()) {
335 double d = value.asDouble();
336 if (isfinite(d))
337 return JSValue::encode(jsNumber(exec, (d > 0) ? floor(d) : ceil(d)));
338 if (isnan(d) || isinf(d))
339 return JSValue::encode(jsNaN(exec));
340 return JSValue::encode(jsNumber(exec, 0));
341 }
342
343 return JSValue::encode(jsNumber(exec, parseInt(value.toString(exec), radix)));
344}
345
346EncodedJSValue JSC_HOST_CALL globalFuncParseFloat(ExecState* exec)
347{
348 return JSValue::encode(jsNumber(exec, parseFloat(exec->argument(0).toString(exec))));
349}
350
351EncodedJSValue JSC_HOST_CALL globalFuncIsNaN(ExecState* exec)
352{
353 return JSValue::encode(jsBoolean(isnan(exec->argument(0).toNumber(exec))));
354}
355
356EncodedJSValue JSC_HOST_CALL globalFuncIsFinite(ExecState* exec)
357{
358 double n = exec->argument(0).toNumber(exec);
359 return JSValue::encode(jsBoolean(!isnan(n) && !isinf(n)));
360}
361
362EncodedJSValue JSC_HOST_CALL globalFuncDecodeURI(ExecState* exec)
363{
364 static const char do_not_unescape_when_decoding_URI[] =
365 "#$&+,/:;=?@";
366
367 return JSValue::encode(decode(exec, do_not_unescape_when_decoding_URI, true));
368}
369
370EncodedJSValue JSC_HOST_CALL globalFuncDecodeURIComponent(ExecState* exec)
371{
372 return JSValue::encode(decode(exec, "", true));
373}
374
375EncodedJSValue JSC_HOST_CALL globalFuncEncodeURI(ExecState* exec)
376{
377 static const char do_not_escape_when_encoding_URI[] =
378 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
379 "abcdefghijklmnopqrstuvwxyz"
380 "0123456789"
381 "!#$&'()*+,-./:;=?@_~";
382
383 return JSValue::encode(encode(exec, do_not_escape_when_encoding_URI));
384}
385
386EncodedJSValue JSC_HOST_CALL globalFuncEncodeURIComponent(ExecState* exec)
387{
388 static const char do_not_escape_when_encoding_URI_component[] =
389 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
390 "abcdefghijklmnopqrstuvwxyz"
391 "0123456789"
392 "!'()*-._~";
393
394 return JSValue::encode(encode(exec, do_not_escape_when_encoding_URI_component));
395}
396
397EncodedJSValue JSC_HOST_CALL globalFuncEscape(ExecState* exec)
398{
399 static const char do_not_escape[] =
400 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
401 "abcdefghijklmnopqrstuvwxyz"
402 "0123456789"
403 "*+-./@_";
404
405 JSStringBuilder builder;
406 UString str = exec->argument(0).toString(exec);
407 const UChar* c = str.data();
408 for (unsigned k = 0; k < str.size(); k++, c++) {
409 int u = c[0];
410 if (u > 255) {
411 char tmp[7];
412 snprintf(tmp, sizeof(tmp), "%%u%04X", u);
413 builder.append(tmp);
414 } else if (u != 0 && strchr(do_not_escape, static_cast<char>(u)))
415 builder.append(c, 1);
416 else {
417 char tmp[4];
418 snprintf(tmp, sizeof(tmp), "%%%02X", u);
419 builder.append(tmp);
420 }
421 }
422
423 return JSValue::encode(builder.build(exec));
424}
425
426EncodedJSValue JSC_HOST_CALL globalFuncUnescape(ExecState* exec)
427{
428 StringBuilder builder;
429 UString str = exec->argument(0).toString(exec);
430 int k = 0;
431 int len = str.size();
432 while (k < len) {
433 const UChar* c = str.data() + k;
434 UChar u;
435 if (c[0] == '%' && k <= len - 6 && c[1] == 'u') {
436 if (isASCIIHexDigit(c[2]) && isASCIIHexDigit(c[3]) && isASCIIHexDigit(c[4]) && isASCIIHexDigit(c[5])) {
437 u = Lexer::convertUnicode(c[2], c[3], c[4], c[5]);
438 c = &u;
439 k += 5;
440 }
441 } else if (c[0] == '%' && k <= len - 3 && isASCIIHexDigit(c[1]) && isASCIIHexDigit(c[2])) {
442 u = UChar(Lexer::convertHex(c[1], c[2]));
443 c = &u;
444 k += 2;
445 }
446 k++;
447 builder.append(*c);
448 }
449
450 return JSValue::encode(jsString(exec, builder.build()));
451}
452
453#ifndef NDEBUG
454EncodedJSValue JSC_HOST_CALL globalFuncJSCPrint(ExecState* exec)
455{
456 CString string = exec->argument(0).toString(exec).UTF8String();
457 puts(string.data());
458 return JSValue::encode(jsUndefined());
459}
460#endif
461
462} // namespace JSC
Note: See TracBrowser for help on using the repository browser.