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 <vince.a.bridgers@ericsson.com>
This commit is contained in:
vabridgers 2024-10-28 03:53:36 -05:00 committed by GitHub
parent 14171b0b13
commit 3d6923dbac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 713 additions and 331 deletions

View File

@ -49,6 +49,7 @@
#include "MultipleStatementMacroCheck.h"
#include "NoEscapeCheck.h"
#include "NonZeroEnumToBoolConversionCheck.h"
#include "NondeterministicPointerIterationOrderCheck.h"
#include "NotNullTerminatedResultCheck.h"
#include "OptionalValueConversionCheck.h"
#include "ParentVirtualCallCheck.h"
@ -174,6 +175,8 @@ public:
"bugprone-multiple-new-in-one-expression");
CheckFactories.registerCheck<MultipleStatementMacroCheck>(
"bugprone-multiple-statement-macro");
CheckFactories.registerCheck<NondeterministicPointerIterationOrderCheck>(
"bugprone-nondeterministic-pointer-iteration-order");
CheckFactories.registerCheck<OptionalValueConversionCheck>(
"bugprone-optional-value-conversion");
CheckFactories.registerCheck<PointerArithmeticOnPolymorphicObjectCheck>(

View File

@ -45,6 +45,7 @@ add_clang_library(clangTidyBugproneModule STATIC
MultipleNewInOneExpressionCheck.cpp
MultipleStatementMacroCheck.cpp
NoEscapeCheck.cpp
NondeterministicPointerIterationOrderCheck.cpp
NonZeroEnumToBoolConversionCheck.cpp
NotNullTerminatedResultCheck.cpp
OptionalValueConversionCheck.cpp

View File

@ -0,0 +1,79 @@
//===----- NondeterministicPointerIterationOrderCheck.cpp - clang-tidy ----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "NondeterministicPointerIterationOrderCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
namespace clang::tidy::bugprone {
void NondeterministicPointerIterationOrderCheck::registerMatchers(
MatchFinder *Finder) {
auto LoopVariable = varDecl(hasType(
qualType(hasCanonicalType(anyOf(referenceType(), pointerType())))));
auto RangeInit = declRefExpr(to(varDecl(
hasType(recordDecl(hasAnyName("std::unordered_set", "std::unordered_map",
"std::unordered_multiset",
"std::unordered_multimap"))
.bind("recorddecl")))));
Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVariable),
hasRangeInit(RangeInit.bind("rangeinit")))
.bind("cxxForRangeStmt"),
this);
auto SortFuncM = callee(functionDecl(hasAnyName(
"std::is_sorted", "std::nth_element", "std::sort", "std::partial_sort",
"std::partition", "std::stable_partition", "std::stable_sort")));
auto IteratesPointerEltsM = hasArgument(
0,
cxxMemberCallExpr(on(hasType(cxxRecordDecl(has(fieldDecl(hasType(qualType(
hasCanonicalType(pointsTo(hasCanonicalType(pointerType()))))))))))));
Finder->addMatcher(
callExpr(allOf(SortFuncM, IteratesPointerEltsM)).bind("sortsemantic"),
this);
}
void NondeterministicPointerIterationOrderCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *ForRangePointers =
Result.Nodes.getNodeAs<CXXForRangeStmt>("cxxForRangeStmt");
if ((ForRangePointers) && !(ForRangePointers->getBeginLoc().isMacroID())) {
const auto *RangeInit = Result.Nodes.getNodeAs<Stmt>("rangeinit");
if (const auto *ClassTemplate =
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(
"recorddecl")) {
const TemplateArgumentList &TemplateArgs =
ClassTemplate->getTemplateArgs();
const llvm::StringRef AlgoName = ClassTemplate->getName();
const bool IsAlgoArgPointer =
TemplateArgs[0].getAsType()->isPointerType();
if (IsAlgoArgPointer) {
SourceRange R = RangeInit->getSourceRange();
diag(R.getBegin(), "iteration of pointers is nondeterministic") << R;
}
}
return;
}
const auto *SortPointers = Result.Nodes.getNodeAs<Stmt>("sortsemantic");
if ((SortPointers) && !(SortPointers->getBeginLoc().isMacroID())) {
SourceRange R = SortPointers->getSourceRange();
diag(R.getBegin(), "sorting pointers is nondeterministic") << R;
}
}
} // namespace clang::tidy::bugprone

View File

