1 //===--- FoldInitTypeCheck.cpp - clang-tidy--------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "FoldInitTypeCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 using namespace clang::ast_matchers
;
15 namespace clang::tidy::bugprone
{
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
));
23 const auto IteratorWithValueType
= [&BuiltinTypeWithId
](const char *ID
) {
26 pointsTo(BuiltinTypeWithId(ID
)),
27 // Iterator types have an `operator*` whose return type is the type we
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.
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
)))
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")));
53 // std::accumulate, std::reduce.
55 callExpr(callee(functionDecl(
56 hasAnyName("::std::accumulate", "::std::reduce"),
57 hasParameter(0, IteratorParam
), hasParameter(2, InitParam
))),
61 // std::inner_product.
63 callExpr(callee(functionDecl(hasName("::std::inner_product"),
64 hasParameter(0, IteratorParam
),
65 hasParameter(2, Iterator2Param
),
66 hasParameter(3, InitParam
))),
70 // std::reduce with a policy.
72 callExpr(callee(functionDecl(hasName("::std::reduce"),
73 hasParameter(1, IteratorParam
),
74 hasParameter(3, InitParam
))),
78 // std::inner_product with a policy.
80 callExpr(callee(functionDecl(hasName("::std::inner_product"),
81 hasParameter(1, IteratorParam
),
82 hasParameter(3, Iterator2Param
),
83 hasParameter(4, InitParam
))),
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
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
;
111 if (InitType
.isFloatingPoint())
112 return InitTypeSize
>= ValueTypeSize
;
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(IterValueType
, InitType
, Context
)) {
124 diag(CallNode
.getExprLoc(), "folding type %0 into type %1 might result in "
126 << IterValueType
.desugar() << InitType
.desugar();
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);
140 const auto *CallNode
= Result
.Nodes
.getNodeAs
<CallExpr
>("Call");
141 assert(CallNode
!= nullptr);
143 doCheck(*IterValueType
, *InitType
, *Result
.Context
, *CallNode
);
145 if (const auto *Iter2ValueType
=
146 Result
.Nodes
.getNodeAs
<BuiltinType
>("Iter2ValueType"))
147 doCheck(*Iter2ValueType
, *InitType
, *Result
.Context
, *CallNode
);
150 } // namespace clang::tidy::bugprone