diff options
Diffstat (limited to 'Source/JavaScriptCore/runtime/JSCJSValueInlines.h')
-rw-r--r-- | Source/JavaScriptCore/runtime/JSCJSValueInlines.h | 380 |
1 files changed, 312 insertions, 68 deletions
diff --git a/Source/JavaScriptCore/runtime/JSCJSValueInlines.h b/Source/JavaScriptCore/runtime/JSCJSValueInlines.h index b34adf809..5bcb995b4 100644 --- a/Source/JavaScriptCore/runtime/JSCJSValueInlines.h +++ b/Source/JavaScriptCore/runtime/JSCJSValueInlines.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, 2012 Apple Inc. All rights reserved. + * Copyright (C) 2011-2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -23,13 +23,19 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef JSValueInlines_h -#define JSValueInlines_h +#pragma once +#include "Error.h" +#include "ExceptionHelpers.h" +#include "Identifier.h" #include "InternalFunction.h" #include "JSCJSValue.h" #include "JSCellInlines.h" #include "JSFunction.h" +#include "JSObject.h" +#include "JSStringInlines.h" +#include "MathCommon.h" +#include <wtf/text/StringImpl.h> namespace JSC { @@ -42,10 +48,31 @@ ALWAYS_INLINE int32_t JSValue::toInt32(ExecState* exec) const inline uint32_t JSValue::toUInt32(ExecState* exec) const { - // See comment on JSC::toUInt32, above. + // See comment on JSC::toUInt32, in JSCJSValue.h. return toInt32(exec); } +inline uint32_t JSValue::toIndex(ExecState* exec, const char* errorName) const +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + double d = toNumber(exec); + + if (d <= -1) { + throwException(exec, scope, createRangeError(exec, makeString(errorName, " cannot be negative"))); + return 0; + } + if (d > std::numeric_limits<unsigned>::max()) { + throwException(exec, scope, createRangeError(exec, makeString(errorName, " too large"))); + return 0; + } + + if (isInt32()) + return asInt32(); + return JSC::toInt32(d); +} + inline bool JSValue::isUInt32() const { return isInt32() && asInt32() >= 0; @@ -65,7 +92,7 @@ inline double JSValue::asNumber() const inline JSValue jsNaN() { - return JSValue(QNaN); + return JSValue(JSValue::EncodeAsDouble, PNaN); } inline JSValue::JSValue(char i) @@ -135,6 +162,7 @@ inline JSValue::JSValue(unsigned long long i) inline JSValue::JSValue(double d) { + // Note: while this behavior is undefined for NaN and inf, the subsequent statement will catch these cases. const int32_t asInt32 = static_cast<int32_t>(d); if (asInt32 != d || (!asInt32 && std::signbit(d))) { // true for -0.0 *this = JSValue(EncodeAsDouble, d); @@ -210,10 +238,10 @@ inline JSValue::JSValue(const JSCell* ptr) u.asBits.payload = reinterpret_cast<int32_t>(const_cast<JSCell*>(ptr)); } -inline JSValue::operator UnspecifiedBoolType*() const +inline JSValue::operator bool() const { ASSERT(tag() != DeletedValueTag); - return tag() != EmptyValueTag ? reinterpret_cast<UnspecifiedBoolType*>(1) : 0; + return tag() != EmptyValueTag; } inline bool JSValue::operator==(const JSValue& other) const @@ -301,6 +329,7 @@ ALWAYS_INLINE JSCell* JSValue::asCell() const ALWAYS_INLINE JSValue::JSValue(EncodeAsDoubleTag, double d) { + ASSERT(!isImpureNaN(d)); u.asDouble = d; } @@ -310,7 +339,7 @@ inline JSValue::JSValue(int i) u.asBits.payload = i; } -#if ENABLE(LLINT_C_LOOP) +#if !ENABLE(JIT) inline JSValue::JSValue(int32_t tag, int32_t payload) { u.asBits.tag = tag; @@ -325,7 +354,7 @@ inline bool JSValue::isNumber() const inline bool JSValue::isBoolean() const { - return isTrue() || isFalse(); + return tag() == BooleanTag; } inline bool JSValue::asBoolean() const @@ -358,9 +387,9 @@ inline JSValue::JSValue(const JSCell* ptr) u.asInt64 = reinterpret_cast<uintptr_t>(const_cast<JSCell*>(ptr)); } -inline JSValue::operator UnspecifiedBoolType*() const +inline JSValue::operator bool() const { - return u.asInt64 ? reinterpret_cast<UnspecifiedBoolType*>(1) : 0; + return u.asInt64; } inline bool JSValue::operator==(const JSValue& other) const @@ -467,6 +496,7 @@ inline double reinterpretInt64ToDouble(int64_t value) ALWAYS_INLINE JSValue::JSValue(EncodeAsDoubleTag, double d) { + ASSERT(!isImpureNaN(d)); u.asInt64 = reinterpretDoubleToInt64(d) + DoubleEncodeOffset; } @@ -494,30 +524,48 @@ ALWAYS_INLINE JSCell* JSValue::asCell() const #endif // USE(JSVALUE64) -inline bool JSValue::isMachineInt() const +inline int64_t tryConvertToInt52(double number) { - if (isInt32()) - return true; - if (!isNumber()) - return false; - double number = asDouble(); if (number != number) - return false; + return JSValue::notInt52; +#if OS(WINDOWS) && CPU(X86) + // The VS Compiler for 32-bit builds generates a floating point error when attempting to cast + // from an infinity to a 64-bit integer. We leave this routine with the floating point error + // left in a register, causing undefined behavior in later floating point operations. + // + // To avoid this issue, we check for infinity here, and return false in that case. + if (std::isinf(number)) + return JSValue::notInt52; +#endif int64_t asInt64 = static_cast<int64_t>(number); if (asInt64 != number) - return false; + return JSValue::notInt52; if (!asInt64 && std::signbit(number)) + return JSValue::notInt52; + if (asInt64 >= (static_cast<int64_t>(1) << (JSValue::numberOfInt52Bits - 1))) + return JSValue::notInt52; + if (asInt64 < -(static_cast<int64_t>(1) << (JSValue::numberOfInt52Bits - 1))) + return JSValue::notInt52; + return asInt64; +} + +inline bool isInt52(double number) +{ + return tryConvertToInt52(number) != JSValue::notInt52; +} + +inline bool JSValue::isAnyInt() const +{ + if (isInt32()) + return true; + if (!isNumber()) return false; - if (asInt64 >= (static_cast<int64_t>(1) << (numberOfInt52Bits - 1))) - return false; - if (asInt64 < -(static_cast<int64_t>(1) << (numberOfInt52Bits - 1))) - return false; - return true; + return isInt52(asDouble()); } -inline int64_t JSValue::asMachineInt() const +inline int64_t JSValue::asAnyInt() const { - ASSERT(isMachineInt()); + ASSERT(isAnyInt()); if (isInt32()) return asInt32(); return static_cast<int64_t>(asDouble()); @@ -528,9 +576,14 @@ inline bool JSValue::isString() const return isCell() && asCell()->isString(); } +inline bool JSValue::isSymbol() const +{ + return isCell() && asCell()->isSymbol(); +} + inline bool JSValue::isPrimitive() const { - return !isCell() || asCell()->isString(); + return !isCell() || asCell()->isString() || asCell()->isSymbol(); } inline bool JSValue::isGetterSetter() const @@ -538,6 +591,11 @@ inline bool JSValue::isGetterSetter() const return isCell() && asCell()->isGetterSetter(); } +inline bool JSValue::isCustomGetterSetter() const +{ + return isCell() && asCell()->isCustomGetterSetter(); +} + inline bool JSValue::isObject() const { return isCell() && asCell()->isObject(); @@ -578,11 +636,51 @@ ALWAYS_INLINE bool JSValue::getUInt32(uint32_t& v) const return false; } +ALWAYS_INLINE Identifier JSValue::toPropertyKey(ExecState* exec) const +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (isString()) + return asString(*this)->toIdentifier(exec); + + JSValue primitive = toPrimitive(exec, PreferString); + RETURN_IF_EXCEPTION(scope, vm.propertyNames->emptyIdentifier); + if (primitive.isSymbol()) + return Identifier::fromUid(asSymbol(primitive)->privateName()); + scope.release(); + return primitive.toString(exec)->toIdentifier(exec); +} + inline JSValue JSValue::toPrimitive(ExecState* exec, PreferredPrimitiveType preferredType) const { return isCell() ? asCell()->toPrimitive(exec, preferredType) : asValue(); } +inline PreferredPrimitiveType toPreferredPrimitiveType(ExecState* exec, JSValue value) +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (!value.isString()) { + throwTypeError(exec, scope, ASCIILiteral("Primitive hint is not a string.")); + return NoPreference; + } + + StringImpl* hintString = asString(value)->value(exec).impl(); + RETURN_IF_EXCEPTION(scope, NoPreference); + + if (WTF::equal(hintString, "default")) + return NoPreference; + if (WTF::equal(hintString, "number")) + return PreferNumber; + if (WTF::equal(hintString, "string")) + return PreferString; + + throwTypeError(exec, scope, ASCIILiteral("Expected primitive hint to match one of 'default', 'number', 'string'.")); + return NoPreference; +} + inline bool JSValue::getPrimitiveNumber(ExecState* exec, double& number, JSValue& value) { if (isInt32()) { @@ -608,7 +706,7 @@ inline bool JSValue::getPrimitiveNumber(ExecState* exec, double& number, JSValue return true; } ASSERT(isUndefined()); - number = QNaN; + number = PNaN; value = *this; return true; } @@ -634,58 +732,123 @@ inline JSObject* JSValue::toObject(ExecState* exec, JSGlobalObject* globalObject inline bool JSValue::isFunction() const { - return isCell() && (asCell()->inherits(JSFunction::info()) || asCell()->inherits(InternalFunction::info())); + if (!isCell()) + return false; + JSCell* cell = asCell(); + CallData ignored; + return cell->methodTable()->getCallData(cell, ignored) != CallType::None; +} + +inline bool JSValue::isFunction(CallType& callType, CallData& callData) const +{ + return isCallable(callType, callData); +} + +inline bool JSValue::isCallable(CallType& callType, CallData& callData) const +{ + if (!isCell()) + return false; + JSCell* cell = asCell(); + callType = cell->methodTable()->getCallData(cell, callData); + return callType != CallType::None; +} + +inline bool JSValue::isConstructor() const +{ + if (!isCell()) + return false; + JSCell* cell = asCell(); + ConstructData ignored; + return cell->methodTable()->getConstructData(cell, ignored) != ConstructType::None; +} + +inline bool JSValue::isConstructor(ConstructType& constructType, ConstructData& constructData) const +{ + if (!isCell()) + return false; + JSCell* cell = asCell(); + constructType = cell->methodTable()->getConstructData(cell, constructData); + return constructType != ConstructType::None; } // this method is here to be after the inline declaration of JSCell::inherits -inline bool JSValue::inherits(const ClassInfo* classInfo) const +inline bool JSValue::inherits(VM& vm, const ClassInfo* classInfo) const +{ + return isCell() && asCell()->inherits(vm, classInfo); +} + +inline const ClassInfo* JSValue::classInfoOrNull(VM& vm) const { - return isCell() && asCell()->inherits(classInfo); + return isCell() ? asCell()->classInfo(vm) : nullptr; } inline JSValue JSValue::toThis(ExecState* exec, ECMAMode ecmaMode) const { - return isCell() ? asCell()->methodTable()->toThis(asCell(), exec, ecmaMode) : toThisSlowCase(exec, ecmaMode); + return isCell() ? asCell()->methodTable(exec->vm())->toThis(asCell(), exec, ecmaMode) : toThisSlowCase(exec, ecmaMode); } -inline JSValue JSValue::get(ExecState* exec, PropertyName propertyName) const +ALWAYS_INLINE JSValue JSValue::get(ExecState* exec, PropertyName propertyName) const { - PropertySlot slot(asValue()); + PropertySlot slot(asValue(), PropertySlot::InternalMethodType::Get); return get(exec, propertyName, slot); } -inline JSValue JSValue::get(ExecState* exec, PropertyName propertyName, PropertySlot& slot) const +ALWAYS_INLINE JSValue JSValue::get(ExecState* exec, PropertyName propertyName, PropertySlot& slot) const +{ + return getPropertySlot(exec, propertyName, slot) ? + slot.getValue(exec, propertyName) : jsUndefined(); +} + +template<typename CallbackWhenNoException> +ALWAYS_INLINE typename std::result_of<CallbackWhenNoException(bool, PropertySlot&)>::type JSValue::getPropertySlot(ExecState* exec, PropertyName propertyName, CallbackWhenNoException callback) const +{ + PropertySlot slot(asValue(), PropertySlot::InternalMethodType::Get); + return getPropertySlot(exec, propertyName, slot, callback); +} + +template<typename CallbackWhenNoException> +ALWAYS_INLINE typename std::result_of<CallbackWhenNoException(bool, PropertySlot&)>::type JSValue::getPropertySlot(ExecState* exec, PropertyName propertyName, PropertySlot& slot, CallbackWhenNoException callback) const +{ + auto scope = DECLARE_THROW_SCOPE(exec->vm()); + bool found = getPropertySlot(exec, propertyName, slot); + RETURN_IF_EXCEPTION(scope, { }); + return callback(found, slot); +} + +ALWAYS_INLINE bool JSValue::getPropertySlot(ExecState* exec, PropertyName propertyName, PropertySlot& slot) const { // If this is a primitive, we'll need to synthesize the prototype - // and if it's a string there are special properties to check first. JSObject* object; if (UNLIKELY(!isObject())) { - if (isCell() && asString(*this)->getStringPropertySlot(exec, propertyName, slot)) - return slot.getValue(exec, propertyName); + if (isString() && asString(*this)->getStringPropertySlot(exec, propertyName, slot)) + return true; object = synthesizePrototype(exec); + if (UNLIKELY(!object)) + return false; } else object = asObject(asCell()); - if (object->getPropertySlot(exec, propertyName, slot)) - return slot.getValue(exec, propertyName); - return jsUndefined(); + return object->getPropertySlot(exec, propertyName, slot); } -inline JSValue JSValue::get(ExecState* exec, unsigned propertyName) const +ALWAYS_INLINE JSValue JSValue::get(ExecState* exec, unsigned propertyName) const { - PropertySlot slot(asValue()); + PropertySlot slot(asValue(), PropertySlot::InternalMethodType::Get); return get(exec, propertyName, slot); } -inline JSValue JSValue::get(ExecState* exec, unsigned propertyName, PropertySlot& slot) const +ALWAYS_INLINE JSValue JSValue::get(ExecState* exec, unsigned propertyName, PropertySlot& slot) const { // If this is a primitive, we'll need to synthesize the prototype - // and if it's a string there are special properties to check first. JSObject* object; if (UNLIKELY(!isObject())) { - if (isCell() && asString(*this)->getStringPropertySlot(exec, propertyName, slot)) + if (isString() && asString(*this)->getStringPropertySlot(exec, propertyName, slot)) return slot.getValue(exec, propertyName); object = synthesizePrototype(exec); + if (UNLIKELY(!object)) + return JSValue(); } else object = asObject(asCell()); @@ -694,22 +857,50 @@ inline JSValue JSValue::get(ExecState* exec, unsigned propertyName, PropertySlot return jsUndefined(); } -inline void JSValue::put(ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +ALWAYS_INLINE JSValue JSValue::get(ExecState* exec, uint64_t propertyName) const { - if (UNLIKELY(!isCell())) { - putToPrimitive(exec, propertyName, value, slot); - return; - } - asCell()->methodTable()->put(asCell(), exec, propertyName, value, slot); + if (LIKELY(propertyName <= std::numeric_limits<unsigned>::max())) + return get(exec, static_cast<unsigned>(propertyName)); + return get(exec, Identifier::from(exec, static_cast<double>(propertyName))); } -inline void JSValue::putByIndex(ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow) +inline bool JSValue::put(ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) { - if (UNLIKELY(!isCell())) { - putToPrimitiveByIndex(exec, propertyName, value, shouldThrow); - return; - } - asCell()->methodTable()->putByIndex(asCell(), exec, propertyName, value, shouldThrow); + if (UNLIKELY(!isCell())) + return putToPrimitive(exec, propertyName, value, slot); + + return asCell()->methodTable(exec->vm())->put(asCell(), exec, propertyName, value, slot); +} + +ALWAYS_INLINE bool JSValue::putInline(ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +{ + if (UNLIKELY(!isCell())) + return putToPrimitive(exec, propertyName, value, slot); + + JSCell* cell = asCell(); + auto putMethod = cell->methodTable(exec->vm())->put; + if (LIKELY(putMethod == JSObject::put)) + return JSObject::putInline(cell, exec, propertyName, value, slot); + + PutPropertySlot otherSlot = slot; + bool result = putMethod(cell, exec, propertyName, value, otherSlot); + slot = otherSlot; + return result; +} + +inline bool JSValue::putByIndex(ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow) +{ + if (UNLIKELY(!isCell())) + return putToPrimitiveByIndex(exec, propertyName, value, shouldThrow); + + return asCell()->methodTable(exec->vm())->putByIndex(asCell(), exec, propertyName, value, shouldThrow); +} + +inline Structure* JSValue::structureOrNull() const +{ + if (isCell()) + return asCell()->structure(); + return nullptr; } inline JSValue JSValue::structureOrUndefined() const @@ -730,6 +921,8 @@ inline bool JSValue::equal(ExecState* exec, JSValue v1, JSValue v2) ALWAYS_INLINE bool JSValue::equalSlowCaseInline(ExecState* exec, JSValue v1, JSValue v2) { + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); do { if (v1.isNumber() && v2.isNumber()) return v1.asNumber() == v2.asNumber(); @@ -737,28 +930,27 @@ ALWAYS_INLINE bool JSValue::equalSlowCaseInline(ExecState* exec, JSValue v1, JSV bool s1 = v1.isString(); bool s2 = v2.isString(); if (s1 && s2) - return asString(v1)->value(exec) == asString(v2)->value(exec); + return asString(v1)->equal(exec, asString(v2)); if (v1.isUndefinedOrNull()) { if (v2.isUndefinedOrNull()) return true; if (!v2.isCell()) return false; - return v2.asCell()->structure()->masqueradesAsUndefined(exec->lexicalGlobalObject()); + return v2.asCell()->structure(vm)->masqueradesAsUndefined(exec->lexicalGlobalObject()); } if (v2.isUndefinedOrNull()) { if (!v1.isCell()) return false; - return v1.asCell()->structure()->masqueradesAsUndefined(exec->lexicalGlobalObject()); + return v1.asCell()->structure(vm)->masqueradesAsUndefined(exec->lexicalGlobalObject()); } if (v1.isObject()) { if (v2.isObject()) return v1 == v2; JSValue p1 = v1.toPrimitive(exec); - if (exec->hadException()) - return false; + RETURN_IF_EXCEPTION(scope, false); v1 = p1; if (v1.isInt32() && v2.isInt32()) return v1 == v2; @@ -767,14 +959,21 @@ ALWAYS_INLINE bool JSValue::equalSlowCaseInline(ExecState* exec, JSValue v1, JSV if (v2.isObject()) { JSValue p2 = v2.toPrimitive(exec); - if (exec->hadException()) - return false; + RETURN_IF_EXCEPTION(scope, false); v2 = p2; if (v1.isInt32() && v2.isInt32()) return v1 == v2; continue; } + bool sym1 = v1.isSymbol(); + bool sym2 = v2.isSymbol(); + if (sym1 || sym2) { + if (sym1 && sym2) + return asSymbol(v1) == asSymbol(v2); + return false; + } + if (s1 || s2) { double d1 = v1.toNumber(exec); double d2 = v2.toNumber(exec); @@ -799,8 +998,7 @@ ALWAYS_INLINE bool JSValue::strictEqualSlowCaseInline(ExecState* exec, JSValue v ASSERT(v1.isCell() && v2.isCell()); if (v1.asCell()->isString() && v2.asCell()->isString()) - return asString(v1)->value(exec) == asString(v2)->value(exec); - + return asString(v1)->equal(exec, asString(v2)); return v1 == v2; } @@ -818,6 +1016,13 @@ inline bool JSValue::strictEqual(ExecState* exec, JSValue v1, JSValue v2) return strictEqualSlowCaseInline(exec, v1, v2); } +inline int32_t JSValue::asInt32ForArithmetic() const +{ + if (isBoolean()) + return asBoolean(); + return asInt32(); +} + inline TriState JSValue::pureStrictEqual(JSValue v1, JSValue v2) { if (v1.isInt32() && v2.isInt32()) @@ -834,7 +1039,7 @@ inline TriState JSValue::pureStrictEqual(JSValue v1, JSValue v2) const StringImpl* v2String = asString(v2)->tryGetValueImpl(); if (!v1String || !v2String) return MixedTriState; - return triState(WTF::equal(v1String, v2String)); + return triState(WTF::equal(*v1String, *v2String)); } return triState(v1 == v2); @@ -851,7 +1056,46 @@ inline TriState JSValue::pureToBoolean() const return isTrue() ? TrueTriState : FalseTriState; } -} // namespace JSC +ALWAYS_INLINE bool JSValue::requireObjectCoercible(ExecState* exec) const +{ + VM& vm = exec->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); -#endif // JSValueInlines_h + if (!isUndefinedOrNull()) + return true; + throwException(exec, scope, createNotAnObjectError(exec, *this)); + return false; +} + +ALWAYS_INLINE bool isThisValueAltered(const PutPropertySlot& slot, JSObject* baseObject) +{ + JSValue thisValue = slot.thisValue(); + if (LIKELY(thisValue == baseObject)) + return false; + if (!thisValue.isObject()) + return true; + JSObject* thisObject = asObject(thisValue); + // Only PureForwardingProxyType can be seen as the same to the original target object. + if (thisObject->type() == PureForwardingProxyType && jsCast<JSProxy*>(thisObject)->target() == baseObject) + return false; + return true; +} + +// See section 7.2.9: https://tc39.github.io/ecma262/#sec-samevalue +ALWAYS_INLINE bool sameValue(ExecState* exec, JSValue a, JSValue b) +{ + if (!a.isNumber()) + return JSValue::strictEqual(exec, a, b); + if (!b.isNumber()) + return false; + double x = a.asNumber(); + double y = b.asNumber(); + bool xIsNaN = std::isnan(x); + bool yIsNaN = std::isnan(y); + if (xIsNaN || yIsNaN) + return xIsNaN && yIsNaN; + return bitwise_cast<uint64_t>(x) == bitwise_cast<uint64_t>(y); +} + +} // namespace JSC |