diff --git a/clang/include/clang/APINotes/APINotesYAMLCompiler.h b/clang/include/clang/APINotes/APINotesYAMLCompiler.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/APINotes/APINotesYAMLCompiler.h @@ -0,0 +1,24 @@ +//===-- APINotesYAMLCompiler.h - API Notes YAML Format Reader ---*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_APINOTES_APINOTESYAMLCOMPILER_H +#define LLVM_CLANG_APINOTES_APINOTESYAMLCOMPILER_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +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); +} // namespace api_notes +} // namespace clang + +#endif diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/APINotes/Types.h @@ -0,0 +1,40 @@ +//===-- Types.h - API Notes Data Types --------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_APINOTES_TYPES_H +#define LLVM_CLANG_APINOTES_TYPES_H + +namespace clang { +namespace api_notes { +enum class RetainCountConventionKind { + None, + CFReturnsRetained, + CFReturnsNotRetained, + NSReturnsRetained, + NSReturnsNotRetained, +}; + +/// The payload for an enum_extensibility attribute. This is a tri-state rather +/// than just a boolean because the presence of the attribute indicates +/// auditing. +enum class EnumExtensibilityKind { + None, + Open, + Closed, +}; + +/// The kind of a swift_wrapper/swift_newtype. +enum class SwiftNewTypeKind { + None, + Struct, + Enum, +}; +} // namespace api_notes +} // namespace clang + +#endif diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp @@ -0,0 +1,612 @@ +//===-- APINotesYAMLCompiler.cpp - API Notes YAML Format Reader -*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/APINotes/APINotesYAMLCompiler.h" +#include "clang/APINotes/Types.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/Specifiers.h" +#include "llvm/ADT/Optional.h" +#include "llvm/Support/VersionTuple.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" +#include +using namespace clang; +using namespace api_notes; + +namespace { +enum class APIAvailability { + Available = 0, + OSX, + IOS, + None, + NonSwift, +}; +} // namespace + +namespace llvm { +namespace yaml { +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, APIAvailability &AA) { + IO.enumCase(AA, "OSX", APIAvailability::OSX); + IO.enumCase(AA, "iOS", APIAvailability::IOS); + IO.enumCase(AA, "none", APIAvailability::None); + IO.enumCase(AA, "nonswift", APIAvailability::NonSwift); + IO.enumCase(AA, "available", APIAvailability::Available); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +enum class MethodKind { + Class, + Instance, +}; +} // namespace + +namespace llvm { +namespace yaml { +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, MethodKind &MK) { + IO.enumCase(MK, "Class", MethodKind::Class); + IO.enumCase(MK, "Instance", MethodKind::Instance); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +struct Param { + unsigned Position; + Optional NoEscape = false; + Optional Nullability; + Optional RetainCountConvention; + StringRef Type; +}; + +typedef std::vector ParamsSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(Param) +LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(NullabilityKind) + +namespace llvm { +namespace yaml { +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, NullabilityKind &NK) { + IO.enumCase(NK, "Nonnull", NullabilityKind::NonNull); + IO.enumCase(NK, "Optional", NullabilityKind::Nullable); + IO.enumCase(NK, "Unspecified", NullabilityKind::Unspecified); + // TODO: Mapping this to it's own value would allow for better cross + // checking. Also the default should be Unknown. + IO.enumCase(NK, "Scalar", NullabilityKind::Unspecified); + + // Aliases for compatibility with existing APINotes. + IO.enumCase(NK, "N", NullabilityKind::NonNull); + IO.enumCase(NK, "O", NullabilityKind::Nullable); + IO.enumCase(NK, "U", NullabilityKind::Unspecified); + IO.enumCase(NK, "S", NullabilityKind::Unspecified); + } +}; + +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, RetainCountConventionKind &RCCK) { + IO.enumCase(RCCK, "none", RetainCountConventionKind::None); + IO.enumCase(RCCK, "CFReturnsRetained", + RetainCountConventionKind::CFReturnsRetained); + IO.enumCase(RCCK, "CFReturnsNotRetained", + RetainCountConventionKind::CFReturnsNotRetained); + IO.enumCase(RCCK, "NSReturnsRetained", + RetainCountConventionKind::NSReturnsRetained); + IO.enumCase(RCCK, "NSReturnsNotRetained", + RetainCountConventionKind::NSReturnsNotRetained); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &IO, Param& P) { + IO.mapRequired("Position", P.Position); + IO.mapOptional("Nullability", P.Nullability, llvm::None); + IO.mapOptional("RetainCountConvention", P.RetainCountConvention); + IO.mapOptional("NoEscape", P.NoEscape); + IO.mapOptional("Type", P.Type, StringRef("")); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +typedef std::vector NullabilitySeq; + +struct AvailabilityItem { + APIAvailability Mode = APIAvailability::Available; + StringRef Msg; +}; + +/// Old attribute deprecated in favor of SwiftName. +enum class FactoryAsInitKind { + /// Infer based on name and type (the default). + Infer, + /// Treat as a class method. + AsClassMethod, + /// Treat as an initializer. + AsInitializer, +}; + +struct Method { + StringRef Selector; + MethodKind Kind; + ParamsSeq Params; + NullabilitySeq Nullability; + Optional NullabilityOfRet; + Optional RetainCountConvention; + AvailabilityItem Availability; + Optional SwiftPrivate; + StringRef SwiftName; + FactoryAsInitKind FactoryAsInit = FactoryAsInitKind::Infer; + bool DesignatedInit = false; + bool Required = false; + StringRef ResultType; +}; + +typedef std::vector MethodsSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(Method) + +namespace llvm { +namespace yaml { +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, FactoryAsInitKind &FIK) { + IO.enumCase(FIK, "A", FactoryAsInitKind::Infer); + IO.enumCase(FIK, "C", FactoryAsInitKind::AsClassMethod); + IO.enumCase(FIK, "I", FactoryAsInitKind::AsInitializer); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &IO, Method &M) { + IO.mapRequired("Selector", M.Selector); + IO.mapRequired("MethodKind", M.Kind); + IO.mapOptional("Parameters", M.Params); + IO.mapOptional("Nullability", M.Nullability); + IO.mapOptional("NullabilityOfRet", M.NullabilityOfRet, llvm::None); + IO.mapOptional("RetainCountConvention", M.RetainCountConvention); + IO.mapOptional("Availability", M.Availability.Mode, + APIAvailability::Available); + IO.mapOptional("AvailabilityMsg", M.Availability.Msg, StringRef("")); + IO.mapOptional("SwiftPrivate", M.SwiftPrivate); + IO.mapOptional("SwiftName", M.SwiftName, StringRef("")); + IO.mapOptional("FactoryAsInit", M.FactoryAsInit, + FactoryAsInitKind::Infer); + IO.mapOptional("DesignatedInit", M.DesignatedInit, false); + IO.mapOptional("Required", M.Required, false); + IO.mapOptional("ResultType", M.ResultType, StringRef("")); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +struct Property { + StringRef Name; + llvm::Optional Kind; + llvm::Optional Nullability; + AvailabilityItem Availability; + Optional SwiftPrivate; + StringRef SwiftName; + Optional SwiftImportAsAccessors; + StringRef Type; +}; + +typedef std::vector PropertiesSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(Property) + +namespace llvm { +namespace yaml { +template <> +struct MappingTraits { + static void mapping(IO &IO, Property &P) { + IO.mapRequired("Name", P.Name); + IO.mapOptional("PropertyKind", P.Kind); + IO.mapOptional("Nullability", P.Nullability, llvm::None); + IO.mapOptional("Availability", P.Availability.Mode, + APIAvailability::Available); + IO.mapOptional("AvailabilityMsg", P.Availability.Msg, StringRef("")); + IO.mapOptional("SwiftPrivate", P.SwiftPrivate); + IO.mapOptional("SwiftName", P.SwiftName, StringRef("")); + IO.mapOptional("SwiftImportAsAccessors", P.SwiftImportAsAccessors); + IO.mapOptional("Type", P.Type, StringRef("")); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +struct Class { + StringRef Name; + bool AuditedForNullability = false; + AvailabilityItem Availability; + Optional SwiftPrivate; + StringRef SwiftName; + Optional SwiftBridge; + Optional NSErrorDomain; + Optional SwiftImportAsNonGeneric; + Optional SwiftObjCMembers; + MethodsSeq Methods; + PropertiesSeq Properties; +}; + +typedef std::vector ClassesSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(Class) + +namespace llvm { +namespace yaml { +template <> +struct MappingTraits { + static void mapping(IO &IO, Class &C) { + IO.mapRequired("Name", C.Name); + IO.mapOptional("AuditedForNullability", C.AuditedForNullability, false); + IO.mapOptional("Availability", C.Availability.Mode, + APIAvailability::Available); + IO.mapOptional("AvailabilityMsg", C.Availability.Msg, + StringRef("")); + IO.mapOptional("SwiftPrivate", C.SwiftPrivate); + IO.mapOptional("SwiftName", C.SwiftName, StringRef("")); + IO.mapOptional("SwiftBridge", C.SwiftBridge); + IO.mapOptional("NSErrorDomain", C.NSErrorDomain); + IO.mapOptional("SwiftImportAsNonGeneric", C.SwiftImportAsNonGeneric); + IO.mapOptional("SwiftObjCMembers", C.SwiftObjCMembers); + IO.mapOptional("Methods", C.Methods); + IO.mapOptional("Properties", C.Properties); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +struct Function { + StringRef Name; + ParamsSeq Params; + NullabilitySeq Nullability; + Optional NullabilityOfRet; + Optional RetainCountConvention; + AvailabilityItem Availability; + Optional SwiftPrivate; + StringRef SwiftName; + StringRef Type; + StringRef ResultType; +}; + +typedef std::vector FunctionsSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(Function) + +namespace llvm { +namespace yaml { +template <> +struct MappingTraits { + static void mapping(IO &IO, Function &F) { + IO.mapRequired("Name", F.Name); + IO.mapOptional("Parameters", F.Params); + IO.mapOptional("Nullability", F.Nullability); + IO.mapOptional("NullabilityOfRet", F.NullabilityOfRet, llvm::None); + IO.mapOptional("RetainCountConvention", F.RetainCountConvention); + IO.mapOptional("Availability", F.Availability.Mode, + APIAvailability::Available); + IO.mapOptional("AvailabilityMsg", F.Availability.Msg, StringRef("")); + IO.mapOptional("SwiftPrivate", F.SwiftPrivate); + IO.mapOptional("SwiftName", F.SwiftName, StringRef("")); + IO.mapOptional("ResultType", F.ResultType, StringRef("")); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +struct GlobalVariable { + StringRef Name; + llvm::Optional Nullability; + AvailabilityItem Availability; + Optional SwiftPrivate; + StringRef SwiftName; + StringRef Type; +}; + +typedef std::vector GlobalVariablesSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(GlobalVariable) + +namespace llvm { +namespace yaml { +template <> +struct MappingTraits { + static void mapping(IO &IO, GlobalVariable &GV) { + IO.mapRequired("Name", GV.Name); + IO.mapOptional("Nullability", GV.Nullability, llvm::None); + IO.mapOptional("Availability", GV.Availability.Mode, + APIAvailability::Available); + IO.mapOptional("AvailabilityMsg", GV.Availability.Msg, StringRef("")); + IO.mapOptional("SwiftPrivate", GV.SwiftPrivate); + IO.mapOptional("SwiftName", GV.SwiftName, StringRef("")); + IO.mapOptional("Type", GV.Type, StringRef("")); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +struct EnumConstant { + StringRef Name; + AvailabilityItem Availability; + Optional SwiftPrivate; + StringRef SwiftName; +}; + +typedef std::vector EnumConstantsSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(EnumConstant) + +namespace llvm { +namespace yaml { +template <> +struct MappingTraits { + static void mapping(IO &IO, EnumConstant& EC) { + IO.mapRequired("Name", EC.Name); + IO.mapOptional("Availability", EC.Availability.Mode, + APIAvailability::Available); + IO.mapOptional("AvailabilityMsg", EC.Availability.Msg, StringRef("")); + IO.mapOptional("SwiftPrivate", EC.SwiftPrivate); + IO.mapOptional("SwiftName", EC.SwiftName, StringRef("")); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +/// Syntactic sugar for EnumExtensibility and FlagEnum +enum class EnumConvenienceAliasKind { + /// EnumExtensibility: none, FlagEnum: false + None, + /// EnumExtensibility: open, FlagEnum: false + CFEnum, + /// EnumExtensibility: open, FlagEnum: true + CFOptions, + /// EnumExtensibility: closed, FlagEnum: false + CFClosedEnum +}; +} // namespace + +namespace llvm { +namespace yaml { +template<> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, EnumConvenienceAliasKind &ECAK) { + IO.enumCase(ECAK, "none", EnumConvenienceAliasKind::None); + IO.enumCase(ECAK, "CFEnum", EnumConvenienceAliasKind::CFEnum); + IO.enumCase(ECAK, "NSEnum", EnumConvenienceAliasKind::CFEnum); + IO.enumCase(ECAK, "CFOptions", EnumConvenienceAliasKind::CFOptions); + IO.enumCase(ECAK, "NSOptions", EnumConvenienceAliasKind::CFOptions); + IO.enumCase(ECAK, "CFClosedEnum", EnumConvenienceAliasKind::CFClosedEnum); + IO.enumCase(ECAK, "NSClosedEnum", EnumConvenienceAliasKind::CFClosedEnum); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +struct Tag { + StringRef Name; + AvailabilityItem Availability; + StringRef SwiftName; + Optional SwiftPrivate; + Optional SwiftBridge; + Optional NSErrorDomain; + Optional EnumExtensibility; + Optional FlagEnum; + Optional EnumConvenienceKind; +}; + +typedef std::vector TagsSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(Tag) + +namespace llvm { +namespace yaml { +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, EnumExtensibilityKind &EEK) { + IO.enumCase(EEK, "none", EnumExtensibilityKind::None); + IO.enumCase(EEK, "open", EnumExtensibilityKind::Open); + IO.enumCase(EEK, "closed", EnumExtensibilityKind::Closed); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &IO, Tag &T) { + IO.mapRequired("Name", T.Name); + IO.mapOptional("Availability", T.Availability.Mode, + APIAvailability::Available); + IO.mapOptional("AvailabilityMsg", T.Availability.Msg, StringRef("")); + IO.mapOptional("SwiftPrivate", T.SwiftPrivate); + IO.mapOptional("SwiftName", T.SwiftName, StringRef("")); + IO.mapOptional("SwiftBridge", T.SwiftBridge); + IO.mapOptional("NSErrorDomain", T.NSErrorDomain); + IO.mapOptional("EnumExtensibility", T.EnumExtensibility); + IO.mapOptional("FlagEnum", T.FlagEnum); + IO.mapOptional("EnumKind", T.EnumConvenienceKind); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +struct Typedef { + StringRef Name; + AvailabilityItem Availability; + StringRef SwiftName; + Optional SwiftPrivate; + Optional SwiftBridge; + Optional NSErrorDomain; + Optional SwiftType; +}; + +typedef std::vector TypedefsSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(Typedef) + +namespace llvm { +namespace yaml { +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, SwiftNewTypeKind &SWK) { + IO.enumCase(SWK, "none", SwiftNewTypeKind::None); + IO.enumCase(SWK, "struct", SwiftNewTypeKind::Struct); + IO.enumCase(SWK, "enum", SwiftNewTypeKind::Enum); + } +}; + +template <> +struct MappingTraits { + static void mapping(IO &IO, Typedef &T) { + IO.mapRequired("Name", T.Name); + IO.mapOptional("Availability", T.Availability.Mode, + APIAvailability::Available); + IO.mapOptional("AvailabilityMsg", T.Availability.Msg, StringRef("")); + IO.mapOptional("SwiftPrivate", T.SwiftPrivate); + IO.mapOptional("SwiftName", T.SwiftName, StringRef("")); + IO.mapOptional("SwiftBridge", T.SwiftBridge); + IO.mapOptional("NSErrorDomain", T.NSErrorDomain); + IO.mapOptional("SwiftWrapper", T.SwiftType); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +struct TopLevelItems { + ClassesSeq Classes; + ClassesSeq Protocols; + FunctionsSeq Functions; + GlobalVariablesSeq Globals; + EnumConstantsSeq EnumConstants; + TagsSeq Tags; + TypedefsSeq Typedefs; +}; +} // namespace + +namespace llvm { +namespace yaml { +static void mapTopLevelItems(IO &IO, TopLevelItems &TLI) { + IO.mapOptional("Classes", TLI.Classes); + IO.mapOptional("Protocols", TLI.Protocols); + IO.mapOptional("Functions", TLI.Functions); + IO.mapOptional("Globals", TLI.Globals); + IO.mapOptional("Enumerators", TLI.EnumConstants); + IO.mapOptional("Tags", TLI.Tags); + IO.mapOptional("Typedefs", TLI.Typedefs); +} +} // namespace yaml +} // namespace llvm + +namespace { +struct Versioned { + VersionTuple Version; + TopLevelItems Items; +}; + +typedef std::vector VersionedSeq; +} // namespace + +LLVM_YAML_IS_SEQUENCE_VECTOR(Versioned) + +namespace llvm { +namespace yaml { +template <> +struct MappingTraits { + static void mapping(IO &IO, Versioned &V) { + IO.mapRequired("Version", V.Version); + mapTopLevelItems(IO, V.Items); + } +}; +} // namespace yaml +} // namespace llvm + +namespace { +struct Module { + StringRef Name; + AvailabilityItem Availability; + TopLevelItems TopLevel; + VersionedSeq SwiftVersions; + + llvm::Optional SwiftInferImportAsMember = {llvm::None}; + + LLVM_DUMP_METHOD void dump() /*const*/; +}; +} // namespace + +namespace llvm { +namespace yaml { +template <> +struct MappingTraits { + static void mapping(IO &IO, Module &M) { + IO.mapRequired("Name", M.Name); + IO.mapOptional("Availability", M.Availability.Mode, + APIAvailability::Available); + IO.mapOptional("AvailabilityMsg", M.Availability.Msg, + StringRef("")); + IO.mapOptional("SwiftInferImportAsMember", M.SwiftInferImportAsMember); + mapTopLevelItems(IO, M.TopLevel); + IO.mapOptional("SwiftVersions", M.SwiftVersions); + } +}; +} // namespace yaml +} // namespace llvm + +void Module::dump() { + llvm::yaml::Output OS(llvm::errs()); + OS << *this; +} + +namespace { +bool parseAPINotes(StringRef YI, Module &M, + llvm::SourceMgr::DiagHandlerTy Diag, void *DiagContext) { + llvm::yaml::Input IS(YI, nullptr, Diag, DiagContext); + IS >> M; + return static_cast(IS.error()); +} +} + +bool +clang::api_notes::parseAndDumpAPINotes(StringRef YI, llvm::raw_ostream &OS) { + Module M; + if (parseAPINotes(YI, M, nullptr, nullptr)) + return true; + + llvm::yaml::Output YOS(OS); + YOS << M; + + return false; +} diff --git a/clang/lib/APINotes/CMakeLists.txt b/clang/lib/APINotes/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang/lib/APINotes/CMakeLists.txt @@ -0,0 +1,6 @@ +set(LLVM_LINK_COMPONENTS + Support) +add_clang_library(clangAPINotes + APINotesYAMLCompiler.cpp + LINK_LIBS + clangBasic) diff --git a/clang/lib/CMakeLists.txt b/clang/lib/CMakeLists.txt --- a/clang/lib/CMakeLists.txt +++ b/clang/lib/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(Headers) add_subdirectory(Basic) +add_subdirectory(APINotes) add_subdirectory(Lex) add_subdirectory(Parse) add_subdirectory(AST) diff --git a/clang/test/APINotes/Inputs/Frameworks/Simple.framework/Headers/Simple.h b/clang/test/APINotes/Inputs/Frameworks/Simple.framework/Headers/Simple.h new file mode 100644 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/Simple.framework/Headers/Simple.h @@ -0,0 +1,19 @@ +#ifndef SIMPLE_H +#define SIMPLE_H + +__attribute__((__objc_root__)) +@interface I +@property(class, nonatomic, readonly) id nonnullProperty; +@property(class, nonatomic, readonly) id nonnullNewProperty; + +@property(class, nonatomic, readonly) id optionalProperty; +@property(class, nonatomic, readonly) id optionalNewProperty; + +@property(nonatomic, readonly) id unspecifiedProperty; +@property(nonatomic, readonly) id unspecifiedNewProperty; + +@property(nonatomic, readonly) id scalarProperty; +@property(nonatomic, readonly) id scalarNewProperty; +@end + +#endif diff --git a/clang/test/APINotes/Inputs/Frameworks/Simple.framework/Headers/Simple.apinotes b/clang/test/APINotes/Inputs/Frameworks/Simple.framework/Headers/Simple.apinotes new file mode 100644 --- /dev/null +++ b/clang/test/APINotes/Inputs/Frameworks/Simple.framework/Headers/Simple.apinotes @@ -0,0 +1,28 @@ +Name: SimpleKit +Classes: + - Name: I + Properties: + - Name: nonnullProperty + PropertyKind: Class + Nullability: N + - Name: nonnullNewProperty + PropertyKind: Class + Nullability: Nonnull + - Name: optionalProperty + PropertyKind: Class + Nullability: O + - Name: optionalNewProperty + PropertyKind: Class + Nullability: Optional + - Name: unspecifiedProperty + PropertyKind: Instance + Nullability: U + - Name: unspecifiedNewProperty + PropertyKind: Instance + Nullability: Unspecified + - Name: scalarProperty + PropertyKind: Instance + Nullability: S + - Name: scalarNewProperty + PropertyKind: Instance + Nullability: Scalar diff --git a/clang/test/APINotes/yaml-roundtrip.test b/clang/test/APINotes/yaml-roundtrip.test new file mode 100644 --- /dev/null +++ b/clang/test/APINotes/yaml-roundtrip.test @@ -0,0 +1,26 @@ +RUN: apinotes-test %S/Inputs/Frameworks/Simple.framework/Headers/Simple.apinotes > %t.result +RUN: not diff %S/Inputs/Frameworks/Simple.framework/Headers/Simple.apinotes %t.result | FileCheck %s + +We expect only the nullability to be different as it is canonicalized during the +roudtrip. + +CHECK: 7c8 +CHECK-NEXT: < Nullability: N +CHECK-NEXT: --- +CHECK-NEXT: > Nullability: Nonnull +CHECK-NEXT: 13c14 +CHECK-NEXT: < Nullability: O +CHECK-NEXT: --- +CHECK-NEXT: > Nullability: Optional +CHECK-NEXT: 19c20 +CHECK-NEXT: < Nullability: U +CHECK-NEXT: --- +CHECK-NEXT: > Nullability: Unspecified +CHECK-NEXT: 25c26 +CHECK-NEXT: < Nullability: S +CHECK-NEXT: --- +CHECK-NEXT: > Nullability: Unspecified +CHECK-NEXT: 28c29,30 +CHECK-NEXT: < Nullability: Scalar +CHECK-NEXT: --- +CHECK-NEXT: > Nullability: Unspecified diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt --- a/clang/test/CMakeLists.txt +++ b/clang/test/CMakeLists.txt @@ -58,6 +58,7 @@ endif () list(APPEND CLANG_TEST_DEPS + apinotes-test c-index-test clang clang-resource-headers diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py --- a/clang/test/lit.cfg.py +++ b/clang/test/lit.cfg.py @@ -63,7 +63,8 @@ tool_dirs = [config.clang_tools_dir, config.llvm_tools_dir] tools = [ - 'c-index-test', 'clang-diff', 'clang-format', 'clang-tblgen', 'opt', 'llvm-ifs', + 'apinotes-test', 'c-index-test', 'clang-diff', 'clang-format', + 'clang-tblgen', 'opt', 'llvm-ifs', ToolSubst('%clang_extdef_map', command=FindTool( 'clang-extdef-mapping'), unresolved='ignore'), ] diff --git a/clang/tools/CMakeLists.txt b/clang/tools/CMakeLists.txt --- a/clang/tools/CMakeLists.txt +++ b/clang/tools/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) +add_clang_subdirectory(apinotes-test) add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) diff --git a/clang/tools/apinotes-test/APINotesTest.cpp b/clang/tools/apinotes-test/APINotesTest.cpp new file mode 100644 --- /dev/null +++ b/clang/tools/apinotes-test/APINotesTest.cpp @@ -0,0 +1,53 @@ +//===-- APINotesTest.cpp - API Notes Testing Tool ------------------ C++ --===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/APINotes/APINotesYAMLCompiler.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/WithColor.h" + +static llvm::cl::list +APINotes(llvm::cl::Positional, llvm::cl::desc("[ ...]"), + llvm::cl::Required); + +static llvm::cl::opt +OutputFileName("o", llvm::cl::desc("output filename"), + llvm::cl::value_desc("filename"), llvm::cl::init("-")); + +int main(int argc, const char **argv) { + const bool DisableCrashReporting = true; + llvm::sys::PrintStackTraceOnErrorSignal(argv[0], DisableCrashReporting); + llvm::cl::ParseCommandLineOptions(argc, argv); + + auto Error = [](const llvm::Twine &Msg) { + llvm::WithColor::error(llvm::errs(), "apinotes-test") << Msg << '\n'; + }; + + std::error_code EC; + auto Out = std::make_unique(OutputFileName, EC, + llvm::sys::fs::OF_None); + if (EC) { + Error("failed to open '" + OutputFileName + "': " + EC.message()); + return EXIT_FAILURE; + } + + for (const std::string &Notes : APINotes) { + llvm::ErrorOr> NotesOrError = + llvm::MemoryBuffer::getFileOrSTDIN(Notes); + if (std::error_code EC = NotesOrError.getError()) { + llvm::errs() << EC.message() << '\n'; + return EXIT_FAILURE; + } + + clang::api_notes::parseAndDumpAPINotes((*NotesOrError)->getBuffer(), + Out->os()); + } + + return EXIT_SUCCESS; +} diff --git a/clang/tools/apinotes-test/CMakeLists.txt b/clang/tools/apinotes-test/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang/tools/apinotes-test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(LLVM_LINK_COMPONENTS + Support) +add_clang_executable(apinotes-test + APINotesTest.cpp) +clang_target_link_libraries(apinotes-test PRIVATE + clangAPINotes)