Skip to content
File

Blob: src/workerd/jsg/util.h

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