//===--- 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 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(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("InitType"); const auto *IterValueType = Result.Nodes.getNodeAs("IterValueType"); assert(InitType != nullptr); assert(IterValueType != nullptr); const auto *CallNode = Result.Nodes.getNodeAs("Call"); assert(CallNode != nullptr); doCheck(*IterValueType, *InitType, *Result.Context, *CallNode); if (const auto *Iter2ValueType = Result.Nodes.getNodeAs("Iter2ValueType")) doCheck(*Iter2ValueType, *InitType, *Result.Context, *CallNode); } } // namespace bugprone } // namespace tidy } // namespace clang