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