diff --git a/clang/include/clang/APINotes/APINotesYAMLCompiler.h b/clang/include/clang/APINotes/APINotesYAMLCompiler.h index 6098d0ee36fc4..9c24ed85b6a12 100644 --- a/clang/include/clang/APINotes/APINotesYAMLCompiler.h +++ b/clang/include/clang/APINotes/APINotesYAMLCompiler.h @@ -10,14 +10,25 @@ #define LLVM_CLANG_APINOTES_APINOTESYAMLCOMPILER_H #include "llvm/ADT/StringRef.h" +#include "llvm/Support/SourceMgr.h" #include "llvm/Support/raw_ostream.h" +namespace clang { +class FileEntry; +} // namespace clang + namespace clang { namespace api_notes { /// Parses the APINotes YAML content and writes the representation back to the /// specified stream. This provides a means of testing the YAML processing of /// the APINotes format. bool parseAndDumpAPINotes(llvm::StringRef YI, llvm::raw_ostream &OS); + +/// Converts API notes from YAML format to binary format. +bool compileAPINotes(llvm::StringRef YAMLInput, const FileEntry *SourceFile, + llvm::raw_ostream &OS, + llvm::SourceMgr::DiagHandlerTy DiagHandler = nullptr, + void *DiagHandlerCtxt = nullptr); } // namespace api_notes } // namespace clang diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index b74244bc8f1cb..79595abcf7d02 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -766,6 +766,7 @@ struct Context { /// data they contain; it is up to the user to ensure that the data /// referenced by the identifier list persists. struct ObjCSelectorRef { + unsigned NumArgs; llvm::ArrayRef Identifiers; }; } // namespace api_notes diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h index 5897b45d3796d..615314c46f09c 100644 --- a/clang/lib/APINotes/APINotesFormat.h +++ b/clang/lib/APINotes/APINotesFormat.h @@ -247,7 +247,7 @@ using EnumConstantDataLayout = /// A stored Objective-C selector. struct StoredObjCSelector { - unsigned NumPieces; + unsigned NumArgs; llvm::SmallVector Identifiers; }; @@ -302,7 +302,7 @@ template <> struct DenseMapInfo { static unsigned getHashValue(const clang::api_notes::StoredObjCSelector &Selector) { - auto hash = llvm::hash_value(Selector.NumPieces); + auto hash = llvm::hash_value(Selector.NumArgs); hash = hash_combine(hash, Selector.Identifiers.size()); for (auto piece : Selector.Identifiers) hash = hash_combine(hash, static_cast(piece)); @@ -313,7 +313,7 @@ template <> struct DenseMapInfo { static bool isEqual(const clang::api_notes::StoredObjCSelector &LHS, const clang::api_notes::StoredObjCSelector &RHS) { - return LHS.NumPieces == RHS.NumPieces && LHS.Identifiers == RHS.Identifiers; + return LHS.NumArgs == RHS.NumArgs && LHS.Identifiers == RHS.Identifiers; } }; diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp index 2cbf5fd3bf503..ff9b95d9bf75e 100644 --- a/clang/lib/APINotes/APINotesReader.cpp +++ b/clang/lib/APINotes/APINotesReader.cpp @@ -421,7 +421,7 @@ class ObjCSelectorTableInfo { static internal_key_type ReadKey(const uint8_t *Data, unsigned Length) { internal_key_type Key; - Key.NumPieces = + Key.NumArgs = endian::readNext(Data); unsigned NumIdents = (Length - sizeof(uint16_t)) / sizeof(uint32_t); for (unsigned i = 0; i != NumIdents; ++i) { @@ -741,6 +741,7 @@ APINotesReader::Implementation::getSelector(ObjCSelectorRef Selector) { // Translate the identifiers. StoredObjCSelector Key; + Key.NumArgs = Selector.NumArgs; for (auto Ident : Selector.Identifiers) { if (auto IdentID = getIdentifier(Ident)) { Key.Identifiers.push_back(*IdentID); diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp index 770d78e22050c..62a2ab1799913 100644 --- a/clang/lib/APINotes/APINotesWriter.cpp +++ b/clang/lib/APINotes/APINotesWriter.cpp @@ -823,7 +823,7 @@ class ObjCSelectorTableInfo { void EmitKey(raw_ostream &OS, key_type_ref Key, unsigned) { llvm::support::endian::Writer writer(OS, llvm::endianness::little); - writer.write(Key.NumPieces); + writer.write(Key.NumArgs); for (auto Identifier : Key.Identifiers) writer.write(Identifier); } diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp index 647455111214c..4dfd01dae05f2 100644 --- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -14,14 +14,17 @@ // #include "clang/APINotes/APINotesYAMLCompiler.h" +#include "clang/APINotes/APINotesWriter.h" #include "clang/APINotes/Types.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/Specifiers.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/SourceMgr.h" #include "llvm/Support/VersionTuple.h" -#include "llvm/Support/YAMLParser.h" #include "llvm/Support/YAMLTraits.h" #include #include + using namespace clang; using namespace api_notes; @@ -635,3 +638,476 @@ bool clang::api_notes::parseAndDumpAPINotes(StringRef YI, return false; } + +namespace { +using namespace api_notes; + +class YAMLConverter { + const Module &M; + APINotesWriter Writer; + llvm::raw_ostream &OS; + llvm::SourceMgr::DiagHandlerTy DiagHandler; + void *DiagHandlerCtxt; + bool ErrorOccured; + + /// Emit a diagnostic + bool emitError(llvm::Twine Message) { + DiagHandler( + llvm::SMDiagnostic("", llvm::SourceMgr::DK_Error, Message.str()), + DiagHandlerCtxt); + ErrorOccured = true; + return true; + } + +public: + YAMLConverter(const Module &TheModule, const FileEntry *SourceFile, + llvm::raw_ostream &OS, + llvm::SourceMgr::DiagHandlerTy DiagHandler, + void *DiagHandlerCtxt) + : M(TheModule), Writer(TheModule.Name, SourceFile), OS(OS), + DiagHandler(DiagHandler), DiagHandlerCtxt(DiagHandlerCtxt), + ErrorOccured(false) {} + + void convertAvailability(const AvailabilityItem &Availability, + CommonEntityInfo &CEI, llvm::StringRef APIName) { + // Populate the unavailability information. + CEI.Unavailable = (Availability.Mode == APIAvailability::None); + CEI.UnavailableInSwift = (Availability.Mode == APIAvailability::NonSwift); + if (CEI.Unavailable || CEI.UnavailableInSwift) { + CEI.UnavailableMsg = std::string(Availability.Msg); + } else { + if (!Availability.Msg.empty()) + emitError(llvm::Twine("availability message for available API '") + + APIName + "' will not be used"); + } + } + + void convertParams(const ParamsSeq &Params, FunctionInfo &OutInfo) { + for (const auto &P : Params) { + ParamInfo PI; + if (P.Nullability) + PI.setNullabilityAudited(*P.Nullability); + PI.setNoEscape(P.NoEscape); + PI.setType(std::string(P.Type)); + PI.setRetainCountConvention(P.RetainCountConvention); + if (OutInfo.Params.size() <= P.Position) + OutInfo.Params.resize(P.Position + 1); + OutInfo.Params[P.Position] |= PI; + } + } + + void convertNullability(const NullabilitySeq &Nullability, + std::optional ReturnNullability, + FunctionInfo &OutInfo, llvm::StringRef APIName) { + if (Nullability.size() > FunctionInfo::getMaxNullabilityIndex()) { + emitError(llvm::Twine("nullability info for '") + APIName + + "' does not fit"); + return; + } + + bool audited = false; + unsigned int idx = 1; + for (const auto &N : Nullability) + OutInfo.addTypeInfo(idx++, N); + audited = Nullability.size() > 0 || ReturnNullability; + if (audited) + OutInfo.addTypeInfo(0, ReturnNullability ? *ReturnNullability + : NullabilityKind::NonNull); + if (!audited) + return; + OutInfo.NullabilityAudited = audited; + OutInfo.NumAdjustedNullable = idx; + } + + /// Convert the common parts of an entity from YAML. + template + void convertCommonEntity(const T &Common, CommonEntityInfo &Info, + StringRef APIName) { + convertAvailability(Common.Availability, Info, APIName); + Info.setSwiftPrivate(Common.SwiftPrivate); + Info.SwiftName = std::string(Common.SwiftName); + } + + /// Convert the common parts of a type entity from YAML. + template + void convertCommonType(const T &Common, CommonTypeInfo &Info, + StringRef APIName) { + convertCommonEntity(Common, Info, APIName); + if (Common.SwiftBridge) + Info.setSwiftBridge(std::string(*Common.SwiftBridge)); + Info.setNSErrorDomain(Common.NSErrorDomain); + } + + // Translate from Method into ObjCMethodInfo and write it out. + void convertMethod(const Method &M, ContextID ClassID, StringRef ClassName, + VersionTuple SwiftVersion) { + ObjCMethodInfo MI; + convertCommonEntity(M, MI, M.Selector); + + // Check if the selector ends with ':' to determine if it takes arguments. + bool takesArguments = M.Selector.endswith(":"); + + // Split the selector into pieces. + llvm::SmallVector Args; + M.Selector.split(Args, ":", /*MaxSplit*/ -1, /*KeepEmpty*/ false); + if (!takesArguments && Args.size() > 1) { + emitError("selector '" + M.Selector + "' is missing a ':' at the end"); + return; + } + + // Construct ObjCSelectorRef. + api_notes::ObjCSelectorRef Selector; + Selector.NumArgs = !takesArguments ? 0 : Args.size(); + Selector.Identifiers = Args; + + // Translate the initializer info. + MI.DesignatedInit = M.DesignatedInit; + MI.RequiredInit = M.Required; + if (M.FactoryAsInit != FactoryAsInitKind::Infer) + emitError("'FactoryAsInit' is no longer valid; use 'SwiftName' instead"); + + MI.ResultType = std::string(M.ResultType); + + // Translate parameter information. + convertParams(M.Params, MI); + + // Translate nullability info. + convertNullability(M.Nullability, M.NullabilityOfRet, MI, M.Selector); + + MI.setRetainCountConvention(M.RetainCountConvention); + + // Write it. + Writer.addObjCMethod(ClassID, Selector, M.Kind == MethodKind::Instance, MI, + SwiftVersion); + } + + void convertContext(std::optional ParentContextID, const Class &C, + ContextKind Kind, VersionTuple SwiftVersion) { + // Write the class. + ObjCContextInfo CI; + convertCommonType(C, CI, C.Name); + + if (C.AuditedForNullability) + CI.setDefaultNullability(NullabilityKind::NonNull); + if (C.SwiftImportAsNonGeneric) + CI.setSwiftImportAsNonGeneric(*C.SwiftImportAsNonGeneric); + if (C.SwiftObjCMembers) + CI.setSwiftObjCMembers(*C.SwiftObjCMembers); + + ContextID CtxID = + Writer.addObjCContext(ParentContextID, C.Name, Kind, CI, SwiftVersion); + + // Write all methods. + llvm::StringMap> KnownMethods; + for (const auto &method : C.Methods) { + // Check for duplicate method definitions. + bool IsInstanceMethod = method.Kind == MethodKind::Instance; + bool &Known = IsInstanceMethod ? KnownMethods[method.Selector].first + : KnownMethods[method.Selector].second; + if (Known) { + emitError(llvm::Twine("duplicate definition of method '") + + (IsInstanceMethod ? "-" : "+") + "[" + C.Name + " " + + method.Selector + "]'"); + continue; + } + Known = true; + + convertMethod(method, CtxID, C.Name, SwiftVersion); + } + + // Write all properties. + llvm::StringSet<> KnownInstanceProperties; + llvm::StringSet<> KnownClassProperties; + for (const auto &Property : C.Properties) { + // Check for duplicate property definitions. + if ((!Property.Kind || *Property.Kind == MethodKind::Instance) && + !KnownInstanceProperties.insert(Property.Name).second) { + emitError(llvm::Twine("duplicate definition of instance property '") + + C.Name + "." + Property.Name + "'"); + continue; + } + + if ((!Property.Kind || *Property.Kind == MethodKind::Class) && + !KnownClassProperties.insert(Property.Name).second) { + emitError(llvm::Twine("duplicate definition of class property '") + + C.Name + "." + Property.Name + "'"); + continue; + } + + // Translate from Property into ObjCPropertyInfo. + ObjCPropertyInfo PI; + convertAvailability(Property.Availability, PI, Property.Name); + PI.setSwiftPrivate(Property.SwiftPrivate); + PI.SwiftName = std::string(Property.SwiftName); + if (Property.Nullability) + PI.setNullabilityAudited(*Property.Nullability); + if (Property.SwiftImportAsAccessors) + PI.setSwiftImportAsAccessors(*Property.SwiftImportAsAccessors); + PI.setType(std::string(Property.Type)); + + // Add both instance and class properties with this name. + if (Property.Kind) { + Writer.addObjCProperty(CtxID, Property.Name, + *Property.Kind == MethodKind::Instance, PI, + SwiftVersion); + } else { + Writer.addObjCProperty(CtxID, Property.Name, true, PI, SwiftVersion); + Writer.addObjCProperty(CtxID, Property.Name, false, PI, SwiftVersion); + } + } + } + + void convertNamespaceContext(std::optional ParentContextID, + const Namespace &TheNamespace, + VersionTuple SwiftVersion) { + // Write the namespace. + ObjCContextInfo CI; + convertCommonEntity(TheNamespace, CI, TheNamespace.Name); + + ContextID CtxID = + Writer.addObjCContext(ParentContextID, TheNamespace.Name, + ContextKind::Namespace, CI, SwiftVersion); + + convertTopLevelItems(Context(CtxID, ContextKind::Namespace), + TheNamespace.Items, SwiftVersion); + } + + void convertTopLevelItems(std::optional Ctx, + const TopLevelItems &TLItems, + VersionTuple SwiftVersion) { + std::optional CtxID = + Ctx ? std::optional(Ctx->id) : std::nullopt; + + // Write all classes. + llvm::StringSet<> KnownClasses; + for (const auto &Class : TLItems.Classes) { + // Check for duplicate class definitions. + if (!KnownClasses.insert(Class.Name).second) { + emitError(llvm::Twine("multiple definitions of class '") + Class.Name + + "'"); + continue; + } + + convertContext(CtxID, Class, ContextKind::ObjCClass, SwiftVersion); + } + + // Write all protocols. + llvm::StringSet<> KnownProtocols; + for (const auto &Protocol : TLItems.Protocols) { + // Check for duplicate protocol definitions. + if (!KnownProtocols.insert(Protocol.Name).second) { + emitError(llvm::Twine("multiple definitions of protocol '") + + Protocol.Name + "'"); + continue; + } + + convertContext(CtxID, Protocol, ContextKind::ObjCProtocol, SwiftVersion); + } + + // Write all namespaces. + llvm::StringSet<> KnownNamespaces; + for (const auto &Namespace : TLItems.Namespaces) { + // Check for duplicate namespace definitions. + if (!KnownNamespaces.insert(Namespace.Name).second) { + emitError(llvm::Twine("multiple definitions of namespace '") + + Namespace.Name + "'"); + continue; + } + + convertNamespaceContext(CtxID, Namespace, SwiftVersion); + } + + // Write all global variables. + llvm::StringSet<> KnownGlobals; + for (const auto &Global : TLItems.Globals) { + // Check for duplicate global variables. + if (!KnownGlobals.insert(Global.Name).second) { + emitError(llvm::Twine("multiple definitions of global variable '") + + Global.Name + "'"); + continue; + } + + GlobalVariableInfo GVI; + convertAvailability(Global.Availability, GVI, Global.Name); + GVI.setSwiftPrivate(Global.SwiftPrivate); + GVI.SwiftName = std::string(Global.SwiftName); + if (Global.Nullability) + GVI.setNullabilityAudited(*Global.Nullability); + GVI.setType(std::string(Global.Type)); + Writer.addGlobalVariable(Ctx, Global.Name, GVI, SwiftVersion); + } + + // Write all global functions. + llvm::StringSet<> KnownFunctions; + for (const auto &Function : TLItems.Functions) { + // Check for duplicate global functions. + if (!KnownFunctions.insert(Function.Name).second) { + emitError(llvm::Twine("multiple definitions of global function '") + + Function.Name + "'"); + continue; + } + + GlobalFunctionInfo GFI; + convertAvailability(Function.Availability, GFI, Function.Name); + GFI.setSwiftPrivate(Function.SwiftPrivate); + GFI.SwiftName = std::string(Function.SwiftName); + convertParams(Function.Params, GFI); + convertNullability(Function.Nullability, Function.NullabilityOfRet, GFI, + Function.Name); + GFI.ResultType = std::string(Function.ResultType); + GFI.setRetainCountConvention(Function.RetainCountConvention); + Writer.addGlobalFunction(Ctx, Function.Name, GFI, SwiftVersion); + } + + // Write all enumerators. + llvm::StringSet<> KnownEnumConstants; + for (const auto &EnumConstant : TLItems.EnumConstants) { + // Check for duplicate enumerators + if (!KnownEnumConstants.insert(EnumConstant.Name).second) { + emitError(llvm::Twine("multiple definitions of enumerator '") + + EnumConstant.Name + "'"); + continue; + } + + EnumConstantInfo ECI; + convertAvailability(EnumConstant.Availability, ECI, EnumConstant.Name); + ECI.setSwiftPrivate(EnumConstant.SwiftPrivate); + ECI.SwiftName = std::string(EnumConstant.SwiftName); + Writer.addEnumConstant(EnumConstant.Name, ECI, SwiftVersion); + } + + // Write all tags. + llvm::StringSet<> KnownTags; + for (const auto &Tag : TLItems.Tags) { + // Check for duplicate tag definitions. + if (!KnownTags.insert(Tag.Name).second) { + emitError(llvm::Twine("multiple definitions of tag '") + Tag.Name + + "'"); + continue; + } + + TagInfo TI; + convertCommonType(Tag, TI, Tag.Name); + + if ((Tag.SwiftRetainOp || Tag.SwiftReleaseOp) && !Tag.SwiftImportAs) { + emitError(llvm::Twine("should declare SwiftImportAs to use " + "SwiftRetainOp and SwiftReleaseOp (for ") + + Tag.Name + ")"); + continue; + } + if (Tag.SwiftReleaseOp.has_value() != Tag.SwiftRetainOp.has_value()) { + emitError(llvm::Twine("should declare both SwiftReleaseOp and " + "SwiftRetainOp (for ") + + Tag.Name + ")"); + continue; + } + + if (Tag.SwiftImportAs) + TI.SwiftImportAs = Tag.SwiftImportAs; + if (Tag.SwiftRetainOp) + TI.SwiftRetainOp = Tag.SwiftRetainOp; + if (Tag.SwiftReleaseOp) + TI.SwiftReleaseOp = Tag.SwiftReleaseOp; + + if (Tag.EnumConvenienceKind) { + if (Tag.EnumExtensibility) { + emitError( + llvm::Twine("cannot mix EnumKind and EnumExtensibility (for ") + + Tag.Name + ")"); + continue; + } + if (Tag.FlagEnum) { + emitError(llvm::Twine("cannot mix EnumKind and FlagEnum (for ") + + Tag.Name + ")"); + continue; + } + switch (*Tag.EnumConvenienceKind) { + case EnumConvenienceAliasKind::None: + TI.EnumExtensibility = EnumExtensibilityKind::None; + TI.setFlagEnum(false); + break; + case EnumConvenienceAliasKind::CFEnum: + TI.EnumExtensibility = EnumExtensibilityKind::Open; + TI.setFlagEnum(false); + break; + case EnumConvenienceAliasKind::CFOptions: + TI.EnumExtensibility = EnumExtensibilityKind::Open; + TI.setFlagEnum(true); + break; + case EnumConvenienceAliasKind::CFClosedEnum: + TI.EnumExtensibility = EnumExtensibilityKind::Closed; + TI.setFlagEnum(false); + break; + } + } else { + TI.EnumExtensibility = Tag.EnumExtensibility; + TI.setFlagEnum(Tag.FlagEnum); + } + + Writer.addTag(Ctx, Tag.Name, TI, SwiftVersion); + } + + // Write all typedefs. + llvm::StringSet<> KnownTypedefs; + for (const auto &Typedef : TLItems.Typedefs) { + // Check for duplicate typedef definitions. + if (!KnownTypedefs.insert(Typedef.Name).second) { + emitError(llvm::Twine("multiple definitions of typedef '") + + Typedef.Name + "'"); + continue; + } + + TypedefInfo TInfo; + convertCommonType(Typedef, TInfo, Typedef.Name); + TInfo.SwiftWrapper = Typedef.SwiftType; + + Writer.addTypedef(Ctx, Typedef.Name, TInfo, SwiftVersion); + } + } + + bool convertModule() { + // Write the top-level items. + convertTopLevelItems(/* context */ std::nullopt, M.TopLevel, + VersionTuple()); + + // Convert the versioned information. + for (const auto &Versioned : M.SwiftVersions) + convertTopLevelItems(/* context */ std::nullopt, Versioned.Items, + Versioned.Version); + + if (!ErrorOccured) + Writer.writeToStream(OS); + + return ErrorOccured; + } +}; +} // namespace + +static bool compile(const Module &M, const FileEntry *SourceFile, + llvm::raw_ostream &OS, + llvm::SourceMgr::DiagHandlerTy DiagHandler, + void *DiagHandlerCtxt) { + YAMLConverter C(M, SourceFile, OS, DiagHandler, DiagHandlerCtxt); + return C.convertModule(); +} + +/// Simple diagnostic handler that prints diagnostics to standard error. +static void printDiagnostic(const llvm::SMDiagnostic &Diag, void *Context) { + Diag.print(nullptr, llvm::errs()); +} + +bool api_notes::compileAPINotes(StringRef YAMLInput, + const FileEntry *SourceFile, + llvm::raw_ostream &OS, + llvm::SourceMgr::DiagHandlerTy DiagHandler, + void *DiagHandlerCtxt) { + Module TheModule; + + if (!DiagHandler) + DiagHandler = &printDiagnostic; + + if (parseAPINotes(YAMLInput, TheModule, DiagHandler, DiagHandlerCtxt)) + return true; + + return compile(TheModule, SourceFile, OS, DiagHandler, DiagHandlerCtxt); +}