Skip to content
File

Blob: src/workerd/io/frankenvalue.h

cpp172 lines
1// Copyright (c) 2024 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 
7#include <workerd/io/frankenvalue.capnp.h>
8#include <workerd/jsg/jsg.h>
9 
10namespace workerd {
11 
12// C++ class mirroring `Frankenvalue` as defined in `frankenvalue.capnp`.
13//
14// Represents a JavaScript value that has been stitched together from multiple sources outside of
15// a JavaScript evaluation context. The Frankevalue can be evaluated down to a JS value as soon
16// as it has a JS execution environment in which to be evaluated.
17//
18// This is used in particular to represent `ctx.props`.
19class Frankenvalue {
20 public:
21 Frankenvalue(): value(EmptyObject()) {}
22 
23 bool empty() const {
24 return value.is<EmptyObject>() && properties.empty();
25 }
26 
27 Frankenvalue clone();
28 
29 // This method only works if the `CapTableEntry`s in this `Frankenvalue` all implement
30 // `threadSafeClone()`.
31 Frankenvalue threadSafeClone() const;
32 
33 class CapTableEntry;
34 
35 // Convert to/from capnp format.
36 //
37 // The CapTable, if any, is expected to be handled separately, as different use cases call for
38 // very different handling of the cap table.
39 void toCapnp(rpc::Frankenvalue::Builder builder);
40 static Frankenvalue fromCapnp(
41 rpc::Frankenvalue::Reader reader, kj::Vector<kj::Own<CapTableEntry>> capTable = {});
42 
43 // Convert to/from JavaScript values. Note that round trips here don't produce the exact same
44 // Frankenvalue representation: toJs() puts all the contents together into a single value, and
45 // fromJs() always returns a Frankenvalue containing a single V8-serialized value.
46 jsg::JsValue toJs(jsg::Lock& js);
47 static Frankenvalue fromJs(jsg::Lock& js, jsg::JsValue value);
48 
49 // Like toJs() but add the properties to an existing object. Throws if the `Frankenvalue` does
50 // not represent an object. This is used to populate `env` in particular.
51 void populateJsObject(jsg::Lock& js, jsg::JsObject target);
52 
53 // Construct a Frankenvalue from JSON.
54 //
55 // (It's not possible to convert a Frankenvalue back to JSON, except by evaluating it in JS and
56 // then JSON-stringifying from there.)
57 static Frankenvalue fromJson(kj::String json);
58 
59 // Add a property to the value, represented as another Frankenvalue. This is how you "stitch
60 // together" values!
61 //
62 // This is called `set` because the new property will override any existing property with the
63 // same name, but note that this strictly appends content. The replacement happens only when the
64 // Frankenvalue is finally converted to JS.
65 void setProperty(kj::String name, Frankenvalue value);
66 
67 // ---------------------------------------------------------------------------
68 // Capability handling
69 //
70 // A Frankenvalue can contain capabilities (typically ServiceStubs). When serializing from
71 // JavaScript, these will be encoded as integer indexes into a separate table -- the CapTable.
72 
73 // The Frankenvalue itself doesn't know how these "capabilities" are implemneted, so leaves this
74 // up to a higher layer. It simply maintains a table of `CapTableEntry` objects. `CapTableEntry`
75 // serves as a generic base class for multiple representations which serializers and
76 // deserializers for specific types will need to support through downcasting.
77 //
78 // In particular:
79 // - Typically, the type is `IoChannelFactory::SubrequestChannel`.
80 // - When a Frankenvalue is being used to initialize the `env` of a dynamically-loaded isolate,
81 // each CapTableEntry may simply contain an I/O channel number.
82 // - In some environments, a CapTableEntry might be some sort of description of how to load a
83 // Worker that implements the capability.
84 class CapTableEntry {
85 public:
86 // Clone the entry, used when `Frankenvalue::clone()` is called. Many implementations may
87 // implement this using addRef().
88 virtual kj::Own<CapTableEntry> clone() = 0;
89 
90 // Like `clone()` but works on const values. Used only when `Frankenvalue::threadSafeClone()`
91 // is called. The default implementation throws an exception.
92 virtual kj::Own<CapTableEntry> threadSafeClone() const;
93 };
94 
95 kj::ArrayPtr<kj::Own<CapTableEntry>> getCapTable() {
96 return capTable;
97 }
98 
99 // Rewrite all the caps in the table by calling the `rewrite()` callback on each one.
100 template <typename Func>
101 void rewriteCaps(Func&& rewrite) {
102 for (auto& slot: capTable) {
103 slot = rewrite(kj::mv(slot));
104 }
105 }
106 
107 // When deserializing a JS value, the jsg::Deserializer's ExternalHandler will have this type.
108 class CapTableReader final: public jsg::Deserializer::ExternalHandler {
109 public:
110 kj::Maybe<CapTableEntry&> get(uint index) {
111 if (index < table.size()) {
112 return *table[index];
113 } else {
114 return kj::none;
115 }
116 }
117 
118 private:
119 kj::ArrayPtr<kj::Own<CapTableEntry>> table;
120 CapTableReader(kj::ArrayPtr<kj::Own<CapTableEntry>> table): table(table) {}
121 friend class Frankenvalue;
122 };
123 
124 // When serializing a JS value, the jsg::Serializer's ExternalHandler will have this type.
125 class CapTableBuilder final: public jsg::Serializer::ExternalHandler {
126 public:
127 uint add(kj::Own<CapTableEntry> entry) {
128 uint result = target.capTable.size();
129 target.capTable.add(kj::mv(entry));
130 return result;
131 }
132 
133 private:
134 Frankenvalue& target;
135 CapTableBuilder(Frankenvalue& target): target(target) {}
136 friend class Frankenvalue;
137 };
138 
139 private:
140 struct EmptyObject {};
141 struct Json {
142 kj::String json;
143 };
144 struct V8Serialized {
145 kj::Array<byte> data;
146 };
147 kj::OneOf<EmptyObject, Json, V8Serialized> value;
148 
149 struct Property;
150 kj::Vector<Property> properties;
151 
152 kj::Vector<kj::Own<CapTableEntry>> capTable;
153 
154 Frankenvalue cloneImpl() const;
155 void fromCapnpImpl(rpc::Frankenvalue::Reader reader, uint& capTablePos);
156 void toCapnpImpl(rpc::Frankenvalue::Builder builder, uint capTableSize);
157 jsg::JsValue toJsImpl(jsg::Lock& js, kj::ArrayPtr<kj::Own<CapTableEntry>> capTable);
158};
159 
160// Can't be defined inline since `Frankenvalue` is still incomplete there.
161struct Frankenvalue::Property {
162 kj::String name;
163 Frankenvalue value;
164 
165 // `value.capTable` is always empty. Instead, these two values specify the slice of the parent's
166 // capTable which this Frankenvalue refers into.
167 uint capTableOffset = 0;
168 uint capTableSize = 0;
169};
170 
171} // namespace workerd