[clang] Add preliminary lifetimebound support to APINotes (PR #114830)

Gábor Horváth via cfe-commits cfe-commits at lists.llvm.org
Mon Nov 4 08:58:40 PST 2024


https://github.com/Xazax-hun created https://github.com/llvm/llvm-project/pull/114830

This patch adds the ability to mark function and method parameters as lifetimebound. Unfortunately, this does not support lifetimebound annotating 'this' (putting the annotation on the method type instead of on the parameters), or annotating constructors. For the latter, we need to support to annotate overloaded functions are ctors are always overloaded.

>From 69cdbc8d9ba737ecc524f0a8cfb61018268c160a Mon Sep 17 00:00:00 2001
From: Gabor Horvath <gaborh at apple.com>
Date: Mon, 4 Nov 2024 16:28:31 +0000
Subject: [PATCH] Add preliminary lifetimebound support to APINotes

This patch adds the ability to mark function and method parameters as
lifetimebound. Unfortunately, this does not support lifetimebound
annotating 'this' (putting the annotation on the method type instead of
on the parameters), or annotating constructors. For the latter, we need
to support to annotate overloaded functions are ctors are always
overloaded.
---
 clang/include/clang/APINotes/Types.h          | 29 ++++++++++++++++++-
 clang/lib/APINotes/APINotesFormat.h           |  2 +-
 clang/lib/APINotes/APINotesReader.cpp         |  3 ++
 clang/lib/APINotes/APINotesTypes.cpp          |  2 ++
 clang/lib/APINotes/APINotesWriter.cpp         |  6 ++++
 clang/lib/APINotes/APINotesYAMLCompiler.cpp   |  3 ++
 clang/lib/Sema/SemaAPINotes.cpp               | 25 ++++++++++------
 .../Inputs/Headers/Lifetimebound.apinotes     | 14 +++++++++
 .../APINotes/Inputs/Headers/Lifetimebound.h   |  8 +++++
 .../APINotes/Inputs/Headers/module.modulemap  |  5 ++++
 clang/test/APINotes/lifetimebound.cpp         | 13 +++++++++
 11 files changed, 99 insertions(+), 11 deletions(-)
 create mode 100644 clang/test/APINotes/Inputs/Headers/Lifetimebound.apinotes
 create mode 100644 clang/test/APINotes/Inputs/Headers/Lifetimebound.h
 create mode 100644 clang/test/APINotes/lifetimebound.cpp

diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h
index 89889910d1a073..6327b7d75486fc 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -425,6 +425,14 @@ class ParamInfo : public VariableInfo {
   LLVM_PREFERRED_TYPE(bool)
   unsigned NoEscape : 1;
 
+  /// Whether lifetimebound was specified.
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned LifetimeboundSpecified : 1;
+
+  /// Whether the this parameter has the 'lifetimebound' attribute.
+  LLVM_PREFERRED_TYPE(bool)
+  unsigned Lifetimebound : 1;
+
   /// A biased RetainCountConventionKind, where 0 means "unspecified".
   ///
   /// Only relevant for out-parameters.
@@ -432,7 +440,9 @@ class ParamInfo : public VariableInfo {
 
 public:
   ParamInfo()
-      : NoEscapeSpecified(false), NoEscape(false), RawRetainCountConvention() {}
+      : NoEscapeSpecified(false), NoEscape(false),
+        LifetimeboundSpecified(false), Lifetimebound(false),
+        RawRetainCountConvention() {}
 
   std::optional<bool> isNoEscape() const {
     if (!NoEscapeSpecified)
@@ -444,6 +454,16 @@ class ParamInfo : public VariableInfo {
     NoEscape = Value.value_or(false);
   }
 
+  std::optional<bool> isLifetimebound() const {
+    if (!LifetimeboundSpecified)
+      return std::nullopt;
+    return Lifetimebound;
+  }
+  void setLifetimebound(std::optional<bool> Value) {
+    LifetimeboundSpecified = Value.has_value();
+    Lifetimebound = Value.value_or(false);
+  }
+
   std::optional<RetainCountConventionKind> getRetainCountConvention() const {
     if (!RawRetainCountConvention)
       return std::nullopt;
@@ -463,6 +483,11 @@ class ParamInfo : public VariableInfo {
       NoEscape = RHS.NoEscape;
     }
 
+    if (!LifetimeboundSpecified && RHS.LifetimeboundSpecified) {
+      LifetimeboundSpecified = true;
+      Lifetimebound = RHS.Lifetimebound;
+    }
+
     if (!RawRetainCountConvention)
       RawRetainCountConvention = RHS.RawRetainCountConvention;
 
@@ -478,6 +503,8 @@ inline bool operator==(const ParamInfo &LHS, const ParamInfo &RHS) {
   return static_cast<const VariableInfo &>(LHS) == RHS &&
          LHS.NoEscapeSpecified == RHS.NoEscapeSpecified &&
          LHS.NoEscape == RHS.NoEscape &&
+         LHS.LifetimeboundSpecified == RHS.LifetimeboundSpecified &&
+         LHS.Lifetimebound == RHS.Lifetimebound &&
          LHS.RawRetainCountConvention == RHS.RawRetainCountConvention;
 }
 
diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h
index d724a9ea471b54..014ee7e2e3d397 100644
--- a/clang/lib/APINotes/APINotesFormat.h
+++ b/clang/lib/APINotes/APINotesFormat.h
@@ -24,7 +24,7 @@ const uint16_t VERSION_MAJOR = 0;
 /// API notes file minor version number.
 ///
 /// When the format changes IN ANY WAY, this number should be incremented.
-const uint16_t VERSION_MINOR = 30; // fields
+const uint16_t VERSION_MINOR = 31; // lifetimebound
 
 const uint8_t kSwiftCopyable = 1;
 const uint8_t kSwiftNonCopyable = 2;
diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index da8a86f7adb16e..1bde8482fce033 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -331,6 +331,9 @@ void ReadParamInfo(const uint8_t *&Data, ParamInfo &Info) {
     Info.setRetainCountConvention(Convention);
   }
   Payload >>= 3;
+  if (Payload & 0x01)
+    Info.setLifetimebound(Payload & 0x02);
+  Payload >>= 2;
   if (Payload & 0x01)
     Info.setNoEscape(Payload & 0x02);
   Payload >>= 2;
diff --git a/clang/lib/APINotes/APINotesTypes.cpp b/clang/lib/APINotes/APINotesTypes.cpp
index a87ecb3bc30eec..be7dec3295b4ce 100644
--- a/clang/lib/APINotes/APINotesTypes.cpp
+++ b/clang/lib/APINotes/APINotesTypes.cpp
@@ -65,6 +65,8 @@ LLVM_DUMP_METHOD void ParamInfo::dump(llvm::raw_ostream &OS) const {
   static_cast<const VariableInfo &>(*this).dump(OS);
   if (NoEscapeSpecified)
     OS << (NoEscape ? "[NoEscape] " : "");
+  if (LifetimeboundSpecified)
+    OS << (Lifetimebound ? "[Lifetimebound] " : "");
   OS << "RawRetainCountConvention: " << RawRetainCountConvention << ' ';
   OS << '\n';
 }
diff --git a/clang/lib/APINotes/APINotesWriter.cpp b/clang/lib/APINotes/APINotesWriter.cpp
index a2b3669a314476..d81394edfde304 100644
--- a/clang/lib/APINotes/APINotesWriter.cpp
+++ b/clang/lib/APINotes/APINotesWriter.cpp
@@ -1052,6 +1052,12 @@ void emitParamInfo(raw_ostream &OS, const ParamInfo &PI) {
     if (*noescape)
       flags |= 0x02;
   }
+  flags <<= 2;
+  if (auto lifetimebound = PI.isLifetimebound()) {
+    flags |= 0x01;
+    if (*lifetimebound)
+      flags |= 0x02;
+  }
   flags <<= 3;
   if (auto RCC = PI.getRetainCountConvention())
     flags |= static_cast<uint8_t>(RCC.value()) + 1;
diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
index f72a1d65b5456f..11578e4d054b7f 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -70,6 +70,7 @@ namespace {
 struct Param {
   unsigned Position;
   std::optional<bool> NoEscape = false;
+  std::optional<bool> Lifetimebound = false;
   std::optional<NullabilityKind> Nullability;
   std::optional<RetainCountConventionKind> RetainCountConvention;
   StringRef Type;
@@ -121,6 +122,7 @@ template <> struct MappingTraits<Param> {
     IO.mapOptional("Nullability", P.Nullability, std::nullopt);
     IO.mapOptional("RetainCountConvention", P.RetainCountConvention);
     IO.mapOptional("NoEscape", P.NoEscape);
+    IO.mapOptional("Lifetimebound", P.Lifetimebound);
     IO.mapOptional("Type", P.Type, StringRef(""));
   }
 };
@@ -734,6 +736,7 @@ class YAMLConverter {
       if (P.Nullability)
         PI.setNullabilityAudited(*P.Nullability);
       PI.setNoEscape(P.NoEscape);
+      PI.setLifetimebound(P.Lifetimebound);
       PI.setType(std::string(P.Type));
       PI.setRetainCountConvention(P.RetainCountConvention);
       if (OutInfo.Params.size() <= P.Position)
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index ec43a0def9c1e6..edb4c19d59745c 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -12,6 +12,7 @@
 
 #include "clang/APINotes/APINotesReader.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Lex/Lexer.h"
@@ -415,6 +416,13 @@ static void ProcessAPINotes(Sema &S, ParmVarDecl *D,
       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());
@@ -860,13 +868,12 @@ void Sema::ProcessAPINotes(Decl *D) {
   if (!D)
     return;
 
+  auto *DC = D->getDeclContext();
   // Globals.
-  if (D->getDeclContext()->isFileContext() ||
-      D->getDeclContext()->isNamespace() ||
-      D->getDeclContext()->isExternCContext() ||
-      D->getDeclContext()->isExternCXXContext()) {
+  if (DC->isFileContext() || DC->isNamespace() || DC->isExternCContext() ||
+      DC->isExternCXXContext()) {
     std::optional<api_notes::Context> APINotesContext =
-        UnwindNamespaceContext(D->getDeclContext(), APINotes);
+        UnwindNamespaceContext(DC, APINotes);
     // Global variables.
     if (auto VD = dyn_cast<VarDecl>(D)) {
       for (auto Reader : APINotes.findAPINotes(D->getLocation())) {
@@ -967,8 +974,8 @@ void Sema::ProcessAPINotes(Decl *D) {
   }
 
   // Enumerators.
-  if (D->getDeclContext()->getRedeclContext()->isFileContext() ||
-      D->getDeclContext()->getRedeclContext()->isExternCContext()) {
+  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());
@@ -979,7 +986,7 @@ void Sema::ProcessAPINotes(Decl *D) {
     }
   }
 
-  if (auto ObjCContainer = dyn_cast<ObjCContainerDecl>(D->getDeclContext())) {
+  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> {
@@ -1063,7 +1070,7 @@ void Sema::ProcessAPINotes(Decl *D) {
     }
   }
 
-  if (auto TagContext = dyn_cast<TagDecl>(D->getDeclContext())) {
+  if (auto TagContext = dyn_cast<TagDecl>(DC)) {
     if (auto CXXMethod = dyn_cast<CXXMethodDecl>(D)) {
       if (!isa<CXXConstructorDecl>(CXXMethod) &&
           !isa<CXXDestructorDecl>(CXXMethod) &&
diff --git a/clang/test/APINotes/Inputs/Headers/Lifetimebound.apinotes b/clang/test/APINotes/Inputs/Headers/Lifetimebound.apinotes
new file mode 100644
index 00000000000000..d07d87cf02f025
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/Lifetimebound.apinotes
@@ -0,0 +1,14 @@
+---
+Name: Lifetimebound
+Functions:
+  - Name:              funcToAnnotate
+    Parameters:
+      - Position:      0
+        Lifetimebound: true
+Tags:
+- Name: MyClass
+  Methods:
+    - Name: methodToAnnotate
+      Parameters:
+        - Position:      0
+          Lifetimebound: true
diff --git a/clang/test/APINotes/Inputs/Headers/Lifetimebound.h b/clang/test/APINotes/Inputs/Headers/Lifetimebound.h
new file mode 100644
index 00000000000000..2ec302f7801a77
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/Lifetimebound.h
@@ -0,0 +1,8 @@
+int *funcToAnnotate(int *p);
+
+// TODO: support annotating ctors and 'this'.
+struct MyClass {
+    MyClass(int*);
+    int *annotateThis();
+    int *methodToAnnotate(int *p);
+};
diff --git a/clang/test/APINotes/Inputs/Headers/module.modulemap b/clang/test/APINotes/Inputs/Headers/module.modulemap
index 7d304863398ecf..31f7d36356d83e 100644
--- a/clang/test/APINotes/Inputs/Headers/module.modulemap
+++ b/clang/test/APINotes/Inputs/Headers/module.modulemap
@@ -17,6 +17,11 @@ module Fields {
   export *
 }
 
+module Lifetimebound {
+  header "Lifetimebound.h"
+  export *
+}
+
 module HeaderLib {
   header "HeaderLib.h"
 }
diff --git a/clang/test/APINotes/lifetimebound.cpp b/clang/test/APINotes/lifetimebound.cpp
new file mode 100644
index 00000000000000..3e5ce9df895b70
--- /dev/null
+++ b/clang/test/APINotes/lifetimebound.cpp
@@ -0,0 +1,13 @@
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Lifetimebound -fdisable-module-hash -fapinotes-modules -fsyntax-only -I %S/Inputs/Headers %s -x c++
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Lifetimebound -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter funcToAnnotate -x c++ | FileCheck --check-prefix=CHECK-PARAM %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Lifetimebound -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter methodToAnnotate -x c++ | FileCheck --check-prefix=CHECK-METHOD %s
+#include "Lifetimebound.h"
+
+// CHECK-PARAM: FunctionDecl {{.+}} funcToAnnotate 
+// CHECK-PARAM-NEXT: ParmVarDecl {{.+}} p
+// CHECK-PARAM-NEXT: LifetimeBoundAttr
+
+// CHECK-METHOD: CXXMethodDecl {{.+}} methodToAnnotate 
+// CHECK-METHOD-NEXT: ParmVarDecl {{.+}} p
+// CHECK-METHOD-NEXT: LifetimeBoundAttr



More information about the cfe-commits mailing list