[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-17 Thread Utkarsh Saxena via cfe-commits


@@ -249,7 +254,7 @@ static void 
visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
   LocalVisitor Visit);
 
 template  static bool isRecordWithAttr(QualType Type) {
-  if (auto *RD = Type->getAsCXXRecordDecl())
+  if (auto *RD = Type.getNonReferenceType()->getAsCXXRecordDecl())

usx95 wrote:

I will remove this for now from this PR and leave the tests with a FIXME. 
Probably needs a separate PR to keep this PR simpler.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-17 Thread Utkarsh Saxena via cfe-commits


@@ -3229,6 +3230,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);

usx95 wrote:

I feel the existing code is easier to follow. 

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-17 Thread Utkarsh Saxena via cfe-commits


@@ -249,7 +254,7 @@ static void 
visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
   LocalVisitor Visit);
 
 template  static bool isRecordWithAttr(QualType Type) {
-  if (auto *RD = Type->getAsCXXRecordDecl())
+  if (auto *RD = Type.getNonReferenceType()->getAsCXXRecordDecl())

usx95 wrote:

I will remove this for now from this PR and leave the tests with a FIXME. 
Probably needs a separate PR to keep this PR simpler.
It is not possible to do before this PR because I do not have 
non-lifetime_capture_by tests for it.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-17 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 edited https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -0,0 +1,220 @@
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+struct X {
+  const int *x;
+  void captureInt(const int& x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int &i [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValInt(int &&i [[clang::lifetime_capture_by(x)]], X &x);
+void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X &x);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view s [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValSV(std::string_view &&sv [[clang::lifetime_capture_by(x)]], X 
&x);
+void noCaptureSV(std::string_view sv, X &x);
+void captureS(const std::string &s [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValS(std::string &&s [[clang::lifetime_capture_by(x)]], X &x);
+
+const std::string& getLB(const std::string &s [[clang::lifetimebound]]);
+const std::string& getLB(std::string_view sv [[clang::lifetimebound]]);
+const std::string* getPointerLB(const std::string &s [[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string &s);
+
+void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], X 
&x);
+
+struct ThisIsCaptured {
+  void capture(X &x) [[clang::lifetime_capture_by(x)]];
+};
+
+void captureByGlobal(std::string_view s 
[[clang::lifetime_capture_by(global)]]);
+void captureByUnknown(std::string_view s 
[[clang::lifetime_capture_by(unknown)]]);
+
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  X x;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}}
+x);
+  captureRValInt(1, x); // expected-warning {{object whose reference is 
captured by 'x'}}
+  captureInt(local, x);
+  noCaptureInt(1, x);
+  noCaptureInt(local, x);
+
+  // Capture using std::string_view.
+  captureSV(local_sv, x);
+  captureSV(std::string(), // expected-warning {{object whose reference is 
captured by 'x'}}
+x);
+  captureSV(substr(
+  std::string() // expected-warning {{object whose reference is captured 
by 'x'}}
+  ), x);
+  captureSV(substr(local_s), x);
+  captureSV(strcopy(std::string()), x);

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -3229,6 +3230,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);
+  };
+  for (unsigned I = 0; I < FD->getNumParams(); ++I) {
+auto *CapturedByAttr =
+FD->getParamDecl(I)->getAttr();
+if (!CapturedByAttr)
+  continue;
+for (int CapturingParamIdx : CapturedByAttr->params()) {
+  Expr *Capturing = GetArgAt(CapturingParamIdx);
+  Expr *Captured = GetArgAt(I + IsMemberFunction);
+  CapturingEntity CE{Capturing};
+  // Ensure that 'Captured' outlives the 'Capturing' entity.
+  checkExprLifetime(*this, CE, Captured);
+}
+  }
+  // Check when the 'this' object is captured.

usx95 wrote:

Yes.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -3229,6 +3230,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);
+  };
+  for (unsigned I = 0; I < FD->getNumParams(); ++I) {
+auto *CapturedByAttr =
+FD->getParamDecl(I)->getAttr();
+if (!CapturedByAttr)
+  continue;
+for (int CapturingParamIdx : CapturedByAttr->params()) {
+  Expr *Capturing = GetArgAt(CapturingParamIdx);
+  Expr *Captured = GetArgAt(I + IsMemberFunction);

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -1411,18 +1437,34 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
 // warnings or errors on inner temporaries within this one's initializer.
 return false;
   };
-

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -3925,13 +3925,20 @@ def LifetimeCaptureByDocs : Documentation {
 parameter or implicit object parameter indicates that that objects that are 
referred to

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -3925,13 +3925,20 @@ def LifetimeCaptureByDocs : Documentation {
 parameter or implicit object parameter indicates that that objects that are 
referred to
 by that parameter may also be referred to by the capturing entity ``X``.
 
-By default, a reference is considered to refer to its referenced object, a
-pointer is considered to refer to its pointee, a ``std::initializer_list``
-is considered to refer to its underlying array, and aggregates (arrays and
-simple ``struct``\s) are considered to refer to all objects that their
-transitive subobjects refer to.
+By default:
+
+- A reference is considered to refer to its referenced object.
+- A pointer is considered to refer to its pointee.
+- A ``std::initializer_list`` is considered to refer to its underlying 
array.
+- Aggregates (arrays and simple ``struct``\s) are considered to refer to all
+  objects that their transitive subobjects refer to.
+- View types (types annotated with [[``gsl::Pointer()``]]) are considered to 
refer

usx95 wrote:

Oops. Thanks.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -3983,6 +3990,21 @@ The attribute supports specifying more than one 
capturing entities:
 s2.insert(a);
   }
 
+Currently clang would diagnose when a temporary is used as an argument to a

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -3951,7 +3958,7 @@ The capturing entity ``X`` can be one of the following:
   std::set s;
 };
 
-- 'global', 'unknown' (without quotes).
+- `global`, `unknown`.

usx95 wrote:

We would give an error:
'lifetime_capture_by' attribute argument "global" is not a known function 
parameter; must be a function parameter, 'this', 'global' or 'unknown'.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -1411,18 +1437,34 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
 // warnings or errors on inner temporaries within this one's initializer.
 return false;
   };
-

bricknerb wrote:

Bring back the blank line after the visitor definition.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 edited https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -1420,9 +1446,18 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
  ? IndirectLocalPathEntry::LifetimeBoundCall
  : IndirectLocalPathEntry::GslPointerAssignment,
  Init});
+  } else if (LK == LK_LifetimeCapture) {

usx95 wrote:

You might not be looking at the latest changes. This was switched to a switch :)

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -3229,6 +3230,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);
+  };
+  for (unsigned I = 0; I < FD->getNumParams(); ++I) {
+auto *CapturedByAttr =
+FD->getParamDecl(I)->getAttr();
+if (!CapturedByAttr)
+  continue;
+for (int CapturingParamIdx : CapturedByAttr->params()) {
+  Expr *Capturing = GetArgAt(CapturingParamIdx);
+  Expr *Captured = GetArgAt(I + IsMemberFunction);
+  CapturingEntity CE{Capturing};
+  // Ensure that 'Captured' outlives the 'Capturing' entity.
+  checkExprLifetime(*this, CE, Captured);
+}
+  }
+  // Check when the 'this' object is captured.

bricknerb wrote:

Do you mean the attribute is on the implicit object param?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -1110,13 +1117,14 @@ static bool shouldRunGSLAssignmentAnalysis(const Sema 
&SemaRef,
isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
 }
 
-static void checkExprLifetimeImpl(Sema &SemaRef,
-  const InitializedEntity *InitEntity,
-  const InitializedEntity *ExtendingEntity,
-  LifetimeKind LK,
-  const AssignedEntity *AEntity, Expr *Init) {
-  assert((AEntity && LK == LK_Assignment) ||
- (InitEntity && LK != LK_Assignment));
+static void
+checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
+  const InitializedEntity *ExtendingEntity, LifetimeKind 
LK,
+  const AssignedEntity *AEntity,
+  const CapturingEntity *CapEntity, Expr *Init) {
+  assert(!AEntity || LK == LK_Assignment);
+  assert(!CapEntity || LK == LK_LifetimeCapture);
+  assert(!InitEntity || (LK != LK_Assignment && LK != LK_LifetimeCapture));

bricknerb wrote:

I feel that trying to handle all use cases in this method makes the logic much 
more complex.
The fact you have to keep in mind the different cases when following the logic 
here makes it harder to verify correctness and harder to change in the future.
Could we split this, while reusing the shared parts?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -0,0 +1,220 @@
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+struct X {
+  const int *x;
+  void captureInt(const int& x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int &i [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValInt(int &&i [[clang::lifetime_capture_by(x)]], X &x);
+void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X &x);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view s [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValSV(std::string_view &&sv [[clang::lifetime_capture_by(x)]], X 
&x);
+void noCaptureSV(std::string_view sv, X &x);
+void captureS(const std::string &s [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValS(std::string &&s [[clang::lifetime_capture_by(x)]], X &x);
+
+const std::string& getLB(const std::string &s [[clang::lifetimebound]]);
+const std::string& getLB(std::string_view sv [[clang::lifetimebound]]);
+const std::string* getPointerLB(const std::string &s [[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string &s);
+
+void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], X 
&x);
+
+struct ThisIsCaptured {
+  void capture(X &x) [[clang::lifetime_capture_by(x)]];
+};
+
+void captureByGlobal(std::string_view s 
[[clang::lifetime_capture_by(global)]]);
+void captureByUnknown(std::string_view s 
[[clang::lifetime_capture_by(unknown)]]);
+
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  X x;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}}
+x);
+  captureRValInt(1, x); // expected-warning {{object whose reference is 
captured by 'x'}}
+  captureInt(local, x);
+  noCaptureInt(1, x);
+  noCaptureInt(local, x);
+
+  // Capture using std::string_view.
+  captureSV(local_sv, x);
+  captureSV(std::string(), // expected-warning {{object whose reference is 
captured by 'x'}}
+x);
+  captureSV(substr(
+  std::string() // expected-warning {{object whose reference is captured 
by 'x'}}
+  ), x);
+  captureSV(substr(local_s), x);
+  captureSV(strcopy(std::string()), x);

bricknerb wrote:

