
In #115916 I allowed copying empty structs. Later in #115917 I changed how objects are copied, and basically when we would want to copy a struct (an LCV) of a single symbol (likely coming from an opaque fncall or invalidation), just directly bind that symbol instead of creating an LCV referring to the symbol. This was an optimization to skip a layer of indirection. Now, it turns out I should have apply the same logic in #115916. I should not have just blindly created an LCV by calling `createLazyBinding()`, but rather check if I can apply the shortcut described in #115917 and only create the LCV if the shortcut doesn't apply. In this patch I check if there is a single default binding that the copy would refer to and if so, just return that symbol instead of creating an LCV. There shouldn't be any observable changes besides that we should have fewer LCVs. This change may surface bugs in checkers that were associating some metadata with entities in a wrong way. Notably, STLAlgorithmModeling and DebugIteratorModeling checkers would likely stop working after this change. I didn't investigate them deeply because they were broken even prior to this patch. Let me know if I should migrate these checkers to be just as bugged as they were prior to this patch - thus make the tests pass.
127 lines
5.2 KiB
C++
127 lines
5.2 KiB
C++
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config c++-inlining=constructors -verify %s \
|
|
// RUN: 2>&1 | FileCheck %s
|
|
|
|
|
|
void clang_analyzer_printState();
|
|
template <typename T> void clang_analyzer_dump_lref(T& param);
|
|
template <typename T> void clang_analyzer_dump_val(T param);
|
|
template <typename T> void clang_analyzer_denote(T param, const char *name);
|
|
template <typename T> void clang_analyzer_express(T param);
|
|
template <typename T> T conjure();
|
|
template <typename... Ts> void nop(const Ts &... args) {}
|
|
|
|
struct aggr {
|
|
int x;
|
|
int y;
|
|
};
|
|
|
|
struct empty {
|
|
};
|
|
|
|
void test_copy_return() {
|
|
aggr s1 = {1, 2};
|
|
aggr const& cr1 = aggr(s1);
|
|
clang_analyzer_dump_lref(cr1); // expected-warning-re {{&lifetime_extended_object{aggr, cr1, S{{[0-9]+}}} }}
|
|
|
|
empty s2;
|
|
empty const& cr2 = empty{s2};
|
|
clang_analyzer_dump_lref(cr2); // expected-warning-re {{&lifetime_extended_object{empty, cr2, S{{[0-9]+}}} }}
|
|
}
|
|
|
|
void test_assign_return() {
|
|
aggr s1 = {1, 2};
|
|
aggr d1;
|
|
clang_analyzer_dump_lref(d1 = s1); // expected-warning {{&d1 }}
|
|
|
|
empty s2;
|
|
empty d2;
|
|
clang_analyzer_dump_lref(d2 = s2); // expected-warning {{&d2 }} was Unknown
|
|
}
|
|
|
|
|
|
namespace trivial_struct_copy {
|
|
|
|
void _01_empty_structs() {
|
|
clang_analyzer_dump_val(conjure<empty>()); // expected-warning {{conj_$}}
|
|
empty Empty = conjure<empty>();
|
|
empty Empty2 = Empty;
|
|
empty Empty3 = Empty2;
|
|
// All of these should refer to the exact same symbol, because all of
|
|
// these trivial copies refer to the original conjured value.
|
|
// There were Unknown before:
|
|
clang_analyzer_denote(Empty, "$Empty");
|
|
clang_analyzer_express(Empty); // expected-warning {{$Empty}}
|
|
clang_analyzer_express(Empty2); // expected-warning {{$Empty}}
|
|
clang_analyzer_express(Empty3); // expected-warning {{$Empty}}
|
|
|
|
// We should have the same Conjured symbol for "Empty", "Empty2" and "Empty3".
|
|
clang_analyzer_printState();
|
|
// CHECK: "store": { "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "cluster": "GlobalInternalSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "conj_$
|
|
// CHECK-NEXT: ]},
|
|
// CHECK-NEXT: { "cluster": "GlobalSystemSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "conj_$
|
|
// CHECK-NEXT: ]},
|
|
// CHECK-NEXT: { "cluster": "Empty", "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ:conj_\$[0-9]+{int, LC[0-9]+, S[0-9]+, #[0-9]+}]]" }
|
|
// CHECK-NEXT: ]},
|
|
// CHECK-NEXT: { "cluster": "Empty2", "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ]]" }
|
|
// CHECK-NEXT: ]},
|
|
// CHECK-NEXT: { "cluster": "Empty3", "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ]]" }
|
|
// CHECK-NEXT: ]}
|
|
// CHECK-NEXT: ]},
|
|
|
|
nop(Empty, Empty2, Empty3);
|
|
}
|
|
|
|
void _02_structs_with_members() {
|
|
clang_analyzer_dump_val(conjure<aggr>()); // expected-warning {{conj_$}}
|
|
aggr Aggr = conjure<aggr>();
|
|
aggr Aggr2 = Aggr;
|
|
aggr Aggr3 = Aggr2;
|
|
// All of these should refer to the exact same symbol, because all of
|
|
// these trivial copies refer to the original conjured value.
|
|
clang_analyzer_denote(Aggr, "$Aggr");
|
|
clang_analyzer_express(Aggr); // expected-warning {{$Aggr}}
|
|
clang_analyzer_express(Aggr2); // expected-warning {{$Aggr}}
|
|
clang_analyzer_express(Aggr3); // expected-warning {{$Aggr}}
|
|
|
|
// We should have the same Conjured symbol for "Aggr", "Aggr2" and "Aggr3".
|
|
// We used to have Derived symbols for the individual fields that were
|
|
// copied as part of copying the whole struct.
|
|
clang_analyzer_printState();
|
|
// CHECK: "store": { "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "cluster": "GlobalInternalSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "conj_$
|
|
// CHECK-NEXT: ]},
|
|
// CHECK-NEXT: { "cluster": "GlobalSystemSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "conj_$
|
|
// CHECK-NEXT: ]},
|
|
// CHECK-NEXT: { "cluster": "Aggr", "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[AGGR_CONJ:conj_\$[0-9]+{int, LC[0-9]+, S[0-9]+, #[0-9]+}]]" }
|
|
// CHECK-NEXT: ]},
|
|
// CHECK-NEXT: { "cluster": "Aggr2", "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[AGGR_CONJ]]" }
|
|
// CHECK-NEXT: ]},
|
|
// CHECK-NEXT: { "cluster": "Aggr3", "pointer": "0x{{[0-9a-f]+}}", "items": [
|
|
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[AGGR_CONJ]]" }
|
|
// CHECK-NEXT: ]}
|
|
// CHECK-NEXT: ]},
|
|
|
|
nop(Aggr, Aggr2, Aggr3);
|
|
}
|
|
|
|
// Tests that use `clang_analyzer_printState()` must share the analysis entry
|
|
// point, and have a strict ordering between. This is to meet the different
|
|
// `clang_analyzer_printState()` calls in a fixed relative ordering, thus
|
|
// FileCheck could check the stdouts.
|
|
void entrypoint() {
|
|
_01_empty_structs();
|
|
_02_structs_with_members();
|
|
}
|
|
|
|
} // namespace trivial_struct_copy
|