blob: fb96ce77a13df15db91ebf3b8b562e6e08cc10b7 [file] [log] [blame]
//===--- TypeMismatchCheck.cpp - clang-tidy--------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "TypeMismatchCheck.h"
#include "clang/Lex/Lexer.h"
#include "clang/Tooling/FixIt.h"
#include <map>
#include <unordered_set>
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace mpi {
/// Check if a BuiltinType::Kind matches the MPI datatype.
///
/// \param MultiMap datatype group
/// \param Kind buffer type kind
/// \param MPIDatatype name of the MPI datatype
///
/// \returns true if the pair matches
static bool
isMPITypeMatching(const std::multimap<BuiltinType::Kind, std::string> &MultiMap,
const BuiltinType::Kind Kind,
const std::string &MPIDatatype) {
auto ItPair = MultiMap.equal_range(Kind);
while (ItPair.first != ItPair.second) {
if (ItPair.first->second == MPIDatatype)
return true;
++ItPair.first;
}
return false;
}
/// Check if the MPI datatype is a standard type.
///
/// \param MPIDatatype name of the MPI datatype
///
/// \returns true if the type is a standard type
static bool isStandardMPIDatatype(const std::string &MPIDatatype) {
static std::unordered_set<std::string> AllTypes = {
"MPI_C_BOOL",
"MPI_CHAR",
"MPI_SIGNED_CHAR",
"MPI_UNSIGNED_CHAR",
"MPI_WCHAR",
"MPI_INT",
"MPI_LONG",
"MPI_SHORT",
"MPI_LONG_LONG",
"MPI_LONG_LONG_INT",
"MPI_UNSIGNED",
"MPI_UNSIGNED_SHORT",
"MPI_UNSIGNED_LONG",
"MPI_UNSIGNED_LONG_LONG",
"MPI_FLOAT",
"MPI_DOUBLE",
"MPI_LONG_DOUBLE",
"MPI_C_COMPLEX",
"MPI_C_FLOAT_COMPLEX",
"MPI_C_DOUBLE_COMPLEX",
"MPI_C_LONG_DOUBLE_COMPLEX",
"MPI_INT8_T",
"MPI_INT16_T",
"MPI_INT32_T",
"MPI_INT64_T",
"MPI_UINT8_T",
"MPI_UINT16_T",
"MPI_UINT32_T",
"MPI_UINT64_T",
"MPI_CXX_BOOL",
"MPI_CXX_FLOAT_COMPLEX",
"MPI_CXX_DOUBLE_COMPLEX",
"MPI_CXX_LONG_DOUBLE_COMPLEX"};
return AllTypes.find(MPIDatatype) != AllTypes.end();
}
/// Check if a BuiltinType matches the MPI datatype.
///
/// \param Builtin the builtin type
/// \param BufferTypeName buffer type name, gets assigned
/// \param MPIDatatype name of the MPI datatype
/// \param LO language options
///
/// \returns true if the type matches
static bool isBuiltinTypeMatching(const BuiltinType *Builtin,
std::string &BufferTypeName,
const std::string &MPIDatatype,
const LangOptions &LO) {
static std::multimap<BuiltinType::Kind, std::string> BuiltinMatches = {
// On some systems like PPC or ARM, 'char' is unsigned by default which is
// why distinct signedness for the buffer and MPI type is tolerated.
{BuiltinType::SChar, "MPI_CHAR"},
{BuiltinType::SChar, "MPI_SIGNED_CHAR"},
{BuiltinType::SChar, "MPI_UNSIGNED_CHAR"},
{BuiltinType::Char_S, "MPI_CHAR"},
{BuiltinType::Char_S, "MPI_SIGNED_CHAR"},
{BuiltinType::Char_S, "MPI_UNSIGNED_CHAR"},
{BuiltinType::UChar, "MPI_CHAR"},
{BuiltinType::UChar, "MPI_SIGNED_CHAR"},
{BuiltinType::UChar, "MPI_UNSIGNED_CHAR"},
{BuiltinType::Char_U, "MPI_CHAR"},
{BuiltinType::Char_U, "MPI_SIGNED_CHAR"},
{BuiltinType::Char_U, "MPI_UNSIGNED_CHAR"},
{BuiltinType::WChar_S, "MPI_WCHAR"},
{BuiltinType::WChar_U, "MPI_WCHAR"},
{BuiltinType::Bool, "MPI_C_BOOL"},
{BuiltinType::Bool, "MPI_CXX_BOOL"},
{BuiltinType::Short, "MPI_SHORT"},
{BuiltinType::Int, "MPI_INT"},
{BuiltinType::Long, "MPI_LONG"},
{BuiltinType::LongLong, "MPI_LONG_LONG"},
{BuiltinType::LongLong, "MPI_LONG_LONG_INT"},
{BuiltinType::UShort, "MPI_UNSIGNED_SHORT"},
{BuiltinType::UInt, "MPI_UNSIGNED"},
{BuiltinType::ULong, "MPI_UNSIGNED_LONG"},
{BuiltinType::ULongLong, "MPI_UNSIGNED_LONG_LONG"},
{BuiltinType::Float, "MPI_FLOAT"},
{BuiltinType::Double, "MPI_DOUBLE"},
{BuiltinType::LongDouble, "MPI_LONG_DOUBLE"}};
if (!isMPITypeMatching(BuiltinMatches, Builtin->getKind(), MPIDatatype)) {
BufferTypeName = std::string(Builtin->getName(LO));
return false;
}
return true;
}
/// Check if a complex float/double/long double buffer type matches
/// the MPI datatype.
///
/// \param Complex buffer type
/// \param BufferTypeName buffer type name, gets assigned
/// \param MPIDatatype name of the MPI datatype
/// \param LO language options
///
/// \returns true if the type matches or the buffer type is unknown
static bool isCComplexTypeMatching(const ComplexType *const Complex,
std::string &BufferTypeName,
const std::string &MPIDatatype,
const LangOptions &LO) {
static std::multimap<BuiltinType::Kind, std::string> ComplexCMatches = {
{BuiltinType::Float, "MPI_C_COMPLEX"},
{BuiltinType::Float, "MPI_C_FLOAT_COMPLEX"},
{BuiltinType::Double, "MPI_C_DOUBLE_COMPLEX"},
{BuiltinType::LongDouble, "MPI_C_LONG_DOUBLE_COMPLEX"}};
const auto *Builtin =
Complex->getElementType().getTypePtr()->getAs<BuiltinType>();
if (Builtin &&
!isMPITypeMatching(ComplexCMatches, Builtin->getKind(), MPIDatatype)) {
BufferTypeName = (llvm::Twine(Builtin->getName(LO)) + " _Complex").str();
return false;
}
return true;
}
/// Check if a complex<float/double/long double> templated buffer type matches
/// the MPI datatype.
///
/// \param Template buffer type
/// \param BufferTypeName buffer type name, gets assigned
/// \param MPIDatatype name of the MPI datatype
/// \param LO language options
///
/// \returns true if the type matches or the buffer type is unknown
static bool
isCXXComplexTypeMatching(const TemplateSpecializationType *const Template,
std::string &BufferTypeName,
const std::string &MPIDatatype,
const LangOptions &LO) {
static std::multimap<BuiltinType::Kind, std::string> ComplexCXXMatches = {
{BuiltinType::Float, "MPI_CXX_FLOAT_COMPLEX"},
{BuiltinType::Double, "MPI_CXX_DOUBLE_COMPLEX"},
{BuiltinType::LongDouble, "MPI_CXX_LONG_DOUBLE_COMPLEX"}};
if (Template->getAsCXXRecordDecl()->getName() != "complex")
return true;
const auto *Builtin =
Template->getArg(0).getAsType().getTypePtr()->getAs<BuiltinType>();
if (Builtin &&
!isMPITypeMatching(ComplexCXXMatches, Builtin->getKind(), MPIDatatype)) {
BufferTypeName =
(llvm::Twine("complex<") + Builtin->getName(LO) + ">").str();
return false;
}
return true;
}
/// Check if a fixed size width buffer type matches the MPI datatype.
///
/// \param Typedef buffer type
/// \param BufferTypeName buffer type name, gets assigned
/// \param MPIDatatype name of the MPI datatype
///
/// \returns true if the type matches or the buffer type is unknown
static bool isTypedefTypeMatching(const TypedefType *const Typedef,
std::string &BufferTypeName,
const std::string &MPIDatatype) {
static llvm::StringMap<std::string> FixedWidthMatches = {
{"int8_t", "MPI_INT8_T"}, {"int16_t", "MPI_INT16_T"},
{"int32_t", "MPI_INT32_T"}, {"int64_t", "MPI_INT64_T"},
{"uint8_t", "MPI_UINT8_T"}, {"uint16_t", "MPI_UINT16_T"},
{"uint32_t", "MPI_UINT32_T"}, {"uint64_t", "MPI_UINT64_T"}};
const auto It = FixedWidthMatches.find(Typedef->getDecl()->getName());
// Check if the typedef is known and not matching the MPI datatype.
if (It != FixedWidthMatches.end() && It->getValue() != MPIDatatype) {
BufferTypeName = std::string(Typedef->getDecl()->getName());
return false;
}
return true;
}
/// Get the unqualified, dereferenced type of an argument.
///
/// \param CE call expression
/// \param Idx argument index
///
/// \returns type of the argument
static const Type *argumentType(const CallExpr *const CE, const size_t Idx) {
const QualType QT = CE->getArg(Idx)->IgnoreImpCasts()->getType();
return QT.getTypePtr()->getPointeeOrArrayElementType();
}
void TypeMismatchCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(callExpr().bind("CE"), this);
}
void TypeMismatchCheck::check(const MatchFinder::MatchResult &Result) {
const auto *const CE = Result.Nodes.getNodeAs<CallExpr>("CE");
if (!CE->getDirectCallee())
return;
if (!FuncClassifier)
FuncClassifier.emplace(*Result.Context);
const IdentifierInfo *Identifier = CE->getDirectCallee()->getIdentifier();
if (!Identifier || !FuncClassifier->isMPIType(Identifier))
return;
// These containers are used, to capture buffer, MPI datatype pairs.
SmallVector<const Type *, 1> BufferTypes;
SmallVector<const Expr *, 1> BufferExprs;
SmallVector<StringRef, 1> MPIDatatypes;
// Adds a buffer, MPI datatype pair of an MPI call expression to the
// containers. For buffers, the type and expression is captured.
auto AddPair = [&CE, &Result, &BufferTypes, &BufferExprs, &MPIDatatypes](
const size_t BufferIdx, const size_t DatatypeIdx) {
// Skip null pointer constants and in place 'operators'.
if (CE->getArg(BufferIdx)->isNullPointerConstant(
*Result.Context, Expr::NPC_ValueDependentIsNull) ||
tooling::fixit::getText(*CE->getArg(BufferIdx), *Result.Context) ==
"MPI_IN_PLACE")
return;
StringRef MPIDatatype =
tooling::fixit::getText(*CE->getArg(DatatypeIdx), *Result.Context);
const Type *ArgType = argumentType(CE, BufferIdx);
// Skip unknown MPI datatypes and void pointers.
if (!isStandardMPIDatatype(std::string(MPIDatatype)) ||
ArgType->isVoidType())
return;
BufferTypes.push_back(ArgType);
BufferExprs.push_back(CE->getArg(BufferIdx));
MPIDatatypes.push_back(MPIDatatype);
};
// Collect all buffer, MPI datatype pairs for the inspected call expression.
if (FuncClassifier->isPointToPointType(Identifier)) {
AddPair(0, 2);
} else if (FuncClassifier->isCollectiveType(Identifier)) {
if (FuncClassifier->isReduceType(Identifier)) {
AddPair(0, 3);
AddPair(1, 3);
} else if (FuncClassifier->isScatterType(Identifier) ||
FuncClassifier->isGatherType(Identifier) ||
FuncClassifier->isAlltoallType(Identifier)) {
AddPair(0, 2);
AddPair(3, 5);
} else if (FuncClassifier->isBcastType(Identifier)) {
AddPair(0, 2);
}
}
checkArguments(BufferTypes, BufferExprs, MPIDatatypes, getLangOpts());
}
void TypeMismatchCheck::checkArguments(ArrayRef<const Type *> BufferTypes,
ArrayRef<const Expr *> BufferExprs,
ArrayRef<StringRef> MPIDatatypes,
const LangOptions &LO) {
std::string BufferTypeName;
for (size_t I = 0; I < MPIDatatypes.size(); ++I) {
const Type *const BT = BufferTypes[I];
bool Error = false;
if (const auto *Typedef = BT->getAs<TypedefType>()) {
Error = !isTypedefTypeMatching(Typedef, BufferTypeName,
std::string(MPIDatatypes[I]));
} else if (const auto *Complex = BT->getAs<ComplexType>()) {
Error = !isCComplexTypeMatching(Complex, BufferTypeName,
std::string(MPIDatatypes[I]), LO);
} else if (const auto *Template = BT->getAs<TemplateSpecializationType>()) {
Error = !isCXXComplexTypeMatching(Template, BufferTypeName,
std::string(MPIDatatypes[I]), LO);
} else if (const auto *Builtin = BT->getAs<BuiltinType>()) {
Error = !isBuiltinTypeMatching(Builtin, BufferTypeName,
std::string(MPIDatatypes[I]), LO);
}
if (Error) {
const auto Loc = BufferExprs[I]->getSourceRange().getBegin();
diag(Loc, "buffer type '%0' does not match the MPI datatype '%1'")
<< BufferTypeName << MPIDatatypes[I];
}
}
}
void TypeMismatchCheck::onEndOfTranslationUnit() { FuncClassifier.reset(); }
} // namespace mpi
} // namespace tidy
} // namespace clang