I think this will be much more readable if SV would be replaced by StringView.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -3229,6 +3230,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);
+  };
+  for (unsigned I = 0; I < FD->getNumParams(); ++I) {
+auto *CapturedByAttr =
+FD->getParamDecl(I)->getAttr();
+if (!CapturedByAttr)
+  continue;
+for (int CapturingParamIdx : CapturedByAttr->params()) {
+  Expr *Capturing = GetArgAt(CapturingParamIdx);
+  Expr *Captured = GetArgAt(I + IsMemberFunction);

bricknerb wrote:

It seems like this can be moved outside of this loop.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -0,0 +1,220 @@
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+struct X {
+  const int *x;
+  void captureInt(const int& x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int &i [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValInt(int &&i [[clang::lifetime_capture_by(x)]], X &x);
+void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X &x);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view s [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValSV(std::string_view &&sv [[clang::lifetime_capture_by(x)]], X 
&x);
+void noCaptureSV(std::string_view sv, X &x);
+void captureS(const std::string &s [[clang::lifetime_capture_by(x)]], X &x);

bricknerb wrote:

Perhaps replace "S" with "String" to make this more readable?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -3925,13 +3925,20 @@ def LifetimeCaptureByDocs : Documentation {
 parameter or implicit object parameter indicates that that objects that are 
referred to
 by that parameter may also be referred to by the capturing entity ``X``.
 
-By default, a reference is considered to refer to its referenced object, a
-pointer is considered to refer to its pointee, a ``std::initializer_list``
-is considered to refer to its underlying array, and aggregates (arrays and
-simple ``struct``\s) are considered to refer to all objects that their
-transitive subobjects refer to.
+By default:
+
+- A reference is considered to refer to its referenced object.
+- A pointer is considered to refer to its pointee.
+- A ``std::initializer_list`` is considered to refer to its underlying 
array.
+- Aggregates (arrays and simple ``struct``\s) are considered to refer to all
+  objects that their transitive subobjects refer to.
+- View types (types annotated with [[``gsl::Pointer()``]]) are considered to 
refer

bricknerb wrote:

Shouldn't the `` be around the [[...]] ?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -3951,7 +3958,7 @@ The capturing entity ``X`` can be one of the following:
   std::set s;
 };
 
-- 'global', 'unknown' (without quotes).
+- `global`, `unknown`.

bricknerb wrote:

What happens when the parameters are named "global" or "unknown"?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -3229,6 +3230,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);

bricknerb wrote:

How about merging the two return statements to one to share const cast?

return const_cast(IsMemberFunction && Idx == 0 ? ... : ...);

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits

https://github.com/bricknerb requested changes to this pull request.


https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -0,0 +1,220 @@
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+struct X {
+  const int *x;
+  void captureInt(const int& x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int &i [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValInt(int &&i [[clang::lifetime_capture_by(x)]], X &x);
+void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X &x);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view s [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValSV(std::string_view &&sv [[clang::lifetime_capture_by(x)]], X 
&x);
+void noCaptureSV(std::string_view sv, X &x);
+void captureS(const std::string &s [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValS(std::string &&s [[clang::lifetime_capture_by(x)]], X &x);
+
+const std::string& getLB(const std::string &s [[clang::lifetimebound]]);
+const std::string& getLB(std::string_view sv [[clang::lifetimebound]]);
+const std::string* getPointerLB(const std::string &s [[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string &s);
+
+void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], X 
&x);
+
+struct ThisIsCaptured {
+  void capture(X &x) [[clang::lifetime_capture_by(x)]];
+};
+
+void captureByGlobal(std::string_view s 
[[clang::lifetime_capture_by(global)]]);
+void captureByUnknown(std::string_view s 
[[clang::lifetime_capture_by(unknown)]]);
+
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  X x;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object whose reference is captured by 
'x' will be destroyed at the end of the full-expression}}
+x);
+  captureRValInt(1, x); // expected-warning {{object whose reference is 
captured by 'x'}}
+  captureInt(local, x);
+  noCaptureInt(1, x);
+  noCaptureInt(local, x);
+
+  // Capture using std::string_view.
+  captureSV(local_sv, x);
+  captureSV(std::string(), // expected-warning {{object whose reference is 
captured by 'x'}}
+x);
+  captureSV(substr(
+  std::string() // expected-warning {{object whose reference is captured 
by 'x'}}
+  ), x);
+  captureSV(substr(local_s), x);
+  captureSV(strcopy(std::string()), x);
+  captureRValSV(std::move(local_sv), x);
+  captureRValSV(std::string(), x); // expected-warning {{object whose 
reference is captured by 'x'}}
+  captureRValSV(std::string_view{"abcd"}, x);
+  captureRValSV(substr(local_s), x);
+  captureRValSV(substr(std::string()), x); // expected-warning {{object whose 
reference is captured by 'x'}}
+  captureRValSV(strcopy(std::string()), x);
+  noCaptureSV(local_sv, x);
+  noCaptureSV(std::string(), x);
+  noCaptureSV(substr(std::string()), x);
+
+  // Capture using std::string.
+  captureS(std::string(), x); // expected-warning {{object whose reference is 
captured by 'x'}}
+  captureS(local_s, x);
+  captureRValS(std::move(local_s), x);
+  captureRValS(std::string(), x); // expected-warning {{object whose reference 
is captured by 'x'}}
+
+  // Capture with lifetimebound.
+  captureSV(getLB(std::string()), x); // expected-warning {{object whose 
reference is captured by 'x'}}
+  captureSV(getLB(substr(std::string())), x); // expected-warning {{object 
whose reference is captured by 'x'}}
+  captureSV(getLB(getLB(
+std::string()  // expected-warning {{object whose reference is captured by 
'x'}}
+)), x);
+  capturePointer(getPointerLB(std::string()), x); // expected-warning {{object 
whose reference is captured by 'x'}}
+  capturePointer(getPointerLB(*getPointerLB(
+std::string()  // expected-warning {{object whose reference is captured by 
'x'}}
+)), x);
+  capturePointer(getPointerNoLB(std::string()), x);
+
+  // Member functions.
+  x.captureInt(1); // expected-warning {{object whose reference is captured by 
'x'}}
+  x.captureSV(std::string()); // expected-warning {{object whose reference is 
captured by 'x'}}
+  x.captureSV(substr(std::string())); // expected-warning {{object whose 
reference is captured by 'x'}}
+  x.captureSV(strcopy(std::string()));
+
+  // 'this' is captured.
+  ThisIsCaptured{}.capture(x); // expected-warning {{object whose reference is 
captured by 'x'}}
+  ThisIsCaptured TIS;
+  TIS.capture(x);
+
+  // capture by global.
+  captureByGlobal(std::string()); // expected-warning {{object whose reference 
is captured will be destroyed at the end of the full-expression}}
+  captureByGlobal(substr(std::string())); // expected-warning {{captured}}
+  captureByGlobal(local_s);
+  captureByGlobal(local_sv);
+
+  // // capture by unknown.
+  captureByGlobal(std::s

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -0,0 +1,220 @@
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s
+
+#include "Inputs/lifetime-analysis.h"

bricknerb wrote:

Perhaps let's split the test file that checks the capture-by on the standard 
library from a test file that checks the capture-by in a self contained way 
(without the include that imitates the standard library)?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -3229,6 +3230,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);
+  };
+  for (unsigned I = 0; I < FD->getNumParams(); ++I) {
+auto *CapturedByAttr =
+FD->getParamDecl(I)->getAttr();
+if (!CapturedByAttr)
+  continue;
+for (int CapturingParamIdx : CapturedByAttr->params()) {
+  Expr *Capturing = GetArgAt(CapturingParamIdx);
+  Expr *Captured = GetArgAt(I + IsMemberFunction);
+  CapturingEntity CE{Capturing};
+  // Ensure that 'Captured' outlives the 'Capturing' entity.
+  checkExprLifetime(*this, CE, Captured);
+}
+  }
+  // Check when the 'this' object is captured.
+  if (IsMemberFunction) {
+TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+if (!TSI)
+  return;
+AttributedTypeLoc ATL;
+for (TypeLoc TL = TSI->getTypeLoc();
+ (ATL = TL.getAsAdjusted());
+ TL = ATL.getModifiedLoc()) {
+  auto *CapturedByAttr = ATL.getAttrAs();
+  if (!CapturedByAttr)
+continue;
+  Expr *Captured = GetArgAt(0);
+  for (int CapturingParamIdx : CapturedByAttr->params()) {
+Expr *Capturing = GetArgAt(CapturingParamIdx);
+CapturingEntity CE{Capturing};
+checkExprLifetime(*this, CE, Captured);
+  }

bricknerb wrote:

Can some of the logic in lines 3266-3274 reuse some of the logic in lines 
3245-3255?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -0,0 +1,220 @@
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s
+
+#include "Inputs/lifetime-analysis.h"
+
+struct X {
+  const int *x;
+  void captureInt(const int& x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int &i [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValInt(int &&i [[clang::lifetime_capture_by(x)]], X &x);
+void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X &x);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view s [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValSV(std::string_view &&sv [[clang::lifetime_capture_by(x)]], X 
&x);
+void noCaptureSV(std::string_view sv, X &x);
+void captureS(const std::string &s [[clang::lifetime_capture_by(x)]], X &x);
+void captureRValS(std::string &&s [[clang::lifetime_capture_by(x)]], X &x);
+
+const std::string& getLB(const std::string &s [[clang::lifetimebound]]);

bricknerb wrote:

I assume LB is LifetimeBound and I'm not sure I understand what 
getLifetimeBound means.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -1460,7 +1502,15 @@ void checkExprLifetime(Sema &SemaRef, const 
AssignedEntity &Entity,
 
   checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
 /*ExtendingEntity=*/nullptr, LK_Assignment, &Entity,
-Init);
+/*CapEntity=*/nullptr, Init);
+}
+
+void checkExprLifetime(Sema &SemaRef, const CapturingEntity &Entity,

bricknerb wrote:

Perhaps avoid overloading and mention that this is just for capture by?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -3925,13 +3925,20 @@ def LifetimeCaptureByDocs : Documentation {
 parameter or implicit object parameter indicates that that objects that are 
referred to

bricknerb wrote:

While you're here, remove one of the "that" ?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -3983,6 +3990,21 @@ The attribute supports specifying more than one 
capturing entities:
 s2.insert(a);
   }
 
+Currently clang would diagnose when a temporary is used as an argument to a

bricknerb wrote:

Nit: Use "Clang" and not "clang" when referring to it.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Boaz Brickner via cfe-commits


@@ -3925,13 +3925,20 @@ def LifetimeCaptureByDocs : Documentation {
 parameter or implicit object parameter indicates that that objects that are 
referred to
 by that parameter may also be referred to by the capturing entity ``X``.
 
-By default, a reference is considered to refer to its referenced object, a
-pointer is considered to refer to its pointee, a ``std::initializer_list``
-is considered to refer to its underlying array, and aggregates (arrays and
-simple ``struct``\s) are considered to refer to all objects that their
-transitive subobjects refer to.
+By default:

bricknerb wrote:

Below is a list of types and what they're considered to refer to, but it's not 
very clear for what purpose they're considered to refer to.

First, I think it's worth clarifying whether you're talking about the types of 
the parameter or the type of X (the capturing entity).
Second, what is the purpose of this consideration?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -1420,9 +1446,18 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
  ? IndirectLocalPathEntry::LifetimeBoundCall
  : IndirectLocalPathEntry::GslPointerAssignment,
  Init});
+  } else if (LK == LK_LifetimeCapture) {
+Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init});
+if (isRecordWithAttr(Init->getType()))

usx95 wrote:

Added this tests separately as well.
I am in favour of updating the documentation. I do not think we should treat 
`view` and `view&&` differently. So yeah `capture2_1` should be treated same as 
`capture2_2`.
Updated the documentation. Let me know if this is something not what you wanted.



https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/115921

>From 2cef37ecdb81452a8f5882dfe765167c1e45b7b6 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena 
Date: Wed, 13 Nov 2024 10:24:33 +
Subject: [PATCH 1/6] Implement semantics for lifetime analysis

---
 clang/include/clang/Basic/DiagnosticGroups.td |   2 +
 .../clang/Basic/DiagnosticSemaKinds.td|   7 +-
 clang/include/clang/Sema/Sema.h   |   3 +
 clang/lib/Sema/CheckExprLifetime.cpp  |  66 ---
 clang/lib/Sema/CheckExprLifetime.h|  13 +++
 clang/lib/Sema/SemaChecking.cpp   |  47 +++-
 .../Sema/warn-lifetime-analysis-nocfg.cpp | 105 ++
 clang/test/SemaCXX/attr-lifetimebound.cpp |   4 +-
 8 files changed, 228 insertions(+), 19 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 72eada50a56cc9..df9bf94b5d0398 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -453,6 +453,7 @@ def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
 def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
 def DanglingAssignment: DiagGroup<"dangling-assignment">;
 def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
+def DanglingCapture : DiagGroup<"dangling-capture">;
 def DanglingElse: DiagGroup<"dangling-else">;
 def DanglingField : DiagGroup<"dangling-field">;
 def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
@@ -462,6 +463,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">;
 def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
 def Dangling : DiagGroup<"dangling", [DanglingAssignment,
   DanglingAssignmentGsl,
+  DanglingCapture,
   DanglingField,
   DanglingInitializerList,
   DanglingGsl,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 2f5d672e2f0035..58745d450ed63f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10132,10 +10132,10 @@ def err_lifetimebound_ctor_dtor : Error<
   "%select{constructor|destructor}0">;
 def err_lifetimebound_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to a parameter of a function "
-  "that returns void">;
+  "that returns void; did you mean 'lifetime_capture_by(X)'">;
 def err_lifetimebound_implicit_object_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to an implicit object "
-  "parameter of a function that returns void">;
+  "parameter of a function that returns void; did you mean 
'lifetime_capture_by(X)'">;
 
 // CHECK: returning address/reference of stack memory
 def warn_ret_stack_addr_ref : Warning<
@@ -10230,6 +10230,9 @@ def warn_dangling_pointer_assignment : Warning<
"object backing %select{|the pointer }0%1 "
"will be destroyed at the end of the full-expression">,
InGroup;
+def warn_dangling_reference_captured : Warning<
+   "object captured by '%0' will be destroyed at the end of the 
full-expression">,
+   InGroup;
 
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d6f3508a5243f3..6ea6c67447b6f0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2323,6 +2323,9 @@ class Sema final : public SemaBase {
   bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = 
false);
   bool BuiltinVectorToScalarMath(CallExpr *TheCall);
 
+  void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
+  const Expr *ThisArg, ArrayRef 
Args);
+
   /// Handles the checks for format strings, non-POD arguments to vararg
   /// functions, NULL arguments passed to non-NULL parameters, diagnose_if
   /// attributes and AArch64 SME attributes.
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp 
b/clang/lib/Sema/CheckExprLifetime.cpp
index a1a402b4a2b530..81e26f48fb8851 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -45,10 +45,14 @@ enum LifetimeKind {
   /// a default member initializer), the program is ill-formed.
   LK_MemInitializer,
 
-  /// The lifetime of a temporary bound to this entity probably ends too soon,
+  /// The lifetime of a temporary bound to this entity may end too soon,
   /// because the entity is a pointer and we assign the address of a temporary
   /// object to it.
   LK_Assignment,
+
+  /// The lifetime of a temporary bound to this entity may end too soon,
+  /// because the enti

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -793,3 +806,202 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -793,3 +806,202 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct X {
+  const int *x;
+  void captureInt(const int& x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int& i [[clang::lifetime_capture_by(x)]], X& x);
+void captureRValInt(int&& i [[clang::lifetime_capture_by(x)]], X& x);
+void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X& x);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view s [[clang::lifetime_capture_by(x)]], X& x);
+void captureRValSV(std::string_view&& sv [[clang::lifetime_capture_by(x)]], X& 
x);
+void noCaptureSV(std::string_view sv, X& x);
+void captureS(const std::string& s [[clang::lifetime_capture_by(x)]], X& x);
+void captureRValS(std::string&& s [[clang::lifetime_capture_by(x)]], X& x);
+
+const std::string& getLB(const std::string& s[[clang::lifetimebound]]);
+const std::string& getLB(std::string_view sv[[clang::lifetimebound]]);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+
+void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], 
X& x);
+
+struct ThisIsCaptured {
+  void capture(X& x) [[clang::lifetime_capture_by(x)]];
+  void bar(X& x) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-15 Thread Utkarsh Saxena via cfe-commits


@@ -249,7 +254,7 @@ static void 
visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
   LocalVisitor Visit);
 
 template  static bool isRecordWithAttr(QualType Type) {
-  if (auto *RD = Type->getAsCXXRecordDecl())
+  if (auto *RD = Type.getNonReferenceType()->getAsCXXRecordDecl())

usx95 wrote:

Removing this will create a false positive for the following 2 cases only:
```cpp
vsv.push_back(getOptionalSV().value());
vsv.push_back(getOptionalMySV().value());
```

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Haojian Wu via cfe-commits


@@ -249,7 +254,7 @@ static void 
visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
   LocalVisitor Visit);
 
 template  static bool isRecordWithAttr(QualType Type) {
-  if (auto *RD = Type->getAsCXXRecordDecl())
+  if (auto *RD = Type.getNonReferenceType()->getAsCXXRecordDecl())

hokein wrote:

Can you explain why we we need this change? This function is used in multiple 
places of this file. Changing it can cause unexpected behavior change.  

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Haojian Wu via cfe-commits


@@ -793,3 +806,202 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct X {
+  const int *x;
+  void captureInt(const int& x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int& i [[clang::lifetime_capture_by(x)]], X& x);
+void captureRValInt(int&& i [[clang::lifetime_capture_by(x)]], X& x);
+void noCaptureInt(int i [[clang::lifetime_capture_by(x)]], X& x);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view s [[clang::lifetime_capture_by(x)]], X& x);
+void captureRValSV(std::string_view&& sv [[clang::lifetime_capture_by(x)]], X& 
x);
+void noCaptureSV(std::string_view sv, X& x);
+void captureS(const std::string& s [[clang::lifetime_capture_by(x)]], X& x);
+void captureRValS(std::string&& s [[clang::lifetime_capture_by(x)]], X& x);
+
+const std::string& getLB(const std::string& s[[clang::lifetimebound]]);
+const std::string& getLB(std::string_view sv[[clang::lifetimebound]]);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+
+void capturePointer(const std::string* sp [[clang::lifetime_capture_by(x)]], 
X& x);
+
+struct ThisIsCaptured {
+  void capture(X& x) [[clang::lifetime_capture_by(x)]];
+  void bar(X& x) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}

hokein wrote:

I think this test and below have been covered in your previous attribute-syntax 
patch, if so, we can remove them.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Haojian Wu via cfe-commits


@@ -1420,9 +1446,18 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
  ? IndirectLocalPathEntry::LifetimeBoundCall
  : IndirectLocalPathEntry::GslPointerAssignment,
  Init});
