This check flags initializers of globals that access extern objects, and therefore can lead to order-of-initialization problems (this recommandation is part of CPP core guidelines).
Note that this only checks half of the guideline for now (it does not enforce using constexpr functions).
Details
Diff Detail
Event Timeline
That should be in a different commit, right ? Release notes are in a different repo (forgive my ignorance, this is my first patch).
Thank you for working on the new clang-tidy check!
We usually recommend authors to run their checks on a large code base to ensure it doesn't crash and doesn't generate obvious false positives. It would be nice, if you could provide a quick summary of such a run (total number of hits, number of what seems to be a false positive in a sample of ~100).
clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.cpp | ||
---|---|---|
41 | nit: No need for braces around single-line if/for/... bodies. | |
clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.h | ||
20 | I'd leave the "For user-facing documentation, see ..." phrase that the add_new_check.py script adds. | |
test/clang-tidy/cppcoreguidelines-interfaces-global-init.cpp | ||
37 | These should be on a single line. I guess, your clang-format doesn't use -style=file by default and doesn't understand the local style configured for the test/ directory. Otherwise it wouldn't split the lines. |
Looks like there's a race condition in phabricator.
test/clang-tidy/cppcoreguidelines-interfaces-global-init.cpp | ||
---|---|---|
37 | Thanks for the hint. I was using -style=LLVM indeed. |
clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.cpp | ||
---|---|---|
47 | nit: no trailing period needed in diagnostic messages | |
48 | This might work even better without ->getName(). Not sure, but it makes sense to try. | |
docs/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.rst | ||
8 | What about indirect access of other globals, e.g. via function calls? We obviously can't analyze functions defined in a different translation unit, and I'm not sure we need to analyze even those defined in the same TU (since it may be imprecise and quite expensive), but maybe we should use some heuristic here (the most agressive would be to flag all initializers containing function calls, but that might be rather noisy). |
The tool generated 20k positives on our codebase. On a sample of 100, there are:
- 8 instances of the same exact code structure that's just wrong: const string var = FLAGS_some_flag + "some_sufix";
- 8 false positives.
- 84 possible issues. (interestingly 6 of these are from premature use of variations of "extern char* empty_string;"
The false positives fall into 3 categories:
- 3 variations of:
extern int i; static const int* pi = &i; // diag
// Then pi is dereferenced later, once i is intialized.
Public example of this: https://github.com/python-git/python/blob/py3k/Objects/dictobject.c#L2027
- 3 variations of:
// .h class A { static const int i = 42; }; // .cc int A::i; // diag
- 2 variations of:
// .h class A { static int i; static int j; }; // .cc int A::i = 0; int A::j = i; // diag
cosmetics + more documentation
docs/clang-tidy/checks/cppcoreguidelines-interfaces-global-init.rst | ||
---|---|---|
8 | The guidelines actually recommend flagging calls non-constexpr functions, but this would give too many positives. I've added a note. |
Should we warn at all when only an address of a global variable is used?
// Then pi is dereferenced later, once i is intialized.
Public example of this: https://github.com/python-git/python/blob/py3k/Objects/dictobject.c#L2027
- 3 variations of:
// .h class A { static const int i = 42; }; // .cc int A::i; // diag
Looks like we have all information to fix this kind of a false positive.
- 2 variations of:
// .h class A { static int i; static int j; }; // .cc int A::i = 0; int A::j = i; // diag
ditto
We could, but it would allow stuff like:
extern const int i; const char c = *(const char*)&i;
No strong opinion here.
// Then pi is dereferenced later, once i is intialized.
Public example of this: https://github.com/python-git/python/blob/py3k/Objects/dictobject.c#L2027
- 3 variations of:
// .h class A { static const int i = 42; }; // .cc int A::i; // diagLooks like we have all information to fix this kind of a false positive.
- 2 variations of:
// .h class A { static int i; static int j; }; // .cc int A::i = 0; int A::j = i; // diagditto
It turns out (2) and (3) are just variations of the same - (2) was just:
// .h class A { static const int i = 0; static const int j = i; }; // .cc int A::i; int A::j;
Which has *exactly* the same semantics as (3).
I could not find a way to go from a decl to its (possible) definition. I was thinking of matching all global definitions that are not declarations and then making sure that the current matches are not referring to one of those definitions (in syntactic order because of [3.6.2.3]). Does this sound like a reasonable idea ?
VarDecl is also Redeclarable<VarDecl>, which has various ways to iterate over all related declarations.
I was thinking of matching all global definitions that are not declarations and then making sure that the current matches are not referring to one of those definitions (in syntactic order because of [3.6.2.3]). Does this sound like a reasonable idea ?
Update naming to be constistent with the standard: use "non-local" instead of "global" (or "static" in my case, as I started from classes and the name stuck).
clang-tidy/cppcoreguidelines/InterfacesGlobalInitCheck.cpp | ||
---|---|---|
28 | This can be moved into ReferencesUndefinedGlobalVar |
test/clang-tidy/cppcoreguidelines-interfaces-global-init.cpp | ||
---|---|---|
8 | What happens if you add: extern int ExternGlobal; extern int ExternGlobal; int ExternGlobal = 123; ? |
test/clang-tidy/cppcoreguidelines-interfaces-global-init.cpp | ||
---|---|---|
8 | It does not trigger (as expected). I've added a test. |
I'd leave the "For user-facing documentation, see ..." phrase that the add_new_check.py script adds.