diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -40,6 +40,7 @@ StaticAccessedThroughInstanceCheck.cpp StaticDefinitionInAnonymousNamespaceCheck.cpp StringCompareCheck.cpp + SuspiciousCallArgumentCheck.cpp UniqueptrDeleteReleaseCheck.cpp UppercaseLiteralSuffixCheck.cpp diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp --- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -43,6 +43,7 @@ #include "StaticAccessedThroughInstanceCheck.h" #include "StaticDefinitionInAnonymousNamespaceCheck.h" #include "StringCompareCheck.h" +#include "SuspiciousCallArgumentCheck.h" #include "UniqueptrDeleteReleaseCheck.h" #include "UppercaseLiteralSuffixCheck.h" @@ -121,6 +122,8 @@ "readability-redundant-string-init"); CheckFactories.registerCheck( "readability-simplify-boolean-expr"); + CheckFactories.registerCheck( + "readability-suspicious-call-argument"); CheckFactories.registerCheck( "readability-uniqueptr-delete-release"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.h b/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.h @@ -0,0 +1,97 @@ +//===--- SuspiciousCallArgumentCheck.h - clang-tidy--------------*- C -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SUSPICIOUS_CALL_ARGUMENT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SUSPICIOUS_CALL_ARGUMENT_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// This checker finds those function calls where the function arguments are +/// provided in an incorrect order. It compares the name of the given variable +/// to the argument name in the function definition. +/// It issues a message if the given variable name is similar to an another +/// function argument in a function call. It uses case insensitive comparison. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-suspicious-call-argument.html +class SuspiciousCallArgumentCheck : public ClangTidyCheck { +public: + SuspiciousCallArgumentCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + + const static unsigned VectorSmallSize = 8; + +private: + enum class Heuristic { + Equality, + Abbreviation, + Prefix, + Suffix, + Levenshtein, + Substring, + JaroWinkler, + Dice + }; + + enum class Bound { Lower, Upper }; + + SmallVector AppliedHeuristics; + + SmallVector ArgTypes; + SmallVector ArgNames; + SmallVector ParamTypes; + SmallVector ParamNames; + + const bool Equality; + const bool Abbreviation; + const bool Levenshtein; + const bool Prefix; + const bool Suffix; + const bool Substring; + const bool JaroWinkler; + const bool Dice; + + const int LevenshteinUpperBound; + const int PrefixUpperBound; + const int SuffixUpperBound; + const int SubstringUpperBound; + const int JaroWinklerUpperBound; + const int DiceUpperBound; + + const int LevenshteinLowerBound; + const int PrefixLowerBound; + const int SuffixLowerBound; + const int SubstringLowerBound; + const int JaroWinklerLowerBound; + const int DiceLowerBound; + + void setParamNamesAndTypes(const FunctionDecl *CalleeFuncDecl); + + void setArgNamesAndTypes(const CallExpr *MatchedCallExpr, + unsigned InitialArgIndex); + + bool areParamAndArgComparable(unsigned Position1, unsigned Position2, + const ASTContext *Ctx) const; + + bool areArgsSwapped(unsigned Position1, unsigned Position2) const; + + bool areNamesSimilar(StringRef Arg, StringRef Param, Bound bound) const; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SUSPICIOUS_CALL_ARGUMENT_H diff --git a/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp b/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp @@ -0,0 +1,690 @@ +//===--- SuspiciousCallArgumentCheck.cpp - clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SuspiciousCallArgumentCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include + +using namespace clang::ast_matchers; + +namespace { + +llvm::StringMap AbbreviationDictionary; + +} // namespace + +namespace clang { +namespace tidy { +namespace readability { + +static bool applyEqualityHeuristic(StringRef Arg, StringRef Param) { + return Arg.equals_lower(Param); +} + +static bool applyAbbreviationHeuristic(StringRef Arg, StringRef Param) { + if (AbbreviationDictionary.find(Arg) != AbbreviationDictionary.end()) { + if (Param.compare(AbbreviationDictionary.lookup(Arg)) == 0) + return true; + } + + if (AbbreviationDictionary.find(Param) != AbbreviationDictionary.end()) { + if (Arg.compare(AbbreviationDictionary.lookup(Param)) == 0) + return true; + } + + return false; +} + +// Check whether the shorter String is a prefix of the longer String. +static bool applyPrefixHeuristic(StringRef Arg, StringRef Param, + unsigned threshold) { + StringRef Shorter = Arg.size() < Param.size() ? Arg : Param; + StringRef Longer = Arg.size() >= Param.size() ? Arg : Param; + + double PrefixMatch = 0; + if (Longer.startswith_lower(Shorter)) + PrefixMatch = (double)Shorter.size() / Longer.size() * 100; + + return PrefixMatch > threshold; +} + +// Check whether the shorter String is a suffix of the longer String. +static bool applySuffixHeuristic(StringRef Arg, StringRef Param, + unsigned threshold) { + StringRef Shorter = Arg.size() < Param.size() ? Arg : Param; + StringRef Longer = Arg.size() >= Param.size() ? Arg : Param; + + double SuffixMatch = 0; + if (Longer.endswith_lower(Shorter)) + SuffixMatch = (double)Shorter.size() / Longer.size() * 100; + + return SuffixMatch > threshold; +} + +static bool applySubstringHeuristic(StringRef Arg, StringRef Param, + unsigned threshold) { + unsigned MaxLength = 0; + llvm::SmallVector + Current(Param.size()); + llvm::SmallVector + Previous(Param.size()); + + for (unsigned i = 0; i < Arg.size(); ++i) { + for (unsigned j = 0; j < Param.size(); ++j) { + if (Arg[i] == Param[j]) { + if (i == 0 || j == 0) { + Current[j] = 1; + } else { + Current[j] = 1 + Previous[j - 1]; + } + + MaxLength = std::max(MaxLength, Current[j]); + } else { + Current[j] = 0; + } + } + + Current.swap(Previous); + } + + size_t LongerLength = std::max(Arg.size(), Param.size()); + double SubstringRatio = (double)MaxLength / LongerLength * 100; + + return SubstringRatio > threshold; +} + +static bool applyLevenshteinHeuristic(StringRef Arg, StringRef Param, + unsigned threshold) { + unsigned Dist = Arg.edit_distance(Param); + size_t LongerLength = std::max(Arg.size(), Param.size()); + Dist = (1 - (double)Dist / LongerLength) * 100; + return Dist > threshold; +} + +// https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance +static bool applyJaroWinklerHeuristic(StringRef Arg, StringRef Param, + unsigned threshold) { + int Match = 0, Transpos = 0; + int ArgLen = Arg.size(); + int ParamLen = Param.size(); + llvm::SmallVector ArgFlags( + ArgLen); + llvm::SmallVector + ParamFlags(ParamLen); + int Range = std::max(0, std::max(ArgLen, ParamLen) / 2 - 1); + + // Calculate matching characters. + for (int i = 0; i < ParamLen; i++) { + for (int j = std::max(i - Range, 0), l = std::min(i + Range + 1, ArgLen); + j < l; j++) { + if (tolower(Param[i]) == tolower(Arg[j]) && !ArgFlags[j]) { + ArgFlags[j] = 1; + ParamFlags[i] = 1; + Match++; + break; + } + } + } + + if (!Match) + return false; + + // Calculate character transpositions. + int l = 0; + for (int i = 0; i < ParamLen; i++) { + if (ParamFlags[i] == 1) { + int j; + for (j = l; j < ArgLen; j++) { + if (ArgFlags[j] == 1) { + l = j + 1; + break; + } + } + if (tolower(Param[i]) != tolower(Arg[j])) + Transpos++; + } + } + Transpos /= 2; + + // Jaro distance. + double Dist = (((double)Match / ArgLen) + ((double)Match / ParamLen) + + ((double)(Match - Transpos) / Match)) / + 3.0; + + // Calculate common string prefix up to 4 chars. + l = 0; + for (int i = 0; i < std::min(std::min(ArgLen, ParamLen), 4); i++) + if (tolower(Arg[i]) == tolower(Param[i])) + l++; + + // Jaro-Winkler distance. + Dist = (Dist + (l * 0.1 * (1 - Dist))) * 100; + + return Dist > threshold; +} + +// https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient +static bool applyDiceHeuristic(StringRef Arg, StringRef Param, + unsigned threshold) { + double Dice = 0; + + llvm::StringSet<> Arg_bigrams; + llvm::StringSet<> Param_bigrams; + + // Extract character bigrams from Arg. + for (unsigned i = 0; i < (Arg.size() - 1); i++) { + Arg_bigrams.insert(Arg.substr(i, 2)); + } + + // Extract character bigrams from Param. + for (unsigned i = 0; i < (Param.size() - 1); i++) { + Param_bigrams.insert(Param.substr(i, 2)); + } + + int Intersection = 0; + + // Find the intersection between the two sets. + for (auto IT = Param_bigrams.begin(); IT != Param_bigrams.end(); IT++) { + Intersection += Arg_bigrams.count((IT->getKey())); + } + + // Calculate dice coefficient. + Dice = + (Intersection * 2.0) / (Arg_bigrams.size() + Param_bigrams.size()) * 100; + + return Dice > threshold; +} + +// Checks if ArgType binds to ParamType ragerding reference-ness and +// cv-qualifiers. +static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType) { + return !ParamType->isReferenceType() || + ParamType.getNonReferenceType().isAtLeastAsQualifiedAs( + ArgType.getNonReferenceType()); +} + +static bool isPointerOrArray(const QualType &TypeToCheck) { + return TypeToCheck->isAnyPointerType() || TypeToCheck->isArrayType(); +} + +// Checks whether ArgType is an array type identical to ParamType`s array type. +// Enforces array elements` qualifier compatibility as well. +static bool isCompatibleWithArrayReference(const QualType &ArgType, + const QualType &ParamType) { + if (!ArgType->isArrayType()) + return false; + // Here, qualifiers belong to the elements of the arrays. + if (!ParamType.isAtLeastAsQualifiedAs(ArgType)) + return false; + + return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType(); +} + +static void convertToPointeeOrArrayElementQualType(QualType &TypeToConvert) { + unsigned CVRqualifiers = 0; + // Save array element qualifiers, since getElementType() removes qualifiers + // from array elements. + if (TypeToConvert->isArrayType()) + CVRqualifiers = TypeToConvert.getLocalQualifiers().getCVRQualifiers(); + TypeToConvert = TypeToConvert->isPointerType() + ? TypeToConvert->getPointeeType() + : TypeToConvert->getAsArrayTypeUnsafe()->getElementType(); + TypeToConvert = TypeToConvert.withCVRQualifiers(CVRqualifiers); +} + +// Checks if multilevel pointers` qualifiers compatibility continues on the +// current pointer evel. +// For multilevel pointers, C++ permits conversion, if every cv-qualifier in +// ArgType also appears in the corresponding position in ParamType, +// and if PramType has a cv-qualifier that's not in ArgType, then every * in +// ParamType to the right +// of that cv-qualifier, except the last one, must also be const-qualified. +static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType, + bool &IsParamContinuouslyConst) { + // The types are compatible, if the parameter is at least as qualified as the + // argument, and if it is more qualified, it has to be const on upper pointer + // levels. + bool AreTypesQualCompatible = + ParamType.isAtLeastAsQualifiedAs(ArgType) && + (!ParamType.hasQualifiers() || IsParamContinuouslyConst); + // Check whether the parameter's constness continues at the current pointer + // level. + IsParamContinuouslyConst &= ParamType.isConstQualified(); + + return AreTypesQualCompatible; +} + +// Checks whether multilevel pointers are compatible in terms of levels, +// qualifiers and pointee type. +static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType, + bool IsParamContinuouslyConst) { + + if (!arePointersStillQualCompatible(ArgType, ParamType, + IsParamContinuouslyConst)) + return false; + + do { + // Step down one pointer level. + convertToPointeeOrArrayElementQualType(ArgType); + convertToPointeeOrArrayElementQualType(ParamType); + + // Check whether cv-qualifiers premit compatibility on + // current level. + if (!arePointersStillQualCompatible(ArgType, ParamType, + IsParamContinuouslyConst)) + return false; + + if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) + return true; + + } while (ParamType->isAnyPointerType() && ArgType->isAnyPointerType()); + // The final type does not match, or pointer levels differ. + return false; +} + +// Checks whether ArgType converts implicitly to ParamType. +static bool areTypesCompatible(QualType ArgType, QualType ParamType, + const ASTContext *Ctx) { + if (ArgType.isNull() || ParamType.isNull()) + return false; + + ArgType = ArgType.getCanonicalType(); + ParamType = ParamType.getCanonicalType(); + + if (ArgType == ParamType) + return true; + + // Check for constness and reference compatibility. + if (!areRefAndQualCompatible(ArgType, ParamType)) + return false; + + bool IsParamReference = ParamType->isReferenceType(); + + // Reference-ness has already been checked ad should be removed + // before further checking. + ArgType = ArgType.getNonReferenceType(); + ParamType = ParamType.getNonReferenceType(); + + bool IsParamContinuouslyConst = + !IsParamReference || ParamType.getNonReferenceType().isConstQualified(); + + if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) + return true; + + // Arithmetic types are interconvertible, except scoped enums. + if (ParamType->isArithmeticType() && ArgType->isArithmeticType()) { + if ((ParamType->isEnumeralType() && + ParamType->getAs()->getDecl()->isScoped()) || + (ArgType->isEnumeralType() && + ArgType->getAs()->getDecl()->isScoped())) + return false; + + return true; + } + + // Check if the argument and the param are both function types (the parameter + // decayed to + // a function pointer). + if (ArgType->isFunctionType() && ParamType->isFunctionPointerType()) { + ParamType = ParamType->getPointeeType(); + return ArgType == ParamType; + } + + // Arrays or pointer arguments convert to array or pointer parameters. + if (!(isPointerOrArray(ArgType) && isPointerOrArray(ParamType))) + return false; + + // When ParamType is an array reference, ArgType has to be of the same sized, + // array type with cv-compatible elements. + if (IsParamReference && ParamType->isArrayType()) + return isCompatibleWithArrayReference(ArgType, ParamType); + + // Remove the first level of indirection. + convertToPointeeOrArrayElementQualType(ArgType); + convertToPointeeOrArrayElementQualType(ParamType); + + // Check qualifier compatibility on the next level. + if (!ParamType.isAtLeastAsQualifiedAs(ArgType)) + return false; + + if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) + return true; + + // At this point, all possible C language implicit conversion were checked + if (!Ctx->getLangOpts().CPlusPlus) + return false; + + // Check whether ParamType and ArgType were both pointers to a class or a + // struct, and check for inheritance. + if (ParamType->isStructureOrClassType() && + ArgType->isStructureOrClassType()) { + auto *ArgDecl = ArgType->getAsCXXRecordDecl(); + auto *ParamDecl = ParamType->getAsCXXRecordDecl(); + if (!ArgDecl || !ArgDecl->hasDefinition() || !ParamDecl || + !ParamDecl->hasDefinition()) + return false; + + return ArgDecl->isDerivedFrom(ParamDecl); + } + + // Unless argument and param are both multilevel pointers, the types are not + // convertible. + if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType())) + return false; + + return arePointerTypesCompatible(ArgType, ParamType, + IsParamContinuouslyConst); +} + +// Constructor. +SuspiciousCallArgumentCheck::SuspiciousCallArgumentCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), Equality(Options.get("Equality", true)), + Abbreviation(Options.get("Abbreviation", true)), + Levenshtein(Options.get("Levenshtein", true)), + Prefix(Options.get("Prefix", true)), Suffix(Options.get("Suffix", true)), + Substring(Options.get("Substring", true)), + JaroWinkler(Options.get("JaroWinkler", true)), + Dice(Options.get("Dice", true)), + LevenshteinUpperBound(Options.get("LevenshteinUpperBound", 66)), + PrefixUpperBound(Options.get("PrefixUpperBound", 30)), + SuffixUpperBound(Options.get("SuffixUpperBound", 30)), + SubstringUpperBound(Options.get("SubstringUpperBound", 50)), + JaroWinklerUpperBound(Options.get("JaroWinklerUpperBound", 85)), + DiceUpperBound(Options.get("DiceUpperBound", 70)), + LevenshteinLowerBound(Options.get("LevenshteinLowerBound", 50)), + PrefixLowerBound(Options.get("PrefixLowerBound", 25)), + SuffixLowerBound(Options.get("SuffixLowerBound", 25)), + SubstringLowerBound(Options.get("SubstringLowerBound", 40)), + JaroWinklerLowerBound(Options.get("JaroWinklerLowerBound", 75)), + DiceLowerBound(Options.get("DiceLowerBound", 60)) { + + AbbreviationDictionary.insert(std::make_pair("ptr", "pointer")); + AbbreviationDictionary.insert(std::make_pair("len", "length")); + AbbreviationDictionary.insert(std::make_pair("addr", "address")); + AbbreviationDictionary.insert(std::make_pair("arr", "array")); + AbbreviationDictionary.insert(std::make_pair("cpy", "copy")); + AbbreviationDictionary.insert(std::make_pair("src", "source")); + AbbreviationDictionary.insert(std::make_pair("val", "value")); + AbbreviationDictionary.insert(std::make_pair("ln", "line")); + AbbreviationDictionary.insert(std::make_pair("col", "column")); + AbbreviationDictionary.insert(std::make_pair("num", "number")); + AbbreviationDictionary.insert(std::make_pair("nr", "number")); + AbbreviationDictionary.insert(std::make_pair("stmt", "statement")); + AbbreviationDictionary.insert(std::make_pair("var", "variable")); + AbbreviationDictionary.insert(std::make_pair("vec", "vector")); + AbbreviationDictionary.insert(std::make_pair("buf", "buffer")); + AbbreviationDictionary.insert(std::make_pair("txt", "text")); + AbbreviationDictionary.insert(std::make_pair("dest", "destination")); + AbbreviationDictionary.insert(std::make_pair("elem", "element")); + AbbreviationDictionary.insert(std::make_pair("wdth", "width")); + AbbreviationDictionary.insert(std::make_pair("hght", "height")); + AbbreviationDictionary.insert(std::make_pair("cnt", "count")); + AbbreviationDictionary.insert(std::make_pair("i", "index")); + AbbreviationDictionary.insert(std::make_pair("idx", "index")); + AbbreviationDictionary.insert(std::make_pair("ind", "index")); + AbbreviationDictionary.insert(std::make_pair("attr", "atttribute")); + AbbreviationDictionary.insert(std::make_pair("pos", "position")); + AbbreviationDictionary.insert(std::make_pair("lst", "list")); + AbbreviationDictionary.insert(std::make_pair("str", "string")); + AbbreviationDictionary.insert(std::make_pair("srvr", "server")); + AbbreviationDictionary.insert(std::make_pair("clnt", "client")); + AbbreviationDictionary.insert(std::make_pair("ref", "reference")); + AbbreviationDictionary.insert(std::make_pair("dst", "distance")); + AbbreviationDictionary.insert(std::make_pair("dist", "distance")); + + if (Equality) + AppliedHeuristics.push_back(Heuristic::Equality); + if (Abbreviation) + AppliedHeuristics.push_back(Heuristic::Abbreviation); + if (Levenshtein) + AppliedHeuristics.push_back(Heuristic::Levenshtein); + if (Prefix) + AppliedHeuristics.push_back(Heuristic::Prefix); + if (Suffix) + AppliedHeuristics.push_back(Heuristic::Suffix); + if (Substring) + AppliedHeuristics.push_back(Heuristic::Substring); + if (JaroWinkler) + AppliedHeuristics.push_back(Heuristic::JaroWinkler); + if (Dice) + AppliedHeuristics.push_back(Heuristic::Dice); +} + +// Options. +void SuspiciousCallArgumentCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + + Options.store(Opts, "Equality", Equality); + Options.store(Opts, "Abbreviation", Abbreviation); + Options.store(Opts, "Levenshtein", Levenshtein); + Options.store(Opts, "Prefix", Prefix); + Options.store(Opts, "Suffix", Suffix); + Options.store(Opts, "Substring", Substring); + Options.store(Opts, "JaroWinkler", JaroWinkler); + Options.store(Opts, "Dice", Dice); + + Options.store(Opts, "LevenshteinUpperBound", LevenshteinUpperBound); + Options.store(Opts, "PrefixUpperBound", PrefixUpperBound); + Options.store(Opts, "SuffixUpperBound", SuffixUpperBound); + Options.store(Opts, "SubstringUpperBound", SubstringUpperBound); + Options.store(Opts, "JaroWinklerUpperBound", JaroWinklerUpperBound); + Options.store(Opts, "DiceUpperBound", DiceUpperBound); + + Options.store(Opts, "LevenshteinLowerBound", LevenshteinLowerBound); + Options.store(Opts, "PrefixLowerBound", PrefixLowerBound); + Options.store(Opts, "SuffixLowerBound", SuffixLowerBound); + Options.store(Opts, "SubstringLowerBound", SubstringLowerBound); + Options.store(Opts, "JaroWinklerLowerBound", JaroWinklerLowerBound); + Options.store(Opts, "DiceLowerBound", DiceLowerBound); +} + +// Matcher. +void SuspiciousCallArgumentCheck::registerMatchers(MatchFinder *Finder) { + // Only match calls with at least 2 arguments. + Finder->addMatcher( + callExpr(unless(argumentCountIs(0)), unless(argumentCountIs(1))) + .bind("functionCall"), + this); +} + +void SuspiciousCallArgumentCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedCallExpr = + Result.Nodes.getNodeAs("functionCall"); + + const Decl *CalleeDecl = MatchedCallExpr->getCalleeDecl(); + if (!CalleeDecl) + return; + + const FunctionDecl *CalleeFuncDecl = CalleeDecl->getAsFunction(); + if (!CalleeFuncDecl) + return; + + // Get param attributes. + setParamNamesAndTypes(CalleeFuncDecl); + + if (ParamNames.empty()) + return; + + // Get Arg attributes. + // Lambda functions first Args are themselves. + unsigned InitialArgIndex = 0; + + if (const auto *MethodDecl = dyn_cast(CalleeFuncDecl)) { + if (MethodDecl->getParent()->isLambda()) + InitialArgIndex = 1; + } + + setArgNamesAndTypes(MatchedCallExpr, InitialArgIndex); + + if (ArgNames.empty()) + return; + + // In case of variadic functions. + unsigned ParamCount = ParamNames.size(); + + // Check similarity. + for (unsigned i = 0; i < ParamCount; i++) { + for (unsigned j = i + 1; j < ParamCount; j++) { + // Do not check if param or arg names are short, or not convertible. + if (!areParamAndArgComparable(i, j, Result.Context)) + continue; + + if (areArgsSwapped(i, j)) { + // Warning at the function call. + diag(MatchedCallExpr->getBeginLoc(), "%0 (%1) is swapped with %2 (%3).") + << ArgNames[i] << ParamNames[i] << ArgNames[j] << ParamNames[j]; + + // Note at the functions declaration. + diag(CalleeFuncDecl->getBeginLoc(), + "%0 is declared here:", DiagnosticIDs::Note) + << CalleeFuncDecl->getNameInfo().getName().getAsString(); + + return; // TODO: Address this return later + } + } + } +} + +void SuspiciousCallArgumentCheck::setParamNamesAndTypes( + const FunctionDecl *CalleeFuncDecl) { + // Reset vectors, and fill them with the currently checked function's + // attributes. + ParamNames.clear(); + ParamTypes.clear(); + + for (unsigned i = 0, e = CalleeFuncDecl->getNumParams(); i != e; ++i) { + + if (const ParmVarDecl *PVD = CalleeFuncDecl->getParamDecl(i)) { + ParamTypes.push_back(PVD->getType()); + + if (IdentifierInfo *II = PVD->getIdentifier()) { + ParamNames.push_back(II->getName()); + continue; + } else { + ParamNames.push_back(StringRef()); + continue; + } + } + ParamTypes.push_back(QualType()); + ParamNames.push_back(StringRef()); + } +} + +void SuspiciousCallArgumentCheck::setArgNamesAndTypes( + const CallExpr *MatchedCallExpr, unsigned InitialArgIndex) { + // Reset vectors, and fill them with the currently checked function's + // attributes. + ArgNames.clear(); + ArgTypes.clear(); + + for (unsigned i = InitialArgIndex, j = MatchedCallExpr->getNumArgs(); i < j; + i++) { + if (const auto *DeclRef = dyn_cast( + MatchedCallExpr->getArg(i)->IgnoreParenImpCasts())) { + if (const auto *Var = dyn_cast(DeclRef->getDecl())) { + + ArgTypes.push_back(Var->getType()); + ArgNames.push_back(Var->getName()); + continue; + } else if (const auto *Var = dyn_cast(DeclRef->getDecl())) { + ArgTypes.push_back(Var->getType()); + ArgNames.push_back(Var->getName()); + continue; + } + } + ArgTypes.push_back(QualType()); + ArgNames.push_back(StringRef()); + } +} + +bool SuspiciousCallArgumentCheck::areParamAndArgComparable( + unsigned Position1, unsigned Position2, const ASTContext *Ctx) const { + if (Position1 >= ArgNames.size() || Position2 >= ArgNames.size()) + return false; + if (ArgNames[Position1].size() < 3 || ArgNames[Position2].size() < 3 || + ParamNames[Position1].size() < 3 || ParamNames[Position2].size() < 3) + return false; + if (!areTypesCompatible(ArgTypes[Position1], ParamTypes[Position2], Ctx) || + !areTypesCompatible(ArgTypes[Position2], ParamTypes[Position1], Ctx)) + return false; + return true; +} + +bool SuspiciousCallArgumentCheck::areArgsSwapped(unsigned Position1, + unsigned Position2) const { + bool param1Arg2NamesSimilar = + areNamesSimilar(ArgNames[Position2], ParamNames[Position1], Bound::Upper); + bool param2Arg1NamesSimilar = + areNamesSimilar(ArgNames[Position1], ParamNames[Position2], Bound::Upper); + bool param1Arg1NamesSimilar = + areNamesSimilar(ArgNames[Position1], ParamNames[Position1], Bound::Lower); + bool param2Arg2NamesSimilar = + areNamesSimilar(ArgNames[Position2], ParamNames[Position2], Bound::Lower); + + return (param1Arg2NamesSimilar || param2Arg1NamesSimilar) && + !param1Arg1NamesSimilar && !param2Arg2NamesSimilar; +} + +bool SuspiciousCallArgumentCheck::areNamesSimilar(StringRef Arg, + StringRef Param, + Bound bound) const { + + bool areNamesSimilar = false; + for (Heuristic Heur : AppliedHeuristics) + switch (Heur) { + case Heuristic::Equality: + areNamesSimilar |= applyEqualityHeuristic(Arg, Param); + break; + case Heuristic::Abbreviation: + areNamesSimilar |= applyAbbreviationHeuristic(Arg, Param); + break; + case Heuristic::Levenshtein: + areNamesSimilar |= applyLevenshteinHeuristic(Arg, Param, + bound == Bound::Upper + ? LevenshteinUpperBound + : LevenshteinLowerBound); + break; + case Heuristic::Prefix: + areNamesSimilar |= applyPrefixHeuristic( + Arg, Param, + bound == Bound::Upper ? PrefixUpperBound : PrefixLowerBound); + break; + case Heuristic::Suffix: + areNamesSimilar |= applySuffixHeuristic( + Arg, Param, + bound == Bound::Upper ? SuffixUpperBound : SuffixLowerBound); + break; + case Heuristic::Substring: + areNamesSimilar |= applySubstringHeuristic( + Arg, Param, + bound == Bound::Upper ? SubstringUpperBound : SubstringLowerBound); + break; + case Heuristic::JaroWinkler: + areNamesSimilar |= applyJaroWinklerHeuristic(Arg, Param, + bound == Bound::Upper + ? JaroWinklerUpperBound + : JaroWinklerLowerBound); + break; + case Heuristic::Dice: + areNamesSimilar |= applyDiceHeuristic( + Arg, Param, bound == Bound::Upper ? DiceUpperBound : DiceLowerBound); + break; + default: + break; + } + return areNamesSimilar; +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -140,6 +140,12 @@ Finds calls to ``NSInvocation`` methods under ARC that don't have proper argument object lifetimes. +- New `readability-suspicious-call-argument + `_ check + + This checker finds those function calls where the function arguments are + provided in an incorrect order. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -295,6 +295,7 @@ `readability-static-accessed-through-instance `_, "Yes" `readability-static-definition-in-anonymous-namespace `_, "Yes" `readability-string-compare `_, "Yes" + `readability-suspicious-call-argument `_, "Yes" `readability-uniqueptr-delete-release `_, "Yes" `readability-uppercase-literal-suffix `_, "Yes" `zircon-temporary-objects `_, diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability-suspicious-call-argument.rst b/clang-tools-extra/docs/clang-tidy/checks/readability-suspicious-call-argument.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/readability-suspicious-call-argument.rst @@ -0,0 +1,11 @@ +.. title:: clang-tidy - readability-suspicious-call-argument + +readability-suspicious-call-argument +==================================== + +This checker finds those function calls where the function arguments are +provided in an incorrect order. It compares the name of the given variable +to the argument name in the function definition. + +It issues a message if the given variable name is similar to an another +function argument in a function call. It uses case insensitive comparison. diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability-suspicious-call-argument.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability-suspicious-call-argument.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/readability-suspicious-call-argument.cpp @@ -0,0 +1,392 @@ +// RUN: %check_clang_tidy %s readability-suspicious-call-argument %t -- -- -std=c++11 + +void foo_1(int aaaaaa, int bbbbbb) {} + +void foo_2(int source, int aaaaaa) {} + +void foo_3(int valToRet, int aaaaaa) {} + +void foo_4(int pointer, int aaaaaa) {} + +void foo_5(int aaaaaa, int bbbbbb, int cccccc, ...) {} + +void foo_6(const int dddddd, bool &eeeeee) {} + +void foo_7(int aaaaaa, int bbbbbb, int cccccc, int ffffff = 7) {} + +// Test functions for convertible argument--parameter types. +void fun(const int &m); +void fun2() { + int m = 3; + fun(m); +} + +// Test cases for parameters of const reference and value. +void value_const_reference(int llllll, const int &kkkkkk); + +void const_ref_value_swapped() { + const int &kkkkkk = 42; + const int &llllll = 42; + value_const_reference(kkkkkk, llllll); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: kkkkkk (llllll) is swapped with llllll (kkkkkk). [readability-suspicious-call-argument] +} + +// Const, non const references. +void const_nonconst_parameters(const int &mmmmmm, int &nnnnnn); + +void const_nonconst_swap1() { + const int &nnnnnn = 42; + int mmmmmm; + // Do not check, because non-const reference parameter cannot bind to const reference argument. + const_nonconst_parameters(nnnnnn, mmmmmm); +} + +void const_nonconst_swap3() { + const int nnnnnn = 42; + int m = 42; + int &mmmmmm = m; + // Do not check, const int does not bind to non const reference. + const_nonconst_parameters(nnnnnn, mmmmmm); +} + +void const_nonconst_swap2() { + int nnnnnn; + int mmmmmm; + // Check for swapped arguments. (Both arguments are non-const.) + const_nonconst_parameters( + nnnnnn, + mmmmmm); + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: nnnnnn (mmmmmm) is swapped with mmmmmm (nnnnnn). [readability-suspicious-call-argument] +} + +void const_nonconst_pointers(const int *mmmmmm, int *nnnnnn); +void const_nonconst_pointers2(const int *mmmmmm, const int *nnnnnn); + +void const_nonconst_pointers_swapped() { + int *mmmmmm; + const int *nnnnnn; + const_nonconst_pointers(nnnnnn, mmmmmm); +} + +void const_nonconst_pointers_swapped2() { + const int *mmmmmm; + int *nnnnnn; + const_nonconst_pointers2(nnnnnn, mmmmmm); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: nnnnnn (mmmmmm) is swapped with mmmmmm (nnnnnn). [readability-suspicious-call-argument] +} + +// Test cases for pointers and arrays. +void pointer_array_parameters( + int *pppppp, int qqqqqq[4]); + +void pointer_array_swap() { + int qqqqqq[5]; + int *pppppp; + // Check for swapped arguments. An array implicitly converts to a pointer. + pointer_array_parameters(qqqqqq, pppppp); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: qqqqqq (pppppp) is swapped with pppppp (qqqqqq). [readability-suspicious-call-argument] +} + +// Test cases for multilevel pointers. +void multilevel_pointer_parameters(int *const **pppppp, + const int *const *volatile const *qqqqqq); +void multilevel_pointer_parameters2( + char *****nnnnnn, char *volatile *const *const *const *const &mmmmmm); + +typedef float T; +typedef T *S; +typedef S *const volatile R; +typedef R *Q; +typedef Q *P; +typedef P *O; +void multilevel_pointer_parameters3(float **const volatile ***rrrrrr, O &ssssss); + +void multilevel_pointer_swap() { + int *const **qqqqqq; + int *const **pppppp; + multilevel_pointer_parameters(qqqqqq, pppppp); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: qqqqqq (pppppp) is swapped with pppppp (qqqqqq). [readability-suspicious-call-argument] + + char *****mmmmmm; + char *****nnnnnn; + multilevel_pointer_parameters2(mmmmmm, nnnnnn); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: mmmmmm (nnnnnn) is swapped with nnnnnn (mmmmmm). [readability-suspicious-call-argument] + + float **const volatile ***rrrrrr; + float **const volatile ***ssssss; + multilevel_pointer_parameters3(ssssss, rrrrrr); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: ssssss (rrrrrr) is swapped with rrrrrr (ssssss). [readability-suspicious-call-argument] +} + +void multilevel_pointer_parameters4(char ****pppppp, + char *const volatile **const *qqqqqq); +void multilevel_pointer_parameters5( + bool *****nnnnnn, bool *volatile *const *const *const *&mmmmmm); +void multilevel_pointer_parameters6(double **llllll, char **&kkkkkk); +void multilevel_pointer_parameters7(const volatile int ***iiiiii, + const int *const *const *jjjjjj); + +void multilevel_pointer_swap3() { + char ****qqqqqq; + char *const volatile **const *pppppp; + // Do not check. + multilevel_pointer_parameters4(qqqqqq, pppppp); + + bool *****mmmmmm; + bool *volatile *const *const *const *nnnnnn; + // Do not check. + multilevel_pointer_parameters5(mmmmmm, nnnnnn); + + double **kkkkkk; + char **llllll; + multilevel_pointer_parameters6(kkkkkk, llllll); + + const volatile int ***jjjjjj; + const int *const *const *iiiiii; + multilevel_pointer_parameters7(jjjjjj, iiiiii); +} + +// Test cases for multidimesional arrays. +void multilevel_array_parameters(int pppppp[2][2][2], const int qqqqqq[][2][2]); + +void multilevel_array_parameters2(int (*mmmmmm)[2][2], int nnnnnn[9][2][23]); + +void multilevel_array_parameters3(int (*eeeeee)[2][2], int (&ffffff)[1][2][2]); + +void multilevel_array_parameters4(int (*llllll)[2][2], int kkkkkk[2][2]); + +void multilevel_array_parameters5(int iiiiii[2][2], char jjjjjj[2][2]); + +void multilevel_array_parameters6(int (*bbbbbb)[2][2], int cccccc[1][2][2]); + +void multilevel_array_swap() { + int qqqqqq[1][2][2]; + int pppppp[][2][2] = {{{1, 2}, {1, 2}}, {{1, 2}, {1, 2}}}; // int [2][2][2] + multilevel_array_parameters(qqqqqq, pppppp); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: qqqqqq (pppppp) is swapped with pppppp (qqqqqq). [readability-suspicious-call-argument] + + int(*nnnnnn)[2][2]; + int mmmmmm[9][2][23]; + // Do not check, array sizes has to match in every dimension, except the first. + multilevel_array_parameters2(nnnnnn, mmmmmm); + + int ffffff[][2][2] = {{{1, 2}, {1, 2}}, {{1, 2}, {1, 2}}}; // int [2][2][2] + int eeeeee[1][2][2] = {{{1, 2}, {1, 2}}}; // int [1][2][2] + // Do not check, for array references, size has to match in every dimension. + multilevel_array_parameters3(ffffff, eeeeee); + + int kkkkkk[2][2][2]; + int(*llllll)[2]; + // Do not check, argument dimensions differ. + multilevel_array_parameters4(kkkkkk, llllll); + + int jjjjjj[2][2]; + char iiiiii[2][2]; + // Do not check, array element types differ. + multilevel_array_parameters5(jjjjjj, iiiiii); + + int t[][2][2] = {{{1, 2}, {1, 2}}, {{1, 2}, {1, 2}}}; // int [2][2][2] + int(*cccccc)[2][2] = t; // int (*)[2][2] + int bbbbbb[][2][2] = {{{1, 2}, {1, 2}}}; // int [1][2][2] + multilevel_array_parameters6(cccccc, bbbbbb); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: cccccc (bbbbbb) is swapped with bbbbbb (cccccc). [readability-suspicious-call-argument] +} + +void multilevel_array_swap2() { + int qqqqqq[2][2][2]; + const int pppppp[][2][2] = {{{1, 2}, {1, 2}}, {{1, 2}, {1, 2}}}; + // Do not check, pppppp is const and cannot bind to an array with nonconst elements. + multilevel_array_parameters(qqqqqq, pppppp); +} + +// Complex test case. +void multilevel_pointer_array_parameters(const int(*const (*volatile const (*const (*const (*const &aaaaaa)[1])[32])[4])[3][2][2]), const int(*const (*volatile const (*const (*const (*&bbbbbb)[1])[32])[4])[3][2][2])); + +void multilevel_pointer_array_swap() { + const int( + *const(*volatile const(*const(*const(*aaaaaa)[1])[32])[4])[3][2][2]); + const int( + *const(*volatile const(*const(*const(*bbbbbb)[1])[32])[4])[3][2][2]); + multilevel_pointer_array_parameters(bbbbbb, aaaaaa); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bbbbbb (aaaaaa) is swapped with aaaaaa (bbbbbb). [readability-suspicious-call-argument] +} + +enum class numbers_scoped { one, + two }; + +// Test cases for arithmetic types. +void arithmetic_type_parameters(float vvvvvv, int wwwwww); +void arithmetic_type_parameters2(numbers_scoped vvvvvv, int wwwwww); + +void arithmetic_types_swap1() { + bool wwwwww; + float vvvvvv; + arithmetic_type_parameters(wwwwww, vvvvvv); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: wwwwww (vvvvvv) is swapped with vvvvvv (wwwwww). [readability-suspicious-call-argument] +} + +void arithmetic_types_swap3() { + char wwwwww; + unsigned long long int vvvvvv; + arithmetic_type_parameters(wwwwww, vvvvvv); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: wwwwww (vvvvvv) is swapped with vvvvvv (wwwwww). [readability-suspicious-call-argument] +} + +void arithmetic_types_swap4() { + enum numbers { one, + two }; + numbers wwwwww = numbers::one; + int vvvvvv; + arithmetic_type_parameters(wwwwww, vvvvvv); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: wwwwww (vvvvvv) is swapped with vvvvvv (wwwwww). [readability-suspicious-call-argument] +} + +void arithmetic_types_swap5() { + wchar_t vvvvvv; + float wwwwww; + arithmetic_type_parameters(wwwwww, vvvvvv); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: wwwwww (vvvvvv) is swapped with vvvvvv (wwwwww). [readability-suspicious-call-argument] +} + +void arithmetic_types_swap6() { + wchar_t vvvvvv; + numbers_scoped wwwwww = numbers_scoped::one; + // Do not check, numers is a scoped enum type. + arithmetic_type_parameters2(wwwwww, vvvvvv); +} + +// Base, derived +class TestClass { +public: + void thisFunction(int integerParam, int thisIsPARAM) {} +}; + +class DerivedTestClass : public TestClass {}; + +void base_derived_pointer_parameters(TestClass *aaaaaa, + DerivedTestClass *bbbbbb); + +void base_derived_swap1() { + TestClass *bbbbbb; + DerivedTestClass *aaaaaa; + // Do not check, because TestClass does not convert implicitly to DerivedTestClass. + base_derived_pointer_parameters(bbbbbb, aaaaaa); +} + +void base_derived_swap2() { + DerivedTestClass *bbbbbb, *aaaaaa; + // Check for swapped arguments, DerivedTestClass converts to TestClass implicitly. + base_derived_pointer_parameters(bbbbbb, aaaaaa); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: bbbbbb (aaaaaa) is swapped with aaaaaa (bbbbbb). [readability-suspicious-call-argument] +} + +// Multilevel inheritance +class DerivedOfDerivedTestClass : public DerivedTestClass {}; + +void multi_level_inheritance_swap() { + DerivedOfDerivedTestClass *aaaaaa, *bbbbbb; + // Check for swapped arguments. Derived classes implicitly convert to their base. + base_derived_pointer_parameters( + bbbbbb, aaaaaa); + // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: bbbbbb (aaaaaa) is swapped with aaaaaa (bbbbbb). [readability-suspicious-call-argument] +} + +// Tests for function pointer swaps +void funct_ptr_params(double (*ffffff)(int, int), double (*gggggg)(int, int)); +void funct_ptr_params(double (*ffffff)(int, int), int (*gggggg)(int, int)); + +double ffffff(int a, int b) { return 0; } +double gggggg(int a, int b) { return 0; } + +void funtionc_ptr_params_swap() { + funct_ptr_params(gggggg, ffffff); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: gggggg (ffffff) is swapped with ffffff (gggggg). [readability-suspicious-call-argument] +} + +int fffff(int a, int b) { return 0; } + +void function_ptr_swap2() { + // Do not check, because the function `ffffff` cannot convert to a function + // with prototype: double(int,int). + funct_ptr_params(gggggg, fffff); +} + +int main() { + + // Equality test. + int aaaaaa, cccccc = 0; + foo_1(cccccc, aaaaaa); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: cccccc (aaaaaa) is swapped with aaaaaa (bbbbbb). [readability-suspicious-call-argument] + + // Abbreviation test. + int src = 0; + foo_2(aaaaaa, src); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: aaaaaa (source) is swapped with src (aaaaaa). [readability-suspicious-call-argument] + + // Levenshtein test. + int aaaabb = 0; + foo_1(cccccc, aaaabb); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: cccccc (aaaaaa) is swapped with aaaabb (bbbbbb). [readability-suspicious-call-argument] + + // Prefix test. + int aaaa = 0; + foo_1(cccccc, aaaa); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: cccccc (aaaaaa) is swapped with aaaa (bbbbbb). [readability-suspicious-call-argument] + + // Suffix test. + int urce = 0; + foo_2(cccccc, urce); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: cccccc (source) is swapped with urce (aaaaaa). [readability-suspicious-call-argument] + + // Substring test. + int ourc = 0; + foo_2(cccccc, ourc); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: cccccc (source) is swapped with ourc (aaaaaa). [readability-suspicious-call-argument] + + // Jaro-Winkler test. + int iPonter = 0; + foo_4(cccccc, iPonter); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: cccccc (pointer) is swapped with iPonter (aaaaaa). [readability-suspicious-call-argument] + + // Dice test. + int aaabaa = 0; + foo_1(cccccc, aaabaa); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: cccccc (aaaaaa) is swapped with aaabaa (bbbbbb). [readability-suspicious-call-argument] + + // Variadic function test. + int bbbbbb = 0; + foo_5(src, bbbbbb, cccccc, aaaaaa); // Should pass. + foo_5(cccccc, bbbbbb, aaaaaa, src); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: cccccc (aaaaaa) is swapped with aaaaaa (cccccc). [readability-suspicious-call-argument] + + // Test function with default argument. + foo_7(src, bbbbbb, cccccc, aaaaaa); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: src (aaaaaa) is swapped with aaaaaa (ffffff). [readability-suspicious-call-argument] + + foo_7(cccccc, bbbbbb, aaaaaa, src); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: cccccc (aaaaaa) is swapped with aaaaaa (cccccc). [readability-suspicious-call-argument] + + int ffffff = 0; + foo_7(ffffff, bbbbbb, cccccc); // Should fail. + + // Type match + bool dddddd = false; + int eeeeee = 0; + auto szam = 0; + foo_6(eeeeee, dddddd); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: eeeeee (dddddd) is swapped with dddddd (eeeeee). [readability-suspicious-call-argument] + foo_1(szam, aaaaaa); // Should fail. + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: szam (aaaaaa) is swapped with aaaaaa (bbbbbb). [readability-suspicious-call-argument] + + // Test lambda. + auto testMethod = [&](int method, int randomParam) { return 0; }; + int method = 0; + testMethod(method, 0); // Should pass. + + // Member function test + TestClass test; + int integ, thisIsAnArg = 0; + test.thisFunction(integ, thisIsAnArg); // Should pass. + + return 0; +}