+  } else if (LK == LK_LifetimeCapture) {
+Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init});
+if (isRecordWithAttr(Init->getType()))

hokein wrote:

 Some interesting cases to consider:

```cpp
void capture1(std::string_view s [[clang::lifetime_capture_by(x)]], 
vector& x);

// Intended to capture the "string_view" itself
void capture2_1(const std::string_view& s [[clang::lifetime_capture_by(x)]], 
vector& x);
// Intended to capture the pointee of the "string_view"
void capture2_2(const std::string_view& s [[clang::lifetime_capture_by(x)]], 
vector& x);

void test1() {
   capture1(std::string(), x1); // should warn
   capture1(std::string_view(), x1); // should not warn
  
   capture2_1(std::string_view(), x2); // expected to warn
   capture2_1(std::string(), x2); // expected to warn
   
   capture2_2(std::string_view(), x3); // ?? should probably not warn
   capture2_2(std::string(), x3); // expected to warn
}
```

If I understand correctly, the current implementation handles the `capture1` 
case appropriately. However, for the `capture2` cases, it treats them similarly 
to `capture2_2`, which appears inconsistent with the `lifetime_capture_by` 
[documentation](https://clang.llvm.org/docs/AttributeReference.html#lifetime-capture-by).
 According to the documentation, for a reference type, we should consider the 
referenced type (`std::string_view` itself in this case).

Furthermore, the implementation seem to rely on the `gsl::pointer` annotation, 
the documentation does not mention GSL pointer types. E.g.

```cpp
// `my_view` is not annotated with gsl pointer.
void capture3(my_view s [[clang::lifetime_capture_by(x)]], vector& x);
```

In this situation, I think no diagnostic should be triggered, this means that 
we might need to update the documentation accordingly.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Haojian Wu via cfe-commits


@@ -793,3 +806,202 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {

hokein wrote:

I think it is probably worth having a separated test for this capture_by 
attribute. We could move the std types to a common header to reuse the code.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/115921

>From 2cef37ecdb81452a8f5882dfe765167c1e45b7b6 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena 
Date: Wed, 13 Nov 2024 10:24:33 +
Subject: [PATCH 1/5] Implement semantics for lifetime analysis

---
 clang/include/clang/Basic/DiagnosticGroups.td |   2 +
 .../clang/Basic/DiagnosticSemaKinds.td|   7 +-
 clang/include/clang/Sema/Sema.h   |   3 +
 clang/lib/Sema/CheckExprLifetime.cpp  |  66 ---
 clang/lib/Sema/CheckExprLifetime.h|  13 +++
 clang/lib/Sema/SemaChecking.cpp   |  47 +++-
 .../Sema/warn-lifetime-analysis-nocfg.cpp | 105 ++
 clang/test/SemaCXX/attr-lifetimebound.cpp |   4 +-
 8 files changed, 228 insertions(+), 19 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 72eada50a56cc9..df9bf94b5d0398 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -453,6 +453,7 @@ def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
 def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
 def DanglingAssignment: DiagGroup<"dangling-assignment">;
 def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
+def DanglingCapture : DiagGroup<"dangling-capture">;
 def DanglingElse: DiagGroup<"dangling-else">;
 def DanglingField : DiagGroup<"dangling-field">;
 def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
@@ -462,6 +463,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">;
 def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
 def Dangling : DiagGroup<"dangling", [DanglingAssignment,
   DanglingAssignmentGsl,
+  DanglingCapture,
   DanglingField,
   DanglingInitializerList,
   DanglingGsl,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 2f5d672e2f0035..58745d450ed63f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10132,10 +10132,10 @@ def err_lifetimebound_ctor_dtor : Error<
   "%select{constructor|destructor}0">;
 def err_lifetimebound_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to a parameter of a function "
-  "that returns void">;
+  "that returns void; did you mean 'lifetime_capture_by(X)'">;
 def err_lifetimebound_implicit_object_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to an implicit object "
-  "parameter of a function that returns void">;
+  "parameter of a function that returns void; did you mean 
'lifetime_capture_by(X)'">;
 
 // CHECK: returning address/reference of stack memory
 def warn_ret_stack_addr_ref : Warning<
@@ -10230,6 +10230,9 @@ def warn_dangling_pointer_assignment : Warning<
"object backing %select{|the pointer }0%1 "
"will be destroyed at the end of the full-expression">,
InGroup;
+def warn_dangling_reference_captured : Warning<
+   "object captured by '%0' will be destroyed at the end of the 
full-expression">,
+   InGroup;
 
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d6f3508a5243f3..6ea6c67447b6f0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2323,6 +2323,9 @@ class Sema final : public SemaBase {
   bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = 
false);
   bool BuiltinVectorToScalarMath(CallExpr *TheCall);
 
+  void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
+  const Expr *ThisArg, ArrayRef 
Args);
+
   /// Handles the checks for format strings, non-POD arguments to vararg
   /// functions, NULL arguments passed to non-NULL parameters, diagnose_if
   /// attributes and AArch64 SME attributes.
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp 
b/clang/lib/Sema/CheckExprLifetime.cpp
index a1a402b4a2b530..81e26f48fb8851 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -45,10 +45,14 @@ enum LifetimeKind {
   /// a default member initializer), the program is ill-formed.
   LK_MemInitializer,
 
-  /// The lifetime of a temporary bound to this entity probably ends too soon,
+  /// The lifetime of a temporary bound to this entity may end too soon,
   /// because the entity is a pointer and we assign the address of a temporary
   /// object to it.
   LK_Assignment,
+
+  /// The lifetime of a temporary bound to this entity may end too soon,
+  /// because the enti

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 edited https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits


@@ -3229,6 +3231,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);
+  };
+  for (unsigned I = 0; I < FD->getNumParams(); ++I) {
+auto *CapturedByAttr =
+FD->getParamDecl(I)->getAttr();
+if (!CapturedByAttr)
+  continue;
+for (int CapturingParamIdx : CapturedByAttr->params()) {
+  Expr *Capturing = GetArgAt(CapturingParamIdx);
+  Expr *Captured = GetArgAt(I + IsMemberFunction);
+  CapturingEntity CE{Capturing};
+  // Ensure that 'Captured' outlives the 'Capturing' entity.
+  checkExprLifetime(*this, CE, Captured);
+}
+  }
+  // Check when the 'this' object is captured.
+  if (IsMemberFunction) {
+TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+if (!TSI)
+  return;
+AttributedTypeLoc ATL;

usx95 wrote:

You could combine the two loop variables into one
```cpp
for (AttributedTypeLoc ATL =
 TSI->getTypeLoc().getAsAdjusted();
 ATL; ATL = ATL.getModifiedLoc().getAsAdjusted()) {
...
}
```
But I would keep it simpler by using two loop variables for readability. Since 
they are different types, both cannot be part of for expression.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits


@@ -1110,12 +1117,13 @@ static bool shouldRunGSLAssignmentAnalysis(const Sema 
&SemaRef,
isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
 }
 
-static void checkExprLifetimeImpl(Sema &SemaRef,
-  const InitializedEntity *InitEntity,
-  const InitializedEntity *ExtendingEntity,
-  LifetimeKind LK,
-  const AssignedEntity *AEntity, Expr *Init) {
+static void
+checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
+  const InitializedEntity *ExtendingEntity, LifetimeKind 
LK,
+  const AssignedEntity *AEntity,
+  const CapturingEntity *CapEntity, Expr *Init) {
   assert((AEntity && LK == LK_Assignment) ||
+ (CapEntity && LK == LK_LifetimeCapture) ||

usx95 wrote:

The third one has a `!=`. Simplified into 3 asserts.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits


@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s

usx95 wrote:

I think this should be fine. The analysis being tested is not specific to 
C++20. The use of concepts in the tests now requires C++20.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 edited https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits


@@ -249,7 +254,7 @@ static void 
visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
   LocalVisitor Visit);
 
 template  static bool isRecordWithAttr(QualType Type) {
-  if (auto *RD = Type->getAsCXXRecordDecl())
+  if (auto *RD = Type.getNonReferenceType()->getAsCXXRecordDecl())

usx95 wrote:

This is something required by the new annotation. This avoids a false positive 
for:
```cpp
vsv.push_back(getOptionalSV().value());
vsv.push_back(getOptionalMySV().value());
```


https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);

usx95 wrote:

SG. I will add an error diagnositc on use of this annotation on non-reference 
and non-view types. But I prefer to do this in a followup PR.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits


@@ -1420,9 +1446,18 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
  ? IndirectLocalPathEntry::LifetimeBoundCall
  : IndirectLocalPathEntry::GslPointerAssignment,
  Init});
+  } else if (LK == LK_LifetimeCapture) {

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits


@@ -793,3 +806,202 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct X {
+  const int *x;
+  void captureInt(const int& x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int& i [[clang::lifetime_capture_by(x)]], X& x);

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Gábor Horváth via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);

Xazax-hun wrote:

Since this diagnostic is not really lifetime analysis related, I think doing 
this in a separate PR is the cleaner way to go. 

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Boaz Brickner via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);

