blob: 973e095fb68b464a35cc864a727b2a64b731bdc4 [file] [edit]
// RUN: %clang_cc1 -fsyntax-only -Wlifetime-safety -Wno-dangling -verify %s
#include "Inputs/lifetime-analysis.h"
bool Bool();
namespace SimpleResize {
void IteratorInvalidAfterResize(int new_size) {
std::vector<int> v;
auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
v.resize(new_size); // expected-note {{invalidated here}}
*it; // expected-note {{later used here}}
}
void IteratorValidAfterResize(int new_size) {
std::vector<int> v;
auto it = std::begin(v);
v.resize(new_size);
it = std::begin(v);
if (it != std::end(v)) {
*it; // ok
}
}
} // namespace SimpleResize
namespace CheckModel {
void IteratorValidAfterCheck() {
std::vector<int> v;
auto it = v.begin();
*it; // ok
}
} // namespace CheckModel
namespace PointerToContainer {
std::vector<int>* GetContainerPointer();
void PointerToContainerTest() {
// FIXME: Use opaque loans.
std::vector<int>* v = GetContainerPointer();
auto it = v->begin();
*it = 0; // not-ok
}
void PointerToContainerTest(std::vector<int>* v) {
// FIXME: Handle placeholder loans.
auto it = v->begin();
*it = 0; // not-ok
}
} // namespace PointerToContainer
namespace InvalidateBeforeSwap {
void InvalidateBeforeSwapIterators(std::vector<int> v1, std::vector<int> v2) {
auto it1 = std::begin(v1); // expected-warning {{object whose reference is captured is later invalidated}}
auto it2 = std::begin(v2);
if (it1 == std::end(v1) || it2 == std::end(v2)) return;
*it1 = 0; // ok
*it2 = 0; // ok
v1.clear(); // expected-note {{invalidated here}}
*it1 = 0; // expected-note {{later used here}}
// FIXME: Handle invalidating functions like std::swap.
std::swap(it1, it2);
*it1 = 0; // ok
*it2 = 0; // not-ok
}
void InvalidateBeforeSwapContainers(std::vector<int> v1, std::vector<int> v2) {
auto it1 = std::begin(v1); // expected-warning {{object whose reference is captured is later invalidated}}
auto it2 = std::begin(v2);
if (it1 == std::end(v1) || it2 == std::end(v2)) return;
*it1 = 0; // ok
*it2 = 0; // ok
v1.clear(); // expected-note {{invalidated here}}
*it1 = 0; // expected-note {{later used here}}
}
} // namespace InvalidateBeforeSwap
namespace MergeConditionBasic {
bool A();
bool B();
void SameConditionInvalidatesThenValidatesIterator() {
std::vector<int> container;
auto it = container.begin(); // expected-warning {{object whose reference is captured is later invalidated}}
if (it == container.end()) return;
const bool a = A();
if (a) {
container.clear(); // expected-note {{invalidated here}}
}
if (a) {
it = container.begin();
if (it == std::end(container)) return;
}
*it = 10; // expected-note {{later used here}}
}
} // namespace MergeConditionBasic
namespace IteratorWithMultipleContainers {
void MergeWithDifferentContainerValuesIteratorNotInvalidated() {
std::vector<int> v1, v2, v3;
auto it = std::find(v1.begin(), v1.end(), 10);
if (Bool()) {
it = std::find(v2.begin(), v2.end(), 10);
} else {
it = std::find(v3.begin(), v3.end(), 10);
}
v1.clear();
*it = 20;
}
void MergeWithDifferentContainerValuesInvalidated() {
std::vector<int> v1, v2, v3;
auto it = std::find(v1.begin(), v1.end(), 10);
if (Bool()) {
it = std::find(v2.begin(), v2.end(), 10); // expected-warning {{object whose reference is captured is later invalidated}}
} else {
it = std::find(v3.begin(), v3.end(), 10);
}
v2.clear(); // expected-note {{invalidated here}}
*it = 20; // expected-note {{later used here}}
}
} // namespace IteratorWithMultipleContainers
namespace InvalidationInLoops {
void IteratorInvalidationInAForLoop(std::vector<int> v) {
for (auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
it != std::end(v);
++it) { // expected-note {{later used here}}
if (Bool()) {
v.erase(it); // expected-note {{invalidated here}}
}
}
}
void IteratorInvalidationInAWhileLoop(std::vector<int> v) {
auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
while (it != std::end(v)) {
if (Bool()) {
v.erase(it); // expected-note {{invalidated here}}
}
++it; // expected-note {{later used here}}
}
}
void NoIteratorInvalidationInAWhileLoopErase(std::unordered_map<int, int> mp) {
auto it = mp.begin();
while (it != std::end(mp)) {
if (Bool()) {
auto next = it;
++next;
mp.erase(it); // Ok. 'next' remains valid.
it = next;
}
else {
++it;
}
}
}
void IteratorInvalidationInAForeachLoop(std::vector<int> v) {
for (int& x : v) { // expected-warning {{object whose reference is captured is later invalidated}} \
// expected-note {{later used here}}
if (x % 2 == 0) {
v.erase(std::find(v.begin(), v.end(), 1)); // expected-note {{invalidated here}}
}
}
}
} // namespace InvalidationInLoops
namespace StdVectorPopBack {
void StdVectorPopBackDoesNotInvalidateOthers(std::vector<int> v) {
auto it = v.begin();
if (it == v.end()) return;
*it;
v.pop_back();
*it;
}
} // namespace StdVectorPopBack
namespace SimpleStdFind {
void IteratorCheckedAfterFind(std::vector<int> v) {
auto it = std::find(std::begin(v), std::end(v), 3);
if (it != std::end(v)) {
*it; // ok
}
}
void IteratorCheckedAfterFindThenErased(std::vector<int> v) {
auto it = std::find(std::begin(v), std::end(v), 3); // expected-warning {{object whose reference is captured is later invalidated}}
if (it != std::end(v)) {
v.erase(it); // expected-note {{invalidated here}}
}
*it; // expected-note {{later used here}}
}
} // namespace SimpleStdFind
namespace SimpleInsert {
void UseReturnedIteratorAfterInsert(std::vector<int> v) {
auto it = std::begin(v);
it = v.insert(it, 10);
if (it != std::end(v)) {
*it; // ok
}
}
void UseInvalidIteratorAfterInsert(std::vector<int> v) {
auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
v.insert(it, 10); // expected-note {{invalidated here}}
if (it != std::end(v)) { // expected-note {{later used here}}
*it;
}
}
} // namespace SimpleInsert
namespace SimpleStdInsert {
void IteratorValidAfterInsert(std::vector<int> v) {
auto it = std::begin(v);
v.insert(it, 0);
it = std::begin(v);
if (it != std::end(v)) {
*it; // ok
}
}
void IteratorInvalidAfterInsert(std::vector<int> v, int value) {
auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
v.insert(it, 0); // expected-note {{invalidated here}}
*it; // expected-note {{later used here}}
}
} // namespace SimpleStdInsert
namespace SimpleInvalidIterators {
void IteratorUsedAfterErase(std::vector<int> v) {
auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
for (; it != std::end(v); ++it) { // expected-note {{later used here}}
if (*it > 3) {
v.erase(it); // expected-note {{invalidated here}}
}
}
}
// FIXME: Detect this. We currently skip invalidation through ref/pointers to containers.
void IteratorUsedAfterPushBackParam(std::vector<int>& v) {
auto it = std::begin(v);
if (it != std::end(v) && *it == 3) {
v.push_back(4);
}
++it;
}
void IteratorUsedAfterPushBack(std::vector<int> v) {
auto it = std::begin(v); // expected-warning {{object whose reference is captured is later invalidated}}
if (it != std::end(v) && *it == 3) {
v.push_back(4); // expected-note {{invalidated here}}
}
++it; // expected-note {{later used here}}
}
} // namespace SimpleInvalidIterators
namespace ElementReferences {
// Testing raw pointers and references to elements, not just iterators.
void ReferenceToVectorElement() {
std::vector<int> v = {1, 2, 3};
int& ref = v[0]; // expected-warning {{object whose reference is captured is later invalidated}}
v.push_back(4); // expected-note {{invalidated here}}
ref = 10; // expected-note {{later used here}}
(void)ref;
}
void PointerRefToVectorElement() {
std::vector<int*> v = {nullptr, nullptr};
int*& ref = v[0]; // expected-warning {{object whose reference is captured is later invalidated}}
v.push_back(nullptr); // expected-note {{invalidated here}}
ref = nullptr; // expected-note {{later used here}}
}
void PointerToVectorElement() {
std::vector<int> v = {1, 2, 3};
int* ptr = &v[0]; // expected-warning {{object whose reference is captured is later invalidated}}
v.resize(100); // expected-note {{invalidated here}}
*ptr = 10; // expected-note {{later used here}}
}
void SelfInvalidatingMap() {
std::flat_map<int, std::string> mp;
// TODO: We do not have a way to differentiate between pointer stability and iterator stability!
//
// std::unordered_map and other node-based containers provide pointer/reference stability.
// Therefore the following is safe in practice.
// On the other hand, std::flat_map (since C++23) does not provide pointer stability on
// insertion and following is unsafe for this container.
mp[1] = "42";
mp[2] // expected-note {{invalidated here}}
=
mp[1]; // expected-warning {{object whose reference is captured is later invalidated}} expected-note {{later used here}}
}
void InvalidateErase() {
std::flat_map<int, std::string> mp;
// None of these containers provide iterator stability. So following is unsafe:
auto it = mp.find(3); // expected-warning {{object whose reference is captured is later invalidated}}
mp.erase(mp.find(4)); // expected-note {{invalidated here}}
if (it != mp.end()) // expected-note {{later used here}}
*it;
}
} // namespace ElementReferences
namespace Strings {
void append(std::string str) {
std::string_view view = str; // expected-warning {{object whose reference is captured is later invalidated}}
str += "456"; // expected-note {{invalidated here}}
(void)view; // expected-note {{later used here}}
}
void reassign(std::string str, std::string str2) {
std::string_view view = str; // expected-warning {{object whose reference is captured is later invalidated}}
str = str2; // expected-note {{invalidated here}}
(void)view; // expected-note {{later used here}}
}
} // namespace Strings
// FIXME: This should be diagnosed as use-after-invalidation but with potential move.
void ReassigningAfterMove(std::string str, std::string str2) {
std::string_view view = str; // expected-warning {{object whose reference is captured is later invalidated}}
std::vector<std::string> someStorage;
someStorage.push_back(std::move(str));
str = str2; // expected-note {{invalidated here}}
(void)view; // expected-note {{later used here}}
}
namespace ContainersAsFields {
struct S {
std::vector<std::string> strings1;
std::vector<std::string> strings2;
};
// FIXME: Make Paths more precise to reason at field granularity.
// Currently we only detect invalidations to direct declarations and not members.
void Invalidate1Use1IsInvalid() {
// FIXME: Detect this.
S s;
auto it = s.strings1.begin();
s.strings1.push_back("1");
*it;
}
void Invalidate1Use2IsOk() {
S s;
auto it = s.strings1.begin();
s.strings2.push_back("1");
*it;
}void Invalidate1Use2ViaRefIsOk() {
S s;
auto it = s.strings2.begin();
auto& strings2 = s.strings2;
strings2.push_back("1");
*it;
}
void Invalidate1UseSIsOk() {
S s;
S* p = &s;
s.strings2.push_back("1");
(void)*p;
}
void PointerToContainerIsOk() {
std::vector<std::string> s;
std::vector<std::string>* p = &s;
p->push_back("1");
(void)*p;
}
void IteratorFromPointerToContainerIsInvalidated() {
// FIXME: Detect this.
std::vector<std::string> s;
std::vector<std::string>* p = &s;
auto it = p->begin();
p->push_back("1");
*it;
}
void ChangingRegionOwnedByContainerIsOk() {
std::vector<std::string> subdirs;
for (std::string& path : subdirs)
path = std::string();
}
} // namespace ContainersAsFields
namespace AssociativeContainers {
void SetInsertDoesNotInvalidate() {
std::set<int> s;
s.insert(0);
auto it = s.begin();
s.insert(2);
*it;
}
void MapInsertDoesNotInvalidate() {
std::map<int, int> m;
auto it = m.begin();
m.insert({1, 2});
*it;
}
void MapEmplaceDoesNotInvalidate() {
std::map<int, int> m;
auto it = m.begin();
m.emplace(1, 2);
*it;
}
void MultisetInsertDoesNotInvalidate() {
std::multiset<int> s;
auto it = s.begin();
s.insert(1);
*it;
}
void MultimapInsertDoesNotInvalidate() {
std::multimap<int, int> m;
auto it = m.begin();
m.insert({1, 2});
*it;
}
void SetEraseDoesNotInvalidateOthers() {
std::set<int> s;
s.insert(1);
s.insert(2);
auto it1 = s.begin();
auto it2 = it1;
++it2;
s.erase(it2);
*it1;
}
void SetExtractDoesNotInvalidateOthers() {
std::set<int> s;
s.insert(1);
s.insert(2);
auto it1 = s.begin();
auto it2 = it1;
++it2;
s.extract(it2);
*it1;
}
void SetClearInvalidates() {
std::set<int> s;
auto it = s.begin(); // expected-warning {{object whose reference is captured is later invalidated}}
s.clear(); // expected-note {{invalidated here}}
*it; // expected-note {{later used here}}
}
void MapClearInvalidates() {
std::map<int, int> m;
auto it = m.begin(); // expected-warning {{object whose reference is captured is later invalidated}}
m.clear(); // expected-note {{invalidated here}}
*it; // expected-note {{later used here}}
}
void MapSubscriptDoesNotInvalidate() {
std::map<int, int> m;
auto it = m.begin();
m[1];
*it;
}
void PrintMax(const int& a, const int& b);
void MapSubscriptMultipleCallsDoesNotInvalidate(std::map<int, int> mp, int a, int b) {
PrintMax(mp[a], mp[b]);
}
void FlatMapSubscriptMultipleCallsInvalidate(std::flat_map<int, int> mp, int a, int b) {
PrintMax(mp[a], mp[b]); // expected-warning {{object whose reference is captured is later invalidated}} \
// expected-note {{invalidated here}} \
// expected-note {{later used here}}
}
} // namespace AssociativeContainers
namespace lambda_capture_invalidation {
void captured_view_invalidated_by_owner() {
std::string s = "42";
std::string_view p = s; // expected-warning {{object whose reference is captured is later invalidated}}
auto lambda = [=]() { return p; };
s.push_back('c'); // expected-note {{invalidated here}}
lambda(); // expected-note {{later used here}}
}
void multiple_captures_one_invalidated() {
std::string s1 = "a", s2 = "b";
std::string_view p1 = s1, p2 = s2; // expected-warning {{object whose reference is captured is later invalidated}}
auto lambda = [=]() { return p1.size() + p2.size(); };
s1.clear(); // expected-note {{invalidated here}}
lambda(); // expected-note {{later used here}}
}
// FIXME: By-ref captures flow only the outermost origin, so
// invalidation of the captured view's pointee is not propagated.
void ref_capture_owner_invalidated() {
std::string s = "42";
std::string_view p = s;
auto lambda = [&]() { return p; };
s.push_back('c'); // invalidates p
lambda(); // should warn: use-after-invalidate
}
// FIXME: Once inner origins are tracked, this case must remain a no-warning.
// Reassigning `p` through the by-ref capture should invalidate the link to `s`.
void ref_capture_reassigned_to_safe() {
std::string s = "42", safe = "not modified";
std::string_view p = s;
auto lambda = [&]() { return p; };
p = safe; // p now points to 'safe', not 's'
s.push_back('c'); // does not invalidate p anymore
lambda(); // should not warn
}
} // namespace lambda_capture_invalidation
namespace method_call_uses_field_invalidation {
struct S {
std::string_view v;
void bar();
void baz(){
std::vector<std::string> vec = {"42"};
v = vec[0]; // expected-warning {{object whose reference is captured is later invalidated}}
vec.push_back("1"); // expected-note {{invalidated here}}
bar(); // expected-note {{later used here}}
v = nullptr;
}
};
} // namespace method_call_uses_field_invalidation
namespace callable_wrappers {
void function_captured_ref_invalidated() {
std::vector<int> v;
v.push_back(1);
std::function<void()> f = [&r = v[0]]() { (void)r; }; // expected-warning {{object whose reference is captured is later invalidated}}
v.push_back(2); // expected-note {{invalidated here}}
(void)f; // expected-note {{later used here}}
}
} // namespace callable_wrappers