source: webkit/trunk/JavaScriptCore/runtime/Identifier.cpp@ 67583

Last change on this file since 67583 was 65588, checked in by [email protected], 15 years ago

Bug 44146 - Remove toDouble/toUInt32 methods from UString.

Reviewed by Sam Weinig.

JavaScriptCore:

These methods all implement JavaScript language specific behaviour, and as such
are not suited to being on a generic string object. They are also inefficient
and incorrectly used, refactor & cleanup. Uses of these methods really divide
out into two cases.

ToNumber:
Uses of toDouble from JSString and from parseFloat are implementing ecma's
ToNumber conversion from strings (see ecma-262 9.3.1), so UString::toDouble
should largely just be moved out to a global jsToNumber function. ToNumber is
capable of recognizing either decimal or hexadecimal numbers, but parseFloat
should only recognize decimal values. This is currently handled by testing for
hexadecimal before calling toDouble, which should unnecessary - instead we can
just split out the two parts to the grammar into separate functions. Also,
strtod recognizes a set of literals (nan, inf, and infinity - all with any
capitalization) - which are not defined by any of the specs we are implementing.
To handle this we need to perform additional work in toDouble to convert the
unsupported cases of infinities back to NaNs. Instead we should simply remove
support for this literals from strtod. This should provide a more desirable
behaviour for all clients of strtod.

Indexed properties:
Uses of the toStrictUInt32 methods are were all converting property names to
indices, and all uses of toUInt32 were incorrect; in all cases we should have
been calling toUInt32. This error results in some incorrect behaviour in the
DOM (accessing property "0 " of a NodeList should fail; it currently does not).
Move this method onto Identifier (our canonical property name), and make it
always perform a strict conversion. Add a layout test to check NodeList does
convert indexed property names correctly.

(JSC::Arguments::getOwnPropertySlot):
(JSC::Arguments::getOwnPropertyDescriptor):
(JSC::Arguments::put):
(JSC::Arguments::deleteProperty):

  • runtime/Identifier.cpp:

(JSC::Identifier::toUInt32):

  • runtime/Identifier.h:

(JSC::Identifier::toUInt32):

  • runtime/JSArray.cpp:

(JSC::JSArray::getOwnPropertySlot):
(JSC::JSArray::getOwnPropertyDescriptor):
(JSC::JSArray::put):
(JSC::JSArray::deleteProperty):

  • runtime/JSArray.h:

(JSC::Identifier::toArrayIndex):

  • runtime/JSByteArray.cpp:

(JSC::JSByteArray::getOwnPropertySlot):
(JSC::JSByteArray::getOwnPropertyDescriptor):
(JSC::JSByteArray::put):

  • runtime/JSGlobalObjectFunctions.cpp:

(JSC::isInfinity):
(JSC::jsHexIntegerLiteral):
(JSC::jsStrDecimalLiteral):
(JSC::jsToNumber):
(JSC::parseFloat):

  • runtime/JSGlobalObjectFunctions.h:
  • runtime/JSString.cpp:

(JSC::JSString::getPrimitiveNumber):
(JSC::JSString::toNumber):
(JSC::JSString::getStringPropertyDescriptor):

  • runtime/JSString.h:

(JSC::JSString::getStringPropertySlot):

  • runtime/ObjectPrototype.cpp:

(JSC::ObjectPrototype::put):

  • runtime/StringObject.cpp:

(JSC::StringObject::deleteProperty):

  • runtime/UString.cpp:
  • runtime/UString.h:
  • wtf/dtoa.cpp:

(WTF::strtod):

WebCore:

These methods all implement JavaScript language specific behaviour, and as such
are not suited to being on a generic string object. They are also inefficient
and incorrectly used, refactor & cleanup. Uses of these methods really divide
out into two cases.