bricknerb wrote:

+1.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Boaz Brickner via cfe-commits

https://github.com/bricknerb edited 
https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Boaz Brickner via cfe-commits


@@ -249,7 +254,7 @@ static void 
visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
   LocalVisitor Visit);
 
 template  static bool isRecordWithAttr(QualType Type) {
-  if (auto *RD = Type->getAsCXXRecordDecl())
+  if (auto *RD = Type.getNonReferenceType()->getAsCXXRecordDecl())

bricknerb wrote:

Can this be done in a separate PR?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Boaz Brickner via cfe-commits


@@ -793,3 +806,202 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct X {
+  const int *x;
+  void captureInt(const int& x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int& i [[clang::lifetime_capture_by(x)]], X& x);

bricknerb wrote:

We typically put the space before the '&', like in line 732 or other places.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Boaz Brickner via cfe-commits


@@ -1420,9 +1446,18 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
  ? IndirectLocalPathEntry::LifetimeBoundCall
  : IndirectLocalPathEntry::GslPointerAssignment,
  Init});
+  } else if (LK == LK_LifetimeCapture) {

bricknerb wrote:

Consider replacing with a switch-case.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Boaz Brickner via cfe-commits

https://github.com/bricknerb commented:

Please request another review after comments are addressed.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Boaz Brickner via cfe-commits


@@ -3229,6 +3231,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);
+  };
+  for (unsigned I = 0; I < FD->getNumParams(); ++I) {
+auto *CapturedByAttr =
+FD->getParamDecl(I)->getAttr();
+if (!CapturedByAttr)
+  continue;
+for (int CapturingParamIdx : CapturedByAttr->params()) {
+  Expr *Capturing = GetArgAt(CapturingParamIdx);
+  Expr *Captured = GetArgAt(I + IsMemberFunction);
+  CapturingEntity CE{Capturing};
+  // Ensure that 'Captured' outlives the 'Capturing' entity.
+  checkExprLifetime(*this, CE, Captured);
+}
+  }
+  // Check when the 'this' object is captured.
+  if (IsMemberFunction) {
+TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+if (!TSI)
+  return;
+AttributedTypeLoc ATL;

bricknerb wrote:

Yes.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 edited https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Gábor Horváth via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], 
S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], 
S&s);
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error 
{{'lifetime_capture_by' argument references itself}}
+};
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object captured by 's' will be destroyed 
at the end of the full-expression}}
+s);
+  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
+  captureInt(local, s);
+  noCaptureInt(1, s);
+  noCaptureInt(local, s);
+
+  // Capture lifetimebound pointer.
+  capturePointer(getPointerLB(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  capturePointer(getPointerLB(*getPointerLB(std::string())), s); // 
expected-warning {{object captured by 's'}}
+  capturePointer(getPointerNoLB(std::string()), s);
+
+  // Capture using std::string_view.
+  captureSV(local_sv, s);
+  captureSV(std::string(), // expected-warning {{object captured by 's'}}
+s);
+  captureSV(substr(
+  std::string() // expected-warning {{object captured by 's'}}
+  ), s);
+  captureSV(substr(local_s), s);
+  captureSV(strcopy(std::string()), s);
+  captureRValSV(std::move(local_sv), s);
+  captureRValSV(std::string(), s); // expected-warning {{object captured by 
's'}}
+  captureRValSV(std::string_view{"abcd"}, s);
+  captureRValSV(substr(local_s), s);
+  captureRValSV(substr(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  captureRValSV(strcopy(std::string()), s);
+  noCaptureSV(local_sv, s);
+  noCaptureSV(std::string(), s);
+  noCaptureSV(substr(std::string()), s);
+
+  // Capture using std::string.
+  captureS(std::string(), s); // expected-warning {{object captured by 's'}}
+  captureS(local_s, s);
+  captureRValS(std::move(local_s), s);
+  captureRValS(std::string(), s); // expected-warning {{object captured by 
's'}}
+
+  // Member functions.
+  s.captureInt(1); // expected-warning {{object captured by 's'}}
+  s.captureSV(std::string()); // expected-warning {{object captured by 's'}}
+  s.captureSV(substr(std::string())); // expected-warning {{object captured by 
's'}}
+  s.captureSV(strcopy(std::string()));
+
+  // 'this' is captured.
+  ThisIsCaptured{}.capture(s); // expected-warning {{object captured by 's'}}
+  ThisIsCaptured TIS;
+  TIS.capture(s);
+}
+class [[gsl::Pointer()]] my_string_view : public std::string_view {};
+class my_string_view_not_pointer : public std::string_view {};
+std::optional getOptionalSV();
+std::optional getOptionalS();
+std::optional getOptionalMySV();
+std::optional getOptionalMySVNotP();
+my_string_view getMySV();
+my_string_view_not_pointer getMySVNotP();
+
+template
+struct MySet {
+void insert(T&& t [[clang::lifetime_capture_by(this)]]);
+void insert(const T& t [[clang::lifetime_capture_by(this)]]);
+};
+void user_defined_containers() {
+  MySet set_of_int;
+  set_of_int.insert(1); // expected-warning {{object captured by 'set_of_int' 
will be destroyed}}

Xazax-hun wrote:

I would argue that either the annotation on `MySet` is wrong, or if we want 
that annotation to be correct, we need to handle these cases without false 
positives. I think this is the case for `lifetimebound` as well. For the 
equivalent false positives we either should not have the lifetimebound 
annotation in the first place since it is incorrect for some of the 
instantiations, or we should have a way to handle it correctly

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-14 Thread Gábor Horváth via cfe-commits

https://github.com/Xazax-hun approved this pull request.

LGTM!

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -10132,10 +10132,10 @@ def err_lifetimebound_ctor_dtor : Error<
   "%select{constructor|destructor}0">;
 def err_lifetimebound_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to a parameter of a function "
-  "that returns void">;
+  "that returns void; did you mean 'lifetime_capture_by(X)'">;
 def err_lifetimebound_implicit_object_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to an implicit object "
-  "parameter of a function that returns void">;
+  "parameter of a function that returns void; did you mean 
'lifetime_capture_by(X)'">;

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -1438,13 +1471,13 @@ void checkExprLifetime(Sema &SemaRef, const 
InitializedEntity &Entity,
   LifetimeKind LK = LTResult.getInt();
   const InitializedEntity *ExtendingEntity = LTResult.getPointer();
   checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK,
-/*AEntity*/ nullptr, Init);
+/*AEntity*/ nullptr, /*CapEntity=*/nullptr, Init);

usx95 wrote:

Fixed the old comments in `AEntity`.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -1199,6 +1207,21 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
   break;
 }
 
+case LK_LifetimeCapture: {
+  if (!MTE)
+return false;
+  assert(shouldLifetimeExtendThroughPath(Path) ==
+ PathLifetimeKind::NoExtend &&
+ "No lifetime extension in function calls");
+  if (CapEntity->Entity != nullptr)

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -1199,6 +1207,21 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
   break;
 }
 
+case LK_LifetimeCapture: {
+  if (!MTE)
+return false;
+  assert(shouldLifetimeExtendThroughPath(Path) ==
+ PathLifetimeKind::NoExtend &&
+ "No lifetime extension in function calls");
+  if (CapEntity->Entity != nullptr)

bricknerb wrote:

Seems like we typically (see line 1211 for example) don't compare to nullptr.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -3229,6 +3231,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);
+  };
+  for (unsigned I = 0; I < FD->getNumParams(); ++I) {
+auto *CapturedByAttr =
+FD->getParamDecl(I)->getAttr();
+if (!CapturedByAttr)
+  continue;
+for (int CapturingParamIdx : CapturedByAttr->params()) {
+  Expr *Capturing = GetArgAt(CapturingParamIdx);
+  Expr *Captured = GetArgAt(I + IsMemberFunction);
+  CapturingEntity CE{Capturing};
+  // Ensure that 'Captured' outlives the 'Capturing' entity.
+  checkExprLifetime(*this, CE, Captured);
+}
+  }
+  // Check when the 'this' object is captured.
+  if (IsMemberFunction) {
+TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+if (!TSI)
+  return;
+AttributedTypeLoc ATL;

usx95 wrote:

I do not fully understand what is the comment I need to add here. Can you 
please elaborate? 
Are you referring to the variable `ATL` here ?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -793,3 +806,202 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], 
S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+
+const std::string& getLB(const std::string& s[[clang::lifetimebound]]);
+const std::string& getLB(std::string_view sv[[clang::lifetimebound]]);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], 
S&s);
+
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error 
{{'lifetime_capture_by' argument references itself}}
+};
+
+void captureByGlobal(std::string_view s 
[[clang::lifetime_capture_by(global)]]);
+void captureByUnknown(std::string_view s 
[[clang::lifetime_capture_by(unknown)]]);
+
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;

