1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
//===--- FoldInitTypeCheck.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 "FoldInitTypeCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
using namespace clang::ast_matchers;
namespace clang::tidy::bugprone {
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 have an `operator*` whose return type is the type we
// care about.
// Notes:
// - `operator*` can be in one of the bases of the iterator class.
// - this does not handle cases when the `operator*` is defined
// outside the iterator class.
recordType(
hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom(has(functionDecl(
hasOverloadedOperatorName("*"),
returns(qualType(hasCanonicalType(anyOf(
// `value_type& operator*();`
references(BuiltinTypeWithId(ID)),
// `value_type operator*();`
BuiltinTypeWithId(ID),
// `auto operator*();`, `decltype(auto) operator*();`
autoType(hasDeducedType(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 retrieved 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 clang::tidy::bugprone
|