@ -0,0 +1,39 @@
//=== NondeterministicPointerIterationOrderCheck.h - clang-tidy -*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NONDETERMINISTIC_POINTER_ITERATION_ORDER_CHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NONDETERMINISTIC_POINTER_ITERATION_ORDER_CHECK_H
#include "../ClangTidyCheck.h"
namespace clang::tidy::bugprone {
/// Finds nondeterministic usages of pointers in unordered containers. The
/// check also finds calls to sorting-like algorithms on a container of
/// pointers.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/nondeterministic-pointer-iteration-order.html
class NondeterministicPointerIterationOrderCheck : public ClangTidyCheck {
public:
NondeterministicPointerIterationOrderCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
std::optional<TraversalKind> getCheckTraversalKind() const override {
return TK_IgnoreUnlessSpelledInSource;
}
};
} // namespace clang::tidy::bugprone
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_NONDETERMINISTIC_POINTER_ITERATION_ORDER_CHECK_H

View File

@ -119,6 +119,12 @@ New checks
Warns about code that tries to cast between pointers by means of
``std::bit_cast`` or ``memcpy``.
- New :doc:`bugprone-nondeterministic-pointer-iteration-order
<clang-tidy/checks/bugprone/nondeterministic-pointer-iteration-order>`
check.
Finds nondeterministic usages of pointers in unordered containers.
- New :doc:`bugprone-tagged-union-member-count
<clang-tidy/checks/bugprone/tagged-union-member-count>` check.

View File

@ -0,0 +1,44 @@
.. title:: clang-tidy - bugprone-nondeterministic-pointer-iteration-order
bugprone-nondeterministic-pointer-iteration-order
=================================================
Finds nondeterministic usages of pointers in unordered containers.
One canonical example is iteration across a container of pointers.
.. code-block:: c++
{
int a = 1, b = 2;
std::unordered_set<int *> UnorderedPtrSet = {&a, &b};
for (auto i : UnorderedPtrSet)
f(i);
}
Another such example is sorting a container of pointers.
.. code-block:: c++
{
int a = 1, b = 2;
std::vector<int *> VectorOfPtr = {&a, &b};
std::sort(VectorOfPtr.begin(), VectorOfPtr.end());
}
Iteration of a containers of pointers may present the order of different
pointers differently across different runs of a program. In some cases this
may be acceptable behavior, in others this may be unexpected behavior. This
check is advisory for this reason.
This check only detects range-based for loops over unordered sets and maps. It
also detects calls sorting-like algorithms on containers holding pointers.
Other similar usages will not be found and are false negatives.
Limitations:
* This check currently does not check if a nondeterministic iteration order is
likely to be a mistake, and instead marks all such iterations as bugprone.
* std::reference_wrapper is not considered yet.
* Only for loops are considered, other iterators can be included in
improvements.

View File

@ -115,6 +115,7 @@ Clang-Tidy Checks
:doc:`bugprone-multiple-new-in-one-expression <bugprone/multiple-new-in-one-expression>`,
:doc:`bugprone-multiple-statement-macro <bugprone/multiple-statement-macro>`,
:doc:`bugprone-no-escape <bugprone/no-escape>`,
:doc:`bugprone-nondeterministic-pointer-iteration-order <bugprone/nondeterministic-pointer-iteration-order>`,
:doc:`bugprone-non-zero-enum-to-bool-conversion <bugprone/non-zero-enum-to-bool-conversion>`,
:doc:`bugprone-not-null-terminated-result <bugprone/not-null-terminated-result>`, "Yes"
:doc:`bugprone-optional-value-conversion <bugprone/optional-value-conversion>`, "Yes"

View File

@ -0,0 +1,31 @@
#ifndef _SIM_ALGORITHM
#define _SIM_ALGORITHM
#pragma clang system_header
namespace std {
template<class ForwardIt>
bool is_sorted(ForwardIt first, ForwardIt last);
template <class RandomIt>
void nth_element(RandomIt first, RandomIt nth, RandomIt last);
template<class RandomIt>
void partial_sort(RandomIt first, RandomIt middle, RandomIt last);
template<class RandomIt>
void sort (RandomIt first, RandomIt last);
template<class RandomIt>
void stable_sort(RandomIt first, RandomIt last);
template<class BidirIt, class UnaryPredicate>
BidirIt partition(BidirIt first, BidirIt last, UnaryPredicate p);
template<class BidirIt, class UnaryPredicate>
BidirIt stable_partition(BidirIt first, BidirIt last, UnaryPredicate p);
} // namespace std
#endif // _SIM_ALGORITHM

View File

@ -0,0 +1,11 @@
#ifndef _SIM_CPP_CONFIG_H
#define _SIM_CPP_CONFIG_H
#pragma clang system_header
typedef unsigned char uint8_t;
typedef __typeof__(sizeof(int)) size_t;
typedef __typeof__((char*)0-(char*)0) ptrdiff_t;
#endif // _SIM_CPP_CONFIG_H