usx95 wrote:

Renamed to `X x;` to make it similar to `lifetime_capture_by(X)`.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/115921

>From 2cef37ecdb81452a8f5882dfe765167c1e45b7b6 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena 
Date: Wed, 13 Nov 2024 10:24:33 +
Subject: [PATCH 1/4] Implement semantics for lifetime analysis

---
 clang/include/clang/Basic/DiagnosticGroups.td |   2 +
 .../clang/Basic/DiagnosticSemaKinds.td|   7 +-
 clang/include/clang/Sema/Sema.h   |   3 +
 clang/lib/Sema/CheckExprLifetime.cpp  |  66 ---
 clang/lib/Sema/CheckExprLifetime.h|  13 +++
 clang/lib/Sema/SemaChecking.cpp   |  47 +++-
 .../Sema/warn-lifetime-analysis-nocfg.cpp | 105 ++
 clang/test/SemaCXX/attr-lifetimebound.cpp |   4 +-
 8 files changed, 228 insertions(+), 19 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 72eada50a56cc9..df9bf94b5d0398 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -453,6 +453,7 @@ def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
 def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
 def DanglingAssignment: DiagGroup<"dangling-assignment">;
 def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
+def DanglingCapture : DiagGroup<"dangling-capture">;
 def DanglingElse: DiagGroup<"dangling-else">;
 def DanglingField : DiagGroup<"dangling-field">;
 def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
@@ -462,6 +463,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">;
 def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
 def Dangling : DiagGroup<"dangling", [DanglingAssignment,
   DanglingAssignmentGsl,
+  DanglingCapture,
   DanglingField,
   DanglingInitializerList,
   DanglingGsl,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 2f5d672e2f0035..58745d450ed63f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10132,10 +10132,10 @@ def err_lifetimebound_ctor_dtor : Error<
   "%select{constructor|destructor}0">;
 def err_lifetimebound_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to a parameter of a function "
-  "that returns void">;
+  "that returns void; did you mean 'lifetime_capture_by(X)'">;
 def err_lifetimebound_implicit_object_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to an implicit object "
-  "parameter of a function that returns void">;
+  "parameter of a function that returns void; did you mean 
'lifetime_capture_by(X)'">;
 
 // CHECK: returning address/reference of stack memory
 def warn_ret_stack_addr_ref : Warning<
@@ -10230,6 +10230,9 @@ def warn_dangling_pointer_assignment : Warning<
"object backing %select{|the pointer }0%1 "
"will be destroyed at the end of the full-expression">,
InGroup;
+def warn_dangling_reference_captured : Warning<
+   "object captured by '%0' will be destroyed at the end of the 
full-expression">,
+   InGroup;
 
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d6f3508a5243f3..6ea6c67447b6f0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2323,6 +2323,9 @@ class Sema final : public SemaBase {
   bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = 
false);
   bool BuiltinVectorToScalarMath(CallExpr *TheCall);
 
+  void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
+  const Expr *ThisArg, ArrayRef 
Args);
+
   /// Handles the checks for format strings, non-POD arguments to vararg
   /// functions, NULL arguments passed to non-NULL parameters, diagnose_if
   /// attributes and AArch64 SME attributes.
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp 
b/clang/lib/Sema/CheckExprLifetime.cpp
index a1a402b4a2b530..81e26f48fb8851 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -45,10 +45,14 @@ enum LifetimeKind {
   /// a default member initializer), the program is ill-formed.
   LK_MemInitializer,
 
-  /// The lifetime of a temporary bound to this entity probably ends too soon,
+  /// The lifetime of a temporary bound to this entity may end too soon,
   /// because the entity is a pointer and we assign the address of a temporary
   /// object to it.
   LK_Assignment,
+
+  /// The lifetime of a temporary bound to this entity may end too soon,
+  /// because the enti

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/115921

>From 2cef37ecdb81452a8f5882dfe765167c1e45b7b6 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena 
Date: Wed, 13 Nov 2024 10:24:33 +
Subject: [PATCH 1/4] Implement semantics for lifetime analysis

---
 clang/include/clang/Basic/DiagnosticGroups.td |   2 +
 .../clang/Basic/DiagnosticSemaKinds.td|   7 +-
 clang/include/clang/Sema/Sema.h   |   3 +
 clang/lib/Sema/CheckExprLifetime.cpp  |  66 ---
 clang/lib/Sema/CheckExprLifetime.h|  13 +++
 clang/lib/Sema/SemaChecking.cpp   |  47 +++-
 .../Sema/warn-lifetime-analysis-nocfg.cpp | 105 ++
 clang/test/SemaCXX/attr-lifetimebound.cpp |   4 +-
 8 files changed, 228 insertions(+), 19 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 72eada50a56cc9..df9bf94b5d0398 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -453,6 +453,7 @@ def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
 def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
 def DanglingAssignment: DiagGroup<"dangling-assignment">;
 def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
+def DanglingCapture : DiagGroup<"dangling-capture">;
 def DanglingElse: DiagGroup<"dangling-else">;
 def DanglingField : DiagGroup<"dangling-field">;
 def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
@@ -462,6 +463,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">;
 def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
 def Dangling : DiagGroup<"dangling", [DanglingAssignment,
   DanglingAssignmentGsl,
+  DanglingCapture,
   DanglingField,
   DanglingInitializerList,
   DanglingGsl,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 2f5d672e2f0035..58745d450ed63f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10132,10 +10132,10 @@ def err_lifetimebound_ctor_dtor : Error<
   "%select{constructor|destructor}0">;
 def err_lifetimebound_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to a parameter of a function "
-  "that returns void">;
+  "that returns void; did you mean 'lifetime_capture_by(X)'">;
 def err_lifetimebound_implicit_object_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to an implicit object "
-  "parameter of a function that returns void">;
+  "parameter of a function that returns void; did you mean 
'lifetime_capture_by(X)'">;
 
 // CHECK: returning address/reference of stack memory
 def warn_ret_stack_addr_ref : Warning<
@@ -10230,6 +10230,9 @@ def warn_dangling_pointer_assignment : Warning<
"object backing %select{|the pointer }0%1 "
"will be destroyed at the end of the full-expression">,
InGroup;
+def warn_dangling_reference_captured : Warning<
+   "object captured by '%0' will be destroyed at the end of the 
full-expression">,
+   InGroup;
 
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d6f3508a5243f3..6ea6c67447b6f0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2323,6 +2323,9 @@ class Sema final : public SemaBase {
   bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = 
false);
   bool BuiltinVectorToScalarMath(CallExpr *TheCall);
 
+  void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
+  const Expr *ThisArg, ArrayRef 
Args);
+
   /// Handles the checks for format strings, non-POD arguments to vararg
   /// functions, NULL arguments passed to non-NULL parameters, diagnose_if
   /// attributes and AArch64 SME attributes.
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp 
b/clang/lib/Sema/CheckExprLifetime.cpp
index a1a402b4a2b530..81e26f48fb8851 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -45,10 +45,14 @@ enum LifetimeKind {
   /// a default member initializer), the program is ill-formed.
   LK_MemInitializer,
 
-  /// The lifetime of a temporary bound to this entity probably ends too soon,
+  /// The lifetime of a temporary bound to this entity may end too soon,
   /// because the entity is a pointer and we assign the address of a temporary
   /// object to it.
   LK_Assignment,
+
+  /// The lifetime of a temporary bound to this entity may end too soon,
+  /// because the enti

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -793,3 +806,202 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -1199,6 +1207,21 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
   break;
 }
 
+case LK_LifetimeCapture: {
+  if (!MTE)

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -10230,6 +10230,9 @@ def warn_dangling_pointer_assignment : Warning<
"object backing %select{|the pointer }0%1 "
"will be destroyed at the end of the full-expression">,
InGroup;
+def warn_dangling_reference_captured : Warning<
+   "object whose reference is captured%select{| by '%1'}0 will be destroyed at 
the end of the full-expression">,

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -1199,6 +1207,21 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
   break;
 }
 
+case LK_LifetimeCapture: {
+  if (!MTE)
+return false;
+  assert(shouldLifetimeExtendThroughPath(Path) ==
+ PathLifetimeKind::NoExtend &&
+ "No lifetime extension in function calls");
+  if (CapEntity->Entity != nullptr)
+SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
+<< true << CapEntity->Entity << DiagRange;
+  else
+SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
+<< false << "" << DiagRange;

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 edited https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits

https://github.com/bricknerb edited 
https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -10230,6 +10230,9 @@ def warn_dangling_pointer_assignment : Warning<
"object backing %select{|the pointer }0%1 "
"will be destroyed at the end of the full-expression">,
InGroup;
+def warn_dangling_reference_captured : Warning<
+   "object whose reference is captured%select{| by '%1'}0 will be destroyed at 
the end of the full-expression">,

bricknerb wrote:

Break the line to avoid going over 80 chars.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -793,3 +806,202 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }

bricknerb wrote:

Be consistent regarding spacing around &.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -1199,6 +1207,21 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
   break;
 }
 
+case LK_LifetimeCapture: {
+  if (!MTE)
+return false;
+  assert(shouldLifetimeExtendThroughPath(Path) ==
+ PathLifetimeKind::NoExtend &&
+ "No lifetime extension in function calls");
+  if (CapEntity->Entity != nullptr)
+SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
+<< true << CapEntity->Entity << DiagRange;
+  else
+SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
+<< false << "" << DiagRange;

bricknerb wrote:

Instead of having true and "" parameters, how about splitting to two 
different warnings?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -10132,10 +10132,10 @@ def err_lifetimebound_ctor_dtor : Error<
   "%select{constructor|destructor}0">;
 def err_lifetimebound_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to a parameter of a function "
-  "that returns void">;
+  "that returns void; did you mean 'lifetime_capture_by(X)'">;
 def err_lifetimebound_implicit_object_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to an implicit object "
-  "parameter of a function that returns void">;
+  "parameter of a function that returns void; did you mean 
'lifetime_capture_by(X)'">;

bricknerb wrote:

Break the line after the ";" to avoid going over 80 chars.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -1438,13 +1471,13 @@ void checkExprLifetime(Sema &SemaRef, const 
InitializedEntity &Entity,
   LifetimeKind LK = LTResult.getInt();
   const InitializedEntity *ExtendingEntity = LTResult.getPointer();
   checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK,
-/*AEntity*/ nullptr, Init);
+/*AEntity*/ nullptr, /*CapEntity=*/nullptr, Init);

bricknerb wrote:

Be consistent at how we comment on nullptr args.
https://llvm.org/docs/CodingStandards.html#comment-formatting

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -3229,6 +3231,52 @@ void Sema::CheckArgAlignment(SourceLocation Loc, 
NamedDecl *FDecl,
 << ParamName << (FDecl != nullptr) << FDecl;
 }
 
