This patch introduces a new `CXXLifetimeExtendedObjectRegion` as a representation
of the memory for the temporary object that is lifetime extended by the reference
to which they are bound.
This separation provides an ability to detect the use of dangling pointers
(either binding or dereference) in a robust manner.
For example, the `ref` is conditionally dangling in the following example:
```
template<typename T>
T const& select(bool cond, T const& t, T const& u) { return cond ? t : u; }
int const& le = Composite{}.x;
auto&& ref = select(cond, le, 10);
```
Before the change, regardless of the value of `cond`, the `select()` call would
have returned a `temp_object` region.
With the proposed change we would produce a (non-dangling) `lifetime_extended_object`
region with lifetime bound to `le` or a `temp_object` region for the dangling case.
We believe that such separation is desired, as such lifetime extended temporaries
are closer to the variables. For example, they may have a static storage duration
(this patch removes a static temporary region, which was an abomination).
We also think that alternative approaches are not viable.
While for some cases it may be possible to determine if the region is lifetime
extended by searching the parents of the initializer expr, this quickly becomes
complex in the presence of the conditions operators like this one:
```
Composite cc;
// Ternary produces prvalue 'int' which is extended, as branches differ in value category
auto&& x = cond ? Composite{}.x : cc.x;
// Ternary produces xvalue, and extends the Composite object
auto&& y = cond ? Composite{}.x : std::move(cc).x;
```
Finally, the lifetime of the `CXXLifetimeExtendedObjectRegion` is tied to the lifetime of
the corresponding variables, however, the "liveness" (or reachability) of the extending
variable does not imply the reachability of all symbols in the region.
In conclusion `CXXLifetimeExtendedObjectRegion`, in contrast to `VarRegions`, does not
need any special handling in `SymReaper`.
RFC: https://discourse.llvm.org/t/rfc-detecting-uses-of-dangling-references/70731
Reviewed By: xazax.hun
Differential Revision: https://reviews.llvm.org/D151325
172 lines
9.6 KiB
C++
172 lines
9.6 KiB
C++
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\
|
|
// RUN: -analyzer-checker=debug.ExprInspection\
|
|
// RUN: -Wno-dangling -Wno-c++1z-extensions\
|
|
// RUN: -verify=expected,cpp14\
|
|
// RUN: -x c++ -std=c++14 %s
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\
|
|
// RUN: -analyzer-checker=debug.ExprInspection\
|
|
// RUN: -analyzer-config elide-constructors=false\
|
|
// RUN: -Wno-dangling -Wno-c++1z-extensions\
|
|
// RUN: -verify=expected,cpp14\
|
|
// RUN: -x c++ -std=c++14 %s
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core\
|
|
// RUN: -analyzer-checker=debug.ExprInspection\
|
|
// RUN: -Wno-dangling -verify=expected,cpp17\
|
|
// RUN: -x c++ -std=c++17 %s
|
|
|
|
template<typename T>
|
|
void clang_analyzer_dump(T&&) {}
|
|
|
|
template<typename T>
|
|
T create() { return T{}; }
|
|
|
|
template<typename T>
|
|
T const& select(bool cond, T const& t, T const& u) { return cond ? t : u; }
|
|
|
|
struct Composite {
|
|
int x;
|
|
int y;
|
|
};
|
|
|
|
struct Derived : Composite {
|
|
int z;
|
|
};
|
|
|
|
template<typename T>
|
|
struct Array {
|
|
T array[20];
|
|
|
|
T&& front() && { return static_cast<T&&>(array[0]); }
|
|
};
|
|
|
|
void whole_object() {
|
|
int const& i = 10; // extends `int`
|
|
clang_analyzer_dump(i); // expected-warning-re {{&lifetime_extended_object{int, i, S{{[0-9]+}}} }}
|
|
Composite&& c = Composite{}; // extends `Composite`
|
|
clang_analyzer_dump(c); // expected-warning-re {{&lifetime_extended_object{Composite, c, S{{[0-9]+}}} }}
|
|
auto&& a = Array<int>{}; // extends `Array<int>`
|
|
clang_analyzer_dump(a); // expected-warning-re {{&lifetime_extended_object{Array<int>, a, S{{[0-9]+}}} }}
|
|
Composite&& d = Derived{}; // extends `Derived`
|
|
clang_analyzer_dump(d); // expected-warning-re {{&Base{lifetime_extended_object{Derived, d, S{{[0-9]+}}},Composite} }}
|
|
}
|
|
|
|
void member_access() {
|
|
int&& x = Composite{}.x; // extends `Composite`
|
|
clang_analyzer_dump(x); // expected-warning-re {{&lifetime_extended_object{Composite, x, S{{[0-9]+}}}.x }}
|
|
int&& y = create<Composite>().y; // extends `Composite`
|
|
clang_analyzer_dump(y); // expected-warning-re {{&lifetime_extended_object{struct Composite, y, S{{[0-9]+}}}.y }}
|
|
int&& d = Array<int>{}.front(); // dangles `Array<int>`
|
|
clang_analyzer_dump(d); // expected-warning-re {{&Element{temp_object{Array<int>, S{{[0-9]+}}}.array,0 S64b,int} }}
|
|
}
|
|
|
|
void array_subscript() {
|
|
int&& i = Array<int>{}.array[0]; // extends `Array<int>`
|
|
clang_analyzer_dump(i); // expected-warning-re {{&Element{lifetime_extended_object{Array<int>, i, S{{[0-9]+}}}.array,0 S64b,int} }}
|
|
auto&& c = Array<Composite>{}.array[0]; // extends `Array<int>`
|
|
clang_analyzer_dump(c); // expected-warning-re {{&Element{lifetime_extended_object{Array<Composite>, c, S{{[0-9]+}}}.array,0 S64b,struct Composite} }}
|
|
auto&& x = Array<Composite>{}.array[0].x; // extends `Array<Composite>`
|
|
clang_analyzer_dump(x); // expected-warning-re {{&Element{lifetime_extended_object{Array<Composite>, x, S{{[0-9]+}}}.array,0 S64b,struct Composite}.x }}
|
|
}
|
|
|
|
void ternary(bool cond) {
|
|
Composite cc;
|
|
// Value category mismatch of the operands (lvalue and xvalue), ternary produces prvalue
|
|
auto&& ternaryProducesPRvalue = cond ? Composite{}.x : cc.x; // extends prvalue of 'int', `Composite` in true branch is destroyed
|
|
clang_analyzer_dump(ternaryProducesPRvalue); // expected-warning-re {{&lifetime_extended_object{int, ternaryProducesPRvalue, S{{[0-9]+}}} }}
|
|
|
|
// Value category agrees (xvalues), lifetime extension is triggered
|
|
auto&& branchesExtended = cond ? Composite{}.x : static_cast<Composite&&>(cc).x; // extends `Composite` in true branch
|
|
clang_analyzer_dump(branchesExtended);
|
|
// expected-warning-re@-1 {{&lifetime_extended_object{Composite, branchesExtended, S{{[0-9]+}}}.x }}
|
|
// expected-warning@-2 {{&cc.x }}
|
|
|
|
// Object of different types in branches are lifetime extended
|
|
auto&& extendingDifferentTypes = cond ? Composite{}.x : Array<int>{}.array[0]; // extends `Composite` or `Array<int>`
|
|
clang_analyzer_dump(extendingDifferentTypes);
|
|
// expected-warning-re@-1 {{&lifetime_extended_object{Composite, extendingDifferentTypes, S{{[0-9]+}}}.x }}
|
|
// expected-warning-re@-2 {{&Element{lifetime_extended_object{Array<int>, extendingDifferentTypes, S{{[0-9]+}}}.array,0 S64b,int} }}
|
|
|
|
Composite const& variableAndExtended = cond ? static_cast<Composite&&>(cc) : Array<Composite>{}.array[0]; // extends `Array<Composite>` in false branch
|
|
clang_analyzer_dump(variableAndExtended);
|
|
// expected-warning@-1 {{&cc }}
|
|
// expected-warning-re@-2 {{&Element{lifetime_extended_object{Array<Composite>, variableAndExtended, S{{[0-9]+}}}.array,0 S64b,struct Composite} }}
|
|
|
|
int const& extendAndDangling = cond ? Array<int>{}.array[0] : Array<int>{}.front(); // extends `Array<int>` only in true branch, false branch dangles
|
|
clang_analyzer_dump(extendAndDangling);
|
|
// expected-warning-re@-1 {{&Element{lifetime_extended_object{Array<int>, extendAndDangling, S{{[0-9]+}}}.array,0 S64b,int} }}
|
|
// expected-warning-re@-2 {{&Element{temp_object{Array<int>, S{{[0-9]+}}}.array,0 S64b,int} }}
|
|
}
|
|
|
|
struct RefAggregate {
|
|
int const& rx;
|
|
Composite&& ry = Composite{};
|
|
};
|
|
|
|
void aggregateWithReferences() {
|
|
RefAggregate multipleExtensions = {10, Composite{}}; // extends `int` and `Composite`
|
|
clang_analyzer_dump(multipleExtensions.rx); // expected-warning-re {{&lifetime_extended_object{int, multipleExtensions, S{{[0-9]+}}} }}
|
|
clang_analyzer_dump(multipleExtensions.ry); // expected-warning-re {{&lifetime_extended_object{Composite, multipleExtensions, S{{[0-9]+}}} }}
|
|
|
|
RefAggregate danglingAndExtended{Array<int>{}.front(), Composite{}}; // extends only `Composite`, `Array<int>` dangles
|
|
clang_analyzer_dump(danglingAndExtended.rx); // expected-warning-re {{&Element{temp_object{Array<int>, S{{[0-9]+}}}.array,0 S64b,int} }}
|
|
clang_analyzer_dump(danglingAndExtended.ry); // expected-warning-re {{&lifetime_extended_object{Composite, danglingAndExtended, S{{[0-9]+}}} }}
|
|
|
|
int i = 10;
|
|
RefAggregate varAndExtended{i, Composite{}}; // extends `Composite`
|
|
clang_analyzer_dump(varAndExtended.rx); // expected-warning {{&i }}
|
|
clang_analyzer_dump(varAndExtended.ry); // expected-warning-re {{&lifetime_extended_object{Composite, varAndExtended, S{{[0-9]+}}} }}
|
|
|
|
auto const& viaReference = RefAggregate{10, Composite{}}; // extends `int`, `Composite`, and `RefAggregate`
|
|
clang_analyzer_dump(viaReference); // expected-warning-re {{&lifetime_extended_object{RefAggregate, viaReference, S{{[0-9]+}}} }}
|
|
clang_analyzer_dump(viaReference.rx); // expected-warning-re {{&lifetime_extended_object{int, viaReference, S{{[0-9]+}}} }}
|
|
clang_analyzer_dump(viaReference.ry); // expected-warning-re {{&lifetime_extended_object{Composite, viaReference, S{{[0-9]+}}} }}
|
|
|
|
// clang does not currently implement extending lifetime of object bound to reference members of aggregates,
|
|
// that are created from default member initializer (see `warn_unsupported_lifetime_extension` from `-Wdangling`)
|
|
RefAggregate defaultInitExtended{i}; // clang-bug does not extend `Composite`
|
|
clang_analyzer_dump(defaultInitExtended.ry); // expected-warning {{Unknown }}
|
|
}
|
|
|
|
void lambda() {
|
|
auto const& lambdaRef = [capture = create<Composite>()] {};
|
|
clang_analyzer_dump(lambdaRef); // expected-warning-re {{lifetime_extended_object{class (lambda at {{[^)]+}}), lambdaRef, S{{[0-9]+}}} }}
|
|
|
|
// The capture [&refCapture = create<Composite const>()] { ... } per [expr.prim.lambda.capture] p6 equivalent to:
|
|
// auto& refCapture = create<Composite const>(); // Well-formed, deduces auto = Composite const, and performs lifetime extension
|
|
// [&refCapture] { ... }
|
|
// Where 'refCapture' has the same lifetime as the lambda itself.
|
|
// However, compilers differ: Clang lifetime-extends from C++17, GCC rejects the code, and MSVC dangles
|
|
// See also CWG2737 (https://cplusplus.github.io/CWG/issues/2737.html)
|
|
auto const refExtendingCapture = [&refCapture = create<Composite const>()] {
|
|
clang_analyzer_dump(refCapture);
|
|
// cpp14-warning-re@-1 {{&temp_object{const struct Composite, S{{[0-9]+}}} }}
|
|
// cpp17-warning-re@-2 {{&lifetime_extended_object{const struct Composite, refExtendingCapture, S{{[0-9]+}}} }}
|
|
};
|
|
refExtendingCapture();
|
|
}
|
|
|
|
void viaStructuredBinding() {
|
|
auto&& [x, y] = Composite{}; // extends `Composite` and binds it to unnamed decomposed object
|
|
clang_analyzer_dump(x); // expected-warning-re {{&lifetime_extended_object{Composite, D{{[0-9]+}}, S{{[0-9]+}}}.x }}
|
|
clang_analyzer_dump(y); // expected-warning-re {{&lifetime_extended_object{Composite, D{{[0-9]+}}, S{{[0-9]+}}}.y }}
|
|
|
|
auto&& [rx, ry] = RefAggregate{10, Composite{}}; // extends `int`, `Composite`, and `RefAggregate`, and binds them to unnamed decomposed object
|
|
clang_analyzer_dump(rx); // expected-warning-re {{&lifetime_extended_object{int, D{{[0-9]+}}, S{{[0-9]+}}} }}
|
|
clang_analyzer_dump(ry); // expected-warning-re {{&lifetime_extended_object{Composite, D{{[0-9]+}}, S{{[0-9]+}}} }}
|
|
}
|
|
|
|
void propagation(bool cond) {
|
|
int const& le = Composite{}.x;
|
|
// May return lifetime-extended region or dangling temporary
|
|
auto&& oneDangling = select(cond, le, 10); // does not extend lifetime of arguments
|
|
clang_analyzer_dump(oneDangling);
|
|
// expected-warning-re@-1 {{&lifetime_extended_object{Composite, le, S{{[0-9]+}}}.x }}
|
|
// expected-warning-re@-2 {{&temp_object{int, S{{[0-9]+}}} }}
|
|
|
|
// Always dangles
|
|
auto&& bothDangling = select(cond, 10, 20); // does not extend lifetime of arguments
|
|
clang_analyzer_dump(bothDangling);
|
|
// expected-warning-re@-1 {{&temp_object{int, S{{[0-9]+}}} }}
|
|
// expected-warning-re@-2 {{&temp_object{int, S{{[0-9]+}}} }}
|
|
}
|