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 {
16 namespace tidy {
17 namespace bugprone {
18
registerMatchers(MatchFinder * Finder)19 void FoldInitTypeCheck::registerMatchers(MatchFinder *Finder) {
20 // We match functions of interest and bind the iterator and init value types.
21 // Note: Right now we check only builtin types.
22 const auto BuiltinTypeWithId = [](const char *ID) {
23 return hasCanonicalType(builtinType().bind(ID));
24 };
25 const auto IteratorWithValueType = [&BuiltinTypeWithId](const char *ID) {
26 return anyOf(
27 // Pointer types.
28 pointsTo(BuiltinTypeWithId(ID)),
29 // Iterator types.
30 recordType(hasDeclaration(has(typedefNameDecl(
31 hasName("value_type"), hasType(BuiltinTypeWithId(ID)))))));
32 };
33
34 const auto IteratorParam = parmVarDecl(
35 hasType(hasCanonicalType(IteratorWithValueType("IterValueType"))));
36 const auto Iterator2Param = parmVarDecl(
37 hasType(hasCanonicalType(IteratorWithValueType("Iter2ValueType"))));
38 const auto InitParam = parmVarDecl(hasType(BuiltinTypeWithId("InitType")));
39
40 // std::accumulate, std::reduce.
41 Finder->addMatcher(
42 callExpr(callee(functionDecl(
43 hasAnyName("::std::accumulate", "::std::reduce"),
44 hasParameter(0, IteratorParam), hasParameter(2, InitParam))),
45 argumentCountIs(3))
46 .bind("Call"),
47 this);
48 // std::inner_product.
49 Finder->addMatcher(
50 callExpr(callee(functionDecl(hasName("::std::inner_product"),
51 hasParameter(0, IteratorParam),
52 hasParameter(2, Iterator2Param),
53 hasParameter(3, InitParam))),
54 argumentCountIs(4))
55 .bind("Call"),
56 this);
57 // std::reduce with a policy.
58 Finder->addMatcher(
59 callExpr(callee(functionDecl(hasName("::std::reduce"),
60 hasParameter(1, IteratorParam),
61 hasParameter(3, InitParam))),
62 argumentCountIs(4))
63 .bind("Call"),
64 this);
65 // std::inner_product with a policy.
66 Finder->addMatcher(
67 callExpr(callee(functionDecl(hasName("::std::inner_product"),
68 hasParameter(1, IteratorParam),
69 hasParameter(3, Iterator2Param),
70 hasParameter(4, InitParam))),
71 argumentCountIs(5))
72 .bind("Call"),
73 this);
74 }
75
76 /// Returns true if ValueType is allowed to fold into InitType, i.e. if:
77 /// static_cast<InitType>(ValueType{some_value})
78 /// does not result in trucation.
isValidBuiltinFold(const BuiltinType & ValueType,const BuiltinType & InitType,const ASTContext & Context)79 static bool isValidBuiltinFold(const BuiltinType &ValueType,
80 const BuiltinType &InitType,
81 const ASTContext &Context) {
82 const auto ValueTypeSize = Context.getTypeSize(&ValueType);
83 const auto InitTypeSize = Context.getTypeSize(&InitType);
84 // It's OK to fold a float into a float of bigger or equal size, but not OK to
85 // fold into an int.
86 if (ValueType.isFloatingPoint())
87 return InitType.isFloatingPoint() && InitTypeSize >= ValueTypeSize;
88 // It's OK to fold an int into:
89 // - an int of the same size and signedness.
90 // - a bigger int, regardless of signedness.
91 // - FIXME: should it be a warning to fold into floating point?
92 if (ValueType.isInteger()) {
93 if (InitType.isInteger()) {
94 if (InitType.isSignedInteger() == ValueType.isSignedInteger())
95 return InitTypeSize >= ValueTypeSize;
96 return InitTypeSize > ValueTypeSize;
97 }
98 if (InitType.isFloatingPoint())
99 return InitTypeSize >= ValueTypeSize;
100 }
101 return false;
102 }
103
104 /// Prints a diagnostic if IterValueType doe snot fold into IterValueType (see
105 // isValidBuiltinFold for details).
doCheck(const BuiltinType & IterValueType,const BuiltinType & InitType,const ASTContext & Context,const CallExpr & CallNode)106 void FoldInitTypeCheck::doCheck(const BuiltinType &IterValueType,
107 const BuiltinType &InitType,
108 const ASTContext &Context,
109 const CallExpr &CallNode) {
110 if (!isValidBuiltinFold(IterValueType, InitType, Context)) {
111 diag(CallNode.getExprLoc(), "folding type %0 into type %1 might result in "
112 "loss of precision")
113 << IterValueType.desugar() << InitType.desugar();
114 }
115 }
116
check(const MatchFinder::MatchResult & Result)117 void FoldInitTypeCheck::check(const MatchFinder::MatchResult &Result) {
118 // Given the iterator and init value type retrieved by the matchers,
119 // we check that the ::value_type of the iterator is compatible with
120 // the init value type.
121 const auto *InitType = Result.Nodes.getNodeAs<BuiltinType>("InitType");
122 const auto *IterValueType =
123 Result.Nodes.getNodeAs<BuiltinType>("IterValueType");
124 assert(InitType != nullptr);
125 assert(IterValueType != nullptr);
126
127 const auto *CallNode = Result.Nodes.getNodeAs<CallExpr>("Call");
128 assert(CallNode != nullptr);
129
130 doCheck(*IterValueType, *InitType, *Result.Context, *CallNode);
131
132 if (const auto *Iter2ValueType =
133 Result.Nodes.getNodeAs<BuiltinType>("Iter2ValueType"))
134 doCheck(*Iter2ValueType, *InitType, *Result.Context, *CallNode);
135 }
136
137 } // namespace bugprone
138 } // namespace tidy
139 } // namespace clang
140