Skip to content

Commit 38b948b

Browse files
authored
[analyzer] Revert #115918, so empty base class optimization works again (#157480)
Tldr; We can't unconditionally trivially copy empty classes because that would clobber the stored entries in the object that the optimized empty class overlaps with. This regression was introduced by #115918, which introduced other clobbering issues, like the handling of `[[no_unique_address]]` fields in #137252. Read issue #157467 for the detailed explanation, but in short, I'd propose reverting the original patch because these was a lot of problems with it for arguably not much gain. In particular, that patch was motivated by unifying the handling of classes so that copy events would be triggered for a class no matter if it had data members or not. So in hindsight, it was not worth it. I plan to backport this to clang-21 as well, and mention in the release notes that this should fix the regression from clang-20. PS: Also an interesting read [D43714](https://reviews.llvm.org/D43714) in hindsight. Fixes #157467 CPP-6574
1 parent 343186d commit 38b948b

File tree

4 files changed

+66
-28
lines changed

4 files changed

+66
-28
lines changed

clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,21 +71,30 @@ void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred,
7171
Bldr.takeNodes(Pred);
7272

7373
assert(ThisRD);
74-
SVal V = Call.getArgSVal(0);
75-
const Expr *VExpr = Call.getArgExpr(0);
7674

77-
// If the value being copied is not unknown, load from its location to get
78-
// an aggregate rvalue.
79-
if (std::optional<Loc> L = V.getAs<Loc>())
80-
V = Pred->getState()->getSVal(*L);
81-
else
82-
assert(V.isUnknownOrUndef());
75+
if (!ThisRD->isEmpty()) {
76+
SVal V = Call.getArgSVal(0);
77+
const Expr *VExpr = Call.getArgExpr(0);
8378

84-
ExplodedNodeSet Tmp;
85-
evalLocation(Tmp, CallExpr, VExpr, Pred, Pred->getState(), V,
86-
/*isLoad=*/true);
87-
for (ExplodedNode *N : Tmp)
88-
evalBind(Dst, CallExpr, N, ThisVal, V, !AlwaysReturnsLValue);
79+
// If the value being copied is not unknown, load from its location to get
80+
// an aggregate rvalue.
81+
if (std::optional<Loc> L = V.getAs<Loc>())
82+
V = Pred->getState()->getSVal(*L);
83+
else
84+
assert(V.isUnknownOrUndef());
85+
86+
ExplodedNodeSet Tmp;
87+
evalLocation(Tmp, CallExpr, VExpr, Pred, Pred->getState(), V,
88+
/*isLoad=*/true);
89+
for (ExplodedNode *N : Tmp)
90+
evalBind(Dst, CallExpr, N, ThisVal, V, !AlwaysReturnsLValue);
91+
} else {
92+
// We can't copy empty classes because of empty base class optimization.
93+
// In that case, copying the empty base class subobject would overwrite the
94+
// object that it overlaps with - so let's not do that.
95+
// See issue-157467.cpp for an example.
96+
Dst.Add(Pred);
97+
}
8998

9099
PostStmt PS(CallExpr, LCtx);
91100
for (ExplodedNode *N : Dst) {

clang/test/Analysis/ctor-trivial-copy.cpp

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,10 @@ void _01_empty_structs() {
4646
empty Empty = conjure<empty>();
4747
empty Empty2 = Empty;
4848
empty Empty3 = Empty2;
49-
// All of these should refer to the exact same symbol, because all of
50-
// these trivial copies refer to the original conjured value.
51-
// There were Unknown before:
52-
clang_analyzer_denote(Empty, "$Empty");
53-
clang_analyzer_express(Empty); // expected-warning {{$Empty}}
54-
clang_analyzer_express(Empty2); // expected-warning {{$Empty}}
55-
clang_analyzer_express(Empty3); // expected-warning {{$Empty}}
5649

57-
// We should have the same Conjured symbol for "Empty", "Empty2" and "Empty3".
50+
// We only have binding for the original Empty object, because copying empty
51+
// objects is a no-op in the performTrivialCopy. This is fine, because empty
52+
// objects don't have any data members that could be accessed anyway.
5853
clang_analyzer_printState();
5954
// CHECK: "store": { "pointer": "0x{{[0-9a-f]+}}", "items": [
6055
// CHECK-NEXT: { "cluster": "GlobalInternalSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
@@ -65,12 +60,6 @@ void _01_empty_structs() {
6560
// CHECK-NEXT: ]},
6661
// CHECK-NEXT: { "cluster": "Empty", "pointer": "0x{{[0-9a-f]+}}", "items": [
6762
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ:conj_\$[0-9]+{int, LC[0-9]+, S[0-9]+, #[0-9]+}]]" }
68-
// CHECK-NEXT: ]},
69-
// CHECK-NEXT: { "cluster": "Empty2", "pointer": "0x{{[0-9a-f]+}}", "items": [
70-
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ]]" }
71-
// CHECK-NEXT: ]},
72-
// CHECK-NEXT: { "cluster": "Empty3", "pointer": "0x{{[0-9a-f]+}}", "items": [
73-
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ]]" }
7463
// CHECK-NEXT: ]}
7564
// CHECK-NEXT: ]},
7665

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s
2+
// expected-no-diagnostics
3+
4+
template <class T, int Idx, bool CanBeEmptyBase = __is_empty(T) && (!__is_final(T))>
5+
struct compressed_pair_elem {
6+
explicit compressed_pair_elem(T u) : value(u) {}
7+
T value;
8+
};
9+
10+
template <class T, int Idx>
11+
struct compressed_pair_elem<T, Idx, /*CanBeEmptyBase=*/true> : T {
12+
explicit compressed_pair_elem(T u) : T(u) {}
13+
};
14+
15+
template <class T1, class T2, class Base1 = compressed_pair_elem<T1, 0>, class Base2 = compressed_pair_elem<T2, 1>>
16+
struct compressed_pair : Base1, Base2 {
17+
explicit compressed_pair(T1 t1, T2 t2) : Base1(t1), Base2(t2) {}
18+
};
19+
20+
// empty deleter object
21+
template <class T>
22+
struct default_delete {
23+
void operator()(T* p) {
24+
delete p;
25+
}
26+
};
27+
28+
template <class T, class Deleter = default_delete<T> >
29+
struct some_unique_ptr {
30+
// compressed_pair will employ the empty base class optimization, thus overlapping
31+
// the `int*` and the empty `Deleter` object, clobbering the pointer.
32+
compressed_pair<int*, Deleter> ptr;
33+
some_unique_ptr(int* p, Deleter d) : ptr(p, d) {}
34+
~some_unique_ptr();
35+
};
36+
37+
void entry_point() {
38+
some_unique_ptr<int, default_delete<int> > u3(new int(12), default_delete<int>());
39+
}

clang/test/Analysis/taint-generic.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,9 @@ void top() {
153153
int Int = mySource1<int>();
154154
clang_analyzer_isTainted(Int); // expected-warning {{YES}}
155155

156+
// It's fine to not propagate taint to empty classes, since they don't have any data members.
156157
Empty E = mySource1<Empty>();
157-
clang_analyzer_isTainted(E); // expected-warning {{YES}}
158+
clang_analyzer_isTainted(E); // expected-warning {{NO}}
158159

159160
Aggr A = mySource1<Aggr>();
160161
clang_analyzer_isTainted(A); // expected-warning {{YES}}

0 commit comments

Comments
 (0)