+void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
+  const Expr *ThisArg,
+  ArrayRef Args) {
+  auto GetArgAt = [&](int Idx) -> Expr * {
+if (Idx == LifetimeCaptureByAttr::GLOBAL ||
+Idx == LifetimeCaptureByAttr::UNKNOWN)
+  return nullptr;
+if (IsMemberFunction && Idx == 0)
+  return const_cast(ThisArg);
+return const_cast(Args[Idx - int(IsMemberFunction)]);
+  };
+  for (unsigned I = 0; I < FD->getNumParams(); ++I) {
+auto *CapturedByAttr =
+FD->getParamDecl(I)->getAttr();
+if (!CapturedByAttr)
+  continue;
+for (int CapturingParamIdx : CapturedByAttr->params()) {
+  Expr *Capturing = GetArgAt(CapturingParamIdx);
+  Expr *Captured = GetArgAt(I + IsMemberFunction);
+  CapturingEntity CE{Capturing};
+  // Ensure that 'Captured' outlives the 'Capturing' entity.
+  checkExprLifetime(*this, CE, Captured);
+}
+  }
+  // Check when the 'this' object is captured.
+  if (IsMemberFunction) {
+TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+if (!TSI)
+  return;
+AttributedTypeLoc ATL;

bricknerb wrote:

Add a comment explaining why this is not declared in the for-statement.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s
+// RUN: %clang_cc1 --std=c++20 -fsyntax-only -Wdangling -Wdangling-field 
-Wreturn-stack-address -verify %s

bricknerb wrote:

Should we limit all the tests here to C++20?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -1199,6 +1207,21 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
   break;
 }
 
+case LK_LifetimeCapture: {
+  if (!MTE)

bricknerb wrote:

Add a comment explaining when do we expect this to happen?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -1110,12 +1117,13 @@ static bool shouldRunGSLAssignmentAnalysis(const Sema 
&SemaRef,
isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
 }
 
-static void checkExprLifetimeImpl(Sema &SemaRef,
-  const InitializedEntity *InitEntity,
-  const InitializedEntity *ExtendingEntity,
-  LifetimeKind LK,
-  const AssignedEntity *AEntity, Expr *Init) {
+static void
+checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
+  const InitializedEntity *ExtendingEntity, LifetimeKind 
LK,
+  const AssignedEntity *AEntity,
+  const CapturingEntity *CapEntity, Expr *Init) {
   assert((AEntity && LK == LK_Assignment) ||
+ (CapEntity && LK == LK_LifetimeCapture) ||

bricknerb wrote:

How can line 1130 be true given this assert?
How can we match different LK values in the switch in line 1165?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits


@@ -793,3 +806,202 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], 
S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+
+const std::string& getLB(const std::string& s[[clang::lifetimebound]]);
+const std::string& getLB(std::string_view sv[[clang::lifetimebound]]);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], 
S&s);
+
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error 
{{'lifetime_capture_by' argument references itself}}
+};
+
+void captureByGlobal(std::string_view s 
[[clang::lifetime_capture_by(global)]]);
+void captureByUnknown(std::string_view s 
[[clang::lifetime_capture_by(unknown)]]);
+
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;

bricknerb wrote:

's' vs. 'local_s' is misleading as you would expect they would be of the same 
type.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Boaz Brickner via cfe-commits

https://github.com/bricknerb commented:

First pass, still didn't dive fully into the logic.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -249,9 +254,10 @@ static void 
visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
   LocalVisitor Visit);
 
 template  static bool isRecordWithAttr(QualType Type) {
-  if (auto *RD = Type->getAsCXXRecordDecl())
-return RD->hasAttr();
-  return false;
+  CXXRecordDecl *RD = Type.getNonReferenceType()->getAsCXXRecordDecl();

usx95 wrote:

I do not think there is a difference in the annotations in that respect.
For example, consider:
```cpp
std::string_view foo(std::string_view&& t [[clang::lifetimebound]]);

void use() {
std::string_view x = foo(std::string_view()); // Clangs warns here.
}
```
I think this is, in principle, a false positive. Pointer-like types when 
appearing as a reference type should be considered values.
https://github.com/llvm/llvm-project/issues/116066

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 edited https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 edited https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Gábor Horváth via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], 
S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], 
S&s);
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error 
{{'lifetime_capture_by' argument references itself}}
+};
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object captured by 's' will be destroyed 
at the end of the full-expression}}
+s);
+  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
+  captureInt(local, s);
+  noCaptureInt(1, s);
+  noCaptureInt(local, s);
+
+  // Capture lifetimebound pointer.
+  capturePointer(getPointerLB(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  capturePointer(getPointerLB(*getPointerLB(std::string())), s); // 
expected-warning {{object captured by 's'}}

Xazax-hun wrote:

Sounds great, thanks! 

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/115921

>From 2cef37ecdb81452a8f5882dfe765167c1e45b7b6 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena 
Date: Wed, 13 Nov 2024 10:24:33 +
Subject: [PATCH 1/3] Implement semantics for lifetime analysis

---
 clang/include/clang/Basic/DiagnosticGroups.td |   2 +
 .../clang/Basic/DiagnosticSemaKinds.td|   7 +-
 clang/include/clang/Sema/Sema.h   |   3 +
 clang/lib/Sema/CheckExprLifetime.cpp  |  66 ---
 clang/lib/Sema/CheckExprLifetime.h|  13 +++
 clang/lib/Sema/SemaChecking.cpp   |  47 +++-
 .../Sema/warn-lifetime-analysis-nocfg.cpp | 105 ++
 clang/test/SemaCXX/attr-lifetimebound.cpp |   4 +-
 8 files changed, 228 insertions(+), 19 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 72eada50a56cc9..df9bf94b5d0398 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -453,6 +453,7 @@ def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
 def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
 def DanglingAssignment: DiagGroup<"dangling-assignment">;
 def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
+def DanglingCapture : DiagGroup<"dangling-capture">;
 def DanglingElse: DiagGroup<"dangling-else">;
 def DanglingField : DiagGroup<"dangling-field">;
 def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
@@ -462,6 +463,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">;
 def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
 def Dangling : DiagGroup<"dangling", [DanglingAssignment,
   DanglingAssignmentGsl,
+  DanglingCapture,
   DanglingField,
   DanglingInitializerList,
   DanglingGsl,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 2f5d672e2f0035..58745d450ed63f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10132,10 +10132,10 @@ def err_lifetimebound_ctor_dtor : Error<
   "%select{constructor|destructor}0">;
 def err_lifetimebound_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to a parameter of a function "
-  "that returns void">;
+  "that returns void; did you mean 'lifetime_capture_by(X)'">;
 def err_lifetimebound_implicit_object_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to an implicit object "
-  "parameter of a function that returns void">;
+  "parameter of a function that returns void; did you mean 
'lifetime_capture_by(X)'">;
 
 // CHECK: returning address/reference of stack memory
 def warn_ret_stack_addr_ref : Warning<
@@ -10230,6 +10230,9 @@ def warn_dangling_pointer_assignment : Warning<
"object backing %select{|the pointer }0%1 "
"will be destroyed at the end of the full-expression">,
InGroup;
+def warn_dangling_reference_captured : Warning<
+   "object captured by '%0' will be destroyed at the end of the 
full-expression">,
+   InGroup;
 
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d6f3508a5243f3..6ea6c67447b6f0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2323,6 +2323,9 @@ class Sema final : public SemaBase {
   bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = 
false);
   bool BuiltinVectorToScalarMath(CallExpr *TheCall);
 
+  void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
+  const Expr *ThisArg, ArrayRef 
Args);
+
   /// Handles the checks for format strings, non-POD arguments to vararg
   /// functions, NULL arguments passed to non-NULL parameters, diagnose_if
   /// attributes and AArch64 SME attributes.
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp 
b/clang/lib/Sema/CheckExprLifetime.cpp
index a1a402b4a2b530..81e26f48fb8851 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -45,10 +45,14 @@ enum LifetimeKind {
   /// a default member initializer), the program is ill-formed.
   LK_MemInitializer,
 
-  /// The lifetime of a temporary bound to this entity probably ends too soon,
+  /// The lifetime of a temporary bound to this entity may end too soon,
   /// because the entity is a pointer and we assign the address of a temporary
   /// object to it.
   LK_Assignment,
+
+  /// The lifetime of a temporary bound to this entity may end too soon,
+  /// because the enti

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], 
S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], 
S&s);
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error 
{{'lifetime_capture_by' argument references itself}}
+};
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object captured by 's' will be destroyed 
at the end of the full-expression}}
+s);
+  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
+  captureInt(local, s);
+  noCaptureInt(1, s);
+  noCaptureInt(local, s);
+
+  // Capture lifetimebound pointer.
+  capturePointer(getPointerLB(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  capturePointer(getPointerLB(*getPointerLB(std::string())), s); // 
expected-warning {{object captured by 's'}}
+  capturePointer(getPointerNoLB(std::string()), s);
+
+  // Capture using std::string_view.
+  captureSV(local_sv, s);
+  captureSV(std::string(), // expected-warning {{object captured by 's'}}
+s);
+  captureSV(substr(
+  std::string() // expected-warning {{object captured by 's'}}
+  ), s);
+  captureSV(substr(local_s), s);
+  captureSV(strcopy(std::string()), s);
+  captureRValSV(std::move(local_sv), s);
+  captureRValSV(std::string(), s); // expected-warning {{object captured by 
's'}}
+  captureRValSV(std::string_view{"abcd"}, s);
+  captureRValSV(substr(local_s), s);
+  captureRValSV(substr(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  captureRValSV(strcopy(std::string()), s);
+  noCaptureSV(local_sv, s);
+  noCaptureSV(std::string(), s);
+  noCaptureSV(substr(std::string()), s);
+
+  // Capture using std::string.
+  captureS(std::string(), s); // expected-warning {{object captured by 's'}}
+  captureS(local_s, s);
+  captureRValS(std::move(local_s), s);
+  captureRValS(std::string(), s); // expected-warning {{object captured by 
's'}}
+
+  // Member functions.
+  s.captureInt(1); // expected-warning {{object captured by 's'}}
+  s.captureSV(std::string()); // expected-warning {{object captured by 's'}}
+  s.captureSV(substr(std::string())); // expected-warning {{object captured by 
's'}}
+  s.captureSV(strcopy(std::string()));
+
+  // 'this' is captured.
+  ThisIsCaptured{}.capture(s); // expected-warning {{object captured by 's'}}
+  ThisIsCaptured TIS;
+  TIS.capture(s);
+}
+class [[gsl::Pointer()]] my_string_view : public std::string_view {};
+class my_string_view_not_pointer : public std::string_view {};
+std::optional getOptionalSV();
+std::optional getOptionalS();
+std::optional getOptionalMySV();
+std::optional getOptionalMySVNotP();
+my_string_view getMySV();
+my_string_view_not_pointer getMySVNotP();
+
+template
+struct MySet {
+void insert(T&& t [[clang::lifetime_capture_by(this)]]);
+void insert(const T& t [[clang::lifetime_capture_by(this)]]);
+};
+void user_defined_containers() {
+  MySet set_of_int;
+  set_of_int.insert(1); // expected-warning {{object captured by 'set_of_int' 
will be destroyed}}
+  MySet set_of_sv;
+  set_of_sv.insert(std::string());  // expected-warning {{object captured by 
'set_of_sv' will be destroyed}}
+}
+} // namespace lifetime_capture_by
+// Test for templated code.
+// 2 nested function calls foo(sv, bar(sv, setsv));

usx95 wrote:

