[clang] [clang] Implement lifetime analysis for lifetime_capture_by(X) (PR #115921)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
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)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
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)
@@ -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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
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)
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)
@@ -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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
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)
@@ -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)
@@ -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.