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
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(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.
92static 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).
119void 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
130void 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

source code of clang-tools-extra/clang-tidy/bugprone/FoldInitTypeCheck.cpp