Skip to content
File

Blob: src/workerd/jsg/function.h

cpp526 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// Handles wrapping a C++ function so that it can be called from JavaScript, and vice versa.
9 
10#include "jsg.h"
11#include "wrappable.h"
12 
13#include <workerd/jsg/meta.h>
14 
15#include <v8-context.h>
16#include <v8-function.h>
17 
18#include <kj/function.h>
19 
20namespace workerd::jsg {
21 
22template <typename Signature>
23class WrappableFunction;
24 
25class WrappableFunctionBase: public Wrappable {
26 public:
27 kj::StringPtr jsgGetMemoryName() const override {
28 return "WrappableFunction"_kjc;
29 }
30 void jsgGetMemoryInfo(MemoryTracker& tracker) const override {
31 Wrappable::jsgGetMemoryInfo(tracker);
32 visitForMemoryInfo(tracker);
33 }
34 virtual void visitForMemoryInfo(MemoryTracker& tracker) const {
35 // TODO(soon): Implement tracking for WrappableFunction.
36 }
37};
38 
39template <typename Ret, typename... Args>
40class WrappableFunction<Ret(Args...)>: public WrappableFunctionBase {
41 public:
42 WrappableFunction(bool needsGcTracing): needsGcTracing(needsGcTracing) {}
43 virtual Ret operator()(Lock& js, Args&&... args) = 0;
44 
45 const bool needsGcTracing;
46 
47 size_t jsgGetMemorySelfSize() const override {
48 return sizeof(WrappableFunction<Ret(Args...)>);
49 }
50};
51 
52template <typename Signature, typename Impl>
53class WrappableFunctionImpl;
54 
55template <typename Ret, typename... Args, typename Impl>
56class WrappableFunctionImpl<Ret(Args...), Impl>: public WrappableFunction<Ret(Args...)> {
57 public:
58 WrappableFunctionImpl(Impl&& func)
59 : WrappableFunction<Ret(Args...)>(hasPublicVisitForGc<Impl>()),
60 func(kj::fwd<Impl>(func)) {}
61 
62 Ret operator()(Lock& js, Args&&... args) override {
63 return func(js, kj::fwd<Args>(args)...);
64 }
65 
66 void jsgVisitForGc(GcVisitor& visitor) override {
67 if constexpr (hasPublicVisitForGc<Impl>()) {
68 visitor.visit(func);
69 }
70 }
71 
72 private:
73 Impl func;
74};
75 
76template <typename TypeWrapper, typename Signature, typename = ArgumentIndexes<Signature>>
77struct FunctorCallback;
78 
79template <typename TypeWrapper, typename Ret, typename... Args, size_t... indexes>
80struct FunctorCallback<TypeWrapper, Ret(Args...), kj::_::Indexes<indexes...>> {
81 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
82 liftKj(args, [&]() {
83 auto isolate = args.GetIsolate();
84 auto context = isolate->GetCurrentContext();
85 auto& js = Lock::from(isolate);
86 auto& wrapper = TypeWrapper::from(isolate);
87 auto& func = extractInternalPointer<WrappableFunction<Ret(Args...)>, false>(
88 context, args.Data().As<v8::Object>());
89 
90 if constexpr (isVoid<Ret>()) {
91 func(Lock::from(isolate),
92 wrapper.template unwrap<Args>(
93 js, context, args, indexes, TypeErrorContext::callbackArgument(indexes))...);
94 } else {
95 return wrapper.wrap(js, context, args.This(),
96 func(Lock::from(isolate),
97 wrapper.template unwrap<Args>(
98 js, context, args, indexes, TypeErrorContext::callbackArgument(indexes))...));
99 }
100 });
101 }
102};
103 
104// Specialization for functions that take `const v8::FunctionCallbackInfo<v8::Value>&` as their
105// second parameter (after Lock&).
106template <typename TypeWrapper, typename Ret, typename... Args, size_t... indexes>
107struct FunctorCallback<TypeWrapper,
108 Ret(const v8::FunctionCallbackInfo<v8::Value>&, Args...),
109 kj::_::Indexes<indexes...>> {
110 static void callback(const v8::FunctionCallbackInfo<v8::Value>& args) {
111 liftKj(args, [&]() {
112 auto isolate = args.GetIsolate();
113 auto context = isolate->GetCurrentContext();
114 auto& wrapper = TypeWrapper::from(isolate);
115 auto& js = Lock::from(isolate);
116 auto& func = extractInternalPointer<
117 WrappableFunction<Ret(const v8::FunctionCallbackInfo<v8::Value>&, Args...)>, false>(
118 context, args.Data().As<v8::Object>());
119 
120 if constexpr (isVoid<Ret>()) {
121 func(js, args,
122 wrapper.template unwrap<Args>(
123 js, context, args, indexes, TypeErrorContext::callbackArgument(indexes))...);
124 } else {
125 return wrapper.wrap(js, context, args.This(),
126 func(js, args,
127 wrapper.template unwrap<Args>(
128 js, context, args, indexes, TypeErrorContext::callbackArgument(indexes))...));
129 }
130 });
131 }
132};
133 
134template <typename Ret, typename... Args>
135class Function<Ret(Args...)> {
136 // (Docs are in jsg.h, the public header.)
137 
138 public:
139 using NativeFunction = jsg::WrappableFunction<Ret(Args...)>;
140 
141 // When holding a JavaScript function, `Wrapper` is a C++ function that will handle converting
142 // C++ arguments into JavaScript values and then call the JS function.
143 using Wrapper = Ret(jsg::Lock& js,
144 v8::Local<v8::Value> receiver, // the `this` value in the function
145 v8::Local<v8::Function> fn,
146 Args...);
147 
148 Function(Wrapper* wrapper, V8Ref<v8::Object> receiver, V8Ref<v8::Function> function)
149 : Function(wrapper, receiver.cast<v8::Value>(jsg::Lock::current()), kj::mv(function)) {}
150 
151 // Construct jsg::Function wrapping a JavaScript function.
152 Function(Wrapper* wrapper, Value receiver, V8Ref<v8::Function> function)
153 : impl(JsImpl{
154 .wrapper = kj::mv(wrapper), .receiver = kj::mv(receiver), .handle = kj::mv(function)}) {}
155 
156 // Construct jsg::Function wrapping a C++ function. The parameter can be a lambda or anything
157 // else with operator() with a compatible signature. If the parameter has a visitForGc(GcVisitor&)
158 // method, then GC visitation will be arranged.
159 template <typename Func,
160 typename = decltype(kj::instance<Func>()(kj::instance<Lock&>(), kj::instance<Args>()...))>
161 Function(Func&& func)
162 : impl(Ref<NativeFunction>(
163 alloc<WrappableFunctionImpl<Ret(Args...), Func>>(kj::fwd<Func>(func)))) {}
164 
165 Function(Function&&) = default;
166 Function& operator=(Function&&) = default;
167 KJ_DISALLOW_COPY(Function);
168 
169 Ret operator()(jsg::Lock& jsl, Args... args) {
170 KJ_SWITCH_ONEOF(impl) {
171 KJ_CASE_ONEOF(native, Ref<NativeFunction>) {
172 return (*native)(jsl, kj::fwd<Args>(args)...);
173 }
174 KJ_CASE_ONEOF(js, JsImpl) {
175 return (*js.wrapper)(
176 jsl, js.receiver.getHandle(jsl), js.handle.getHandle(jsl), kj::fwd<Args>(args)...);
177 }
178 }
179 __builtin_unreachable();
180 }
181 
182 // Get a handle to the underlying function. If this is a native function,
183 // `makeNativeWrapper(Ref<Func>&)` is called to create the wrapper.
184 //
185 // Only the `FunctionWrapper` TypeWrapper mixin should call this. Anyone else needs to call
186 // `tryGetHandle()`.
187 template <typename MakeNativeWrapperFunc>
188 v8::Local<v8::Function> getOrCreateHandle(
189 v8::Isolate* isolate, MakeNativeWrapperFunc&& makeNativeWrapper) {
190 KJ_SWITCH_ONEOF(impl) {
191 KJ_CASE_ONEOF(native, Ref<NativeFunction>) {
192 return makeNativeWrapper(native);
193 }
194 KJ_CASE_ONEOF(js, JsImpl) {
195 return js.handle.getHandle(isolate);
196 }
197 }
198 __builtin_unreachable();
199 }
200 
201 // Like getHandle() but if there's no wrapper yet, returns null.
202 kj::Maybe<v8::Local<v8::Function>> tryGetHandle(v8::Isolate* isolate) {
203 KJ_SWITCH_ONEOF(impl) {
204 KJ_CASE_ONEOF(native, Ref<NativeFunction>) {
205 return kj::none;
206 }
207 KJ_CASE_ONEOF(js, JsImpl) {
208 return js.handle.getHandle(isolate);
209 }
210 }
211 __builtin_unreachable();
212 }
213 
214 inline void visitForGc(GcVisitor& visitor) {
215 KJ_SWITCH_ONEOF(impl) {
216 KJ_CASE_ONEOF(native, Ref<NativeFunction>) {
217 visitor.visit(native);
218 }
219 KJ_CASE_ONEOF(js, JsImpl) {
220 visitor.visit(js.receiver, js.handle);
221 }
222 }
223 }
224 
225 inline Function<Ret(Args...)> addRef(v8::Isolate* isolate) {
226 KJ_SWITCH_ONEOF(impl) {
227 KJ_CASE_ONEOF(native, Ref<NativeFunction>) {
228 return Function<Ret(Args...)>(native.addRef());
229 }
230 KJ_CASE_ONEOF(js, JsImpl) {
231 return Function<Ret(Args...)>(
232 js.wrapper, js.receiver.addRef(isolate), js.handle.addRef(isolate));
233 }
234 }
235 __builtin_unreachable();
236 }
237 
238 inline Function<Ret(Args...)> addRef(Lock& js) {
239 KJ_SWITCH_ONEOF(impl) {
240 KJ_CASE_ONEOF(native, Ref<NativeFunction>) {
241 return Function<Ret(Args...)>(native.addRef());
242 }
243 KJ_CASE_ONEOF(jsi, JsImpl) {
244 return Function<Ret(Args...)>(jsi.wrapper, jsi.receiver.addRef(js), jsi.handle.addRef(js));
245 }
246 }
247 __builtin_unreachable();
248 }
249 
250 inline void setReceiver(Value receiver) {
251 KJ_IF_SOME(i, impl.template tryGet<JsImpl>()) {
252 i.receiver = kj::mv(receiver);
253 }
254 }
255 
256 JSG_MEMORY_INFO(Function) {
257 KJ_SWITCH_ONEOF(impl) {
258 KJ_CASE_ONEOF(ref, Ref<NativeFunction>) {
259 tracker.trackField("native", ref);
260 }
261 KJ_CASE_ONEOF(impl, JsImpl) {
262 tracker.trackField("impl", impl);
263 }
264 }
265 }
266 
267 private:
268 Function(Ref<NativeFunction>&& func): impl(kj::mv(func)) {}
269 
270 struct JsImpl {
271 Wrapper* wrapper;
272 Value receiver;
273 V8Ref<v8::Function> handle;
274 
275 JSG_MEMORY_INFO(JsImpl) {
276 tracker.trackField("receiver", receiver);
277 tracker.trackField("handle", handle);
278 }
279 };
280 
281 kj::OneOf<Ref<NativeFunction>, JsImpl> impl;
282 friend class MemoryTracker;
283};
284 
285template <typename T>
286class Constructor: public jsg::Function<T> {
287 public:
288 using jsg::Function<T>::Function;
289};
290 
291template <typename Method>
292struct MethodSignature_;
293template <typename T, typename Ret, typename... Args>
294struct MethodSignature_<Ret (T::*)(Lock&, Args...)> {
295 using Type = Ret(Args...);
296};
297template <typename T, typename Ret, typename... Args>
298struct MethodSignature_<Ret (T::*)(Lock&, Args...) const> {
299 using Type = Ret(Args...);
300};
301 
302// Extracts a function signature from a method type.
303template <typename Method>
304using MethodSignature = MethodSignature_<Method>::Type;
305 
306// TypeWrapper mixin for functions / lambdas.
307template <typename TypeWrapper>
308class FunctionWrapper {
309 
310 public:
311 template <typename Func, typename = decltype(&kj::Decay<Func>::operator())>
312 static constexpr const char* getName(Func*) {
313 return "function";
314 }
315 
316 template <typename Func,
317 typename Signature = MethodSignature<decltype(&kj::Decay<Func>::operator())>>
318 v8::Local<v8::Function> wrap(Lock& js,
319 v8::Local<v8::Context> context,
320 kj::Maybe<v8::Local<v8::Object>> creator,
321 Func&& func) {
322 return wrap(js, context, creator, jsg::Function<Signature>(kj::mv(func)));
323 }
324 
325 template <typename Signature>
326 v8::Local<v8::Function> wrap(Lock& js,
327 v8::Local<v8::Context> context,
328 kj::Maybe<v8::Local<v8::Object>> creator,
329 Function<Signature>&& func) {
330 v8::Isolate* isolate = js.v8Isolate;
331 return func.getOrCreateHandle(isolate, [&](Ref<WrappableFunction<Signature>>& ref) {
332 v8::Local<v8::Object> data;
333 KJ_IF_SOME(h, ref->tryGetHandle(isolate)) {
334 // Apparently, this function has been wrapped before and already has an opaque handle.
335 // That's interesting. However, unfortunately, we don't have a handle to the v8::Function
336 // that was created last time, so we can't return the same function instance. This is
337 // arguably incorrect; what if the application added properties to it or something?
338 //
339 // Unfortunately, it is exceedingly difficult for us to store the function handle for
340 // reuse without introducing performance problems.
341 // - Ideally, we'd use the v8::Function itself as the object's wrapper, rather than an
342 // "opaque" wrapper. However, this doesn't work, because we can't attach internal fields
343 // to it. v8::Function::New() does not let us specify an internal field count. We can
344 // specify internal fields if we create a FunctionTemplate and then create the Function
345 // from that, but a FunctionTemplate only instantiates one Function (per Context). We
346 // need a separate Function instance for object we want to wrap. So... this doesn't work.
347 // (Note that V8's heap tracing API deeply depends on wrapper objects having two internal
348 // fields, so using other schemes like v8::External doesn't help either.)
349 // - Another approach might be to store the v8::Function on the WrappableFunction, once
350 // it's created. This is a cyclic reference, but we could rely on GC visitation to
351 // collect it. The problem is, cyclic references can only be collected by tracing, not
352 // by scavenging. Tracing runs much less often than scavenging. So we'd be forcing every
353 // function object to live on the heap longer than otherwise necessary.
354 //
355 // In practice, it probably never matters that returning the same jsg::Function twice
356 // produces exactly the same JavaScript handle. So... screw it.
357 data = h;
358 } else {
359 data = ref->attachOpaqueWrapper(context, ref->needsGcTracing);
360 }
361 
362 // TODO(conform): Correctly set `length` on all functions. Probably doesn't need a compat flag
363 // but I'd like to do it as a separate commit which can be reverted. We also currently fail
364 // to set this on constructors and methods (see resource.h). Remember not to count
365 // injected parameters!
366 return check(
367 v8::Function::New(context, &FunctorCallback<TypeWrapper, Signature>::callback, data));
368 });
369 }
370 
371 template <typename Ret, typename... Args>
372 kj::Maybe<Constructor<Ret(Args...)>> tryUnwrap(Lock& js,
373 v8::Local<v8::Context> context,
374 v8::Local<v8::Value> handle,
375 Constructor<Ret(Args...)>*,
376 kj::Maybe<v8::Local<v8::Object>> parentObject) {
377 if (!handle->IsFunction()) {
378 return kj::none;
379 }
380 
381 auto isolate = js.v8Isolate;
382 
383 auto wrapperFn = [](Lock& js, v8::Local<v8::Value> receiver, v8::Local<v8::Function> func,
384 Args... args) -> Ret {
385 auto isolate = js.v8Isolate;
386 auto& typeWrapper = TypeWrapper::from(isolate);
387 
388 return js.withinHandleScope([&] {
389 auto context = js.v8Context();
390 v8::Local<v8::Value> argv[sizeof...(Args)]{
391 typeWrapper.wrap(js, context, kj::none, kj::fwd<Args>(args))...};
392 
393 v8::Local<v8::Object> result = check(func->NewInstance(context, sizeof...(Args), argv));
394 return typeWrapper.template unwrap<Ret>(
395 js, context, result, TypeErrorContext::callbackReturn());
396 });
397 };
398 
399 return Constructor<Ret(Args...)>(wrapperFn,
400 V8Ref(isolate, parentObject.orDefault(context->Global())),
401 V8Ref(isolate, handle.As<v8::Function>()));
402 }
403 
404 template <typename Ret, typename... Args>
405 kj::Maybe<Function<Ret(Args...)>> tryUnwrap(Lock& js,
406 v8::Local<v8::Context> context,
407 v8::Local<v8::Value> handle,
408 Function<Ret(Args...)>*,
409 kj::Maybe<v8::Local<v8::Object>> parentObject) {
410 if (!handle->IsFunction()) {
411 return kj::none;
412 }
413 
414 auto isolate = js.v8Isolate;
415 
416 auto wrapperFn = [](Lock& js, v8::Local<v8::Value> receiver, v8::Local<v8::Function> func,
417 Args... args) -> Ret {
418 auto isolate = js.v8Isolate;
419 auto& typeWrapper = TypeWrapper::from(isolate);
420 
421 return js.withinHandleScope([&] {
422 auto context = js.v8Context();
423 v8::LocalVector<v8::Value> argv(js.v8Isolate,
424 std::initializer_list<v8::Local<v8::Value>>{
425 typeWrapper.wrap(js, context, kj::none, kj::fwd<Args>(args))
426 .template As<v8::Value>()...});
427 
428 auto result = check(func->Call(context, receiver, argv.size(), argv.data()));
429 if constexpr (!isVoid<Ret>()) {
430 return typeWrapper.template unwrap<Ret>(
431 js, context, result, TypeErrorContext::callbackReturn());
432 } else {
433 return;
434 }
435 });
436 };
437 
438 return Function<Ret(Args...)>(wrapperFn,
439 V8Ref(isolate, parentObject.orDefault(context->Global())),
440 V8Ref(isolate, handle.As<v8::Function>()));
441 }
442 
443 template <typename Ret>
444 kj::Maybe<Function<Ret(Arguments<Value>)>> tryUnwrap(Lock& js,
445 v8::Local<v8::Context> context,
446 v8::Local<v8::Value> handle,
447 Function<Ret(Arguments<Value>)>*,
448 kj::Maybe<v8::Local<v8::Object>> parentObject) {
449 if (!handle->IsFunction()) {
450 return kj::none;
451 }
452 
453 auto isolate = js.v8Isolate;
454 
455 auto wrapperFn = [](Lock& js, v8::Local<v8::Value> receiver, v8::Local<v8::Function> func,
456 Arguments<Value> args) -> Ret {
457 auto isolate = js.v8Isolate;
458 auto& typeWrapper = TypeWrapper::from(isolate);
459 
460 return js.withinHandleScope([&] {
461 auto context = js.v8Context();
462 
463 v8::Local<v8::Value> result;
464 if (args.size() > 0) {
465 v8::LocalVector<v8::Value> argv(js.v8Isolate, args.size());
466 for (size_t n = 0; n < args.size(); n++) {
467 argv[n] = args[n].getHandle(js);
468 }
469 result = check(func->Call(context, receiver, argv.size(), argv.data()));
470 } else {
471 result = check(func->Call(context, receiver, 0, nullptr));
472 }
473 
474 if constexpr (!isVoid<Ret>()) {
475 return typeWrapper.template unwrap<Ret>(
476 js, context, result, TypeErrorContext::callbackReturn());
477 } else {
478 return;
479 }
480 });
481 };
482 
483 return Function<Ret(Arguments<Value>)>(wrapperFn,
484 V8Ref(isolate, parentObject.orDefault(context->Global())),
485 V8Ref(isolate, handle.As<v8::Function>()));
486 }
487};
488 
489template <typename Func>
490class VisitableLambda {
491 public:
492 VisitableLambda(Func&& func): func(kj::fwd<Func>(func)) {}
493 
494 template <typename... Params>
495 auto operator()(Params&&... params) {
496 return func(kj::fwd<Params>(params)...);
497 }
498 
499 void visitForGc(GcVisitor& visitor) {
500 func(visitor);
501 }
502 
503 private:
504 Func func;
505};
506 
507template <typename... Params>
508constexpr bool isGcVisitor() {
509 return false;
510}
511template <>
512constexpr bool isGcVisitor<GcVisitor&>() {
513 return true;
514}
515 
516#define JSG_VISITABLE_LAMBDA(CAPTURES, VISITS, ...) \
517 ::workerd::jsg::VisitableLambda([JSG_EXPAND CAPTURES](auto&&... params) mutable { \
518 if constexpr (::workerd::jsg::isGcVisitor<decltype(params)...>()) { \
519 (params.visit VISITS, ...); \
520 } else { \
521 return ([&] __VA_ARGS__)(kj::fwd<decltype(params)>(params)...); \
522 } \
523 })
524 
525} // namespace workerd::jsg