ToNumber:
Uses of toDouble from JSString and from parseFloat are implementing ecma's
ToNumber conversion from strings (see ecma-262 9.3.1), so UString::toDouble
should largely just be moved out to a global jsToNumber function. ToNumber is
capable of recognizing either decimal or hexadecimal numbers, but parseFloat
should only recognize decimal values. This is currently handled by testing for
hexadecimal before calling toDouble, which should unnecessary - instead we can
just split out the two parts to the grammar into separate functions. Also,
strtod recognizes a set of literals (nan, inf, and infinity - all with any
capitalization) - which are not defined by any of the specs we are implementing.
To handle this we need to perform additional work in toDouble to convert the
unsupported cases of infinities back to NaNs. Instead we should simply remove
support for this literals from strtod. This should provide a more desirable
behaviour for all clients of strtod.

Indexed properties:
Uses of the toStrictUInt32 methods are were all converting property names to
indices, and all uses of toUInt32 were incorrect; in all cases we should have
been calling toUInt32. This error results in some incorrect behaviour in the
DOM (accessing property "0 " of a NodeList should fail; it currently does not).
Move this method onto Identifier (our canonical property name), and make it
always perform a strict conversion. Add a layout test to check NodeList does
convert indexed property names correctly.

Test: fast/dom/NodeList/nodelist-item-with-index.html

  • WebCore.xcodeproj/project.pbxproj:
  • bindings/js/JSDOMWindowCustom.cpp:

(WebCore::JSDOMWindow::getOwnPropertySlot):
(WebCore::JSDOMWindow::getOwnPropertyDescriptor):

  • bindings/js/JSHTMLAllCollectionCustom.cpp:

(WebCore::callHTMLAllCollection):
(WebCore::JSHTMLAllCollection::item):

  • bindings/js/JSHTMLCollectionCustom.cpp:

(WebCore::callHTMLCollection):
(WebCore::JSHTMLCollection::item):

  • bindings/js/JSNodeListCustom.cpp:

(WebCore::callNodeList):

  • bindings/scripts/CodeGeneratorJS.pm:
  • bridge/runtime_array.cpp:

(JSC::RuntimeArray::getOwnPropertySlot):
(JSC::RuntimeArray::getOwnPropertyDescriptor):
(JSC::RuntimeArray::put):

LayoutTests:

Test that indexing into nodelists works correctly, particularly
wrt indices passed as strings that contain whitespace.

  • fast/dom/NodeList/nodelist-item-with-index-expected.txt: Added.
  • fast/dom/NodeList/nodelist-item-with-index.html: Added.
  • Property svn:eol-style set to native
