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 | |
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(InnerMatcher: builtinType().bind(ID)); |
22 | }; |
23 | const auto IteratorWithValueType = [&BuiltinTypeWithId](const char *ID) { |
24 | return anyOf( |
25 | // Pointer types. |
26 | pointsTo(InnerMatcher: 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(InnerMatcher: cxxRecordDecl(isSameOrDerivedFrom(Base: has(functionDecl( |
35 | hasOverloadedOperatorName(Name: "*" ), |
36 | returns(InnerMatcher: qualType(hasCanonicalType(InnerMatcher: anyOf( |
37 | // `value_type& operator*();` |
38 | references(InnerMatcher: 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(InnerMatcher: hasCanonicalType(InnerMatcher: IteratorWithValueType("IterValueType" )))); |
49 | const auto Iterator2Param = parmVarDecl( |
50 | hasType(InnerMatcher: hasCanonicalType(InnerMatcher: IteratorWithValueType("Iter2ValueType" )))); |
51 | const auto InitParam = parmVarDecl(hasType(InnerMatcher: BuiltinTypeWithId("InitType" ))); |
52 | |
53 | // std::accumulate, std::reduce. |
54 | Finder->addMatcher( |
55 | NodeMatch: callExpr(callee(InnerMatcher: functionDecl( |
56 | hasAnyName("::std::accumulate" , "::std::reduce" ), |
57 | hasParameter(N: 0, InnerMatcher: IteratorParam), hasParameter(N: 2, InnerMatcher: InitParam))), |
58 | argumentCountIs(N: 3)) |
59 | .bind(ID: "Call" ), |
60 | Action: this); |
61 | // std::inner_product. |
62 | Finder->addMatcher( |
63 | NodeMatch: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "::std::inner_product" ), |
64 | hasParameter(N: 0, InnerMatcher: IteratorParam), |
65 | hasParameter(N: 2, InnerMatcher: Iterator2Param), |
66 | hasParameter(N: 3, InnerMatcher: InitParam))), |
67 | argumentCountIs(N: 4)) |
68 | .bind(ID: "Call" ), |
69 | Action: this); |
70 | // std::reduce with a policy. |
71 | Finder->addMatcher( |
72 | NodeMatch: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "::std::reduce" ), |
73 | hasParameter(N: 1, InnerMatcher: IteratorParam), |
74 | hasParameter(N: 3, InnerMatcher: InitParam))), |
75 | argumentCountIs(N: 4)) |
76 | .bind(ID: "Call" ), |
77 | Action: this); |
78 | // std::inner_product with a policy. |
79 | Finder->addMatcher( |
80 | NodeMatch: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "::std::inner_product" ), |
81 | hasParameter(N: 1, InnerMatcher: IteratorParam), |
82 | hasParameter(N: 3, InnerMatcher: Iterator2Param), |
83 | hasParameter(N: 4, InnerMatcher: InitParam))), |
84 | argumentCountIs(N: 5)) |
85 | .bind(ID: "Call" ), |
86 | Action: 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. |
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). |
119 | void FoldInitTypeCheck::doCheck(const BuiltinType &IterValueType, |
120 | const BuiltinType &InitType, |
121 | const ASTContext &Context, |
122 | const CallExpr &CallNode) { |
123 | if (!isValidBuiltinFold(ValueType: 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 | |
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>(ID: "InitType" ); |
135 | const auto *IterValueType = |
136 | Result.Nodes.getNodeAs<BuiltinType>(ID: "IterValueType" ); |
137 | assert(InitType != nullptr); |
138 | assert(IterValueType != nullptr); |
139 | |
140 | const auto *CallNode = Result.Nodes.getNodeAs<CallExpr>(ID: "Call" ); |
141 | assert(CallNode != nullptr); |
142 | |
143 | doCheck(IterValueType: *IterValueType, InitType: *InitType, Context: *Result.Context, CallNode: *CallNode); |
144 | |
145 | if (const auto *Iter2ValueType = |
146 | Result.Nodes.getNodeAs<BuiltinType>(ID: "Iter2ValueType" )) |
147 | doCheck(IterValueType: *Iter2ValueType, InitType: *InitType, Context: *Result.Context, CallNode: *CallNode); |
148 | } |
149 | |
150 | } // namespace clang::tidy::bugprone |
151 | |