Skip to content
File

Blob: src/workerd/io/frankenvalue.c++

7.2 KB
1#include "frankenvalue.h"
2 
3#include <workerd/jsg/ser.h>
4 
5namespace workerd {
6 
7Frankenvalue Frankenvalue::cloneImpl() const {
8 Frankenvalue result;
9 
10 KJ_SWITCH_ONEOF(value) {
11 KJ_CASE_ONEOF(_, EmptyObject) {
12 result.value = EmptyObject();
13 }
14 KJ_CASE_ONEOF(json, Json) {
15 result.value = Json{kj::str(json.json)};
16 }
17 KJ_CASE_ONEOF(v8Serialized, V8Serialized) {
18 result.value = V8Serialized{kj::heapArray(v8Serialized.data.asPtr())};
19 }
20 }
21 
22 if (!properties.empty()) {
23 result.properties.reserve(properties.size());
24 
25 for (auto& property: properties) {
26 KJ_ASSERT(property.value.capTable.empty());
27 result.properties.add(Property{
28 .name = kj::str(property.name),
29 .value = property.value.cloneImpl(),
30 .capTableOffset = property.capTableOffset,
31 .capTableSize = property.capTableSize,
32 });
33 }
34 }
35 
36 return result;
37}
38 
39Frankenvalue Frankenvalue::clone() {
40 auto result = cloneImpl();
41 
42 if (!capTable.empty()) {
43 result.capTable.reserve(capTable.size());
44 for (auto& entry: capTable) {
45 result.capTable.add(entry->clone());
46 }
47 }
48 
49 return result;
50}
51 
52Frankenvalue Frankenvalue::threadSafeClone() const {
53 auto result = cloneImpl();
54 
55 if (!capTable.empty()) {
56 result.capTable.reserve(capTable.size());
57 for (auto& entry: capTable) {
58 result.capTable.add(entry->threadSafeClone());
59 }
60 }
61 
62 return result;
63}
64 
65kj::Own<Frankenvalue::CapTableEntry> Frankenvalue::CapTableEntry::threadSafeClone() const {
66 KJ_FAIL_REQUIRE("a capability in the Frankenvalue does not implement threadSafeClone()");
67}
68 
69void Frankenvalue::toCapnp(rpc::Frankenvalue::Builder builder) {
70 toCapnpImpl(builder, capTable.size());
71}
72 
73void Frankenvalue::toCapnpImpl(rpc::Frankenvalue::Builder builder, uint capTableSize) {
74 KJ_SWITCH_ONEOF(value) {
75 KJ_CASE_ONEOF(_, EmptyObject) {
76 builder.setEmptyObject();
77 }
78 KJ_CASE_ONEOF(json, Json) {
79 builder.setJson(json.json);
80 }
81 KJ_CASE_ONEOF(v8Serialized, V8Serialized) {
82 builder.setV8Serialized(v8Serialized.data);
83 }
84 }
85 
86 if (properties.empty()) {
87 builder.setCapTableSize(capTableSize);
88 } else {
89 uint capTablePos = properties[0].capTableOffset;
90 builder.setCapTableSize(capTablePos);
91 
92 auto listBuilder = builder.initProperties(properties.size());
93 
94 for (auto i: kj::indices(properties)) {
95 KJ_ASSERT(properties[i].capTableOffset == capTablePos);
96 capTablePos += properties[i].capTableSize;
97 auto elemBuilder = listBuilder[i];
98 elemBuilder.setName(properties[i].name);
99 properties[i].value.toCapnpImpl(elemBuilder, properties[i].capTableSize);
100 }
101 KJ_ASSERT(capTablePos == capTableSize);
102 }
103}
104 
105Frankenvalue Frankenvalue::fromCapnp(
106 rpc::Frankenvalue::Reader reader, kj::Vector<kj::Own<CapTableEntry>> capTable) {
107 Frankenvalue result;
108 
109 uint capCount = 0;
110 result.fromCapnpImpl(reader, capCount);
111 
112 KJ_REQUIRE(capTable.size() == capCount);
113 result.capTable = kj::mv(capTable);
114 
115 return result;
116}
117 
118void Frankenvalue::fromCapnpImpl(rpc::Frankenvalue::Reader reader, uint& capCount) {
119 switch (reader.which()) {
120 case rpc::Frankenvalue::EMPTY_OBJECT:
121 this->value = EmptyObject();
122 break;
123 case rpc::Frankenvalue::JSON:
124 this->value = Json{kj::str(reader.getJson())};
125 break;
126 case rpc::Frankenvalue::V8_SERIALIZED:
127 this->value = V8Serialized{kj::heapArray(reader.getV8Serialized())};
128 break;
129 }
130 
131 capCount += reader.getCapTableSize();
132 
133 auto properties = reader.getProperties();
134 if (properties.size() > 0) {
135 this->properties.reserve(properties.size());
136 
137 for (auto property: properties) {
138 Property result{
139 .name = kj::str(property.getName()),
140 .capTableOffset = capCount,
141 };
142 result.value.fromCapnpImpl(property, capCount);
143 result.capTableSize = capCount - result.capTableOffset;
144 this->properties.add(kj::mv(result));
145 }
146 }
147}
148 
149jsg::JsValue Frankenvalue::toJs(jsg::Lock& js) {
150 return toJsImpl(js, capTable);
151}
152 
153jsg::JsValue Frankenvalue::toJsImpl(jsg::Lock& js, kj::ArrayPtr<kj::Own<CapTableEntry>> capTable) {
154 return js.withinHandleScope([&]() -> jsg::JsValue {
155 jsg::JsValue result = [&]() -> jsg::JsValue {
156 KJ_SWITCH_ONEOF(value) {
157 KJ_CASE_ONEOF(_, EmptyObject) {
158 return js.obj();
159 }
160 KJ_CASE_ONEOF(json, Json) {
161 // TODO(cleanup): Make jsg::Lock::parseJson() not return a persistent handle.
162 return jsg::JsValue(jsg::check(v8::JSON::Parse(js.v8Context(), js.str(json.json))));
163 }
164 KJ_CASE_ONEOF(v8Serialized, V8Serialized) {
165 CapTableReader capTableReader(
166 properties.empty() ? capTable : capTable.slice(0, properties[0].capTableOffset));
167 
168 jsg::Deserializer deser(js, v8Serialized.data, kj::none, kj::none,
169 jsg::Deserializer::Options{
170 .externalHandler = capTableReader,
171 });
172 return deser.readValue(js);
173 }
174 }
175 KJ_UNREACHABLE;
176 }();
177 
178 if (!properties.empty()) {
179 jsg::JsObject obj = KJ_REQUIRE_NONNULL(
180 result.tryCast<jsg::JsObject>(), "non-object Frankenvalue can't have properties");
181 
182 for (auto& property: properties) {
183 obj.set(js, property.name,
184 property.value.toJsImpl(js,
185 capTable.slice(
186 property.capTableOffset, property.capTableOffset + property.capTableSize)));
187 }
188 }
189 
190 return result;
191 });
192}
193 
194void Frankenvalue::populateJsObject(jsg::Lock& js, jsg::JsObject target) {
195 if (!empty()) {
196 js.withinHandleScope([&]() {
197 auto sourceObj = KJ_REQUIRE_NONNULL(toJs(js).tryCast<jsg::JsObject>(),
198 "Frankenvalue must be an object for populateJsObject()");
199 auto props = sourceObj.getPropertyNames(js, jsg::KeyCollectionFilter::OWN_ONLY,
200 jsg::PropertyFilter::ONLY_ENUMERABLE, jsg::IndexFilter::INCLUDE_INDICES);
201 auto propCount = props.size();
202 for (auto i: kj::zeroTo(propCount)) {
203 auto prop = props.get(js, i);
204 target.set(js, prop, sourceObj.get(js, prop));
205 }
206 });
207 }
208}
209 
210Frankenvalue Frankenvalue::fromJs(jsg::Lock& js, jsg::JsValue value) {
211 Frankenvalue result;
212 
213 js.withinHandleScope([&]() {
214 CapTableBuilder capTableBuilder(result);
215 jsg::Serializer ser(js,
216 {
217 .treatClassInstancesAsPlainObjects = false,
218 .externalHandler = capTableBuilder,
219 });
220 ser.write(js, value);
221 result.value = V8Serialized{ser.release().data};
222 });
223 
224 return result;
225}
226 
227Frankenvalue Frankenvalue::fromJson(kj::String json) {
228 Frankenvalue result;
229 result.value = Json{kj::mv(json)};
230 return result;
231}
232 
233void Frankenvalue::setProperty(kj::String name, Frankenvalue value) {
234 // We need to merge the value's cap table into ours.
235 uint capTableOffset = capTable.size();
236 uint capTableSize = value.capTable.size();
237 
238 capTable.reserve(capTable.size() + value.capTable.size());
239 for (auto& cap: value.capTable) {
240 capTable.add(kj::mv(cap));
241 }
242 
243 // Drop the value's capTable. Overwrite it rather than use clear() so that the backing buffer
244 // is actually freed.
245 value.capTable = {};
246 
247 properties.add(Property{
248 .name = kj::mv(name),
249 .value = kj::mv(value),
250 .capTableOffset = capTableOffset,
251 .capTableSize = capTableSize,
252 });
253}
254 
255} // namespace workerd