File
Blob: src/workerd/io/frankenvalue.c++
| 1 | #include "frankenvalue.h" |
| 2 | |
| 3 | #include <workerd/jsg/ser.h> |
| 4 | |
| 5 | namespace workerd { |
| 6 | |
| 7 | Frankenvalue 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 | |
| 39 | Frankenvalue 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 | |
| 52 | Frankenvalue 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 | |
| 65 | kj::Own<Frankenvalue::CapTableEntry> Frankenvalue::CapTableEntry::threadSafeClone() const { |
| 66 | KJ_FAIL_REQUIRE("a capability in the Frankenvalue does not implement threadSafeClone()"); |
| 67 | } |
| 68 | |
| 69 | void Frankenvalue::toCapnp(rpc::Frankenvalue::Builder builder) { |
| 70 | toCapnpImpl(builder, capTable.size()); |
| 71 | } |
| 72 | |
| 73 | void 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 | |
| 105 | Frankenvalue 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 | |
| 118 | void 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 | |
| 149 | jsg::JsValue Frankenvalue::toJs(jsg::Lock& js) { |
| 150 | return toJsImpl(js, capTable); |
| 151 | } |
| 152 | |
| 153 | jsg::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 | |
| 194 | void 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 | |
| 210 | Frankenvalue 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 | |
| 227 | Frankenvalue Frankenvalue::fromJson(kj::String json) { |
| 228 | Frankenvalue result; |
| 229 | result.value = Json{kj::mv(json)}; |
| 230 | return result; |
| 231 | } |
| 232 | |
| 233 | void 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 |