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 
registerMatchers(MatchFinder * Finder)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.
isValidBuiltinFold(const BuiltinType & ValueType,const BuiltinType & InitType,const ASTContext & Context)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).
doCheck(const BuiltinType & IterValueType,const BuiltinType & InitType,const ASTContext & Context,const CallExpr & CallNode)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 
check(const MatchFinder::MatchResult & Result)117 void FoldInitTypeCheck::check(const MatchFinder::MatchResult &Result) {
118   // Given the iterator and init value type retrieved 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