[clang] [Lifetime Safety] Name operator calls in alias-chain diagnostics (PR #221803)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 7 12:04:48 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: PushkarSingh (iitianpushkar)
<details>
<summary>Changes</summary>
This updates Lifetime Safety alias-chain diagnostics to identify overloaded operator and conversion calls instead of describing them generically as an `expression`.
Previously, these calls were reported as `expression aliases the storage of ...`, which could make it unclear which operation introduced the alias. The diagnostic now reports the callee using the existing diagnostic name printer, producing descriptions such as:
- `result of call to 'operator->'`
- `result of call to 'operator()'`
- `result of call to 'operator basic_string_view'`
Template arguments are preserved for operator function-template specializations. For example:
- `result of call to 'operator+<const int>'`
- `result of call to 'operator-<__gnu_cxx::basic_iterator<const int>>'`
This makes each aliasing step explicit and identifies the particular call responsible for propagating the alias.
References to [#<!-- -->220248#discussion_r3905817437](https://github.com/llvm/llvm-project/pull/220248#discussion_r3905817437)
---
Patch is 33.79 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/221803.diff
4 Files Affected:
- (modified) clang/lib/Sema/SemaLifetimeSafety.h (-2)
- (modified) clang/test/Sema/LifetimeSafety/invalidations.cpp (+18-16)
- (modified) clang/test/Sema/LifetimeSafety/nocfg.cpp (+14-14)
- (modified) clang/test/Sema/LifetimeSafety/safety.cpp (+14-14)
``````````diff
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index 0fd3486bcee46..620032c27f955 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -687,8 +687,6 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
const auto *FD = CE->getDirectCallee();
if (!FD)
return "result of call";
- if (FD->isOverloadedOperator() || isa<CXXConversionDecl>(FD))
- return "expression";
std::string Name;
llvm::raw_string_ostream OS(Name);
FD->getNameForDiagnostic(OS, S.getPrintingPolicy(),
diff --git a/clang/test/Sema/LifetimeSafety/invalidations.cpp b/clang/test/Sema/LifetimeSafety/invalidations.cpp
index 127e375bc023c..593ba92cf7443 100644
--- a/clang/test/Sema/LifetimeSafety/invalidations.cpp
+++ b/clang/test/Sema/LifetimeSafety/invalidations.cpp
@@ -271,7 +271,7 @@ void IteratorUsedAfterPreIncrement() {
std::vector<int> v;
auto it = v.begin(); // expected-warning {{local variable 'v' is later invalidated}} \
// expected-note {{result of call to 'begin' aliases the storage of local variable 'v'}}
- auto next = ++it; // expected-note {{expression aliases the storage of local variable 'v'}}
+ auto next = ++it; // expected-note {{result of call to 'operator++' aliases the storage of local variable 'v'}}
v.push_back(1); // expected-note {{local variable 'v' is invalidated here}}
(void)*next; // expected-note {{later used here}}
}
@@ -279,7 +279,7 @@ void IteratorUsedAfterPreIncrement() {
void IteratorUsedAfterPostDecrement(std::vector<int> v) {
auto it = v.rbegin(); // expected-warning {{parameter 'v' is later invalidated}} \
// expected-note {{result of call to 'rbegin' aliases the storage of parameter 'v'}}
- auto prev = it--; // expected-note {{expression aliases the storage of parameter 'v'}}
+ auto prev = it--; // expected-note {{result of call to 'operator--' aliases the storage of parameter 'v'}}
v.push_back(1); // expected-note {{parameter 'v' is invalidated here}}
(void)*prev; // expected-note {{later used here}}
}
@@ -288,7 +288,7 @@ void IteratorUsedAfterAddition() {
std::vector<int> v;
auto it = v.cbegin(); // expected-warning {{local variable 'v' is later invalidated}} \
// expected-note {{result of call to 'cbegin' aliases the storage of local variable 'v'}}
- auto next = it + 5; // expected-note {{expression aliases the storage of local variable 'v'}}
+ auto next = it + 5; // expected-note {{result of call to 'operator+' aliases the storage of local variable 'v'}}
v.push_back(1); // expected-note {{local variable 'v' is invalidated here}}
(void)*next; // expected-note {{later used here}}
}
@@ -297,7 +297,7 @@ void IteratorUsedAfterReverseSubtraction(std::vector<int> v) {
auto it = v.crbegin(); // expected-warning {{parameter 'v' is later invalidated}} \
// expected-note {{result of call to 'crbegin' aliases the storage of parameter 'v'}}
auto prev = 5 - it; // expected-note {{local variable 'it' aliases the storage of parameter 'v'}} \
- // expected-note {{expression aliases the storage of parameter 'v'}}
+ // expected-note {{result of call to 'operator-<__gnu_cxx::basic_iterator<const int>>' aliases the storage of parameter 'v'}}
v.push_back(1); // expected-note {{parameter 'v' is invalidated here}}
(void)*prev; // expected-note {{later used here}}
}
@@ -305,7 +305,7 @@ void IteratorUsedAfterReverseSubtraction(std::vector<int> v) {
void IteratorUsedAfterAddAdd(std::vector<int> v) {
auto it = v.cbegin(); // expected-warning {{parameter 'v' is later invalidated}} \
// expected-note {{result of call to 'cbegin' aliases the storage of parameter 'v'}}
- auto next = (it + 5) + 5; // expected-note 2 {{expression aliases the storage of parameter 'v'}}
+ auto next = (it + 5) + 5; // expected-note 2 {{result of call to 'operator+' aliases the storage of parameter 'v'}}
v.push_back(1); // expected-note {{parameter 'v' is invalidated here}}
(void)*next; // expected-note {{later used here}}
}
@@ -314,7 +314,8 @@ void IteratorUsedAfterMixedAddition() {
std::vector<int> v;
auto it = v.cbegin(); // expected-warning {{local variable 'v' is later invalidated}} \
// expected-note {{result of call to 'cbegin' aliases the storage of local variable 'v'}}
- auto next = 1 + it + 2 + 3; // expected-note 3 {{expression aliases the storage of local variable 'v'}} \
+ auto next = 1 + it + 2 + 3; // expected-note {{result of call to 'operator+<const int>' aliases the storage of local variable 'v'}} \
+ // expected-note 2 {{result of call to 'operator+' aliases the storage of local variable 'v'}} \
// expected-note {{local variable 'it' aliases the storage of local variable 'v'}}
v.push_back(1); // expected-note {{local variable 'v' is invalidated here}}
(void)*next; // expected-note {{later used here}}
@@ -323,7 +324,8 @@ void IteratorUsedAfterMixedAddition() {
void IteratorUsedAfterPreIncrementAddAssign(std::vector<int> v) {
auto it = v.begin(); // expected-warning {{parameter 'v' is later invalidated}} \
// expected-note {{result of call to 'begin' aliases the storage of parameter 'v'}}
- it = ++it + 1 + 2; // expected-note 3 {{expression aliases the storage of parameter 'v'}}
+ it = ++it + 1 + 2; // expected-note {{result of call to 'operator++' aliases the storage of parameter 'v'}} \
+ // expected-note 2 {{result of call to 'operator+' aliases the storage of parameter 'v'}}
v.push_back(1); // expected-note {{parameter 'v' is invalidated here}}
(void)*it; // expected-note {{later used here}}
}
@@ -331,7 +333,7 @@ void IteratorUsedAfterPreIncrementAddAssign(std::vector<int> v) {
void IteratorUsedAfterBeginAddAssign() {
std::vector<int> v;
auto it = v.begin() + 1; // expected-warning {{local variable 'v' is later invalidated}} \
- // expected-note {{expression aliases the storage of local variable 'v'}} \
+ // expected-note {{result of call to 'operator+' aliases the storage of local variable 'v'}} \
// expected-note {{result of call to 'begin' aliases the storage of local variable 'v'}}
v.push_back(1); // expected-note {{local variable 'v' is invalidated here}}
(void)*it; // expected-note {{later used here}}
@@ -341,7 +343,7 @@ void IteratorUsedAfterStdBeginAddAssign() {
std::vector<int> v;
std::vector<int>::iterator it;
it = std::begin(v) + 1; // expected-warning {{local variable 'v' is later invalidated}} \
- // expected-note {{expression aliases the storage of local variable 'v'}} \
+ // expected-note {{result of call to 'operator+' aliases the storage of local variable 'v'}} \
// expected-note {{result of call to 'begin<std::vector<int>>' aliases the storage of local variable 'v'}}
v.push_back(1); // expected-note {{local variable 'v' is invalidated here}}
(void)*it; // expected-note {{later used here}}
@@ -404,7 +406,7 @@ namespace ElementReferences {
void ReferenceToVectorElement() {
std::vector<int> v = {1, 2, 3};
int& ref = v[0]; // expected-warning {{local variable 'v' is later invalidated}} \
- // expected-note {{expression aliases the storage of local variable 'v'}}
+ // expected-note {{result of call to 'operator[]' aliases the storage of local variable 'v'}}
v.push_back(4); // expected-note {{local variable 'v' is invalidated here}}
ref = 10; // expected-note {{later used here}}
(void)ref;
@@ -413,7 +415,7 @@ void ReferenceToVectorElement() {
void PointerRefToVectorElement() {
std::vector<int*> v = {nullptr, nullptr};
int*& ref = v[0]; // expected-warning {{local variable 'v' is later invalidated}} \
- // expected-note {{expression aliases the storage of local variable 'v'}}
+ // expected-note {{result of call to 'operator[]' aliases the storage of local variable 'v'}}
v.push_back(nullptr); // expected-note {{local variable 'v' is invalidated here}}
ref = nullptr; // expected-note {{later used here}}
}
@@ -421,7 +423,7 @@ void PointerRefToVectorElement() {
void PointerToVectorElement() {
std::vector<int> v = {1, 2, 3};
int* ptr = &v[0]; // expected-warning {{local variable 'v' is later invalidated}} \
- // expected-note {{expression aliases the storage of local variable 'v'}}
+ // expected-note {{result of call to 'operator[]' aliases the storage of local variable 'v'}}
v.resize(100); // expected-note {{local variable 'v' is invalidated here}}
*ptr = 10; // expected-note {{later used here}}
}
@@ -447,7 +449,7 @@ void SelfInvalidatingMap() {
// expected-note {{local variable 'mp' is invalidated here}} \
// expected-note {{later used here}} \
// expected-note {{local variable 'mp' is invalidated here}} \
- // expected-note {{expression aliases the storage of local variable 'mp'}} \
+ // expected-note {{result of call to 'operator[]' aliases the storage of local variable 'mp'}} \
// expected-note {{later used here}}
}
@@ -798,7 +800,7 @@ void FlatMapSubscriptMultipleCallsInvalidate(std::flat_map<int, int> mp, int a,
// expected-note {{parameter 'mp' is invalidated here}} \
// expected-note {{later used here}} \
// expected-note {{parameter 'mp' is invalidated here}} \
- // expected-note 2 {{expression aliases the storage of parameter 'mp'}} \
+ // expected-note 2 {{result of call to 'operator[]' aliases the storage of parameter 'mp'}} \
// expected-note {{later used here}}
}
@@ -851,7 +853,7 @@ struct S {
void baz(){
std::vector<std::string> vec = {"42"};
v = vec[0]; // expected-warning {{local variable 'vec' is later invalidated}} \
- // expected-note {{expression aliases the storage of local variable 'vec'}}
+ // expected-note {{result of call to 'operator[]' aliases the storage of local variable 'vec'}}
vec.push_back("1"); // expected-note {{local variable 'vec' is invalidated here}}
bar(); // expected-note {{later used here}}
v = nullptr;
@@ -865,7 +867,7 @@ void function_captured_ref_invalidated() {
std::vector<int> v;
v.push_back(1);
std::function<void()> f = [&r = v[0]]() { (void)r; }; // expected-warning {{local variable 'v' is later invalidated}} \
- // expected-note {{expression aliases the storage of local variable 'v'}}
+ // expected-note {{result of call to 'operator[]' aliases the storage of local variable 'v'}}
v.push_back(2); // expected-note {{local variable 'v' is invalidated here}}
(void)f; // expected-note {{later used here}}
}
diff --git a/clang/test/Sema/LifetimeSafety/nocfg.cpp b/clang/test/Sema/LifetimeSafety/nocfg.cpp
index 5c5f0e6f7ce52..7ef209572c437 100644
--- a/clang/test/Sema/LifetimeSafety/nocfg.cpp
+++ b/clang/test/Sema/LifetimeSafety/nocfg.cpp
@@ -310,11 +310,11 @@ std::string_view danglingRefToOptionalFromTemp4() {
void danglingReferenceFromTempOwner() {
int &&r = *std::optional<int>(); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
// cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \
- // cfg-note {{expression aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
+ // cfg-note {{result of call to 'operator*' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
// https://github.com/llvm/llvm-project/issues/175893
int &&r2 = *std::optional<int>(5); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
// cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \
- // cfg-note {{expression aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
+ // cfg-note {{result of call to 'operator*' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
// https://github.com/llvm/llvm-project/issues/175893
int &&r3 = std::optional<int>(5).value(); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
@@ -331,7 +331,7 @@ void danglingReferenceFromTempOwner() {
std::string_view sv = *getTempOptStr(); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
// cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \
- // cfg-note {{expression aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
+ // cfg-note {{result of call to 'operator*' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
use(sv); // cfg-note {{later used here}}
}
@@ -343,7 +343,7 @@ void testLoops() {
;
for (auto i : *getTempOptVec()) // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
// cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} cfg-note {{later used here}} \
- // cfg-note {{expression aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
+ // cfg-note {{result of call to 'operator*' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
;
}
@@ -1050,15 +1050,15 @@ void operator_star_arrow_reference() {
auto temporary = []() { return std::vector<std::string>{{"1"}}; };
const char* x = temporary().begin()->data(); // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \
// cfg-note {{result of call to 'begin' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
- // cfg-note {{expression aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
+ // cfg-note {{result of call to 'operator->' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
// cfg-note {{result of call to 'data' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
const char* y = (*temporary().begin()).data(); // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \
// cfg-note {{result of call to 'begin' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
- // cfg-note {{expression aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
+ // cfg-note {{result of call to 'operator*' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
// cfg-note {{result of call to 'data' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
const std::string& z = (*temporary().begin()); // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \
// cfg-note {{result of call to 'begin' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
- // cfg-note {{expression aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
+ // cfg-note {{result of call to 'operator*' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
use(p, q, r, x, y, z); // cfg-note 3 {{later used here}}
}
@@ -1072,15 +1072,15 @@ void operator_star_arrow_of_iterators_false_positive_no_cfg_analysis() {
auto temporary = []() { return std::vector<std::pair<int, std::string>>{{1, "1"}}; };
const char* x = temporary().begin()->second.data(); // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \
// cfg-note {{result of call to 'begin' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
- // cfg-note {{expression aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
+ // cfg-note {{result of call to 'operator->' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
// cfg-note {{result of call to 'data' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
const char* y = (*temporary().begin()).second.data(); // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \
// cfg-note {{result of call to 'begin' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
- // cfg-note {{expression aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
+ // cfg-note {{result of call to 'operator*' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
// cfg-note {{result of call to 'data' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}}
const std::string& z = (*temporary().begin()).second; // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \
// cfg-note {{result of call to 'begin' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \
- // cfg-note {{expression aliases the storage of tem...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/221803
More information about the cfe-commits
mailing list