Skip to content

Commit 3ab0347

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 89d2a9d commit 3ab0347

26 files changed

+691
-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 "NondeterministicPointerUsageCheck.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<NondeterministicPointerUsageCheck>(
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
4545
MultipleNewInOneExpressionCheck.cpp
4646
MultipleStatementMacroCheck.cpp
4747
NoEscapeCheck.cpp
48+
NondeterministicPointerUsageCheck.cpp
4849
NonZeroEnumToBoolConversionCheck.cpp
4950
NotNullTerminatedResultCheck.cpp
5051
OptionalValueConversionCheck.cpp
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//===------- NondetermnisticPointerUsageCheck.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 "NondeterministicPointerUsageCheck.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 NondeterministicPointerUsageCheck::registerMatchers(MatchFinder *Finder) {
18+
19+
auto LoopVariable = varDecl(hasType(hasCanonicalType(pointerType())));
20+
21+
auto RangeInit = declRefExpr(to(varDecl(hasType(recordDecl(
22+
anyOf(hasName("std::unordered_set"), hasName("std::unordered_map"),
23+
hasName("std::unordered_multiset"),
24+
hasName("std::unordered_multimap")))))));
25+
26+
Finder->addMatcher(
27+
stmt(cxxForRangeStmt(hasRangeInit(RangeInit.bind("rangeinit")),
28+
hasLoopVariable(LoopVariable.bind("loopVar"))))
29+
.bind("cxxForRangeStmt"),
30+
this);
31+
32+
auto SortFuncM = anyOf(callee(functionDecl(hasName("std::is_sorted"))),
33+
callee(functionDecl(hasName("std::nth_element"))),
34+
callee(functionDecl(hasName("std::sort"))),
35+
callee(functionDecl(hasName("std::partial_sort"))),
36+
callee(functionDecl(hasName("std::partition"))),
37+
callee(functionDecl(hasName("std::stable_partition"))),
38+
callee(functionDecl(hasName("std::stable_sort"))));
39+
40+
auto IteratesPointerEltsM = hasArgument(
41+
0,
42+
cxxMemberCallExpr(on(hasType(cxxRecordDecl(has(fieldDecl(hasType(
43+
hasCanonicalType(pointsTo(hasCanonicalType(pointerType())))))))))));
44+
45+
Finder->addMatcher(stmt(callExpr(allOf(SortFuncM, IteratesPointerEltsM)))
46+
.bind("sortsemantic"),
47+
this);
48+
}
49+
50+
void NondeterministicPointerUsageCheck::check(
51+
const MatchFinder::MatchResult &Result) {
52+
const auto *ForRangePointers =
53+
Result.Nodes.getNodeAs<Stmt>("cxxForRangeStmt");
54+
const auto *SortPointers = Result.Nodes.getNodeAs<Stmt>("sortsemantic");
55+
56+
if ((ForRangePointers) && !(ForRangePointers->getBeginLoc().isMacroID())) {
57+
const auto *Node = dyn_cast<CXXForRangeStmt>(ForRangePointers);
58+
diag(Node->getRParenLoc(), "Iteration of pointers is nondeterministic");
59+
}
60+
61+
if ((SortPointers) && !(SortPointers->getBeginLoc().isMacroID())) {
62+
const auto *Node = dyn_cast<Stmt>(SortPointers);
63+
diag(Node->getBeginLoc(), "Sorting pointers is nondeterministic");
64+
}
65+
}
66+
67+
} // namespace clang::tidy::bugprone
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//===------- NondeterministicPointerUsageCheck.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_NONDETERMINISTICPOINTERUSAGECHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NONDETERMINISTICPOINTERUSAGECHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// For the user-facing documentation see:
17+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/nondeterministic-pointer-usage.html
18+
class NondeterministicPointerUsageCheck : public ClangTidyCheck {
19+
public:
20+
NondeterministicPointerUsageCheck(StringRef Name, ClangTidyContext *Context)
21+
: ClangTidyCheck(Name, Context) {}
22+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
23+
return LangOpts.CPlusPlus;
24+
}
25+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
26+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
27+
std::optional<TraversalKind> getCheckTraversalKind() const override {
28+
return TK_IgnoreUnlessSpelledInSource;
29+
}
30+
};
31+
32+
} // namespace clang::tidy::bugprone
33+
34+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NONDETERMINISTICPOINTERUSAGECHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ New checks
121121
Gives warnings for tagged unions, where the number of tags is
122122
different from the number of data members inside the union.
123123

124+
- New :doc:`bugprone-nondeterministic-pointer-iteration-order
125+
<clang-tidy/checks/bugprone/nondeterministic-pointer-iteration-order>`
126+
check.
127+
128+
Finds nondeterministic usages of pointers in unordered containers.
129+
124130
New check aliases
125131
^^^^^^^^^^^^^^^^^
126132

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. Other
35+
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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
public:
28+
Key *val;
29+
iterator begin() const { return iterator(val); }
30+
iterator end() const { return iterator(val + 1); }
31+
};
32+
33+
} // namespace std
34+
35+
#endif // _SIM_MAP
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
public:
38+
Key *val;
39+
iterator begin() const { return iterator(val); }
40+
iterator end() const { return iterator(val + 1); }
41+
};
42+
43+
} // namespace std
44+
45+
#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)