File
Blob: src/workerd/jsg/util.h
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | #pragma once |
| 6 | // INTERNAL IMPLEMENTATION FILE |
| 7 | // |
| 8 | // This file contains misc utility functions used elsewhere. |
| 9 | #include <workerd/jsg/exception.h> |
| 10 | |
| 11 | #include <v8-array-buffer.h> |
| 12 | #include <v8-exception.h> |
| 13 | #include <v8-primitive.h> |
| 14 | #include <v8-promise.h> |
| 15 | |
| 16 | #include <kj/debug.h> |
| 17 | #include <kj/exception.h> |
| 18 | #include <kj/string.h> |
| 19 | |
| 20 | #include <typeinfo> |
| 21 | |
| 22 | namespace v8 { |
| 23 | class Isolate; |
| 24 | } |
| 25 | namespace workerd::jsg { |
| 26 | |
| 27 | class Lock; |
| 28 | class JsObject; |
| 29 | |
| 30 | using uint = unsigned int; |
| 31 | |
| 32 | #define JS_ERROR_TYPES(V) \ |
| 33 | V("Error", Error) \ |
| 34 | V("RangeError", RangeError) \ |
| 35 | V("TypeError", TypeError) \ |
| 36 | V("SyntaxError", SyntaxError) \ |
| 37 | V("ReferenceError", ReferenceError) \ |
| 38 | V("CompileError", WasmCompileError) \ |
| 39 | V("LinkError", WasmLinkError) \ |
| 40 | V("RuntimeError", WasmRuntimeError) \ |
| 41 | V("SuspendError", WasmSuspendError) \ |
| 42 | V("AggregateError", AggregateError) \ |
| 43 | V("SuppressedError", SuppressedError) \ |
| 44 | V("URIError", URIError) \ |
| 45 | V("EvalError", EvalError) |
| 46 | |
| 47 | // When a C++ callback wishes to throw a JavaScript exception, it should first call |
| 48 | // isolate->ThrowException() to set the JavaScript error value, then it should throw |
| 49 | // JsExceptionThrown() as a C++ exception. This will be caught by the callback glue before the |
| 50 | // code returns to V8. |
| 51 | // |
| 52 | // This differs from the usual convention in V8 which is to return a v8::Maybe that is null in the |
| 53 | // case an exception is thrown. Writing code that deals with maybes is cumbersome and error-prone |
| 54 | // compared to C++ exceptions. |
| 55 | class JsExceptionThrown: public std::exception { |
| 56 | public: |
| 57 | JsExceptionThrown(); |
| 58 | ~JsExceptionThrown() noexcept = default; // We must match `std::exception`'s noexcept. |
| 59 | const char* what() const noexcept override; |
| 60 | |
| 61 | private: |
| 62 | void* trace[16]; |
| 63 | kj::ArrayPtr<void* const> tracePtr; |
| 64 | mutable kj::String whatBuffer; |
| 65 | }; |
| 66 | |
| 67 | bool getCaptureThrowsAsRejections(v8::Isolate* isolate); |
| 68 | bool getShouldSetToStringTag(v8::Isolate* isolate); |
| 69 | bool getShouldSetImmutablePrototype(v8::Isolate* isolate); |
| 70 | bool getSpecCompliantPropertyAttributes(v8::Isolate* isolate); |
| 71 | |
| 72 | kj::String fullyQualifiedTypeName(const std::type_info& type); |
| 73 | kj::String typeName(const std::type_info& type); |
| 74 | |
| 75 | // Creates a JavaScript error that obfuscates the exception details, while logging the full details |
| 76 | // to stderr. If the KJ exception was created using throwTunneledException(), don't log anything |
| 77 | // but instead return the original reconstructed JavaScript exception. |
| 78 | v8::Local<v8::Value> makeInternalError(v8::Isolate* isolate, kj::StringPtr internalMessage); |
| 79 | |
| 80 | // Creates a JavaScript error that obfuscates the exception details, while logging the full details |
| 81 | // to stderr. If the KJ exception was created using throwTunneledException(), don't log anything |
| 82 | // but instead return the original reconstructed JavaScript exception. |
| 83 | v8::Local<v8::Value> exceptionToJs( |
| 84 | v8::Isolate* isolate, kj::Exception&& exception, ExceptionToJsOptions options = {}); |
| 85 | |
| 86 | // calls makeInternalError() and then tells the isolate to throw it. |
| 87 | void throwInternalError(v8::Isolate* isolate, kj::StringPtr internalMessage); |
| 88 | |
| 89 | // calls makeInternalError() and then tells the isolate to throw it. |
| 90 | void throwInternalError( |
| 91 | v8::Isolate* isolate, kj::Exception&& exception, ExceptionToJsOptions options = {}); |
| 92 | |
| 93 | constexpr kj::Exception::DetailTypeId TUNNELED_EXCEPTION_DETAIL_ID = 0xe8027292171b1646ull; |
| 94 | |
| 95 | // Detail type for JavaScript exception metadata (error type and stack trace) |
| 96 | constexpr kj::Exception::DetailTypeId JS_EXCEPTION_METADATA_DETAIL_ID = 0xa9ae63464030fcefull; |
| 97 | |
| 98 | // Add a serialized copy of the exception value to the KJ exception, as a "detail". |
| 99 | void addExceptionDetail(Lock& js, kj::Exception& exception, v8::Local<v8::Value> handle); |
| 100 | |
| 101 | // Extract and add JavaScript exception metadata (error type and stack trace) to the KJ exception. |
| 102 | // Serializes using Cap'n Proto schema defined in exception-metadata.capnp. |
| 103 | void addJsExceptionMetadata(Lock& js, kj::Exception& exception, v8::Local<v8::Value> handle); |
| 104 | |
| 105 | struct TypeErrorContext { |
| 106 | enum Kind : uint8_t { |
| 107 | METHOD_ARGUMENT, // has type name, member (method) name, and argument index |
| 108 | CONSTRUCTOR_ARGUMENT, // has type name, argument index |
| 109 | SETTER_ARGUMENT, // has type name and member (property) name |
| 110 | STRUCT_FIELD, // has type name and member (field) name |
| 111 | ARRAY_ELEMENT, // has argument (element) index |
| 112 | // TODO(someday): Capture where the array itself was declared? |
| 113 | CALLBACK_ARGUMENT, // has argument index |
| 114 | // TODO(someday): Track where callback was introduced for better errors. |
| 115 | CALLBACK_RETURN, // has nothing |
| 116 | // TODO(someday): Track where callback was introduced for better errors. |
| 117 | DICT_KEY, // has member (key) name |
| 118 | DICT_FIELD, // has member (field) name |
| 119 | PROMISE_RESOLUTION, // has nothing |
| 120 | // TODO(someday): Track where the promise was introduced. |
| 121 | OTHER, // has nothing |
| 122 | }; |
| 123 | |
| 124 | Kind kind; |
| 125 | uint argumentIndex; |
| 126 | kj::Maybe<const std::type_info&> type; |
| 127 | const char* memberName; |
| 128 | |
| 129 | static inline TypeErrorContext methodArgument( |
| 130 | const std::type_info& type, const char* methodName, uint argumentIndex) { |
| 131 | return {METHOD_ARGUMENT, argumentIndex, type, methodName}; |
| 132 | } |
| 133 | static inline TypeErrorContext constructorArgument( |
| 134 | const std::type_info& type, uint argumentIndex) { |
| 135 | return {CONSTRUCTOR_ARGUMENT, argumentIndex, type, nullptr}; |
| 136 | } |
| 137 | static inline TypeErrorContext setterArgument( |
| 138 | const std::type_info& type, const char* propertyName) { |
| 139 | return {SETTER_ARGUMENT, 0, type, propertyName}; |
| 140 | } |
| 141 | static inline TypeErrorContext structField(const std::type_info& type, const char* fieldName) { |
| 142 | return {STRUCT_FIELD, 0, type, fieldName}; |
| 143 | } |
| 144 | static inline TypeErrorContext arrayElement(uint index) { |
| 145 | return {ARRAY_ELEMENT, index, kj::none, nullptr}; |
| 146 | } |
| 147 | static inline TypeErrorContext callbackArgument(uint argumentIndex) { |
| 148 | return {CALLBACK_ARGUMENT, argumentIndex, kj::none, nullptr}; |
| 149 | } |
| 150 | static inline TypeErrorContext callbackReturn() { |
| 151 | return {CALLBACK_RETURN, 0, kj::none, nullptr}; |
| 152 | } |
| 153 | static inline TypeErrorContext dictKey(const char* keyName) { |
| 154 | return {DICT_KEY, 0, kj::none, keyName}; |
| 155 | } |
| 156 | static inline TypeErrorContext dictField(const char* fieldName) { |
| 157 | return {DICT_FIELD, 0, kj::none, fieldName}; |
| 158 | } |
| 159 | static inline TypeErrorContext promiseResolution() { |
| 160 | return {PROMISE_RESOLUTION, 0, kj::none, nullptr}; |
| 161 | } |
| 162 | static inline TypeErrorContext other() { |
| 163 | return {OTHER, 0, kj::none, nullptr}; |
| 164 | } |
| 165 | }; |
| 166 | |
| 167 | // Throw a JavaScript exception indicating an argument type error, and then throw a C++ exception |
| 168 | // of type JsExceptionThrown, which will be caught by liftKj(). |
| 169 | [[noreturn]] void throwTypeError( |
| 170 | v8::Isolate* isolate, TypeErrorContext errorContext, const char* expectedType); |
| 171 | |
| 172 | // Throw a JavaScript exception indicating an argument type error, and then throw a C++ exception |
| 173 | // of type JsExceptionThrown, which will be caught by liftKj(). |
| 174 | [[noreturn]] void throwTypeError( |
| 175 | v8::Isolate* isolate, TypeErrorContext errorContext, const std::type_info& expectedType); |
| 176 | |
| 177 | // Throw a JavaScript exception indicating an argument type error, and then throw a C++ exception |
| 178 | // of type JsExceptionThrown, which will be caught by liftKj(). |
| 179 | [[noreturn]] void throwTypeError( |
| 180 | v8::Isolate* isolate, TypeErrorContext errorContext, kj::String expectedType); |
| 181 | |
| 182 | // Throw a JavaScript TypeError with a free-form message. |
| 183 | [[noreturn]] void throwTypeError(v8::Isolate* isolate, kj::StringPtr message); |
| 184 | |
| 185 | // Callback used when attempting to construct a type that can't be constructed from JavaScript. |
| 186 | void throwIllegalConstructor(const v8::FunctionCallbackInfo<v8::Value>& args); |
| 187 | |
| 188 | kj::StringPtr extractTunneledExceptionDescription(kj::StringPtr message); |
| 189 | |
| 190 | // Given a JavaScript exception, returns a KJ exception that contains a tunneled exception type that |
| 191 | // can be converted back to JavaScript via makeInternalError(). |
| 192 | kj::Exception createTunneledException(v8::Isolate* isolate, v8::Local<v8::Value> exception); |
| 193 | |
| 194 | // Given a JavaScript exception, throw a KJ exception that contains a tunneled exception type that |
| 195 | // can be converted back to JavaScript via makeInternalError(). |
| 196 | // |
| 197 | // Equivalent to throwing the exception returned by `createTunneledException(exception)`. |
| 198 | [[noreturn]] void throwTunneledException(v8::Isolate* isolate, v8::Local<v8::Value> exception); |
| 199 | |
| 200 | template <typename T> |
| 201 | v8::Local<T> check(v8::MaybeLocal<T> maybe) { |
| 202 | // V8 usually returns a MaybeLocal to mean that the function can throw a JavaScript exception. |
| 203 | // If the MaybeLocal is empty then an exception was already thrown. |
| 204 | |
| 205 | v8::Local<T> result; |
| 206 | if (!maybe.ToLocal(&result)) { |
| 207 | throw JsExceptionThrown(); |
| 208 | } |
| 209 | return result; |
| 210 | } |
| 211 | |
| 212 | template <typename T> |
| 213 | T check(v8::Maybe<T> maybe) { |
| 214 | T result; |
| 215 | if (maybe.To(&result)) { |
| 216 | return result; |
| 217 | } else { |
| 218 | throw JsExceptionThrown(); |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | // View the contents of the given v8::ArrayBuffer/ArrayBufferView as an ArrayPtr<byte>. |
| 223 | kj::Array<kj::byte> asBytes(v8::Local<v8::ArrayBuffer> arrayBuffer); |
| 224 | |
| 225 | // View the contents of the given v8::ArrayBuffer/ArrayBufferView as an ArrayPtr<byte>. |
| 226 | kj::Array<kj::byte> asBytes(v8::Local<v8::ArrayBufferView> arrayBufferView); |
| 227 | |
| 228 | // View the contents of the given v8::SharedArrayBuffer as an ArrayPtr<byte>. |
| 229 | kj::Array<kj::byte> asBytes(v8::Local<v8::SharedArrayBuffer> sharedArrayBuffer); |
| 230 | |
| 231 | // Freeze the given object and all its members, making it recursively immutable. |
| 232 | // |
| 233 | // WARNING: This function is unsafe to call on user-provided content since if the value is cyclic |
| 234 | // or may contain non-simple values it won't do the right thing. It is safe to call this on the |
| 235 | // output of JSON.parse(). |
| 236 | // TODO(cleanup): Maybe replace this with a function that parses and freezes JSON in one step. |
| 237 | void recursivelyFreeze(v8::Local<v8::Context> context, v8::Local<v8::Value> value); |
| 238 | |
| 239 | // Make a deep clone of the given object. |
| 240 | v8::Local<v8::Value> deepClone(v8::Local<v8::Context> context, v8::Local<v8::Value> value); |
| 241 | |
| 242 | // Make a JavaScript String in v8's Heap. |
| 243 | // |
| 244 | // The type T of kj::Array<T> will determine the specific type of v8 string that is created: |
| 245 | // * When T is const char, the kj::Array<T> is interpreted as UTF-8. |
| 246 | // * When T is char16_t, the kj::Array<T> is interpreted as UTF-16. |
| 247 | // |
| 248 | // TODO(cleanup): Call sites should migrate to the new js.str(...) variants on jsg::Lock |
| 249 | // rather than calling v8Str directly. Once the migration is a big further along, v8Str |
| 250 | // and its variants will be explicitly marked deprecated. |
| 251 | template <typename T> |
| 252 | v8::Local<v8::String> v8Str(v8::Isolate* isolate, |
| 253 | kj::ArrayPtr<T> ptr, |
| 254 | v8::NewStringType newType = v8::NewStringType::kNormal) { |
| 255 | if constexpr (kj::isSameType<char16_t, T>()) { |
| 256 | return check(v8::String::NewFromTwoByte( |
| 257 | isolate, reinterpret_cast<uint16_t*>(ptr.begin()), newType, ptr.size())); |
| 258 | } else if constexpr (kj::isSameType<const char16_t, T>()) { |
| 259 | return check(v8::String::NewFromTwoByte( |
| 260 | isolate, reinterpret_cast<const uint16_t*>(ptr.begin()), newType, ptr.size())); |
| 261 | } else if constexpr (kj::isSameType<uint16_t, T>()) { |
| 262 | return check(v8::String::NewFromTwoByte(isolate, ptr.begin(), newType, ptr.size())); |
| 263 | } else if constexpr (kj::isSameType<const char, T>()) { |
| 264 | return check(v8::String::NewFromUtf8(isolate, ptr.begin(), newType, ptr.size())); |
| 265 | } else if constexpr (kj::isSameType<char, T>()) { |
| 266 | return check(v8::String::NewFromUtf8(isolate, ptr.begin(), newType, ptr.size())); |
| 267 | } else { |
| 268 | KJ_UNREACHABLE; |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | // Make a JavaScript String in v8's Heap with the kj::StringPtr interpreted as UTF-8. |
| 273 | inline v8::Local<v8::String> v8Str(v8::Isolate* isolate, |
| 274 | kj::StringPtr str, |
| 275 | v8::NewStringType newType = v8::NewStringType::kNormal) { |
| 276 | return v8Str(isolate, str.asArray(), newType); |
| 277 | } |
| 278 | |
| 279 | // Make a JavaScript String in v8's Heap with the kj::ArrayPtr interpreted as Latin1. |
| 280 | inline v8::Local<v8::String> v8StrFromLatin1(v8::Isolate* isolate, |
| 281 | kj::ArrayPtr<const kj::byte> ptr, |
| 282 | v8::NewStringType newType = v8::NewStringType::kNormal) { |
| 283 | return check(v8::String::NewFromOneByte(isolate, ptr.begin(), newType, ptr.size())); |
| 284 | } |
| 285 | |
| 286 | inline v8::Local<v8::String> v8StrIntern(v8::Isolate* isolate, kj::StringPtr str) { |
| 287 | return v8Str(isolate, str, v8::NewStringType::kInternalized); |
| 288 | } |
| 289 | |
| 290 | template <typename T> |
| 291 | constexpr bool isVoid() { |
| 292 | return false; |
| 293 | } |
| 294 | template <> |
| 295 | constexpr bool isVoid<void>() { |
| 296 | return true; |
| 297 | } |
| 298 | |
| 299 | template <typename T> |
| 300 | struct RemoveMaybe_; |
| 301 | template <typename T> |
| 302 | struct RemoveMaybe_<kj::Maybe<T>> { |
| 303 | using Type = T; |
| 304 | }; |
| 305 | template <typename T> |
| 306 | using RemoveMaybe = RemoveMaybe_<T>::Type; |
| 307 | |
| 308 | template <typename T> |
| 309 | struct RemoveRvalueRef_ { |
| 310 | using Type = T; |
| 311 | }; |
| 312 | template <typename T> |
| 313 | struct RemoveRvalueRef_<T&&> { |
| 314 | using Type = T; |
| 315 | }; |
| 316 | template <typename T> |
| 317 | using RemoveRvalueRef = RemoveRvalueRef_<T>::Type; |
| 318 | |
| 319 | enum class JsgKind { RESOURCE, STRUCT, EXTENSION }; |
| 320 | |
| 321 | template <typename T> |
| 322 | struct LiftKj_ { |
| 323 | template <typename Info, typename Func, typename Ret = void> |
| 324 | static Ret apply(const Info& info, Func&& func) { |
| 325 | constexpr bool isFastApi = kj::isSameType<v8::Isolate*, Info>(); |
| 326 | v8::Isolate* isolate; |
| 327 | if constexpr (isFastApi) { |
| 328 | isolate = info; |
| 329 | } else { |
| 330 | isolate = info.GetIsolate(); |
| 331 | } |
| 332 | |
| 333 | try { |
| 334 | try { |
| 335 | if constexpr (isFastApi) { |
| 336 | return func(); |
| 337 | } else { |
| 338 | if constexpr (isVoid<T>()) { |
| 339 | func(); |
| 340 | if constexpr (!kj::canConvert<Info&, v8::PropertyCallbackInfo<void>&>()) { |
| 341 | info.GetReturnValue().SetUndefined(); |
| 342 | } |
| 343 | } else { |
| 344 | info.GetReturnValue().Set(func()); |
| 345 | } |
| 346 | } |
| 347 | } catch (kj::Exception& exception) { |
| 348 | // This throwInternalError() overload may decode a tunneled error. While constructing the |
| 349 | // v8::Value representing the tunneled error, it itself may cause a JS exception to be |
| 350 | // thrown. This is the reason for the nested try-catch blocks -- we need to be able to |
| 351 | // swallow any JsExceptionThrown exceptions that this catch block generates. |
| 352 | throwInternalError(isolate, kj::mv(exception)); |
| 353 | } |
| 354 | } catch (JsExceptionThrown&) { |
| 355 | // nothing to do |
| 356 | } catch (std::exception& exception) { |
| 357 | throwInternalError(isolate, exception.what()); |
| 358 | } catch (...) { |
| 359 | throwInternalError( |
| 360 | isolate, kj::str("caught unknown exception of type: ", kj::getCaughtExceptionType())); |
| 361 | } |
| 362 | |
| 363 | if constexpr (isFastApi && !isVoid<Ret>()) { |
| 364 | // Since we return early on fast api calls, this should never get executed |
| 365 | // unless there is an error thrown from the fast api call itself. |
| 366 | return Ret{}; |
| 367 | } |
| 368 | } |
| 369 | }; |
| 370 | |
| 371 | void returnRejectedPromise(const v8::FunctionCallbackInfo<v8::Value>& info, |
| 372 | v8::Local<v8::Value> exception, |
| 373 | v8::TryCatch& tryCatch); |
| 374 | |
| 375 | void returnRejectedPromise(const v8::PropertyCallbackInfo<v8::Value>& info, |
| 376 | v8::Local<v8::Value> exception, |
| 377 | v8::TryCatch& tryCatch); |
| 378 | |
| 379 | template <> |
| 380 | struct LiftKj_<v8::Local<v8::Promise>> { |
| 381 | template <typename Info, typename Func> |
| 382 | static void apply(Info& info, Func&& func) { |
| 383 | auto isolate = info.GetIsolate(); |
| 384 | if (!getCaptureThrowsAsRejections(isolate)) { |
| 385 | // Capturing exceptions into rejected promises is not enabled, so fall back to the regular |
| 386 | // implementation. |
| 387 | LiftKj_<v8::Local<v8::Value>>::apply(info, kj::fwd<Func>(func)); |
| 388 | return; |
| 389 | } |
| 390 | |
| 391 | v8::TryCatch tryCatch(isolate); |
| 392 | try { |
| 393 | try { |
| 394 | info.GetReturnValue().Set(func()); |
| 395 | } catch (kj::Exception& exception) { |
| 396 | // returnRejectedPromise() may decode a tunneled error. While constructing |
| 397 | // the v8::Value representing the tunneled error, it itself may cause a JS exception to be |
| 398 | // thrown. This is the reason for the nested try-catch blocks -- we need to be able to |
| 399 | // swallow any JsExceptionThrown exceptions that this catch block generates. |
| 400 | returnRejectedPromise(info, exceptionToJs(isolate, kj::mv(exception)), tryCatch); |
| 401 | } |
| 402 | } catch (JsExceptionThrown&) { |
| 403 | if (tryCatch.CanContinue()) { |
| 404 | returnRejectedPromise(info, tryCatch.Exception(), tryCatch); |
| 405 | } |
| 406 | // If CanContinue() is false, then there's nothing we can do. |
| 407 | } catch (std::exception& exception) { |
| 408 | throwInternalError(isolate, exception.what()); |
| 409 | } catch (...) { |
| 410 | throwInternalError( |
| 411 | isolate, kj::str("caught unknown exception of type: ", kj::getCaughtExceptionType())); |
| 412 | } |
| 413 | } |
| 414 | }; |
| 415 | |
| 416 | // Lifts KJ code into V8 code: Catches exceptions and manages HandleScope. Converts the |
| 417 | // function's return value into the appropriate V8 return. |
| 418 | // |
| 419 | // liftKj() translates certain KJ exceptions thrown into JS exceptions. KJ exceptions opt into |
| 420 | // this behavior by suffixing their description strings with "jsg.SomeError", followed by an |
| 421 | // optional colon, optional whitespace, and a message which will be exposed to the user. |
| 422 | // In the example "jsg.SomeError", "SomeError" must be a specific recognized string: |
| 423 | // "RangeError", "TypeError", or "DOMException(name)", where `name` is the "error name" of the |
| 424 | // DOMException you wish to throw. |
| 425 | // |
| 426 | // The DOMException error name will typically be dictated by the spec governing the API you are |
| 427 | // implementing. While it can be any string without a closing parenthesis as far as JSG is |
| 428 | // concerned, it will likely be one of the ones listed here: |
| 429 | // |
| 430 | // https://heycam.github.io/webidl/#dfn-error-names-table |
| 431 | template <typename Info, typename Func> |
| 432 | void liftKj(const Info& info, Func&& func) { |
| 433 | LiftKj_<decltype(func())>::apply(info, kj::fwd<Func>(func)); |
| 434 | } |
| 435 | |
| 436 | // Direct value version of liftKj for use with Fast API and other contexts where callback info isn't available |
| 437 | template <typename Ret, typename Func> |
| 438 | Ret liftKj(v8::Isolate* isolate, Func&& func) { |
| 439 | return LiftKj_<decltype(func())>::template apply<v8::Isolate*, Func, Ret>( |
| 440 | isolate, kj::fwd<Func>(func)); |
| 441 | } |
| 442 | |
| 443 | namespace _ { |
| 444 | |
| 445 | struct NoneDetected; |
| 446 | |
| 447 | template <typename Default, typename AlwaysVoid, template <typename...> class Op, class... Args> |
| 448 | struct Detector { |
| 449 | using Type = Default; |
| 450 | static constexpr bool value = false; |
| 451 | }; |
| 452 | |
| 453 | template <typename Default, template <typename...> class Op, class... Args> |
| 454 | struct Detector<Default, kj::VoidSfinae<Op<Args...>>, Op, Args...> { |
| 455 | using Type = Op<Args...>; |
| 456 | static constexpr bool value = true; |
| 457 | }; |
| 458 | |
| 459 | } // namespace _ |
| 460 | |
| 461 | // A typedef for `Op<Args...>` if that template is instantiable, otherwise `Default`. |
| 462 | template <typename Default, template <typename...> class Op, typename... Args> |
| 463 | using DetectedOr = _::Detector<Default, void, Op, Args...>::Type; |
| 464 | |
| 465 | // True if Op<Args...> is instantiable, false otherwise. This is basically the same as |
| 466 | // std::experimental::is_detected from the library fundamentals TS v2. |
| 467 | // http://en.cppreference.com/w/cpp/experimental/is_detected |
| 468 | // |
| 469 | template <template <typename...> class Op, typename... Args> |
| 470 | constexpr bool isDetected() { |
| 471 | return _::Detector<_::NoneDetected, void, Op, Args...>::value; |
| 472 | } |
| 473 | // TODO(cleanup): Should live in kj? |
| 474 | |
| 475 | // SFINAE-friendly accessor for a resource type's configuration parameter. |
| 476 | template <typename Arg> |
| 477 | auto getParameterType(void (*)(Arg)) -> Arg; |
| 478 | |
| 479 | // SFINAE-friendly accessor for a resource type's configuration parameter. |
| 480 | template <typename T> |
| 481 | using GetConfiguration = decltype(getParameterType(&T::jsgConfiguration)); |
| 482 | |
| 483 | template <typename T> |
| 484 | concept HasConfiguration = requires(GetConfiguration<T> arg) { T::jsgConfiguration(arg); }; |
| 485 | |
| 486 | inline bool isFinite(double value) { |
| 487 | return !(kj::isNaN(value) || value == kj::inf() || value == -kj::inf()); |
| 488 | } |
| 489 | |
| 490 | template <typename T> |
| 491 | concept StrictlyBool = kj::isSameType<T, bool>(); |
| 492 | |
| 493 | // ====================================================================================== |
| 494 | |
| 495 | class Lock; |
| 496 | |
| 497 | // Interface for allocating backing stores for v8 external string. |
| 498 | class ExternalStringAllocator { |
| 499 | public: |
| 500 | virtual ~ExternalStringAllocator() = default; |
| 501 | |
| 502 | virtual void* allocate(size_t size) = 0; |
| 503 | virtual void deallocate(void* ptr) = 0; |
| 504 | }; |
| 505 | |
| 506 | // Returns a singleton DefaultExternalStringAllocator. |
| 507 | kj::Own<ExternalStringAllocator> defaultExternalStringAllocator(); |
| 508 | |
| 509 | // Creates v8 Strings from buffers not on the v8 heap. These do not copy and do not |
| 510 | // take ownership of the buf. The buf *must* point to a static constant with infinite |
| 511 | // lifetime. |
| 512 | // |
| 513 | // It is important to understand that the OneByteString variant will interpret buf as |
| 514 | // latin-1 rather than UTF-8, which is how KJ normally represents text. There is no |
| 515 | // variation of external strings that support UTF-8 encoded bytes. To represent any |
| 516 | // text outside of the Latin1 range, the two-byte (uint16_t) variant must be used. |
| 517 | // |
| 518 | // Note that these intentionally do not use the v8Str naming convention like the other |
| 519 | // string methods because it needs to be absolutely clear that these use external buffers |
| 520 | // that are not owned by the v8 heap. |
| 521 | v8::Local<v8::String> newExternalOneByteString(Lock& js, kj::ArrayPtr<const char> buf); |
| 522 | |
| 523 | // Creates v8 Strings from buffers not on the v8 heap. These do not copy and do not |
| 524 | // take ownership of the buf. The buf *must* point to a static constant with infinite |
| 525 | // lifetime. |
| 526 | // |
| 527 | // It is important to understand that the OneByteString variant will interpret buf as |
| 528 | // latin-1 rather than UTF-8, which is how KJ normally represents text. There is no |
| 529 | // variation of external strings that support UTF-8 encoded bytes. To represent any |
| 530 | // text outside of the Latin1 range, the two-byte (uint16_t) variant must be used. |
| 531 | // |
| 532 | // Note that these intentionally do not use the v8Str naming convention like the other |
| 533 | // string methods because it needs to be absolutely clear that these use external buffers |
| 534 | // that are not owned by the v8 heap. |
| 535 | v8::Local<v8::String> newExternalTwoByteString(Lock& js, kj::ArrayPtr<const uint16_t> buf); |
| 536 | |
| 537 | // Use this type to mark APIs that are not implemented. Attempts to use the API will throw an |
| 538 | // exception. |
| 539 | // - Use Unimplemented as a method parameter type or struct field type to mark that |
| 540 | // parameter/field unimplemented; only the value `undefined` will be allowed. |
| 541 | // - Use Unimplemented as the return type of a method to mark the whole method unimplemented. |
| 542 | // Have the method body simply return `Unimplemented()`. |
| 543 | struct Unimplemented {}; |
| 544 | // TODO(someday): We should consider making it easier for people to probe features by doing |
| 545 | // `if (obj.someMember)`. Currently this check would pass for methods and would throw an |
| 546 | // exception for properties. Is it possible for us to hook into the V8 feature where there are |
| 547 | // special values of `undefined` that augment the error message thrown if they are used? |
| 548 | |
| 549 | // Use to mark APIs that are not just unimplemented, but that we don't plan to implement, e.g. |
| 550 | // standard ServiceWorker APIs that don't make sense for Workers. |
| 551 | using WontImplement = Unimplemented; |
| 552 | |
| 553 | // ====================================================================================== |
| 554 | // Module utilities |
| 555 | |
| 556 | // Creates a mutable copy of a module namespace object for CommonJS compatibility. |
| 557 | // This is needed because ES module namespaces have read-only properties, but |
| 558 | // CommonJS require() is expected to return objects with mutable properties. |
| 559 | // This matches Node.js behavior when requiring an ESM module from CJS. |
| 560 | // See: https://github.com/cloudflare/workerd/issues/5844 |
| 561 | JsObject createMutableModuleExports(Lock& js, JsObject moduleNamespace); |
| 562 | |
| 563 | // ====================================================================================== |
| 564 | // Node.js Compat |
| 565 | |
| 566 | kj::Maybe<kj::String> checkNodeSpecifier(kj::StringPtr specifier); |
| 567 | bool isNodeJsCompatEnabled(jsg::Lock& js); |
| 568 | bool isNodeJsProcessV2Enabled(jsg::Lock& js); |
| 569 | bool isRequireReturnsDefaultExportEnabled(jsg::Lock& js); |
| 570 | |
| 571 | // The following counter is used to track the number of times a method is called. |
| 572 | // This is mostly useful for validating/testing v8 fast api methods, but also for |
| 573 | // tracking the number of times a method is called in general. |
| 574 | struct CallCounter { |
| 575 | // Referring to the slow method triggered at least once for a single method call. |
| 576 | uint32_t slow = 0; |
| 577 | // Referring to the fast method whenever V8 optimizes the method. |
| 578 | // Might or might not be called depending on the method's implementation, |
| 579 | // and the current limitations of v8 fast api. |
| 580 | uint32_t fast = 0; |
| 581 | |
| 582 | void reset() { |
| 583 | slow = 0; |
| 584 | fast = 0; |
| 585 | } |
| 586 | |
| 587 | bool operator==(const CallCounter& rhs) { |
| 588 | return slow == rhs.slow && fast == rhs.fast; |
| 589 | } |
| 590 | }; |
| 591 | |
| 592 | inline kj::String KJ_STRINGIFY(CallCounter& counter) { |
| 593 | return kj::str("(", counter.slow, ", ", counter.fast, ")"); |
| 594 | } |
| 595 | |
| 596 | static CallCounter callCounter{}; |
| 597 | |
| 598 | } // namespace workerd::jsg |