File size: 9.3 KB
Line 
1/*
2 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 *
19 */
20
21#include "config.h"
22#include "Identifier.h"
23
24#include "CallFrame.h"
25#include "NumericStrings.h"
26#include <new> // for placement new
27#include <string.h> // for strlen
28#include <wtf/Assertions.h>
29#include <wtf/FastMalloc.h>
30#include <wtf/HashSet.h>
31#include <wtf/WTFThreadData.h>
32#include <wtf/text/StringHash.h>
33
34using WTF::ThreadSpecific;
35
36namespace JSC {
37
38IdentifierTable::~IdentifierTable()
39{
40 HashSet<StringImpl*>::iterator end = m_table.end();
41 for (HashSet<StringImpl*>::iterator iter = m_table.begin(); iter != end; ++iter)
42 (*iter)->setIsIdentifier(false);
43}
44std::pair<HashSet<StringImpl*>::iterator, bool> IdentifierTable::add(StringImpl* value)
45{
46 std::pair<HashSet<StringImpl*>::iterator, bool> result = m_table.add(value);
47 (*result.first)->setIsIdentifier(true);
48 return result;
49}
50template<typename U, typename V>
51std::pair<HashSet<StringImpl*>::iterator, bool> IdentifierTable::add(U value)
52{
53 std::pair<HashSet<StringImpl*>::iterator, bool> result = m_table.add<U, V>(value);
54 (*result.first)->setIsIdentifier(true);
55 return result;
56}
57
58IdentifierTable* createIdentifierTable()
59{
60 return new IdentifierTable;
61}
62
63void deleteIdentifierTable(IdentifierTable* table)
64{
65 delete table;
66}
67
68bool Identifier::equal(const StringImpl* r, const char* s)
69{
70 int length = r->length();
71 const UChar* d = r->characters();
72 for (int i = 0; i != length; ++i)
73 if (d[i] != (unsigned char)s[i])
74 return false;
75 return s[length] == 0;
76}
77
78bool Identifier::equal(const StringImpl* r, const UChar* s, unsigned length)
79{
80 if (r->length() != length)
81 return false;
82 const UChar* d = r->characters();
83 for (unsigned i = 0; i != length; ++i)
84 if (d[i] != s[i])
85 return false;
86 return true;
87}
88
89struct IdentifierCStringTranslator {
90 static unsigned hash(const char* c)
91 {
92 return StringImpl::computeHash(c);
93 }
94
95 static bool equal(StringImpl* r, const char* s)
96 {
97 return Identifier::equal(r, s);
98 }
99
100 static void translate(StringImpl*& location, const char* c, unsigned hash)
101 {
102 size_t length = strlen(c);
103 UChar* d;
104 StringImpl* r = StringImpl::createUninitialized(length, d).releaseRef();
105 for (size_t i = 0; i != length; i++)
106 d[i] = static_cast<unsigned char>(c[i]); // use unsigned char to zero-extend instead of sign-extend
107 r->setHash(hash);
108 location = r;
109 }
110};
111
112PassRefPtr<StringImpl> Identifier::add(JSGlobalData* globalData, const char* c)
113{
114 if (!c)
115 return 0;
116 if (!c[0])
117 return StringImpl::empty();
118 if (!c[1])
119 return add(globalData, globalData->smallStrings.singleCharacterStringRep(static_cast<unsigned char>(c[0])));
120
121 IdentifierTable& identifierTable = *globalData->identifierTable;
122 LiteralIdentifierTable& literalIdentifierTable = identifierTable.literalTable();
123
124 const LiteralIdentifierTable::iterator& iter = literalIdentifierTable.find(c);
125 if (iter != literalIdentifierTable.end())
126 return iter->second;
127
128 pair<HashSet<StringImpl*>::iterator, bool> addResult = identifierTable.add<const char*, IdentifierCStringTranslator>(c);
129
130 // If the string is newly-translated, then we need to adopt it.
131 // The boolean in the pair tells us if that is so.
132 RefPtr<StringImpl> addedString = addResult.second ? adoptRef(*addResult.first) : *addResult.first;
133
134 literalIdentifierTable.add(c, addedString.get());
135
136 return addedString.release();
137}
138
139PassRefPtr<StringImpl> Identifier::add(ExecState* exec, const char* c)
140{
141 return add(&exec->globalData(), c);
142}
143
144struct UCharBuffer {
145 const UChar* s;
146 unsigned int length;
147};
148
149struct IdentifierUCharBufferTranslator {
150 static unsigned hash(const UCharBuffer& buf)
151 {
152 return StringImpl::computeHash(buf.s, buf.length);
153 }
154
155 static bool equal(StringImpl* str, const UCharBuffer& buf)
156 {
157 return Identifier::equal(str, buf.s, buf.length);
158 }
159
160 static void translate(StringImpl*& location, const UCharBuffer& buf, unsigned hash)
161 {
162 UChar* d;
163 StringImpl* r = StringImpl::createUninitialized(buf.length, d).releaseRef();
164 for (unsigned i = 0; i != buf.length; i++)
165 d[i] = buf.s[i];
166 r->setHash(hash);
167 location = r;
168 }
169};
170
171uint32_t Identifier::toUInt32(const UString& string, bool& ok)
172{
173 ok = false;
174
175 unsigned length = string.length();
176 const UChar* characters = string.characters();
177
178 // An empty string is not a number.
179 if (!length)
180 return 0;
181
182 // Get the first character, turning it into a digit.
183 uint32_t value = characters[0] - '0';
184 if (value > 9)
185 return 0;
186
187 // Check for leading zeros. If the first characher is 0, then the
188 // length of the string must be one - e.g. "042" is not equal to "42".
189 if (!value && length > 1)
190 return 0;
191
192 while (--length) {
193 // Multiply value by 10, checking for overflow out of 32 bits.
194 if (value > 0xFFFFFFFFU / 10)
195 return 0;
196 value *= 10;
197
198 // Get the next character, turning it into a digit.
199 uint32_t newValue = *(++characters) - '0';
200 if (newValue > 9)
201 return 0;
202
203 // Add in the old value, checking for overflow out of 32 bits.
204 newValue += value;
205 if (newValue < value)
206 return 0;
207 value = newValue;
208 }
209
210 ok = true;
211 return value;
212}
213
214PassRefPtr<StringImpl> Identifier::add(JSGlobalData* globalData, const UChar* s, int length)
215{
216 if (length == 1) {
217 UChar c = s[0];
218 if (c <= 0xFF)
219 return add(globalData, globalData->smallStrings.singleCharacterStringRep(c));
220 }
221 if (!length)
222 return StringImpl::empty();
223 UCharBuffer buf = {s, length};
224 pair<HashSet<StringImpl*>::iterator, bool> addResult = globalData->identifierTable->add<UCharBuffer, IdentifierUCharBufferTranslator>(buf);
225
226 // If the string is newly-translated, then we need to adopt it.
227 // The boolean in the pair tells us if that is so.
228 return addResult.second ? adoptRef(*addResult.first) : *addResult.first;
229}
230
231PassRefPtr<StringImpl> Identifier::add(ExecState* exec, const UChar* s, int length)
232{
233 return add(&exec->globalData(), s, length);
234}
235
236PassRefPtr<StringImpl> Identifier::addSlowCase(JSGlobalData* globalData, StringImpl* r)
237{
238 ASSERT(!r->isIdentifier());
239 // The empty & null strings are static singletons, and static strings are handled
240 // in ::add() in the header, so we should never get here with a zero length string.
241 ASSERT(r->length());
242
243 if (r->length() == 1) {
244 UChar c = r->characters()[0];
245 if (c <= 0xFF)
246 r = globalData->smallStrings.singleCharacterStringRep(c);
247 if (r->isIdentifier())
248 return r;
249 }
250
251 return *globalData->identifierTable->add(r).first;
252}
253
254PassRefPtr<StringImpl> Identifier::addSlowCase(ExecState* exec, StringImpl* r)
255{
256 return addSlowCase(&exec->globalData(), r);
257}
258
259Identifier Identifier::from(ExecState* exec, unsigned value)
260{
261 return Identifier(exec, exec->globalData().numericStrings.add(value));
262}
263
264Identifier Identifier::from(ExecState* exec, int value)
265{
266 return Identifier(exec, exec->globalData().numericStrings.add(value));
267}
268
269Identifier Identifier::from(ExecState* exec, double value)
270{
271 return Identifier(exec, exec->globalData().numericStrings.add(value));
272}
273
274Identifier Identifier::from(JSGlobalData* globalData, unsigned value)
275{
276 return Identifier(globalData, globalData->numericStrings.add(value));
277}
278
279Identifier Identifier::from(JSGlobalData* globalData, int value)
280{
281 return Identifier(globalData, globalData->numericStrings.add(value));
282}
283
284Identifier Identifier::from(JSGlobalData* globalData, double value)
285{
286 return Identifier(globalData, globalData->numericStrings.add(value));
287}
288
289#ifndef NDEBUG
290
291void Identifier::checkCurrentIdentifierTable(JSGlobalData* globalData)
292{
293 // Check the identifier table accessible through the threadspecific matches the
294 // globalData's identifier table.
295 ASSERT_UNUSED(globalData, globalData->identifierTable == wtfThreadData().currentIdentifierTable());
296}
297
298void Identifier::checkCurrentIdentifierTable(ExecState* exec)
299{
300 checkCurrentIdentifierTable(&exec->globalData());
301}
302
303#else
304
305// These only exists so that our exports are the same for debug and release builds.
306// This would be an ASSERT_NOT_REACHED(), but we're in NDEBUG only code here!
307void Identifier::checkCurrentIdentifierTable(JSGlobalData*) { CRASH(); }
308void Identifier::checkCurrentIdentifierTable(ExecState*) { CRASH(); }
309
310#endif
311
312} // namespace JSC
Note: See TracBrowser for help on using the repository browser.