xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/FoldInitTypeCheck.cpp (revision 1012677284b87091f76542c3c79da641101578ae)
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::tidy::bugprone {
16 
registerMatchers(MatchFinder * Finder)17 void 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 we
28         // 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 defined
32         //     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.
isValidBuiltinFold(const BuiltinType & ValueType,const BuiltinType & InitType,const ASTContext & Context)92 static 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 to
98   // 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 (see
118 // isValidBuiltinFold for details).
doCheck(const BuiltinType & IterValueType,const BuiltinType & InitType,const ASTContext & Context,const CallExpr & CallNode)119 void 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 
check(const MatchFinder::MatchResult & Result)130 void 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 with
133   // 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::bugprone
151