// Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 #pragma once // INTERNAL IMPLEMENTATION FILE // // Handling of various basic value types: numbers, booleans, strings, optionals, maybes, variants, // arrays, buffers, dicts. #include #include #include #include #include #include #include #include #include namespace workerd::jsg { // ======================================================================================= // Primitives (numbers, booleans) // TypeWrapper mixin for numbers and booleans. // // This wrapper has extra wrap() overloads that take an isolate instead of a // lock and a context. This is used to implement static constants in // JavaScript: we need to be able to wrap C++ constants in V8 values before a // context has been entered. // // Note that we can't generally change the wrap(js, context, ...) functions to wrap(isolate, ...) // because ResourceWrapper::wrap() needs the context to create new object instances. class PrimitiveWrapper { public: static constexpr const char* getName(double*) { return "number"; } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, double value) { return wrap(js.v8Isolate, creator, value); } v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, double value) { return v8::Number::New(isolate, value); } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, double*, kj::Maybe> parentObject) { return check(handle->ToNumber(context))->Value(); } static constexpr const char* getName(int8_t*) { return "byte"; } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, int8_t value) { return wrap(js.v8Isolate, creator, value); } v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, int8_t value) { return v8::Integer::New(isolate, value); } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, int8_t*, kj::Maybe> parentObject) { auto value = check(handle->ToNumber(context))->Value(); JSG_REQUIRE( isFinite(value), TypeError, "The value cannot be converted because it is not an integer."); JSG_REQUIRE( value <= static_cast(kj::maxValue) && value >= static_cast(kj::minValue), TypeError, kj::str("Value out of range. Must be between ", static_cast(kj::minValue), " and ", static_cast(kj::maxValue), " (inclusive).")); return static_cast(value); } static constexpr const char* getName(uint8_t*) { return "octet"; } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, uint8_t value) { return wrap(js.v8Isolate, creator, value); } v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, uint8_t value) { return v8::Integer::NewFromUnsigned(isolate, value); } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, uint8_t*, kj::Maybe> parentObject) { auto value = check(handle->ToNumber(context))->Value(); JSG_REQUIRE( isFinite(value), TypeError, "The value cannot be converted because it is not an integer."); JSG_REQUIRE(value >= 0, TypeError, "The value cannot be converted because it is negative and this " "API expects a positive number."); JSG_REQUIRE(value <= static_cast(kj::maxValue), TypeError, kj::str("Value out of range. Must be less than or equal to ", static_cast(kj::maxValue), ".")); return static_cast(value); } static constexpr const char* getName(int16_t*) { return "short integer"; } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, int16_t value) { return wrap(js.v8Isolate, creator, value); } v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, int16_t value) { return v8::Number::New(isolate, value); } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, int16_t*, kj::Maybe> parentObject) { auto value = check(handle->ToNumber(context))->Value(); JSG_REQUIRE( isFinite(value), TypeError, "The value cannot be converted because it is not an integer."); JSG_REQUIRE( value <= static_cast(kj::maxValue) && value >= static_cast(kj::minValue), TypeError, kj::str("Value out of range. Must be between ", static_cast(kj::minValue), " and ", static_cast(kj::maxValue), " (inclusive).")); return static_cast(value); } static constexpr const char* getName(uint16_t*) { return "unsigned short integer"; } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, uint16_t value) { return wrap(js.v8Isolate, creator, value); } v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, uint16_t value) { return v8::Integer::NewFromUnsigned(isolate, value); } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, uint16_t*, kj::Maybe> parentObject) { auto value = check(handle->ToNumber(context))->Value(); JSG_REQUIRE( isFinite(value), TypeError, "The value cannot be converted because it is not an integer."); JSG_REQUIRE(value >= 0, TypeError, "The value cannot be converted because it is negative and this " "API expects a positive number."); JSG_REQUIRE(value <= static_cast(kj::maxValue), TypeError, kj::str("Value out of range. Must be less than or equal to ", static_cast(kj::maxValue), ".")); return static_cast(value); } static constexpr const char* getName(int*) { return "integer"; } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, int value) { return wrap(js.v8Isolate, creator, value); } v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, int value) { return v8::Number::New(isolate, value); } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, int*, kj::Maybe> parentObject) { if (int num; handle->IsInt32() && handle->Int32Value(context).To(&num)) { return num; } auto value = check(handle->ToNumber(context))->Value(); if (!isFinite(value)) { return 0; } // One would think that RangeError is more appropriate than TypeError, // but WebIDL says it should be TypeError. JSG_REQUIRE(value <= static_cast(kj::maxValue) && value >= static_cast(kj::minValue), TypeError, kj::str("Value out of range. Must be between ", static_cast(kj::minValue), " and ", static_cast(kj::maxValue), " (inclusive).")); return static_cast(value); } static constexpr const char* getName(uint32_t*) { return "unsigned integer"; } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, uint32_t value) { return wrap(js.v8Isolate, creator, value); } v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, uint32_t value) { return v8::Integer::NewFromUnsigned(isolate, value); } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, uint32_t*, kj::Maybe> parentObject) { if (uint32_t num; handle->IsUint32() && handle->Uint32Value(context).To(&num)) { return num; } auto value = check(handle->ToNumber(context))->Value(); JSG_REQUIRE( isFinite(value), TypeError, "The value cannot be converted because it is not an integer."); JSG_REQUIRE(value >= 0, TypeError, "The value cannot be converted because it is negative and this " "API expects a positive number."); JSG_REQUIRE(value <= static_cast(kj::maxValue), TypeError, kj::str("Value out of range. Must be less than or equal to ", static_cast(kj::maxValue), ".")); return static_cast(value); } static constexpr const char* getName(uint64_t*) { return "bigint"; } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, uint64_t value) { return wrap(js.v8Isolate, creator, value); } v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, uint64_t value) { return v8::BigInt::New(isolate, value); } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, uint64_t*, kj::Maybe> parentObject) { if (v8::Local bigint; handle->IsBigInt() && handle->ToBigInt(context).ToLocal(&bigint)) { bool lossless; auto value = bigint->Uint64Value(&lossless); JSG_REQUIRE(lossless, TypeError, "The value cannot be converted because it is either negative and this " "API expects a positive bigint, or the value would be truncated."); return value; } auto value = check(handle->ToNumber(context))->Value(); JSG_REQUIRE( isFinite(value), TypeError, "The value cannot be converted because it is not an integer."); JSG_REQUIRE(value >= 0, TypeError, "The value cannot be converted because it is negative and this " "API expects a positive bigint."); JSG_REQUIRE(value <= static_cast(kj::maxValue), TypeError, kj::str("Value out of range. Must be less than or equal to ", static_cast(kj::maxValue), ".")); return static_cast(value); } static constexpr const char* getName(int64_t*) { return "bigint"; } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, int64_t value) { return wrap(js.v8Isolate, creator, value); } v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, int64_t value) { return v8::BigInt::New(isolate, value); } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, int64_t*, kj::Maybe> parentObject) { if (v8::Local bigint; handle->IsBigInt() && handle->ToBigInt(context).ToLocal(&bigint)) { bool lossless; auto value = bigint->Int64Value(&lossless); JSG_REQUIRE( lossless, TypeError, "The value cannot be converted because it would be truncated."); return value; } auto value = check(handle->ToNumber(context))->Value(); JSG_REQUIRE( isFinite(value), TypeError, "The value cannot be converted because it is not an integer."); JSG_REQUIRE( value <= static_cast(kj::maxValue) && value >= static_cast(kj::minValue), TypeError, kj::str("Value out of range. Must be between ", static_cast(kj::minValue), " and ", static_cast(kj::maxValue), " (inclusive).")); return static_cast(value); } static constexpr const char* getName(bool*) { return "boolean"; } template v8::Local wrap( Lock& js, v8::Local context, kj::Maybe> creator, T value) { // The template is needed to prevent this overload from being chosen for arbitrary types that // can convert to bool, such as pointers. return wrap(js.v8Isolate, creator, value); } template v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, T value) { // The template is needed to prevent this overload from being chosen for arbitrary types that // can convert to bool, such as pointers. return v8::Boolean::New(isolate, value); } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, bool*, kj::Maybe> parentObject) { return handle->ToBoolean(js.v8Isolate)->Value(); } }; // ======================================================================================= // Name class NameWrapper { public: static constexpr const char* getName(Name*) { return "string or Symbol"; } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, Name value) { KJ_SWITCH_ONEOF(value.getUnwrapped(js.v8Isolate)) { KJ_CASE_ONEOF(string, kj::StringPtr) { return js.str(string); } KJ_CASE_ONEOF(symbol, v8::Local) { return symbol; } } KJ_UNREACHABLE; } kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, Name*, kj::Maybe> parentObject) { if (handle->IsSymbol()) { return Name(js, handle.As()); } // Since most things are coercible to a string, this ought to catch pretty much // any value other than symbol return Name(js.toString(handle)); } }; // ======================================================================================= // Strings // TypeWrapper mixin for strings. // // This wrapper has an extra wrap() overload that takes an isolate instead of a context, for the // same reason discussed in PrimitiveWrapper. class StringWrapper { public: // TODO(someday): The conversion to kj::String doesn't explicitly consider the distinction // between DOMString (~ WTF-8; could contain invalid code points) and USVString (invalid code // points are always replaced with U+FFFD). Code should make an explict choice between the two. template requires(kj::isSameType>() || kj::isSameType>() || kj::isSameType()) static constexpr const char* getName(T*) { return "string"; } template requires(kj::isSameType() || kj::isSameType()) static constexpr const char* getName(T*) { if constexpr (kj::isSameType()) { return "USVString"; } else { static_assert(kj::isSameType()); return "DOMString"; } } v8::Local wrap( v8::Isolate* isolate, kj::Maybe> creator, kj::StringPtr value) { return v8Str(isolate, value); } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, kj::ArrayPtr value) { return v8Str(js.v8Isolate, value); } v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, kj::Array value) { return wrap(js, context, creator, value.asPtr()); } template requires(kj::isSameType() || kj::isSameType()) v8::Local wrap( Lock& js, v8::Local context, kj::Maybe> creator, T value) { // TODO(cleanup): Move to a HeaderStringWrapper in the api directory. return v8Str(js.v8Isolate, value.asPtr()); } template requires(kj::isSameType() || kj::isSameType() || kj::isSameType()) kj::Maybe tryUnwrap(Lock& js, v8::Local context, v8::Local handle, T*, kj::Maybe> parentObject) { // Note that if handle is already a string, calling ToString will just // return handle without any further coercion. For any other type of // value, v8 will try to coerce it into a string. So there is no need // for us to check if handle is a string here or not, ToString does // that for us. JsString str(check(handle->ToString(context))); if constexpr (kj::isSameType()) { return str.toString(js); } else if constexpr (kj::isSameType()) { return str.toUSVString(js); } else if constexpr (kj::isSameType()) { return str.toDOMString(js); } KJ_UNREACHABLE; } }; // ======================================================================================= // Optional (value or undefined) and Maybe (value or null) // TypeWrapper mixin for optionals. template class OptionalWrapper { public: template static constexpr decltype(auto) getName(Optional*) { return TypeWrapper::getName(static_cast*>(nullptr)); } template v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, Optional ptr) { KJ_IF_SOME(p, ptr) { return static_cast(this)->wrap(js, context, creator, kj::fwd(p)); } else { return js.undefined(); } } template kj::Maybe> tryUnwrap(Lock& js, v8::Local context, v8::Local handle, Optional*, kj::Maybe> parentObject) { if (handle->IsUndefined()) { return Optional(kj::none); } else { return static_cast(this) ->tryUnwrap(js, context, handle, static_cast*>(nullptr), parentObject) .map([](auto&& value) -> Optional { return kj::fwd(value); }); } } }; // TypeWrapper mixin for lenient optionals. template class LenientOptionalWrapper { public: template static constexpr decltype(auto) getName(LenientOptional*) { return TypeWrapper::getName(static_cast*>(nullptr)); } template v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, LenientOptional ptr) { KJ_IF_SOME(p, ptr) { return static_cast(this)->wrap(js, context, creator, kj::fwd(p)); } else { return js.undefined(); } } template kj::Maybe> tryUnwrap(Lock& js, v8::Local context, v8::Local handle, LenientOptional*, kj::Maybe> parentObject) { if (handle->IsUndefined()) { return LenientOptional(kj::none); } else { KJ_IF_SOME(unwrapped, static_cast(this)->tryUnwrap( js, context, handle, static_cast*>(nullptr), parentObject)) { return LenientOptional(kj::mv(unwrapped)); } else { return LenientOptional(kj::none); } } } }; // TypeWrapper mixin for maybes. template class MaybeWrapper { public: // The constructor here is a bit of a hack. The config is optional and might not be a JsgConfig // object (or convertible to a JsgConfig) if is provided. However, because of the way TypeWrapper // inherits MaybeWrapper, we always end up passing a config option (which might be std::nullptr_t) // The getConfig allows us to handle any case using reasonable defaults. MaybeWrapper(const auto& config): config(getConfig(config)) {} template static constexpr decltype(auto) getName(kj::Maybe*) { return TypeWrapper::getName(static_cast*>(nullptr)); } template v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, kj::Maybe ptr) { KJ_IF_SOME(p, ptr) { return static_cast(this)->wrap(js, context, creator, kj::fwd(p)); } else { return js.null(); } } template kj::Maybe> tryUnwrap(Lock& js, v8::Local context, v8::Local handle, kj::Maybe*, kj::Maybe> parentObject) { if (handle->IsNullOrUndefined()) { return kj::Maybe(kj::none); } else if (config.noSubstituteNull) { // There was a bug in the initial version of this method that failed to correctly handle // the following tryUnwrap returning a nullptr because of an incorrect type. The // noSubstituteNull compatibility flag is needed to fix that. return static_cast(this) ->tryUnwrap(js, context, handle, static_cast*>(nullptr), parentObject) .map([](auto&& value) -> kj::Maybe { return kj::fwd(value); }); } else { return static_cast(this)->tryUnwrap( js, context, handle, static_cast*>(nullptr), parentObject); } } private: const JsgConfig config; }; // ======================================================================================= // OneOf / variants template constexpr bool isOneOf = false; template constexpr bool isOneOf> = true; // TODO(cleanup): Move to kj/one-of.h? // TypeWrapper mixin for variants. template class OneOfWrapper { public: template static kj::String getName(kj::OneOf*) { const auto getNameStr = [](auto u) { if constexpr (kj::isSameType()) { return typeName(TypeWrapper::getName(u)); } else { return kj::str(TypeWrapper::getName(u)); } }; return kj::strArray(kj::arr(getNameStr(static_cast(nullptr))...), " or "); } template bool wrapHelper(Lock& js, v8::Local context, kj::Maybe> creator, kj::OneOf& in, v8::Local& out) { if (in.template is()) { out = static_cast(this)->wrap(js, context, creator, kj::mv(in.template get())); return true; } else { return false; } } template v8::Local wrap(Lock& js, v8::Local context, kj::Maybe> creator, kj::OneOf value) { v8::Local result; if (!(wrapHelper(js, context, creator, value, result) || ...)) { result = js.undefined(); } return result; } template