| //===--- 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 "CheckExprLifetime.h" |
| #include "TypeLocBuilder.h" |
| #include "clang/APINotes/APINotesReader.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/DeclObjC.h" |
| #include "clang/AST/TypeLoc.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Lex/Lexer.h" |
| #include "clang/Sema/SemaObjC.h" |
| #include "clang/Sema/SemaSwift.h" |
| #include <stack> |
| |
| 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 GetModified = |
| [&](Decl *D, QualType QT, |
| NullabilityKind Nullability) -> std::optional<QualType> { |
| QualType Original = QT; |
| S.CheckImplicitNullabilityTypeSpecifier(QT, Nullability, D->getLocation(), |
| isa<ParmVarDecl>(D), |
| /*OverrideExisting=*/true); |
| return (QT.getTypePtr() != Original.getTypePtr()) ? std::optional(QT) |
| : std::nullopt; |
| }; |
| |
| if (auto Function = dyn_cast<FunctionDecl>(D)) { |
| if (auto Modified = |
| GetModified(D, Function->getReturnType(), Nullability)) { |
| const FunctionType *FnType = Function->getType()->castAs<FunctionType>(); |
| if (const FunctionProtoType *proto = dyn_cast<FunctionProtoType>(FnType)) |
| Function->setType(S.Context.getFunctionType( |
| *Modified, proto->getParamTypes(), proto->getExtProtoInfo())); |
| else |
| Function->setType( |
| S.Context.getFunctionNoProtoType(*Modified, FnType->getExtInfo())); |
| } |
| } else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) { |
| if (auto Modified = GetModified(D, Method->getReturnType(), Nullability)) { |
| Method->setReturnType(*Modified); |
| |
| // Make it a context-sensitive keyword if we can. |
| if (!isIndirectPointerType(*Modified)) |
| Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier( |
| Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability)); |
| } |
| } else if (auto Value = dyn_cast<ValueDecl>(D)) { |
| if (auto Modified = GetModified(D, Value->getType(), Nullability)) { |
| Value->setType(*Modified); |
| |
| // Make it a context-sensitive keyword if we can. |
| if (auto Parm = dyn_cast<ParmVarDecl>(D)) { |
| if (Parm->isObjCMethodParameter() && !isIndirectPointerType(*Modified)) |
| Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier( |
| Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability)); |
| } |
| } |
| } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) { |
| if (auto Modified = GetModified(D, Property->getType(), Nullability)) { |
| Property->setType(*Modified, Property->getTypeSourceInfo()); |
| |
| // Make it a property attribute if we can. |
| if (!isIndirectPointerType(*Modified)) |
| 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<char *>(mem), String.size()); |
| } |
| |
| static AttributeCommonInfo getPlaceholderAttrInfo() { |
| return AttributeCommonInfo(SourceRange(), |
| AttributeCommonInfo::UnknownAttribute, |
| {AttributeCommonInfo::AS_GNU, |
| /*Spelling*/ 0, /*IsAlignas*/ false, |
| /*IsRegularKeywordAttribute*/ false}); |
| } |
| |
| namespace { |
| template <typename A> struct AttrKindFor {}; |
| |
| #define ATTR(X) \ |
| template <> struct AttrKindFor<X##Attr> { \ |
| 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 <typename A> |
| void handleAPINotedAttribute( |
| Sema &S, Decl *D, bool IsAddition, VersionedInfoMetadata Metadata, |
| llvm::function_ref<A *()> CreateAttr, |
| llvm::function_ref<Decl::attr_iterator(const Decl *)> 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<A>::value, |
| /*IsReplacedByActive*/ Metadata.IsReplacement); |
| D->addAttr(Versioned); |
| } |
| } |
| |
| template <typename A> |
| void handleAPINotedAttribute(Sema &S, Decl *D, bool ShouldAddAttribute, |
| VersionedInfoMetadata Metadata, |
| llvm::function_ref<A *()> CreateAttr) { |
| handleAPINotedAttribute<A>( |
| S, D, ShouldAddAttribute, Metadata, CreateAttr, [](const Decl *D) { |
| return llvm::find_if(D->attrs(), |
| [](const Attr *Next) { return isa<A>(Next); }); |
| }); |
| } |
| } // namespace |
| |
| template <typename A> |
| 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<A>( |
| 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<CFReturnsRetainedAttr>(Next) || |
| isa<CFReturnsNotRetainedAttr>(Next) || |
| isa<NSReturnsRetainedAttr>(Next) || |
| isa<NSReturnsNotRetainedAttr>(Next) || |
| isa<CFAuditedTransferAttr>(Next); |
| }); |
| }); |
| } |
| |
| static void handleAPINotedRetainCountConvention( |
| Sema &S, Decl *D, VersionedInfoMetadata Metadata, |
| std::optional<api_notes::RetainCountConventionKind> Convention) { |
| if (!Convention) |
| return; |
| switch (*Convention) { |
| case api_notes::RetainCountConventionKind::None: |
| if (isa<FunctionDecl>(D)) { |
| handleAPINotedRetainCountAttribute<CFUnknownTransferAttr>( |
| S, D, /*shouldAddAttribute*/ true, Metadata); |
| } else { |
| handleAPINotedRetainCountAttribute<CFReturnsRetainedAttr>( |
| S, D, /*shouldAddAttribute*/ false, Metadata); |
| } |
| break; |
| case api_notes::RetainCountConventionKind::CFReturnsRetained: |
| handleAPINotedRetainCountAttribute<CFReturnsRetainedAttr>( |
| S, D, /*shouldAddAttribute*/ true, Metadata); |
| break; |
| case api_notes::RetainCountConventionKind::CFReturnsNotRetained: |
| handleAPINotedRetainCountAttribute<CFReturnsNotRetainedAttr>( |
| S, D, /*shouldAddAttribute*/ true, Metadata); |
| break; |
| case api_notes::RetainCountConventionKind::NSReturnsRetained: |
| handleAPINotedRetainCountAttribute<NSReturnsRetainedAttr>( |
| S, D, /*shouldAddAttribute*/ true, Metadata); |
| break; |
| case api_notes::RetainCountConventionKind::NSReturnsNotRetained: |
| handleAPINotedRetainCountAttribute<NSReturnsNotRetainedAttr>( |
| 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<UnavailableAttr>(S, D, true, Metadata, [&] { |
| return new (S.Context) |
| UnavailableAttr(S.Context, getPlaceholderAttrInfo(), |
| ASTAllocateString(S.Context, Info.UnavailableMsg)); |
| }); |
| } |
| |
| if (Info.UnavailableInSwift) { |
| handleAPINotedAttribute<AvailabilityAttr>( |
| 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, |
| /*Environment=*/nullptr); |
| }, |
| [](const Decl *D) { |
| return llvm::find_if(D->attrs(), [](const Attr *next) -> bool { |
| if (const auto *AA = dyn_cast<AvailabilityAttr>(next)) |
| if (const auto *II = AA->getPlatform()) |
| return II->isStr("swift"); |
| return false; |
| }); |
| }); |
| } |
| |
| // swift_private |
| if (auto SwiftPrivate = Info.isSwiftPrivate()) { |
| handleAPINotedAttribute<SwiftPrivateAttr>( |
| S, D, *SwiftPrivate, Metadata, [&] { |
| return new (S.Context) |
| SwiftPrivateAttr(S.Context, getPlaceholderAttrInfo()); |
| }); |
| } |
| |
| // swift_name |
| if (!Info.SwiftName.empty()) { |
| handleAPINotedAttribute<SwiftNameAttr>( |
| 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.Swift().DiagnoseName(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<SwiftBridgeAttr>( |
| 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<NSErrorDomainAttr>( |
| S, D, !NSErrorDomain->empty(), Metadata, [&] { |
| return new (S.Context) |
| NSErrorDomainAttr(S.Context, getPlaceholderAttrInfo(), |
| &S.Context.Idents.get(*NSErrorDomain)); |
| }); |
| } |
| |
| ProcessAPINotes(S, D, static_cast<const api_notes::CommonEntityInfo &>(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(), "<API Notes>", D->getLocation()); |
| if (ParsedType.isUsable()) { |
| QualType Type = Sema::GetTypeFromParser(ParsedType.get()); |
| auto TypeInfo = |
| S.Context.getTrivialTypeSourceInfo(Type, D->getLocation()); |
| |
| if (auto Var = dyn_cast<VarDecl>(D)) { |
| // Make adjustments to parameter types. |
| if (isa<ParmVarDecl>(Var)) { |
| Type = S.ObjC().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<ObjCPropertyDecl>(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<const api_notes::CommonEntityInfo &>(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<NoEscapeAttr>(S, D, *NoEscape, Metadata, [&] { |
| return new (S.Context) NoEscapeAttr(S.Context, getPlaceholderAttrInfo()); |
| }); |
| |
| if (auto Lifetimebound = Info.isLifetimebound()) |
| handleAPINotedAttribute<LifetimeBoundAttr>( |
| S, D, *Lifetimebound, Metadata, [&] { |
| return new (S.Context) |
| LifetimeBoundAttr(S.Context, getPlaceholderAttrInfo()); |
| }); |
| |
| // Retain count convention |
| handleAPINotedRetainCountConvention(S, D, Metadata, |
| Info.getRetainCountConvention()); |
| |
| // Handle common entity information. |
| ProcessAPINotes(S, D, static_cast<const api_notes::VariableInfo &>(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<const api_notes::VariableInfo &>(Info), |
| metadata); |
| } |
| |
| /// Process API notes for a C field. |
| static void ProcessAPINotes(Sema &S, FieldDecl *D, |
| const api_notes::FieldInfo &Info, |
| VersionedInfoMetadata metadata) { |
| // Handle common entity information. |
| ProcessAPINotes(S, D, static_cast<const api_notes::VariableInfo &>(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<const api_notes::VariableInfo &>(Info), |
| Metadata); |
| |
| if (auto AsAccessors = Info.getSwiftImportAsAccessors()) { |
| handleAPINotedAttribute<SwiftImportPropertyAsAccessorsAttr>( |
| S, D, *AsAccessors, Metadata, [&] { |
| return new (S.Context) SwiftImportPropertyAsAccessorsAttr( |
| S.Context, getPlaceholderAttrInfo()); |
| }); |
| } |
| } |
| |
| namespace { |
| typedef llvm::PointerUnion<FunctionDecl *, ObjCMethodDecl *> 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 = dyn_cast<FunctionDecl *>(AnyFunc); |
| Decl *D = FD; |
| ObjCMethodDecl *MD = nullptr; |
| if (!D) { |
| MD = cast<ObjCMethodDecl *>(AnyFunc); |
| D = MD; |
| } |
| |
| assert((FD || MD) && "Expecting Function or ObjCMethod"); |
| |
| // 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; |
| } |
| |
| // returns_(un)retained |
| if (!Info.SwiftReturnOwnership.empty()) |
| D->addAttr(SwiftAttrAttr::Create(S.Context, |
| "returns_" + Info.SwiftReturnOwnership)); |
| |
| // Result type override. |
| QualType OverriddenResultType; |
| if (Metadata.IsActive && !Info.ResultType.empty() && |
| S.ParseTypeFromStringCallback) { |
| auto ParsedType = S.ParseTypeFromStringCallback( |
| Info.ResultType, "<API Notes>", 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<FunctionProtoType>()) { |
| if (OverriddenResultType.isNull()) |
| OverriddenResultType = fnProtoType->getReturnType(); |
| |
| SmallVector<QualType, 4> 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<FunctionNoProtoType>(); |
| 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<const api_notes::CommonEntityInfo &>(Info), |
| Metadata); |
| } |
| |
| /// Process API notes for a C++ method. |
| static void ProcessAPINotes(Sema &S, CXXMethodDecl *Method, |
| const api_notes::CXXMethodInfo &Info, |
| VersionedInfoMetadata Metadata) { |
| if (Info.This && Info.This->isLifetimebound() && |
| !sema::implicitObjectParamIsLifetimeBound(Method)) { |
| auto MethodType = Method->getType(); |
| auto *attr = ::new (S.Context) |
| LifetimeBoundAttr(S.Context, getPlaceholderAttrInfo()); |
| QualType AttributedType = |
| S.Context.getAttributedType(attr, MethodType, MethodType); |
| TypeLocBuilder TLB; |
| TLB.pushFullCopy(Method->getTypeSourceInfo()->getTypeLoc()); |
| AttributedTypeLoc TyLoc = TLB.push<AttributedTypeLoc>(AttributedType); |
| TyLoc.setAttr(attr); |
| Method->setType(AttributedType); |
| Method->setTypeSourceInfo(TLB.getTypeSourceInfo(S.Context, AttributedType)); |
| } |
| |
| ProcessAPINotes(S, (FunctionOrMethod)Method, 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<const api_notes::FunctionInfo &>(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<const api_notes::CommonEntityInfo &>(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<ObjCDesignatedInitializerAttr>( |
| 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<const api_notes::FunctionInfo &>(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 ConformsTo = Info.SwiftConformance) |
| D->addAttr( |
| SwiftAttrAttr::Create(S.Context, "conforms_to:" + ConformsTo.value())); |
| |
| if (auto Copyable = Info.isSwiftCopyable()) { |
| if (!*Copyable) |
| D->addAttr(SwiftAttrAttr::Create(S.Context, "~Copyable")); |
| } |
| |
| if (auto Escapable = Info.isSwiftEscapable()) { |
| D->addAttr(SwiftAttrAttr::Create(S.Context, |
| *Escapable ? "Escapable" : "~Escapable")); |
| } |
| |
| if (auto Extensibility = Info.EnumExtensibility) { |
| using api_notes::EnumExtensibilityKind; |
| bool ShouldAddAttribute = (*Extensibility != EnumExtensibilityKind::None); |
| handleAPINotedAttribute<EnumExtensibilityAttr>( |
| 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<FlagEnumAttr>(S, D, *FlagEnum, Metadata, [&] { |
| return new (S.Context) FlagEnumAttr(S.Context, getPlaceholderAttrInfo()); |
| }); |
| } |
| |
| // Handle common type information. |
| ProcessAPINotes(S, D, static_cast<const api_notes::CommonTypeInfo &>(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<SwiftNewTypeAttr>( |
| 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<const api_notes::CommonTypeInfo &>(Info), |
| Metadata); |
| } |
| |
| /// Process API notes for an Objective-C class or protocol. |
| static void ProcessAPINotes(Sema &S, ObjCContainerDecl *D, |
| const api_notes::ContextInfo &Info, |
| VersionedInfoMetadata Metadata) { |
| // Handle common type information. |
| ProcessAPINotes(S, D, static_cast<const api_notes::CommonTypeInfo &>(Info), |
| Metadata); |
| } |
| |
| /// Process API notes for an Objective-C class. |
| static void ProcessAPINotes(Sema &S, ObjCInterfaceDecl *D, |
| const api_notes::ContextInfo &Info, |
| VersionedInfoMetadata Metadata) { |
| if (auto AsNonGeneric = Info.getSwiftImportAsNonGeneric()) { |
| handleAPINotedAttribute<SwiftImportAsNonGenericAttr>( |
| S, D, *AsNonGeneric, Metadata, [&] { |
| return new (S.Context) |
| SwiftImportAsNonGenericAttr(S.Context, getPlaceholderAttrInfo()); |
| }); |
| } |
| |
| if (auto ObjcMembers = Info.getSwiftObjCMembers()) { |
| handleAPINotedAttribute<SwiftObjCMembersAttr>( |
| 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<clang::ObjCContainerDecl *>(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 <typename SpecificInfo> |
| static void maybeAttachUnversionedSwiftName( |
| Sema &S, Decl *D, |
| const api_notes::APINotesReader::VersionedInfo<SpecificInfo> Info) { |
| if (D->hasAttr<SwiftNameAttr>()) |
| 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<SwiftNameAttr>( |
| 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 <typename SpecificDecl, typename SpecificInfo> |
| static void ProcessVersionedAPINotes( |
| Sema &S, SpecificDecl *D, |
| const api_notes::APINotesReader::VersionedInfo<SpecificInfo> 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)); |
| } |
| } |
| |
| static std::optional<api_notes::Context> |
| UnwindNamespaceContext(DeclContext *DC, api_notes::APINotesManager &APINotes) { |
| if (auto NamespaceContext = dyn_cast<NamespaceDecl>(DC)) { |
| for (auto Reader : APINotes.findAPINotes(NamespaceContext->getLocation())) { |
| // Retrieve the context ID for the parent namespace of the decl. |
| std::stack<NamespaceDecl *> NamespaceStack; |
| { |
| for (auto CurrentNamespace = NamespaceContext; CurrentNamespace; |
| CurrentNamespace = |
| dyn_cast<NamespaceDecl>(CurrentNamespace->getParent())) { |
| if (!CurrentNamespace->isInlineNamespace()) |
| NamespaceStack.push(CurrentNamespace); |
| } |
| } |
| std::optional<api_notes::ContextID> NamespaceID; |
| while (!NamespaceStack.empty()) { |
| auto CurrentNamespace = NamespaceStack.top(); |
| NamespaceStack.pop(); |
| NamespaceID = |
| Reader->lookupNamespaceID(CurrentNamespace->getName(), NamespaceID); |
| if (!NamespaceID) |
| return std::nullopt; |
| } |
| if (NamespaceID) |
| return api_notes::Context(*NamespaceID, |
| api_notes::ContextKind::Namespace); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| static std::optional<api_notes::Context> |
| UnwindTagContext(TagDecl *DC, api_notes::APINotesManager &APINotes) { |
| assert(DC && "tag context must not be null"); |
| for (auto Reader : APINotes.findAPINotes(DC->getLocation())) { |
| // Retrieve the context ID for the parent tag of the decl. |
| std::stack<TagDecl *> TagStack; |
| { |
| for (auto CurrentTag = DC; CurrentTag; |
| CurrentTag = dyn_cast<TagDecl>(CurrentTag->getParent())) |
| TagStack.push(CurrentTag); |
| } |
| assert(!TagStack.empty()); |
| std::optional<api_notes::Context> Ctx = |
| UnwindNamespaceContext(TagStack.top()->getDeclContext(), APINotes); |
| while (!TagStack.empty()) { |
| auto CurrentTag = TagStack.top(); |
| TagStack.pop(); |
| auto CtxID = Reader->lookupTagID(CurrentTag->getName(), Ctx); |
| if (!CtxID) |
| return std::nullopt; |
| Ctx = api_notes::Context(*CtxID, api_notes::ContextKind::Tag); |
| } |
| return Ctx; |
| } |
| return std::nullopt; |
| } |
| |
| /// Process API notes that are associated with this declaration, mapping them |
| /// to attributes as appropriate. |
| void Sema::ProcessAPINotes(Decl *D) { |
| if (!D) |
| return; |
| |
| auto *DC = D->getDeclContext(); |
| // Globals. |
| if (DC->isFileContext() || DC->isNamespace() || DC->isExternCContext() || |
| DC->isExternCXXContext()) { |
| std::optional<api_notes::Context> APINotesContext = |
| UnwindNamespaceContext(DC, APINotes); |
| // Global variables. |
| if (auto VD = dyn_cast<VarDecl>(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<FunctionDecl>(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<ObjCInterfaceDecl>(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<ObjCProtocolDecl>(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<TagDecl>(D)) { |
| // Determine the name of the entity to search for. If this is an |
| // anonymous tag that gets its linked name from a typedef, look for the |
| // typedef name. This allows tag-specific information to be added |
| // to the declaration. |
| std::string LookupName; |
| if (auto typedefName = Tag->getTypedefNameForAnonDecl()) |
| LookupName = typedefName->getName().str(); |
| else |
| 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<clang::EnumDecl>(Tag) && |
| (MacroName == "CF_OPTIONS" || MacroName == "NS_OPTIONS" || |
| MacroName == "OBJC_OPTIONS" || MacroName == "SWIFT_OPTIONS")) { |
| |
| clang::QualType T = llvm::cast<clang::EnumDecl>(Tag)->getIntegerType(); |
| LookupName = clang::QualType::getAsString( |
| T.split(), getASTContext().getPrintingPolicy()); |
| } |
| |
| for (auto Reader : APINotes.findAPINotes(D->getLocation())) { |
| if (auto ParentTag = dyn_cast<TagDecl>(Tag->getDeclContext())) |
| APINotesContext = UnwindTagContext(ParentTag, APINotes); |
| auto Info = Reader->lookupTag(LookupName, APINotesContext); |
| ProcessVersionedAPINotes(*this, Tag, Info); |
| } |
| |
| return; |
| } |
| |
| // Typedefs |
| if (auto Typedef = dyn_cast<TypedefNameDecl>(D)) { |
| for (auto Reader : APINotes.findAPINotes(D->getLocation())) { |
| auto Info = Reader->lookupTypedef(Typedef->getName(), APINotesContext); |
| ProcessVersionedAPINotes(*this, Typedef, Info); |
| } |
| |
| return; |
| } |
| } |
| |
| // Enumerators. |
| if (DC->getRedeclContext()->isFileContext() || |
| DC->getRedeclContext()->isExternCContext()) { |
| if (auto EnumConstant = dyn_cast<EnumConstantDecl>(D)) { |
| for (auto Reader : APINotes.findAPINotes(D->getLocation())) { |
| auto Info = Reader->lookupEnumConstant(EnumConstant->getName()); |
| ProcessVersionedAPINotes(*this, EnumConstant, Info); |
| } |
| |
| return; |
| } |
| } |
| |
| if (auto ObjCContainer = dyn_cast<ObjCContainerDecl>(DC)) { |
| // Location function that looks up an Objective-C context. |
| auto GetContext = [&](api_notes::APINotesReader *Reader) |
| -> std::optional<api_notes::ContextID> { |
| if (auto Protocol = dyn_cast<ObjCProtocolDecl>(ObjCContainer)) { |
| if (auto Found = Reader->lookupObjCProtocolID(Protocol->getName())) |
| return *Found; |
| |
| return std::nullopt; |
| } |
| |
| if (auto Impl = dyn_cast<ObjCCategoryImplDecl>(ObjCContainer)) { |
| if (auto Cat = Impl->getCategoryDecl()) |
| ObjCContainer = Cat->getClassInterface(); |
| else |
| return std::nullopt; |
| } |
| |
| if (auto Category = dyn_cast<ObjCCategoryDecl>(ObjCContainer)) { |
| if (Category->getClassInterface()) |
| ObjCContainer = Category->getClassInterface(); |
| else |
| return std::nullopt; |
| } |
| |
| if (auto Impl = dyn_cast<ObjCImplDecl>(ObjCContainer)) { |
| if (Impl->getClassInterface()) |
| ObjCContainer = Impl->getClassInterface(); |
| else |
| return std::nullopt; |
| } |
| |
| if (auto Class = dyn_cast<ObjCInterfaceDecl>(ObjCContainer)) { |
| if (auto Found = Reader->lookupObjCClassID(Class->getName())) |
| return *Found; |
| |
| return std::nullopt; |
| } |
| |
| return std::nullopt; |
| }; |
| |
| // Objective-C methods. |
| if (auto Method = dyn_cast<ObjCMethodDecl>(D)) { |
| for (auto Reader : APINotes.findAPINotes(D->getLocation())) { |
| if (auto Context = GetContext(Reader)) { |
| // Map the selector. |
| Selector Sel = Method->getSelector(); |
| SmallVector<StringRef, 2> 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<ObjCPropertyDecl>(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; |
| } |
| } |
| |
| if (auto TagContext = dyn_cast<TagDecl>(DC)) { |
| if (auto CXXMethod = dyn_cast<CXXMethodDecl>(D)) { |
| if (!isa<CXXConstructorDecl>(CXXMethod) && |
| !isa<CXXDestructorDecl>(CXXMethod) && |
| !isa<CXXConversionDecl>(CXXMethod) && |
| !CXXMethod->isOverloadedOperator()) { |
| for (auto Reader : APINotes.findAPINotes(D->getLocation())) { |
| if (auto Context = UnwindTagContext(TagContext, APINotes)) { |
| auto Info = |
| Reader->lookupCXXMethod(Context->id, CXXMethod->getName()); |
| ProcessVersionedAPINotes(*this, CXXMethod, Info); |
| } |
| } |
| } |
| } |
| |
| if (auto Field = dyn_cast<FieldDecl>(D)) { |
| if (!Field->isUnnamedBitField() && !Field->isAnonymousStructOrUnion()) { |
| for (auto Reader : APINotes.findAPINotes(D->getLocation())) { |
| if (auto Context = UnwindTagContext(TagContext, APINotes)) { |
| auto Info = Reader->lookupField(Context->id, Field->getName()); |
| ProcessVersionedAPINotes(*this, Field, Info); |
| } |
| } |
| } |
| } |
| |
| if (auto Tag = dyn_cast<TagDecl>(D)) { |
| for (auto Reader : APINotes.findAPINotes(D->getLocation())) { |
| if (auto Context = UnwindTagContext(TagContext, APINotes)) { |
| auto Info = Reader->lookupTag(Tag->getName(), Context); |
| ProcessVersionedAPINotes(*this, Tag, Info); |
| } |
| } |
| } |
| } |
| } |