1 //===--- TypeMismatchCheck.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 "TypeMismatchCheck.h"
10 #include "clang/Lex/Lexer.h"
11 #include "clang/StaticAnalyzer/Checkers/MPIFunctionClassifier.h"
12 #include "clang/Tooling/FixIt.h"
13 #include <map>
14 #include <unordered_set>
15
16 using namespace clang::ast_matchers;
17
18 namespace clang {
19 namespace tidy {
20 namespace mpi {
21
22 /// Check if a BuiltinType::Kind matches the MPI datatype.
23 ///
24 /// \param MultiMap datatype group
25 /// \param Kind buffer type kind
26 /// \param MPIDatatype name of the MPI datatype
27 ///
28 /// \returns true if the pair matches
29 static bool
isMPITypeMatching(const std::multimap<BuiltinType::Kind,std::string> & MultiMap,const BuiltinType::Kind Kind,const std::string & MPIDatatype)30 isMPITypeMatching(const std::multimap<BuiltinType::Kind, std::string> &MultiMap,
31 const BuiltinType::Kind Kind,
32 const std::string &MPIDatatype) {
33 auto ItPair = MultiMap.equal_range(Kind);
34 while (ItPair.first != ItPair.second) {
35 if (ItPair.first->second == MPIDatatype)
36 return true;
37 ++ItPair.first;
38 }
39 return false;
40 }
41
42 /// Check if the MPI datatype is a standard type.
43 ///
44 /// \param MPIDatatype name of the MPI datatype
45 ///
46 /// \returns true if the type is a standard type
isStandardMPIDatatype(const std::string & MPIDatatype)47 static bool isStandardMPIDatatype(const std::string &MPIDatatype) {
48 static std::unordered_set<std::string> AllTypes = {
49 "MPI_C_BOOL",
50 "MPI_CHAR",
51 "MPI_SIGNED_CHAR",
52 "MPI_UNSIGNED_CHAR",
53 "MPI_WCHAR",
54 "MPI_INT",
55 "MPI_LONG",
56 "MPI_SHORT",
57 "MPI_LONG_LONG",
58 "MPI_LONG_LONG_INT",
59 "MPI_UNSIGNED",
60 "MPI_UNSIGNED_SHORT",
61 "MPI_UNSIGNED_LONG",
62 "MPI_UNSIGNED_LONG_LONG",
63 "MPI_FLOAT",
64 "MPI_DOUBLE",
65 "MPI_LONG_DOUBLE",
66 "MPI_C_COMPLEX",
67 "MPI_C_FLOAT_COMPLEX",
68 "MPI_C_DOUBLE_COMPLEX",
69 "MPI_C_LONG_DOUBLE_COMPLEX",
70 "MPI_INT8_T",
71 "MPI_INT16_T",
72 "MPI_INT32_T",
73 "MPI_INT64_T",
74 "MPI_UINT8_T",
75 "MPI_UINT16_T",
76 "MPI_UINT32_T",
77 "MPI_UINT64_T",
78 "MPI_CXX_BOOL",
79 "MPI_CXX_FLOAT_COMPLEX",
80 "MPI_CXX_DOUBLE_COMPLEX",
81 "MPI_CXX_LONG_DOUBLE_COMPLEX"};
82
83 return AllTypes.find(MPIDatatype) != AllTypes.end();
84 }
85
86 /// Check if a BuiltinType matches the MPI datatype.
87 ///
88 /// \param Builtin the builtin type
89 /// \param BufferTypeName buffer type name, gets assigned
90 /// \param MPIDatatype name of the MPI datatype
91 /// \param LO language options
92 ///
93 /// \returns true if the type matches
isBuiltinTypeMatching(const BuiltinType * Builtin,std::string & BufferTypeName,const std::string & MPIDatatype,const LangOptions & LO)94 static bool isBuiltinTypeMatching(const BuiltinType *Builtin,
95 std::string &BufferTypeName,
96 const std::string &MPIDatatype,
97 const LangOptions &LO) {
98 static std::multimap<BuiltinType::Kind, std::string> BuiltinMatches = {
99 // On some systems like PPC or ARM, 'char' is unsigned by default which is
100 // why distinct signedness for the buffer and MPI type is tolerated.
101 {BuiltinType::SChar, "MPI_CHAR"},
102 {BuiltinType::SChar, "MPI_SIGNED_CHAR"},
103 {BuiltinType::SChar, "MPI_UNSIGNED_CHAR"},
104 {BuiltinType::Char_S, "MPI_CHAR"},
105 {BuiltinType::Char_S, "MPI_SIGNED_CHAR"},
106 {BuiltinType::Char_S, "MPI_UNSIGNED_CHAR"},
107 {BuiltinType::UChar, "MPI_CHAR"},
108 {BuiltinType::UChar, "MPI_SIGNED_CHAR"},
109 {BuiltinType::UChar, "MPI_UNSIGNED_CHAR"},
110 {BuiltinType::Char_U, "MPI_CHAR"},
111 {BuiltinType::Char_U, "MPI_SIGNED_CHAR"},
112 {BuiltinType::Char_U, "MPI_UNSIGNED_CHAR"},
113 {BuiltinType::WChar_S, "MPI_WCHAR"},
114 {BuiltinType::WChar_U, "MPI_WCHAR"},
115 {BuiltinType::Bool, "MPI_C_BOOL"},
116 {BuiltinType::Bool, "MPI_CXX_BOOL"},
117 {BuiltinType::Short, "MPI_SHORT"},
118 {BuiltinType::Int, "MPI_INT"},
119 {BuiltinType::Long, "MPI_LONG"},
120 {BuiltinType::LongLong, "MPI_LONG_LONG"},
121 {BuiltinType::LongLong, "MPI_LONG_LONG_INT"},
122 {BuiltinType::UShort, "MPI_UNSIGNED_SHORT"},
123 {BuiltinType::UInt, "MPI_UNSIGNED"},
124 {BuiltinType::ULong, "MPI_UNSIGNED_LONG"},
125 {BuiltinType::ULongLong, "MPI_UNSIGNED_LONG_LONG"},
126 {BuiltinType::Float, "MPI_FLOAT"},
127 {BuiltinType::Double, "MPI_DOUBLE"},
128 {BuiltinType::LongDouble, "MPI_LONG_DOUBLE"}};
129
130 if (!isMPITypeMatching(BuiltinMatches, Builtin->getKind(), MPIDatatype)) {
131 BufferTypeName = std::string(Builtin->getName(LO));
132 return false;
133 }
134
135 return true;
136 }
137
138 /// Check if a complex float/double/long double buffer type matches
139 /// the MPI datatype.
140 ///
141 /// \param Complex buffer type
142 /// \param BufferTypeName buffer type name, gets assigned
143 /// \param MPIDatatype name of the MPI datatype
144 /// \param LO language options
145 ///
146 /// \returns true if the type matches or the buffer type is unknown
isCComplexTypeMatching(const ComplexType * const Complex,std::string & BufferTypeName,const std::string & MPIDatatype,const LangOptions & LO)147 static bool isCComplexTypeMatching(const ComplexType *const Complex,
148 std::string &BufferTypeName,
149 const std::string &MPIDatatype,
150 const LangOptions &LO) {
151 static std::multimap<BuiltinType::Kind, std::string> ComplexCMatches = {
152 {BuiltinType::Float, "MPI_C_COMPLEX"},
153 {BuiltinType::Float, "MPI_C_FLOAT_COMPLEX"},
154 {BuiltinType::Double, "MPI_C_DOUBLE_COMPLEX"},
155 {BuiltinType::LongDouble, "MPI_C_LONG_DOUBLE_COMPLEX"}};
156
157 const auto *Builtin =
158 Complex->getElementType().getTypePtr()->getAs<BuiltinType>();
159
160 if (Builtin &&
161 !isMPITypeMatching(ComplexCMatches, Builtin->getKind(), MPIDatatype)) {
162 BufferTypeName = (llvm::Twine(Builtin->getName(LO)) + " _Complex").str();
163 return false;
164 }
165 return true;
166 }
167
168 /// Check if a complex<float/double/long double> templated buffer type matches
169 /// the MPI datatype.
170 ///
171 /// \param Template buffer type
172 /// \param BufferTypeName buffer type name, gets assigned
173 /// \param MPIDatatype name of the MPI datatype
174 /// \param LO language options
175 ///
176 /// \returns true if the type matches or the buffer type is unknown
177 static bool
isCXXComplexTypeMatching(const TemplateSpecializationType * const Template,std::string & BufferTypeName,const std::string & MPIDatatype,const LangOptions & LO)178 isCXXComplexTypeMatching(const TemplateSpecializationType *const Template,
179 std::string &BufferTypeName,
180 const std::string &MPIDatatype,
181 const LangOptions &LO) {
182 static std::multimap<BuiltinType::Kind, std::string> ComplexCXXMatches = {
183 {BuiltinType::Float, "MPI_CXX_FLOAT_COMPLEX"},
184 {BuiltinType::Double, "MPI_CXX_DOUBLE_COMPLEX"},
185 {BuiltinType::LongDouble, "MPI_CXX_LONG_DOUBLE_COMPLEX"}};
186
187 if (Template->getAsCXXRecordDecl()->getName() != "complex")
188 return true;
189
190 const auto *Builtin =
191 Template->getArg(0).getAsType().getTypePtr()->getAs<BuiltinType>();
192
193 if (Builtin &&
194 !isMPITypeMatching(ComplexCXXMatches, Builtin->getKind(), MPIDatatype)) {
195 BufferTypeName =
196 (llvm::Twine("complex<") + Builtin->getName(LO) + ">").str();
197 return false;
198 }
199
200 return true;
201 }
202
203 /// Check if a fixed size width buffer type matches the MPI datatype.
204 ///
205 /// \param Typedef buffer type
206 /// \param BufferTypeName buffer type name, gets assigned
207 /// \param MPIDatatype name of the MPI datatype
208 ///
209 /// \returns true if the type matches or the buffer type is unknown
isTypedefTypeMatching(const TypedefType * const Typedef,std::string & BufferTypeName,const std::string & MPIDatatype)210 static bool isTypedefTypeMatching(const TypedefType *const Typedef,
211 std::string &BufferTypeName,
212 const std::string &MPIDatatype) {
213 static llvm::StringMap<std::string> FixedWidthMatches = {
214 {"int8_t", "MPI_INT8_T"}, {"int16_t", "MPI_INT16_T"},
215 {"int32_t", "MPI_INT32_T"}, {"int64_t", "MPI_INT64_T"},
216 {"uint8_t", "MPI_UINT8_T"}, {"uint16_t", "MPI_UINT16_T"},
217 {"uint32_t", "MPI_UINT32_T"}, {"uint64_t", "MPI_UINT64_T"}};
218
219 const auto it = FixedWidthMatches.find(Typedef->getDecl()->getName());
220 // Check if the typedef is known and not matching the MPI datatype.
221 if (it != FixedWidthMatches.end() && it->getValue() != MPIDatatype) {
222 BufferTypeName = std::string(Typedef->getDecl()->getName());
223 return false;
224 }
225 return true;
226 }
227
228 /// Get the unqualified, dereferenced type of an argument.
229 ///
230 /// \param CE call expression
231 /// \param idx argument index
232 ///
233 /// \returns type of the argument
argumentType(const CallExpr * const CE,const size_t idx)234 static const Type *argumentType(const CallExpr *const CE, const size_t idx) {
235 const QualType QT = CE->getArg(idx)->IgnoreImpCasts()->getType();
236 return QT.getTypePtr()->getPointeeOrArrayElementType();
237 }
238
registerMatchers(MatchFinder * Finder)239 void TypeMismatchCheck::registerMatchers(MatchFinder *Finder) {
240 Finder->addMatcher(callExpr().bind("CE"), this);
241 }
242
check(const MatchFinder::MatchResult & Result)243 void TypeMismatchCheck::check(const MatchFinder::MatchResult &Result) {
244 static ento::mpi::MPIFunctionClassifier FuncClassifier(*Result.Context);
245 const auto *const CE = Result.Nodes.getNodeAs<CallExpr>("CE");
246 if (!CE->getDirectCallee())
247 return;
248
249 const IdentifierInfo *Identifier = CE->getDirectCallee()->getIdentifier();
250 if (!Identifier || !FuncClassifier.isMPIType(Identifier))
251 return;
252
253 // These containers are used, to capture buffer, MPI datatype pairs.
254 SmallVector<const Type *, 1> BufferTypes;
255 SmallVector<const Expr *, 1> BufferExprs;
256 SmallVector<StringRef, 1> MPIDatatypes;
257
258 // Adds a buffer, MPI datatype pair of an MPI call expression to the
259 // containers. For buffers, the type and expression is captured.
260 auto addPair = [&CE, &Result, &BufferTypes, &BufferExprs, &MPIDatatypes](
261 const size_t BufferIdx, const size_t DatatypeIdx) {
262 // Skip null pointer constants and in place 'operators'.
263 if (CE->getArg(BufferIdx)->isNullPointerConstant(
264 *Result.Context, Expr::NPC_ValueDependentIsNull) ||
265 tooling::fixit::getText(*CE->getArg(BufferIdx), *Result.Context) ==
266 "MPI_IN_PLACE")
267 return;
268
269 StringRef MPIDatatype =
270 tooling::fixit::getText(*CE->getArg(DatatypeIdx), *Result.Context);
271
272 const Type *ArgType = argumentType(CE, BufferIdx);
273 // Skip unknown MPI datatypes and void pointers.
274 if (!isStandardMPIDatatype(std::string(MPIDatatype)) ||
275 ArgType->isVoidType())
276 return;
277
278 BufferTypes.push_back(ArgType);
279 BufferExprs.push_back(CE->getArg(BufferIdx));
280 MPIDatatypes.push_back(MPIDatatype);
281 };
282
283 // Collect all buffer, MPI datatype pairs for the inspected call expression.
284 if (FuncClassifier.isPointToPointType(Identifier)) {
285 addPair(0, 2);
286 } else if (FuncClassifier.isCollectiveType(Identifier)) {
287 if (FuncClassifier.isReduceType(Identifier)) {
288 addPair(0, 3);
289 addPair(1, 3);
290 } else if (FuncClassifier.isScatterType(Identifier) ||
291 FuncClassifier.isGatherType(Identifier) ||
292 FuncClassifier.isAlltoallType(Identifier)) {
293 addPair(0, 2);
294 addPair(3, 5);
295 } else if (FuncClassifier.isBcastType(Identifier)) {
296 addPair(0, 2);
297 }
298 }
299 checkArguments(BufferTypes, BufferExprs, MPIDatatypes, getLangOpts());
300 }
301
checkArguments(ArrayRef<const Type * > BufferTypes,ArrayRef<const Expr * > BufferExprs,ArrayRef<StringRef> MPIDatatypes,const LangOptions & LO)302 void TypeMismatchCheck::checkArguments(ArrayRef<const Type *> BufferTypes,
303 ArrayRef<const Expr *> BufferExprs,
304 ArrayRef<StringRef> MPIDatatypes,
305 const LangOptions &LO) {
306 std::string BufferTypeName;
307
308 for (size_t i = 0; i < MPIDatatypes.size(); ++i) {
309 const Type *const BT = BufferTypes[i];
310 bool Error = false;
311
312 if (const auto *Typedef = BT->getAs<TypedefType>()) {
313 Error = !isTypedefTypeMatching(Typedef, BufferTypeName,
314 std::string(MPIDatatypes[i]));
315 } else if (const auto *Complex = BT->getAs<ComplexType>()) {
316 Error = !isCComplexTypeMatching(Complex, BufferTypeName,
317 std::string(MPIDatatypes[i]), LO);
318 } else if (const auto *Template = BT->getAs<TemplateSpecializationType>()) {
319 Error = !isCXXComplexTypeMatching(Template, BufferTypeName,
320 std::string(MPIDatatypes[i]), LO);
321 } else if (const auto *Builtin = BT->getAs<BuiltinType>()) {
322 Error = !isBuiltinTypeMatching(Builtin, BufferTypeName,
323 std::string(MPIDatatypes[i]), LO);
324 }
325
326 if (Error) {
327 const auto Loc = BufferExprs[i]->getSourceRange().getBegin();
328 diag(Loc, "buffer type '%0' does not match the MPI datatype '%1'")
329 << BufferTypeName << MPIDatatypes[i];
330 }
331 }
332 }
333
334 } // namespace mpi
335 } // namespace tidy
336 } // namespace clang
337