clang-tools 23.0.0git
FoldInitTypeCheck.cpp
Go to the documentation of this file.
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-exception
6//
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 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 // Transparent standard functors that preserve arithmetic conversion
54 // semantics.
55 const auto TransparentFunctor = expr(hasType(
56 hasCanonicalType(recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
57 "::std::plus", "::std::minus", "::std::multiplies", "::std::divides",
58 "::std::bit_and", "::std::bit_or", "::std::bit_xor")))))));
59
60 // std::accumulate, std::reduce.
61 Finder->addMatcher(
62 callExpr(
63 callee(functionDecl(hasAnyName("::std::accumulate", "::std::reduce"),
64 hasParameter(0, IteratorParam),
65 hasParameter(2, InitParam))),
66 anyOf(argumentCountIs(3),
67 allOf(argumentCountIs(4), hasArgument(3, TransparentFunctor))))
68 .bind("Call"),
69 this);
70 // std::inner_product.
71 Finder->addMatcher(
72 callExpr(
73 callee(functionDecl(
74 hasName("::std::inner_product"), hasParameter(0, IteratorParam),
75 hasParameter(2, Iterator2Param), hasParameter(3, InitParam))),
76 anyOf(argumentCountIs(4),
77 allOf(argumentCountIs(6), hasArgument(4, TransparentFunctor),
78 hasArgument(5, TransparentFunctor))))
79 .bind("Call"),
80 this);
81 // std::reduce with a policy.
82 Finder->addMatcher(
83 callExpr(
84 callee(functionDecl(hasName("::std::reduce"),
85 hasParameter(1, IteratorParam),
86 hasParameter(3, InitParam))),
87 anyOf(argumentCountIs(4),
88 allOf(argumentCountIs(5), hasArgument(4, TransparentFunctor))))
89 .bind("Call"),
90 this);
91 // std::inner_product with a policy.
92 Finder->addMatcher(
93 callExpr(
94 callee(functionDecl(
95 hasName("::std::inner_product"), hasParameter(1, IteratorParam),
96 hasParameter(3, Iterator2Param), hasParameter(4, InitParam))),
97 anyOf(argumentCountIs(5),
98 allOf(argumentCountIs(7), hasArgument(5, TransparentFunctor),
99 hasArgument(6, TransparentFunctor))))
100 .bind("Call"),
101 this);
102}
103
104/// Returns true if ValueType is allowed to fold into InitType, i.e. if:
105/// static_cast<InitType>(ValueType{some_value})
106/// does not result in trucation.
107static bool isValidBuiltinFold(const BuiltinType &ValueType,
108 const BuiltinType &InitType,
109 const ASTContext &Context) {
110 const auto ValueTypeSize = Context.getTypeSize(&ValueType);
111 const auto InitTypeSize = Context.getTypeSize(&InitType);
112 // It's OK to fold a float into a float of bigger or equal size, but not OK to
113 // fold into an int.
114 if (ValueType.isFloatingPoint())
115 return InitType.isFloatingPoint() && InitTypeSize >= ValueTypeSize;
116 // It's OK to fold an int into:
117 // - an int of the same size and signedness.
118 // - a bigger int, regardless of signedness.
119 // - FIXME: should it be a warning to fold into floating point?
120 if (ValueType.isInteger()) {
121 if (InitType.isInteger()) {
122 if (InitType.isSignedInteger() == ValueType.isSignedInteger())
123 return InitTypeSize >= ValueTypeSize;
124 return InitTypeSize > ValueTypeSize;
125 }
126 if (InitType.isFloatingPoint())
127 return InitTypeSize >= ValueTypeSize;
128 }
129 return false;
130}
131
132/// Prints a diagnostic if IterValueType doe snot fold into IterValueType (see
133// isValidBuiltinFold for details).
134void FoldInitTypeCheck::doCheck(const BuiltinType &IterValueType,
135 const BuiltinType &InitType,
136 const ASTContext &Context,
137 const CallExpr &CallNode) {
138 if (!isValidBuiltinFold(IterValueType, InitType, Context)) {
139 diag(CallNode.getExprLoc(), "folding type %0 into type %1 might result in "
140 "loss of precision")
141 << IterValueType.desugar() << InitType.desugar();
142 }
143}
144
145void FoldInitTypeCheck::check(const MatchFinder::MatchResult &Result) {
146 // Given the iterator and init value type retrieved by the matchers,
147 // we check that the ::value_type of the iterator is compatible with
148 // the init value type.
149 const auto *InitType = Result.Nodes.getNodeAs<BuiltinType>("InitType");
150 const auto *IterValueType =
151 Result.Nodes.getNodeAs<BuiltinType>("IterValueType");
152 assert(InitType != nullptr);
153 assert(IterValueType != nullptr);
154
155 const auto *CallNode = Result.Nodes.getNodeAs<CallExpr>("Call");
156 assert(CallNode != nullptr);
157
158 doCheck(*IterValueType, *InitType, *Result.Context, *CallNode);
159
160 if (const auto *Iter2ValueType =
161 Result.Nodes.getNodeAs<BuiltinType>("Iter2ValueType"))
162 doCheck(*Iter2ValueType, *InitType, *Result.Context, *CallNode);
163}
164
165} // namespace clang::tidy::bugprone
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static bool isValidBuiltinFold(const BuiltinType &ValueType, const BuiltinType &InitType, const ASTContext &Context)
Returns true if ValueType is allowed to fold into InitType, i.e.