// 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 #pragma once // Runtime type system for jsg. // Produces capnp description (rtti.capnp) of jsg structs, resources and c++ types of their // members. // Can be used to generate typescript type, dynamically invoke methods, fuzz, check backward // compatibility etc. #include #include #include #include namespace workerd::jsg::rtti { namespace impl { // Struct for partial specialization. template struct BuildRtti; } // namespace impl // User's entry point into rtti. // Builder owns capnp builder for all the objects it returns, so usual capnp builder // rules apply. // The rtti describes object structure and their types. // All structure references in rtti are stored by name. The builder maintains a symbol table // which can be used to resolve them. It is guaranteed that the table is full enough to // interpret all types passed through a given builder. template class Builder { public: const MetaConfiguration config; Builder(const MetaConfiguration& config): config(config) {} template Type::Reader type() { auto type = builder.initRoot(); impl::BuildRtti::build(type, *this); return type; } template Structure::Reader structure() { auto name = jsg::fullyQualifiedTypeName(typeid(T)); KJ_IF_SOME(builder, symbols.find(name)) { return builder->template getRoot(); } auto& builder = symbols.insert(kj::str(name), kj::heap()).value; auto structure = builder->template initRoot(); impl::BuildRtti::build(structure, *this); return structure; } kj::Maybe structure(kj::StringPtr name) { // lookup structure in the symbol table return symbols.find(name).map([](kj::Own& builder) { return builder->template getRoot(); }); } private: capnp::MallocMessageBuilder builder; kj::HashMap> symbols; }; namespace impl { // Implementation tools template struct FunctionTraits; template struct FunctionTraits { using ReturnType = R; using ArgsTuple = std::tuple; }; template struct FunctionTraits { using ReturnType = R; using ArgsTuple = std::tuple; }; template struct FunctionTraits { using ReturnType = R; using ArgsTuple = std::tuple; }; template struct FunctionTraits> : public FunctionTraits {}; template struct FunctionTraits { using ReturnType = R; using ArgsTuple = std::tuple; }; template struct TupleRttiBuilder { static inline void build(capnp::List::Builder builder, Builder& rtti) { build(std::make_integer_sequence>{}, builder, rtti); } private: template static inline void build(std::integer_sequence seq, capnp::List::Builder builder, Builder& rtti) { ((buildIndex(builder, rtti)), ...); } template static inline void buildIndex(capnp::List::Builder builder, Builder& rtti) { BuildRtti>::build(builder[I], rtti); } }; // Primitives template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { builder.setVoidt(); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { builder.setBoolt(); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { builder.setBoolt(); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { builder.setUnknown(); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { builder.setUnknown(); } }; template struct BuildRtti { // This isn't really unknown but we currently do not expose these types at all, so // this is OK for now. static void build(Type::Builder builder, Builder& rtti) { builder.setUnknown(); } }; template struct BuildRtti { // This isn't really unknown but we currently do not expose these types at all, so // this is OK for now. static void build(Type::Builder builder, Builder& rtti) { builder.setUnknown(); } }; template struct BuildRtti { // This isn't really unknown but we currently do not expose these types at all, so // this is OK for now. static void build(Type::Builder builder, Builder& rtti) { builder.setUnknown(); } }; template struct BuildRtti { // This isn't really unknown but we currently do not expose these types at all, so // this is OK for now. static void build(Type::Builder builder, Builder& rtti) { builder.setUnknown(); } }; // Numbers #define DECLARE_NUMBER_TYPE(T) \ template \ struct BuildRtti { \ static void build(Type::Builder builder, Builder& rtti) { \ builder.initNumber().setName(#T); \ } \ }; #define FOR_EACH_NUMBER_TYPE(F) \ F(char) \ F(signed char) \ F(unsigned char) \ F(short) \ F(unsigned short) \ F(int) \ F(unsigned int) \ F(long) \ F(unsigned long) \ F(long long) \ F(unsigned long long) \ F(double) \ F(jsg::JsNumber) \ F(jsg::JsInt32) \ F(jsg::JsUint32) \ F(jsg::JsBigInt) FOR_EACH_NUMBER_TYPE(DECLARE_NUMBER_TYPE) #undef FOR_EACH_NUMBER_TYPE #undef DECLARE_NUMBER_TYPE // Strings #define DECLARE_STRING_TYPE(T) \ template \ struct BuildRtti { \ static void build(Type::Builder builder, Builder& rtti) { \ builder.initString().setName(#T); \ } \ }; #define FOR_EACH_STRING_TYPE(F) \ F(kj::String) \ F(kj::StringPtr) \ F(v8::String) \ F(USVString) \ F(DOMString) \ F(jsg::JsString) FOR_EACH_STRING_TYPE(DECLARE_STRING_TYPE) #undef FOR_EACH_STRING_TYPE #undef DECLARE_STRING_TYPE // Object Types template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { builder.setObject(); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { builder.setObject(); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { builder.setObject(); } }; // References template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; // Maybe Types #define DECLARE_MAYBE_TYPE(T) \ template \ struct BuildRtti> { \ static void build(Type::Builder builder, Builder& rtti) { \ auto maybe = builder.initMaybe(); \ BuildRtti::build(maybe.initValue(), rtti); \ maybe.setName(#T); \ } \ }; #define FOR_EACH_MAYBE_TYPE(F) \ F(kj::Maybe) \ F(jsg::Optional) \ F(jsg::LenientOptional) FOR_EACH_MAYBE_TYPE(DECLARE_MAYBE_TYPE) #undef FOR_EACH_MAYBE_TYPE #undef DECLARE_MAYBE_TYPE // Array Types #define DECLARE_ARRAY_TYPE(T) \ template \ struct BuildRtti> { \ static void build(Type::Builder builder, Builder& rtti) { \ auto array = builder.initArray(); \ BuildRtti::build(array.initElement(), rtti); \ array.setName(#T); \ } \ }; #define FOR_EACH_ARRAY_TYPE(F) \ F(kj::Array) \ F(kj::ArrayPtr) \ F(kj::HashSet) \ F(jsg::Sequence) \ F(jsg::AsyncGenerator) \ F(jsg::AsyncGeneratorIgnoringStrings) template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { auto array = builder.initArray(); BuildRtti::build(array.initElement(), rtti); array.setName("jsg::JsArray"); } }; FOR_EACH_ARRAY_TYPE(DECLARE_ARRAY_TYPE) #undef FOR_EACH_ARRAY_TYPE #undef DECLARE_ARRAY_TYPE // Misc Generic Types template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { auto dict = builder.initDict(); BuildRtti::build(dict.initKey(), rtti); BuildRtti::build(dict.initValue(), rtti); } }; template struct BuildRtti> { using Seq = std::index_sequence_for; using Tuple = std::tuple; template static inline void buildVariant( capnp::List::Builder builder, Builder& rtti) { BuildRtti>::build(builder[I], rtti); } template static inline void buildVariants(std::integer_sequence seq, capnp::List::Builder builder, Builder& rtti) { ((buildVariant(builder, rtti)), ...); } static void build(Type::Builder builder, Builder& rtti) { auto variants = builder.initOneOf().initVariants(Seq::size()); buildVariants(Seq{}, variants, rtti); } }; // Promises template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder.initPromise().initValue(), rtti); } }; template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder.initPromise().initValue(), rtti); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { builder.initPromise().initValue().setUnknown(); } }; // Builtins #define DECLARE_BUILTIN_TYPE(T, V) \ template \ struct BuildRtti { \ static void build(Type::Builder builder, Builder& rtti) { \ builder.initBuiltin().setType(V); \ } \ }; #define FOR_EACH_BUILTIN_TYPE(F, ...) \ F(jsg::JsUint8Array, BuiltinType::Type::V8_UINT8_ARRAY) \ F(jsg::JsArrayBuffer, BuiltinType::Type::V8_ARRAY_BUFFER) \ F(jsg::JsArrayBufferView, BuiltinType::Type::V8_ARRAY_BUFFER_VIEW) \ F(jsg::JsBufferSource, BuiltinType::Type::JSG_BUFFER_SOURCE) \ F(jsg::BufferSource, BuiltinType::Type::JSG_BUFFER_SOURCE) \ F(kj::Date, BuiltinType::Type::KJ_DATE) \ F(v8::ArrayBufferView, BuiltinType::Type::V8_ARRAY_BUFFER_VIEW) \ F(v8::ArrayBuffer, BuiltinType::Type::V8_ARRAY_BUFFER) \ F(v8::Function, BuiltinType::Type::V8_FUNCTION) \ F(v8::Uint8Array, BuiltinType::Type::V8_UINT8_ARRAY) \ F(jsg::JsDate, BuiltinType::Type::KJ_DATE) FOR_EACH_BUILTIN_TYPE(DECLARE_BUILTIN_TYPE) #undef FOR_EACH_BUILTIN_TYPE #undef DECLARE_BUILTIN_TYPE // Jsg implementation types #define DECLARE_JSG_IMPL_TYPE(T, V) \ template \ struct BuildRtti { \ static void build(Type::Builder builder, Builder& rtti) { \ builder.initJsgImpl().setType(V); \ } \ }; #define FOR_EACH_JSG_IMPL_TYPE(F, ...) \ F(jsg::Lock, JsgImplType::Type::JSG_LOCK) \ F(jsg::Name, JsgImplType::Type::JSG_NAME) \ F(jsg::SelfRef, JsgImplType::Type::JSG_SELF_REF) \ F(jsg::Unimplemented, JsgImplType::Type::JSG_UNIMPLEMENTED) \ F(v8::Isolate*, JsgImplType::Type::V8_ISOLATE) \ F(v8::FunctionCallbackInfo, JsgImplType::Type::V8_FUNCTION_CALLBACK_INFO) \ F(v8::PropertyCallbackInfo, JsgImplType::Type::V8_PROPERTY_CALLBACK_INFO) FOR_EACH_JSG_IMPL_TYPE(DECLARE_JSG_IMPL_TYPE) #undef FOR_EACH_JSG_IMPL_TYPE #undef DECLARE_JSG_IMPL_TYPE template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { // TODO(someday): Create a representation of Arguments that actually encodes the type T. builder.initJsgImpl().setType(JsgImplType::Type::JSG_VARARGS); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { builder.initJsgImpl().setType(JsgImplType::Type::CONFIGURATION); } }; template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { builder.initJsgImpl().setType(JsgImplType::Type::JSG_TYPE_HANDLER); } }; // Functions template struct BuildRtti> { static void build(Type::Builder builder, Builder& rtti) { auto fn = builder.initFunction(); using Traits = FunctionTraits; BuildRtti::build(fn.initReturnType(), rtti); using Args = Traits::ArgsTuple; TupleRttiBuilder::build(fn.initArgs(std::tuple_size_v), rtti); } }; // C++ modifiers template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; template struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { BuildRtti::build(builder, rtti); } }; // Structs // count all members in the structure struct MemberCounter { template inline void registerWildcardProperty() { /* not a member */ } template inline void registerMethod() { ++members; } template inline void registerCallable() { /* not a member */ } template inline void registerInherit() { /* inherit is not a member */ } template inline void registerInheritIntrinsic(v8::Intrinsic intrinsic) { /* inherit is not a member */ } template inline void registerIterable() { /* not a member */ } template inline void registerAsyncIterable() { /* not a member */ } template inline void registerDispose() { /* not a member */ } template inline void registerAsyncDispose() { /* not a member */ } template inline void registerNestedType() { ++members; } template inline void registerStructProperty(const char* name) { ++members; } template inline void registerReadonlyPrototypeProperty() { ++members; } template inline void registerPrototypeProperty() { ++members; } template inline void registerReadonlyInstanceProperty() { ++members; } template inline void registerReadonlyInstanceProperty(kj::StringPtr, T value) { ++members; } template inline void registerInstanceProperty() { ++members; } template inline void registerLazyInstanceProperty() { ++members; } template inline void registerInspectProperty() { /* not included */ } template inline void registerStaticConstant(T value) { ++members; } template inline void registerStaticProperty() { ++members; } template inline void registerStaticMethod() { ++members; } inline void registerTypeScriptRoot() { /* not a member */ } template inline void registerTypeScriptOverride() { /* not a member */ } template inline void registerTypeScriptDefine() { /* not a member */ } inline void registerJsBundle(Bundle::Reader bundle) { modules += bundle.getModules().size(); } size_t members = 0; size_t modules = 0; }; template struct MembersBuilder { Structure::Builder structure; capnp::List::Builder members; capnp::List::Builder modules; Builder& rtti; uint memberIndex = 0; uint moduleIndex = 0; MembersBuilder(Structure::Builder structure, capnp::List::Builder members, capnp::List::Builder modules, Builder& rtti) : structure(structure), members(members), modules(modules), rtti(rtti) {} template inline void registerInherit() { BuildRtti::build(structure.initExtends(), rtti); } template inline void registerInheritIntrinsic(v8::Intrinsic intrinsic) { structure.initExtends().initIntrinsic().setName(name); } template inline void registerNestedType() { auto nested = members[memberIndex++].initNested(); nested.setName(name); BuildRtti::build(nested.initStructure(), rtti); } template inline void registerInstanceProperty() { auto prop = members[memberIndex++].initProperty(); prop.setName(name); prop.setGetterFastApiCompatible(isFastApiCompatible); prop.setSetterFastApiCompatible(isFastApiCompatible); using GetterTraits = FunctionTraits; BuildRtti::build(prop.initType(), rtti); } template inline void registerReadonlyInstanceProperty() { auto prop = members[memberIndex++].initProperty(); prop.setName(name); prop.setReadonly(true); prop.setGetterFastApiCompatible(isFastApiCompatible); using GetterTraits = FunctionTraits; BuildRtti::build(prop.initType(), rtti); } template inline void registerReadonlyInstanceProperty(kj::StringPtr name, T value) { auto prop = members[memberIndex++].initProperty(); prop.setName(name); prop.setReadonly(true); BuildRtti::build(prop.initType(), rtti); } template inline void registerLazyInstanceProperty() { auto prop = members[memberIndex++].initProperty(); prop.setName(name); prop.setReadonly(readOnly); prop.setLazy(true); using GetterTraits = FunctionTraits; BuildRtti::build(prop.initType(), rtti); } template inline void registerPrototypeProperty() { auto prop = members[memberIndex++].initProperty(); prop.setName(name); prop.setPrototype(true); prop.setGetterFastApiCompatible(isFastApiCompatible); prop.setSetterFastApiCompatible(isFastApiCompatible); using GetterTraits = FunctionTraits; BuildRtti::build(prop.initType(), rtti); } template inline void registerReadonlyPrototypeProperty() { auto prop = members[memberIndex++].initProperty(); prop.setName(name); prop.setPrototype(true); prop.setReadonly(true); using GetterTraits = FunctionTraits; BuildRtti::build(prop.initType(), rtti); } template inline void registerInspectProperty() {} template inline void registerStaticConstant(T value) { auto constant = members[memberIndex++].initConstant(); constant.setName(name); constant.setValue(value); // BuildRtti::build(constant.initType()); } template inline void registerStaticProperty() { auto prop = members[memberIndex++].initProperty(); prop.setName(name); prop.setReadonly(true); prop.setGetterFastApiCompatible(isFastApiCompatible); using GetterTraits = FunctionTraits; BuildRtti::build(prop.initType(), rtti); } template void registerStructProperty(const char* name) { auto prop = members[memberIndex++].initProperty(); prop.setName(name); BuildRtti::build(prop.initType(), rtti); } template inline void registerMethod() { auto method = members[memberIndex++].initMethod(); method.setName(name); method.setFastApiCompatible(isFastApiCompatible); using Traits = FunctionTraits; BuildRtti::build(method.initReturnType(), rtti); using Args = Traits::ArgsTuple; TupleRttiBuilder::build(method.initArgs(std::tuple_size_v), rtti); } template inline void registerCallable() { auto func = structure.initCallable(); using Traits = FunctionTraits; BuildRtti::build(func.initReturnType(), rtti); using Args = Traits::ArgsTuple; TupleRttiBuilder::build(func.initArgs(std::tuple_size_v), rtti); } template inline void registerStaticMethod() { auto method = members[memberIndex++].initMethod(); method.setName(name); method.setStatic(true); method.setFastApiCompatible(isFastApiCompatible); using Traits = FunctionTraits; BuildRtti::build(method.initReturnType(), rtti); using Args = Traits::ArgsTuple; TupleRttiBuilder::build(method.initArgs(std::tuple_size_v), rtti); } template inline void registerIterable() { structure.setIterable(true); auto method = structure.initIterator(); method.setName(name); using Traits = FunctionTraits; BuildRtti::build(method.initReturnType(), rtti); using Args = Traits::ArgsTuple; TupleRttiBuilder::build(method.initArgs(std::tuple_size_v), rtti); } template inline void registerAsyncIterable() { structure.setAsyncIterable(true); auto method = structure.initAsyncIterator(); method.setName(name); using Traits = FunctionTraits; BuildRtti::build(method.initReturnType(), rtti); using Args = Traits::ArgsTuple; TupleRttiBuilder::build(method.initArgs(std::tuple_size_v), rtti); } template inline void registerDispose() { structure.setDisposable(true); auto method = structure.initDispose(); method.setName(name); using Traits = FunctionTraits; BuildRtti::build(method.initReturnType(), rtti); using Args = Traits::ArgsTuple; TupleRttiBuilder::build(method.initArgs(std::tuple_size_v), rtti); } template inline void registerAsyncDispose() { structure.setAsyncDisposable(true); auto method = structure.initAsyncDispose(); method.setName(name); using Traits = FunctionTraits; BuildRtti::build(method.initReturnType(), rtti); using Args = Traits::ArgsTuple; TupleRttiBuilder::build(method.initArgs(std::tuple_size_v), rtti); } inline void registerTypeScriptRoot() { structure.setTsRoot(true); } template inline void registerTypeScriptOverride() { structure.setTsOverride(tsOverride); } template inline void registerTypeScriptDefine() { structure.setTsDefine(tsDefine); } inline void registerJsBundle(Bundle::Reader bundle) { for (auto module: bundle.getModules()) { auto m = modules[moduleIndex++]; m.setSpecifier(module.getName()); m.setTsDeclarations(module.getTsDeclaration()); } } template inline void registerWildcardProperty() { // Nothing to do in this case. } }; // Concept: true when T has registerMembers() function generated by JSG_RESOURCE/JSG_STRUCT template concept HasRegisterMembers = requires { T::template registerMembers; }; // Concept: true when T has constructor() function template concept HasConstructor = requires { T::constructor; }; template requires HasRegisterMembers struct BuildRtti { static void build(Type::Builder builder, Builder& rtti) { auto structure = builder.initStructure(); structure.setName(jsg::typeName(typeid(T))); structure.setFullyQualifiedName(jsg::fullyQualifiedTypeName(typeid(T))); rtti.template structure(); } static void build(Structure::Builder builder, Builder& rtti) { builder.setName(jsg::typeName(typeid(T))); builder.setFullyQualifiedName(jsg::fullyQualifiedTypeName(typeid(T))); MemberCounter counter; if constexpr (isDetected()) { T::template registerMembers(counter, rtti.config); } else { T::template registerMembers(counter); } auto membersCount = counter.members; if constexpr (HasConstructor) { membersCount++; } auto members = builder.initMembers(membersCount); auto modules = counter.modules > 0 ? builder.initBuiltinModules(counter.modules) : capnp::List::Builder(); MembersBuilder membersBuilder(builder, members, modules, rtti); if constexpr (isDetected()) { T::template registerMembers(membersBuilder, rtti.config); } else { T::template registerMembers(membersBuilder); } if constexpr (HasConstructor) { auto constructor = members[membersBuilder.memberIndex++].initConstructor(); using Traits = FunctionTraits; using Args = Traits::ArgsTuple; TupleRttiBuilder::build( constructor.initArgs(std::tuple_size_v), rtti); } } }; } // namespace impl } // namespace workerd::jsg::rtti