[clang] [APINotes] Upstream Sema logic to apply API Notes to decls (PR #78445)

Saleem Abdulrasool via cfe-commits cfe-commits at lists.llvm.org
Sat Feb 24 17:15:43 PST 2024


================
@@ -0,0 +1,989 @@
+//===--- 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 IsReplacement_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,
+                        IsReplacement_t Replacement)
+      : Version(Version), IsActive(Active == IsActive_t::Active),
+        IsReplacement(Replacement == IsReplacement_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 IsUnmodified = [&](Decl *D, QualType QT,
+                          NullabilityKind Nullability) -> bool {
+    QualType Original = QT;
+    S.CheckImplicitNullabilityTypeSpecifier(QT, Nullability, D->getLocation(),
+                                            isa<ParmVarDecl>(D),
+                                            /*OverrideExisting=*/true);
+    return QT.getTypePtr() == Original.getTypePtr();
+  };
+
+  if (auto Function = dyn_cast<FunctionDecl>(D)) {
+    if (!IsUnmodified(D, Function->getReturnType(), Nullability)) {
+      QualType FnType = Function->getType();
+      Function->setType(FnType);
+    }
+  } else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) {
+    QualType Type = Method->getReturnType();
+    if (!IsUnmodified(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<ValueDecl>(D)) {
+    QualType Type = Value->getType();
+    if (!IsUnmodified(D, Type, Nullability)) {
+      Value->setType(Type);
+
+      // Make it a context-sensitive keyword if we can.
+      if (auto Parm = dyn_cast<ParmVarDecl>(D)) {
+        if (Parm->isObjCMethodParameter() && !isIndirectPointerType(Type))
+          Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
+              Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
+      }
+    }
+  } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
+    QualType Type = Property->getType();
+    if (!IsUnmodified(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<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);
+    }
+
+  } else {
+    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 = 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, 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(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);
+        },
+        [](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.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<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;
----------------
compnerd wrote:

An early return feels like it is slightly beneficial.

```suggestion
  if (S.Context.getTypeSize(OrigType) ==
      S.Context.getTypeSize(ReplacementType))
    return false;

  S.Diag(Loc, diag::err_incompatible_replacement_type)
      << ReplacementType << OrigType;
  return true;
```

https://github.com/llvm/llvm-project/pull/78445


More information about the cfe-commits mailing list