1/*
2 * Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
3 * Copyright (C) 2001 Peter Kelly (pmk@post.com)
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
39namespace JSC {
40
41// ECMA 9.4
42double 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
50double JSValue::toIntegerPreserveNaN(JSGlobalObject* globalObject) const
51{
52 if (isInt32())
53 return asInt32();
54 return trunc(toNumber(globalObject));
55}
56
57double 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
69double 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
79Optional<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
94JSObject* 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
110JSValue 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
125JSObject* 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
150bool 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
211bool 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
234void JSValue::dump(PrintStream& out) const
235{
236 dumpInContext(out, 0);
237}
238
239void JSValue::dumpInContext(PrintStream& out, DumpContext* context) const
240{
241 dumpInContextAssumingStructure(
242 out, context, (!!*this && isCell()) ? asCell()->structure() : nullptr);
243}
244
245void 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
307void 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
349bool JSValue::isValidCallee()
350{
351 return asObject(asCell())->globalObject();
352}
353
354JSString* 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
404String 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