• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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