blob: d3bcdb23959a6d0438fc78d627c289a27fc12814 [file] [log] [blame]
//===--- 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 {
namespace tidy {
namespace 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.
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 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 bugprone
} // namespace tidy
} // namespace clang