View File

@ -0,0 +1,39 @@
#ifndef _INITIALIZER_LIST
#define _INITIALIZER_LIST
#pragma clang system_header
#
#include "sim_c++config.h" // size_t
namespace std {
template <class _E>
class initializer_list {
const _E* __begin_;
size_t __size_;
initializer_list(const _E* __b, size_t __s)
: __begin_(__b),
__size_(__s)
{}
public:
typedef _E value_type;
typedef const _E& reference;
typedef const _E& const_reference;
typedef size_t size_type;
typedef const _E* iterator;
typedef const _E* const_iterator;
initializer_list() : __begin_(0), __size_(0) {}
size_t size() const {return __size_;}
const _E* begin() const {return __begin_;}
const _E* end() const {return __begin_ + __size_;}
}; // class initializer_list
} // namespace std
#endif // _INITIALIZER_LIST

View File

@ -0,0 +1,22 @@
#ifndef _SIM_ITERATOR_BASE
#define _SIM_ITERATOR_BASE
namespace std {
struct input_iterator_tag { };
struct output_iterator_tag { };
struct forward_iterator_tag : public input_iterator_tag { };
struct bidirectional_iterator_tag : public forward_iterator_tag { };
struct random_access_iterator_tag : public bidirectional_iterator_tag { };
template <typename Iterator> struct iterator_traits {
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
typedef typename Iterator::iterator_category iterator_category;
};
} // namespace std
#endif // _SIM_ITERATOR_BASE

View File

@ -0,0 +1,34 @@
#ifndef _SIM_MAP
#define _SIM_MAP
#pragma clang system_header
#include "sim_stl_pair"
namespace std {
template <typename Key, typename Value>
class map {
public:
using value_type = pair<Key, Value>;
map();
map(initializer_list<pair<Key, Value>> initList);
value_type& operator[](const Key& key);
value_type& operator[](Key&& key);
class iterator {
public:
iterator(Key *key): ptr(key) {}
iterator& operator++() { ++ptr; return *this; }
bool operator!=(const iterator &other) const { return ptr != other.ptr; }
const Key &operator*() const { return *ptr; }
private:
Key *ptr;
};
Key *val;
iterator begin() const { return iterator(val); }
iterator end() const { return iterator(val + 1); }
};
} // namespace std
#endif // _SIM_MAP

View File

@ -0,0 +1,44 @@
#ifndef _SIM_SET
#define _SIM_SET
#pragma clang system_header
#include "sim_initializer_list"
namespace std {
template< class T = void >
struct less;
template< class T >
struct allocator;
template< class Key >
struct hash;
template<
class Key,
class Compare = std::less<Key>,
class Alloc = std::allocator<Key>
> class set {
public:
set(initializer_list<Key> __list) {}
class iterator {
public:
iterator(Key *key): ptr(key) {}
iterator& operator++() { ++ptr; return *this; }
bool operator!=(const iterator &other) const { return ptr != other.ptr; }
const Key &operator*() const { return *ptr; }
private:
Key *ptr;
};
Key *val;
iterator begin() const { return iterator(val); }
iterator end() const { return iterator(val + 1); }
};
} // namespace std
#endif // _SIM_SET

View File

@ -0,0 +1,32 @@
#ifndef _SIM_STL_PAIR
#define _SIM_STL_PAIR
#pragma clang system_header
#include "sim_type_traits"
namespace std {
template <class T1, class T2>
struct pair {
T1 first;
T2 second;
pair() : first(), second() {}
pair(const T1 &a, const T2 &b) : first(a), second(b) {}
template<class U1, class U2>
pair(const pair<U1, U2> &other) : first(other.first),
second(other.second) {}
};
template <typename T1, typename T2>
pair<typename remove_reference<T1>::type, typename remove_reference<T2>::type>
make_pair(T1 &&, T2 &&) {
return {};
};
} // namespace std
#endif // _SIM_STL_PAIR

View File

@ -0,0 +1,19 @@
#ifndef _SIM_TYPE_TRAITS
#define _SIM_TYPE_TRAITS
#pragma clang system_header
namespace std {
template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};
template<typename T> typename remove_reference<T>::type&& move(T&& a);
template< class T >
using remove_reference_t = typename remove_reference<T>::type;
} // namespace std
#endif // _SIM_TYPE_TRAITS

View File

