Skip to content

Commit 1cf734f

Browse files
author
einvbri
committed
[clang-tidy] [analyzer] Move nondeterministic pointer usage check to tidy
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 clang-tidy checks 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.
1 parent 20bda93 commit 1cf734f

26 files changed

+689
-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: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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 =
21+
varDecl(hasType(qualType(hasCanonicalType(pointerType()))));
22+
23+
auto RangeInit = declRefExpr(to(varDecl(hasType(recordDecl(
24+
hasAnyName("std::unordered_set", "std::unordered_map",
25+
"std::unordered_multiset", "std::unordered_multimap"))))));
26+
27+
Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVariable),
28+
hasRangeInit(RangeInit.bind("rangeinit")))
29+
.bind("cxxForRangeStmt"),
30+
this);
31+
32+
auto SortFuncM = callee(functionDecl(hasAnyName(
33+
"std::is_sorted", "std::nth_element", "std::sort", "std::partial_sort",
34+
"std::partition", "std::stable_partition", "std::stable_sort")));
35+
36+
auto IteratesPointerEltsM = hasArgument(
37+
0,
38+
cxxMemberCallExpr(on(hasType(cxxRecordDecl(has(fieldDecl(hasType(qualType(
39+
hasCanonicalType(pointsTo(hasCanonicalType(pointerType()))))))))))));
40+
41+
Finder->addMatcher(
42+
callExpr(allOf(SortFuncM, IteratesPointerEltsM)).bind("sortsemantic"),
43+
this);
44+
}
45+
46+
void NondeterministicPointerIterationOrderCheck::check(
47+
const MatchFinder::MatchResult &Result) {
48+
const auto *ForRangePointers =
49+
Result.Nodes.getNodeAs<CXXForRangeStmt>("cxxForRangeStmt");
50+
51+
if ((ForRangePointers) && !(ForRangePointers->getBeginLoc().isMacroID())) {
52+
const auto *RangeInit = Result.Nodes.getNodeAs<Stmt>("rangeinit");
53+
SourceRange R = RangeInit->getSourceRange();
54+
diag(R.getBegin(), "iteration of pointers is nondeterministic") << R;
55+
return;
56+
}
57+
const auto *SortPointers = Result.Nodes.getNodeAs<Stmt>("sortsemantic");
58+
59+
if ((SortPointers) && !(SortPointers->getBeginLoc().isMacroID())) {
60+
const auto *SortSemantic = Result.Nodes.getNodeAs<Stmt>("sortsemantic");
61+
SourceRange R = SortSemantic->getSourceRange();
62+
diag(R.getBegin(), "sorting pointers is nondeterministic") << R;
63+
}
64+
}
65+
66+
} // namespace clang::tidy::bugprone
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.
17+
///
18+
/// For the user-facing documentation see:
19+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/nondeterministic-pointer-iteration-order.html
20+
class NondeterministicPointerIterationOrderCheck : public ClangTidyCheck {
21+
public:
22+
NondeterministicPointerIterationOrderCheck(StringRef Name,
23+
ClangTidyContext *Context)
24+
: ClangTidyCheck(Name, Context) {}
25+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
26+
return LangOpts.CPlusPlus;
27+
}
28+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
29+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
30+
std::optional<TraversalKind> getCheckTraversalKind() const override {
31+
return TK_IgnoreUnlessSpelledInSource;
32+
}
33+
};
34+
35+
} // namespace clang::tidy::bugprone
36+
37+
#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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.
35+
Other similar usages will not be found and are false negatives.
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
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#ifndef _SIM_STL_PAIR
2+
#define _SIM_STL_PAIR
3+
4+
#pragma clang system_header
5+
6+
#include "sim_type_traits"
7+
8+
namespace std {
9+
10+
11+
template <class T1, class T2>
12+
struct pair {
13+
T1 first;
14+
T2 second;
15+
16+
pair() : first(), second() {}
17+
pair(const T1 &a, const T2 &b) : first(a), second(b) {}
18+
19+
template<class U1, class U2>
20+
pair(const pair<U1, U2> &other) : first(other.first),
21+
second(other.second) {}
22+
};
23+
24+
template <typename T1, typename T2>
25+
pair<typename remove_reference<T1>::type, typename remove_reference<T2>::type>
26+
make_pair(T1 &&, T2 &&) {
27+
return {};
28+
};
29+
30+
} // namespace std
31+
32+
#endif // _SIM_STL_PAIR
33+

0 commit comments

Comments
 (0)