Skip to content

Commit 3d6923d

Browse files
vabridgersVince Bridgers
andauthored
RFC: [clang-tidy] [analyzer] Move nondeterministic pointer usage check to tidy (#110471)
This change moves the `alpha.nondeterministic.PointerSorting` and `alpha.nondeterministic.PointerIteration` static analyzer checkers to a single `clang-tidy` check. Those checkers were implemented as simple `clang-tidy` check-like code, wrapped in the static analyzer framework. The documentation was updated to describe what the checks can and cannot do, and testing was completed on a broad set of open-source projects. Co-authored-by: Vince Bridgers <[email protected]>
1 parent 14171b0 commit 3d6923d

27 files changed

+713
-331
lines changed

clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include "MultipleStatementMacroCheck.h"
5050
#include "NoEscapeCheck.h"
5151
#include "NonZeroEnumToBoolConversionCheck.h"
52+
#include "NondeterministicPointerIterationOrderCheck.h"
5253
#include "NotNullTerminatedResultCheck.h"
5354
#include "OptionalValueConversionCheck.h"
5455
#include "ParentVirtualCallCheck.h"
@@ -174,6 +175,8 @@ class BugproneModule : public ClangTidyModule {
174175
"bugprone-multiple-new-in-one-expression");
175176
CheckFactories.registerCheck<MultipleStatementMacroCheck>(
176177
"bugprone-multiple-statement-macro");
178+
CheckFactories.registerCheck<NondeterministicPointerIterationOrderCheck>(
179+
"bugprone-nondeterministic-pointer-iteration-order");
177180
CheckFactories.registerCheck<OptionalValueConversionCheck>(
178181
"bugprone-optional-value-conversion");
179182
CheckFactories.registerCheck<PointerArithmeticOnPolymorphicObjectCheck>(

clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ add_clang_library(clangTidyBugproneModule STATIC
4545
MultipleNewInOneExpressionCheck.cpp
4646
MultipleStatementMacroCheck.cpp
4747
NoEscapeCheck.cpp
48+
NondeterministicPointerIterationOrderCheck.cpp
4849
NonZeroEnumToBoolConversionCheck.cpp
4950
NotNullTerminatedResultCheck.cpp
5051
OptionalValueConversionCheck.cpp
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//===----- NondeterministicPointerIterationOrderCheck.cpp - clang-tidy ----===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "NondeterministicPointerIterationOrderCheck.h"
10+
#include "clang/AST/ASTContext.h"
11+
#include "clang/Lex/Lexer.h"
12+
13+
using namespace clang::ast_matchers;
14+
15+
namespace clang::tidy::bugprone {
16+
17+
void NondeterministicPointerIterationOrderCheck::registerMatchers(
18+
MatchFinder *Finder) {
19+
20+
auto LoopVariable = varDecl(hasType(
21+
qualType(hasCanonicalType(anyOf(referenceType(), pointerType())))));
22+
23+
auto RangeInit = declRefExpr(to(varDecl(
24+
hasType(recordDecl(hasAnyName("std::unordered_set", "std::unordered_map",
25+
"std::unordered_multiset",
26+
"std::unordered_multimap"))
27+
.bind("recorddecl")))));
28+
29+
Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVariable),
30+
hasRangeInit(RangeInit.bind("rangeinit")))
31+
.bind("cxxForRangeStmt"),
32+
this);
33+
34+
auto SortFuncM = callee(functionDecl(hasAnyName(
35+
"std::is_sorted", "std::nth_element", "std::sort", "std::partial_sort",
36+
"std::partition", "std::stable_partition", "std::stable_sort")));
37+
38+
auto IteratesPointerEltsM = hasArgument(
39+
0,
40+
cxxMemberCallExpr(on(hasType(cxxRecordDecl(has(fieldDecl(hasType(qualType(
41+
hasCanonicalType(pointsTo(hasCanonicalType(pointerType()))))))))))));
42+
43+
Finder->addMatcher(
44+
callExpr(allOf(SortFuncM, IteratesPointerEltsM)).bind("sortsemantic"),
45+
this);
46+
}
47+
48+
void NondeterministicPointerIterationOrderCheck::check(
49+
const MatchFinder::MatchResult &Result) {
50+
const auto *ForRangePointers =
51+
Result.Nodes.getNodeAs<CXXForRangeStmt>("cxxForRangeStmt");
52+
53+
if ((ForRangePointers) && !(ForRangePointers->getBeginLoc().isMacroID())) {
54+
const auto *RangeInit = Result.Nodes.getNodeAs<Stmt>("rangeinit");
55+
if (const auto *ClassTemplate =
56+
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(
57+
"recorddecl")) {
58+
const TemplateArgumentList &TemplateArgs =
59+
ClassTemplate->getTemplateArgs();
60+
const llvm::StringRef AlgoName = ClassTemplate->getName();
61+
const bool IsAlgoArgPointer =
62+
TemplateArgs[0].getAsType()->isPointerType();
63+
64+
if (IsAlgoArgPointer) {
65+
SourceRange R = RangeInit->getSourceRange();
66+
diag(R.getBegin(), "iteration of pointers is nondeterministic") << R;
67+
}
68+
}
69+
return;
70+
}
71+
const auto *SortPointers = Result.Nodes.getNodeAs<Stmt>("sortsemantic");
72+
73+
if ((SortPointers) && !(SortPointers->getBeginLoc().isMacroID())) {
74+
SourceRange R = SortPointers->getSourceRange();
75+
diag(R.getBegin(), "sorting pointers is nondeterministic") << R;
76+
}
77+
}
78+
79+
} // namespace clang::tidy::bugprone
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//=== NondeterministicPointerIterationOrderCheck.h - clang-tidy -*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NONDETERMINISTIC_POINTER_ITERATION_ORDER_CHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NONDETERMINISTIC_POINTER_ITERATION_ORDER_CHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// Finds nondeterministic usages of pointers in unordered containers. The
17+
/// check also finds calls to sorting-like algorithms on a container of
18+
/// pointers.
19+
///
20+
/// For the user-facing documentation see:
21+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/nondeterministic-pointer-iteration-order.html
22+
class NondeterministicPointerIterationOrderCheck : public ClangTidyCheck {
23+
public:
24+
NondeterministicPointerIterationOrderCheck(StringRef Name,
25+
ClangTidyContext *Context)
26+
: ClangTidyCheck(Name, Context) {}
27+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
28+
return LangOpts.CPlusPlus;
29+
}
30+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
31+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
32+
std::optional<TraversalKind> getCheckTraversalKind() const override {
33+
return TK_IgnoreUnlessSpelledInSource;
34+
}
35+
};
36+
37+
} // namespace clang::tidy::bugprone
38+
39+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NONDETERMINISTIC_POINTER_ITERATION_ORDER_CHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ New checks
119119
Warns about code that tries to cast between pointers by means of
120120
``std::bit_cast`` or ``memcpy``.
121121

