1 //===--- FoldInitTypeCheck.cpp - clang-tidy--------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "FoldInitTypeCheck.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 13 using namespace clang::ast_matchers; 14 15 namespace clang { 16 namespace tidy { 17 namespace bugprone { 18 19 void FoldInitTypeCheck::registerMatchers(MatchFinder *Finder) { 20 // We match functions of interest and bind the iterator and init value types. 21 // Note: Right now we check only builtin types. 22 const auto BuiltinTypeWithId = [](const char *ID) { 23 return hasCanonicalType(builtinType().bind(ID)); 24 }; 25 const auto IteratorWithValueType = [&BuiltinTypeWithId](const char *ID) { 26 return anyOf( 27 // Pointer types. 28 pointsTo(BuiltinTypeWithId(ID)), 29 // Iterator types. 30 recordType(hasDeclaration(has(typedefNameDecl( 31 hasName("value_type"), hasType(BuiltinTypeWithId(ID))))))); 32 }; 33 34 const auto IteratorParam = parmVarDecl( 35 hasType(hasCanonicalType(IteratorWithValueType("IterValueType")))); 36 const auto Iterator2Param = parmVarDecl( 37 hasType(hasCanonicalType(IteratorWithValueType("Iter2ValueType")))); 38 const auto InitParam = parmVarDecl(hasType(BuiltinTypeWithId("InitType"))); 39 40 // std::accumulate, std::reduce. 41 Finder->addMatcher( 42 callExpr(callee(functionDecl( 43 hasAnyName("::std::accumulate", "::std::reduce"), 44 hasParameter(0, IteratorParam), hasParameter(2, InitParam))), 45 argumentCountIs(3)) 46 .bind("Call"), 47 this); 48 // std::inner_product. 49 Finder->addMatcher( 50 callExpr(callee(functionDecl(hasName("::std::inner_product"), 51 hasParameter(0, IteratorParam), 52 hasParameter(2, Iterator2Param), 53 hasParameter(3, InitParam))), 54 argumentCountIs(4)) 55 .bind("Call"), 56 this); 57 // std::reduce with a policy. 58 Finder->addMatcher( 59 callExpr(callee(functionDecl(hasName("::std::reduce"), 60 hasParameter(1, IteratorParam), 61 hasParameter(3, InitParam))), 62 argumentCountIs(4)) 63 .bind("Call"), 64 this); 65 // std::inner_product with a policy. 66 Finder->addMatcher( 67 callExpr(callee(functionDecl(hasName("::std::inner_product"), 68 hasParameter(1, IteratorParam), 69 hasParameter(3, Iterator2Param), 70 hasParameter(4, InitParam))), 71 argumentCountIs(5)) 72 .bind("Call"), 73 this); 74 } 75 76 /// Returns true if ValueType is allowed to fold into InitType, i.e. if: 77 /// static_cast<InitType>(ValueType{some_value}) 78 /// does not result in trucation. 79 static bool isValidBuiltinFold(const BuiltinType &ValueType, 80 const BuiltinType &InitType, 81 const ASTContext &Context) { 82 const auto ValueTypeSize = Context.getTypeSize(&ValueType); 83 const auto InitTypeSize = Context.getTypeSize(&InitType); 84 // It's OK to fold a float into a float of bigger or equal size, but not OK to 85 // fold into an int. 86 if (ValueType.isFloatingPoint()) 87 return InitType.isFloatingPoint() && InitTypeSize >= ValueTypeSize; 88 // It's OK to fold an int into: 89 // - an int of the same size and signedness. 90 // - a bigger int, regardless of signedness. 91 // - FIXME: should it be a warning to fold into floating point? 92 if (ValueType.isInteger()) { 93 if (InitType.isInteger()) { 94 if (InitType.isSignedInteger() == ValueType.isSignedInteger()) 95 return InitTypeSize >= ValueTypeSize; 96 return InitTypeSize > ValueTypeSize; 97 } 98 if (InitType.isFloatingPoint()) 99 return InitTypeSize >= ValueTypeSize; 100 } 101 return false; 102 } 103 104 /// Prints a diagnostic if IterValueType doe snot fold into IterValueType (see 105 // isValidBuiltinFold for details). 106 void FoldInitTypeCheck::doCheck(const BuiltinType &IterValueType, 107 const BuiltinType &InitType, 108 const ASTContext &Context, 109 const CallExpr &CallNode) { 110 if (!isValidBuiltinFold(IterValueType, InitType, Context)) { 111 diag(CallNode.getExprLoc(), "folding type %0 into type %1 might result in " 112 "loss of precision") 113 << IterValueType.desugar() << InitType.desugar(); 114 } 115 } 116 117 void FoldInitTypeCheck::check(const MatchFinder::MatchResult &Result) { 118 // Given the iterator and init value type retreived by the matchers, 119 // we check that the ::value_type of the iterator is compatible with 120 // the init value type. 121 const auto *InitType = Result.Nodes.getNodeAs<BuiltinType>("InitType"); 122 const auto *IterValueType = 123 Result.Nodes.getNodeAs<BuiltinType>("IterValueType"); 124 assert(InitType != nullptr); 125 assert(IterValueType != nullptr); 126 127 const auto *CallNode = Result.Nodes.getNodeAs<CallExpr>("Call"); 128 assert(CallNode != nullptr); 129 130 doCheck(*IterValueType, *InitType, *Result.Context, *CallNode); 131 132 if (const auto *Iter2ValueType = 133 Result.Nodes.getNodeAs<BuiltinType>("Iter2ValueType")) 134 doCheck(*Iter2ValueType, *InitType, *Result.Context, *CallNode); 135 } 136 137 } // namespace bugprone 138 } // namespace tidy 139 } // namespace clang 140