Done.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], 
S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], 
S&s);
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error 
{{'lifetime_capture_by' argument references itself}}
+};
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object captured by 's' will be destroyed 
at the end of the full-expression}}
+s);
+  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
+  captureInt(local, s);
+  noCaptureInt(1, s);
+  noCaptureInt(local, s);
+
+  // Capture lifetimebound pointer.
+  capturePointer(getPointerLB(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  capturePointer(getPointerLB(*getPointerLB(std::string())), s); // 
expected-warning {{object captured by 's'}}

usx95 wrote:

We refer to the capturing entity (here `s`) to make it clear. Also the 
diagnostic is attached to the temporary expression (and not the whole 
expression). I have added line breaks to some complex function calls to test 
that the correct expression is underscored.
WDYT ?

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], 
S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], 
S&s);
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error 
{{'lifetime_capture_by' argument references itself}}
+};
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object captured by 's' will be destroyed 
at the end of the full-expression}}
+s);
+  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
+  captureInt(local, s);
+  noCaptureInt(1, s);
+  noCaptureInt(local, s);
+
+  // Capture lifetimebound pointer.
+  capturePointer(getPointerLB(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  capturePointer(getPointerLB(*getPointerLB(std::string())), s); // 
expected-warning {{object captured by 's'}}
+  capturePointer(getPointerNoLB(std::string()), s);
+
+  // Capture using std::string_view.
+  captureSV(local_sv, s);
+  captureSV(std::string(), // expected-warning {{object captured by 's'}}
+s);
+  captureSV(substr(
+  std::string() // expected-warning {{object captured by 's'}}
+  ), s);
+  captureSV(substr(local_s), s);
+  captureSV(strcopy(std::string()), s);
+  captureRValSV(std::move(local_sv), s);
+  captureRValSV(std::string(), s); // expected-warning {{object captured by 
's'}}
+  captureRValSV(std::string_view{"abcd"}, s);
+  captureRValSV(substr(local_s), s);
+  captureRValSV(substr(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  captureRValSV(strcopy(std::string()), s);
+  noCaptureSV(local_sv, s);
+  noCaptureSV(std::string(), s);
+  noCaptureSV(substr(std::string()), s);
+
+  // Capture using std::string.
+  captureS(std::string(), s); // expected-warning {{object captured by 's'}}
+  captureS(local_s, s);
+  captureRValS(std::move(local_s), s);
+  captureRValS(std::string(), s); // expected-warning {{object captured by 
's'}}
+
+  // Member functions.
+  s.captureInt(1); // expected-warning {{object captured by 's'}}
+  s.captureSV(std::string()); // expected-warning {{object captured by 's'}}
+  s.captureSV(substr(std::string())); // expected-warning {{object captured by 
's'}}
+  s.captureSV(strcopy(std::string()));
+
+  // 'this' is captured.
+  ThisIsCaptured{}.capture(s); // expected-warning {{object captured by 's'}}
+  ThisIsCaptured TIS;
+  TIS.capture(s);
+}
+class [[gsl::Pointer()]] my_string_view : public std::string_view {};
+class my_string_view_not_pointer : public std::string_view {};
+std::optional getOptionalSV();
+std::optional getOptionalS();
+std::optional getOptionalMySV();
+std::optional getOptionalMySVNotP();
+my_string_view getMySV();
+my_string_view_not_pointer getMySVNotP();
+
+template
+struct MySet {
+void insert(T&& t [[clang::lifetime_capture_by(this)]]);
+void insert(const T& t [[clang::lifetime_capture_by(this)]]);
+};
+void user_defined_containers() {
+  MySet set_of_int;
+  set_of_int.insert(1); // expected-warning {{object captured by 'set_of_int' 
will be destroyed}}

usx95 wrote:

This is now WAI. The problem is same with `lifetimebound` annotation as well.
Internally we used specialised type traits to distinguish between pointer-type 
and other element types.
Added few tests to describe it. I agree it is not the most convenient but seems 
like there is no better solution than this. This is also what we do internally 
at google for lifetimebound.

https://github.com/llvm/llvm-project/pull/115921
___

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], 
S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], 
S&s);
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error 
{{'lifetime_capture_by' argument references itself}}
+};
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object captured by 's' will be destroyed 
at the end of the full-expression}}
+s);
+  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
+  captureInt(local, s);
+  noCaptureInt(1, s);
+  noCaptureInt(local, s);
+
+  // Capture lifetimebound pointer.
+  capturePointer(getPointerLB(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  capturePointer(getPointerLB(*getPointerLB(std::string())), s); // 
expected-warning {{object captured by 's'}}
+  capturePointer(getPointerNoLB(std::string()), s);
+
+  // Capture using std::string_view.
+  captureSV(local_sv, s);
+  captureSV(std::string(), // expected-warning {{object captured by 's'}}
+s);
+  captureSV(substr(
+  std::string() // expected-warning {{object captured by 's'}}
+  ), s);
+  captureSV(substr(local_s), s);
+  captureSV(strcopy(std::string()), s);
+  captureRValSV(std::move(local_sv), s);
+  captureRValSV(std::string(), s); // expected-warning {{object captured by 
's'}}
+  captureRValSV(std::string_view{"abcd"}, s);
+  captureRValSV(substr(local_s), s);
+  captureRValSV(substr(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  captureRValSV(strcopy(std::string()), s);
+  noCaptureSV(local_sv, s);
+  noCaptureSV(std::string(), s);
+  noCaptureSV(substr(std::string()), s);
+
+  // Capture using std::string.
+  captureS(std::string(), s); // expected-warning {{object captured by 's'}}
+  captureS(local_s, s);
+  captureRValS(std::move(local_s), s);
+  captureRValS(std::string(), s); // expected-warning {{object captured by 
's'}}
+
+  // Member functions.
+  s.captureInt(1); // expected-warning {{object captured by 's'}}
+  s.captureSV(std::string()); // expected-warning {{object captured by 's'}}
+  s.captureSV(substr(std::string())); // expected-warning {{object captured by 
's'}}
+  s.captureSV(strcopy(std::string()));
+
+  // 'this' is captured.
+  ThisIsCaptured{}.capture(s); // expected-warning {{object captured by 's'}}
+  ThisIsCaptured TIS;
+  TIS.capture(s);
+}
+class [[gsl::Pointer()]] my_string_view : public std::string_view {};
+class my_string_view_not_pointer : public std::string_view {};
+std::optional getOptionalSV();
+std::optional getOptionalS();
+std::optional getOptionalMySV();
+std::optional getOptionalMySVNotP();
+my_string_view getMySV();
+my_string_view_not_pointer getMySVNotP();
+
+template
+struct MySet {
+void insert(T&& t [[clang::lifetime_capture_by(this)]]);
+void insert(const T& t [[clang::lifetime_capture_by(this)]]);
+};
+void user_defined_containers() {
+  MySet set_of_int;
+  set_of_int.insert(1); // expected-warning {{object captured by 'set_of_int' 
will be destroyed}}
+  MySet set_of_sv;
+  set_of_sv.insert(std::string());  // expected-warning {{object captured by 
'set_of_sv' will be destroyed}}
+}
+} // namespace lifetime_capture_by

usx95 wrote:

Oops I missed to implement this. Added.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/115921

>From 2cef37ecdb81452a8f5882dfe765167c1e45b7b6 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena 
Date: Wed, 13 Nov 2024 10:24:33 +
Subject: [PATCH 1/2] Implement semantics for lifetime analysis

---
 clang/include/clang/Basic/DiagnosticGroups.td |   2 +
 .../clang/Basic/DiagnosticSemaKinds.td|   7 +-
 clang/include/clang/Sema/Sema.h   |   3 +
 clang/lib/Sema/CheckExprLifetime.cpp  |  66 ---
 clang/lib/Sema/CheckExprLifetime.h|  13 +++
 clang/lib/Sema/SemaChecking.cpp   |  47 +++-
 .../Sema/warn-lifetime-analysis-nocfg.cpp | 105 ++
 clang/test/SemaCXX/attr-lifetimebound.cpp |   4 +-
 8 files changed, 228 insertions(+), 19 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 72eada50a56cc9..df9bf94b5d0398 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -453,6 +453,7 @@ def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
 def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
 def DanglingAssignment: DiagGroup<"dangling-assignment">;
 def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
+def DanglingCapture : DiagGroup<"dangling-capture">;
 def DanglingElse: DiagGroup<"dangling-else">;
 def DanglingField : DiagGroup<"dangling-field">;
 def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
@@ -462,6 +463,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">;
 def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
 def Dangling : DiagGroup<"dangling", [DanglingAssignment,
   DanglingAssignmentGsl,
+  DanglingCapture,
   DanglingField,
   DanglingInitializerList,
   DanglingGsl,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 2f5d672e2f0035..58745d450ed63f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10132,10 +10132,10 @@ def err_lifetimebound_ctor_dtor : Error<
   "%select{constructor|destructor}0">;
 def err_lifetimebound_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to a parameter of a function "
-  "that returns void">;
+  "that returns void; did you mean 'lifetime_capture_by(X)'">;
 def err_lifetimebound_implicit_object_parameter_void_return_type : Error<
   "'lifetimebound' attribute cannot be applied to an implicit object "
-  "parameter of a function that returns void">;
+  "parameter of a function that returns void; did you mean 
'lifetime_capture_by(X)'">;
 
 // CHECK: returning address/reference of stack memory
 def warn_ret_stack_addr_ref : Warning<
@@ -10230,6 +10230,9 @@ def warn_dangling_pointer_assignment : Warning<
"object backing %select{|the pointer }0%1 "
"will be destroyed at the end of the full-expression">,
InGroup;
+def warn_dangling_reference_captured : Warning<
+   "object captured by '%0' will be destroyed at the end of the 
full-expression">,
+   InGroup;
 
 // For non-floating point, expressions of the form x == x or x != x
 // should result in a warning, since these always evaluate to a constant.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d6f3508a5243f3..6ea6c67447b6f0 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2323,6 +2323,9 @@ class Sema final : public SemaBase {
   bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res, bool FPOnly = 
false);
   bool BuiltinVectorToScalarMath(CallExpr *TheCall);
 