@ -0,0 +1,33 @@
#ifndef _SIM_UNORDERED_MAP
#define _SIM_UNORDERED_MAP
#pragma clang system_header
#include "sim_initializer_list"
namespace std {
template <typename Key, typename Value>
class unordered_map {
public:
using value_type = pair<Key, Value>;
unordered_map();
unordered_map(initializer_list<pair<Key, Value>> initList);
value_type& operator[](const Key& key);
value_type& operator[](Key&& key);
class iterator {
public:
iterator(Key *key): ptr(key) {}
iterator& operator++() { ++ptr; return *this; }
bool operator!=(const iterator &other) const { return ptr != other.ptr; }
const Key &operator*() const { return *ptr; }
private:
Key *ptr;
};
Key *val;
iterator begin() const { return iterator(val); }
iterator end() const { return iterator(val + 1); }
};
} // namespace std
#endif // _SIM_UNORDERED_MAP

View File

@ -0,0 +1,35 @@
#ifndef _SIM_UNORDERED_SET
#define _SIM_UNORDERED_SET
#pragma clang system_header
#include "sim_initializer_list"
namespace std {
template<
class Key,
class Hash = std::hash<Key>,
class Compare = std::less<Key>,
class Alloc = std::allocator<Key>
> class unordered_set {
public:
unordered_set(initializer_list<Key> __list) {}
class iterator {
public:
iterator(Key *key): ptr(key) {}
iterator& operator++() { ++ptr; return *this; }
bool operator!=(const iterator &other) const { return ptr != other.ptr; }
const Key &operator*() const { return *ptr; }
private:
Key *ptr;
};
Key *val;
iterator begin() const { return iterator(val); }
iterator end() const { return iterator(val + 1); }
};
} // namespace std
#endif // _SIM_UNORDERED_SET

View File

@ -0,0 +1,150 @@
#ifndef _SIM_VECTOR
#define _SIM_VECTOR
#pragma clang system_header
#include "sim_iterator_base"
namespace std {
template <typename T, typename Ptr, typename Ref> struct __vector_iterator {
typedef __vector_iterator<T, T *, T &> iterator;
typedef __vector_iterator<T, const T *, const T &> const_iterator;
typedef ptrdiff_t difference_type;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef std::random_access_iterator_tag iterator_category;
__vector_iterator(const Ptr p = 0) : ptr(p) {}
__vector_iterator(const iterator &rhs): ptr(rhs.base()) {}
__vector_iterator<T, Ptr, Ref>& operator++() { ++ ptr; return *this; }
__vector_iterator<T, Ptr, Ref> operator++(int) {
auto tmp = *this;
++ ptr;
return tmp;
}
__vector_iterator<T, Ptr, Ref> operator--() { -- ptr; return *this; }
__vector_iterator<T, Ptr, Ref> operator--(int) {
auto tmp = *this; -- ptr;
return tmp;
}
__vector_iterator<T, Ptr, Ref> operator+(difference_type n) {
return ptr + n;
}
friend __vector_iterator<T, Ptr, Ref> operator+(
difference_type n,
const __vector_iterator<T, Ptr, Ref> &iter) {
return n + iter.ptr;
}
__vector_iterator<T, Ptr, Ref> operator-(difference_type n) {
return ptr - n;
}
__vector_iterator<T, Ptr, Ref> operator+=(difference_type n) {
return ptr += n;
}
__vector_iterator<T, Ptr, Ref> operator-=(difference_type n) {
return ptr -= n;
}
template<typename U, typename Ptr2, typename Ref2>
difference_type operator-(const __vector_iterator<U, Ptr2, Ref2> &rhs);
Ref operator*() const { return *ptr; }
Ptr operator->() const { return ptr; }
Ref operator[](difference_type n) {
return *(ptr+n);
}
bool operator==(const iterator &rhs) const { return ptr == rhs.ptr; }
bool operator==(const const_iterator &rhs) const { return ptr == rhs.ptr; }
bool operator!=(const iterator &rhs) const { return ptr != rhs.ptr; }
bool operator!=(const const_iterator &rhs) const { return ptr != rhs.ptr; }
const Ptr& base() const { return ptr; }
private:
Ptr ptr;
};
template<typename T>
class vector {
T *_start;
T *_finish;
T *_end_of_storage;
public:
typedef T value_type;
typedef size_t size_type;
typedef __vector_iterator<T, T *, T &> iterator;
typedef __vector_iterator<T, const T *, const T &> const_iterator;
vector() : _start(0), _finish(0), _end_of_storage(0) {}
template <typename InputIterator>
vector(InputIterator first, InputIterator last);
vector(const vector &other);
vector(vector &&other);
~vector();
size_t size() const {
return size_t(_finish - _start);
}
vector& operator=(const vector &other);
vector& operator=(vector &&other);
vector& operator=(std::initializer_list<T> ilist);
void assign(size_type count, const T &value);
template <typename InputIterator >
void assign(InputIterator first, InputIterator last);
void assign(std::initializer_list<T> ilist);
void clear();
void push_back(const T &value);
void push_back(T &&value);
template<class... Args>
void emplace_back(Args&&... args);
void pop_back();
iterator insert(const_iterator position, const value_type &val);
iterator insert(const_iterator position, size_type n,
const value_type &val);
template <typename InputIterator>
iterator insert(const_iterator position, InputIterator first,
InputIterator last);
iterator insert(const_iterator position, value_type &&val);
iterator insert(const_iterator position, initializer_list<value_type> il);
template <class... Args>
iterator emplace(const_iterator position, Args&&... args);
iterator erase(const_iterator position);
iterator erase(const_iterator first, const_iterator last);
T &operator[](size_t n) {
return _start[n];
}
const T &operator[](size_t n) const {
return _start[n];
}
iterator begin() { return iterator(_start); }
const_iterator begin() const { return const_iterator(_start); }
const_iterator cbegin() const { return const_iterator(_start); }
iterator end() { return iterator(_finish); }
const_iterator end() const { return const_iterator(_finish); }
const_iterator cend() const { return const_iterator(_finish); }
T& front() { return *begin(); }
const T& front() const { return *begin(); }
T& back() { return *(end() - 1); }
const T& back() const { return *(end() - 1); }
};
} // namespace std
#endif // _SIM_VECTOR

