/ runtime / JSCJSValue.cpp
JSCJSValue.cpp
  1  /*
  2   *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
  3   *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
  4   *  Copyright (C) 2003-2020 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 "BigIntObject.h"
 27  #include "BooleanConstructor.h"
 28  #include "CustomGetterSetter.h"
 29  #include "GetterSetter.h"
 30  #include "JSBigInt.h"
 31  #include "JSCInlines.h"
 32  #include "NumberObject.h"
 33  #include "TypeError.h"
 34  
 35  #ifdef DARLING
 36  #include "ParseInt.h"
 37  #endif
 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      VM& vm = globalObject->vm();
 72      auto scope = DECLARE_THROW_SCOPE(vm);
 73  
 74      ASSERT(!isInt32() && !isDouble());
 75      if (isCell())
 76          RELEASE_AND_RETURN(scope, asCell()->toNumber(globalObject));
 77  #if USE(BIGINT32)
 78      if (isBigInt32()) {
 79          throwTypeError(globalObject, scope, "Conversion from 'BigInt' to 'number' is not allowed."_s);
 80          return 0.0;
 81      }
 82  #endif
 83      if (isTrue())
 84          return 1.0;
 85      return isUndefined() ? PNaN : 0; // null and false both convert to 0.
 86  }
 87  
 88  Optional<double> JSValue::toNumberFromPrimitive() const
 89  {
 90      if (isEmpty())
 91          return WTF::nullopt;
 92      if (isNumber())
 93          return asNumber();
 94      if (isBoolean())
 95          return asBoolean();
 96      if (isUndefined())
 97          return PNaN;
 98      if (isNull())
 99          return 0;
100      return WTF::nullopt;
101  }
102  
103  // https://tc39.es/ecma262/#sec-tobigint
104  JSValue JSValue::toBigInt(JSGlobalObject* globalObject) const
105  {
106      VM& vm = globalObject->vm();
107      auto scope = DECLARE_THROW_SCOPE(vm);
108  
109      JSValue primitive = toPrimitive(globalObject);
110      RETURN_IF_EXCEPTION(scope, { });
111  
112      if (primitive.isBigInt())
113          return primitive;
114  
115      if (primitive.isBoolean()) {
116  #if USE(BIGINT32)
117          return jsBigInt32(primitive.asBoolean());
118  #else
119          RELEASE_AND_RETURN(scope, JSBigInt::createFrom(globalObject, primitive.asBoolean()));
120  #endif
121      }
122  
123      if (primitive.isString()) {
124          scope.release();
125          return toStringView(globalObject, primitive, [&] (StringView view) {
126              return JSBigInt::parseInt(globalObject, view);
127          });
128      }
129  
130      ASSERT(primitive.isUndefinedOrNull() || primitive.isNumber() || primitive.isSymbol());
131      throwTypeError(globalObject, scope, "Invalid argument type in ToBigInt operation"_s);
132      return jsUndefined();
133  }
134  
135  // https://tc39.es/ecma262/#sec-tobigint64
136  int64_t JSValue::toBigInt64(JSGlobalObject* globalObject) const
137  {
138      VM& vm = globalObject->vm();
139      auto scope = DECLARE_THROW_SCOPE(vm);
140  
141      JSValue value = toBigInt(globalObject);
142      RETURN_IF_EXCEPTION(scope, { });
143      return JSBigInt::toBigInt64(value);
144  }
145  
146  // https://tc39.es/ecma262/#sec-tobiguint64
147  uint64_t JSValue::toBigUInt64(JSGlobalObject* globalObject) const
148  {
149      VM& vm = globalObject->vm();
150      auto scope = DECLARE_THROW_SCOPE(vm);
151  
152      JSValue value = toBigInt(globalObject);
153      RETURN_IF_EXCEPTION(scope, { });
154      return JSBigInt::toBigUInt64(value);
155  }
156  
157  JSObject* JSValue::toObjectSlowCase(JSGlobalObject* globalObject) const
158  {
159      VM& vm = globalObject->vm();
160      auto scope = DECLARE_THROW_SCOPE(vm);
161      ASSERT(!isCell());
162  
163      if (isInt32() || isDouble())
164          return constructNumber(globalObject, asValue());
165      if (isTrue() || isFalse())
166          return constructBooleanFromImmediateBoolean(globalObject, asValue());
167  #if USE(BIGINT32)
168      if (isBigInt32())
169          return BigIntObject::create(vm, globalObject, *this);
170  #endif
171  
172      ASSERT(isUndefinedOrNull());
173      throwException(globalObject, scope, createNotAnObjectError(globalObject, *this));
174      return nullptr;
175  }
176  
177  JSValue JSValue::toThisSlowCase(JSGlobalObject* globalObject, ECMAMode ecmaMode) const
178  {
179      ASSERT(!isCell());
180  
181      if (ecmaMode.isStrict())
182          return *this;
183  
184      if (isInt32() || isDouble())
185          return constructNumber(globalObject, asValue());
186      if (isTrue() || isFalse())
187          return constructBooleanFromImmediateBoolean(globalObject, asValue());
188  #if USE(BIGINT32)
189      if (isBigInt32())
190          return BigIntObject::create(globalObject->vm(), globalObject, *this);
191  #endif
192  
193      ASSERT(isUndefinedOrNull());
194      return globalObject->globalThis();
195  }
196  
197  JSObject* JSValue::synthesizePrototype(JSGlobalObject* globalObject) const
198  {
199      VM& vm = globalObject->vm();
200      auto scope = DECLARE_THROW_SCOPE(vm);
201  
202      if (isCell()) {
203          if (isString())
204              return globalObject->stringPrototype();
205          if (isHeapBigInt())
206              return globalObject->bigIntPrototype();
207          ASSERT(isSymbol());
208          return globalObject->symbolPrototype();
209      }
210  
211      if (isNumber())
212          return globalObject->numberPrototype();
213      if (isBoolean())
214          return globalObject->booleanPrototype();
215  #if USE(BIGINT32)
216      if (isBigInt32())
217          return globalObject->bigIntPrototype();
218  #endif
219  
220      ASSERT(isUndefinedOrNull());
221      throwException(globalObject, scope, createNotAnObjectError(globalObject, *this));
222      return nullptr;
223  }
224  
225  // ECMA 8.7.2
226  bool JSValue::putToPrimitive(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
227  {
228      VM& vm = globalObject->vm();
229      auto scope = DECLARE_THROW_SCOPE(vm);
230  
231      if (Optional<uint32_t> index = parseIndex(propertyName))
232          RELEASE_AND_RETURN(scope, putToPrimitiveByIndex(globalObject, index.value(), value, slot.isStrictMode()));
233  
234      // Check if there are any setters or getters in the prototype chain
235      JSObject* obj = synthesizePrototype(globalObject);
236      EXCEPTION_ASSERT(!!scope.exception() == !obj);
237      if (UNLIKELY(!obj))
238          return false;
239      JSValue prototype;
240      if (propertyName != vm.propertyNames->underscoreProto) {
241          while (true) {
242              Structure* structure = obj->structure(vm);
243              if (structure->hasReadOnlyOrGetterSetterPropertiesExcludingProto() || structure->typeInfo().hasPutPropertySecurityCheck())
244                  break;
245              if (obj->type() == ProxyObjectType) {
246                  auto* proxy = jsCast<ProxyObject*>(obj);
247                  RELEASE_AND_RETURN(scope, proxy->ProxyObject::put(proxy, globalObject, propertyName, value, slot));
248              }
249              prototype = obj->getPrototype(vm, globalObject);
250              RETURN_IF_EXCEPTION(scope, false);
251  
252              if (prototype.isNull())
253                  return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
254              obj = asObject(prototype);
255          }
256      }
257  
258      for (; ; obj = asObject(prototype)) {
259          Structure* structure = obj->structure(vm);
260          if (UNLIKELY(structure->typeInfo().hasPutPropertySecurityCheck())) {
261              obj->methodTable(vm)->doPutPropertySecurityCheck(obj, globalObject, propertyName, slot);
262              RETURN_IF_EXCEPTION(scope, false);
263          }
264          unsigned attributes;
265          PropertyOffset offset = structure->get(vm, propertyName, attributes);
266          if (offset != invalidOffset) {
267              if (attributes & PropertyAttribute::ReadOnly)
268                  return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
269  
270              JSValue gs = obj->getDirect(offset);
271              if (gs.isGetterSetter())
272                  RELEASE_AND_RETURN(scope, callSetter(globalObject, *this, gs, value, slot.isStrictMode() ? ECMAMode::strict() : ECMAMode::sloppy()));
273  
274              if (gs.isCustomGetterSetter()) {
275                  auto setter = jsCast<CustomGetterSetter*>(gs.asCell())->setter();
276                  bool isAccessor = attributes & PropertyAttribute::CustomAccessor;
277                  auto result = callCustomSetter(globalObject, setter, isAccessor, obj, slot.thisValue(), value);
278                  if (result != TriState::Indeterminate)
279                      RELEASE_AND_RETURN(scope, result == TriState::True);
280              }
281  
282              // If there's an existing property on the object or one of its 
283              // prototypes it should be replaced, so break here.
284              break;
285          }
286          if (obj->type() == ProxyObjectType) {
287              auto* proxy = jsCast<ProxyObject*>(obj);
288              RELEASE_AND_RETURN(scope, proxy->ProxyObject::put(proxy, globalObject, propertyName, value, slot));
289          }
290          prototype = obj->getPrototype(vm, globalObject);
291          RETURN_IF_EXCEPTION(scope, false);
292          if (prototype.isNull())
293              break;
294      }
295      
296      return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
297  }
298  
299  bool JSValue::putToPrimitiveByIndex(JSGlobalObject* globalObject, unsigned propertyName, JSValue value, bool shouldThrow)
300  {
301      VM& vm = globalObject->vm();
302      auto scope = DECLARE_THROW_SCOPE(vm);
303  
304      if (propertyName > MAX_ARRAY_INDEX) {
305          PutPropertySlot slot(*this, shouldThrow);
306          return putToPrimitive(globalObject, Identifier::from(vm, propertyName), value, slot);
307      }
308      
309      JSObject* prototype = synthesizePrototype(globalObject);
310      EXCEPTION_ASSERT(!!scope.exception() == !prototype);
311      if (UNLIKELY(!prototype))
312          return false;
313      bool putResult = false;
314      bool success = prototype->attemptToInterceptPutByIndexOnHoleForPrototype(globalObject, *this, propertyName, value, shouldThrow, putResult);
315      RETURN_IF_EXCEPTION(scope, false);
316      if (success)
317          return putResult;
318      
319      return typeError(globalObject, scope, shouldThrow, ReadonlyPropertyWriteError);
320  }
321  
322  void JSValue::dump(PrintStream& out) const
323  {
324      dumpInContext(out, nullptr);
325  }
326  
327  void JSValue::dumpInContext(PrintStream& out, DumpContext* context) const
328  {
329      dumpInContextAssumingStructure(
330          out, context, (!!*this && isCell()) ? asCell()->structure() : nullptr);
331  }
332  
333  void JSValue::dumpInContextAssumingStructure(
334      PrintStream& out, DumpContext* context, Structure* structure) const
335  {
336      if (!*this)
337          out.print("<JSValue()>");
338      else if (isInt32())
339          out.printf("Int32: %d", asInt32());
340      else if (isDouble()) {
341  #if USE(JSVALUE64)
342          out.printf("Double: %lld, %lf", (long long)reinterpretDoubleToInt64(asDouble()), asDouble());
343  #else
344          union {
345              double asDouble;
346              uint32_t asTwoInt32s[2];
347          } u;
348          u.asDouble = asDouble();
349          out.printf("Double: %08x:%08x, %lf", u.asTwoInt32s[1], u.asTwoInt32s[0], asDouble());
350  #endif
351      } else if (isCell()) {
352          if (structure->classInfo()->isSubClassOf(JSString::info())) {
353              JSString* string = asString(asCell());
354              out.print("String");
355              if (string->isRope())
356                  out.print(" (rope)");
357              const StringImpl* impl = string->tryGetValueImpl();
358              if (impl) {
359                  if (impl->isAtom())
360                      out.print(" (atomic)");
361                  if (impl->isSymbol())
362                      out.print(" (symbol)");
363              } else
364                  out.print(" (unresolved)");
365              if (string->is8Bit())
366                  out.print(",8Bit:(1)");
367              else
368                  out.print(",8Bit:(0)");
369              out.print(",length:(", string->length(), ")");
370              out.print(": ", impl);
371          } else if (structure->classInfo()->isSubClassOf(RegExp::info()))
372              out.print("RegExp: ", *jsCast<RegExp*>(asCell()));
373          else if (structure->classInfo()->isSubClassOf(Symbol::info()))
374              out.print("Symbol: ", RawPointer(asCell()));
375          else if (structure->classInfo()->isSubClassOf(Structure::info()))
376              out.print("Structure: ", inContext(*jsCast<Structure*>(asCell()), context));
377          else if (isHeapBigInt())
378              out.print("BigInt[heap-allocated]: addr=", RawPointer(asCell()), ", length=", jsCast<JSBigInt*>(asCell())->length(), ", sign=", jsCast<JSBigInt*>(asCell())->sign());
379          else if (structure->classInfo()->isSubClassOf(JSObject::info())) {
380              out.print("Object: ", RawPointer(asCell()));
381              out.print(" with butterfly ", RawPointer(asObject(asCell())->butterfly()));
382              out.print(" (Structure ", inContext(*structure, context), ")");
383          } else {
384              out.print("Cell: ", RawPointer(asCell()));
385              out.print(" (", inContext(*structure, context), ")");
386          }
387  #if USE(JSVALUE64)
388          out.print(", StructureID: ", asCell()->structureID());
389  #endif
390      } else if (isTrue())
391          out.print("True");
392      else if (isFalse())
393          out.print("False");
394      else if (isNull())
395          out.print("Null");
396      else if (isUndefined())
397          out.print("Undefined");
398  #if USE(BIGINT32)
399      else if (isBigInt32())
400          out.printf("BigInt[inline]: %d", bigInt32AsInt32());
401  #endif
402      else
403          out.print("INVALID");
404  }
405  
406  void JSValue::dumpForBacktrace(PrintStream& out) const
407  {
408      if (!*this)
409          out.print("<JSValue()>");
410      else if (isInt32())
411          out.printf("%d", asInt32());
412      else if (isDouble())
413          out.printf("%lf", asDouble());
414      else if (isCell()) {
415          VM& vm = asCell()->vm();
416          if (asCell()->inherits<JSString>(vm)) {
417              JSString* string = asString(asCell());
418              const StringImpl* impl = string->tryGetValueImpl();
419              if (impl)
420                  out.print("\"", impl, "\"");
421              else
422                  out.print("(unresolved string)");
423          } else if (asCell()->inherits<Structure>(vm)) {
424              out.print("Structure[ ", asCell()->structure()->classInfo()->className);
425  #if USE(JSVALUE64)
426              out.print(" ID: ", asCell()->structureID());
427  #endif
428              out.print("]: ", RawPointer(asCell()));
429          } else {
430              out.print("Cell[", asCell()->structure()->classInfo()->className);
431  #if USE(JSVALUE64)
432              out.print(" ID: ", asCell()->structureID());
433  #endif
434              out.print("]: ", RawPointer(asCell()));
435          }
436      } else if (isTrue())
437          out.print("True");
438      else if (isFalse())
439          out.print("False");
440      else if (isNull())
441          out.print("Null");
442      else if (isUndefined())
443          out.print("Undefined");
444  #if USE(BIGINT32)
445      else if (isBigInt32())
446          out.printf("BigInt[inline]: %d", bigInt32AsInt32());
447  #endif
448      else
449          out.print("INVALID");
450  }
451  
452  bool JSValue::isValidCallee()
453  {
454      return asObject(asCell())->globalObject();
455  }
456  
457  JSString* JSValue::toStringSlowCase(JSGlobalObject* globalObject, bool returnEmptyStringOnError) const
458  {
459      VM& vm = globalObject->vm();
460      auto scope = DECLARE_THROW_SCOPE(vm);
461  
462      auto errorValue = [&] () -> JSString* {
463          if (returnEmptyStringOnError)
464              return jsEmptyString(vm);
465          return nullptr;
466      };
467      
468      ASSERT(!isString());
469      if (isInt32()) {
470          auto integer = asInt32();
471          if (static_cast<unsigned>(integer) <= 9)
472              return vm.smallStrings.singleCharacterString(integer + '0');
473          return jsNontrivialString(vm, vm.numericStrings.add(integer));
474      }
475      if (isDouble())
476          return jsString(vm, vm.numericStrings.add(asDouble()));
477      if (isTrue())
478          return vm.smallStrings.trueString();
479      if (isFalse())
480          return vm.smallStrings.falseString();
481      if (isNull())
482          return vm.smallStrings.nullString();
483      if (isUndefined())
484          return vm.smallStrings.undefinedString();
485  #if USE(BIGINT32)
486      if (isBigInt32()) {
487          auto integer = bigInt32AsInt32();
488          if (static_cast<unsigned>(integer) <= 9)
489              return vm.smallStrings.singleCharacterString(integer + '0');
490          return jsNontrivialString(vm, vm.numericStrings.add(integer));
491      }
492  #endif
493      if (isHeapBigInt()) {
494          JSBigInt* bigInt = asHeapBigInt();
495          // FIXME: we should rather have two cases here: one-character string vs jsNonTrivialString for everything else.
496          auto string = bigInt->toString(globalObject, 10);
497          RETURN_IF_EXCEPTION(scope, errorValue());
498          JSString* returnString = JSString::create(vm, string.releaseImpl().releaseNonNull());
499          RETURN_IF_EXCEPTION(scope, errorValue());
500          return returnString;
501      }
502      if (isSymbol()) {
503          throwTypeError(globalObject, scope, "Cannot convert a symbol to a string"_s);
504          return errorValue();
505      }
506  
507      ASSERT(isCell());
508      JSValue value = asCell()->toPrimitive(globalObject, PreferString);
509      RETURN_IF_EXCEPTION(scope, errorValue());
510      ASSERT(!value.isObject());
511      JSString* result = value.toString(globalObject);
512      RETURN_IF_EXCEPTION(scope, errorValue());
513      return result;
514  }
515  
516  String JSValue::toWTFStringSlowCase(JSGlobalObject* globalObject) const
517  {
518      VM& vm = globalObject->vm();
519      auto scope = DECLARE_THROW_SCOPE(vm);
520      if (isInt32())
521          return vm.numericStrings.add(asInt32());
522      if (isDouble())
523          return vm.numericStrings.add(asDouble());
524      if (isTrue())
525          return vm.propertyNames->trueKeyword.string();
526      if (isFalse())
527          return vm.propertyNames->falseKeyword.string();
528      if (isNull())
529          return vm.propertyNames->nullKeyword.string();
530      if (isUndefined())
531          return vm.propertyNames->undefinedKeyword.string();
532      JSString* string = toString(globalObject);
533      RETURN_IF_EXCEPTION(scope, { });
534      RELEASE_AND_RETURN(scope, string->value(globalObject));
535  }
536  
537  #if !COMPILER(GCC_COMPATIBLE)
538  // This makes the argument opaque from the compiler.
539  NEVER_INLINE void ensureStillAliveHere(JSValue)
540  {
541  }
542  #endif
543  
544  } // namespace JSC