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