+  void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
+  const Expr *ThisArg, ArrayRef 
Args);
+
   /// Handles the checks for format strings, non-POD arguments to vararg
   /// functions, NULL arguments passed to non-NULL parameters, diagnose_if
   /// attributes and AArch64 SME attributes.
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp 
b/clang/lib/Sema/CheckExprLifetime.cpp
index a1a402b4a2b530..81e26f48fb8851 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -45,10 +45,14 @@ enum LifetimeKind {
   /// a default member initializer), the program is ill-formed.
   LK_MemInitializer,
 
-  /// The lifetime of a temporary bound to this entity probably ends too soon,
+  /// The lifetime of a temporary bound to this entity may end too soon,
   /// because the entity is a pointer and we assign the address of a temporary
   /// object to it.
   LK_Assignment,
+
+  /// The lifetime of a temporary bound to this entity may end too soon,
+  /// because the enti

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/115921

>From 3c233df64906972016c26909263cfd53940d87a0 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena 
Date: Tue, 12 Nov 2024 04:28:37 +
Subject: [PATCH 1/4] Reapply "[clang] Introduce
 [[clang::lifetime_capture_by(X)]] (#111499)"

This reverts commit 3a03513fc6ef8f3272d33be19164243c9dbf0452.
---
 clang/docs/ReleaseNotes.rst   |   3 +
 clang/include/clang/Basic/Attr.td |  33 ++
 clang/include/clang/Basic/AttrDocs.td |  69 +++
 .../clang/Basic/DiagnosticSemaKinds.td|  14 +++
 clang/include/clang/Sema/Sema.h   |   8 ++
 clang/lib/AST/TypePrinter.cpp |  15 +++
 clang/lib/Sema/SemaDecl.cpp   |   1 +
 clang/lib/Sema/SemaDeclAttr.cpp   | 111 ++
 clang/lib/Sema/SemaType.cpp   |  13 ++
 clang/test/AST/attr-lifetime-capture-by.cpp   |   9 ++
 .../test/SemaCXX/attr-lifetime-capture-by.cpp |  46 
 11 files changed, 322 insertions(+)
 create mode 100644 clang/test/AST/attr-lifetime-capture-by.cpp
 create mode 100644 clang/test/SemaCXX/attr-lifetime-capture-by.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 4ef48bed58d95c..482b30848b57e8 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -449,6 +449,9 @@ Attribute Changes in Clang
 - Fix a bug where clang doesn't automatically apply the ``[[gsl::Owner]]`` or
   ``[[gsl::Pointer]]`` to STL explicit template specialization decls. 
(#GH109442)
 
+- Clang now supports ``[[clang::lifetime_capture_by(X)]]``. Similar to 
lifetimebound, this can be
+  used to specify when a reference to a function parameter is captured by 
another capturing entity ``X``.
+
 Improvements to Clang's diagnostics
 ---
 
diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index a631e81d40aa68..6a77967c32cbcb 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1889,6 +1889,39 @@ def LifetimeBound : DeclOrTypeAttr {
   let SimpleHandler = 1;
 }
 
+def LifetimeCaptureBy : DeclOrTypeAttr {
+  let Spellings = [Clang<"lifetime_capture_by", 0>];
+  let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
+  let Args = [VariadicParamOrParamIdxArgument<"Params">];
+  let Documentation = [LifetimeCaptureByDocs];
+  let AdditionalMembers = [{
+private:
+  SmallVector ArgIdents;
+  SmallVector ArgLocs;
+
+public:
+  static constexpr int THIS = 0;
+  static constexpr int INVALID = -1;
+  static constexpr int UNKNOWN = -2;
+  static constexpr int GLOBAL = -3;
+
+  void setArgs(SmallVector&& Idents,
+   SmallVector&& Locs) { 
+assert(Idents.size() == Locs.size());
+assert(Idents.size() == params_Size);
+ArgIdents = std::move(Idents);
+ArgLocs = std::move(Locs);
+  }
+  
+  ArrayRef getArgIdents() const { return ArgIdents; }
+  ArrayRef getArgLocs() const { return ArgLocs; }
+  void setParamIdx(size_t Idx, int Val) { 
+assert(Idx < params_Size);
+params_[Idx] = Val;
+  }
+}];
+}
+
 def TrivialABI : InheritableAttr {
   // This attribute does not have a C [[]] spelling because it requires the
   // CPlusPlus language option.
diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index b64dbef6332e6a..21fcd183e8969c 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -3918,6 +3918,75 @@ have their lifetimes extended.
   }];
 }
 
+def LifetimeCaptureByDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+Similar to `lifetimebound`_, the ``lifetime_capture_by(X)`` attribute on a 
function
+parameter or implicit object parameter indicates that that objects that are 
referred to
+by that parameter may also be referred to by the capturing entity ``X``.
+
+By default, a reference is considered to refer to its referenced object, a
+pointer is considered to refer to its pointee, a ``std::initializer_list``
+is considered to refer to its underlying array, and aggregates (arrays and
+simple ``struct``\s) are considered to refer to all objects that their
+transitive subobjects refer to.
+
+The capturing entity ``X`` can be one of the following:
+- Another (named) function parameter. 
+  
+  .. code-block:: c++
+
+void addToSet(std::string_view a [[clang::lifetime_capture_by(s)]], 
std::set& s) {
+  s.insert(a);
+}
+
+- ``this`` (in case of member functions).
+  
+  .. code-block:: c++
+
+class S {
+  void addToSet(std::string_view a [[clang::lifetime_capture_by(this)]]) {
+s.insert(a);
+  }
+  std::set s;
+};
+
+- 'global', 'unknown' (without quotes).
+  
+  .. code-block:: c++
+
+std::set s;
+void addToSet(std::string_view a [[clang::lifetime_capture_by(global)]]) {
+  s.insert(a);
+}
+void addSomewhere(std::string_v

[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Gábor Horváth via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], 
S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], 
S&s);
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error 
{{'lifetime_capture_by' argument references itself}}
+};
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object captured by 's' will be destroyed 
at the end of the full-expression}}
+s);
+  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
+  captureInt(local, s);
+  noCaptureInt(1, s);
+  noCaptureInt(local, s);
+
+  // Capture lifetimebound pointer.
+  capturePointer(getPointerLB(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  capturePointer(getPointerLB(*getPointerLB(std::string())), s); // 
expected-warning {{object captured by 's'}}
+  capturePointer(getPointerNoLB(std::string()), s);
+
+  // Capture using std::string_view.
+  captureSV(local_sv, s);
+  captureSV(std::string(), // expected-warning {{object captured by 's'}}
+s);
+  captureSV(substr(
+  std::string() // expected-warning {{object captured by 's'}}
+  ), s);
+  captureSV(substr(local_s), s);
+  captureSV(strcopy(std::string()), s);
+  captureRValSV(std::move(local_sv), s);
+  captureRValSV(std::string(), s); // expected-warning {{object captured by 
's'}}
+  captureRValSV(std::string_view{"abcd"}, s);
+  captureRValSV(substr(local_s), s);
+  captureRValSV(substr(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  captureRValSV(strcopy(std::string()), s);
+  noCaptureSV(local_sv, s);
+  noCaptureSV(std::string(), s);
+  noCaptureSV(substr(std::string()), s);
+
+  // Capture using std::string.
+  captureS(std::string(), s); // expected-warning {{object captured by 's'}}
+  captureS(local_s, s);
+  captureRValS(std::move(local_s), s);
+  captureRValS(std::string(), s); // expected-warning {{object captured by 
's'}}
+
+  // Member functions.
+  s.captureInt(1); // expected-warning {{object captured by 's'}}
+  s.captureSV(std::string()); // expected-warning {{object captured by 's'}}
+  s.captureSV(substr(std::string())); // expected-warning {{object captured by 
's'}}
+  s.captureSV(strcopy(std::string()));
+
+  // 'this' is captured.
+  ThisIsCaptured{}.capture(s); // expected-warning {{object captured by 's'}}
+  ThisIsCaptured TIS;
+  TIS.capture(s);
+}
+class [[gsl::Pointer()]] my_string_view : public std::string_view {};
+class my_string_view_not_pointer : public std::string_view {};
+std::optional getOptionalSV();
+std::optional getOptionalS();
+std::optional getOptionalMySV();
+std::optional getOptionalMySVNotP();
+my_string_view getMySV();
+my_string_view_not_pointer getMySVNotP();
+
+template
+struct MySet {
+void insert(T&& t [[clang::lifetime_capture_by(this)]]);
+void insert(const T& t [[clang::lifetime_capture_by(this)]]);
+};
+void user_defined_containers() {
+  MySet set_of_int;
+  set_of_int.insert(1); // expected-warning {{object captured by 'set_of_int' 
will be destroyed}}

Xazax-hun wrote:

This is a false positive, right? We should probably have a FIXME or something 
similar. 
Also, I wonder if we could suppress warnings from template instantiations until 
we figure out how to handle these cases. 

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)

2024-11-13 Thread Gábor Horváth via cfe-commits


@@ -793,3 +793,108 @@ void test13() {
 }
 
 } // namespace GH100526
+
+namespace lifetime_capture_by {
+struct S {
+  const int *x;
+  void captureInt(const int&x [[clang::lifetime_capture_by(this)]]) { this->x 
= &x; }
+  void captureSV(std::string_view sv [[clang::lifetime_capture_by(this)]]);
+};
+///
+// Detect dangling cases.
+///
+void captureInt(const int&x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValInt(int&&x [[clang::lifetime_capture_by(s)]], S&s);
+void noCaptureInt(int x [[clang::lifetime_capture_by(s)]], S&s);
+std::string_view substr(const std::string& s [[clang::lifetimebound]]);
+std::string_view strcopy(const std::string& s);
+void captureSV(std::string_view x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValSV(std::string_view&& x [[clang::lifetime_capture_by(s)]], 
S&s);
+void noCaptureSV(std::string_view x, S&s);
+void captureS(const std::string& x [[clang::lifetime_capture_by(s)]], S&s);
+void captureRValS(std::string&& x [[clang::lifetime_capture_by(s)]], S&s);
+const std::string* getPointerLB(const std::string& s[[clang::lifetimebound]]);
+const std::string* getPointerNoLB(const std::string& s);
+void capturePointer(const std::string* x [[clang::lifetime_capture_by(s)]], 
S&s);
+struct ThisIsCaptured {
+  void capture(S& s) [[clang::lifetime_capture_by(s)]];
+  void bar(S& s) [[clang::lifetime_capture_by(abcd)]]; // expected-error 
{{'lifetime_capture_by' attribute argument 'abcd' is not a known function 
parameter}}
+  void baz(S& s) [[clang::lifetime_capture_by(this)]]; // expected-error 
{{'lifetime_capture_by' argument references itself}}
+};
+void use() {
+  std::string_view local_sv;
+  std::string local_s;
+  S s;
+  // Capture an 'int'.
+  int local;
+  captureInt(1, // expected-warning {{object captured by 's' will be destroyed 
at the end of the full-expression}}
+s);
+  captureRValInt(1, s); // expected-warning {{object captured by 's'}}
+  captureInt(local, s);
+  noCaptureInt(1, s);
+  noCaptureInt(local, s);
+
+  // Capture lifetimebound pointer.
+  capturePointer(getPointerLB(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  capturePointer(getPointerLB(*getPointerLB(std::string())), s); // 
expected-warning {{object captured by 's'}}
+  capturePointer(getPointerNoLB(std::string()), s);
+
+  // Capture using std::string_view.
+  captureSV(local_sv, s);
+  captureSV(std::string(), // expected-warning {{object captured by 's'}}
+s);
+  captureSV(substr(
+  std::string() // expected-warning {{object captured by 's'}}
+  ), s);
+  captureSV(substr(local_s), s);
+  captureSV(strcopy(std::string()), s);
+  captureRValSV(std::move(local_sv), s);
+  captureRValSV(std::string(), s); // expected-warning {{object captured by 
's'}}
+  captureRValSV(std::string_view{"abcd"}, s);
+  captureRValSV(substr(local_s), s);
+  captureRValSV(substr(std::string()), s); // expected-warning {{object 
captured by 's'}}
+  captureRValSV(strcopy(std::string()), s);
+  noCaptureSV(local_sv, s);
+  noCaptureSV(std::string(), s);
+  noCaptureSV(substr(std::string()), s);
+
+  // Capture using std::string.
+  captureS(std::string(), s); // expected-warning {{object captured by 's'}}
+  captureS(local_s, s);
+  captureRValS(std::move(local_s), s);
+  captureRValS(std::string(), s); // expected-warning {{object captured by 
's'}}
+
+  // Member functions.
+  s.captureInt(1); // expected-warning {{object captured by 's'}}
+  s.captureSV(std::string()); // expected-warning {{object captured by 's'}}
+  s.captureSV(substr(std::string())); // expected-warning {{object captured by 
's'}}
+  s.captureSV(strcopy(std::string()));
+
+  // 'this' is captured.
+  ThisIsCaptured{}.capture(s); // expected-warning {{object captured by 's'}}
+  ThisIsCaptured TIS;
+  TIS.capture(s);
+}
+class [[gsl::Pointer()]] my_string_view : public std::string_view {};
+class my_string_view_not_pointer : public std::string_view {};
+std::optional getOptionalSV();
+std::optional getOptionalS();
+std::optional getOptionalMySV();
+std::optional getOptionalMySVNotP();
+my_string_view getMySV();
+my_string_view_not_pointer getMySVNotP();
+
+template
+struct MySet {
+void insert(T&& t [[clang::lifetime_capture_by(this)]]);
+void insert(const T& t [[clang::lifetime_capture_by(this)]]);
+};
+void user_defined_containers() {
+  MySet set_of_int;
+  set_of_int.insert(1); // expected-warning {{object captured by 'set_of_int' 
will be destroyed}}
+  MySet set_of_sv;
+  set_of_sv.insert(std::string());  // expected-warning {{object captured by 
'set_of_sv' will be destroyed}}
+}
+} // namespace lifetime_capture_by
+// Test for templated code.
+// 2 nested function calls foo(sv, bar(sv, setsv));

Xazax-hun wrote:

Nit: add line break at the end.

https://github.com/llvm/llvm-project/pull/115921
___
cfe-commits mailing list
cfe-commits@lists.llvm.

  1   2   >