[clang] [clang] Respect the lifetimebound in assignment operator. (PR #106997)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 2 07:43:44 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Haojian Wu (hokein)
<details>
<summary>Changes</summary>
Fixes #<!-- -->106372
---
Full diff: https://github.com/llvm/llvm-project/pull/106997.diff
5 Files Affected:
- (modified) clang/docs/ReleaseNotes.rst (+2)
- (modified) clang/lib/Sema/CheckExprLifetime.cpp (+42-23)
- (modified) clang/lib/Sema/CheckExprLifetime.h (+1)
- (modified) clang/lib/Sema/SemaOverload.cpp (+3-1)
- (modified) clang/test/SemaCXX/attr-lifetimebound.cpp (+15)
``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index fc940db4813948..f87080955b3302 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -266,6 +266,8 @@ Improvements to Clang's diagnostics
compilation speed with modules. This warning is disabled by default and it needs
to be explicitly enabled or by ``-Weverything``.
+- Clang now respects lifetimebound attribute for the assignment operator parameter. (#GH106372).
+
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index f28789dba34e10..776d0e8b07a30b 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -326,24 +326,11 @@ static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
return false;
}
-static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
- const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
- if (!TSI)
- return false;
- // Don't declare this variable in the second operand of the for-statement;
- // GCC miscompiles that by ending its lifetime before evaluating the
- // third operand. See gcc.gnu.org/PR86769.
- AttributedTypeLoc ATL;
- for (TypeLoc TL = TSI->getTypeLoc();
- (ATL = TL.getAsAdjusted<AttributedTypeLoc>());
- TL = ATL.getModifiedLoc()) {
- if (ATL.getAttrAs<LifetimeBoundAttr>())
- return true;
- }
-
- // Assume that all assignment operators with a "normal" return type return
- // *this, that is, an lvalue reference that is the same type as the implicit
- // object parameter (or the LHS for a non-member operator$=).
+// Return true if this is an "normal" assignment operator.
+// We assuments that a normal assingment operator always returns *this, that is,
+// an lvalue reference that is the same type as the implicit object parameter
+// (or the LHS for a non-member operator$=).
+static bool isNormalAsisgnmentOperator(const FunctionDecl *FD) {
OverloadedOperatorKind OO = FD->getDeclName().getCXXOverloadedOperator();
if (OO == OO_Equal || isCompoundAssignmentOperator(OO)) {
QualType RetT = FD->getReturnType();
@@ -359,10 +346,27 @@ static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
return true;
}
}
-
return false;
}
+static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
+ const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
+ if (!TSI)
+ return false;
+ // Don't declare this variable in the second operand of the for-statement;
+ // GCC miscompiles that by ending its lifetime before evaluating the
+ // third operand. See gcc.gnu.org/PR86769.
+ AttributedTypeLoc ATL;
+ for (TypeLoc TL = TSI->getTypeLoc();
+ (ATL = TL.getAsAdjusted<AttributedTypeLoc>());
+ TL = ATL.getModifiedLoc()) {
+ if (ATL.getAttrAs<LifetimeBoundAttr>())
+ return true;
+ }
+
+ return isNormalAsisgnmentOperator(FD);
+}
+
// Visit lifetimebound or gsl-pointer arguments.
static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
LocalVisitor Visit,
@@ -968,6 +972,23 @@ static bool pathOnlyHandlesGslPointer(IndirectLocalPath &Path) {
return false;
}
+static bool isAssginmentOperatorLifetimeBound(CXXMethodDecl *CMD) {
+ if (!CMD)
+ return false;
+ assert(CMD->getOverloadedOperator() == OverloadedOperatorKind::OO_Equal);
+ return isNormalAsisgnmentOperator(CMD) && CMD->param_size() == 1 &&
+ CMD->getParamDecl(0)->hasAttr<LifetimeBoundAttr>();
+}
+
+static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
+ const AssignedEntity &Entity) {
+ bool EnableGSLAssignmentWarnings = !SemaRef.getDiagnostics().isIgnored(
+ diag::warn_dangling_lifetime_pointer_assignment, SourceLocation());
+ return (EnableGSLAssignmentWarnings &&
+ (isRecordWithAttr<PointerAttr>(Entity.LHS->getType()) ||
+ isAssginmentOperatorLifetimeBound(Entity.AssignmentOperator)));
+}
+
static void checkExprLifetimeImpl(Sema &SemaRef,
const InitializedEntity *InitEntity,
const InitializedEntity *ExtendingEntity,
@@ -1267,8 +1288,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
};
llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
- if (EnableLifetimeWarnings && LK == LK_Assignment &&
- isRecordWithAttr<PointerAttr>(AEntity->LHS->getType()))
+ if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity))
Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init});
if (Init->isGLValue())
@@ -1301,8 +1321,7 @@ void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
diag::warn_dangling_pointer_assignment, SourceLocation());
bool RunAnalysis = (EnableDanglingPointerAssignment &&
Entity.LHS->getType()->isPointerType()) ||
- (EnableLifetimeWarnings &&
- isRecordWithAttr<PointerAttr>(Entity.LHS->getType()));
+ shouldRunGSLAssignmentAnalysis(SemaRef, Entity);
if (!RunAnalysis)
return;
diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h
index af381fb96c4d68..8c8d0806dee0a3 100644
--- a/clang/lib/Sema/CheckExprLifetime.h
+++ b/clang/lib/Sema/CheckExprLifetime.h
@@ -22,6 +22,7 @@ namespace clang::sema {
struct AssignedEntity {
// The left-hand side expression of the assignment.
Expr *LHS = nullptr;
+ CXXMethodDecl *AssignmentOperator = nullptr;
};
/// Check that the lifetime of the given expr (and its subobjects) is
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 95551173df91a5..861b0a91240b3b 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -14768,7 +14768,9 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
// Check for a self move.
DiagnoseSelfMove(Args[0], Args[1], OpLoc);
// lifetime check.
- checkExprLifetime(*this, AssignedEntity{Args[0]}, Args[1]);
+ checkExprLifetime(
+ *this, AssignedEntity{Args[0], dyn_cast<CXXMethodDecl>(FnDecl)},
+ Args[1]);
}
if (ImplicitThis) {
QualType ThisType = Context.getPointerType(ImplicitThis->getType());
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index 6566ed6270cd14..0bc06ef9cab3ab 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -287,3 +287,18 @@ std::span<int> test2() {
return abc; // expected-warning {{address of stack memory associated with local variable}}
}
} // namespace ctor_cases
+
+namespace GH106372 {
+class [[gsl::Owner]] Foo {};
+class [[gsl::Pointer]] FooView {};
+
+template <typename T>
+struct StatusOr {
+ template <typename U = T>
+ StatusOr& operator=(U&& v [[clang::lifetimebound]]);
+};
+
+void test(StatusOr<FooView> foo) {
+ foo = Foo(); // expected-warning {{object backing the pointer foo will be destroyed at the end}}
+}
+} // namespace GH106372
``````````
</details>
https://github.com/llvm/llvm-project/pull/106997
More information about the cfe-commits
mailing list