Skip to content
File

Blob: src/workerd/jsg/setup.h

cpp955 lines
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 
24namespace workerd::jsg {
25 
26class Deserializer;
27class 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.
39kj::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.
44class 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.
87class 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.
519kj::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().
523void 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.
527void* 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//
563template <typename TypeWrapper>
564class 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