File
Blob: src/workerd/jsg/setup.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 | // Public API for setting up JavaScript context. Only high-level code needs to include this file. |
| 7 | |
| 8 | #include "async-context.h" |
| 9 | #include "jsg.h" |
| 10 | #include "v8-platform-wrapper.h" |
| 11 | |
| 12 | #include <workerd/jsg/observer.h> |
| 13 | #include <workerd/jsg/util.h> |
| 14 | #include <workerd/util/batch-queue.h> |
| 15 | |
| 16 | #include <v8-profiler.h> |
| 17 | |
| 18 | #include <kj/map.h> |
| 19 | #include <kj/mutex.h> |
| 20 | #include <kj/vector.h> |
| 21 | |
| 22 | #include <typeindex> |
| 23 | |
| 24 | namespace workerd::jsg { |
| 25 | |
| 26 | class Deserializer; |
| 27 | class Serializer; |
| 28 | |
| 29 | // Construct a default V8 platform, with the given background thread pool size. |
| 30 | // |
| 31 | // Passing zero for `backgroundThreadCount` causes V8 to ask glibc how many processors there are. |
| 32 | // Now, glibc *could* answer this problem easily by calling `sched_getaffinity()`, which would |
| 33 | // not only tell it how many cores exist, but also how many cores are available to this specific |
| 34 | // process. But does glibc do that? No, it does not. Instead, it frantically tries to open |
| 35 | // `/sys/devices/system/cpu/online`, then `/proc/stat`, then `/proc/cpuinfo`, and parses the text |
| 36 | // it reads from whichever file successfully opens to find out the number of processors. Of course, |
| 37 | // if you're in a sandbox, that probably won't work. And anyway, you probably don't actually want |
| 38 | // V8 to consume all available cores with background work. So, please specify a thread pool size. |
| 39 | kj::Own<v8::Platform> defaultPlatform(uint backgroundThreadCount); |
| 40 | |
| 41 | // In order to use any part of the JSG API, you must first construct a V8System. You can only |
| 42 | // construct one of these per process. This performs process-wide initialization of the V8 |
| 43 | // library. |
| 44 | class V8System { |
| 45 | using PumpMsgLoopType = kj::Function<bool(v8::Isolate*)>; |
| 46 | using ShutdownIsolateType = kj::Function<void(v8::Isolate*)>; |
| 47 | |
| 48 | public: |
| 49 | // Uses the default v8::Platform implementation, as if by: |
| 50 | // auto v8Platform = jsg::defaultPlatform(); |
| 51 | // auto v8System = V8System(*v8Platform, flags); |
| 52 | // (Optional) `flags` is a list of command-line flags to pass to V8, like "--expose-gc" or |
| 53 | // "--single_threaded_gc". An exception will be thrown if any flags are not recognized. |
| 54 | explicit V8System(kj::ArrayPtr<const kj::StringPtr> flags = nullptr); |
| 55 | |
| 56 | // Use a possibly-custom v8::Platform wrapper over default v8::Platform, and apply flags. |
| 57 | explicit V8System(v8::Platform& platform, |
| 58 | kj::ArrayPtr<const kj::StringPtr> flags, |
| 59 | v8::Platform* defaultPlatformPtr); |
| 60 | |
| 61 | // Use a possibly-custom v8::Platform implementation with custom task queue, and apply flags. |
| 62 | explicit V8System(v8::Platform& platform, |
| 63 | kj::ArrayPtr<const kj::StringPtr> flags, |
| 64 | PumpMsgLoopType, |
| 65 | ShutdownIsolateType); |
| 66 | |
| 67 | ~V8System() noexcept(false); |
| 68 | |
| 69 | using FatalErrorCallback = void(kj::StringPtr location, kj::StringPtr message); |
| 70 | static void setFatalErrorCallback(FatalErrorCallback* callback); |
| 71 | |
| 72 | private: |
| 73 | kj::Own<v8::Platform> platformInner; |
| 74 | kj::Own<V8PlatformWrapper> platformWrapper; |
| 75 | PumpMsgLoopType pumpMsgLoop; |
| 76 | ShutdownIsolateType shutdownIsolate; |
| 77 | friend class IsolateBase; |
| 78 | |
| 79 | void init(kj::Own<v8::Platform>, |
| 80 | kj::ArrayPtr<const kj::StringPtr>, |
| 81 | PumpMsgLoopType, |
| 82 | ShutdownIsolateType); |
| 83 | }; |
| 84 | |
| 85 | // Base class of Isolate<T> containing parts that don't need to be templated, to avoid code |
| 86 | // bloat. |
| 87 | class IsolateBase { |
| 88 | public: |
| 89 | static IsolateBase& from(v8::Isolate* isolate); |
| 90 | |
| 91 | // Unwraps a JavaScript exception as a kj::Exception. |
| 92 | virtual kj::Exception unwrapException( |
| 93 | Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> exception) = 0; |
| 94 | |
| 95 | // Wraps a kj::Exception as a JavaScript Exception. |
| 96 | virtual v8::Local<v8::Value> wrapException( |
| 97 | Lock& js, v8::Local<v8::Context> context, kj::Exception&& exception) = 0; |
| 98 | |
| 99 | // Used by Serializer/Deserializer implementations, calls into DynamicResourceTypeMap |
| 100 | // serializerMap and deserializerMap. |
| 101 | virtual bool serialize( |
| 102 | Lock& js, std::type_index type, jsg::Object& instance, Serializer& serializer) = 0; |
| 103 | virtual kj::Maybe<v8::Local<v8::Object>> deserialize( |
| 104 | Lock& js, uint tag, Deserializer& deserializer) = 0; |
| 105 | |
| 106 | // Immediately cancels JavaScript execution in this isolate, causing an uncatchable exception to |
| 107 | // be thrown. Safe to call across threads, without holding the lock. |
| 108 | void terminateExecution() const; |
| 109 | |
| 110 | using Logger = Lock::Logger; |
| 111 | inline void setLoggerCallback(kj::Badge<Lock>, kj::Function<Logger>&& logger) { |
| 112 | maybeLogger = kj::mv(logger); |
| 113 | } |
| 114 | |
| 115 | using ErrorReporter = Lock::ErrorReporter; |
| 116 | inline void setErrorReporterCallback(kj::Badge<Lock>, kj::Function<ErrorReporter>&& reporter) { |
| 117 | maybeErrorReporter = kj::mv(reporter); |
| 118 | } |
| 119 | |
| 120 | using ModuleFallbackCallback = kj::Maybe<kj::OneOf<kj::String, jsg::ModuleRegistry::ModuleInfo>>( |
| 121 | jsg::Lock&, |
| 122 | kj::StringPtr, |
| 123 | kj::Maybe<kj::String>, |
| 124 | jsg::CompilationObserver&, |
| 125 | jsg::ModuleRegistry::ResolveMethod, |
| 126 | kj::Maybe<kj::StringPtr>); |
| 127 | inline void setModuleFallbackCallback(kj::Function<ModuleFallbackCallback>&& callback) { |
| 128 | maybeModuleFallbackCallback = kj::mv(callback); |
| 129 | } |
| 130 | inline kj::Maybe<kj::Function<ModuleFallbackCallback>&> tryGetModuleFallback() { |
| 131 | KJ_IF_SOME(moduleFallbackCallback, maybeModuleFallbackCallback) { |
| 132 | return moduleFallbackCallback; |
| 133 | } |
| 134 | return kj::none; |
| 135 | } |
| 136 | |
| 137 | // Requests an extra microtask checkpoint after the current one completes. |
| 138 | inline void requestExtraMicrotaskCheckpoint(kj::Badge<Lock>) { |
| 139 | extraMicrotaskCheckpointRequested = true; |
| 140 | } |
| 141 | |
| 142 | // Returns true if an extra microtask checkpoint was requested since the last |
| 143 | // call, and clears the flag. |
| 144 | inline bool takeExtraMicrotaskCheckpointRequested(kj::Badge<Lock>) { |
| 145 | bool requested = extraMicrotaskCheckpointRequested; |
| 146 | extraMicrotaskCheckpointRequested = false; |
| 147 | return requested; |
| 148 | } |
| 149 | |
| 150 | inline void setAllowEval(kj::Badge<Lock>, bool allow) { |
| 151 | if (alwaysAllowEval) return; |
| 152 | evalAllowed = allow; |
| 153 | } |
| 154 | |
| 155 | inline void setAllowsAllowEval() { |
| 156 | alwaysAllowEval = true; |
| 157 | evalAllowed = true; |
| 158 | } |
| 159 | |
| 160 | inline void setCaptureThrowsAsRejections(kj::Badge<Lock>, bool capture) { |
| 161 | captureThrowsAsRejections = capture; |
| 162 | } |
| 163 | |
| 164 | inline void setNodeJsCompatEnabled(kj::Badge<Lock>, bool enabled) { |
| 165 | nodeJsCompatEnabled = enabled; |
| 166 | } |
| 167 | |
| 168 | inline void setNodeJsProcessV2Enabled(kj::Badge<Lock>, bool enabled) { |
| 169 | nodeJsProcessV2Enabled = enabled; |
| 170 | } |
| 171 | |
| 172 | inline void setRequireReturnsDefaultExportEnabled(kj::Badge<Lock>, bool enabled) { |
| 173 | requireReturnsDefaultExportEnabled = enabled; |
| 174 | } |
| 175 | |
| 176 | inline bool areWarningsLogged() const { |
| 177 | return maybeLogger != kj::none; |
| 178 | } |
| 179 | inline bool areErrorsReported() const { |
| 180 | return maybeErrorReporter != kj::none; |
| 181 | } |
| 182 | |
| 183 | inline bool isNodeJsCompatEnabled() const { |
| 184 | return nodeJsCompatEnabled; |
| 185 | } |
| 186 | |
| 187 | inline bool isNodeJsProcessV2Enabled() const { |
| 188 | return nodeJsProcessV2Enabled; |
| 189 | } |
| 190 | |
| 191 | inline bool isRequireReturnsDefaultExportEnabled() const { |
| 192 | return requireReturnsDefaultExportEnabled; |
| 193 | } |
| 194 | |
| 195 | inline bool shouldSetToStringTag() const { |
| 196 | return setToStringTag; |
| 197 | } |
| 198 | |
| 199 | void enableSetToStringTag() { |
| 200 | setToStringTag = true; |
| 201 | } |
| 202 | |
| 203 | inline bool shouldSetImmutablePrototype() const { |
| 204 | return shouldSetImmutablePrototypeFlag; |
| 205 | } |
| 206 | |
| 207 | void enableSetImmutablePrototype() { |
| 208 | shouldSetImmutablePrototypeFlag = true; |
| 209 | } |
| 210 | |
| 211 | inline bool shouldUseSpecCompliantPropertyAttributes() const { |
| 212 | return specCompliantPropertyAttributesFlag; |
| 213 | } |
| 214 | |
| 215 | void enableSpecCompliantPropertyAttributes() { |
| 216 | specCompliantPropertyAttributesFlag = true; |
| 217 | } |
| 218 | |
| 219 | inline void disableTopLevelAwait() { |
| 220 | allowTopLevelAwait = false; |
| 221 | } |
| 222 | |
| 223 | inline bool isTopLevelAwaitEnabled() const { |
| 224 | return allowTopLevelAwait; |
| 225 | } |
| 226 | |
| 227 | // The logger will be optionally set by the isolate setup logic if there is anywhere |
| 228 | // for the log to go (for instance, if debug logging is enabled or the inspector is |
| 229 | // being used). |
| 230 | inline void logWarning(Lock& js, kj::StringPtr message) { |
| 231 | KJ_IF_SOME(logger, maybeLogger) { |
| 232 | logger(js, message); |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | inline void reportError( |
| 237 | Lock& js, kj::String desc, const JsValue& error, const JsMessage& message) { |
| 238 | KJ_IF_SOME(reporter, maybeErrorReporter) { |
| 239 | reporter(js, kj::mv(desc), error, message); |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | IsolateObserver& getObserver() { |
| 244 | return *observer; |
| 245 | } |
| 246 | |
| 247 | ExternalStringAllocator& getExternalStringAllocator() { |
| 248 | return *externalStringAllocator; |
| 249 | } |
| 250 | |
| 251 | // Implementation of MemoryRetainer |
| 252 | void jsgGetMemoryInfo(MemoryTracker& tracker) const; |
| 253 | kj::StringPtr jsgGetMemoryName() const { |
| 254 | return "IsolateBase"_kjc; |
| 255 | } |
| 256 | size_t jsgGetMemorySelfSize() const { |
| 257 | return sizeof(IsolateBase); |
| 258 | } |
| 259 | bool jsgGetMemoryInfoIsRootNode() const { |
| 260 | return true; |
| 261 | } |
| 262 | |
| 263 | // Get an object referencing this isolate that can be used to adjust external memory usage later |
| 264 | kj::Arc<const ExternalMemoryTarget> getExternalMemoryTarget(); |
| 265 | |
| 266 | // Equivalent to getExternalMemoryTarget()->getAdjustment(amount), but saves an atomic refcount |
| 267 | // increment and decrement. |
| 268 | ExternalMemoryAdjustment getExternalMemoryAdjustment(int64_t amount) { |
| 269 | return externalMemoryTarget->getAdjustment(amount); |
| 270 | } |
| 271 | |
| 272 | AsyncContextFrame::StorageKey& getEnvAsyncContextKey() { |
| 273 | return *envAsyncContextKey; |
| 274 | } |
| 275 | |
| 276 | AsyncContextFrame::StorageKey& getExportsAsyncContextKey() { |
| 277 | return *exportsAsyncContextKey; |
| 278 | } |
| 279 | |
| 280 | void setUsingNewModuleRegistry() { |
| 281 | usingNewModuleRegistry = true; |
| 282 | } |
| 283 | |
| 284 | bool isUsingNewModuleRegistry() const { |
| 285 | return usingNewModuleRegistry; |
| 286 | } |
| 287 | |
| 288 | void setThrowOnUnrecognizedImportAssertion() { |
| 289 | throwOnUnrecognizedImportAssertion = true; |
| 290 | } |
| 291 | |
| 292 | bool getThrowOnUnrecognizedImportAssertion() const { |
| 293 | return throwOnUnrecognizedImportAssertion; |
| 294 | } |
| 295 | |
| 296 | void setUsingEnhancedErrorSerialization() { |
| 297 | usingEnhancedErrorSerialization = true; |
| 298 | } |
| 299 | |
| 300 | bool getUsingEnhancedErrorSerialization() const { |
| 301 | return usingEnhancedErrorSerialization; |
| 302 | } |
| 303 | |
| 304 | void setUsingFastJsgStruct() { |
| 305 | usingFastJsgStruct = true; |
| 306 | } |
| 307 | |
| 308 | bool getUsingFastJsgStruct() const { |
| 309 | return usingFastJsgStruct; |
| 310 | } |
| 311 | |
| 312 | bool pumpMsgLoop() { |
| 313 | return v8System.pumpMsgLoop(ptr); |
| 314 | } |
| 315 | |
| 316 | // Allows an object to register an that will be dropped when the destroy |
| 317 | // queue is drained under the isolate lock. |
| 318 | void destroyUnderLock(kj::Own<void> item) { |
| 319 | deferDestruction(kj::mv(item)); |
| 320 | } |
| 321 | |
| 322 | v8::Isolate* getIsolate() const { |
| 323 | return ptr; |
| 324 | } |
| 325 | |
| 326 | private: |
| 327 | template <typename TypeWrapper> |
| 328 | friend class Isolate; |
| 329 | |
| 330 | static void buildEmbedderGraph(v8::Isolate* isolate, v8::EmbedderGraph* graph, void* data); |
| 331 | |
| 332 | // The internals of a jsg::Ref<T> to be deleted. |
| 333 | class RefToDelete { |
| 334 | public: |
| 335 | RefToDelete(bool strong, kj::Own<void> ownWrappable, Wrappable* wrappable) |
| 336 | : strong(strong), |
| 337 | ownWrappable(kj::mv(ownWrappable)), |
| 338 | wrappable(wrappable) {} |
| 339 | ~RefToDelete() noexcept(false) { |
| 340 | if (ownWrappable.get() != nullptr && strong) { |
| 341 | wrappable->removeStrongRef(); |
| 342 | } |
| 343 | } |
| 344 | RefToDelete(RefToDelete&&) noexcept = default; |
| 345 | |
| 346 | // Default move ctor okay because ownWrappable.get() will be null if moved-from. |
| 347 | KJ_DISALLOW_COPY(RefToDelete); |
| 348 | |
| 349 | private: |
| 350 | bool strong; |
| 351 | // Keeps the `wrappable` pointer below valid. |
| 352 | kj::Own<void> ownWrappable; |
| 353 | Wrappable* wrappable; |
| 354 | }; |
| 355 | |
| 356 | class GlobalToDelete { |
| 357 | // wrapper around v8::Global<v8::Data> with noexcept move constructor. |
| 358 | public: |
| 359 | GlobalToDelete(v8::Global<v8::Data> handle) noexcept: handle(kj::mv(handle)) {} |
| 360 | GlobalToDelete(GlobalToDelete&&) noexcept = default; |
| 361 | KJ_DISALLOW_COPY(GlobalToDelete); |
| 362 | |
| 363 | private: |
| 364 | v8::Global<v8::Data> handle; |
| 365 | }; |
| 366 | using Item = kj::OneOf<GlobalToDelete, RefToDelete, kj::Own<void>>; |
| 367 | |
| 368 | V8System& v8System; |
| 369 | // TODO(cleanup): After v8 13.4 is fully released we can inline this into `newIsolate` |
| 370 | // and remove this member. |
| 371 | std::unique_ptr<class v8::CppHeap> cppHeap; |
| 372 | v8::Isolate* ptr; |
| 373 | // When true, evalAllowed is true and switching it to false is a no-op. |
| 374 | bool alwaysAllowEval = false; |
| 375 | bool evalAllowed = false; |
| 376 | |
| 377 | // The Web Platform API specifications require that any API that returns a JavaScript Promise |
| 378 | // should never throw errors synchronously. Rather, they are supposed to capture any synchronous |
| 379 | // throws and return a rejected Promise. Historically, Workers did not follow that guideline |
| 380 | // and there are a number of async APIs that currently throw. When the captureThrowsAsRejections |
| 381 | // flag is set, that old behavior is changed to be correct. |
| 382 | bool captureThrowsAsRejections = false; |
| 383 | bool asyncContextTrackingEnabled = false; |
| 384 | bool nodeJsCompatEnabled = false; |
| 385 | bool nodeJsProcessV2Enabled = false; |
| 386 | bool requireReturnsDefaultExportEnabled = false; |
| 387 | bool setToStringTag = false; |
| 388 | bool shouldSetImmutablePrototypeFlag = false; |
| 389 | bool specCompliantPropertyAttributesFlag = false; |
| 390 | bool allowTopLevelAwait = true; |
| 391 | bool usingNewModuleRegistry = false; |
| 392 | bool usingEnhancedErrorSerialization = false; |
| 393 | bool usingFastJsgStruct = false; |
| 394 | bool extraMicrotaskCheckpointRequested = false; |
| 395 | |
| 396 | // Only used when the original module registry is used. |
| 397 | bool throwOnUnrecognizedImportAssertion = false; |
| 398 | |
| 399 | kj::Maybe<kj::Function<Logger>> maybeLogger; |
| 400 | kj::Maybe<kj::Function<ErrorReporter>> maybeErrorReporter; |
| 401 | kj::Maybe<kj::Function<ModuleFallbackCallback>> maybeModuleFallbackCallback; |
| 402 | |
| 403 | // FunctionTemplate used by Wrappable::attachOpaqueWrapper(). Just a constructor for an empty |
| 404 | // object with 2 internal fields. |
| 405 | v8::Global<v8::FunctionTemplate> opaqueTemplate; |
| 406 | |
| 407 | // Object used as the underlying storage for a workers environment. |
| 408 | v8::Global<v8::Object> workerEnvObj; |
| 409 | |
| 410 | // Object used as the underlying storage for a workers exports. |
| 411 | v8::Global<v8::Object> workerExportsObj; |
| 412 | |
| 413 | /* *** External Memory accounting *** */ |
| 414 | // ExternalMemoryTarget holds a weak reference back to the isolate. ExternalMemoryAjustments |
| 415 | // hold references to the ExternalMemoryTarget. This allows the ExternalMemoryAjustments to |
| 416 | // outlive the isolate. |
| 417 | kj::Arc<const ExternalMemoryTarget> externalMemoryTarget; |
| 418 | |
| 419 | // A shared async context key for accessing env |
| 420 | kj::Own<AsyncContextFrame::StorageKey> envAsyncContextKey; |
| 421 | |
| 422 | // A shared async context key for accessing exports |
| 423 | kj::Own<AsyncContextFrame::StorageKey> exportsAsyncContextKey; |
| 424 | |
| 425 | // We expect queues to remain relatively small -- 8 is the largest size I have observed from local |
| 426 | // testing. |
| 427 | static constexpr auto DESTRUCTION_QUEUE_INITIAL_SIZE = 8; |
| 428 | |
| 429 | // If a queue grows larger than this, we reset it back to the initial size. |
| 430 | static constexpr auto DESTRUCTION_QUEUE_MAX_CAPACITY = 10'000; |
| 431 | |
| 432 | // We use a double buffer for our deferred destruction queue. This allows us to avoid any |
| 433 | // allocations in the general, steady state case, and forces us to clear the vector (a O(n) |
| 434 | // operation) outside of the queue lock. |
| 435 | const kj::MutexGuarded<BatchQueue<Item>> queue{ |
| 436 | DESTRUCTION_QUEUE_INITIAL_SIZE, DESTRUCTION_QUEUE_MAX_CAPACITY}; |
| 437 | |
| 438 | enum QueueState { ACTIVE, DROPPING, DROPPED }; |
| 439 | QueueState queueState = ACTIVE; |
| 440 | |
| 441 | struct CodeBlockInfo { |
| 442 | size_t size = 0; |
| 443 | kj::Maybe<v8::JitCodeEvent::CodeType> type; |
| 444 | kj::String name; |
| 445 | |
| 446 | struct PositionMapping { |
| 447 | uint instructionOffset; |
| 448 | uint sourceOffset; |
| 449 | }; |
| 450 | kj::Array<PositionMapping> mapping; |
| 451 | // Sorted |
| 452 | }; |
| 453 | |
| 454 | // Maps instructions to source code locations. |
| 455 | kj::TreeMap<uintptr_t, CodeBlockInfo> codeMap; |
| 456 | |
| 457 | explicit IsolateBase(V8System& system, |
| 458 | v8::Isolate::CreateParams&& createParams, |
| 459 | kj::Own<IsolateObserver> observer, |
| 460 | kj::Own<ExternalStringAllocator> externalStringAllocator, |
| 461 | v8::IsolateGroup group); |
| 462 | ~IsolateBase() noexcept(false); |
| 463 | KJ_DISALLOW_COPY_AND_MOVE(IsolateBase); |
| 464 | |
| 465 | void dropWrappers(kj::FunctionParam<void()> drop); |
| 466 | |
| 467 | bool getCaptureThrowsAsRejections() const { |
| 468 | return captureThrowsAsRejections; |
| 469 | } |
| 470 | |
| 471 | // Add an item to the deferred destruction queue. Safe to call from any thread at any time. |
| 472 | void deferDestruction(v8::Global<v8::Data> item); |
| 473 | void deferDestruction(Item item); |
| 474 | |
| 475 | // Destroy everything in the deferred destruction queue and apply deferred external memory |
| 476 | // updates. Called each time a lock is taken. Must be called under the isolate lock. |
| 477 | void applyDeferredActions(); |
| 478 | |
| 479 | static void fatalError(const char* location, const char* message); |
| 480 | static void oomError(const char* location, const v8::OOMDetails& details); |
| 481 | |
| 482 | static v8::ModifyCodeGenerationFromStringsResult modifyCodeGenCallback( |
| 483 | v8::Local<v8::Context> context, v8::Local<v8::Value> source, bool isCodeLike); |
| 484 | static bool allowWasmCallback(v8::Local<v8::Context> context, v8::Local<v8::String> source); |
| 485 | static bool jspiEnabledCallback(v8::Local<v8::Context> context); |
| 486 | |
| 487 | static void jitCodeEvent(const v8::JitCodeEvent* event) noexcept; |
| 488 | |
| 489 | friend kj::Maybe<kj::StringPtr> getJsStackTrace(void* ucontext, kj::ArrayPtr<char> scratch); |
| 490 | |
| 491 | HeapTracer heapTracer; |
| 492 | kj::Own<IsolateObserver> observer; |
| 493 | kj::Own<ExternalStringAllocator> externalStringAllocator; |
| 494 | |
| 495 | friend class Data; |
| 496 | friend class Wrappable; |
| 497 | friend class HeapTracer; |
| 498 | friend class ExternalMemoryTarget; |
| 499 | |
| 500 | friend bool getCaptureThrowsAsRejections(v8::Isolate* isolate); |
| 501 | friend kj::Maybe<kj::StringPtr> getJsStackTrace(void* ucontext, kj::ArrayPtr<char> scratch); |
| 502 | |
| 503 | friend kj::Exception createTunneledException( |
| 504 | v8::Isolate* isolate, v8::Local<v8::Value> exception); |
| 505 | |
| 506 | // Get a singleton ObjectTemplate used for opaque wrappers (which have an empty-object interface |
| 507 | // in JavaScript). (Called by Wrappable::attachOpaqueWrapper().) |
| 508 | // |
| 509 | // This returns a FunctionTemplate which should be used as a constructor. That is, you can use |
| 510 | // use `->InstanceTemplate()->NewInstance()` to construct an object, and you can pass this to |
| 511 | // `FindInstanceInPrototypeChain()` on an existing object to check whether it was created using |
| 512 | // this template. |
| 513 | static v8::Local<v8::FunctionTemplate> getOpaqueTemplate(v8::Isolate* isolate); |
| 514 | }; |
| 515 | |
| 516 | // If JavaScript frames are currently on the stack, returns a string representing a stack trace |
| 517 | // through it. The trace is built inside `scratch` without performing any allocation. This is |
| 518 | // intended to be invoked from a signal handler. |
| 519 | kj::Maybe<kj::StringPtr> getJsStackTrace(void* ucontext, kj::ArrayPtr<char> scratch); |
| 520 | |
| 521 | // Set the location of the pointer cage base for the current isolate. This is only |
| 522 | // used by getJsCageBase(). |
| 523 | void setJsCageBase(void* cageBase); |
| 524 | |
| 525 | // Get the location previously set by setJsCageBase() for the current isolate. Returns |
| 526 | // a null pointer if there is no current isolate. |
| 527 | void* getJsCageBase(); |
| 528 | |
| 529 | // Class representing a JavaScript execution engine, with the ability to wrap some set of API |
| 530 | // classes which you specify. |
| 531 | // |
| 532 | // To use this, you must declare your own custom specialization listing all of the API types that |
| 533 | // you want to support in this JavaScript context. API types are types which have |
| 534 | // JSG_RESOURCE_TYPE or JSG_STRUCT declarations, as well as TypeWrapperExtensions. |
| 535 | // |
| 536 | // To declare a specialization, do: |
| 537 | // |
| 538 | // JSG_DECLARE_ISOLATE_TYPE(MyIsolateType, MyApiType1, MyApiType2, ...); |
| 539 | // |
| 540 | // This declares a class `MyIsolateType` which is a subclass of Isolate. You can then |
| 541 | // instantiate this class to begin executing JavaScript. |
| 542 | // |
| 543 | // You can instantiate multiple Isolates which can run on separate threads simultaneously. |
| 544 | // |
| 545 | // Example usage: |
| 546 | // |
| 547 | // // Create once per process, probably in main(). |
| 548 | // V8System system; |
| 549 | // |
| 550 | // // Create an isolate with the ability to wrap MyType and MyContextType. |
| 551 | // JSG_DECLARE_ISOLATE_TYPE(MyIsolate, MyApiType, MyContextApiType); |
| 552 | // MyIsolate isolate(system); |
| 553 | // |
| 554 | // // Lock the isolate in this thread (creates a v8::Isolate::Scope). |
| 555 | // isolate.runInLockScope([&] (MyIsolate::Lock& lock) { |
| 556 | // // Create a context based on MyContextType. |
| 557 | // v8::Local<v8::Context> context = lock.newContext(lock.isolate, MyContextType()); |
| 558 | // |
| 559 | // // Create an instance of MyType. |
| 560 | // v8::Local<v8::Object> obj = lock.getTypeHandler<MyType>().wrap(lock, context, MyType()); |
| 561 | // }); |
| 562 | // |
| 563 | template <typename TypeWrapper> |
| 564 | class Isolate: public IsolateBase { |
| 565 | public: |
| 566 | // Construct an isolate that requires configuration. `configuration` is a value that all |
| 567 | // individual wrappers' configurations must be able to be constructed from. For example, if all |
| 568 | // wrappers use the same configuration type, then `MetaConfiguration` should just be that type. |
| 569 | // If different wrappers use different types, then `MetaConfiguration` should be some value that |
| 570 | // inherits or defines conversion operators to each required type -- or the individual |
| 571 | // configuration types must declare constructors from `MetaConfiguration`. |
| 572 | // If `instantiateTypeWrapper` is false, then the default wrapper will not be instantiated |
| 573 | // and should be instantiated with `instantiateTypeWrapper` before `newContext` is called on |
| 574 | // a jsg::Lock of this Isolate. |
| 575 | // |
| 576 | // If using v8 sandboxing, the group argument controls which isolates share a |
| 577 | // sandbox, and which are isolated (as much as possible) in the event of a |
| 578 | // heap corruption attack. Note: The isolates in a group are limited to at |
| 579 | // most 4Gbytes of V8 heap in all. Groups can be created with |
| 580 | // v8::IsolateGroup::Create(). (If using V8 pointer compression, this |
| 581 | // requires the enable_pointer_compression_multiple_cages build flag for V8.) |
| 582 | // Pass v8::IsolateGroup::Default() as the group to put all isolates in the |
| 583 | // same group. |
| 584 | template <typename MetaConfiguration> |
| 585 | explicit Isolate(V8System& system, |
| 586 | v8::IsolateGroup group, |
| 587 | MetaConfiguration&& configuration, |
| 588 | kj::Own<IsolateObserver> observer, |
| 589 | kj::Own<ExternalStringAllocator> externalStringAllocator = defaultExternalStringAllocator(), |
| 590 | v8::Isolate::CreateParams createParams = {}, |
| 591 | bool instantiateTypeWrapper = true) |
| 592 | : IsolateBase(system, |
| 593 | kj::mv(createParams), |
| 594 | kj::mv(observer), |
| 595 | kj::mv(externalStringAllocator), |
| 596 | group) { |
| 597 | wrappers.resize(1); |
| 598 | if (instantiateTypeWrapper) { |
| 599 | instantiateDefaultWrapper(kj::fwd<MetaConfiguration>(configuration)); |
| 600 | } |
| 601 | } |
| 602 | |
| 603 | // Legacy isolate constructor that creates a new IsolateGroup for the new |
| 604 | // Isolate. Currently used by non-sandboxing edgeworker, but deprecated. |
| 605 | template <typename MetaConfiguration> |
| 606 | explicit Isolate(V8System& system, |
| 607 | MetaConfiguration&& configuration, |
| 608 | kj::Own<IsolateObserver> observer, |
| 609 | v8::Isolate::CreateParams createParams = {}, |
| 610 | bool instantiateTypeWrapper = true) |
| 611 | : IsolateBase(system, |
| 612 | kj::mv(createParams), |
| 613 | kj::mv(observer), |
| 614 | defaultExternalStringAllocator(), |
| 615 | v8::IsolateGroup::Create()) { |
| 616 | wrappers.resize(1); |
| 617 | if (instantiateTypeWrapper) { |
| 618 | instantiateDefaultWrapper(kj::fwd<MetaConfiguration>(configuration)); |
| 619 | } |
| 620 | } |
| 621 | |
| 622 | // Use this constructor when no wrappers have any required configuration. |
| 623 | explicit Isolate(V8System& system, |
| 624 | kj::Own<IsolateObserver> observer, |
| 625 | v8::Isolate::CreateParams createParams = {}) |
| 626 | : Isolate(system, |
| 627 | v8::IsolateGroup::GetDefault(), |
| 628 | nullptr, |
| 629 | kj::mv(observer), |
| 630 | defaultExternalStringAllocator(), |
| 631 | kj::mv(createParams)) {} |
| 632 | |
| 633 | template <typename MetaConfiguration> |
| 634 | void instantiateDefaultWrapper(MetaConfiguration&& configuration) { |
| 635 | KJ_DASSERT(wrappers[0].get() == nullptr); |
| 636 | auto wrapper = wrapperSpace.construct(ptr, kj::fwd<MetaConfiguration>(configuration)); |
| 637 | wrapper->initTypeWrapper(); |
| 638 | wrappers[0] = kj::mv(wrapper); |
| 639 | } |
| 640 | |
| 641 | ~Isolate() noexcept(false) { |
| 642 | dropWrappers([this]() { wrappers.clear(); }); |
| 643 | } |
| 644 | |
| 645 | kj::Exception unwrapException( |
| 646 | Lock& js, v8::Local<v8::Context> context, v8::Local<v8::Value> exception) override { |
| 647 | return getWrapperByContext(context)->template unwrap<kj::Exception>( |
| 648 | js, context, exception, jsg::TypeErrorContext::other()); |
| 649 | } |
| 650 | |
| 651 | v8::Local<v8::Value> wrapException( |
| 652 | Lock& js, v8::Local<v8::Context> context, kj::Exception&& exception) override { |
| 653 | return getWrapperByContext(context)->wrap( |
| 654 | js, context, kj::none, kj::fwd<kj::Exception>(exception)); |
| 655 | } |
| 656 | |
| 657 | bool serialize( |
| 658 | Lock& js, std::type_index type, jsg::Object& instance, Serializer& serializer) override { |
| 659 | auto* wrapper = getWrapperByContext(js); |
| 660 | KJ_IF_SOME(func, wrapper->serializerMap.find(type)) { |
| 661 | func(*wrapper, js, instance, serializer); |
| 662 | return true; |
| 663 | } else { |
| 664 | return false; |
| 665 | } |
| 666 | } |
| 667 | kj::Maybe<v8::Local<v8::Object>> deserialize( |
| 668 | Lock& js, uint tag, Deserializer& deserializer) override { |
| 669 | auto* wrapper = getWrapperByContext(js); |
| 670 | KJ_IF_SOME(func, wrapper->deserializerMap.find(tag)) { |
| 671 | return func(*wrapper, js, tag, deserializer); |
| 672 | } else { |
| 673 | return kj::none; |
| 674 | } |
| 675 | } |
| 676 | |
| 677 | // Before you can execute code in your Isolate you must lock it to the current thread by |
| 678 | // constructing a `Lock` on the stack. |
| 679 | class Lock final: public jsg::Lock { |
| 680 | |
| 681 | public: |
| 682 | // `V8StackScope` must be provided to prove that one has been created on the stack before |
| 683 | // taking a lock. Any GC'ed pointers stored on the stack must be kept within this scope in |
| 684 | // order for V8's stack-scanning GC to find them. |
| 685 | Lock(const Isolate& isolate, V8StackScope&) |
| 686 | : jsg::Lock(isolate.ptr), |
| 687 | jsgIsolate(const_cast<Isolate&>(isolate)) { |
| 688 | jsgIsolate.applyDeferredActions(); |
| 689 | } |
| 690 | KJ_DISALLOW_COPY_AND_MOVE(Lock); |
| 691 | KJ_DISALLOW_AS_COROUTINE_PARAM; |
| 692 | |
| 693 | // Creates a `TypeHandler` for the given type. You can use this to convert between the type |
| 694 | // and V8 handles, as well as to allocate instances of the type on the V8 heap (if it is |
| 695 | // a resource type). |
| 696 | template <typename T> |
| 697 | const TypeHandler<T>& getTypeHandler() { |
| 698 | return TypeWrapper::template TYPE_HANDLER_INSTANCE<T>; |
| 699 | } |
| 700 | |
| 701 | // Wrap a C++ value, returning a v8::Local (possibly of a specific type). |
| 702 | template <typename T> |
| 703 | auto wrap(v8::Local<v8::Context> context, T&& value) { |
| 704 | return jsgIsolate.getWrapperByContext(context)->wrap( |
| 705 | *this, context, kj::none, kj::fwd<T>(value)); |
| 706 | } |
| 707 | |
| 708 | // Wrap a context-independent value. Only a few built-in types, like numbers and strings, |
| 709 | // can be wrapped without a context. |
| 710 | template <typename T> |
| 711 | auto wrapNoContext(T&& value) { |
| 712 | return jsgIsolate.getWrapperByContext(*this)->wrap(v8Isolate, kj::none, kj::fwd<T>(value)); |
| 713 | } |
| 714 | |
| 715 | // Convert a JavaScript value to a C++ value, or throw a JS exception if the type doesn't |
| 716 | // match. |
| 717 | template <typename T> |
| 718 | auto unwrap(v8::Local<v8::Context> context, v8::Local<v8::Value> handle) { |
| 719 | return jsgIsolate.getWrapperByContext(context)->template unwrap<T>( |
| 720 | *this, context, handle, jsg::TypeErrorContext::other()); |
| 721 | } |
| 722 | |
| 723 | Ref<DOMException> domException( |
| 724 | kj::String name, kj::String message, kj::Maybe<kj::String> maybeStack) override { |
| 725 | return withinHandleScope([&] { |
| 726 | v8::Local<v8::FunctionTemplate> tmpl = jsgIsolate.getWrapperByContext(*this)->getTemplate( |
| 727 | v8Isolate, static_cast<DOMException*>(nullptr)); |
| 728 | KJ_DASSERT(!tmpl.IsEmpty()); |
| 729 | v8::Local<v8::Object> obj = check(tmpl->InstanceTemplate()->NewInstance(v8Context())); |
| 730 | v8::Local<v8::String> stackName = str("stack"_kjc); |
| 731 | |
| 732 | KJ_IF_SOME(stack, maybeStack) { |
| 733 | v8::PropertyDescriptor prop(str(stack), true); |
| 734 | prop.set_enumerable(true); |
| 735 | jsg::check(obj->DefineProperty(v8Context(), stackName, prop)); |
| 736 | } else { |
| 737 | v8::Exception::CaptureStackTrace(v8Context(), obj); |
| 738 | v8::PropertyDescriptor prop; |
| 739 | prop.set_enumerable(true); |
| 740 | jsg::check(obj->DefineProperty(v8Context(), stackName, prop)); |
| 741 | } |
| 742 | |
| 743 | auto de = alloc<DOMException>(kj::mv(message), kj::mv(name)); |
| 744 | de.attachWrapper(v8Isolate, obj); |
| 745 | |
| 746 | return kj::mv(de); |
| 747 | }); |
| 748 | } |
| 749 | |
| 750 | // Returns the constructor function for a given type declared as JSG_RESOURCE_TYPE. |
| 751 | // |
| 752 | // Note there's a useful property of class constructor functions: A constructor's __proto__ |
| 753 | // is set to the parent type's constructor. Thus you can discover whether one class is a |
| 754 | // subclass of another by following the __proto__ chain. |
| 755 | // |
| 756 | // TODO(cleanup): This should return `JsFunction`, but there is no such type. We only have |
| 757 | // `jsg::Function<...>` (or perhaps more appropriately, `jsg::Constructor<...>`), but we |
| 758 | // don't actually know the function signature so that's not useful here. Should we add a |
| 759 | // `JsFunction` that has no signature? |
| 760 | template <typename T> |
| 761 | jsg::JsObject getConstructor(v8::Local<v8::Context> context) { |
| 762 | v8::EscapableHandleScope scope(v8Isolate); |
| 763 | v8::Local<v8::FunctionTemplate> tpl = |
| 764 | jsgIsolate.getWrapperByContext(context)->getTemplate(v8Isolate, static_cast<T*>(nullptr)); |
| 765 | v8::Local<v8::Object> prototype = check(tpl->GetFunction(context)); |
| 766 | return jsg::JsObject(scope.Escape(prototype)); |
| 767 | } |
| 768 | |
| 769 | v8::Local<v8::ArrayBuffer> wrapBytes(kj::Array<byte> data) override { |
| 770 | return jsgIsolate.getWrapperByContext(*this)->wrap(v8Isolate, kj::none, kj::mv(data)); |
| 771 | } |
| 772 | v8::Local<v8::Function> wrapSimpleFunction(v8::Local<v8::Context> context, |
| 773 | jsg::Function<void(const v8::FunctionCallbackInfo<v8::Value>& info)> simpleFunction) |
| 774 | override { |
| 775 | return jsgIsolate.getWrapperByContext(context)->wrap( |
| 776 | *this, context, kj::none, kj::mv(simpleFunction)); |
| 777 | } |
| 778 | v8::Local<v8::Function> wrapReturningFunction(v8::Local<v8::Context> context, |
| 779 | jsg::Function<v8::Local<v8::Value>(const v8::FunctionCallbackInfo<v8::Value>& info)> |
| 780 | returningFunction) override { |
| 781 | return jsgIsolate.getWrapperByContext(context)->wrap( |
| 782 | *this, context, kj::none, kj::mv(returningFunction)); |
| 783 | } |
| 784 | v8::Local<v8::Function> wrapPromiseReturningFunction(v8::Local<v8::Context> context, |
| 785 | jsg::Function<jsg::Promise<jsg::Value>(const v8::FunctionCallbackInfo<v8::Value>& info)> |
| 786 | returningFunction) override { |
| 787 | return jsgIsolate.getWrapperByContext(context)->wrap( |
| 788 | *this, context, kj::none, kj::mv(returningFunction)); |
| 789 | } |
| 790 | kj::String toString(v8::Local<v8::Value> value) override { |
| 791 | return jsgIsolate.getWrapperByContext(*this)->template unwrap<kj::String>( |
| 792 | *this, v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other()); |
| 793 | } |
| 794 | jsg::Dict<v8::Local<v8::Value>> toDict(v8::Local<v8::Value> value) override { |
| 795 | return jsgIsolate.getWrapperByContext(*this) |
| 796 | ->template unwrap<jsg::Dict<v8::Local<v8::Value>>>( |
| 797 | *this, v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other()); |
| 798 | } |
| 799 | jsg::Dict<jsg::JsValue> toDict(const jsg::JsValue& value) override { |
| 800 | return jsgIsolate.getWrapperByContext(*this)->template unwrap<jsg::Dict<jsg::JsValue>>( |
| 801 | *this, v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other()); |
| 802 | } |
| 803 | v8::Local<v8::Promise> wrapSimplePromise(jsg::Promise<jsg::Value> promise) override { |
| 804 | return jsgIsolate.getWrapperByContext(*this)->wrap( |
| 805 | *this, v8Context(), kj::none, kj::mv(promise)); |
| 806 | } |
| 807 | jsg::Promise<jsg::Value> toPromise(v8::Local<v8::Value> promise) override { |
| 808 | return jsgIsolate.getWrapperByContext(*this)->template unwrap<jsg::Promise<jsg::Value>>( |
| 809 | *this, v8Isolate->GetCurrentContext(), promise, jsg::TypeErrorContext::other()); |
| 810 | } |
| 811 | |
| 812 | template <typename T, typename... Args> |
| 813 | JsContext<T> newContextWithWrapper( |
| 814 | TypeWrapper* wrapper, NewContextOptions options, Args&&... args) { |
| 815 | // TODO(soon): Requiring move semantics for the global object is awkward. This should instead |
| 816 | // allocate the object (forwarding arguments to the constructor) and return something like |
| 817 | // a Ref. |
| 818 | auto context = wrapper->newContext(*this, options, jsgIsolate.getObserver(), |
| 819 | static_cast<T*>(nullptr), kj::fwd<Args>(args)...); |
| 820 | jsg::setAlignedPointerInEmbedderData( |
| 821 | context.getHandle(v8Isolate), jsg::ContextPointerSlot::EXTENDED_CONTEXT_WRAPPER, wrapper); |
| 822 | return context; |
| 823 | } |
| 824 | |
| 825 | // Creates a new JavaScript "context", i.e. the global object. This is the first step to |
| 826 | // executing JavaScript code. T should be one of your API types which you want to use as the |
| 827 | // global object. `args...` are passed to the type's constructor. |
| 828 | template <typename T, typename... Args> |
| 829 | JsContext<T> newContext(NewContextOptions options, Args&&... args) { |
| 830 | KJ_DASSERT(!jsgIsolate.wrappers.empty()); |
| 831 | KJ_DASSERT(jsgIsolate.wrappers[0].get() != nullptr); |
| 832 | return newContextWithWrapper<T>( |
| 833 | jsgIsolate.wrappers[0].get(), options, kj::fwd<Args>(args)...); |
| 834 | } |
| 835 | |
| 836 | // Creates a new JavaScript "context", i.e. the global object. This is the first step to |
| 837 | // executing JavaScript code. T should be one of your API types which you want to use as the |
| 838 | // global object. `args...` are passed to the type's constructor. |
| 839 | template <typename T, typename... Args> |
| 840 | JsContext<T> newContext(Args&&... args) { |
| 841 | return newContext<T>(NewContextOptions{}, kj::fwd<Args>(args)...); |
| 842 | } |
| 843 | |
| 844 | template <typename T, typename MetaConfiguration, typename... Args> |
| 845 | JsContext<T> newContextWithConfiguration( |
| 846 | MetaConfiguration&& configuration, NewContextOptions options, Args&&... args) { |
| 847 | jsgIsolate.hasExtraWrappers = true; |
| 848 | auto& wrapper = jsgIsolate.wrappers.add( |
| 849 | kj::heap<TypeWrapper>(jsgIsolate.ptr, kj::fwd<MetaConfiguration>(configuration))); |
| 850 | return newContextWithWrapper<T>(wrapper.get(), options, kj::fwd<Args>(args)...); |
| 851 | } |
| 852 | |
| 853 | void reportError(const JsValue& value) override { |
| 854 | auto& js = Lock::from(v8Isolate); |
| 855 | KJ_IF_SOME(domException, |
| 856 | jsgIsolate.getWrapperByContext(*this)->tryUnwrap( |
| 857 | js, v8Context(), value, static_cast<DOMException*>(nullptr), kj::none)) { |
| 858 | auto desc = |
| 859 | kj::str("DOMException(", domException.getName(), "): ", domException.getMessage()); |
| 860 | jsgIsolate.reportError(*this, kj::mv(desc), value, JsMessage::create(*this, value)); |
| 861 | } else { |
| 862 | jsgIsolate.reportError( |
| 863 | *this, value.toString(*this), value, JsMessage::create(*this, value)); |
| 864 | } |
| 865 | } |
| 866 | |
| 867 | void setWorkerEnv(V8Ref<v8::Object> value) override { |
| 868 | jsgIsolate.workerEnvObj.Reset(v8Isolate, value.getHandle(*this)); |
| 869 | } |
| 870 | |
| 871 | kj::Maybe<V8Ref<v8::Object>> getWorkerEnv() override { |
| 872 | if (jsgIsolate.workerEnvObj.IsEmpty()) return kj::none; |
| 873 | return v8Ref<v8::Object>(jsgIsolate.workerEnvObj.Get(v8Isolate)); |
| 874 | } |
| 875 | |
| 876 | void setWorkerExports(V8Ref<v8::Object> value) override { |
| 877 | jsgIsolate.workerExportsObj.Reset(v8Isolate, value.getHandle(*this)); |
| 878 | } |
| 879 | |
| 880 | kj::Maybe<V8Ref<v8::Object>> getWorkerExports() override { |
| 881 | if (jsgIsolate.workerExportsObj.IsEmpty()) return kj::none; |
| 882 | return v8Ref<v8::Object>(jsgIsolate.workerExportsObj.Get(v8Isolate)); |
| 883 | } |
| 884 | |
| 885 | private: |
| 886 | Isolate& jsgIsolate; |
| 887 | |
| 888 | virtual kj::Maybe<Object&> getInstance( |
| 889 | v8::Local<v8::Object> obj, const std::type_info& type) override { |
| 890 | auto instance = v8::Local<v8::Object>(obj)->FindInstanceInPrototypeChain( |
| 891 | jsgIsolate.getWrapperByContext(*this)->getDynamicTypeInfo(v8Isolate, type).tmpl); |
| 892 | if (instance.IsEmpty()) { |
| 893 | return kj::none; |
| 894 | } else { |
| 895 | return *reinterpret_cast<Object*>( |
| 896 | instance->GetAlignedPointerFromInternalField(Wrappable::WRAPPED_OBJECT_FIELD_INDEX, |
| 897 | static_cast<v8::EmbedderDataTypeTag>(Wrappable::WRAPPED_OBJECT_FIELD_INDEX))); |
| 898 | } |
| 899 | } |
| 900 | |
| 901 | virtual v8::Local<v8::Object> getPrototypeFor(const std::type_info& type) override { |
| 902 | v8::EscapableHandleScope scope(v8Isolate); |
| 903 | auto tmpl = jsgIsolate.getWrapperByContext(*this)->getDynamicTypeInfo(v8Isolate, type).tmpl; |
| 904 | auto constructor = JsObject(check(tmpl->GetFunction(v8Context()))); |
| 905 | |
| 906 | // Note that `constructor.getPrototype()` returns the prototype of the constructor itself, |
| 907 | // which is NOT the same as the prototype of the object it constructs. For the latter we |
| 908 | // need to access the `prototype` property. |
| 909 | auto proto = constructor.get(*this, "prototype"); |
| 910 | |
| 911 | KJ_ASSERT(proto.isObject()); |
| 912 | return scope.Escape(v8::Local<v8::Value>(proto).As<v8::Object>()); |
| 913 | } |
| 914 | }; |
| 915 | |
| 916 | // The func must be a callback with the signature: T(jsg::Lock&) |
| 917 | // Be careful not to leak v8 objects outside of the scope. |
| 918 | auto runInLockScope(auto func) { |
| 919 | return runInV8Stack([&](V8StackScope& stackScope) { |
| 920 | Lock lock(*this, stackScope); |
| 921 | return lock.withinHandleScope([&] { return func(lock); }); |
| 922 | }); |
| 923 | } |
| 924 | |
| 925 | protected: |
| 926 | inline TypeWrapper* getWrapperByContext(jsg::Lock& js) { |
| 927 | if (KJ_LIKELY(!hasExtraWrappers)) { |
| 928 | return wrappers[0].get(); |
| 929 | } else { |
| 930 | return getWrapperByContext(js.v8Context()); |
| 931 | } |
| 932 | } |
| 933 | inline TypeWrapper* getWrapperByContext(v8::Local<v8::Context> context) { |
| 934 | if (KJ_LIKELY(!hasExtraWrappers)) { |
| 935 | return wrappers[0].get(); |
| 936 | } else { |
| 937 | KJ_IF_SOME(data, |
| 938 | jsg::getAlignedPointerFromEmbedderData<TypeWrapper>( |
| 939 | context, ContextPointerSlot::EXTENDED_CONTEXT_WRAPPER)) { |
| 940 | return &data; |
| 941 | } |
| 942 | return wrappers[0].get(); |
| 943 | } |
| 944 | } |
| 945 | |
| 946 | private: |
| 947 | kj::SpaceFor<TypeWrapper> wrapperSpace; |
| 948 | kj::Vector<kj::Own<TypeWrapper>> wrappers; // Needs to be destroyed under lock... |
| 949 | // This is just an optimization boolean, when we only have one wrapper we can skip calling |
| 950 | // GetAlignedPointerFromEmbedderData and just return wrappers[0]. |
| 951 | bool hasExtraWrappers = false; |
| 952 | }; |
| 953 | |
| 954 | } // namespace workerd::jsg |