Skip to content
File

Blob: src/workerd/jsg/jsg.c++

22.6 KB
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#include "jsg.h"
6 
7#include "setup.h"
8#include "simdutf.h"
9 
10#include <workerd/jsg/modules-new.h>
11#include <workerd/jsg/modules.h>
12#include <workerd/jsg/util.h>
13#include <workerd/util/thread-scopes.h>
14 
15#ifdef V8_ENABLE_SANDBOX
16#include <sys/mman.h>
17#endif
18 
19namespace workerd::jsg {
20 
21kj::String stringifyHandle(v8::Local<v8::Value> value) {
22 // TODO(cleanup): Perhaps we should require you to call `js.toString(handle)`?
23 auto& js = jsg::Lock::current();
24 return js.withinHandleScope([&] {
25 v8::Local<v8::String> str = workerd::jsg::check(value->ToDetailString(js.v8Context()));
26 v8::String::Utf8Value utf8(js.v8Isolate, str);
27 if (*utf8 == nullptr) {
28 return kj::str("(couldn't stringify)");
29 } else {
30 return kj::str(*utf8);
31 }
32 });
33}
34 
35JsExceptionThrown::JsExceptionThrown() {
36 tracePtr = kj::getStackTrace(trace, 0);
37}
38 
39const char* JsExceptionThrown::what() const noexcept {
40 whatBuffer =
41 kj::str("Uncaught JsExceptionThrown\nstack: ", kj::stringifyStackTraceAddresses(tracePtr));
42 return whatBuffer.cStr();
43}
44 
45void Data::destroy() {
46 assertInvariant();
47 if (isolate != nullptr) {
48 if (v8::Locker::IsLocked(isolate)) {
49 handle.Reset();
50 
51 // If we have a TracedReference, Reset() it too, to let V8 know that this value is no longer
52 // used. Note that merely destroying the TracedReference does nothing -- only explicitly
53 // calling Reset() has an effect.
54 //
55 // In particular, this permits `Data` values to be collected by minor (non-tracing) GC, as
56 // long as there are no cycles.
57 //
58 // HOWEVER, this is not safe if the TracedReference is being destroyed as a result of a
59 // major (traced) GC. In that case, the TracedReference itself may point to a reference slot
60 // that was already collected, and trying to reset it would be UB.
61 //
62 // In all other cases, resetting the handle is safe:
63 // - During minor GC, TracedReferences aren't collected by the GC itself, so must still be
64 // valid.
65 // - If the `Data` is being destroyed _not_ as part of GC, e.g. it's being destroyed because
66 // the data structure holding it is being modified in a way that drops the reference, then
67 // that implies that the reference is still reachable, so must still be valid.
68 KJ_IF_SOME(t, tracedHandle) {
69 if (!HeapTracer::isInCppgcDestructor()) {
70 t.Reset();
71 }
72 }
73 } else {
74 // This thread doesn't have the isolate locked right now. To minimize lock contention, we'll
75 // defer these handles' destruction to the next time the isolate is locked.
76 //
77 // Note that only the v8::Global part of `handle` needs to be destroyed under isolate lock.
78 // The `tracedRef` part has a trivial destructor so can be destroyed on any thread.
79 auto& jsgIsolate = *reinterpret_cast<IsolateBase*>(isolate->GetData(SET_DATA_ISOLATE_BASE));
80 jsgIsolate.deferDestruction(v8::Global<v8::Data>(kj::mv(handle)));
81 }
82 isolate = nullptr;
83 }
84}
85 
86void Data::moveFromTraced(Data& other, v8::TracedReference<v8::Data>& otherTracedRef) noexcept {
87 // Implement move constructor when the source of the move has previously been visited for
88 // garbage collection.
89 //
90 // This method is `noexcept` because if an exception is thrown below we're probably going to
91 // segfault later.
92 
93 // We must hold a lock to move from a GC-reachable reference. (But we don't generally need a lock
94 // for moving from non-GC-reachable refs.)
95 KJ_ASSERT(v8::Locker::IsLocked(isolate));
96 
97 // Verify the handle was not garbage-collected by trying to read it. The intention is for this
98 // to crash if the handle was GC'ed before being moved away.
99 {
100 auto& js = jsg::Lock::from(isolate);
101 js.withinHandleScope([&] {
102 auto local = handle.Get(js.v8Isolate);
103 if (local->IsValue()) {
104 local.As<v8::Value>()->IsArrayBufferView();
105 }
106 });
107 }
108 
109 // `other` is a traced `Data`, but once moved, we don't assume the new location is traced.
110 // So, we need to make the handle strong.
111 handle.ClearWeak<void>();
112 
113 // Presumably, `other` is about to be destroyed. The destructor of `TracedReference`, though,
114 // does nothing, because it doesn't know if the reference is even still valid, since it
115 // could be called during GC sweep time. But here, we know that `other` is definitely still
116 // valid, because we wouldn't be moving from an unreachable object. So we should Reset() the
117 // `TracedReference` so that V8 knows it's gone, which might make minor GCs more effective.
118 otherTracedRef.Reset();
119 
120 other.tracedHandle = kj::none;
121}
122 
123Lock::Lock(v8::Isolate* v8Isolate)
124 : v8Isolate(v8Isolate),
125 locker(v8Isolate),
126 isolateScope(v8Isolate),
127 previousData(v8Isolate->GetData(SET_DATA_LOCK)),
128 warningsLogged(IsolateBase::from(v8Isolate).areWarningsLogged()) {
129 if (previousData != nullptr) {
130 // Hmm, there's already a current lock. It must be a recursive lock (i.e. a second lock taken
131 // on the same isolate in the same thread), otherwise `locker`'s constructor would have blocked
132 // waiting for the other thread to release the lock. We don't want to support this, but
133 // historically we have.
134#ifdef KJ_DEBUG
135 // In debug mode, abort immediately. This makes it a little easier to debug than if we threw
136 // an exception.
137 ([]() noexcept { KJ_FAIL_REQUIRE("attempt to take recursive isolate lock"); })();
138#else
139 // In release mode, log the error.
140 // TODO(soon): This shouldn't happen but we know it does in at least one case. Once things
141 // are cleaned up and we know this no longer happens in production, change this to throw.
142 // Then we can stop storing `previousData`.
143 KJ_LOG(ERROR, "took recursive isolate lock", kj::getStackTrace());
144#endif
145 }
146 v8Isolate->SetData(SET_DATA_LOCK, this);
147}
148Lock::~Lock() noexcept(false) {
149 v8Isolate->SetData(SET_DATA_LOCK, previousData);
150}
151 
152Value Lock::parseJson(kj::ArrayPtr<const char> data) {
153 return withinHandleScope(
154 [&] { return v8Ref(jsg::check(v8::JSON::Parse(v8Context(), v8Str(v8Isolate, data)))); });
155}
156 
157Value Lock::parseJson(v8::Local<v8::String> text) {
158 return withinHandleScope([&] { return v8Ref(jsg::check(v8::JSON::Parse(v8Context(), text))); });
159}
160 
161kj::String Lock::serializeJson(v8::Local<v8::Value> value) {
162 return withinHandleScope(
163 [&] { return toString(jsg::check(v8::JSON::Stringify(v8Context(), value))); });
164}
165 
166void Lock::recursivelyFreeze(Value& value) {
167 jsg::recursivelyFreeze(v8Context(), value.getHandle(*this));
168}
169 
170v8::Local<v8::String> Lock::wrapString(kj::StringPtr text) {
171 return v8Str(v8Isolate, text);
172}
173 
174bool Lock::toBool(v8::Local<v8::Value> value) {
175 return value->BooleanValue(v8Isolate);
176}
177 
178v8::Local<v8::Value> Lock::v8Error(kj::StringPtr message) {
179 return v8::Exception::Error(v8Str(v8Isolate, message));
180}
181 
182v8::Local<v8::Value> Lock::v8TypeError(kj::StringPtr message) {
183 return v8::Exception::TypeError(v8Str(v8Isolate, message));
184}
185 
186void Lock::logWarning(kj::StringPtr message) {
187 IsolateBase::from(v8Isolate).logWarning(*this, message);
188}
189 
190void Lock::setAllowEval(bool allow) {
191 IsolateBase::from(v8Isolate).setAllowEval({}, allow);
192}
193 
194void Lock::setUsingEnhancedErrorSerialization() {
195 IsolateBase::from(v8Isolate).setUsingEnhancedErrorSerialization();
196}
197 
198void Lock::setUsingFastJsgStruct() {
199 IsolateBase::from(v8Isolate).setUsingFastJsgStruct();
200}
201 
202bool Lock::isUsingFastJsgStruct() const {
203 return IsolateBase::from(v8Isolate).getUsingFastJsgStruct();
204}
205 
206bool Lock::isUsingEnhancedErrorSerialization() const {
207 return IsolateBase::from(v8Isolate).getUsingEnhancedErrorSerialization();
208}
209 
210void Lock::setCaptureThrowsAsRejections(bool capture) {
211 IsolateBase::from(v8Isolate).setCaptureThrowsAsRejections({}, capture);
212}
213 
214void Lock::setNodeJsCompatEnabled() {
215 IsolateBase::from(v8Isolate).setNodeJsCompatEnabled({}, true);
216}
217 
218void Lock::setNodeJsProcessV2Enabled() {
219 IsolateBase::from(v8Isolate).setNodeJsProcessV2Enabled({}, true);
220}
221 
222void Lock::setRequireReturnsDefaultExportEnabled() {
223 IsolateBase::from(v8Isolate).setRequireReturnsDefaultExportEnabled({}, true);
224}
225 
226void Lock::setThrowOnUnrecognizedImportAssertion() {
227 IsolateBase::from(v8Isolate).setThrowOnUnrecognizedImportAssertion();
228}
229 
230bool Lock::getThrowOnUnrecognizedImportAssertion() const {
231 return IsolateBase::from(v8Isolate).getThrowOnUnrecognizedImportAssertion();
232}
233 
234void Lock::disableTopLevelAwait() {
235 IsolateBase::from(v8Isolate).disableTopLevelAwait();
236}
237 
238void Lock::setToStringTag() {
239 IsolateBase::from(v8Isolate).enableSetToStringTag();
240}
241 
242void Lock::setImmutablePrototype() {
243 IsolateBase::from(v8Isolate).enableSetImmutablePrototype();
244}
245 
246void Lock::setSpecCompliantPropertyAttributes() {
247 IsolateBase::from(v8Isolate).enableSpecCompliantPropertyAttributes();
248}
249 
250void Lock::setLoggerCallback(kj::Function<Logger>&& logger) {
251 IsolateBase::from(v8Isolate).setLoggerCallback({}, kj::mv(logger));
252}
253 
254void Lock::setErrorReporterCallback(kj::Function<ErrorReporter>&& errorReporter) {
255 IsolateBase::from(v8Isolate).setErrorReporterCallback({}, kj::mv(errorReporter));
256}
257 
258void Lock::requestGcForTesting() const {
259 if (!isPredictableModeForTest()) {
260 KJ_LOG(ERROR, "Test GC used while not in a test");
261 return;
262 }
263 v8Isolate->RequestGarbageCollectionForTesting(
264 v8::Isolate::GarbageCollectionType::kFullGarbageCollection);
265}
266 
267void Lock::v8Set(v8::Local<v8::Object> obj, kj::StringPtr name, v8::Local<v8::Value> value) {
268 KJ_ASSERT(check(obj->Set(v8Context(), v8StrIntern(v8Isolate, name), value)));
269}
270 
271void Lock::v8Set(v8::Local<v8::Object> obj, kj::StringPtr name, Value& value) {
272 v8Set(obj, name, value.getHandle(*this));
273}
274 
275void Lock::v8Set(v8::Local<v8::Object> obj, V8Ref<v8::String>& name, Value& value) {
276 KJ_ASSERT(check(obj->Set(v8Context(), name.getHandle(*this), value.getHandle(*this))));
277}
278 
279v8::Local<v8::Value> Lock::v8Get(v8::Local<v8::Object> obj, kj::StringPtr name) {
280 return check(obj->Get(v8Context(), v8StrIntern(v8Isolate, name)));
281}
282 
283v8::Local<v8::Value> Lock::v8Get(v8::Local<v8::Array> obj, uint idx) {
284 return check(obj->Get(v8Context(), idx));
285}
286 
287bool Lock::v8Has(v8::Local<v8::Object> obj, kj::StringPtr name) {
288 return check(obj->Has(v8Context(), v8StrIntern(v8Isolate, name)));
289}
290 
291bool Lock::v8HasOwn(v8::Local<v8::Object> obj, kj::StringPtr name) {
292 return check(obj->HasOwnProperty(v8Context(), v8StrIntern(v8Isolate, name)));
293}
294 
295void Lock::runMicrotasks() {
296 v8Isolate->PerformMicrotaskCheckpoint();
297 
298 auto& isolate = IsolateBase::from(v8Isolate);
299 // We only expect at most a handful of extra checkpoints. Keep a generous cap
300 // to flush cascaded microtasks but prevent a potential busy loop.
301 static constexpr uint MAX_EXTRA_MICROTASK_CHECKPOINTS = 64;
302 for (uint i = 0; i < MAX_EXTRA_MICROTASK_CHECKPOINTS; ++i) {
303 if (!isolate.takeExtraMicrotaskCheckpointRequested({})) {
304 return;
305 }
306 v8Isolate->PerformMicrotaskCheckpoint();
307 }
308 
309 if (isolate.takeExtraMicrotaskCheckpointRequested({})) {
310 KJ_LOG(WARNING, "extra microtask checkpoint limit reached", MAX_EXTRA_MICROTASK_CHECKPOINTS);
311 }
312}
313 
314void Lock::requestExtraMicrotaskCheckpoint() {
315 IsolateBase::from(v8Isolate).requestExtraMicrotaskCheckpoint({});
316}
317 
318void Lock::terminateNextExecution() {
319 v8Isolate->TerminateExecution();
320}
321 
322[[noreturn]] void Lock::terminateExecutionNow() {
323 terminateNextExecution();
324 
325 // HACK: This has been observed to reliably make V8 check the termination flag and raise the
326 // uncatchable termination exception.
327 jsg::check(v8::JSON::Stringify(v8Context(), str()));
328 
329 // Shouldn't get here.
330 KJ_FAIL_ASSERT("V8 did not terminate execution when asked.");
331}
332 
333bool Lock::pumpMsgLoop() {
334 return IsolateBase::from(v8Isolate).pumpMsgLoop();
335}
336 
337Name Lock::newSymbol(kj::StringPtr symbol) {
338 return Name(*this, v8::Symbol::New(v8Isolate, v8StrIntern(v8Isolate, symbol)));
339}
340 
341Name Lock::newSharedSymbol(kj::StringPtr symbol) {
342 return Name(*this, v8::Symbol::For(v8Isolate, v8StrIntern(v8Isolate, symbol)));
343}
344 
345Name Lock::newApiSymbol(kj::StringPtr symbol) {
346 return Name(*this, v8::Symbol::ForApi(v8Isolate, v8StrIntern(v8Isolate, symbol)));
347}
348 
349kj::Maybe<JsObject> Lock::resolveInternalModule(kj::StringPtr specifier) {
350 auto& isolate = IsolateBase::from(v8Isolate);
351 if (isolate.isUsingNewModuleRegistry()) {
352 return jsg::modules::ModuleRegistry::tryResolveModuleNamespace(
353 *this, specifier, jsg::modules::ResolveContext::Type::BUILTIN)
354 .map([](JsValue val) { return KJ_ASSERT_NONNULL(val.tryCast<JsObject>()); });
355 }
356 
357 // Use the original module registry implementation
358 auto registry = ModuleRegistry::from(*this);
359 KJ_ASSERT(registry != nullptr);
360 auto module = registry->resolveInternalImport(*this, specifier);
361 return jsg::JsObject(module.getHandle(*this).As<v8::Object>());
362}
363 
364kj::Maybe<JsObject> Lock::resolvePublicBuiltinModule(kj::StringPtr specifier) {
365 auto& isolate = IsolateBase::from(v8Isolate);
366 KJ_ASSERT(isolate.isUsingNewModuleRegistry());
367 return jsg::modules::ModuleRegistry::tryResolveModuleNamespace(*this, specifier,
368 jsg::modules::ResolveContext::Type::PUBLIC_BUILTIN,
369 jsg::modules::ResolveContext::Source::REQUIRE)
370 .map([](JsValue val) { return KJ_ASSERT_NONNULL(val.tryCast<JsObject>()); });
371}
372 
373kj::Maybe<JsObject> Lock::resolveModule(kj::StringPtr specifier, RequireEsm requireEsm) {
374 auto& isolate = IsolateBase::from(v8Isolate);
375 if (isolate.isUsingNewModuleRegistry()) {
376 return jsg::modules::ModuleRegistry::tryResolveModuleNamespace(
377 *this, specifier, jsg::modules::ResolveContext::Type::BUNDLE)
378 .map([](JsValue val) { return KJ_ASSERT_NONNULL(val.tryCast<JsObject>()); });
379 }
380 
381 auto moduleRegistry = jsg::ModuleRegistry::from(*this);
382 if (moduleRegistry == nullptr) return kj::none;
383 auto spec = kj::Path::parse(specifier);
384 auto& info = JSG_REQUIRE_NONNULL(
385 moduleRegistry->resolve(*this, spec), Error, kj::str("No such module: ", specifier));
386 JSG_REQUIRE(!requireEsm || info.maybeSynthetic == kj::none, TypeError,
387 "Main module must be an ES module.");
388 auto module = info.module.getHandle(*this);
389 jsg::instantiateModule(*this, module);
390 return JsObject(module->GetModuleNamespace().As<v8::Object>());
391}
392 
393void ExternalMemoryTarget::maybeDeferAdjustment(ssize_t amount) const {
394 // Carefully check whether `isolate` is locked by the current thread. Note that there's a
395 // possibility that the isolate is being torn down in a different thread, which means we cannot
396 // safely call `v8::Locekr::IsLocked()` on it.
397 if (amount == 0) return;
398 v8::Isolate* current = v8::Isolate::TryGetCurrent();
399 v8::Isolate* target = isolate.load(std::memory_order_relaxed); // could be null!
400 
401 if (current != nullptr && current == target) {
402 // The isolate is currently locked by this thread. Note that it's impossible that `isolate` is
403 // concurrently being torn down because only the thread that holds the isolate lock could be
404 // making such a change, and that's us, and we're not.
405 KJ_ASSERT(v8::Locker::IsLocked(target));
406 
407 // We use AdjustAmountOfExternalAllocatedMemoryImpl() instead of ExternalMemoryAccounter
408 // because we explicitly want external memory to be allowed to live beyond the isolate
409 // in some cases. The Impl variant bypasses the destructor check that
410 // ExternalMemoryAccounter enforces (requiring adjustment to return to zero).
411 // See patches/v8/0031-Expose-AdjustAmountOfExternalAllocatedMemoryImpl.patch
412 target->AdjustAmountOfExternalAllocatedMemoryImpl(amount);
413 } else {
414 // We don't hold the isolate lock. Instead, record the adjustment to be applied the next time
415 // the isolate lock is acquired.
416 pendingExternalMemoryUpdate.fetch_add(amount, std::memory_order_relaxed);
417 }
418}
419 
420void ExternalMemoryTarget::adjustNow(Lock& js, ssize_t amount) const {
421#ifdef KJ_DEBUG
422 v8::Isolate* target = isolate.load(std::memory_order_relaxed);
423 if (target != nullptr) {
424 KJ_ASSERT(target == js.v8Isolate);
425 }
426#endif
427 if (amount == 0) return;
428 js.v8Isolate->AdjustAmountOfExternalAllocatedMemoryImpl(amount);
429}
430 
431void ExternalMemoryTarget::detach() const {
432 isolate.store(nullptr, std::memory_order_relaxed);
433}
434 
435ExternalMemoryAdjustment ExternalMemoryTarget::getAdjustment(size_t amount) const {
436 return ExternalMemoryAdjustment(this->addRefToThis(), amount);
437}
438 
439void ExternalMemoryTarget::applyDeferredMemoryUpdate() const {
440 int64_t amount = pendingExternalMemoryUpdate.exchange(0, std::memory_order_relaxed);
441 if (amount != 0) {
442 isolate.load(std::memory_order_relaxed)->AdjustAmountOfExternalAllocatedMemoryImpl(amount);
443 }
444}
445 
446bool ExternalMemoryTarget::isIsolateAliveForTest() const {
447 return isolate.load(std::memory_order_relaxed) != nullptr;
448}
449 
450int64_t ExternalMemoryTarget::getPendingMemoryUpdateForTest() const {
451 return pendingExternalMemoryUpdate.load(std::memory_order_relaxed);
452}
453 
454ExternalMemoryAdjustment Lock::getExternalMemoryAdjustment(int64_t amount) {
455 auto adjustment = IsolateBase::from(v8Isolate).getExternalMemoryAdjustment(0);
456 adjustment.adjustNow(*this, amount);
457 return kj::mv(adjustment);
458}
459 
460kj::Arc<const ExternalMemoryTarget> Lock::getExternalMemoryTarget() {
461 return IsolateBase::from(v8Isolate).getExternalMemoryTarget();
462}
463 
464void ExternalMemoryAdjustment::maybeDeferAdjustment(ssize_t amount) {
465 KJ_ASSERT(amount >= -static_cast<ssize_t>(this->amount),
466 "Memory usage may not be decreased below zero");
467 if (amount == 0) return;
468 this->amount += amount;
469 externalMemory->maybeDeferAdjustment(amount);
470}
471 
472ExternalMemoryAdjustment::ExternalMemoryAdjustment(
473 kj::Arc<const ExternalMemoryTarget> externalMemory, size_t amount)
474 : externalMemory(kj::mv(externalMemory)) {
475 if (amount == 0) return;
476 maybeDeferAdjustment(amount);
477}
478ExternalMemoryAdjustment::ExternalMemoryAdjustment(ExternalMemoryAdjustment&& other) noexcept
479 : externalMemory(kj::mv(other.externalMemory)),
480 amount(other.amount) {
481 other.amount = 0;
482}
483 
484ExternalMemoryAdjustment& ExternalMemoryAdjustment::operator=(ExternalMemoryAdjustment&& other) {
485 // If we currently have an amount, adjust it back to zero.
486 // In the case we don't have the isolate lock here, the adjustment
487 // will be deferred until the next time we do.
488 
489 if (amount > 0) maybeDeferAdjustment(-amount);
490 externalMemory = kj::mv(other.externalMemory);
491 amount = other.amount;
492 other.amount = 0;
493 return *this;
494}
495 
496ExternalMemoryAdjustment::~ExternalMemoryAdjustment() noexcept(false) {
497 if (amount != 0) {
498 maybeDeferAdjustment(-amount);
499 }
500}
501 
502void ExternalMemoryAdjustment::adjust(ssize_t amount) {
503 if (amount == 0) return;
504 maybeDeferAdjustment(amount);
505}
506 
507void ExternalMemoryAdjustment::adjustNow(Lock& js, ssize_t amount) {
508 KJ_ASSERT(amount >= -static_cast<ssize_t>(this->amount),
509 "Memory usage may not be decreased below zero");
510 
511 this->amount += amount;
512 externalMemory->adjustNow(js, amount);
513}
514 
515void ExternalMemoryAdjustment::set(size_t amount) {
516 adjust(amount - this->amount);
517}
518 
519void ExternalMemoryAdjustment::setNow(Lock& js, size_t amount) {
520 adjustNow(js, amount - this->amount);
521}
522 
523Name::Name(kj::String string): hash(kj::hashCode(string)), inner(kj::mv(string)) {}
524 
525Name::Name(kj::StringPtr string): hash(kj::hashCode(string)), inner(kj::str(string)) {}
526 
527Name::Name(Lock& js, v8::Local<v8::Symbol> symbol)
528 : hash(kj::hashCode(symbol->GetIdentityHash())),
529 inner(js.v8Ref(symbol)) {}
530 
531kj::OneOf<kj::StringPtr, v8::Local<v8::Symbol>> Name::getUnwrapped(v8::Isolate* isolate) {
532 KJ_SWITCH_ONEOF(inner) {
533 KJ_CASE_ONEOF(str, kj::String) {
534 return str.asPtr();
535 }
536 KJ_CASE_ONEOF(symbol, V8Ref<v8::Symbol>) {
537 return symbol.getHandle(isolate);
538 }
539 }
540 KJ_UNREACHABLE;
541}
542 
543void Name::visitForGc(GcVisitor& visitor) {
544 KJ_SWITCH_ONEOF(inner) {
545 KJ_CASE_ONEOF(string, kj::String) {}
546 KJ_CASE_ONEOF(symbol, V8Ref<v8::Symbol>) {
547 visitor.visit(symbol);
548 }
549 }
550}
551 
552Name Name::clone(jsg::Lock& js) {
553 KJ_SWITCH_ONEOF(inner) {
554 KJ_CASE_ONEOF(str, kj::String) {
555 return Name(kj::str(str));
556 }
557 KJ_CASE_ONEOF(symbol, V8Ref<v8::Symbol>) {
558 return Name(js, symbol.getHandle(js));
559 }
560 }
561 KJ_UNREACHABLE;
562}
563 
564kj::String Name::toString(jsg::Lock& js) {
565 KJ_SWITCH_ONEOF(inner) {
566 KJ_CASE_ONEOF(str, kj::String) {
567 return kj::str(str);
568 }
569 KJ_CASE_ONEOF(sym, V8Ref<v8::Symbol>) {
570 return kj::str("Symbol(", sym.getHandle(js)->Description(js.v8Isolate), ")");
571 }
572 }
573 KJ_UNREACHABLE;
574}
575 
576bool isInGcDestructor() {
577 return HeapTracer::isInCppgcDestructor();
578}
579 
580bool USVString::isValidUtf8() const {
581 return simdutf::validate_utf8(cStr(), size());
582}
583 
584std::unique_ptr<v8::BackingStore> Lock::allocBackingStore(size_t size, AllocOption init_mode) {
585 auto v8_mode = (init_mode == AllocOption::ZERO_INITIALIZED)
586 ? v8::BackingStoreInitializationMode::kZeroInitialized
587 : v8::BackingStoreInitializationMode::kUninitialized;
588 auto store = v8::ArrayBuffer::NewBackingStore(
589 v8Isolate, size, v8_mode, v8::BackingStoreOnFailureMode::kReturnNull);
590 JSG_REQUIRE(store != nullptr, RangeError, "Failed to allocate ArrayBuffer backing store");
591 return kj::mv(store);
592}
593 
594const capnp::SchemaLoader& ContextGlobal::getSchemaLoader() {
595 return KJ_ASSERT_NONNULL(schemaLoader);
596}
597 
598void ContextGlobal::setSchemaLoader(const capnp::SchemaLoader& schemaLoader) {
599 this->schemaLoader = schemaLoader;
600}
601 
602#ifdef V8_ENABLE_SANDBOX
603// These are disabled by default in workerd. We do not build workerd with
604// the V8_ENABLED_SANDBOX flag. If we do decide to enable it, we will need
605// additional setup to ensure that these are handled correctly on all platforms.
606// For now, keeping it simple. This bit will only be used in the internal
607// project.
608static constexpr int kPkeyNoRestrictions = 0;
609MemoryProtectionKeyScope::MemoryProtectionKeyScope(Lock& js)
610 : pkey(js.v8Isolate->GetMemoryProtectionKey()) {}
611 
612MemoryProtectionKeyScope::PkeyScope::PkeyScope(int pkey): key(pkey), saved(pkey_get(key)) {
613 pkey_set(pkey, kPkeyNoRestrictions);
614}
615MemoryProtectionKeyScope::PkeyScope::~PkeyScope() {
616 pkey_set(key, saved);
617}
618#endif
619 
620namespace _ {
621 
622JsgCatchScope::JsgCatchScope(Lock& js): js(js) {
623 tryCatchHolder.emplace(js.v8Isolate);
624}
625 
626void JsgCatchScope::catchException(ExceptionToJsOptions options) {
627 // Be sure to release our TryCatch on the way out.
628 KJ_DEFER(tryCatchHolder = kj::none);
629 
630 auto& tryCatch = KJ_ASSERT_NONNULL(tryCatchHolder).tryCatch;
631 
632 // Same logic as that found in `jsg::Lock::tryCatch()`.
633 try {
634 throw;
635 } catch (JsExceptionThrown&) {
636 if (!tryCatch.CanContinue() || !tryCatch.HasCaught() || tryCatch.Exception().IsEmpty()) {
637 tryCatch.ReThrow();
638 throw;
639 }
640 caughtException.emplace(js.v8Isolate, tryCatch.Exception());
641 } catch (kj::Exception& e) {
642 caughtException.emplace(js.exceptionToJs(kj::mv(e), options));
643 }
644}
645 
646} // namespace _
647 
648} // namespace workerd::jsg