================ @@ -0,0 +1,1014 @@ +//===--- 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 IsActive_t : bool { IsNotActive, IsActive }; +enum IsReplacement_t : bool { IsNotReplacement, IsReplacement }; + +struct VersionedInfoMetadata { + /// An empty version refers to unversioned metadata. + VersionTuple Version; + unsigned IsActive : 1; + unsigned IsReplacement : 1; + + VersionedInfoMetadata(VersionTuple Version, IsActive_t Active, + IsReplacement_t Replacement) + : Version(Version), IsActive(Active == IsActive_t::IsActive), + IsReplacement(Replacement == IsReplacement_t::IsReplacement) {} +}; +} // end anonymous namespace + +/// Determine whether this is a multi-level pointer type. +static bool isMultiLevelPointerType(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; + + QualType Type; + + // Nullability for a function/method appertains to the retain type. + if (auto Function = dyn_cast<FunctionDecl>(D)) + Type = Function->getReturnType(); + else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) + Type = Method->getReturnType(); + else if (auto Value = dyn_cast<ValueDecl>(D)) + Type = Value->getType(); + else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) + Type = Property->getType(); + else + return; + + // Check the nullability specifier on this type. + QualType OrigType = Type; + S.CheckImplicitNullabilityTypeSpecifier(Type, Nullability, D->getLocation(), + isa<ParmVarDecl>(D), + /*overrideExisting=*/true); + if (Type.getTypePtr() == OrigType.getTypePtr()) + return; + + if (auto Function = dyn_cast<FunctionDecl>(D)) { + const FunctionType *FnType = Function->getType()->castAs<FunctionType>(); + if (const FunctionProtoType *Proto = dyn_cast<FunctionProtoType>(FnType)) + Function->setType(S.Context.getFunctionType(Type, Proto->getParamTypes(), + Proto->getExtProtoInfo())); + else + Function->setType( + S.Context.getFunctionNoProtoType(Type, FnType->getExtInfo())); + } else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) { + Method->setReturnType(Type); + + // Make it a context-sensitive keyword if we can. + if (!isMultiLevelPointerType(Type)) + Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier( + Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability)); + + } else if (auto Value = dyn_cast<ValueDecl>(D)) { + Value->setType(Type); + + // Make it a context-sensitive keyword if we can. + if (auto Parm = dyn_cast<ParmVarDecl>(D)) { + if (Parm->isObjCMethodParameter() && !isMultiLevelPointerType(Type)) + Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier( + Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability)); + } + } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) { + Property->setType(Type, Property->getTypeSourceInfo()); + + // Make it a property attribute if we can. + if (!isMultiLevelPointerType(Type)) + Property->setPropertyAttributes( + ObjCPropertyAttribute::kind_null_resettable); + + } else + llvm_unreachable("cannot handle nullability here"); +} + +/// Copy a string into ASTContext-allocated memory. +static StringRef CopyString(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 getDummyAttrInfo() { + 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 ShouldAddAttribute 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 ShouldAddAttribute, 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 (ShouldAddAttribute) { + if (auto Attr = CreateAttr()) + D->addAttr(Attr); + } + + } else { + if (ShouldAddAttribute) { + 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 = CFReturnsRetainedAttr> +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, getDummyAttrInfo()); }, + [](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(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, getDummyAttrInfo(), + CopyString(S.Context, Info.UnavailableMsg)); + }); + } + + if (Info.UnavailableInSwift) { + handleAPINotedAttribute<AvailabilityAttr>( + S, D, true, Metadata, + [&] { + return new (S.Context) AvailabilityAttr( + S.Context, getDummyAttrInfo(), &S.Context.Idents.get("swift"), + VersionTuple(), VersionTuple(), VersionTuple(), + /*Unavailable=*/true, CopyString(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 { + auto *AA = dyn_cast<AvailabilityAttr>(next); + if (!AA) + return false; + const IdentifierInfo *platform = AA->getPlatform(); + if (!platform) + return false; + return platform->isStr("swift"); + }); + }); + } + + // swift_private + if (auto SwiftPrivate = Info.isSwiftPrivate()) { + handleAPINotedAttribute<SwiftPrivateAttr>( + S, D, *SwiftPrivate, Metadata, [&] { + return new (S.Context) + SwiftPrivateAttr(S.Context, getDummyAttrInfo()); + }); + } + + // 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.DiagnoseSwiftName(D, Info.SwiftName, D->getLocation(), *SNA, + /*IsAsync=*/false)) + return nullptr; + + return new (S.Context) + SwiftNameAttr(S.Context, getDummyAttrInfo(), + CopyString(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, getDummyAttrInfo(), + CopyString(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, getDummyAttrInfo(), + &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.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, getDummyAttrInfo()); + }); + + // 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 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, getDummyAttrInfo()); + }); + } +} + +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 = AnyFunc.dyn_cast<FunctionDecl *>(); + Decl *D = FD; + ObjCMethodDecl *MD = 0; ---------------- egorzhdan wrote:
Fixed https://github.com/llvm/llvm-project/pull/78445 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits