lib/VM/Operations.cpp (1,520 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "hermes/VM/Operations.h" #include "hermes/Support/Conversions.h" #include "hermes/Support/OSCompat.h" #include "hermes/VM/Callable.h" #include "hermes/VM/Casting.h" #include "hermes/VM/JSArray.h" #include "hermes/VM/JSCallableProxy.h" #include "hermes/VM/JSError.h" #include "hermes/VM/JSObject.h" #include "hermes/VM/JSRegExp.h" #include "hermes/VM/PrimitiveBox.h" #include "hermes/VM/PropertyAccessor.h" #include "hermes/VM/Runtime.h" #include "hermes/VM/StringBuilder.h" #include "hermes/VM/StringPrimitive.h" #include "hermes/VM/StringView.h" #include "dtoa/dtoa.h" #include "llvh/ADT/SmallString.h" #include <cfloat> #include <cmath> namespace hermes { namespace vm { CallResult<Handle<SymbolID>> stringToSymbolID( Runtime &runtime, PseudoHandle<StringPrimitive> strPrim) { // Unique the string. return runtime.getIdentifierTable().getSymbolHandleFromPrimitive( runtime, std::move(strPrim)); } CallResult<Handle<SymbolID>> valueToSymbolID( Runtime &runtime, Handle<> nameValHnd) { if (nameValHnd->isSymbol()) { return Handle<SymbolID>::vmcast(nameValHnd); } // Convert the value to a string. auto res = toString_RJS(runtime, nameValHnd); if (res == ExecutionStatus::EXCEPTION) return ExecutionStatus::EXCEPTION; // Unique the string. return stringToSymbolID(runtime, std::move(*res)); } HermesValue typeOf(Runtime &runtime, Handle<> valueHandle) { switch (valueHandle->getETag()) { case HermesValue::ETag::Undefined: return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::undefined)); case HermesValue::ETag::Null: return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::object)); case HermesValue::ETag::Str1: case HermesValue::ETag::Str2: return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::string)); case HermesValue::ETag::Bool: return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::boolean)); case HermesValue::ETag::Symbol: return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::symbol)); case HermesValue::ETag::Object1: case HermesValue::ETag::Object2: if (vmisa<Callable>(*valueHandle)) return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::function)); return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::object)); default: assert(valueHandle->isNumber() && "Invalid type."); return HermesValue::encodeStringValue( runtime.getPredefinedString(Predefined::number)); } } OptValue<uint32_t> toArrayIndex( Runtime &runtime, Handle<StringPrimitive> strPrim) { auto view = StringPrimitive::createStringView(runtime, strPrim); return toArrayIndex(view); } OptValue<uint32_t> toArrayIndex(StringView str) { auto len = str.length(); if (str.isASCII()) { const char *ptr = str.castToCharPtr(); return hermes::toArrayIndex(ptr, ptr + len); } const char16_t *ptr = str.castToChar16Ptr(); return hermes::toArrayIndex(ptr, ptr + len); } bool isSameValue(HermesValue x, HermesValue y) { if (x.getTag() != y.getTag()) { // If the tags are different, they must be different. return false; } assert( !x.isEmpty() && !x.isNativeValue() && "Empty and Native Value cannot be compared"); // Strings are the only type that requires deep comparison. if (x.isString()) { // For strings, we compare each character in sequence. return x.getString()->equals(y.getString()); } // Otherwise they are identical if the raw bits are the same. return x.getRaw() == y.getRaw(); } bool isSameValueZero(HermesValue x, HermesValue y) { if (x.isNumber() && y.isNumber() && x.getNumber() == y.getNumber()) { // Takes care of +0 == -0. return true; } return isSameValue(x, y); } bool isPrimitive(HermesValue val) { assert(val.getTag() != EmptyInvalidTag && "empty value encountered"); assert(val.getTag() != NativeValueTag && "native value encountered"); return !val.isObject(); } CallResult<HermesValue> ordinaryToPrimitive( Handle<JSObject> selfHandle, Runtime &runtime, PreferredType preferredType) { GCScope gcScope{runtime}; assert( preferredType != PreferredType::NONE && "OrdinaryToPrimitive requires a type hint"); for (int i = 0; i < 2; ++i) { if (preferredType == PreferredType::STRING) { auto propRes = JSObject::getNamed_RJS( selfHandle, runtime, Predefined::getSymbolID(Predefined::toString)); if (propRes == ExecutionStatus::EXCEPTION) return ExecutionStatus::EXCEPTION; if (auto funcHandle = Handle<Callable>::dyn_vmcast( runtime.makeHandle(std::move(*propRes)))) { auto callRes = funcHandle->executeCall0(funcHandle, runtime, selfHandle); if (callRes == ExecutionStatus::EXCEPTION) return ExecutionStatus::EXCEPTION; if (isPrimitive(callRes->get())) return callRes.toCallResultHermesValue(); } // This method failed. Try the other one. preferredType = PreferredType::NUMBER; } else { auto propRes = JSObject::getNamed_RJS( selfHandle, runtime, Predefined::getSymbolID(Predefined::valueOf)); if (propRes == ExecutionStatus::EXCEPTION) return ExecutionStatus::EXCEPTION; if (auto funcHandle = Handle<Callable>::dyn_vmcast( runtime.makeHandle(std::move(*propRes)))) { auto callRes = funcHandle->executeCall0(funcHandle, runtime, selfHandle); if (callRes == ExecutionStatus::EXCEPTION) return ExecutionStatus::EXCEPTION; if (isPrimitive(callRes->get())) return callRes.toCallResultHermesValue(); } // This method failed. Try the other one. preferredType = PreferredType::STRING; } } // Nothing succeeded, time to give up. return runtime.raiseTypeError("Cannot determine default value of object"); } /// ES5.1 9.1 CallResult<HermesValue> toPrimitive_RJS(Runtime &runtime, Handle<> valueHandle, PreferredType hint) { assert( valueHandle->getTag() != EmptyInvalidTag && "empty value is not allowed"); assert( valueHandle->getTag() != NativeValueTag && "native value is not allowed"); if (valueHandle->getTag() != ObjectTag) return *valueHandle; // 4. Let exoticToPrim be GetMethod(input, @@toPrimitive). auto exoticToPrim = getMethod( runtime, valueHandle, runtime.makeHandle( Predefined::getSymbolID(Predefined::SymbolToPrimitive))); if (LLVM_UNLIKELY(exoticToPrim == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 6. If exoticToPrim is not undefined, then if (vmisa<Callable>(exoticToPrim->getHermesValue())) { auto callable = runtime.makeHandle<Callable>( dyn_vmcast<Callable>(exoticToPrim->getHermesValue())); CallResult<PseudoHandle<>> resultRes = Callable::executeCall1( callable, runtime, valueHandle, HermesValue::encodeStringValue(runtime.getPredefinedString( hint == PreferredType::NONE ? Predefined::defaultStr : hint == PreferredType::STRING ? Predefined::string : Predefined::number))); if (LLVM_UNLIKELY(resultRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } PseudoHandle<> result = std::move(*resultRes); if (!result->isObject()) { return result.getHermesValue(); } return runtime.raiseTypeError( "Symbol.toPrimitive function must return a primitive"); } // 7. If hint is "default", let hint be "number". // 8. Return OrdinaryToPrimitive(input,hint). return ordinaryToPrimitive( Handle<JSObject>::vmcast(valueHandle), runtime, hint == PreferredType::NONE ? PreferredType::NUMBER : hint); } bool toBoolean(HermesValue value) { switch (value.getETag()) { #ifdef HERMES_SLOW_DEBUG case HermesValue::ETag::Invalid: llvm_unreachable("invalid value"); #endif // HERMES_SLOW_DEBUG case HermesValue::ETag::Empty: llvm_unreachable("empty value"); case HermesValue::ETag::Native1: case HermesValue::ETag::Native2: llvm_unreachable("native value"); case HermesValue::ETag::Undefined: case HermesValue::ETag::Null: return false; case HermesValue::ETag::Bool: return value.getBool(); case HermesValue::ETag::Symbol: case HermesValue::ETag::Object1: case HermesValue::ETag::Object2: return true; case HermesValue::ETag::Str1: case HermesValue::ETag::Str2: return value.getString()->getStringLength() != 0; default: { auto m = value.getNumber(); return !(m == 0 || std::isnan(m)); } } } /// ES5.1 9.8.1 static CallResult<PseudoHandle<StringPrimitive>> numberToString( Runtime &runtime, double m) LLVM_NO_SANITIZE("float-cast-overflow"); static CallResult<PseudoHandle<StringPrimitive>> numberToString( Runtime &runtime, double m) { char buf8[hermes::NUMBER_TO_STRING_BUF_SIZE]; // Optimization: Fast-case for positive integers < 2^31 int32_t n = static_cast<int32_t>(m); if (m == static_cast<double>(n) && n > 0) { // Write base 10 digits in reverse from end of buf8. char *p = buf8 + sizeof(buf8); do { *--p = '0' + (n % 10); n /= 10; } while (n); size_t len = buf8 + sizeof(buf8) - p; // Temporarily stop the propagation of removing. auto result = StringPrimitive::create(runtime, ASCIIRef(p, len)); if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return createPseudoHandle(vmcast<StringPrimitive>(*result)); } auto getPredefined = [&runtime](Predefined::Str predefinedID) { return createPseudoHandle(runtime.getPredefinedString(predefinedID)); }; if (std::isnan(m)) return getPredefined(Predefined::NaN); if (m == 0) return getPredefined(Predefined::zero); if (m == std::numeric_limits<double>::infinity()) return getPredefined(Predefined::Infinity); if (m == -std::numeric_limits<double>::infinity()) return getPredefined(Predefined::NegativeInfinity); // After special cases, run the generic routine to convert. size_t len = hermes::numberToString(m, buf8, sizeof(buf8)); auto result = StringPrimitive::create(runtime, ASCIIRef(buf8, len)); if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return createPseudoHandle(vmcast<StringPrimitive>(*result)); } CallResult<PseudoHandle<StringPrimitive>> toString_RJS( Runtime &runtime, Handle<> valueHandle) { HermesValue value = valueHandle.get(); StringPrimitive *result; switch (value.getETag()) { #ifdef HERMES_SLOW_DEBUG case HermesValue::ETag::Invalid: llvm_unreachable("invalid value"); #endif // HERMES_SLOW_DEBUG case HermesValue::ETag::Empty: llvm_unreachable("empty value"); case HermesValue::ETag::Native1: case HermesValue::ETag::Native2: llvm_unreachable("native value"); case HermesValue::ETag::Str1: case HermesValue::ETag::Str2: result = vmcast<StringPrimitive>(value); break; case HermesValue::ETag::Undefined: result = runtime.getPredefinedString(Predefined::undefined); break; case HermesValue::ETag::Null: result = runtime.getPredefinedString(Predefined::null); break; case HermesValue::ETag::Bool: result = value.getBool() ? runtime.getPredefinedString(Predefined::trueStr) : runtime.getPredefinedString(Predefined::falseStr); break; case HermesValue::ETag::Object1: case HermesValue::ETag::Object2: { auto res = toPrimitive_RJS(runtime, valueHandle, PreferredType::STRING); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } return toString_RJS(runtime, runtime.makeHandle(res.getValue())); } case HermesValue::ETag::Symbol: return runtime.raiseTypeError("Cannot convert Symbol to string"); default: return numberToString(runtime, value.getNumber()); } return createPseudoHandle(result); } double parseIntWithRadix(const StringView str, int radix) { auto res = hermes::parseIntWithRadix</* AllowNumericSeparator */ false>(str, radix); return res ? res.getValue() : std::numeric_limits<double>::quiet_NaN(); } /// ES5.1 9.3.1 static inline double stringToNumber( Runtime &runtime, Handle<StringPrimitive> strPrim) { auto &idTable = runtime.getIdentifierTable(); // Fast check for special values (no extraneous whitespace). if (runtime.symbolEqualsToStringPrim( Predefined::getSymbolID(Predefined::Infinity), *strPrim)) { return std::numeric_limits<double>::infinity(); } if (runtime.symbolEqualsToStringPrim( Predefined::getSymbolID(Predefined::PositiveInfinity), *strPrim)) { return std::numeric_limits<double>::infinity(); } if (runtime.symbolEqualsToStringPrim( Predefined::getSymbolID(Predefined::NegativeInfinity), *strPrim)) { } if (runtime.symbolEqualsToStringPrim( Predefined::getSymbolID(Predefined::NaN), *strPrim)) { return std::numeric_limits<double>::quiet_NaN(); } // Trim string to the interval [begin, end). auto orig = StringPrimitive::createStringView(runtime, strPrim); auto begin = orig.begin(); auto end = orig.end(); // Move begin and end to ignore whitespace. while (begin != end && (isWhiteSpaceChar(*begin) || isLineTerminatorChar(*begin))) { ++begin; } while (begin != end && (isWhiteSpaceChar(*(end - 1)) || isLineTerminatorChar(*(end - 1)))) { --end; } // Early return for empty strings (strings only containing whitespace). if (begin == end) { return 0; } // Trim the string. StringView str16 = orig.slice(begin, end); // Slow check for special values. // This should only run if user created a string with extra whitespace, // since normal uses would get caught by the initial check. if (LLVM_UNLIKELY(str16.equals(idTable.getStringView( runtime, Predefined::getSymbolID(Predefined::Infinity))))) { return std::numeric_limits<double>::infinity(); } if (LLVM_UNLIKELY(str16.equals(idTable.getStringView( runtime, Predefined::getSymbolID(Predefined::PositiveInfinity))))) { return std::numeric_limits<double>::infinity(); } if (LLVM_UNLIKELY(str16.equals(idTable.getStringView( runtime, Predefined::getSymbolID(Predefined::NegativeInfinity))))) { return -std::numeric_limits<double>::infinity(); } if (LLVM_UNLIKELY(str16.equals(idTable.getStringView( runtime, Predefined::getSymbolID(Predefined::NaN))))) { return std::numeric_limits<double>::quiet_NaN(); } auto len = str16.length(); // Parse hex codes, since dtoa doesn't do it. // FIXME: May be inaccurate for some hex values. // We need to check other sources first. if (len > 2) { if (str16[0] == u'0' && letterToLower(str16[1]) == u'x') { return parseIntWithRadix(str16.slice(2), 16); } if (str16[0] == u'0' && letterToLower(str16[1]) == u'o') { return parseIntWithRadix(str16.slice(2), 8); } if (str16[0] == u'0' && letterToLower(str16[1]) == u'b') { return parseIntWithRadix(str16.slice(2), 2); } } // Finally, copy 16 bit chars into 8 bit chars and call dtoa. llvh::SmallVector<char, 32> str8(len + 1); uint32_t i = 0; for (auto c16 : str16) { // Check to ensure we only have valid number characters now. if ((u'0' <= c16 && c16 <= u'9') || c16 == u'.' || letterToLower(c16) == u'e' || c16 == u'+' || c16 == u'-') { str8[i] = static_cast<char>(c16); } else { return std::numeric_limits<double>::quiet_NaN(); } ++i; } str8[len] = '\0'; char *endPtr; double result = ::hermes_g_strtod(str8.data(), &endPtr); if (endPtr == str8.data() + len) { return result; } // If everything failed, return NaN. return std::numeric_limits<double>::quiet_NaN(); } CallResult<HermesValue> toNumber_RJS(Runtime &runtime, Handle<> valueHandle) { auto value = valueHandle.get(); double result; switch (value.getETag()) { #ifdef HERMES_SLOW_DEBUG case HermesValue::ETag::Invalid: llvm_unreachable("invalid value"); #endif // HERMES_SLOW_DEBUG case HermesValue::ETag::Empty: llvm_unreachable("empty value"); case HermesValue::ETag::Native1: case HermesValue::ETag::Native2: llvm_unreachable("native value"); case HermesValue::ETag::Object1: case HermesValue::ETag::Object2: { auto res = toPrimitive_RJS(runtime, valueHandle, PreferredType::NUMBER); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } return toNumber_RJS(runtime, runtime.makeHandle(res.getValue())); } case HermesValue::ETag::Str1: case HermesValue::ETag::Str2: result = stringToNumber(runtime, Handle<StringPrimitive>::vmcast(valueHandle)); break; case HermesValue::ETag::Undefined: result = std::numeric_limits<double>::quiet_NaN(); break; case HermesValue::ETag::Null: result = +0.0; break; case HermesValue::ETag::Bool: result = value.getBool(); break; case HermesValue::ETag::Symbol: return runtime.raiseTypeError("Cannot convert Symbol to number"); default: // Already have a number, just return it. return value; } return HermesValue::encodeDoubleValue(result); } CallResult<HermesValue> toLength(Runtime &runtime, Handle<> valueHandle) { constexpr double maxLength = 9007199254740991.0; // 2**53 - 1 auto res = toIntegerOrInfinity(runtime, valueHandle); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto len = res->getNumber(); if (len <= 0) { len = 0; } else if (len > maxLength) { len = maxLength; } return HermesValue::encodeDoubleValue(len); } CallResult<uint64_t> toLengthU64(Runtime &runtime, Handle<> valueHandle) { constexpr double highestIntegralDouble = ((uint64_t)1 << std::numeric_limits<double>::digits) - 1; auto res = toIntegerOrInfinity(runtime, valueHandle); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto len = res->getNumber(); if (len <= 0) { len = 0; } else if (len > highestIntegralDouble) { len = highestIntegralDouble; } return len; } CallResult<HermesValue> toIndex(Runtime &runtime, Handle<> valueHandle) { auto value = (valueHandle->isUndefined()) ? runtime.makeHandle(HermesValue::encodeDoubleValue(0)) : valueHandle; auto res = toIntegerOrInfinity(runtime, value); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto integerIndex = res->getNumber(); if (integerIndex < 0) { return runtime.raiseRangeError("A negative value cannot be an index"); } auto integerIndexHandle = runtime.makeHandle(HermesValue::encodeDoubleValue(integerIndex)); res = toLength(runtime, integerIndexHandle); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto index = res.getValue(); if (index.getNumber() != integerIndex) { return runtime.raiseRangeError( "The value given for the index must be between 0 and 2 ^ 53 - 1"); } return res; } CallResult<HermesValue> toIntegerOrInfinity( Runtime &runtime, Handle<> valueHandle) { auto res = toNumber_RJS(runtime, valueHandle); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } double num = res->getNumber(); double result; if (std::isnan(num)) { result = 0; } else { result = std::trunc(num); } return HermesValue::encodeDoubleValue(result); } /// Conversion of HermesValues to integers. template <typename T> static inline CallResult<HermesValue> toInt( Runtime &runtime, Handle<> valueHandle) { auto res = toNumber_RJS(runtime, valueHandle); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } double num = res->getNumber(); T result = static_cast<T>(hermes::truncateToInt32(num)); return HermesValue::encodeNumberValue(result); } CallResult<HermesValue> toInt8(Runtime &runtime, Handle<> valueHandle) { return toInt<int8_t>(runtime, valueHandle); } CallResult<HermesValue> toInt16(Runtime &runtime, Handle<> valueHandle) { return toInt<int16_t>(runtime, valueHandle); } CallResult<HermesValue> toInt32_RJS(Runtime &runtime, Handle<> valueHandle) { return toInt<int32_t>(runtime, valueHandle); } CallResult<HermesValue> toUInt8(Runtime &runtime, Handle<> valueHandle) { return toInt<uint8_t>(runtime, valueHandle); } uint8_t toUInt8Clamp(double number) { // 3. If number is NaN, return +0. // 4. If number <= 0, return +0. // Not < so that NaN coerces to 0. // NOTE: this check correctly rounds numbers less than 0.5 if (!(number >= 0.5)) { return 0; } // 5. If number >= 255, return 255. if (number > 255) { return 255; } // The next steps are the equivalent of the spec's round-to-even requirement. // Round up and then do the even/odd check. double toTruncate = number + 0.5; uint8_t x = static_cast<uint8_t>(toTruncate); // If it was a tie (i.e. it ended in 0.5) then if (x == toTruncate) { // number ended in 0.5 and was rounded up, reduce by 1 if odd, // else leave the same. // That is the same as unsetting the least significant bit. return (x & ~1); } else { // number did not end in 0.5, don't need to check the parity. return x; } } CallResult<HermesValue> toUInt8Clamp(Runtime &runtime, Handle<> valueHandle) { // 1. Let number be toNumber_RJS(argument) auto res = toNumber_RJS(runtime, valueHandle); if (res == ExecutionStatus::EXCEPTION) { // 2. ReturnIfAbrupt(number) return ExecutionStatus::EXCEPTION; } return HermesValue::encodeNumberValue(toUInt8Clamp(res->getNumber())); } CallResult<HermesValue> toUInt16(Runtime &runtime, Handle<> valueHandle) { return toInt<uint16_t>(runtime, valueHandle); } CallResult<HermesValue> toUInt32_RJS(Runtime &runtime, Handle<> valueHandle) { return toInt<uint32_t>(runtime, valueHandle); } CallResult<Handle<JSObject>> getPrimitivePrototype( Runtime &runtime, Handle<> base) { switch (base->getETag()) { #ifdef HERMES_SLOW_DEBUG case HermesValue::ETag::Invalid: llvm_unreachable("invalid value"); #endif // HERMES_SLOW_DEBUG case HermesValue::ETag::Empty: llvm_unreachable("empty value"); case HermesValue::ETag::Native1: case HermesValue::ETag::Native2: llvm_unreachable("native value"); case HermesValue::ETag::Object1: case HermesValue::ETag::Object2: llvm_unreachable("object value"); case HermesValue::ETag::Undefined: return runtime.raiseTypeError("Cannot convert undefined value to object"); case HermesValue::ETag::Null: return runtime.raiseTypeError("Cannot convert null value to object"); case HermesValue::ETag::Str1: case HermesValue::ETag::Str2: return Handle<JSObject>::vmcast(&runtime.stringPrototype); case HermesValue::ETag::Bool: return Handle<JSObject>::vmcast(&runtime.booleanPrototype); case HermesValue::ETag::Symbol: return Handle<JSObject>::vmcast(&runtime.symbolPrototype); default: assert(base->isNumber() && "Unknown tag in getPrimitivePrototype."); return Handle<JSObject>::vmcast(&runtime.numberPrototype); } } CallResult<HermesValue> toObject(Runtime &runtime, Handle<> valueHandle) { auto value = valueHandle.get(); switch (value.getETag()) { #ifdef HERMES_SLOW_DEBUG case HermesValue::ETag::Invalid: llvm_unreachable("invalid value"); #endif // HERMES_SLOW_DEBUG case HermesValue::ETag::Empty: llvm_unreachable("empty value"); case HermesValue::ETag::Native1: case HermesValue::ETag::Native2: llvm_unreachable("native value"); case HermesValue::ETag::Undefined: return runtime.raiseTypeError("Cannot convert undefined value to object"); case HermesValue::ETag::Null: return runtime.raiseTypeError("Cannot convert null value to object"); case HermesValue::ETag::Object1: case HermesValue::ETag::Object2: return value; case HermesValue::ETag::Bool: return JSBoolean::create( runtime, value.getBool(), Handle<JSObject>::vmcast(&runtime.booleanPrototype)) .getHermesValue(); case HermesValue::ETag::Str1: case HermesValue::ETag::Str2: { auto res = JSString::create( runtime, runtime.makeHandle(value.getString()), Handle<JSObject>::vmcast(&runtime.stringPrototype)); if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return res->getHermesValue(); } case HermesValue::ETag::Symbol: return JSSymbol::create( runtime, *Handle<SymbolID>::vmcast(valueHandle), Handle<JSObject>::vmcast(&runtime.symbolPrototype)) .getHermesValue(); default: assert(valueHandle->isNumber() && "Unknown tag in toObject."); return JSNumber::create( runtime, value.getNumber(), Handle<JSObject>::vmcast(&runtime.numberPrototype)) .getHermesValue(); } } ExecutionStatus amendPropAccessErrorMsgWithPropName( Runtime &runtime, Handle<> valueHandle, llvh::StringRef operationStr, SymbolID id) { if (!valueHandle->isNull() && !valueHandle->isUndefined()) { // If value is not null/undefined, fall back to the original exception. return ExecutionStatus::EXCEPTION; } assert(!runtime.getThrownValue().isEmpty() && "Error must have been thrown"); // Clear the error first because we will re-throw. runtime.clearThrownValue(); // Construct an error message that contains the property name. llvh::StringRef valueStr = valueHandle->isNull() ? "null" : "undefined"; return runtime.raiseTypeError( TwineChar16("Cannot ") + operationStr + " property '" + runtime.getIdentifierTable().getStringView(runtime, id) + "' of " + valueStr); } /// Implement a comparison operator. First both operands a converted to /// primitives. If they both end up being strings, a lexicographical comparison /// is performed. Otherwise both operands are converted to numbers and the /// values are compared. /// \param oper is the comparison operator to use when comparing numbers. #define IMPLEMENT_COMPARISON_OP(name, oper) \ CallResult<bool> name( \ Runtime &runtime, Handle<> leftHandle, Handle<> rightHandle) { \ auto resLeft = \ toPrimitive_RJS(runtime, leftHandle, PreferredType::NUMBER); \ if (resLeft == ExecutionStatus::EXCEPTION) \ return ExecutionStatus::EXCEPTION; \ MutableHandle<> left(runtime, resLeft.getValue()); \ \ auto resRight = \ toPrimitive_RJS(runtime, rightHandle, PreferredType::NUMBER); \ if (resRight == ExecutionStatus::EXCEPTION) \ return ExecutionStatus::EXCEPTION; \ MutableHandle<> right(runtime, resRight.getValue()); \ \ /* If both are strings, we must do a string comparison.*/ \ if (left->isString() && right->isString()) { \ return left->getString()->compare(right->getString()) oper 0; \ } \ \ /* Convert both to a number and compare the numbers. */ \ resLeft = toNumber_RJS(runtime, left); \ if (resLeft == ExecutionStatus::EXCEPTION) \ return ExecutionStatus::EXCEPTION; \ left = resLeft.getValue(); \ resRight = toNumber_RJS(runtime, right); \ if (resRight == ExecutionStatus::EXCEPTION) \ return ExecutionStatus::EXCEPTION; \ right = resRight.getValue(); \ \ return left->getNumber() oper right->getNumber(); \ } IMPLEMENT_COMPARISON_OP(lessOp_RJS, <); IMPLEMENT_COMPARISON_OP(greaterOp_RJS, >); IMPLEMENT_COMPARISON_OP(lessEqualOp_RJS, <=); IMPLEMENT_COMPARISON_OP(greaterEqualOp_RJS, >=); /// ES11 7.2.15 Abstract Equality Comparison CallResult<bool> abstractEqualityTest_RJS(Runtime &runtime, Handle<> xHandle, Handle<> yHandle) { MutableHandle<> x{runtime, xHandle.get()}; MutableHandle<> y{runtime, yHandle.get()}; while (true) { // Combine tags for use in the switch statement. Use NativeValueTag as a // placeholder for numbers. assert( !x->isNativeValue() && !x->isEmpty() && "invalid value for comparison"); assert( !y->isNativeValue() && !y->isEmpty() && "invalid value for comparison"); // The following macros are used to generate the switch cases using // HermesValue::combineETags; an S in the name means it is a single ETag // (e.g., ETag::Bool), while M means it is a multi ETag (e.g., ETag::Object1 // and ETag::Object2). #define CASE_S_S(typeA, typeB) \ case HermesValue::combineETags( \ HermesValue::ETag::typeA, HermesValue::ETag::typeB): #define CASE_S_M(typeA, typeB) \ CASE_S_S(typeA, typeB##1) \ CASE_S_S(typeA, typeB##2) #define CASE_M_S(typeA, typeB) \ CASE_S_S(typeA##1, typeB) \ CASE_S_S(typeA##2, typeB) #define CASE_M_M(typeA, typeB) \ CASE_M_S(typeA, typeB##1) \ CASE_M_S(typeA, typeB##2) // NUMBER_TAG is a "virtual" ETag member that is used to tag numbers (which // don't have a tag assigned to them). It reuses ETag::Native1 there will // never be any native values in this part of the code. #define NUMBER_TAG Native1 // Tag numbers as with the "virtual" ETag member NUMBER_TAG, and use default // tag values for everything else. HermesValue::ETag xType = x->isNumber() ? HermesValue::ETag::NUMBER_TAG : x->getETag(); HermesValue::ETag yType = y->isNumber() ? HermesValue::ETag::NUMBER_TAG : y->getETag(); switch (HermesValue::combineETags(xType, yType)) { // 1. If Type(x) is the same as Type(y), then // a. Return the result of performing Strict Equality Comparison x === y. CASE_S_S(Undefined, Undefined) CASE_S_S(Null, Null) { return true; } CASE_S_S(NUMBER_TAG, NUMBER_TAG) { return x->getNumber() == y->getNumber(); } CASE_M_M(Str, Str) { return x->getString()->equals(y->getString()); } CASE_S_S(Bool, Bool) CASE_S_S(Symbol, Symbol) CASE_M_M(Object, Object) { return x->getRaw() == y->getRaw(); } // 2. If x is null and y is undefined, return true. // 3. If x is undefined and y is null, return true. CASE_S_S(Undefined, Null) CASE_S_S(Null, Undefined) { return true; } // 4. If Type(x) is Number and Type(y) is String, return the result of the // comparison x == ! ToNumber(y). CASE_S_M(NUMBER_TAG, Str) { return x->getNumber() == stringToNumber(runtime, Handle<StringPrimitive>::vmcast(y)); } // 5. If Type(x) is String and Type(y) is Number, return the result of the // comparison ! ToNumber(x) == y. CASE_M_S(Str, NUMBER_TAG) { return stringToNumber(runtime, Handle<StringPrimitive>::vmcast(x)) == y->getNumber(); } // 6. If Type(x) is BigInt and Type(y) is String, then // a. Let n be ! StringToBigInt(y). // b. If n is NaN, return false. // c. Return the result of the comparison x == n. // 7. If Type(x) is String and Type(y) is BigInt, return the result of the // comparison y == x. // 8. If Type(x) is Boolean, return the result of the comparison ! // ToNumber(x) == y. CASE_S_S(Bool, NUMBER_TAG) { // Do both conversions and check numerical equality. return x->getBool() == y->getNumber(); } CASE_S_M(Bool, Str) { // Do string parsing and check double equality. return x->getBool() == stringToNumber(runtime, Handle<StringPrimitive>::vmcast(y)); } CASE_S_M(Bool, Object) { x = HermesValue::encodeDoubleValue(x->getBool()); break; } // 9. If Type(y) is Boolean, return the result of the comparison x == ! // ToNumber(y). CASE_S_S(NUMBER_TAG, Bool) { return x->getNumber() == y->getBool(); } CASE_M_S(Str, Bool) { return stringToNumber(runtime, Handle<StringPrimitive>::vmcast(x)) == y->getBool(); } CASE_M_S(Object, Bool) { y = HermesValue::encodeDoubleValue(y->getBool()); break; } // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) // is Object, return the result of the comparison x == ToPrimitive(y). CASE_M_M(Str, Object) CASE_S_M(Symbol, Object) CASE_S_M(NUMBER_TAG, Object) { auto status = toPrimitive_RJS(runtime, y, PreferredType::NONE); if (status == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } y = status.getValue(); break; } // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, // or Symbol, return the result of the comparison ToPrimitive(x) == y. CASE_M_M(Object, Str) CASE_M_S(Object, Symbol) CASE_M_S(Object, NUMBER_TAG) { auto status = toPrimitive_RJS(runtime, x, PreferredType::NONE); if (status == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } x = status.getValue(); break; } // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is // Number and Type(y) is BigInt, then a. If x or y are any of NaN, +∞, // or -∞, return false. b. If the mathematical value of x is equal to // the mathematical value of y, return true; otherwise return false. // 13. Return false. default: return false; } #undef CASE_S_S #undef CASE_S_M #undef CASE_M_S #undef CASE_M_M #undef NUMBER_TAG } } bool strictEqualityTest(HermesValue x, HermesValue y) { // Numbers are special because they can have different tags and they don't // obey bit-exact equality (because of NaN). if (x.isNumber()) return y.isNumber() && x.getNumber() == y.getNumber(); // If they are not numbers and are bit exact, they must be the same. if (x.getRaw() == y.getRaw()) return true; // All the rest of the cases need to have the same tags. if (x.getTag() != y.getTag()) return false; // The only remaining case is string, which needs a deep comparison. return x.isString() && x.getString()->equals(y.getString()); } CallResult<HermesValue> addOp_RJS(Runtime &runtime, Handle<> xHandle, Handle<> yHandle) { auto resX = toPrimitive_RJS(runtime, xHandle, PreferredType::NONE); if (resX == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto x = runtime.makeHandle(resX.getValue()); auto resY = toPrimitive_RJS(runtime, yHandle, PreferredType::NONE); if (resY == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto y = runtime.makeHandle(resY.getValue()); // If one of the values is a string, concatenate as strings. if (x->isString() || y->isString()) { auto resX = toString_RJS(runtime, x); if (resX == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto xStr = runtime.makeHandle(std::move(*resX)); auto resY = toString_RJS(runtime, y); if (resY == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } auto yStr = runtime.makeHandle(std::move(*resY)); return StringPrimitive::concat(runtime, xStr, yStr); } // Add the numbers since neither are strings. resX = toNumber_RJS(runtime, x); if (LLVM_UNLIKELY(resX == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto xNum = resX.getValue().getNumber(); resY = toNumber_RJS(runtime, y); if (LLVM_UNLIKELY(resY == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto yNum = resY.getValue().getNumber(); return HermesValue::encodeDoubleValue(xNum + yNum); } static const size_t MIN_RADIX = 2; static const size_t MAX_RADIX = 36; static inline char toRadixChar(unsigned x, unsigned radix) { const char chars[] = "0123456789abcdefghijklmnopqrstuvwxyz"; static_assert(sizeof(chars) - 1 == MAX_RADIX, "Invalid chars array"); assert( x < radix && x < std::strlen(chars) && "invalid number to radix conversion"); return chars[x]; } /// \return the exponent component of the double \p x. static inline int doubleExponent(double x) { int e; std::frexp(x, &e); return e; } Handle<StringPrimitive> numberToStringWithRadix(Runtime &runtime, double number, unsigned radix) { (void)MIN_RADIX; (void)MAX_RADIX; assert(MIN_RADIX <= radix && radix <= MAX_RADIX && "Invalid radix"); // Two parts of the final result: integer part and fractional part. llvh::SmallString<64> result{}; // Used to store just the fractional part of the string (not including '.'). llvh::SmallString<32> fStr{}; // If negative, treat as if positive and add a '-' later. bool negative = false; if (number < 0) { negative = true; number = -number; } // Split number into integer and fractional parts. double iPart; double fPart = std::modf(number, &iPart); // If there's a fractional part, convert it and store in fStr. if (fPart != 0) { // Distance to the next double value. double next = std::nextafter(number, std::numeric_limits<double>::infinity()); double minDenorm = std::nextafter(0.0, std::numeric_limits<double>::infinity()); // Precision of the input (half the distance to the next double). // We only compute digits up to that precision. // Ensure that delta > 0 by clamping it by the min denormalized positive // double number. double delta = std::max(0.5 * (next - number), minDenorm); while (fPart > delta) { // Multiply by radix to find the next digit. fPart *= radix; delta *= radix; // Write the next digit. unsigned digit = static_cast<unsigned>(fPart); fStr.push_back(toRadixChar(digit, radix)); // Remove current digit from fPart to prepare for next iteration. fPart -= digit; // Round-to-even. if (fPart > 0.5 || (fPart == 0.5 && (digit & 1))) { // Must round up, necessitating changing written digits. if (fPart + delta > 1) { // Round because printing the next closest double would not give // closer results than rounding. The distance between the next // double and this one is large enough that at this point, we're // doing worse than rounding up if we were to print out the next // double precisely. while (true) { // Rounding requires backtracking to fix everything up. if (fStr.size() == 0) { // Rounding failed to stop in the fractional part, // so carry over to the integral part. ++iPart; break; } // Iterator to the last digit of the string. char &c = fStr.back(); unsigned digitForC = c <= '9' ? c - '0' : c - 'a' + 10; if (digitForC + 1 < radix) { // Can increment this digit, and we're done. c = toRadixChar(digitForC + 1, radix); break; } // We weren't able to increment, so this will be a trailing 0, // which we don't want to keep around anyway. So, pop the last // digit off the string and continue on. fStr.pop_back(); } // Rounded off the number, done writing the fractional string. break; } } } } // Now, create the integer part. if (iPart == 0) { result.push_back('0'); } else { // Write the number backwards, then reverse it. This simplifies the code. // Physical mantissa size. // Hidden bit is not included because it's not after the decimal point in // the binary scientific notation representation of a double. // We use this to calculate whether we have the precision required to know // what the next digit is going to be. constexpr const int MANTISSA_SIZE = DBL_MANT_DIG - 1; // Handle trailing zeros. while (doubleExponent(iPart / radix) > MANTISSA_SIZE) { // (iPart / radix) doesn't have enough precision to be useful here, // because its exponent is larger than the number of bits that the // mantissa can encode. So, just put a trailing zero, divide by radix, // and move on to the next digit. result.push_back('0'); iPart /= radix; } // Print the rest of the string when we know we have enough precision to // do so. while (iPart > 0) { // Cast digit to int because we know 2 <= digit <= 36. int digit = static_cast<int>(std::fmod(iPart, radix)); result.push_back(toRadixChar(digit, radix)); iPart = (iPart - digit) / radix; } // Int string was generated in reverse, so flip it. std::reverse(result.begin(), result.end()); } // Concatenate the fractional string on if it exists. if (!fStr.empty()) { result += '.'; result += fStr; } // Account for negative numbers. if (negative) { result.insert(result.begin(), '-'); } return runtime.makeHandle<StringPrimitive>(runtime.ignoreAllocationFailure( StringPrimitive::create(runtime, result))); } CallResult<PseudoHandle<>> getMethod(Runtime &runtime, Handle<> O, Handle<> key) { GCScopeMarkerRAII gcScope{runtime}; auto objRes = toObject(runtime, O); if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto obj = runtime.makeHandle<JSObject>(*objRes); auto funcRes = JSObject::getComputed_RJS(obj, runtime, key); if (LLVM_UNLIKELY(funcRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if ((*funcRes)->isUndefined() || (*funcRes)->isNull()) { return PseudoHandle<>::create(HermesValue::encodeUndefinedValue()); } if (!vmisa<Callable>(funcRes->get())) { return runtime.raiseTypeError("Could not get callable method from object"); } return funcRes; } CallResult<IteratorRecord> getIterator( Runtime &runtime, Handle<> obj, llvh::Optional<Handle<Callable>> methodOpt) { MutableHandle<Callable> method{runtime}; if (LLVM_LIKELY(!methodOpt.hasValue())) { auto methodRes = getMethod( runtime, obj, runtime.makeHandle( Predefined::getSymbolID(Predefined::SymbolIterator))); if (LLVM_UNLIKELY(methodRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if (!vmisa<Callable>(methodRes->getHermesValue())) { return runtime.raiseTypeError("iterator method is not callable"); } method = vmcast<Callable>(methodRes->getHermesValue()); } else { method = **methodOpt; } auto iteratorRes = Callable::executeCall0(method, runtime, obj); if (LLVM_UNLIKELY(iteratorRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if (LLVM_UNLIKELY(!(*iteratorRes)->isObject())) { return runtime.raiseTypeError("iterator is not an object"); } auto iterator = runtime.makeHandle<JSObject>(std::move(*iteratorRes)); CallResult<PseudoHandle<>> nextMethodRes = JSObject::getNamed_RJS( iterator, runtime, Predefined::getSymbolID(Predefined::next)); if (LLVM_UNLIKELY(nextMethodRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // We perform this check prior to returning, because every function in the JS // library which gets an iterator immediately calls the 'next' function. if (!vmisa<Callable>(nextMethodRes->get())) { return runtime.raiseTypeError("'next' method on iterator must be callable"); } auto nextMethod = Handle<Callable>::vmcast(runtime.makeHandle(std::move(*nextMethodRes))); return IteratorRecord{iterator, nextMethod}; } CallResult<PseudoHandle<JSObject>> iteratorNext( Runtime &runtime, const IteratorRecord &iteratorRecord, llvh::Optional<Handle<>> value) { GCScopeMarkerRAII marker{runtime}; auto resultRes = value ? Callable::executeCall1( iteratorRecord.nextMethod, runtime, iteratorRecord.iterator, value->getHermesValue()) : Callable::executeCall0( iteratorRecord.nextMethod, runtime, iteratorRecord.iterator); if (LLVM_UNLIKELY(resultRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if (LLVM_UNLIKELY(!(*resultRes)->isObject())) { return runtime.raiseTypeError("iterator.next() did not return an object"); } return PseudoHandle<JSObject>::vmcast(std::move(*resultRes)); } CallResult<Handle<JSObject>> iteratorStep( Runtime &runtime, const IteratorRecord &iteratorRecord) { auto resultRes = iteratorNext(runtime, iteratorRecord); if (LLVM_UNLIKELY(resultRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } Handle<JSObject> result = runtime.makeHandle(std::move(*resultRes)); auto completeRes = JSObject::getNamed_RJS( result, runtime, Predefined::getSymbolID(Predefined::done)); if (LLVM_UNLIKELY(completeRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if (toBoolean(completeRes->get())) { return Runtime::makeNullHandle<JSObject>(); } return result; } ExecutionStatus iteratorClose( Runtime &runtime, Handle<JSObject> iterator, Handle<> completion) { ExecutionStatus completionStatus = completion->isEmpty() ? ExecutionStatus::RETURNED : ExecutionStatus::EXCEPTION; // 4. Let innerResult be GetMethod(iterator, "return"). // Do this lazily: innerResult is only actually used if GetMethod returns // a callable which, when called, doesn't throw. Defer storing to innerResult // until that point. auto returnRes = getMethod( runtime, iterator, runtime.makeHandle(Predefined::getSymbolID(Predefined::returnStr))); MutableHandle<> innerResult{runtime}; if (LLVM_LIKELY(returnRes != ExecutionStatus::EXCEPTION)) { if (!vmisa<Callable>(returnRes->getHermesValue())) { runtime.setThrownValue(*completion); return completionStatus; } Handle<Callable> returnFn = runtime.makeHandle(vmcast<Callable>(returnRes->getHermesValue())); auto innerResultRes = Callable::executeCall0(returnFn, runtime, iterator); if (LLVM_UNLIKELY(innerResultRes == ExecutionStatus::EXCEPTION)) { if (isUncatchableError(runtime.getThrownValue())) { // If the call to return threw an uncatchable exception, that overrides // the completion, since the point of an uncatchable exception is to // prevent more JS from executing. return ExecutionStatus::EXCEPTION; } // If the error is catchable, suppress it temporarily below in lieu // of the returnRes exception by writing to innerResultException. // Spec text overwrites the value in `innerResult`. } else { innerResult = std::move(*innerResultRes); } } // Runtime::thrownValue now contains the innerResult's exception if it // was thrown. // GetMethod error here is deliberately suppressed (no "?" in the spec). if (completionStatus == ExecutionStatus::EXCEPTION) { // 6. If completion.[[Type]] is throw, return Completion(completion). // Note: Overrides the innerResult exception. runtime.setThrownValue(*completion); return ExecutionStatus::EXCEPTION; } if (LLVM_UNLIKELY(!runtime.getThrownValue().isEmpty())) { // 7. If innerResult.[[Type]] is throw, return Completion(innerResult). // Note: innerResult exception is still in Runtime::thrownValue, // so there is no need to set it again. return ExecutionStatus::EXCEPTION; } if (!innerResult->isObject()) { // 8. If Type(innerResult.[[Value]]) is not Object, // throw a TypeError exception. return runtime.raiseTypeError("iterator.return() did not return an object"); } return ExecutionStatus::RETURNED; } bool isUncatchableError(HermesValue value) { if (auto *jsError = dyn_vmcast<JSError>(value)) { return !jsError->catchable(); } return false; } Handle<JSObject> createIterResultObject(Runtime &runtime, Handle<> value, bool done) { auto objHandle = runtime.makeHandle(JSObject::create(runtime)); auto status = JSObject::defineOwnProperty( objHandle, runtime, Predefined::getSymbolID(Predefined::value), DefinePropertyFlags::getDefaultNewPropertyFlags(), value); (void)status; assert( status != ExecutionStatus::EXCEPTION && *status && "put own value property cannot fail"); status = JSObject::defineOwnProperty( objHandle, runtime, Predefined::getSymbolID(Predefined::done), DefinePropertyFlags::getDefaultNewPropertyFlags(), Runtime::getBoolValue(done)); assert( status != ExecutionStatus::EXCEPTION && *status && "put own value property cannot fail"); return objHandle; } CallResult<Handle<Callable>> speciesConstructor( Handle<JSObject> O, Runtime &runtime, Handle<Callable> defaultConstructor) { // construct from the "constructor" property in self if that is defined, else // use the default one. auto res = JSObject::getNamed_RJS( O, runtime, Predefined::getSymbolID(Predefined::constructor)); if (res == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } PseudoHandle<> cons = std::move(*res); if (cons->isUndefined()) { return defaultConstructor; } if (!cons->isObject()) { return runtime.raiseTypeError( "Constructor must be an object if it is not undefined"); } // There is no @@species (no Symbols yet), so we'll assume that there was // no other constructor specified. return defaultConstructor; } CallResult<bool> isConstructor(Runtime &runtime, HermesValue value) { return isConstructor(runtime, dyn_vmcast<Callable>(value)); } CallResult<bool> isConstructor(Runtime &runtime, Callable *callable) { // This is not a complete definition, since ES6 and later define member // functions of objects to not be constructors; however, Hermes does not have // ES6 classes implemented yet, so we cannot check for that case. if (!callable) { return false; } // We traverse the BoundFunction target chain to find the eventual target. while (BoundFunction *b = dyn_vmcast<BoundFunction>(callable)) { callable = b->getTarget(runtime); } // If it is a bytecode function, check the flags. if (auto *func = dyn_vmcast<JSFunction>(callable)) { auto *cb = func->getCodeBlock(); // Even though it doesn't make sense logically, we need to compile the // function in order to access it flags. cb->lazyCompile(runtime); return !func->getCodeBlock()->getHeaderFlags().isCallProhibited(true); } // We check for NativeFunction since those are defined to not be // constructible, with the exception of NativeConstructor. if (!vmisa<NativeFunction>(callable) || vmisa<NativeConstructor>(callable)) { return true; } // JSCallableProxy is a NativeFunction, but may or may not be a // constructor, so we ask it. if (auto *cproxy = dyn_vmcast<JSCallableProxy>(callable)) { return cproxy->isConstructor(runtime); } return false; } CallResult<bool> ordinaryHasInstance(Runtime &runtime, Handle<> constructor, Handle<> object) { // 1. If IsCallable(C) is false, return false. if (!vmisa<Callable>(*constructor)) { return false; } Callable *ctor = vmcast<Callable>(*constructor); BoundFunction *bound; // 2. If C has a [[BoundTargetFunction]] internal slot, then while (LLVM_UNLIKELY(bound = dyn_vmcast<BoundFunction>(ctor))) { // 2a. Let BC be the value of C’s [[BoundTargetFunction]] internal slot. // 2b. Return InstanceofOperator(O,BC) (see 12.9.4). // Note that we can do this with the loop instead, // because bound->getTarget() must be a Callable, and Callables cannot // redefine @@hasInstance (non-configurable). // Callables call this function directly from their @@hasInstance // function. ctor = bound->getTarget(runtime); } // At this point 'ctor' is the actual function with a prototype. assert(ctor != nullptr && "ctor must not be null"); // 3. If Type(O) is not Object, return false. if (LLVM_UNLIKELY(!object->isObject())) { return false; } // 4. Let P be Get(C, "prototype"). auto propRes = JSObject::getNamed_RJS( runtime.makeHandle(ctor), runtime, Predefined::getSymbolID(Predefined::prototype)); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 5. If Type(P) is not Object, throw a TypeError exception. Handle<JSObject> ctorPrototype = runtime.makeHandle( PseudoHandle<JSObject>::dyn_vmcast(std::move(*propRes))); if (LLVM_UNLIKELY(!ctorPrototype)) { return runtime.raiseTypeError( "function's '.prototype' is not an object in 'instanceof'"); } // 6.1.7.3 Invariants of the Essential Internal Methods notes that // detection of infinite prototype chains is not enforceable as an // invariant if exotic objects exist in the chain. Most of the // time, ScopedNativeDepthTracker will detect this. Here, we need to // check that we're not repeating forever. Since ordinary object // chains are verified at the time the parent is set, we count Proxy // objects. Thus, any length chain of ordinary objects is ok. constexpr unsigned int kMaxProxyCount = 1024; unsigned int proxyCount = 0; MutableHandle<JSObject> head{runtime, vmcast<JSObject>(object.get())}; GCScopeMarkerRAII gcScope{runtime}; // 6. Repeat while (true) { // 6a. Let O be O.[[GetPrototypeOf]](). CallResult<PseudoHandle<JSObject>> parentRes = JSObject::getPrototypeOf(head, runtime); if (LLVM_UNLIKELY(parentRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 6b. If O is null, return false. if (!*parentRes) { return false; } // 6c. If SameValue(P, O) is true, return true. if (parentRes->get() == ctorPrototype.get()) { return true; } if (head->isProxyObject()) { ++proxyCount; if (proxyCount > kMaxProxyCount) { return runtime.raiseRangeError( "Maximum prototype chain length exceeded"); } } head = parentRes->get(); gcScope.flush(); } } CallResult<bool> instanceOfOperator_RJS( Runtime &runtime, Handle<> object, Handle<> constructor) { // 1. If Type(C) is not Object, throw a TypeError exception. if (LLVM_UNLIKELY(!constructor->isObject())) { return runtime.raiseTypeError( "right operand of 'instanceof' is not an object"); } // Fast path: Function.prototype[Symbol.hasInstance] is non-configurable // and non-writable (ES6.0 19.2.3.6), so we directly run its behavior here. // Simply call through to ordinaryHasInstance. if (vmisa<JSFunction>(*constructor)) { return ordinaryHasInstance(runtime, constructor, object); } // 2. Let instOfHandler be GetMethod(C,@@hasInstance). CallResult<PseudoHandle<>> instOfHandlerRes = JSObject::getNamed_RJS( Handle<JSObject>::vmcast(constructor), runtime, Predefined::getSymbolID(Predefined::SymbolHasInstance)); if (LLVM_UNLIKELY(instOfHandlerRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } auto instOfHandler = runtime.makeHandle(std::move(*instOfHandlerRes)); // 4. If instOfHandler is not undefined, then if (!instOfHandler->isUndefined()) { // 5. Return ToBoolean(Call(instOfHandler, C, «O»)). if (!vmisa<Callable>(*instOfHandler)) { return runtime.raiseTypeError("instanceof handler must be callable"); } auto callRes = Callable::executeCall1( Handle<Callable>::vmcast(instOfHandler), runtime, constructor, *object); if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } return toBoolean(callRes->get()); } // 6. If IsCallable(C) is false, throw a TypeError exception. if (!vmisa<Callable>(*constructor)) { return runtime.raiseTypeError( "right operand of 'instanceof' is not callable"); } // 7. Return OrdinaryHasInstance(C, O). return ordinaryHasInstance(runtime, constructor, object); } /// ES6.0 7.2.8 /// Returns true if the object is a JSRegExp or has a Symbol.match property that /// evaluates to true. CallResult<bool> isRegExp(Runtime &runtime, Handle<> arg) { // 1. If Type(argument) is not Object, return false. if (!arg->isObject()) { return false; } Handle<JSObject> obj = Handle<JSObject>::vmcast(arg); // 2. Let isRegExp be Get(argument, @@match). auto propRes = JSObject::getNamed_RJS( obj, runtime, Predefined::getSymbolID(Predefined::SymbolMatch)); // 3. ReturnIfAbrupt(isRegExp). if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } // 4. If isRegExp is not undefined, return ToBoolean(isRegExp). if (!(*propRes)->isUndefined()) { return toBoolean(propRes->get()); } // 5. If argument has a [[RegExpMatcher]] internal slot, return true. // 6. Return false. return vmisa<JSRegExp>(arg.get()); } CallResult<Handle<StringPrimitive>> symbolDescriptiveString( Runtime &runtime, Handle<SymbolID> sym) { // 1. Assert: Type(sym) is Symbol. // 2. Let desc be sym's [[Description]] value. // 3. If desc is undefined, set desc to the empty string. // 4. Assert: Type(desc) is String. auto desc = runtime.makeHandle<StringPrimitive>( runtime.getStringPrimFromSymbolID(*sym)); SafeUInt32 descLen(desc->getStringLength()); descLen.add(8); // 5. Return the string-concatenation of "Symbol(", desc, and ")". auto builder = StringBuilder::createStringBuilder(runtime, descLen); if (LLVM_UNLIKELY(builder == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } builder->appendASCIIRef({"Symbol(", 7}); builder->appendStringPrim(desc); builder->appendCharacter(')'); return builder->getStringPrimitive(); } CallResult<bool> isArray(Runtime &runtime, JSObject *obj) { if (!obj) { return false; } while (true) { if (vmisa<JSArray>(obj)) { return true; } if (LLVM_LIKELY(!obj->isProxyObject())) { return false; } if (JSProxy::isRevoked(obj, runtime)) { return runtime.raiseTypeError("Proxy has been revoked"); } obj = JSProxy::getTarget(obj, runtime).get(); assert(obj && "target of non-revoked Proxy is null"); } } CallResult<bool> isConcatSpreadable(Runtime &runtime, Handle<> value) { auto O = Handle<JSObject>::dyn_vmcast(value); if (!O) { return false; } CallResult<PseudoHandle<>> spreadable = JSObject::getNamed_RJS( O, runtime, Predefined::getSymbolID(Predefined::SymbolIsConcatSpreadable)); if (LLVM_UNLIKELY(spreadable == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } if (!(*spreadable)->isUndefined()) { return toBoolean(spreadable->get()); } return isArray(runtime, *O); } ExecutionStatus toPropertyDescriptor( Handle<> obj, Runtime &runtime, DefinePropertyFlags &flags, MutableHandle<> &valueOrAccessor) { GCScopeMarkerRAII gcMarker{runtime}; // Verify that the attributes argument is also an object. auto attributes = Handle<JSObject>::dyn_vmcast(obj); if (!attributes) { return runtime.raiseTypeError( "Object.defineProperty() Attributes argument is not an object"); } NamedPropertyDescriptor desc; // Get enumerable property of the attributes. if (JSObject::getNamedDescriptorPredefined( attributes, runtime, Predefined::enumerable, desc)) { auto propRes = JSObject::getNamed_RJS( attributes, runtime, Predefined::getSymbolID(Predefined::enumerable), PropOpFlags().plusThrowOnError()); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } flags.enumerable = toBoolean(propRes->get()); flags.setEnumerable = true; } // Get configurable property of the attributes. if (JSObject::getNamedDescriptorPredefined( attributes, runtime, Predefined::configurable, desc)) { auto propRes = JSObject::getNamed_RJS( attributes, runtime, Predefined::getSymbolID(Predefined::configurable), PropOpFlags().plusThrowOnError()); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } flags.configurable = toBoolean(propRes->get()); flags.setConfigurable = true; } // Get value property of the attributes. if (JSObject::getNamedDescriptorPredefined( attributes, runtime, Predefined::value, desc)) { auto propRes = JSObject::getNamed_RJS( attributes, runtime, Predefined::getSymbolID(Predefined::value), PropOpFlags().plusThrowOnError()); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } valueOrAccessor = std::move(*propRes); flags.setValue = true; } // Get writable property of the attributes. if (JSObject::getNamedDescriptorPredefined( attributes, runtime, Predefined::writable, desc)) { auto propRes = JSObject::getNamed_RJS( attributes, runtime, Predefined::getSymbolID(Predefined::writable), PropOpFlags().plusThrowOnError()); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } flags.writable = toBoolean(propRes->get()); flags.setWritable = true; } // Get getter property of the attributes. MutableHandle<Callable> getterPtr{runtime}; if (JSObject::getNamedDescriptorPredefined( attributes, runtime, Predefined::get, desc)) { auto propRes = JSObject::getNamed_RJS( attributes, runtime, Predefined::getSymbolID(Predefined::get), PropOpFlags().plusThrowOnError()); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } flags.setGetter = true; PseudoHandle<> getter = std::move(*propRes); if (LLVM_LIKELY(!getter->isUndefined())) { getterPtr = dyn_vmcast<Callable>(getter.get()); if (LLVM_UNLIKELY(!getterPtr)) { return runtime.raiseTypeError( "Invalid property descriptor. Getter must be a function."); } } } // Get setter property of the attributes. MutableHandle<Callable> setterPtr{runtime}; if (JSObject::getNamedDescriptorPredefined( attributes, runtime, Predefined::set, desc)) { auto propRes = JSObject::getNamed_RJS( attributes, runtime, Predefined::getSymbolID(Predefined::set), PropOpFlags().plusThrowOnError()); if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } flags.setSetter = true; PseudoHandle<> setter = std::move(*propRes); if (LLVM_LIKELY(!setter->isUndefined())) { setterPtr = PseudoHandle<Callable>::dyn_vmcast(std::move(setter)); if (LLVM_UNLIKELY(!setterPtr)) { return runtime.raiseTypeError( "Invalid property descriptor. Setter must be a function."); } } } // Construct property accessor if getter/setter is set. if (flags.setSetter || flags.setGetter) { if (flags.setValue) { return runtime.raiseTypeError( "Invalid property descriptor. Can't set both accessor and value."); } if (flags.setWritable) { return runtime.raiseTypeError( "Invalid property descriptor. Can't set both accessor and writable."); } auto crtRes = PropertyAccessor::create(runtime, getterPtr, setterPtr); if (LLVM_UNLIKELY(crtRes == ExecutionStatus::EXCEPTION)) { return ExecutionStatus::EXCEPTION; } valueOrAccessor = *crtRes; } return ExecutionStatus::RETURNED; } CallResult<HermesValue> objectFromPropertyDescriptor( Runtime &runtime, ComputedPropertyDescriptor desc, Handle<> valueOrAccessor) { Handle<JSObject> obj = runtime.makeHandle(JSObject::create(runtime)); DefinePropertyFlags dpf = DefinePropertyFlags::getDefaultNewPropertyFlags(); if (!desc.flags.accessor) { // Data Descriptor auto result = JSObject::defineOwnProperty( obj, runtime, Predefined::getSymbolID(Predefined::value), dpf, valueOrAccessor, PropOpFlags().plusThrowOnError()); assert( result != ExecutionStatus::EXCEPTION && "defineOwnProperty() failed on a new object"); if (result == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } result = JSObject::defineOwnProperty( obj, runtime, Predefined::getSymbolID(Predefined::writable), dpf, Runtime::getBoolValue(desc.flags.writable), PropOpFlags().plusThrowOnError()); assert( result != ExecutionStatus::EXCEPTION && "defineOwnProperty() failed on a new object"); if (result == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } } else { // Accessor auto *accessor = vmcast<PropertyAccessor>(valueOrAccessor.get()); auto getter = runtime.makeHandle( accessor->getter ? HermesValue::encodeObjectValue( accessor->getter.getNonNull(runtime)) : HermesValue::encodeUndefinedValue()); auto setter = runtime.makeHandle( accessor->setter ? HermesValue::encodeObjectValue( accessor->setter.getNonNull(runtime)) : HermesValue::encodeUndefinedValue()); auto result = JSObject::defineOwnProperty( obj, runtime, Predefined::getSymbolID(Predefined::get), dpf, getter, PropOpFlags().plusThrowOnError()); assert( result != ExecutionStatus::EXCEPTION && "defineOwnProperty() failed on a new object"); if (result == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } result = JSObject::defineOwnProperty( obj, runtime, Predefined::getSymbolID(Predefined::set), dpf, setter, PropOpFlags().plusThrowOnError()); assert( result != ExecutionStatus::EXCEPTION && "defineOwnProperty() failed on a new object"); if (result == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } } auto result = JSObject::defineOwnProperty( obj, runtime, Predefined::getSymbolID(Predefined::enumerable), dpf, Runtime::getBoolValue(desc.flags.enumerable), PropOpFlags().plusThrowOnError()); assert( result != ExecutionStatus::EXCEPTION && "defineOwnProperty() failed on a new object"); if (result == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } result = JSObject::defineOwnProperty( obj, runtime, Predefined::getSymbolID(Predefined::configurable), dpf, Runtime::getBoolValue(desc.flags.configurable), PropOpFlags().plusThrowOnError()); assert( result != ExecutionStatus::EXCEPTION && "defineOwnProperty() failed on a new object"); if (result == ExecutionStatus::EXCEPTION) { return ExecutionStatus::EXCEPTION; } return obj.getHermesValue(); } } // namespace vm } // namespace hermes