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