diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index a7f2858477bee..dad1764ad4f86 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10760,6 +10760,13 @@ def warn_imp_cast_drops_unaligned : Warning< } // end of sema category +let CategoryName = "API Notes Issue" in { + +def err_incompatible_replacement_type : Error< + "API notes replacement type %0 has a different size from original type %1">; + +} // end of API Notes category + let CategoryName = "OpenMP Issue" in { // OpenMP support. def err_omp_expected_var_arg : Error< diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index e457694e4625d..c966a99c51968 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4883,6 +4883,12 @@ class Sema final { bool checkCommonAttributeFeatures(const Stmt *S, const ParsedAttr &A, bool SkipArgCountCheck = false); + /// Map any API notes provided for this declaration to attributes on the + /// declaration. + /// + /// Triggered by declaration-attribute processing. + void ProcessAPINotes(Decl *D); + /// Determine if type T is a valid subject for a nonnull and similar /// attributes. By default, we look through references (the behavior used by /// nonnull), but if the second parameter is true, then we treat a reference diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index 862f9d4ffb825..e8bff07ced0cf 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -27,6 +27,7 @@ add_clang_library(clangSema Sema.cpp SemaAccess.cpp SemaAttr.cpp + SemaAPINotes.cpp SemaAvailability.cpp SemaCXXScopeSpec.cpp SemaCast.cpp diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp new file mode 100644 index 0000000000000..836c633e9d204 --- /dev/null +++ b/clang/lib/Sema/SemaAPINotes.cpp @@ -0,0 +1,988 @@ +//===--- SemaAPINotes.cpp - API Notes Handling ----------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements the mapping from API notes to declaration attributes. +// +//===----------------------------------------------------------------------===// + +#include "clang/APINotes/APINotesReader.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclObjC.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" +#include "clang/Sema/SemaInternal.h" + +using namespace clang; + +namespace { +enum class IsActive_t : bool { Inactive, Active }; +enum class IsSubstitution_t : bool { Original, Replacement }; + +struct VersionedInfoMetadata { + /// An empty version refers to unversioned metadata. + VersionTuple Version; + unsigned IsActive : 1; + unsigned IsReplacement : 1; + + VersionedInfoMetadata(VersionTuple Version, IsActive_t Active, + IsSubstitution_t Replacement) + : Version(Version), IsActive(Active == IsActive_t::Active), + IsReplacement(Replacement == IsSubstitution_t::Replacement) {} +}; +} // end anonymous namespace + +/// Determine whether this is a multi-level pointer type. +static bool isIndirectPointerType(QualType Type) { + QualType Pointee = Type->getPointeeType(); + if (Pointee.isNull()) + return false; + + return Pointee->isAnyPointerType() || Pointee->isObjCObjectPointerType() || + Pointee->isMemberPointerType(); +} + +/// Apply nullability to the given declaration. +static void applyNullability(Sema &S, Decl *D, NullabilityKind Nullability, + VersionedInfoMetadata Metadata) { + if (!Metadata.IsActive) + return; + + auto IsModified = [&](Decl *D, QualType QT, + NullabilityKind Nullability) -> bool { + QualType Original = QT; + S.CheckImplicitNullabilityTypeSpecifier(QT, Nullability, D->getLocation(), + isa(D), + /*OverrideExisting=*/true); + return QT.getTypePtr() != Original.getTypePtr(); + }; + + if (auto Function = dyn_cast(D)) { + if (IsModified(D, Function->getReturnType(), Nullability)) { + QualType FnType = Function->getType(); + Function->setType(FnType); + } + } else if (auto Method = dyn_cast(D)) { + QualType Type = Method->getReturnType(); + if (IsModified(D, Type, Nullability)) { + Method->setReturnType(Type); + + // Make it a context-sensitive keyword if we can. + if (!isIndirectPointerType(Type)) + Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier( + Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability)); + } + } else if (auto Value = dyn_cast(D)) { + QualType Type = Value->getType(); + if (IsModified(D, Type, Nullability)) { + Value->setType(Type); + + // Make it a context-sensitive keyword if we can. + if (auto Parm = dyn_cast(D)) { + if (Parm->isObjCMethodParameter() && !isIndirectPointerType(Type)) + Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier( + Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability)); + } + } + } else if (auto Property = dyn_cast(D)) { + QualType Type = Property->getType(); + if (IsModified(D, Type, Nullability)) { + Property->setType(Type, Property->getTypeSourceInfo()); + + // Make it a property attribute if we can. + if (!isIndirectPointerType(Type)) + Property->setPropertyAttributes( + ObjCPropertyAttribute::kind_null_resettable); + } + } +} + +/// Copy a string into ASTContext-allocated memory. +static StringRef ASTAllocateString(ASTContext &Ctx, StringRef String) { + void *mem = Ctx.Allocate(String.size(), alignof(char *)); + memcpy(mem, String.data(), String.size()); + return StringRef(static_cast(mem), String.size()); +} + +static AttributeCommonInfo getPlaceholderAttrInfo() { + return AttributeCommonInfo(SourceRange(), + AttributeCommonInfo::UnknownAttribute, + {AttributeCommonInfo::AS_GNU, + /*Spelling*/ 0, /*IsAlignas*/ false, + /*IsRegularKeywordAttribute*/ false}); +} + +namespace { +template struct AttrKindFor {}; + +#define ATTR(X) \ + template <> struct AttrKindFor { \ + static const attr::Kind value = attr::X; \ + }; +#include "clang/Basic/AttrList.inc" + +/// Handle an attribute introduced by API notes. +/// +/// \param IsAddition Whether we should add a new attribute +/// (otherwise, we might remove an existing attribute). +/// \param CreateAttr Create the new attribute to be added. +template +void handleAPINotedAttribute( + Sema &S, Decl *D, bool IsAddition, VersionedInfoMetadata Metadata, + llvm::function_ref CreateAttr, + llvm::function_ref GetExistingAttr) { + if (Metadata.IsActive) { + auto Existing = GetExistingAttr(D); + if (Existing != D->attr_end()) { + // Remove the existing attribute, and treat it as a superseded + // non-versioned attribute. + auto *Versioned = SwiftVersionedAdditionAttr::CreateImplicit( + S.Context, Metadata.Version, *Existing, /*IsReplacedByActive*/ true); + + D->getAttrs().erase(Existing); + D->addAttr(Versioned); + } + + // If we're supposed to add a new attribute, do so. + if (IsAddition) { + if (auto Attr = CreateAttr()) + D->addAttr(Attr); + } + + return; + } + if (IsAddition) { + if (auto Attr = CreateAttr()) { + auto *Versioned = SwiftVersionedAdditionAttr::CreateImplicit( + S.Context, Metadata.Version, Attr, + /*IsReplacedByActive*/ Metadata.IsReplacement); + D->addAttr(Versioned); + } + } else { + // FIXME: This isn't preserving enough information for things like + // availability, where we're trying to remove a /specific/ kind of + // attribute. + auto *Versioned = SwiftVersionedRemovalAttr::CreateImplicit( + S.Context, Metadata.Version, AttrKindFor::value, + /*IsReplacedByActive*/ Metadata.IsReplacement); + D->addAttr(Versioned); + } +} + +template +void handleAPINotedAttribute(Sema &S, Decl *D, bool ShouldAddAttribute, + VersionedInfoMetadata Metadata, + llvm::function_ref CreateAttr) { + handleAPINotedAttribute( + S, D, ShouldAddAttribute, Metadata, CreateAttr, [](const Decl *D) { + return llvm::find_if(D->attrs(), + [](const Attr *Next) { return isa(Next); }); + }); +} +} // namespace + +template +static void handleAPINotedRetainCountAttribute(Sema &S, Decl *D, + bool ShouldAddAttribute, + VersionedInfoMetadata Metadata) { + // The template argument has a default to make the "removal" case more + // concise; it doesn't matter /which/ attribute is being removed. + handleAPINotedAttribute( + S, D, ShouldAddAttribute, Metadata, + [&] { return new (S.Context) A(S.Context, getPlaceholderAttrInfo()); }, + [](const Decl *D) -> Decl::attr_iterator { + return llvm::find_if(D->attrs(), [](const Attr *Next) -> bool { + return isa(Next) || + isa(Next) || + isa(Next) || + isa(Next) || + isa(Next); + }); + }); +} + +static void handleAPINotedRetainCountConvention( + Sema &S, Decl *D, VersionedInfoMetadata Metadata, + std::optional Convention) { + if (!Convention) + return; + switch (*Convention) { + case api_notes::RetainCountConventionKind::None: + if (isa(D)) { + handleAPINotedRetainCountAttribute( + S, D, /*shouldAddAttribute*/ true, Metadata); + } else { + handleAPINotedRetainCountAttribute( + S, D, /*shouldAddAttribute*/ false, Metadata); + } + break; + case api_notes::RetainCountConventionKind::CFReturnsRetained: + handleAPINotedRetainCountAttribute( + S, D, /*shouldAddAttribute*/ true, Metadata); + break; + case api_notes::RetainCountConventionKind::CFReturnsNotRetained: + handleAPINotedRetainCountAttribute( + S, D, /*shouldAddAttribute*/ true, Metadata); + break; + case api_notes::RetainCountConventionKind::NSReturnsRetained: + handleAPINotedRetainCountAttribute( + S, D, /*shouldAddAttribute*/ true, Metadata); + break; + case api_notes::RetainCountConventionKind::NSReturnsNotRetained: + handleAPINotedRetainCountAttribute( + S, D, /*shouldAddAttribute*/ true, Metadata); + break; + } +} + +static void ProcessAPINotes(Sema &S, Decl *D, + const api_notes::CommonEntityInfo &Info, + VersionedInfoMetadata Metadata) { + // Availability + if (Info.Unavailable) { + handleAPINotedAttribute(S, D, true, Metadata, [&] { + return new (S.Context) + UnavailableAttr(S.Context, getPlaceholderAttrInfo(), + ASTAllocateString(S.Context, Info.UnavailableMsg)); + }); + } + + if (Info.UnavailableInSwift) { + handleAPINotedAttribute( + S, D, true, Metadata, + [&] { + return new (S.Context) AvailabilityAttr( + S.Context, getPlaceholderAttrInfo(), + &S.Context.Idents.get("swift"), VersionTuple(), VersionTuple(), + VersionTuple(), + /*Unavailable=*/true, + ASTAllocateString(S.Context, Info.UnavailableMsg), + /*Strict=*/false, + /*Replacement=*/StringRef(), + /*Priority=*/Sema::AP_Explicit); + }, + [](const Decl *D) { + return llvm::find_if(D->attrs(), [](const Attr *next) -> bool { + if (const auto *AA = dyn_cast(next)) + if (const auto *II = AA->getPlatform()) + return II->isStr("swift"); + return false; + }); + }); + } + + // swift_private + if (auto SwiftPrivate = Info.isSwiftPrivate()) { + handleAPINotedAttribute( + S, D, *SwiftPrivate, Metadata, [&] { + return new (S.Context) + SwiftPrivateAttr(S.Context, getPlaceholderAttrInfo()); + }); + } + + // swift_name + if (!Info.SwiftName.empty()) { + handleAPINotedAttribute( + S, D, true, Metadata, [&]() -> SwiftNameAttr * { + AttributeFactory AF{}; + AttributePool AP{AF}; + auto &C = S.getASTContext(); + ParsedAttr *SNA = + AP.create(&C.Idents.get("swift_name"), SourceRange(), nullptr, + SourceLocation(), nullptr, nullptr, nullptr, + ParsedAttr::Form::GNU()); + + if (!S.DiagnoseSwiftName(D, Info.SwiftName, D->getLocation(), *SNA, + /*IsAsync=*/false)) + return nullptr; + + return new (S.Context) + SwiftNameAttr(S.Context, getPlaceholderAttrInfo(), + ASTAllocateString(S.Context, Info.SwiftName)); + }); + } +} + +static void ProcessAPINotes(Sema &S, Decl *D, + const api_notes::CommonTypeInfo &Info, + VersionedInfoMetadata Metadata) { + // swift_bridge + if (auto SwiftBridge = Info.getSwiftBridge()) { + handleAPINotedAttribute( + S, D, !SwiftBridge->empty(), Metadata, [&] { + return new (S.Context) + SwiftBridgeAttr(S.Context, getPlaceholderAttrInfo(), + ASTAllocateString(S.Context, *SwiftBridge)); + }); + } + + // ns_error_domain + if (auto NSErrorDomain = Info.getNSErrorDomain()) { + handleAPINotedAttribute( + S, D, !NSErrorDomain->empty(), Metadata, [&] { + return new (S.Context) + NSErrorDomainAttr(S.Context, getPlaceholderAttrInfo(), + &S.Context.Idents.get(*NSErrorDomain)); + }); + } + + ProcessAPINotes(S, D, static_cast(Info), + Metadata); +} + +/// Check that the replacement type provided by API notes is reasonable. +/// +/// This is a very weak form of ABI check. +static bool checkAPINotesReplacementType(Sema &S, SourceLocation Loc, + QualType OrigType, + QualType ReplacementType) { + if (S.Context.getTypeSize(OrigType) != + S.Context.getTypeSize(ReplacementType)) { + S.Diag(Loc, diag::err_incompatible_replacement_type) + << ReplacementType << OrigType; + return true; + } + + return false; +} + +/// Process API notes for a variable or property. +static void ProcessAPINotes(Sema &S, Decl *D, + const api_notes::VariableInfo &Info, + VersionedInfoMetadata Metadata) { + // Type override. + if (Metadata.IsActive && !Info.getType().empty() && + S.ParseTypeFromStringCallback) { + auto ParsedType = S.ParseTypeFromStringCallback( + Info.getType(), "", D->getLocation()); + if (ParsedType.isUsable()) { + QualType Type = Sema::GetTypeFromParser(ParsedType.get()); + auto TypeInfo = + S.Context.getTrivialTypeSourceInfo(Type, D->getLocation()); + + if (auto Var = dyn_cast(D)) { + // Make adjustments to parameter types. + if (isa(Var)) { + Type = S.AdjustParameterTypeForObjCAutoRefCount( + Type, D->getLocation(), TypeInfo); + Type = S.Context.getAdjustedParameterType(Type); + } + + if (!checkAPINotesReplacementType(S, Var->getLocation(), Var->getType(), + Type)) { + Var->setType(Type); + Var->setTypeSourceInfo(TypeInfo); + } + } else if (auto Property = dyn_cast(D)) { + if (!checkAPINotesReplacementType(S, Property->getLocation(), + Property->getType(), Type)) + Property->setType(Type, TypeInfo); + + } else + llvm_unreachable("API notes allowed a type on an unknown declaration"); + } + } + + // Nullability. + if (auto Nullability = Info.getNullability()) + applyNullability(S, D, *Nullability, Metadata); + + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info), + Metadata); +} + +/// Process API notes for a parameter. +static void ProcessAPINotes(Sema &S, ParmVarDecl *D, + const api_notes::ParamInfo &Info, + VersionedInfoMetadata Metadata) { + // noescape + if (auto NoEscape = Info.isNoEscape()) + handleAPINotedAttribute(S, D, *NoEscape, Metadata, [&] { + return new (S.Context) NoEscapeAttr(S.Context, getPlaceholderAttrInfo()); + }); + + // Retain count convention + handleAPINotedRetainCountConvention(S, D, Metadata, + Info.getRetainCountConvention()); + + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info), + Metadata); +} + +/// Process API notes for a global variable. +static void ProcessAPINotes(Sema &S, VarDecl *D, + const api_notes::GlobalVariableInfo &Info, + VersionedInfoMetadata metadata) { + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info), + metadata); +} + +/// Process API notes for an Objective-C property. +static void ProcessAPINotes(Sema &S, ObjCPropertyDecl *D, + const api_notes::ObjCPropertyInfo &Info, + VersionedInfoMetadata Metadata) { + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info), + Metadata); + + if (auto AsAccessors = Info.getSwiftImportAsAccessors()) { + handleAPINotedAttribute( + S, D, *AsAccessors, Metadata, [&] { + return new (S.Context) SwiftImportPropertyAsAccessorsAttr( + S.Context, getPlaceholderAttrInfo()); + }); + } +} + +namespace { +typedef llvm::PointerUnion FunctionOrMethod; +} + +/// Process API notes for a function or method. +static void ProcessAPINotes(Sema &S, FunctionOrMethod AnyFunc, + const api_notes::FunctionInfo &Info, + VersionedInfoMetadata Metadata) { + // Find the declaration itself. + FunctionDecl *FD = AnyFunc.dyn_cast(); + Decl *D = FD; + ObjCMethodDecl *MD = nullptr; + if (!D) { + MD = AnyFunc.get(); + D = MD; + } + + // Nullability of return type. + if (Info.NullabilityAudited) + applyNullability(S, D, Info.getReturnTypeInfo(), Metadata); + + // Parameters. + unsigned NumParams = FD ? FD->getNumParams() : MD->param_size(); + + bool AnyTypeChanged = false; + for (unsigned I = 0; I != NumParams; ++I) { + ParmVarDecl *Param = FD ? FD->getParamDecl(I) : MD->param_begin()[I]; + QualType ParamTypeBefore = Param->getType(); + + if (I < Info.Params.size()) + ProcessAPINotes(S, Param, Info.Params[I], Metadata); + + // Nullability. + if (Info.NullabilityAudited) + applyNullability(S, Param, Info.getParamTypeInfo(I), Metadata); + + if (ParamTypeBefore.getAsOpaquePtr() != Param->getType().getAsOpaquePtr()) + AnyTypeChanged = true; + } + + // Result type override. + QualType OverriddenResultType; + if (Metadata.IsActive && !Info.ResultType.empty() && + S.ParseTypeFromStringCallback) { + auto ParsedType = S.ParseTypeFromStringCallback( + Info.ResultType, "", D->getLocation()); + if (ParsedType.isUsable()) { + QualType ResultType = Sema::GetTypeFromParser(ParsedType.get()); + + if (MD) { + if (!checkAPINotesReplacementType(S, D->getLocation(), + MD->getReturnType(), ResultType)) { + auto ResultTypeInfo = + S.Context.getTrivialTypeSourceInfo(ResultType, D->getLocation()); + MD->setReturnType(ResultType); + MD->setReturnTypeSourceInfo(ResultTypeInfo); + } + } else if (!checkAPINotesReplacementType( + S, FD->getLocation(), FD->getReturnType(), ResultType)) { + OverriddenResultType = ResultType; + AnyTypeChanged = true; + } + } + } + + // If the result type or any of the parameter types changed for a function + // declaration, we have to rebuild the type. + if (FD && AnyTypeChanged) { + if (const auto *fnProtoType = FD->getType()->getAs()) { + if (OverriddenResultType.isNull()) + OverriddenResultType = fnProtoType->getReturnType(); + + SmallVector ParamTypes; + for (auto Param : FD->parameters()) + ParamTypes.push_back(Param->getType()); + + FD->setType(S.Context.getFunctionType(OverriddenResultType, ParamTypes, + fnProtoType->getExtProtoInfo())); + } else if (!OverriddenResultType.isNull()) { + const auto *FnNoProtoType = FD->getType()->castAs(); + FD->setType(S.Context.getFunctionNoProtoType( + OverriddenResultType, FnNoProtoType->getExtInfo())); + } + } + + // Retain count convention + handleAPINotedRetainCountConvention(S, D, Metadata, + Info.getRetainCountConvention()); + + // Handle common entity information. + ProcessAPINotes(S, D, static_cast(Info), + Metadata); +} + +/// Process API notes for a global function. +static void ProcessAPINotes(Sema &S, FunctionDecl *D, + const api_notes::GlobalFunctionInfo &Info, + VersionedInfoMetadata Metadata) { + // Handle common function information. + ProcessAPINotes(S, FunctionOrMethod(D), + static_cast(Info), Metadata); +} + +/// Process API notes for an enumerator. +static void ProcessAPINotes(Sema &S, EnumConstantDecl *D, + const api_notes::EnumConstantInfo &Info, + VersionedInfoMetadata Metadata) { + // Handle common information. + ProcessAPINotes(S, D, static_cast(Info), + Metadata); +} + +/// Process API notes for an Objective-C method. +static void ProcessAPINotes(Sema &S, ObjCMethodDecl *D, + const api_notes::ObjCMethodInfo &Info, + VersionedInfoMetadata Metadata) { + // Designated initializers. + if (Info.DesignatedInit) { + handleAPINotedAttribute( + S, D, true, Metadata, [&] { + if (ObjCInterfaceDecl *IFace = D->getClassInterface()) + IFace->setHasDesignatedInitializers(); + + return new (S.Context) ObjCDesignatedInitializerAttr( + S.Context, getPlaceholderAttrInfo()); + }); + } + + // Handle common function information. + ProcessAPINotes(S, FunctionOrMethod(D), + static_cast(Info), Metadata); +} + +/// Process API notes for a tag. +static void ProcessAPINotes(Sema &S, TagDecl *D, const api_notes::TagInfo &Info, + VersionedInfoMetadata Metadata) { + if (auto ImportAs = Info.SwiftImportAs) + D->addAttr(SwiftAttrAttr::Create(S.Context, "import_" + ImportAs.value())); + + if (auto RetainOp = Info.SwiftRetainOp) + D->addAttr(SwiftAttrAttr::Create(S.Context, "retain:" + RetainOp.value())); + + if (auto ReleaseOp = Info.SwiftReleaseOp) + D->addAttr( + SwiftAttrAttr::Create(S.Context, "release:" + ReleaseOp.value())); + + if (auto Extensibility = Info.EnumExtensibility) { + using api_notes::EnumExtensibilityKind; + bool ShouldAddAttribute = (*Extensibility != EnumExtensibilityKind::None); + handleAPINotedAttribute( + S, D, ShouldAddAttribute, Metadata, [&] { + EnumExtensibilityAttr::Kind kind; + switch (*Extensibility) { + case EnumExtensibilityKind::None: + llvm_unreachable("remove only"); + case EnumExtensibilityKind::Open: + kind = EnumExtensibilityAttr::Open; + break; + case EnumExtensibilityKind::Closed: + kind = EnumExtensibilityAttr::Closed; + break; + } + return new (S.Context) + EnumExtensibilityAttr(S.Context, getPlaceholderAttrInfo(), kind); + }); + } + + if (auto FlagEnum = Info.isFlagEnum()) { + handleAPINotedAttribute(S, D, *FlagEnum, Metadata, [&] { + return new (S.Context) FlagEnumAttr(S.Context, getPlaceholderAttrInfo()); + }); + } + + // Handle common type information. + ProcessAPINotes(S, D, static_cast(Info), + Metadata); +} + +/// Process API notes for a typedef. +static void ProcessAPINotes(Sema &S, TypedefNameDecl *D, + const api_notes::TypedefInfo &Info, + VersionedInfoMetadata Metadata) { + // swift_wrapper + using SwiftWrapperKind = api_notes::SwiftNewTypeKind; + + if (auto SwiftWrapper = Info.SwiftWrapper) { + handleAPINotedAttribute( + S, D, *SwiftWrapper != SwiftWrapperKind::None, Metadata, [&] { + SwiftNewTypeAttr::NewtypeKind Kind; + switch (*SwiftWrapper) { + case SwiftWrapperKind::None: + llvm_unreachable("Shouldn't build an attribute"); + + case SwiftWrapperKind::Struct: + Kind = SwiftNewTypeAttr::NK_Struct; + break; + + case SwiftWrapperKind::Enum: + Kind = SwiftNewTypeAttr::NK_Enum; + break; + } + AttributeCommonInfo SyntaxInfo{ + SourceRange(), + AttributeCommonInfo::AT_SwiftNewType, + {AttributeCommonInfo::AS_GNU, SwiftNewTypeAttr::GNU_swift_wrapper, + /*IsAlignas*/ false, /*IsRegularKeywordAttribute*/ false}}; + return new (S.Context) SwiftNewTypeAttr(S.Context, SyntaxInfo, Kind); + }); + } + + // Handle common type information. + ProcessAPINotes(S, D, static_cast(Info), + Metadata); +} + +/// Process API notes for an Objective-C class or protocol. +static void ProcessAPINotes(Sema &S, ObjCContainerDecl *D, + const api_notes::ObjCContextInfo &Info, + VersionedInfoMetadata Metadata) { + // Handle common type information. + ProcessAPINotes(S, D, static_cast(Info), + Metadata); +} + +/// Process API notes for an Objective-C class. +static void ProcessAPINotes(Sema &S, ObjCInterfaceDecl *D, + const api_notes::ObjCContextInfo &Info, + VersionedInfoMetadata Metadata) { + if (auto AsNonGeneric = Info.getSwiftImportAsNonGeneric()) { + handleAPINotedAttribute( + S, D, *AsNonGeneric, Metadata, [&] { + return new (S.Context) + SwiftImportAsNonGenericAttr(S.Context, getPlaceholderAttrInfo()); + }); + } + + if (auto ObjcMembers = Info.getSwiftObjCMembers()) { + handleAPINotedAttribute( + S, D, *ObjcMembers, Metadata, [&] { + return new (S.Context) + SwiftObjCMembersAttr(S.Context, getPlaceholderAttrInfo()); + }); + } + + // Handle information common to Objective-C classes and protocols. + ProcessAPINotes(S, static_cast(D), Info, + Metadata); +} + +/// If we're applying API notes with an active, non-default version, and the +/// versioned API notes have a SwiftName but the declaration normally wouldn't +/// have one, add a removal attribute to make it clear that the new SwiftName +/// attribute only applies to the active version of \p D, not to all versions. +/// +/// This must be run \em before processing API notes for \p D, because otherwise +/// any existing SwiftName attribute will have been packaged up in a +/// SwiftVersionedAdditionAttr. +template +static void maybeAttachUnversionedSwiftName( + Sema &S, Decl *D, + const api_notes::APINotesReader::VersionedInfo Info) { + if (D->hasAttr()) + return; + if (!Info.getSelected()) + return; + + // Is the active slice versioned, and does it set a Swift name? + VersionTuple SelectedVersion; + SpecificInfo SelectedInfoSlice; + std::tie(SelectedVersion, SelectedInfoSlice) = Info[*Info.getSelected()]; + if (SelectedVersion.empty()) + return; + if (SelectedInfoSlice.SwiftName.empty()) + return; + + // Does the unversioned slice /not/ set a Swift name? + for (const auto &VersionAndInfoSlice : Info) { + if (!VersionAndInfoSlice.first.empty()) + continue; + if (!VersionAndInfoSlice.second.SwiftName.empty()) + return; + } + + // Then explicitly call that out with a removal attribute. + VersionedInfoMetadata DummyFutureMetadata( + SelectedVersion, IsActive_t::Inactive, IsSubstitution_t::Replacement); + handleAPINotedAttribute( + S, D, /*add*/ false, DummyFutureMetadata, []() -> SwiftNameAttr * { + llvm_unreachable("should not try to add an attribute here"); + }); +} + +/// Processes all versions of versioned API notes. +/// +/// Just dispatches to the various ProcessAPINotes functions in this file. +template +static void ProcessVersionedAPINotes( + Sema &S, SpecificDecl *D, + const api_notes::APINotesReader::VersionedInfo Info) { + + maybeAttachUnversionedSwiftName(S, D, Info); + + unsigned Selected = Info.getSelected().value_or(Info.size()); + + VersionTuple Version; + SpecificInfo InfoSlice; + for (unsigned i = 0, e = Info.size(); i != e; ++i) { + std::tie(Version, InfoSlice) = Info[i]; + auto Active = (i == Selected) ? IsActive_t::Active : IsActive_t::Inactive; + auto Replacement = IsSubstitution_t::Original; + if (Active == IsActive_t::Inactive && Version.empty()) { + Replacement = IsSubstitution_t::Replacement; + Version = Info[Selected].first; + } + ProcessAPINotes(S, D, InfoSlice, + VersionedInfoMetadata(Version, Active, Replacement)); + } +} + +/// Process API notes that are associated with this declaration, mapping them +/// to attributes as appropriate. +void Sema::ProcessAPINotes(Decl *D) { + if (!D) + return; + + // Globals. + if (D->getDeclContext()->isFileContext() || + D->getDeclContext()->isNamespace() || + D->getDeclContext()->isExternCContext() || + D->getDeclContext()->isExternCXXContext()) { + std::optional APINotesContext; + if (auto NamespaceContext = dyn_cast(D->getDeclContext())) { + for (auto Reader : + APINotes.findAPINotes(NamespaceContext->getLocation())) { + // Retrieve the context ID for the parent namespace of the decl. + std::stack NamespaceStack; + { + for (auto CurrentNamespace = NamespaceContext; CurrentNamespace; + CurrentNamespace = + dyn_cast(CurrentNamespace->getParent())) { + if (!CurrentNamespace->isInlineNamespace()) + NamespaceStack.push(CurrentNamespace); + } + } + std::optional NamespaceID; + while (!NamespaceStack.empty()) { + auto CurrentNamespace = NamespaceStack.top(); + NamespaceStack.pop(); + NamespaceID = Reader->lookupNamespaceID(CurrentNamespace->getName(), + NamespaceID); + if (!NamespaceID) + break; + } + if (NamespaceID) + APINotesContext = api_notes::Context( + *NamespaceID, api_notes::ContextKind::Namespace); + } + } + + // Global variables. + if (auto VD = dyn_cast(D)) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { + auto Info = + Reader->lookupGlobalVariable(VD->getName(), APINotesContext); + ProcessVersionedAPINotes(*this, VD, Info); + } + + return; + } + + // Global functions. + if (auto FD = dyn_cast(D)) { + if (FD->getDeclName().isIdentifier()) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { + auto Info = + Reader->lookupGlobalFunction(FD->getName(), APINotesContext); + ProcessVersionedAPINotes(*this, FD, Info); + } + } + + return; + } + + // Objective-C classes. + if (auto Class = dyn_cast(D)) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { + auto Info = Reader->lookupObjCClassInfo(Class->getName()); + ProcessVersionedAPINotes(*this, Class, Info); + } + + return; + } + + // Objective-C protocols. + if (auto Protocol = dyn_cast(D)) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { + auto Info = Reader->lookupObjCProtocolInfo(Protocol->getName()); + ProcessVersionedAPINotes(*this, Protocol, Info); + } + + return; + } + + // Tags + if (auto Tag = dyn_cast(D)) { + std::string LookupName = Tag->getName().str(); + + // Use the source location to discern if this Tag is an OPTIONS macro. + // For now we would like to limit this trick of looking up the APINote tag + // using the EnumDecl's QualType in the case where the enum is anonymous. + // This is only being used to support APINotes lookup for C++ + // NS/CF_OPTIONS when C++-Interop is enabled. + std::string MacroName = + LookupName.empty() && Tag->getOuterLocStart().isMacroID() + ? clang::Lexer::getImmediateMacroName( + Tag->getOuterLocStart(), + Tag->getASTContext().getSourceManager(), LangOpts) + .str() + : ""; + + if (LookupName.empty() && isa(Tag) && + (MacroName == "CF_OPTIONS" || MacroName == "NS_OPTIONS" || + MacroName == "OBJC_OPTIONS" || MacroName == "SWIFT_OPTIONS")) { + + clang::QualType T = llvm::cast(Tag)->getIntegerType(); + LookupName = clang::QualType::getAsString( + T.split(), getASTContext().getPrintingPolicy()); + } + + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { + auto Info = Reader->lookupTag(LookupName, APINotesContext); + ProcessVersionedAPINotes(*this, Tag, Info); + } + + return; + } + + // Typedefs + if (auto Typedef = dyn_cast(D)) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { + auto Info = Reader->lookupTypedef(Typedef->getName(), APINotesContext); + ProcessVersionedAPINotes(*this, Typedef, Info); + } + + return; + } + } + + // Enumerators. + if (D->getDeclContext()->getRedeclContext()->isFileContext() || + D->getDeclContext()->getRedeclContext()->isExternCContext()) { + if (auto EnumConstant = dyn_cast(D)) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { + auto Info = Reader->lookupEnumConstant(EnumConstant->getName()); + ProcessVersionedAPINotes(*this, EnumConstant, Info); + } + + return; + } + } + + if (auto ObjCContainer = dyn_cast(D->getDeclContext())) { + // Location function that looks up an Objective-C context. + auto GetContext = [&](api_notes::APINotesReader *Reader) + -> std::optional { + if (auto Protocol = dyn_cast(ObjCContainer)) { + if (auto Found = Reader->lookupObjCProtocolID(Protocol->getName())) + return *Found; + + return std::nullopt; + } + + if (auto Impl = dyn_cast(ObjCContainer)) { + if (auto Cat = Impl->getCategoryDecl()) + ObjCContainer = Cat->getClassInterface(); + else + return std::nullopt; + } + + if (auto Category = dyn_cast(ObjCContainer)) { + if (Category->getClassInterface()) + ObjCContainer = Category->getClassInterface(); + else + return std::nullopt; + } + + if (auto Impl = dyn_cast(ObjCContainer)) { + if (Impl->getClassInterface()) + ObjCContainer = Impl->getClassInterface(); + else + return std::nullopt; + } + + if (auto Class = dyn_cast(ObjCContainer)) { + if (auto Found = Reader->lookupObjCClassID(Class->getName())) + return *Found; + + return std::nullopt; + } + + return std::nullopt; + }; + + // Objective-C methods. + if (auto Method = dyn_cast(D)) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { + if (auto Context = GetContext(Reader)) { + // Map the selector. + Selector Sel = Method->getSelector(); + SmallVector SelPieces; + if (Sel.isUnarySelector()) { + SelPieces.push_back(Sel.getNameForSlot(0)); + } else { + for (unsigned i = 0, n = Sel.getNumArgs(); i != n; ++i) + SelPieces.push_back(Sel.getNameForSlot(i)); + } + + api_notes::ObjCSelectorRef SelectorRef; + SelectorRef.NumArgs = Sel.getNumArgs(); + SelectorRef.Identifiers = SelPieces; + + auto Info = Reader->lookupObjCMethod(*Context, SelectorRef, + Method->isInstanceMethod()); + ProcessVersionedAPINotes(*this, Method, Info); + } + } + } + + // Objective-C properties. + if (auto Property = dyn_cast(D)) { + for (auto Reader : APINotes.findAPINotes(D->getLocation())) { + if (auto Context = GetContext(Reader)) { + bool isInstanceProperty = + (Property->getPropertyAttributesAsWritten() & + ObjCPropertyAttribute::kind_class) == 0; + auto Info = Reader->lookupObjCProperty(*Context, Property->getName(), + isInstanceProperty); + ProcessVersionedAPINotes(*this, Property, Info); + } + } + + return; + } + } +} diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 10b5c271f25c1..9fdd8eb236d1e 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -16424,6 +16424,7 @@ void Sema::ActOnFinishDelayedAttribute(Scope *S, Decl *D, if (TemplateDecl *TD = dyn_cast(D)) D = TD->getTemplatedDecl(); ProcessDeclAttributeList(S, D, Attrs); + ProcessAPINotes(D); if (CXXMethodDecl *Method = dyn_cast_or_null(D)) if (Method->isStatic()) @@ -19683,6 +19684,7 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl, CDecl->setIvarRBraceLoc(RBrac); } } + ProcessAPINotes(Record); } /// Determine whether the given integral value is representable within @@ -19997,6 +19999,7 @@ Decl *Sema::ActOnEnumConstant(Scope *S, Decl *theEnumDecl, Decl *lastEnumConst, // Process attributes. ProcessDeclAttributeList(S, New, Attrs); AddPragmaAttributes(S, New); + ProcessAPINotes(New); // Register this decl in the current scope stack. New->setAccess(TheEnumDecl->getAccess()); @@ -20195,6 +20198,7 @@ void Sema::ActOnEnumBody(SourceLocation EnumLoc, SourceRange BraceRange, QualType EnumType = Context.getTypeDeclType(Enum); ProcessDeclAttributeList(S, Enum, Attrs); + ProcessAPINotes(Enum); if (Enum->isDependentType()) { for (unsigned i = 0, e = Elements.size(); i != e; ++i) { diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 8a204b1d3b88d..c1c28a73fd79c 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -10146,6 +10146,9 @@ void Sema::ProcessDeclAttributes(Scope *S, Decl *D, const Declarator &PD) { // Apply additional attributes specified by '#pragma clang attribute'. AddPragmaAttributes(S, D); + + // Look for API notes that map to attributes. + ProcessAPINotes(D); } /// Is the given declaration allowed to use a forbidden type? diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 7c009d9c8ec09..d4e1dc67cb50a 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -11724,6 +11724,7 @@ Decl *Sema::ActOnStartNamespaceDef(Scope *NamespcScope, ProcessDeclAttributeList(DeclRegionScope, Namespc, AttrList); AddPragmaAttributes(DeclRegionScope, Namespc); + ProcessAPINotes(Namespc); // FIXME: Should we be merging attributes? if (const VisibilityAttr *Attr = Namespc->getAttr()) @@ -12270,8 +12271,10 @@ Decl *Sema::ActOnUsingDirective(Scope *S, SourceLocation UsingLoc, Diag(IdentLoc, diag::err_expected_namespace_name) << SS.getRange(); } - if (UDir) + if (UDir) { ProcessDeclAttributeList(S, UDir, AttrList); + ProcessAPINotes(UDir); + } return UDir; } @@ -13563,6 +13566,7 @@ Decl *Sema::ActOnAliasDeclaration(Scope *S, AccessSpecifier AS, ProcessDeclAttributeList(S, NewTD, AttrList); AddPragmaAttributes(S, NewTD); + ProcessAPINotes(NewTD); CheckTypedefForVariablyModifiedType(S, NewTD); Invalid |= NewTD->isInvalidDecl(); diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp index bb0d0cd2030ba..2011f4084dd2a 100644 --- a/clang/lib/Sema/SemaDeclObjC.cpp +++ b/clang/lib/Sema/SemaDeclObjC.cpp @@ -1072,6 +1072,7 @@ ObjCInterfaceDecl *Sema::ActOnStartClassInterface( ProcessDeclAttributeList(TUScope, IDecl, AttrList); AddPragmaAttributes(TUScope, IDecl); + ProcessAPINotes(IDecl); // Merge attributes from previous declarations. if (PrevIDecl) @@ -1273,6 +1274,7 @@ ObjCProtocolDecl *Sema::ActOnStartProtocolInterface( ProcessDeclAttributeList(TUScope, PDecl, AttrList); AddPragmaAttributes(TUScope, PDecl); + ProcessAPINotes(PDecl); // Merge attributes from previous declarations. if (PrevDecl) @@ -4807,6 +4809,7 @@ Decl *Sema::ActOnMethodDeclaration( // Apply the attributes to the parameter. ProcessDeclAttributeList(TUScope, Param, ArgInfo[i].ArgAttrs); AddPragmaAttributes(TUScope, Param); + ProcessAPINotes(Param); if (Param->hasAttr()) { Diag(Param->getLocation(), diag::err_block_on_nonlocal); @@ -4837,6 +4840,7 @@ Decl *Sema::ActOnMethodDeclaration( ProcessDeclAttributeList(TUScope, ObjCMethod, AttrList); AddPragmaAttributes(TUScope, ObjCMethod); + ProcessAPINotes(ObjCMethod); // Add the method now. const ObjCMethodDecl *PrevMethod = nullptr; diff --git a/clang/lib/Sema/SemaObjCProperty.cpp b/clang/lib/Sema/SemaObjCProperty.cpp index 349c7fc9c91bd..4636d89ebf2b8 100644 --- a/clang/lib/Sema/SemaObjCProperty.cpp +++ b/clang/lib/Sema/SemaObjCProperty.cpp @@ -2509,6 +2509,8 @@ void Sema::ProcessPropertyDecl(ObjCPropertyDecl *property) { GetterMethod->addAttr(SectionAttr::CreateImplicit( Context, SA->getName(), Loc, SectionAttr::GNU_section)); + ProcessAPINotes(GetterMethod); + if (getLangOpts().ObjCAutoRefCount) CheckARCMethodDecl(GetterMethod); } else @@ -2578,6 +2580,9 @@ void Sema::ProcessPropertyDecl(ObjCPropertyDecl *property) { if (const SectionAttr *SA = property->getAttr()) SetterMethod->addAttr(SectionAttr::CreateImplicit( Context, SA->getName(), Loc, SectionAttr::GNU_section)); + + ProcessAPINotes(SetterMethod); + // It's possible for the user to have set a very odd custom // setter selector that causes it to have a method family. if (getLangOpts().ObjCAutoRefCount) diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 7d3d665194add..c95c08c0422ed 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -2158,6 +2158,7 @@ DeclResult Sema::CheckClassTemplate( NewClass->startDefinition(); ProcessDeclAttributeList(S, NewClass, Attr); + ProcessAPINotes(NewClass); if (PrevClassTemplate) mergeDeclAttributes(NewClass, PrevClassTemplate->getTemplatedDecl()); @@ -9112,6 +9113,7 @@ DeclResult Sema::ActOnClassTemplateSpecialization( } ProcessDeclAttributeList(S, Specialization, Attr); + ProcessAPINotes(Specialization); // Add alignment attributes if necessary; these attributes are checked when // the ASTContext lays out the structure. @@ -10346,6 +10348,7 @@ DeclResult Sema::ActOnExplicitInstantiation( bool PreviouslyDLLExported = Specialization->hasAttr(); ProcessDeclAttributeList(S, Specialization, Attr); + ProcessAPINotes(Specialization); // Add the explicit instantiation into its lexical context. However, // since explicit instantiations are never found by name lookup, we @@ -10756,6 +10759,9 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S, Prev->setTemplateSpecializationKind(TSK, D.getIdentifierLoc()); // Merge attributes. ProcessDeclAttributeList(S, Prev, D.getDeclSpec().getAttributes()); + if (PrevTemplate) + ProcessAPINotes(Prev); + if (TSK == TSK_ExplicitInstantiationDefinition) InstantiateVariableDefinition(D.getIdentifierLoc(), Prev); } @@ -10931,6 +10937,7 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S, } ProcessDeclAttributeList(S, Specialization, D.getDeclSpec().getAttributes()); + ProcessAPINotes(Specialization); // In MSVC mode, dllimported explicit instantiation definitions are treated as // instantiation declarations.