A clang-tidy check for std:accumulate.

Summary:
For folds (e.g. std::accumulate), check matches between the provided init value and the range's value_type. A typical error is "std::accumulate(begin, end, 0);", where begin and end have float value_type. See the documentation for more examples.

For now we check std::accumulate, std::reduce and std::inner_product.

Reviewers: hokein, alexfh

Subscribers: Prazek, aaron.ballman, cfe-commits, courbet

Patch by Clément Courbet!

Differential Revision: http://reviews.llvm.org/D18442

llvm-svn: 267542
This commit is contained in:
Alexander Kornienko 2016-04-26 10:05:45 +00:00
parent 5a55a029c0
commit 6e2f32c561
7 changed files with 374 additions and 0 deletions

View File

@ -7,6 +7,7 @@ add_clang_library(clangTidyMiscModule
BoolPointerImplicitConversionCheck.cpp
DanglingHandleCheck.cpp
DefinitionsInHeadersCheck.cpp
FoldInitTypeCheck.cpp
ForwardDeclarationNamespaceCheck.cpp
InaccurateEraseCheck.cpp
IncorrectRoundings.cpp

View File

@ -0,0 +1,140 @@
//===--- FoldInitTypeCheck.cpp - clang-tidy--------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "FoldInitTypeCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace misc {
void FoldInitTypeCheck::registerMatchers(MatchFinder *Finder) {
// We match functions of interest and bind the iterator and init value types.
// Note: Right now we check only builtin types.
const auto BuiltinTypeWithId = [](const char *ID) {
return hasCanonicalType(builtinType().bind(ID));
};
const auto IteratorWithValueType = [&BuiltinTypeWithId](const char *ID) {
return anyOf(
// Pointer types.
pointsTo(BuiltinTypeWithId(ID)),
// Iterator types.
recordType(hasDeclaration(has(typedefNameDecl(
hasName("value_type"), hasType(BuiltinTypeWithId(ID)))))));
};
const auto IteratorParam = parmVarDecl(
hasType(hasCanonicalType(IteratorWithValueType("IterValueType"))));
const auto Iterator2Param = parmVarDecl(
hasType(hasCanonicalType(IteratorWithValueType("Iter2ValueType"))));
const auto InitParam = parmVarDecl(hasType(BuiltinTypeWithId("InitType")));
// std::accumulate, std::reduce.
Finder->addMatcher(
callExpr(callee(functionDecl(
hasAnyName("::std::accumulate", "::std::reduce"),
hasParameter(0, IteratorParam), hasParameter(2, InitParam))),
argumentCountIs(3))
.bind("Call"),
this);
// std::inner_product.
Finder->addMatcher(
callExpr(callee(functionDecl(hasName("::std::inner_product"),
hasParameter(0, IteratorParam),
hasParameter(2, Iterator2Param),
hasParameter(3, InitParam))),
argumentCountIs(4))
.bind("Call"),
this);
// std::reduce with a policy.
Finder->addMatcher(
callExpr(callee(functionDecl(hasName("::std::reduce"),
hasParameter(1, IteratorParam),
hasParameter(3, InitParam))),
argumentCountIs(4))
.bind("Call"),
this);
// std::inner_product with a policy.
Finder->addMatcher(
callExpr(callee(functionDecl(hasName("::std::inner_product"),
hasParameter(1, IteratorParam),
hasParameter(3, Iterator2Param),
hasParameter(4, InitParam))),
argumentCountIs(5))
.bind("Call"),
this);
}
/// Returns true if ValueType is allowed to fold into InitType, i.e. if:
/// static_cast<InitType>(ValueType{some_value})
/// does not result in trucation.
static bool isValidBuiltinFold(const BuiltinType &ValueType,
const BuiltinType &InitType,
const ASTContext &Context) {
const auto ValueTypeSize = Context.getTypeSize(&ValueType);
const auto InitTypeSize = Context.getTypeSize(&InitType);
// It's OK to fold a float into a float of bigger or equal size, but not OK to
// fold into an int.
if (ValueType.isFloatingPoint())
return InitType.isFloatingPoint() && InitTypeSize >= ValueTypeSize;
// It's OK to fold an int into:
// - an int of the same size and signedness.
// - a bigger int, regardless of signedness.
// - FIXME: should it be a warning to fold into floating point?
if (ValueType.isInteger()) {
if (InitType.isInteger()) {
if (InitType.isSignedInteger() == ValueType.isSignedInteger())
return InitTypeSize >= ValueTypeSize;
return InitTypeSize > ValueTypeSize;
}
if (InitType.isFloatingPoint())
return InitTypeSize >= ValueTypeSize;
}
return false;
}
/// Prints a diagnostic if IterValueType doe snot fold into IterValueType (see
// isValidBuiltinFold for details).
void FoldInitTypeCheck::doCheck(const BuiltinType &IterValueType,
const BuiltinType &InitType,
const ASTContext &Context,
const CallExpr &CallNode) {
if (!isValidBuiltinFold(IterValueType, InitType, Context)) {
diag(CallNode.getExprLoc(), "folding type %0 into type %1 might result in "
"loss of precision")
<< IterValueType.desugar() << InitType.desugar();
}
}
void FoldInitTypeCheck::check(const MatchFinder::MatchResult &Result) {
// Given the iterator and init value type retreived by the matchers,
// we check that the ::value_type of the iterator is compatible with
// the init value type.
const auto *InitType = Result.Nodes.getNodeAs<BuiltinType>("InitType");
const auto *IterValueType =
Result.Nodes.getNodeAs<BuiltinType>("IterValueType");
assert(InitType != nullptr);
assert(IterValueType != nullptr);
const auto *CallNode = Result.Nodes.getNodeAs<CallExpr>("Call");
assert(CallNode != nullptr);
doCheck(*IterValueType, *InitType, *Result.Context, *CallNode);
if (const auto *Iter2ValueType =
Result.Nodes.getNodeAs<BuiltinType>("Iter2ValueType"))
doCheck(*Iter2ValueType, *InitType, *Result.Context, *CallNode);
}
} // namespace misc
} // namespace tidy
} // namespace clang