122+
- New :doc:`bugprone-nondeterministic-pointer-iteration-order
123+
<clang-tidy/checks/bugprone/nondeterministic-pointer-iteration-order>`
124+
check.
125+
126+
Finds nondeterministic usages of pointers in unordered containers.
127+
122128
- New :doc:`bugprone-tagged-union-member-count
123129
<clang-tidy/checks/bugprone/tagged-union-member-count>` check.
124130

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
.. title:: clang-tidy - bugprone-nondeterministic-pointer-iteration-order
2+
3+
bugprone-nondeterministic-pointer-iteration-order
4+
=================================================
5+
6+
Finds nondeterministic usages of pointers in unordered containers.
7+
8+
One canonical example is iteration across a container of pointers.
9+
10+
.. code-block:: c++
11+
12+
{
13+
int a = 1, b = 2;
14+
std::unordered_set<int *> UnorderedPtrSet = {&a, &b};
15+
for (auto i : UnorderedPtrSet)
16+
f(i);
17+
}
18+
19+
Another such example is sorting a container of pointers.
20+
21+
.. code-block:: c++
22+
23+
{
24+
int a = 1, b = 2;
25+
std::vector<int *> VectorOfPtr = {&a, &b};
26+
std::sort(VectorOfPtr.begin(), VectorOfPtr.end());
27+
}
28+
29+
Iteration of a containers of pointers may present the order of different
30+
pointers differently across different runs of a program. In some cases this
31+
may be acceptable behavior, in others this may be unexpected behavior. This
32+
check is advisory for this reason.
33+
34+
This check only detects range-based for loops over unordered sets and maps. It
35+
also detects calls sorting-like algorithms on containers holding pointers.
36+
Other similar usages will not be found and are false negatives.
37+
38+
Limitations:
39+
40+
* This check currently does not check if a nondeterministic iteration order is
41+
likely to be a mistake, and instead marks all such iterations as bugprone.
42+
* std::reference_wrapper is not considered yet.
43+
* Only for loops are considered, other iterators can be included in
44+
improvements.

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Clang-Tidy Checks
115115
:doc:`bugprone-multiple-new-in-one-expression <bugprone/multiple-new-in-one-expression>`,
116116
:doc:`bugprone-multiple-statement-macro <bugprone/multiple-statement-macro>`,
117117
:doc:`bugprone-no-escape <bugprone/no-escape>`,
118+
:doc:`bugprone-nondeterministic-pointer-iteration-order <bugprone/nondeterministic-pointer-iteration-order>`,
118119
:doc:`bugprone-non-zero-enum-to-bool-conversion <bugprone/non-zero-enum-to-bool-conversion>`,
119120
:doc:`bugprone-not-null-terminated-result <bugprone/not-null-terminated-result>`, "Yes"
120121
:doc:`bugprone-optional-value-conversion <bugprone/optional-value-conversion>`, "Yes"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#ifndef _SIM_ALGORITHM
2+
#define _SIM_ALGORITHM
3+
4+
#pragma clang system_header
5+
6+
namespace std {
7+
8+
template<class ForwardIt>
9+
bool is_sorted(ForwardIt first, ForwardIt last);
10+
11+
template <class RandomIt>
12+
void nth_element(RandomIt first, RandomIt nth, RandomIt last);
13+
14+
template<class RandomIt>
15+
void partial_sort(RandomIt first, RandomIt middle, RandomIt last);
16+
17+
template<class RandomIt>
18+
void sort (RandomIt first, RandomIt last);
19+
20+
template<class RandomIt>
21+
void stable_sort(RandomIt first, RandomIt last);
22+
23+
template<class BidirIt, class UnaryPredicate>
24+
BidirIt partition(BidirIt first, BidirIt last, UnaryPredicate p);
25+
26+
template<class BidirIt, class UnaryPredicate>
27+
BidirIt stable_partition(BidirIt first, BidirIt last, UnaryPredicate p);
28+
29+
} // namespace std
30+
31+
#endif // _SIM_ALGORITHM
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#ifndef _SIM_CPP_CONFIG_H
2+
#define _SIM_CPP_CONFIG_H
3+
4+
#pragma clang system_header
5+
6+
typedef unsigned char uint8_t;
7+
8+
typedef __typeof__(sizeof(int)) size_t;
9+
typedef __typeof__((char*)0-(char*)0) ptrdiff_t;
10+
11+
#endif // _SIM_CPP_CONFIG_H
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#ifndef _INITIALIZER_LIST
2+
#define _INITIALIZER_LIST
3+
4+
#pragma clang system_header
5+
#
6+
#include "sim_c++config.h" // size_t
7+
8+
namespace std {
9+
10+
template <class _E>
11+
class initializer_list {
12+
const _E* __begin_;
13+
size_t __size_;
14+
15+
initializer_list(const _E* __b, size_t __s)
16+
: __begin_(__b),
17+
__size_(__s)
18+
{}
19+
20+
public:
21+
typedef _E value_type;
22+
typedef const _E& reference;
23+
typedef const _E& const_reference;
24+
typedef size_t size_type;
25+
26+
typedef const _E* iterator;
27+
typedef const _E* const_iterator;
28+
29+
initializer_list() : __begin_(0), __size_(0) {}
30+
31+
size_t size() const {return __size_;}
32+
const _E* begin() const {return __begin_;}
33+
const _E* end() const {return __begin_ + __size_;}
34+
35+
}; // class initializer_list
36+
37+
} // namespace std
38+
39+
#endif // _INITIALIZER_LIST
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#ifndef _SIM_ITERATOR_BASE
2+
#define _SIM_ITERATOR_BASE
3+
4+
namespace std {
5+
6+
struct input_iterator_tag { };
7+
struct output_iterator_tag { };
8+
struct forward_iterator_tag : public input_iterator_tag { };
9+
struct bidirectional_iterator_tag : public forward_iterator_tag { };
10+
struct random_access_iterator_tag : public bidirectional_iterator_tag { };
11+
12+
template <typename Iterator> struct iterator_traits {
13+
typedef typename Iterator::difference_type difference_type;
14+
typedef typename Iterator::value_type value_type;
15+
typedef typename Iterator::pointer pointer;
16+
typedef typename Iterator::reference reference;
17+
typedef typename Iterator::iterator_category iterator_category;
18+
};
19+
20+
} // namespace std
21+
22+
#endif // _SIM_ITERATOR_BASE
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
#ifndef _SIM_MAP
3+
#define _SIM_MAP
4+
5+
#pragma clang system_header
6+
#include "sim_stl_pair"
7+
8+
namespace std {
9+
10+
template <typename Key, typename Value>
11+
class map {
12+
public:
13+
using value_type = pair<Key, Value>;
14+
map();
15+
map(initializer_list<pair<Key, Value>> initList);
16+
value_type& operator[](const Key& key);
17+
value_type& operator[](Key&& key);
18+
class iterator {
19+
public:
20+
iterator(Key *key): ptr(key) {}
21+
iterator& operator++() { ++ptr; return *this; }
22+
bool operator!=(const iterator &other) const { return ptr != other.ptr; }
23+
const Key &operator*() const { return *ptr; }
24+
private:
25+
Key *ptr;
26+
};
27+
Key *val;
28+
iterator begin() const { return iterator(val); }
29+
iterator end() const { return iterator(val + 1); }
30+
};
31+
32+
} // namespace std
33+
34+
#endif // _SIM_MAP
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
#ifndef _SIM_SET
3+
#define _SIM_SET
4+
5+
#pragma clang system_header
6+
#include "sim_initializer_list"
7+
8+
namespace std {
9+
10+
template< class T = void >
11+
struct less;
12+
13+
template< class T >
14+
struct allocator;
15+
16+
template< class Key >
17+
struct hash;
18+
19+
template<
20+
class Key,
21+
class Compare = std::less<Key>,
22+
class Alloc = std::allocator<Key>
23+
> class set {
24+
public:
25+
set(initializer_list<Key> __list) {}
26+
27+
class iterator {
28+
public:
29+
iterator(Key *key): ptr(key) {}
30+
iterator& operator++() { ++ptr; return *this; }
31+
bool operator!=(const iterator &other) const { return ptr != other.ptr; }
32+
const Key &operator*() const { return *ptr; }
33+
private:
34+
Key *ptr;
35+
};
36+
37+
Key *val;
38+
iterator begin() const { return iterator(val); }
39+
iterator end() const { return iterator(val + 1); }
40+
};
41+
42+
} // namespace std
43+
44+
#endif // _SIM_SET

0 commit comments

Comments
 (0)