// Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 #include "rtti.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define EW_TYPE_GROUP_FOR_EACH(F) \ F("dom-exception", jsg::DOMException) \ F("global-scope", EW_GLOBAL_SCOPE_ISOLATE_TYPES) \ F("durable-objects", EW_ACTOR_ISOLATE_TYPES) \ F("durable-objects-state", EW_ACTOR_STATE_ISOLATE_TYPES) \ F("analytics-engine", EW_ANALYTICS_ENGINE_ISOLATE_TYPES) \ F("basics", EW_BASICS_ISOLATE_TYPES) \ F("blob", EW_BLOB_ISOLATE_TYPES) \ F("cache", EW_CACHE_ISOLATE_TYPES) \ F("crypto", EW_CRYPTO_ISOLATE_TYPES) \ F("encoding", EW_ENCODING_ISOLATE_TYPES) \ F("events", EW_EVENTS_ISOLATE_TYPES) \ F("form-data", EW_FORMDATA_ISOLATE_TYPES) \ F("html-rewriter", EW_HTML_REWRITER_ISOLATE_TYPES) \ F("http", EW_HTTP_ISOLATE_TYPES) \ F("hyperdrive", EW_HYPERDRIVE_ISOLATE_TYPES) \ F("unsafe", EW_UNSAFE_ISOLATE_TYPES) \ F("memory-cache", EW_MEMORY_CACHE_ISOLATE_TYPES) \ F("pyodide", EW_PYODIDE_ISOLATE_TYPES) \ F("kv", EW_KV_ISOLATE_TYPES) \ F("queue", EW_QUEUE_ISOLATE_TYPES) \ F("r2", EW_R2_PUBLIC_BETA_ISOLATE_TYPES) \ F("worker-rpc", EW_WORKER_RPC_ISOLATE_TYPES) \ F("scheduled", EW_SCHEDULED_ISOLATE_TYPES) \ F("streams", EW_STREAMS_ISOLATE_TYPES) \ F("trace", EW_TRACE_ISOLATE_TYPES) \ F("url", EW_URL_ISOLATE_TYPES) \ F("url-standard", EW_URL_STANDARD_ISOLATE_TYPES) \ F("url-pattern", EW_URLPATTERN_ISOLATE_TYPES) \ F("url-pattern-standard", EW_URLPATTERN_STANDARD_ISOLATE_TYPES) \ F("websocket", EW_WEBSOCKET_ISOLATE_TYPES) \ F("sql", EW_SQL_ISOLATE_TYPES) \ F("sockets", EW_SOCKETS_ISOLATE_TYPES) \ F("base64", EW_BASE64_ISOLATE_TYPES) \ F("node", EW_NODE_ISOLATE_TYPES) \ F("rtti", EW_RTTI_ISOLATE_TYPES) \ F("eventsource", EW_EVENTSOURCE_ISOLATE_TYPES) \ F("container", EW_CONTAINER_ISOLATE_TYPES) \ F("webfs", EW_WEB_FILESYSTEM_ISOLATE_TYPE) \ F("messagechannel", EW_MESSAGECHANNEL_ISOLATE_TYPES) \ F("workers-module", EW_WORKERS_MODULE_ISOLATE_TYPES) \ F("export-loopback", EW_EXPORT_LOOPBACK_ISOLATE_TYPES) \ F("sync-kv", EW_SYNC_KV_ISOLATE_TYPES) \ F("worker-loader", EW_WORKER_LOADER_ISOLATE_TYPES) \ F("performance", EW_PERFORMANCE_ISOLATE_TYPES) \ F("tracing", EW_TRACING_ISOLATE_TYPES) namespace workerd::api { namespace { struct EncoderModuleRegistryImpl { struct CppModuleContents { CppModuleContents(kj::String structureName): structureName(kj::mv(structureName)) {} kj::String structureName; }; struct TypeScriptModuleContents { TypeScriptModuleContents(kj::StringPtr tsDeclarations): tsDeclarations(tsDeclarations) {} kj::StringPtr tsDeclarations; }; struct ModuleInfo { ModuleInfo(kj::StringPtr specifier, jsg::ModuleType type, kj::OneOf contents) : specifier(specifier), type(type), contents(kj::mv(contents)) {} kj::StringPtr specifier; jsg::ModuleType type; kj::OneOf contents; }; void addBuiltinBundle( jsg::Bundle::Reader bundle, kj::Maybe maybeFilter = kj::none) { for (auto module: bundle.getModules()) { if (module.getType() == maybeFilter.orDefault(module.getType())) addBuiltinModule(module); } } template void addBuiltinBundleFiltered(jsg::Bundle::Reader bundle, Func filter) { for (auto module: bundle.getModules()) { if (filter(module)) { addBuiltinModule(module); } } } void addBuiltinModule(jsg::Module::Reader module) { TypeScriptModuleContents contents(module.getTsDeclaration()); ModuleInfo info(module.getName(), module.getType(), kj::mv(contents)); modules.add(kj::mv(info)); } void addBuiltinModule(kj::StringPtr specifier, jsg::ModuleRegistry::ModuleCallback callback, jsg::ModuleRegistry::Type type = jsg::ModuleRegistry::Type::BUILTIN) { // TODO(soon): Implement this function } template void addBuiltinModule(kj::StringPtr specifier, jsg::ModuleRegistry::Type type = jsg::ModuleRegistry::Type::BUILTIN) { auto structureName = jsg::fullyQualifiedTypeName(typeid(T)); CppModuleContents contents(kj::mv(structureName)); ModuleInfo info(specifier, type, kj::mv(contents)); modules.add(kj::mv(info)); } kj::Vector modules; }; CompatibilityFlags::Reader compileFlags(capnp::MessageBuilder &message, kj::StringPtr compatDate, bool experimental, kj::ArrayPtr compatFlags) { // Based on src/workerd/io/compatibility-date-test.c++ auto orphanage = message.getOrphanage(); auto flagListOrphan = orphanage.newOrphan>(compatFlags.size()); auto flagList = flagListOrphan.get(); for (auto i: kj::indices(compatFlags)) { flagList.set(i, compatFlags.begin()[i]); } auto output = message.initRoot(); SimpleWorkerErrorReporter errorReporter; compileCompatibilityFlags(compatDate, flagList.asReader(), output, errorReporter, experimental, CompatibilityDateValidation::FUTURE_FOR_TEST); if (!errorReporter.errors.empty()) { // TODO(someday): throw an `AggregateError` containing all errors JSG_FAIL_REQUIRE(Error, errorReporter.errors[0]); } auto reader = output.asReader(); return kj::mv(reader); } CompatibilityFlags::Reader compileAllFlags(capnp::MessageBuilder &message) { auto output = message.initRoot(); auto schema = capnp::Schema::from(); auto dynamicOutput = capnp::toDynamic(output); for (auto field: schema.getFields()) { bool isNode = false; kj::StringPtr enableFlagName; for (auto annotation: field.getProto().getAnnotations()) { if (annotation.getId() == COMPAT_ENABLE_FLAG_ANNOTATION_ID) { enableFlagName = annotation.getValue().getText(); // Exclude nodejs_compat, since the type generation scripts don't support node:* imports // TODO: Figure out typing for node compat isNode = enableFlagName == "nodejs_compat" || enableFlagName == "nodejs_compat_v2"; } } dynamicOutput.set(field, !isNode); } auto reader = output.asReader(); return kj::mv(reader); } struct TypesEncoder { public: TypesEncoder(): compatFlags(kj::heapArray(0)) {} TypesEncoder(kj::String compatDate, kj::Array compatFlags) : compatDate(kj::mv(compatDate)), compatFlags(kj::mv(compatFlags)) {} kj::Array encode() { capnp::MallocMessageBuilder flagsMessage; CompatibilityFlags::Reader flags; KJ_IF_SOME(date, compatDate) { flags = compileFlags(flagsMessage, date, true, compatFlags); } else { flags = compileAllFlags(flagsMessage); } capnp::MallocMessageBuilder message; auto root = message.initRoot(); // Encode RTTI structures auto builder = jsg::rtti::Builder(flags); #define EW_TYPE_GROUP_COUNT(Name, Types) groupsSize++; #define EW_TYPE_GROUP_WRITE(Name, Types) writeGroup(groups, builder, Name); unsigned int groupsSize = 0; EW_TYPE_GROUP_FOR_EACH(EW_TYPE_GROUP_COUNT) auto groups = root.initGroups(groupsSize); groupsIndex = 0; EW_TYPE_GROUP_FOR_EACH(EW_TYPE_GROUP_WRITE) KJ_ASSERT(groupsIndex == groupsSize); #undef EW_TYPE_GROUP_COUNT #undef EW_TYPE_GROUP_WRITE // Encode modules EncoderModuleRegistryImpl registry; registerModules(registry, flags); unsigned int i = 0; auto modulesBuilder = root.initModules(registry.modules.size()); for (auto moduleBuilder: modulesBuilder) { auto &module = registry.modules[i++]; moduleBuilder.setSpecifier(module.specifier); KJ_SWITCH_ONEOF(module.contents) { KJ_CASE_ONEOF(contents, EncoderModuleRegistryImpl::CppModuleContents) { moduleBuilder.setStructureName(contents.structureName); } KJ_CASE_ONEOF(contents, EncoderModuleRegistryImpl::TypeScriptModuleContents) { moduleBuilder.setTsDeclarations(contents.tsDeclarations); } } } auto words = capnp::messageToFlatArray(message); auto bytes = words.asBytes(); return kj::heapArray(bytes); } private: template void writeStructure(jsg::rtti::Builder &builder, capnp::List::Builder structures) { auto reader = builder.structure(); structures.setWithCaveats(structureIndex++, reader); } template void writeGroup(capnp::List::Builder &groups, jsg::rtti::Builder &builder, kj::StringPtr name) { auto group = groups[groupsIndex++]; group.setName(name); unsigned int structuresSize = sizeof...(Types); auto structures = group.initStructures(structuresSize); structureIndex = 0; (writeStructure(builder, structures), ...); KJ_ASSERT(structureIndex == structuresSize); } kj::Maybe compatDate; kj::Array compatFlags; unsigned int groupsIndex = 0; unsigned int structureIndex = 0; }; } // namespace kj::Array RTTIModule::exportTypes(kj::String compatDate, kj::Array compatFlags) { TypesEncoder encoder(kj::mv(compatDate), kj::mv(compatFlags)); return encoder.encode(); } kj::Array RTTIModule::exportExperimentalTypes() { TypesEncoder encoder; return encoder.encode(); } } // namespace workerd::api