| //===--- 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<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 bugprone |
| } // namespace tidy |
| } // namespace clang |