View File

@ -0,0 +1,84 @@
// RUN: %check_clang_tidy %s bugprone-nondeterministic-pointer-iteration-order %t -- -- -I%S -std=c++!4
#include "Inputs/system-header-simulator/sim_set"
#include "Inputs/system-header-simulator/sim_unordered_set"
#include "Inputs/system-header-simulator/sim_map"
#include "Inputs/system-header-simulator/sim_unordered_map"
#include "Inputs/system-header-simulator/sim_vector"
#include "Inputs/system-header-simulator/sim_algorithm"
template<class T>
void f(T x);
void PointerIteration() {
int a = 1, b = 2;
std::set<int> OrderedIntSet = {a, b};
std::set<int *> OrderedPtrSet = {&a, &b};
std::unordered_set<int> UnorderedIntSet = {a, b};
std::unordered_set<int *> UnorderedPtrSet = {&a, &b};
std::map<int, int> IntMap = { std::make_pair(a,a), std::make_pair(b,b) };
std::map<int*, int*> PtrMap = { std::make_pair(&a,&a), std::make_pair(&b,&b) };
std::unordered_map<int, int> IntUnorderedMap = { std::make_pair(a,a), std::make_pair(b,b) };
std::unordered_map<int*, int*> PtrUnorderedMap = { std::make_pair(&a,&a), std::make_pair(&b,&b) };
for (auto i : OrderedIntSet) // no-warning
f(i);
for (auto i : OrderedPtrSet) // no-warning
f(i);
for (auto i : UnorderedIntSet) // no-warning
f(i);
for (auto i : UnorderedPtrSet)
f(i);
// CHECK-MESSAGES: :[[@LINE-2]]:17: warning: iteration of pointers is nondeterministic
for (auto &i : UnorderedPtrSet)
f(i);
// CHECK-MESSAGES: :[[@LINE-2]]:18: warning: iteration of pointers is nondeterministic
for (auto &i : IntMap) // no-warning
f(i);
for (auto &i : PtrMap) // no-warning
f(i);
for (auto &i : IntUnorderedMap) // no-warning
f(i);
for (auto &i : PtrUnorderedMap)
f(i);
// CHECK-MESSAGES: :[[@LINE-2]]:18: warning: iteration of pointers is nondeterministic
}
bool g (int *x) { return true; }
bool h (int x) { return true; }
void PointerSorting() {
int a = 1, b = 2, c = 3;
std::vector<int> V1 = {a, b};
std::vector<int *> V2 = {&a, &b};
std::is_sorted(V1.begin(), V1.end()); // no-warning
std::nth_element(V1.begin(), V1.begin() + 1, V1.end()); // no-warning
std::partial_sort(V1.begin(), V1.begin() + 1, V1.end()); // no-warning
std::sort(V1.begin(), V1.end()); // no-warning
std::stable_sort(V1.begin(), V1.end()); // no-warning
std::partition(V1.begin(), V1.end(), h); // no-warning
std::stable_partition(V1.begin(), V1.end(), h); // no-warning
std::is_sorted(V2.begin(), V2.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: sorting pointers is nondeterministic
std::nth_element(V2.begin(), V2.begin() + 1, V2.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: sorting pointers is nondeterministic
std::partial_sort(V2.begin(), V2.begin() + 1, V2.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: sorting pointers is nondeterministic
std::sort(V2.begin(), V2.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: sorting pointers is nondeterministic
std::stable_sort(V2.begin(), V2.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: sorting pointers is nondeterministic
std::partition(V2.begin(), V2.end(), g);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: sorting pointers is nondeterministic
std::stable_partition(V2.begin(), V2.end(), g);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: sorting pointers is nondeterministic
}

View File

@ -786,6 +786,12 @@ Moved checkers
To detect too large arguments passed to malloc, consider using the checker
``alpha.taint.TaintedAlloc``.
- The checkers ``alpha.nondeterministic.PointerSorting`` and
``alpha.nondeterministic.PointerIteration`` were moved to a new bugprone
checker named ``bugprone-nondeterministic-pointer-iteration-order``. The
original checkers were implemented only using AST matching and make more
sense as a single clang-tidy check.
.. _release-notes-sanitizers:
Sanitizers

View File

@ -3447,37 +3447,6 @@ Limitations:
More details at the corresponding `GitHub issue <https://github.com/llvm/llvm-project/issues/43459>`_.
.. _alpha-nondeterminism-PointerIteration:
alpha.nondeterminism.PointerIteration (C++)
"""""""""""""""""""""""""""""""""""""""""""
Check for non-determinism caused by iterating unordered containers of pointers.
.. code-block:: c
void test() {
int a = 1, b = 2;
std::unordered_set<int *> UnorderedPtrSet = {&a, &b};
for (auto i : UnorderedPtrSet) // warn
f(i);
}
.. _alpha-nondeterminism-PointerSorting:
alpha.nondeterminism.PointerSorting (C++)
"""""""""""""""""""""""""""""""""""""""""
Check for non-determinism caused by sorting of pointers.
.. code-block:: c
void test() {
int a = 1, b = 2;
std::vector<int *> V = {&a, &b};
std::sort(V.begin(), V.end()); // warn
}
alpha.WebKit
^^^^^^^^^^^^

View File

@ -118,8 +118,6 @@ def Debug : Package<"debug">, Hidden;
def CloneDetectionAlpha : Package<"clone">, ParentPackage<Alpha>;
def NonDeterminismAlpha : Package<"nondeterminism">, ParentPackage<Alpha>;
def Fuchsia : Package<"fuchsia">;
def FuchsiaAlpha : Package<"fuchsia">, ParentPackage<Alpha>;
@ -1711,22 +1709,6 @@ def TaintedDivChecker: Checker<"TaintedDiv">,
} // end "optin.taint"
//===----------------------------------------------------------------------===//
// NonDeterminism checkers.
//===----------------------------------------------------------------------===//
let ParentPackage = NonDeterminismAlpha in {
def PointerIterationChecker : Checker<"PointerIteration">,
HelpText<"Checks for non-determinism caused by iteration of unordered containers of pointers">,
Documentation<HasDocumentation>;
def PointerSortingChecker : Checker<"PointerSorting">,
HelpText<"Check for non-determinism caused by sorting of pointers">,
Documentation<HasDocumentation>;
} // end alpha.nondeterminism
//===----------------------------------------------------------------------===//
// Fuchsia checkers.
//===----------------------------------------------------------------------===//

View File

@ -91,8 +91,6 @@ add_clang_library(clangStaticAnalyzerCheckers
OSObjectCStyleCast.cpp
PaddingChecker.cpp
PointerArithChecker.cpp
PointerIterationChecker.cpp
PointerSortingChecker.cpp
PointerSubChecker.cpp
PthreadLockChecker.cpp
PutenvStackArrayChecker.cpp

View File

@ -1,101 +0,0 @@
//== PointerIterationChecker.cpp ------------------------------- -*- C++ -*--=//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines PointerIterationChecker which checks for non-determinism
// caused due to iteration of unordered containers of pointer elements.
//
//===----------------------------------------------------------------------===//
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
using namespace clang;
using namespace ento;
using namespace ast_matchers;
namespace {
// ID of a node at which the diagnostic would be emitted.
constexpr llvm::StringLiteral WarnAtNode = "iter";
class PointerIterationChecker : public Checker<check::ASTCodeBody> {
public:
void checkASTCodeBody(const Decl *D,
AnalysisManager &AM,
BugReporter &BR) const;
};
static void emitDiagnostics(const BoundNodes &Match, const Decl *D,
BugReporter &BR, AnalysisManager &AM,
const PointerIterationChecker *Checker) {
auto *ADC = AM.getAnalysisDeclContext(D);
const auto *MarkedStmt = Match.getNodeAs<Stmt>(WarnAtNode);
assert(MarkedStmt);
auto Range = MarkedStmt->getSourceRange();
auto Location = PathDiagnosticLocation::createBegin(MarkedStmt,
BR.getSourceManager(),
ADC);
std::string Diagnostics;
llvm::raw_string_ostream OS(Diagnostics);
OS << "Iteration of pointer-like elements "
<< "can result in non-deterministic ordering";
BR.EmitBasicReport(ADC->getDecl(), Checker,
"Iteration of pointer-like elements", "Non-determinism",
Diagnostics, Location, Range);
}
// Assumption: Iteration of ordered containers of pointers is deterministic.
// TODO: Currently, we only check for std::unordered_set. Other unordered
// containers like std::unordered_map also need to be handled.
// TODO: Currently, we do not check what the for loop does with the iterated
// pointer values. Not all iterations may cause non-determinism. For example,
// counting or summing up the elements should not be non-deterministic.
auto matchUnorderedIterWithPointers() -> decltype(decl()) {
auto UnorderedContainerM = declRefExpr(to(varDecl(hasType(
recordDecl(hasName("std::unordered_set")
)))));
auto PointerTypeM = varDecl(hasType(hasCanonicalType(pointerType())));
auto PointerIterM = stmt(cxxForRangeStmt(
hasLoopVariable(PointerTypeM),
hasRangeInit(UnorderedContainerM)
)).bind(WarnAtNode);
return decl(forEachDescendant(PointerIterM));
}
void PointerIterationChecker::checkASTCodeBody(const Decl *D,
AnalysisManager &AM,
BugReporter &BR) const {
auto MatcherM = matchUnorderedIterWithPointers();
auto Matches = match(MatcherM, *D, AM.getASTContext());
for (const auto &Match : Matches)
emitDiagnostics(Match, D, BR, AM, this);
}
} // end of anonymous namespace
void ento::registerPointerIterationChecker(CheckerManager &Mgr) {
Mgr.registerChecker<PointerIterationChecker>();
}
bool ento::shouldRegisterPointerIterationChecker(const CheckerManager &mgr) {
const LangOptions &LO = mgr.getLangOpts();
return LO.CPlusPlus;
}

View File

@ -1,115 +0,0 @@
//== PointerSortingChecker.cpp --------------------------------- -*- C++ -*--=//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines PointerSortingChecker which checks for non-determinism
// caused due to sorting containers with pointer-like elements.
//
//===----------------------------------------------------------------------===//
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
using namespace clang;
using namespace ento;
using namespace ast_matchers;
namespace {
// ID of a node at which the diagnostic would be emitted.
constexpr llvm::StringLiteral WarnAtNode = "sort";
class PointerSortingChecker : public Checker<check::ASTCodeBody> {
public:
void checkASTCodeBody(const Decl *D,
AnalysisManager &AM,
BugReporter &BR) const;
};
static void emitDiagnostics(const BoundNodes &Match, const Decl *D,
BugReporter &BR, AnalysisManager &AM,
const PointerSortingChecker *Checker) {
auto *ADC = AM.getAnalysisDeclContext(D);
const auto *MarkedStmt = Match.getNodeAs<CallExpr>(WarnAtNode);
assert(MarkedStmt);
auto Range = MarkedStmt->getSourceRange();
auto Location = PathDiagnosticLocation::createBegin(MarkedStmt,
BR.getSourceManager(),
ADC);
std::string Diagnostics;
llvm::raw_string_ostream OS(Diagnostics);
OS << "Sorting pointer-like elements "
<< "can result in non-deterministic ordering";
BR.EmitBasicReport(ADC->getDecl(), Checker,
"Sorting of pointer-like elements", "Non-determinism",
OS.str(), Location, Range);
}
decltype(auto) callsName(const char *FunctionName) {
return callee(functionDecl(hasName(FunctionName)));
}
// FIXME: Currently we simply check if std::sort is used with pointer-like
// elements. This approach can have a big false positive rate. Using std::sort,
// std::unique and then erase is common technique for deduplicating a container
// (which in some cases might even be quicker than using, let's say std::set).
// In case a container contains arbitrary memory addresses (e.g. multiple
// things give different stuff but might give the same thing multiple times)
// which we don't want to do things with more than once, we might use
// sort-unique-erase and the sort call will emit a report.
auto matchSortWithPointers() -> decltype(decl()) {
// Match any of these function calls.
auto SortFuncM = anyOf(
callsName("std::is_sorted"),
callsName("std::nth_element"),
callsName("std::partial_sort"),
callsName("std::partition"),
callsName("std::sort"),
callsName("std::stable_partition"),
callsName("std::stable_sort")
);
// Match only if the container has pointer-type elements.
auto IteratesPointerEltsM = hasArgument(0,
hasType(cxxRecordDecl(has(
fieldDecl(hasType(hasCanonicalType(
pointsTo(hasCanonicalType(pointerType()))
)))
))));
auto PointerSortM = traverse(
TK_AsIs,
stmt(callExpr(allOf(SortFuncM, IteratesPointerEltsM))).bind(WarnAtNode));
return decl(forEachDescendant(PointerSortM));
}
void PointerSortingChecker::checkASTCodeBody(const Decl *D,
AnalysisManager &AM,
BugReporter &BR) const {
auto MatcherM = matchSortWithPointers();
auto Matches = match(MatcherM, *D, AM.getASTContext());
for (const auto &Match : Matches)
emitDiagnostics(Match, D, BR, AM, this);
}
} // end of anonymous namespace
void ento::registerPointerSortingChecker(CheckerManager &Mgr) {
Mgr.registerChecker<PointerSortingChecker>();
}
bool ento::shouldRegisterPointerSortingChecker(const CheckerManager &mgr) {
const LangOptions &LO = mgr.getLangOpts();
return LO.CPlusPlus;
}

View File

@ -1,28 +0,0 @@
// RUN: %clang_analyze_cc1 %s -std=c++14 -analyzer-output=text -verify \
// RUN: -analyzer-checker=core,alpha.nondeterminism.PointerIteration
#include "Inputs/system-header-simulator-cxx.h"
template<class T>
void f(T x);
void PointerIteration() {
int a = 1, b = 2;
std::set<int> OrderedIntSet = {a, b};
std::set<int *> OrderedPtrSet = {&a, &b};
std::unordered_set<int> UnorderedIntSet = {a, b};
std::unordered_set<int *> UnorderedPtrSet = {&a, &b};
for (auto i : OrderedIntSet) // no-warning
f(i);
for (auto i : OrderedPtrSet) // no-warning
f(i);
for (auto i : UnorderedIntSet) // no-warning
f(i);
for (auto i : UnorderedPtrSet) // expected-warning {{Iteration of pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerIteration]
// expected-note@-1 {{Iteration of pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerIteration]
f(i);
}

View File

@ -1,36 +0,0 @@
// RUN: %clang_analyze_cc1 %s -std=c++14 -analyzer-output=text -verify \
// RUN: -analyzer-checker=core,alpha.nondeterminism.PointerSorting
#include "Inputs/system-header-simulator-cxx.h"
bool f(int x) { return true; }
bool g(int *x) { return true; }
void PointerSorting() {
int a = 1, b = 2;
std::vector<int> V1 = {a, b};
std::vector<int *> V2 = {&a, &b};
std::is_sorted(V1.begin(), V1.end()); // no-warning
std::nth_element(V1.begin(), V1.begin() + 1, V1.end()); // no-warning
std::partial_sort(V1.begin(), V1.begin() + 1, V1.end()); // no-warning
std::sort(V1.begin(), V1.end()); // no-warning
std::stable_sort(V1.begin(), V1.end()); // no-warning
std::partition(V1.begin(), V1.end(), f); // no-warning
std::stable_partition(V1.begin(), V1.end(), g); // no-warning
std::is_sorted(V2.begin(), V2.end()); // expected-warning {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
// expected-note@-1 {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
std::nth_element(V2.begin(), V2.begin() + 1, V2.end()); // expected-warning {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
// expected-note@-1 {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
std::partial_sort(V2.begin(), V2.begin() + 1, V2.end()); // expected-warning {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
// expected-note@-1 {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
std::sort(V2.begin(), V2.end()); // expected-warning {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
// expected-note@-1 {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
std::stable_sort(V2.begin(), V2.end()); // expected-warning {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
// expected-note@-1 {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
std::partition(V2.begin(), V2.end(), f); // expected-warning {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
// expected-note@-1 {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
std::stable_partition(V2.begin(), V2.end(), g); // expected-warning {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
// expected-note@-1 {{Sorting pointer-like elements can result in non-deterministic ordering}} [alpha.nondeterminism.PointerSorting]
}