Skip to content

[clang] Add preliminary lifetimebound support to APINotes #114830

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 4, 2024

Conversation

Xazax-hun
Copy link
Collaborator

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.

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.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Nov 4, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 4, 2024

@llvm/pr-subscribers-clang

Author: Gábor Horváth (Xazax-hun)

Changes

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.


Full diff: https://github.com/llvm/llvm-project/pull/114830.diff

11 Files Affected:

  • (modified) clang/include/clang/APINotes/Types.h (+28-1)
  • (modified) clang/lib/APINotes/APINotesFormat.h (+1-1)
  • (modified) clang/lib/APINotes/APINotesReader.cpp (+3)
  • (modified) clang/lib/APINotes/APINotesTypes.cpp (+2)
  • (modified) clang/lib/APINotes/APINotesWriter.cpp (+6)
  • (modified) clang/lib/APINotes/APINotesYAMLCompiler.cpp (+3)
  • (modified) clang/lib/Sema/SemaAPINotes.cpp (+16-9)
  • (added) clang/test/APINotes/Inputs/Headers/Lifetimebound.apinotes (+14)
  • (added) clang/test/APINotes/Inputs/Headers/Lifetimebound.h (+8)
  • (modified) clang/test/APINotes/Inputs/Headers/module.modulemap (+5)
  • (added) clang/test/APINotes/lifetimebound.cpp (+13)
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

Copy link
Contributor

@egorzhdan egorzhdan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@Xazax-hun Xazax-hun changed the title Add preliminary lifetimebound support to APINotes [clang] Add preliminary lifetimebound support to APINotes Nov 4, 2024
Comment on lines +458 to +460
if (!LifetimeboundSpecified)
return std::nullopt;
return Lifetimebound;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!LifetimeboundSpecified)
return std::nullopt;
return Lifetimebound;
return LifetimeboundSpecified ? Lifetimebound : std::nullopt;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, sorry. I forgot about this comment. I will incorporate this into a follow up PR. Thanks a lot for the review!

@Xazax-hun Xazax-hun merged commit 7ac78f1 into llvm:main Nov 4, 2024
11 checks passed
@DougGregor
Copy link
Contributor

This PR looks great. About this...

Unfortunately, this does not support lifetimebound annotating 'this'

I suppose we could model this either by duplicating fields like LifetimeBound on methods in API notes, or deciding that a parameter with index -1 means "this" (or "self" in Objective-C).

PhilippRados pushed a commit to PhilippRados/llvm-project that referenced this pull request Nov 6, 2024
Xazax-hun added a commit to swiftlang/llvm-project that referenced this pull request Nov 8, 2024
Xazax-hun added a commit to swiftlang/llvm-project that referenced this pull request Nov 11, 2024
[clang] Add preliminary lifetimebound support to APINotes (llvm#114830)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants