1 | /* |
2 | * Copyright (C) 1999-2001 Harri Porten ([email protected]) |
3 | * Copyright (C) 2001 Peter Kelly ([email protected]) |
4 | * Copyright (C) 2003-2019 Apple Inc. All rights reserved. |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Library General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Library General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Library General Public License |
17 | * along with this library; see the file COPYING.LIB. If not, write to |
18 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
19 | * Boston, MA 02110-1301, USA. |
20 | * |
21 | */ |
22 | |
23 | #include "config.h" |
24 | #include "JSCJSValue.h" |
25 | |
26 | #include "BooleanConstructor.h" |
27 | #include "BooleanPrototype.h" |
28 | #include "CustomGetterSetter.h" |
29 | #include "Error.h" |
30 | #include "ExceptionHelpers.h" |
31 | #include "GetterSetter.h" |
32 | #include "JSBigInt.h" |
33 | #include "JSCInlines.h" |
34 | #include "JSFunction.h" |
35 | #include "JSGlobalObject.h" |
36 | #include "NumberObject.h" |
37 | #include <wtf/MathExtras.h> |
38 | |
39 | namespace JSC { |
40 | |
41 | // ECMA 9.4 |
42 | double JSValue::toInteger(JSGlobalObject* globalObject) const |
43 | { |
44 | if (isInt32()) |
45 | return asInt32(); |
46 | double d = toNumber(globalObject); |
47 | return std::isnan(d) ? 0.0 : trunc(d); |
48 | } |
49 | |
50 | double JSValue::toIntegerPreserveNaN(JSGlobalObject* globalObject) const |
51 | { |
52 | if (isInt32()) |
53 | return asInt32(); |
54 | return trunc(toNumber(globalObject)); |
55 | } |
56 | |
57 | double JSValue::toLength(JSGlobalObject* globalObject) const |
58 | { |
59 | // ECMA 7.1.15 |
60 | // http://www.ecma-international.org/ecma-262/6.0/#sec-tolength |
61 | double d = toInteger(globalObject); |
62 | if (d <= 0) |
63 | return 0.0; |
64 | if (std::isinf(d)) |
65 | return maxSafeInteger(); |
66 | return std::min(d, maxSafeInteger()); |
67 | } |
68 | |
69 | double JSValue::toNumberSlowCase(JSGlobalObject* globalObject) const |
70 | { |
71 | ASSERT(!isInt32() && !isDouble()); |
72 | if (isCell()) |
73 | return asCell()->toNumber(globalObject); |
74 | if (isTrue()) |
75 | return 1.0; |
76 | return isUndefined() ? PNaN : 0; // null and false both convert to 0. |
77 | } |
78 | |
79 | Optional<double> JSValue::toNumberFromPrimitive() const |
80 | { |
81 | if (isEmpty()) |
82 | return WTF::nullopt; |
83 | if (isNumber()) |
84 | return asNumber(); |
85 | if (isBoolean()) |
86 | return asBoolean(); |
87 | if (isUndefined()) |
88 | return PNaN; |
89 | if (isNull()) |
90 | return 0; |
91 | return WTF::nullopt; |
92 | } |
93 | |
94 | JSObject* JSValue::toObjectSlowCase(JSGlobalObject* globalObject) const |
95 | { |
96 | VM& vm = globalObject->vm(); |
97 | auto scope = DECLARE_THROW_SCOPE(vm); |
98 | ASSERT(!isCell()); |
99 | |
100 | if (isInt32() || isDouble()) |
101 | return constructNumber(globalObject, asValue()); |
102 | if (isTrue() || isFalse()) |
103 | return constructBooleanFromImmediateBoolean(globalObject, asValue()); |
104 | |
105 | ASSERT(isUndefinedOrNull()); |
106 | throwException(globalObject, scope, createNotAnObjectError(globalObject, *this)); |
107 | return nullptr; |
108 | } |
109 | |
110 | JSValue JSValue::toThisSlowCase(JSGlobalObject* globalObject, ECMAMode ecmaMode) const |
111 | { |
112 | ASSERT(!isCell()); |
113 | |
114 | if (ecmaMode == StrictMode) |
115 | return *this; |
116 | |
117 | if (isInt32() || isDouble()) |
118 | return constructNumber(globalObject, asValue()); |
119 | if (isTrue() || isFalse()) |
120 | return constructBooleanFromImmediateBoolean(globalObject, asValue()); |
121 | ASSERT(isUndefinedOrNull()); |
122 | return globalObject->globalThis(); |
123 | } |
124 | |
125 | JSObject* JSValue::synthesizePrototype(JSGlobalObject* globalObject) const |
126 | { |
127 | VM& vm = globalObject->vm(); |
128 | auto scope = DECLARE_THROW_SCOPE(vm); |
129 | |
130 | if (isCell()) { |
131 | if (isString()) |
132 | return globalObject->stringPrototype(); |
133 | if (isBigInt()) |
134 | return globalObject->bigIntPrototype(); |
135 | ASSERT(isSymbol()); |
136 | return globalObject->symbolPrototype(); |
137 | } |
138 | |
139 | if (isNumber()) |
140 | return globalObject->numberPrototype(); |
141 | if (isBoolean()) |
142 | return globalObject->booleanPrototype(); |
143 | |
144 | ASSERT(isUndefinedOrNull()); |
145 | throwException(globalObject, scope, createNotAnObjectError(globalObject, *this)); |
146 | return nullptr; |
147 | } |
148 | |
149 | // ECMA 8.7.2 |
150 | bool JSValue::putToPrimitive(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot) |
151 | { |
152 | VM& vm = globalObject->vm(); |
153 | auto scope = DECLARE_THROW_SCOPE(vm); |
154 | |
155 | if (Optional<uint32_t> index = parseIndex(propertyName)) |
156 | RELEASE_AND_RETURN(scope, putToPrimitiveByIndex(globalObject, index.value(), value, slot.isStrictMode())); |
157 | |
158 | // Check if there are any setters or getters in the prototype chain |
159 | JSObject* obj = synthesizePrototype(globalObject); |
160 | EXCEPTION_ASSERT(!!scope.exception() == !obj); |
161 | if (UNLIKELY(!obj)) |
162 | return false; |
163 | JSValue prototype; |
164 | if (propertyName != vm.propertyNames->underscoreProto) { |
165 | while (true) { |
166 | Structure* structure = obj->structure(vm); |
167 | if (structure->hasReadOnlyOrGetterSetterPropertiesExcludingProto() || structure->typeInfo().hasPutPropertySecurityCheck()) |
168 | break; |
169 | prototype = obj->getPrototype(vm, globalObject); |
170 | RETURN_IF_EXCEPTION(scope, false); |
171 | |
172 | if (prototype.isNull()) |
173 | return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError); |
174 | obj = asObject(prototype); |
175 | } |
176 | } |
177 | |
178 | for (; ; obj = asObject(prototype)) { |
179 | Structure* structure = obj->structure(vm); |
180 | if (UNLIKELY(structure->typeInfo().hasPutPropertySecurityCheck())) { |
181 | obj->methodTable(vm)->doPutPropertySecurityCheck(obj, globalObject, propertyName, slot); |
182 | RETURN_IF_EXCEPTION(scope, false); |
183 | } |
184 | unsigned attributes; |
185 | PropertyOffset offset = structure->get(vm, propertyName, attributes); |
186 | if (offset != invalidOffset) { |
187 | if (attributes & PropertyAttribute::ReadOnly) |
188 | return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError); |
189 | |
190 | JSValue gs = obj->getDirect(offset); |
191 | if (gs.isGetterSetter()) |
192 | RELEASE_AND_RETURN(scope, callSetter(globalObject, *this, gs, value, slot.isStrictMode() ? StrictMode : NotStrictMode)); |
193 | |
194 | if (gs.isCustomGetterSetter()) |
195 | return callCustomSetter(globalObject, gs, attributes & PropertyAttribute::CustomAccessor, obj, slot.thisValue(), value); |
196 | |
197 | // If there's an existing property on the object or one of its |
198 | // prototypes it should be replaced, so break here. |
199 | break; |
200 | } |
201 | |
202 | prototype = obj->getPrototype(vm, globalObject); |
203 | RETURN_IF_EXCEPTION(scope, false); |
204 | if (prototype.isNull()) |
205 | break; |
206 | } |
207 | |
208 | return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError); |
209 | } |
210 | |
211 | bool JSValue::putToPrimitiveByIndex(JSGlobalObject* globalObject, unsigned propertyName, JSValue value, bool shouldThrow) |
212 | { |
213 | VM& vm = globalObject->vm(); |
214 | auto scope = DECLARE_THROW_SCOPE(vm); |
215 | |
216 | if (propertyName > MAX_ARRAY_INDEX) { |
217 | PutPropertySlot slot(*this, shouldThrow); |
218 | return putToPrimitive(globalObject, Identifier::from(vm, propertyName), value, slot); |
219 | } |
220 | |
221 | JSObject* prototype = synthesizePrototype(globalObject); |
222 | EXCEPTION_ASSERT(!!scope.exception() == !prototype); |
223 | if (UNLIKELY(!prototype)) |
224 | return false; |
225 | bool putResult = false; |
226 | bool success = prototype->attemptToInterceptPutByIndexOnHoleForPrototype(globalObject, *this, propertyName, value, shouldThrow, putResult); |
227 | RETURN_IF_EXCEPTION(scope, false); |
228 | if (success) |
229 | return putResult; |
230 | |
231 | return typeError(globalObject, scope, shouldThrow, ReadonlyPropertyWriteError); |
232 | } |
233 | |
234 | void JSValue::dump(PrintStream& out) const |
235 | { |
236 | dumpInContext(out, 0); |
237 | } |
238 | |
239 | void JSValue::dumpInContext(PrintStream& out, DumpContext* context) const |
240 | { |
241 | dumpInContextAssumingStructure( |
242 | out, context, (!!*this && isCell()) ? asCell()->structure() : nullptr); |
243 | } |
244 | |
245 | void JSValue::dumpInContextAssumingStructure( |
246 | PrintStream& out, DumpContext* context, Structure* structure) const |
247 | { |
248 | if (!*this) |
249 | out.print("<JSValue()>" ); |
250 | else if (isInt32()) |
251 | out.printf("Int32: %d" , asInt32()); |
252 | else if (isDouble()) { |
253 | #if USE(JSVALUE64) |
254 | out.printf("Double: %lld, %lf" , (long long)reinterpretDoubleToInt64(asDouble()), asDouble()); |
255 | #else |
256 | union { |
257 | double asDouble; |
258 | uint32_t asTwoInt32s[2]; |
259 | } u; |
260 | u.asDouble = asDouble(); |
261 | out.printf("Double: %08x:%08x, %lf" , u.asTwoInt32s[1], u.asTwoInt32s[0], asDouble()); |
262 | #endif |
263 | } else if (isCell()) { |
264 | if (structure->classInfo()->isSubClassOf(JSString::info())) { |
265 | JSString* string = asString(asCell()); |
266 | out.print("String" ); |
267 | if (string->isRope()) |
268 | out.print(" (rope)" ); |
269 | const StringImpl* impl = string->tryGetValueImpl(); |
270 | if (impl) { |
271 | if (impl->isAtom()) |
272 | out.print(" (atomic)" ); |
273 | if (impl->isSymbol()) |
274 | out.print(" (symbol)" ); |
275 | } else |
276 | out.print(" (unresolved)" ); |
277 | out.print(": " , impl); |
278 | } else if (structure->classInfo()->isSubClassOf(RegExp::info())) |
279 | out.print("RegExp: " , *jsCast<RegExp*>(asCell())); |
280 | else if (structure->classInfo()->isSubClassOf(Symbol::info())) |
281 | out.print("Symbol: " , RawPointer(asCell())); |
282 | else if (structure->classInfo()->isSubClassOf(Structure::info())) |
283 | out.print("Structure: " , inContext(*jsCast<Structure*>(asCell()), context)); |
284 | else if (structure->classInfo()->isSubClassOf(JSObject::info())) { |
285 | out.print("Object: " , RawPointer(asCell())); |
286 | out.print(" with butterfly " , RawPointer(asObject(asCell())->butterfly())); |
287 | out.print(" (Structure " , inContext(*structure, context), ")" ); |
288 | } else { |
289 | out.print("Cell: " , RawPointer(asCell())); |
290 | out.print(" (" , inContext(*structure, context), ")" ); |
291 | } |
292 | #if USE(JSVALUE64) |
293 | out.print(", StructureID: " , asCell()->structureID()); |
294 | #endif |
295 | } else if (isTrue()) |
296 | out.print("True" ); |
297 | else if (isFalse()) |
298 | out.print("False" ); |
299 | else if (isNull()) |
300 | out.print("Null" ); |
301 | else if (isUndefined()) |
302 | out.print("Undefined" ); |
303 | else |
304 | out.print("INVALID" ); |
305 | } |
306 | |
307 | void JSValue::dumpForBacktrace(PrintStream& out) const |
308 | { |
309 | if (!*this) |
310 | out.print("<JSValue()>" ); |
311 | else if (isInt32()) |
312 | out.printf("%d" , asInt32()); |
313 | else if (isDouble()) |
314 | out.printf("%lf" , asDouble()); |
315 | else if (isCell()) { |
316 | VM& vm = asCell()->vm(); |
317 | if (asCell()->inherits<JSString>(vm)) { |
318 | JSString* string = asString(asCell()); |
319 | const StringImpl* impl = string->tryGetValueImpl(); |
320 | if (impl) |
321 | out.print("\"" , impl, "\"" ); |
322 | else |
323 | out.print("(unresolved string)" ); |
324 | } else if (asCell()->inherits<Structure>(vm)) { |
325 | out.print("Structure[ " , asCell()->structure()->classInfo()->className); |
326 | #if USE(JSVALUE64) |
327 | out.print(" ID: " , asCell()->structureID()); |
328 | #endif |
329 | out.print("]: " , RawPointer(asCell())); |
330 | } else { |
331 | out.print("Cell[" , asCell()->structure()->classInfo()->className); |
332 | #if USE(JSVALUE64) |
333 | out.print(" ID: " , asCell()->structureID()); |
334 | #endif |
335 | out.print("]: " , RawPointer(asCell())); |
336 | } |
337 | } else if (isTrue()) |
338 | out.print("True" ); |
339 | else if (isFalse()) |
340 | out.print("False" ); |
341 | else if (isNull()) |
342 | out.print("Null" ); |
343 | else if (isUndefined()) |
344 | out.print("Undefined" ); |
345 | else |
346 | out.print("INVALID" ); |
347 | } |
348 | |
349 | bool JSValue::isValidCallee() |
350 | { |
351 | return asObject(asCell())->globalObject(); |
352 | } |
353 | |
354 | JSString* JSValue::toStringSlowCase(JSGlobalObject* globalObject, bool returnEmptyStringOnError) const |
355 | { |
356 | VM& vm = globalObject->vm(); |
357 | auto scope = DECLARE_THROW_SCOPE(vm); |
358 | |
359 | auto errorValue = [&] () -> JSString* { |
360 | if (returnEmptyStringOnError) |
361 | return jsEmptyString(vm); |
362 | return nullptr; |
363 | }; |
364 | |
365 | ASSERT(!isString()); |
366 | if (isInt32()) { |
367 | auto integer = asInt32(); |
368 | if (static_cast<unsigned>(integer) <= 9) |
369 | return vm.smallStrings.singleCharacterString(integer + '0'); |
370 | return jsNontrivialString(vm, vm.numericStrings.add(integer)); |
371 | } |
372 | if (isDouble()) |
373 | return jsString(vm, vm.numericStrings.add(asDouble())); |
374 | if (isTrue()) |
375 | return vm.smallStrings.trueString(); |
376 | if (isFalse()) |
377 | return vm.smallStrings.falseString(); |
378 | if (isNull()) |
379 | return vm.smallStrings.nullString(); |
380 | if (isUndefined()) |
381 | return vm.smallStrings.undefinedString(); |
382 | if (isSymbol()) { |
383 | throwTypeError(globalObject, scope, "Cannot convert a symbol to a string"_s ); |
384 | return errorValue(); |
385 | } |
386 | if (isBigInt()) { |
387 | JSBigInt* bigInt = asBigInt(*this); |
388 | if (auto digit = bigInt->singleDigitValueForString()) |
389 | return vm.smallStrings.singleCharacterString(*digit + '0'); |
390 | JSString* returnString = jsNontrivialString(vm, bigInt->toString(globalObject, 10)); |
391 | RETURN_IF_EXCEPTION(scope, errorValue()); |
392 | return returnString; |
393 | } |
394 | |
395 | ASSERT(isCell()); |
396 | JSValue value = asCell()->toPrimitive(globalObject, PreferString); |
397 | RETURN_IF_EXCEPTION(scope, errorValue()); |
398 | ASSERT(!value.isObject()); |
399 | JSString* result = value.toString(globalObject); |
400 | RETURN_IF_EXCEPTION(scope, errorValue()); |
401 | return result; |
402 | } |
403 | |
404 | String JSValue::toWTFStringSlowCase(JSGlobalObject* globalObject) const |
405 | { |
406 | VM& vm = globalObject->vm(); |
407 | if (isInt32()) |
408 | return vm.numericStrings.add(asInt32()); |
409 | if (isDouble()) |
410 | return vm.numericStrings.add(asDouble()); |
411 | if (isTrue()) |
412 | return vm.propertyNames->trueKeyword.string(); |
413 | if (isFalse()) |
414 | return vm.propertyNames->falseKeyword.string(); |
415 | if (isNull()) |
416 | return vm.propertyNames->nullKeyword.string(); |
417 | if (isUndefined()) |
418 | return vm.propertyNames->undefinedKeyword.string(); |
419 | return toString(globalObject)->value(globalObject); |
420 | } |
421 | |
422 | } // namespace JSC |
423 | |