[cfe-dev] [analyzer] UninitializedObjectChecker evaluation
Dmitri Gribenko via cfe-dev
cfe-dev at lists.llvm.org
Sat May 4 07:56:54 PDT 2019
Hi,
The results of the UninitializedObject checker significantly overlap
with the clang-tidy checker cppcoreguidelines-pro-type-member-init.
When both checkers trigger, messages from
cppcoreguidelines-pro-type-member-init are significantly more clear
and less noisy.
$ cat /tmp/t.cc
struct Foo {
Foo() : y(0) {}
int x;
int y;
};
void entry1() {
Foo x;
}
$ ./bin/clang-tidy -checks="-*,clang-analyzer*" /tmp/t.cc
/tmp/t.cc:2:17: warning: 1 uninitialized field at the end of the
constructor call [clang-analyzer-optin.cplusplus.UninitializedObject]
Foo() : y(0) {}
^
/tmp/t.cc:3:7: note: uninitialized field 'this->x'
int x;
^
/tmp/t.cc:7:7: note: Calling default constructor for 'Foo'
Foo x;
^
/tmp/t.cc:2:17: note: 1 uninitialized field at the end of the constructor call
Foo() : y(0) {}
^
$ ./bin/clang-tidy -checks="-*,cppcoreguidelines-pro-type-member-init" /tmp/t.cc
/tmp/t.cc:2:3: warning: constructor does not initialize these fields:
x [cppcoreguidelines-pro-type-member-init]
Foo() : y(0) {}
^
Note how cppcoreguidelines-pro-type-member-init packs the same
information into one message. The primary message from
UninitializedObject checker says there's "1 uninitialized field". As a
user, my first question is "which?" -- and to figure it out I have to
parse the rest of the output, where two notes out of three have
completely irrelevant information. There's no way to call this
constructor in a way that will initialize `Foo::x`, so why print all
that? Also, the phrasing is passive, I like the phrasing from
cppcoreguidelines-pro-type-member-init much better.
cppcoreguidelines-pro-type-member-init does not trigger on templates,
while UninitializedObject checker does. For example:
template<typename T>
struct Foo {
Foo() : y(0) {}
T x;
T y;
};
void entry1() {
Foo<int> x;
}
It is unclear why cppcoreguidelines-pro-type-member-init decided to
not look into templates.
One source of false positives for the UninitializedObject checker are
`Init()` methods, for example:
bool ReadIntFromFile(int *x);
struct Foo {
Foo() : y(0) {}
int x;
int y;
bool Init() {
return ReadIntFromFile(&x);
}
};
bool entry1() {
Foo x;
return x.Init();
}
It is also unclear how to annotate member variables that are
intentionally left uninitialized:
struct Foo {
Foo() : has_cached_value(false) {}
bool has_cached_value;
int cached_value;
};
bool entry1() {
Foo x;
}
In many cases it is possible to rewrite the code using std::optional,
but not everywhere.
Another source of false positives is the `is_valid` pattern:
bool ReadIntFromFile(int *x);
struct Foo {
Foo() {
int temp;
if (!ReadIntFromFile(&temp)) {
is_valid = false;
return;
}
x = temp;
is_valid = true;
}
int x;
bool is_valid;
};
void entry1() {
Foo x;
}
Inlining in the Clang Static Analyzer helps the UninitializedObject
checker to see through abstractions. In the following example
UninitializedObject checker is silent (as it should be), while
cppcoreguidelines-pro-type-member-init complains:
struct Foo {
Foo() {
Unset();
}
void Unset() {
has_cached_value = false;
x = 0;
}
bool has_cached_value;
int x;
};
void entry1() {
Foo x;
}
To summarize, I have two findings.
First, I haven't found a case where taking the call site context into
account helps this checker. Fundamentally, this check is about
assuming nothing at the entry to the constructor, and checking a
strong postcondition (all fields are initialized) at the exit.
Second, the `Init()` and `is_valid` patterns are fundamentally at odds
with this checker, because it does not try to detect an uninitialized
memory read, it tries to detect a postcondition violation at the
constructor exit.
Hope this helps.
Dmitri
--
main(i,j){for(i=2;;i++){for(j=2;j<i;j++){if(!(i%j)){j=0;break;}}if
(j){printf("%d\n",i);}}} /*Dmitri Gribenko <gribozavr at gmail.com>*/
More information about the cfe-dev
mailing list