View File

@ -0,0 +1,44 @@
//===--- FoldInitTypeCheck.h - clang-tidy------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FOLD_INIT_TYPE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FOLD_INIT_TYPE_H
#include "../ClangTidy.h"
namespace clang {
namespace tidy {
namespace misc {
/// Find and flag invalid initializer values in folds, e.g. std::accumulate.
/// Example:
/// \code
/// auto v = {65536L * 65536 * 65536};
/// std::accumulate(begin(v), end(v), 0 /* int type is too small */);
/// \endcode
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/misc-fold-init-type.html
class FoldInitTypeCheck : public ClangTidyCheck {
public:
FoldInitTypeCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
void doCheck(const BuiltinType &IterValueType, const BuiltinType &InitType,
const ASTContext &Context, const CallExpr &CallNode);
};
} // namespace misc
} // namespace tidy
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FOLD_INIT_TYPE_H

View File

@ -16,6 +16,7 @@
#include "BoolPointerImplicitConversionCheck.h"
#include "DanglingHandleCheck.h"
#include "DefinitionsInHeadersCheck.h"
#include "FoldInitTypeCheck.h"
#include "ForwardDeclarationNamespaceCheck.h"
#include "InaccurateEraseCheck.h"
#include "IncorrectRoundings.h"
@ -67,6 +68,8 @@ public:
"misc-dangling-handle");
CheckFactories.registerCheck<DefinitionsInHeadersCheck>(
"misc-definitions-in-headers");
CheckFactories.registerCheck<FoldInitTypeCheck>(
"misc-fold-init-type");
CheckFactories.registerCheck<ForwardDeclarationNamespaceCheck>(
"misc-forward-declaration-namespace");
CheckFactories.registerCheck<InaccurateEraseCheck>(

View File

@ -53,6 +53,7 @@ Clang-Tidy Checks
misc-bool-pointer-implicit-conversion
misc-dangling-handle
misc-definitions-in-headers
misc-fold-init-type
misc-forward-declaration-namespace
misc-inaccurate-erase
misc-incorrect-roundings

View File

@ -0,0 +1,27 @@
.. title:: clang-tidy - misc-fold-init-type
misc-fold-init-type
===================
The check flags type mismatches in
`folds <https://en.wikipedia.org/wiki/Fold_(higher-order_function)>`_
like `std::accumulate` that might result in loss of precision.
`std::accumulate` folds an input range into an initial value using the type of
the latter, with `operator+` by default. This can cause loss of precision
through:
- Truncation: The following code uses a floating point range and an int
initial value, so trucation wil happen at every application of `operator+`
and the result will be `0`, which might not be what the user expected.
.. code:: c++
auto a = {0.5f, 0.5f, 0.5f, 0.5f};
return std::accumulate(std::begin(a), std::end(a), 0);
- Overflow: The following code also returns `0`.
.. code:: c++
auto a = {65536LL * 65536 * 65536};
return std::accumulate(std::begin(a), std::end(a), 0);

View File

@ -0,0 +1,158 @@
// RUN: %check_clang_tidy %s misc-fold-init-type %t
namespace std {
template <class InputIt, class T>
T accumulate(InputIt first, InputIt last, T init);
template <class InputIt, class T>
T reduce(InputIt first, InputIt last, T init);
template <class ExecutionPolicy, class InputIt, class T>
T reduce(ExecutionPolicy &&policy,
InputIt first, InputIt last, T init);
struct parallel_execution_policy {};
constexpr parallel_execution_policy par{};
template <class InputIt1, class InputIt2, class T>
T inner_product(InputIt1 first1, InputIt1 last1,
InputIt2 first2, T value);
template <class ExecutionPolicy, class InputIt1, class InputIt2, class T>
T inner_product(ExecutionPolicy &&policy, InputIt1 first1, InputIt1 last1,
InputIt2 first2, T value);
} // namespace std
struct FloatIterator {
typedef float value_type;
};
template <typename ValueType>
struct TypedefTemplateIterator { typedef ValueType value_type; };
template <typename ValueType>
struct UsingTemplateIterator { using value_type = ValueType; };
template <typename ValueType>
struct DependentTypedefTemplateIterator { typedef typename ValueType::value_type value_type; };
template <typename ValueType>
struct DependentUsingTemplateIterator : public TypedefTemplateIterator<ValueType> { using typename TypedefTemplateIterator<ValueType>::value_type; };
using TypedeffedIterator = FloatIterator;
// Positives.
int accumulatePositive1() {
float a[1] = {0.5f};
return std::accumulate(a, a + 1, 0);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'float' into type 'int'
}
int accumulatePositive2() {
FloatIterator it;
return std::accumulate(it, it, 0);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'float' into type 'int'
}
int accumulatePositive3() {
double a[1] = {0.0};
return std::accumulate(a, a + 1, 0.0f);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'double' into type 'float'
}
int accumulatePositive4() {
TypedefTemplateIterator<unsigned> it;
return std::accumulate(it, it, 0);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'unsigned int' into type 'int'
}
int accumulatePositive5() {
UsingTemplateIterator<unsigned> it;
return std::accumulate(it, it, 0);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'unsigned int' into type 'int'
}
int accumulatePositive6() {
DependentTypedefTemplateIterator<UsingTemplateIterator<unsigned>> it;
return std::accumulate(it, it, 0);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'unsigned int' into type 'int'
}
int accumulatePositive7() {
TypedeffedIterator it;
return std::accumulate(it, it, 0);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'float' into type 'int'
}
int accumulatePositive8() {
DependentUsingTemplateIterator<unsigned> it;
return std::accumulate(it, it, 0);
// FIXME: this one should trigger too.
}
int reducePositive1() {
float a[1] = {0.5f};
return std::reduce(a, a + 1, 0);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'float' into type 'int'
}
int reducePositive2() {
float a[1] = {0.5f};
return std::reduce(std::par, a, a + 1, 0);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'float' into type 'int'
}
int innerProductPositive1() {
float a[1] = {0.5f};
int b[1] = {1};
return std::inner_product(std::par, a, a + 1, b, 0);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'float' into type 'int'
}
int innerProductPositive2() {
float a[1] = {0.5f};
int b[1] = {1};
return std::inner_product(std::par, a, a + 1, b, 0);
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: folding type 'float' into type 'int'
}
// Negatives.
int negative1() {
float a[1] = {0.5f};
// This is OK because types match.
return std::accumulate(a, a + 1, 0.0);
}
int negative2() {
float a[1] = {0.5f};
// This is OK because double is bigger than float.
return std::accumulate(a, a + 1, 0.0);
}
int negative3() {
float a[1] = {0.5f};
// This is OK because the user explicitly specified T.
return std::accumulate<float *, float>(a, a + 1, 0);
}
int negative4() {
TypedefTemplateIterator<unsigned> it;
// For now this is OK.
return std::accumulate(it, it, 0.0);
}
int negative5() {
float a[1] = {0.5f};
float b[1] = {1.0f};
return std::inner_product(std::par, a, a + 1, b, 0.0f);
}
namespace blah {
namespace std {
template <class InputIt, class T>
T accumulate(InputIt, InputIt, T); // We should not care about this one.
}
int negative5() {
float a[1] = {0.5f};
// Note that this is using blah::std::accumulate.
return std::accumulate(a, a + 1, 0);
}
}