| 1 | //===--- SuspiciousCallArgumentCheck.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 "SuspiciousCallArgumentCheck.h" |
| 10 | #include "../utils/OptionsUtils.h" |
| 11 | #include "clang/AST/ASTContext.h" |
| 12 | #include "clang/AST/Type.h" |
| 13 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 14 | #include <optional> |
| 15 | |
| 16 | using namespace clang::ast_matchers; |
| 17 | namespace optutils = clang::tidy::utils::options; |
| 18 | |
| 19 | namespace clang::tidy::readability { |
| 20 | |
| 21 | namespace { |
| 22 | struct DefaultHeuristicConfiguration { |
| 23 | /// Whether the heuristic is to be enabled by default. |
| 24 | const bool Enabled; |
| 25 | |
| 26 | /// The upper bound of % of similarity the two strings might have to be |
| 27 | /// considered dissimilar. |
| 28 | /// (For purposes of configuration, -1 if the heuristic is not configurable |
| 29 | /// with bounds.) |
| 30 | const int8_t DissimilarBelow; |
| 31 | |
| 32 | /// The lower bound of % of similarity the two string must have to be |
| 33 | /// considered similar. |
| 34 | /// (For purposes of configuration, -1 if the heuristic is not configurable |
| 35 | /// with bounds.) |
| 36 | const int8_t SimilarAbove; |
| 37 | |
| 38 | /// Can the heuristic be configured with bounds? |
| 39 | bool hasBounds() const { return DissimilarBelow > -1 && SimilarAbove > -1; } |
| 40 | }; |
| 41 | } // namespace |
| 42 | |
| 43 | static constexpr std::size_t DefaultMinimumIdentifierNameLength = 3; |
| 44 | |
| 45 | static constexpr StringRef HeuristicToString[] = { |
| 46 | "Equality" , "Abbreviation" , "Prefix" , "Suffix" , |
| 47 | "Substring" , "Levenshtein" , "JaroWinkler" , "Dice" }; |
| 48 | |
| 49 | static constexpr DefaultHeuristicConfiguration Defaults[] = { |
| 50 | {.Enabled: true, .DissimilarBelow: -1, .SimilarAbove: -1}, // Equality. |
| 51 | {.Enabled: true, .DissimilarBelow: -1, .SimilarAbove: -1}, // Abbreviation. |
| 52 | {.Enabled: true, .DissimilarBelow: 25, .SimilarAbove: 30}, // Prefix. |
| 53 | {.Enabled: true, .DissimilarBelow: 25, .SimilarAbove: 30}, // Suffix. |
| 54 | {.Enabled: true, .DissimilarBelow: 40, .SimilarAbove: 50}, // Substring. |
| 55 | {.Enabled: true, .DissimilarBelow: 50, .SimilarAbove: 66}, // Levenshtein. |
| 56 | {.Enabled: true, .DissimilarBelow: 75, .SimilarAbove: 85}, // Jaro-Winkler. |
| 57 | {.Enabled: true, .DissimilarBelow: 60, .SimilarAbove: 70}, // Dice. |
| 58 | }; |
| 59 | |
| 60 | static_assert( |
| 61 | sizeof(HeuristicToString) / sizeof(HeuristicToString[0]) == |
| 62 | SuspiciousCallArgumentCheck::HeuristicCount, |
| 63 | "Ensure that every heuristic has a corresponding stringified name" ); |
| 64 | static_assert(sizeof(Defaults) / sizeof(Defaults[0]) == |
| 65 | SuspiciousCallArgumentCheck::HeuristicCount, |
| 66 | "Ensure that every heuristic has a default configuration." ); |
| 67 | |
| 68 | namespace { |
| 69 | template <std::size_t I> struct HasWellConfiguredBounds { |
| 70 | static constexpr bool Value = |
| 71 | !((Defaults[I].DissimilarBelow == -1) ^ (Defaults[I].SimilarAbove == -1)); |
| 72 | static_assert(Value, "A heuristic must either have a dissimilarity and " |
| 73 | "similarity bound, or neither!" ); |
| 74 | }; |
| 75 | |
| 76 | template <std::size_t I> struct HasWellConfiguredBoundsFold { |
| 77 | static constexpr bool Value = HasWellConfiguredBounds<I>::Value && |
| 78 | HasWellConfiguredBoundsFold<I - 1>::Value; |
| 79 | }; |
| 80 | |
| 81 | template <> struct HasWellConfiguredBoundsFold<0> { |
| 82 | static constexpr bool Value = HasWellConfiguredBounds<0>::Value; |
| 83 | }; |
| 84 | |
| 85 | struct AllHeuristicsBoundsWellConfigured { |
| 86 | static constexpr bool Value = |
| 87 | HasWellConfiguredBoundsFold<SuspiciousCallArgumentCheck::HeuristicCount - |
| 88 | 1>::Value; |
| 89 | }; |
| 90 | |
| 91 | static_assert(AllHeuristicsBoundsWellConfigured::Value); |
| 92 | } // namespace |
| 93 | |
| 94 | static constexpr llvm::StringLiteral DefaultAbbreviations = "addr=address;" |
| 95 | "arr=array;" |
| 96 | "attr=attribute;" |
| 97 | "buf=buffer;" |
| 98 | "cl=client;" |
| 99 | "cnt=count;" |
| 100 | "col=column;" |
| 101 | "cpy=copy;" |
| 102 | "dest=destination;" |
| 103 | "dist=distance" |
| 104 | "dst=distance;" |
| 105 | "elem=element;" |
| 106 | "hght=height;" |
| 107 | "i=index;" |
| 108 | "idx=index;" |
| 109 | "len=length;" |
| 110 | "ln=line;" |
| 111 | "lst=list;" |
| 112 | "nr=number;" |
| 113 | "num=number;" |
| 114 | "pos=position;" |
| 115 | "ptr=pointer;" |
| 116 | "ref=reference;" |
| 117 | "src=source;" |
| 118 | "srv=server;" |
| 119 | "stmt=statement;" |
| 120 | "str=string;" |
| 121 | "val=value;" |
| 122 | "var=variable;" |
| 123 | "vec=vector;" |
| 124 | "wdth=width" ; |
| 125 | |
| 126 | static constexpr std::size_t SmallVectorSize = |
| 127 | SuspiciousCallArgumentCheck::SmallVectorSize; |
| 128 | |
| 129 | /// Returns how many % X is of Y. |
| 130 | static inline double percentage(double X, double Y) { return X / Y * 100.0; } |
| 131 | |
| 132 | static bool applyEqualityHeuristic(StringRef Arg, StringRef Param) { |
| 133 | return Arg.equals_insensitive(RHS: Param); |
| 134 | } |
| 135 | |
| 136 | static bool applyAbbreviationHeuristic( |
| 137 | const llvm::StringMap<std::string> &AbbreviationDictionary, StringRef Arg, |
| 138 | StringRef Param) { |
| 139 | if (AbbreviationDictionary.contains(Key: Arg) && |
| 140 | Param == AbbreviationDictionary.lookup(Key: Arg)) |
| 141 | return true; |
| 142 | |
| 143 | if (AbbreviationDictionary.contains(Key: Param) && |
| 144 | Arg == AbbreviationDictionary.lookup(Key: Param)) |
| 145 | return true; |
| 146 | |
| 147 | return false; |
| 148 | } |
| 149 | |
| 150 | /// Check whether the shorter String is a prefix of the longer String. |
| 151 | static bool applyPrefixHeuristic(StringRef Arg, StringRef Param, |
| 152 | int8_t Threshold) { |
| 153 | StringRef Shorter = Arg.size() < Param.size() ? Arg : Param; |
| 154 | StringRef Longer = Arg.size() >= Param.size() ? Arg : Param; |
| 155 | |
| 156 | if (Longer.starts_with_insensitive(Prefix: Shorter)) |
| 157 | return percentage(X: Shorter.size(), Y: Longer.size()) > Threshold; |
| 158 | |
| 159 | return false; |
| 160 | } |
| 161 | |
| 162 | /// Check whether the shorter String is a suffix of the longer String. |
| 163 | static bool applySuffixHeuristic(StringRef Arg, StringRef Param, |
| 164 | int8_t Threshold) { |
| 165 | StringRef Shorter = Arg.size() < Param.size() ? Arg : Param; |
| 166 | StringRef Longer = Arg.size() >= Param.size() ? Arg : Param; |
| 167 | |
| 168 | if (Longer.ends_with_insensitive(Suffix: Shorter)) |
| 169 | return percentage(X: Shorter.size(), Y: Longer.size()) > Threshold; |
| 170 | |
| 171 | return false; |
| 172 | } |
| 173 | |
| 174 | static bool applySubstringHeuristic(StringRef Arg, StringRef Param, |
| 175 | int8_t Threshold) { |
| 176 | |
| 177 | std::size_t MaxLength = 0; |
| 178 | SmallVector<std::size_t, SmallVectorSize> Current(Param.size()); |
| 179 | SmallVector<std::size_t, SmallVectorSize> Previous(Param.size()); |
| 180 | std::string ArgLower = Arg.lower(); |
| 181 | std::string ParamLower = Param.lower(); |
| 182 | |
| 183 | for (std::size_t I = 0; I < Arg.size(); ++I) { |
| 184 | for (std::size_t J = 0; J < Param.size(); ++J) { |
| 185 | if (ArgLower[I] == ParamLower[J]) { |
| 186 | if (I == 0 || J == 0) |
| 187 | Current[J] = 1; |
| 188 | else |
| 189 | Current[J] = 1 + Previous[J - 1]; |
| 190 | |
| 191 | MaxLength = std::max(a: MaxLength, b: Current[J]); |
| 192 | } else |
| 193 | Current[J] = 0; |
| 194 | } |
| 195 | |
| 196 | Current.swap(RHS&: Previous); |
| 197 | } |
| 198 | |
| 199 | size_t LongerLength = std::max(a: Arg.size(), b: Param.size()); |
| 200 | return percentage(X: MaxLength, Y: LongerLength) > Threshold; |
| 201 | } |
| 202 | |
| 203 | static bool applyLevenshteinHeuristic(StringRef Arg, StringRef Param, |
| 204 | int8_t Threshold) { |
| 205 | std::size_t LongerLength = std::max(a: Arg.size(), b: Param.size()); |
| 206 | double Dist = Arg.edit_distance(Other: Param); |
| 207 | Dist = (1.0 - Dist / LongerLength) * 100.0; |
| 208 | return Dist > Threshold; |
| 209 | } |
| 210 | |
| 211 | // Based on http://en.wikipedia.org/wiki/Jaro–Winkler_distance. |
| 212 | static bool applyJaroWinklerHeuristic(StringRef Arg, StringRef Param, |
| 213 | int8_t Threshold) { |
| 214 | std::size_t Match = 0, Transpos = 0; |
| 215 | std::ptrdiff_t ArgLen = Arg.size(); |
| 216 | std::ptrdiff_t ParamLen = Param.size(); |
| 217 | SmallVector<int, SmallVectorSize> ArgFlags(ArgLen); |
| 218 | SmallVector<int, SmallVectorSize> ParamFlags(ParamLen); |
| 219 | std::ptrdiff_t Range = |
| 220 | std::max(a: std::ptrdiff_t{0}, b: std::max(a: ArgLen, b: ParamLen) / 2 - 1); |
| 221 | |
| 222 | // Calculate matching characters. |
| 223 | for (std::ptrdiff_t I = 0; I < ParamLen; ++I) |
| 224 | for (std::ptrdiff_t J = std::max(a: I - Range, b: std::ptrdiff_t{0}), |
| 225 | L = std::min(a: I + Range + 1, b: ArgLen); |
| 226 | J < L; ++J) |
| 227 | if (tolower(c: Param[I]) == tolower(c: Arg[J]) && !ArgFlags[J]) { |
| 228 | ArgFlags[J] = 1; |
| 229 | ParamFlags[I] = 1; |
| 230 | ++Match; |
| 231 | break; |
| 232 | } |
| 233 | |
| 234 | if (!Match) |
| 235 | return false; |
| 236 | |
| 237 | // Calculate character transpositions. |
| 238 | std::ptrdiff_t L = 0; |
| 239 | for (std::ptrdiff_t I = 0; I < ParamLen; ++I) { |
| 240 | if (ParamFlags[I] == 1) { |
| 241 | std::ptrdiff_t J = 0; |
| 242 | for (J = L; J < ArgLen; ++J) |
| 243 | if (ArgFlags[J] == 1) { |
| 244 | L = J + 1; |
| 245 | break; |
| 246 | } |
| 247 | |
| 248 | if (tolower(c: Param[I]) != tolower(c: Arg[J])) |
| 249 | ++Transpos; |
| 250 | } |
| 251 | } |
| 252 | Transpos /= 2; |
| 253 | |
| 254 | // Jaro distance. |
| 255 | double MatchD = Match; |
| 256 | double Dist = ((MatchD / ArgLen) + (MatchD / ParamLen) + |
| 257 | ((MatchD - Transpos) / Match)) / |
| 258 | 3.0; |
| 259 | |
| 260 | // Calculate common string prefix up to 4 chars. |
| 261 | L = 0; |
| 262 | for (std::ptrdiff_t I = 0; |
| 263 | I < std::min(a: std::min(a: ArgLen, b: ParamLen), b: std::ptrdiff_t{4}); ++I) |
| 264 | if (tolower(c: Arg[I]) == tolower(c: Param[I])) |
| 265 | ++L; |
| 266 | |
| 267 | // Jaro-Winkler distance. |
| 268 | Dist = (Dist + (L * 0.1 * (1.0 - Dist))) * 100.0; |
| 269 | return Dist > Threshold; |
| 270 | } |
| 271 | |
| 272 | // Based on http://en.wikipedia.org/wiki/Sørensen–Dice_coefficient |
| 273 | static bool applyDiceHeuristic(StringRef Arg, StringRef Param, |
| 274 | int8_t Threshold) { |
| 275 | llvm::StringSet<> ArgBigrams; |
| 276 | llvm::StringSet<> ParamBigrams; |
| 277 | |
| 278 | // Extract character bigrams from Arg. |
| 279 | for (std::ptrdiff_t I = 0; I < static_cast<std::ptrdiff_t>(Arg.size()) - 1; |
| 280 | ++I) |
| 281 | ArgBigrams.insert(key: Arg.substr(Start: I, N: 2).lower()); |
| 282 | |
| 283 | // Extract character bigrams from Param. |
| 284 | for (std::ptrdiff_t I = 0; I < static_cast<std::ptrdiff_t>(Param.size()) - 1; |
| 285 | ++I) |
| 286 | ParamBigrams.insert(key: Param.substr(Start: I, N: 2).lower()); |
| 287 | |
| 288 | std::size_t Intersection = 0; |
| 289 | |
| 290 | // Find the intersection between the two sets. |
| 291 | for (auto IT = ParamBigrams.begin(); IT != ParamBigrams.end(); ++IT) |
| 292 | Intersection += ArgBigrams.count(Key: (IT->getKey())); |
| 293 | |
| 294 | // Calculate Dice coefficient. |
| 295 | return percentage(X: Intersection * 2.0, |
| 296 | Y: ArgBigrams.size() + ParamBigrams.size()) > Threshold; |
| 297 | } |
| 298 | |
| 299 | /// Checks if ArgType binds to ParamType regarding reference-ness and |
| 300 | /// cv-qualifiers. |
| 301 | static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType, |
| 302 | const ASTContext &Ctx) { |
| 303 | return !ParamType->isReferenceType() || |
| 304 | ParamType.getNonReferenceType().isAtLeastAsQualifiedAs( |
| 305 | other: ArgType.getNonReferenceType(), Ctx); |
| 306 | } |
| 307 | |
| 308 | static bool isPointerOrArray(QualType TypeToCheck) { |
| 309 | return TypeToCheck->isPointerType() || TypeToCheck->isArrayType(); |
| 310 | } |
| 311 | |
| 312 | /// Checks whether ArgType is an array type identical to ParamType's array type. |
| 313 | /// Enforces array elements' qualifier compatibility as well. |
| 314 | static bool isCompatibleWithArrayReference(QualType ArgType, QualType ParamType, |
| 315 | const ASTContext &Ctx) { |
| 316 | if (!ArgType->isArrayType()) |
| 317 | return false; |
| 318 | // Here, qualifiers belong to the elements of the arrays. |
| 319 | if (!ParamType.isAtLeastAsQualifiedAs(other: ArgType, Ctx)) |
| 320 | return false; |
| 321 | |
| 322 | return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType(); |
| 323 | } |
| 324 | |
| 325 | static QualType convertToPointeeOrArrayElementQualType(QualType TypeToConvert) { |
| 326 | unsigned CVRqualifiers = 0; |
| 327 | // Save array element qualifiers, since getElementType() removes qualifiers |
| 328 | // from array elements. |
| 329 | if (TypeToConvert->isArrayType()) |
| 330 | CVRqualifiers = TypeToConvert.getLocalQualifiers().getCVRQualifiers(); |
| 331 | TypeToConvert = TypeToConvert->isPointerType() |
| 332 | ? TypeToConvert->getPointeeType() |
| 333 | : TypeToConvert->getAsArrayTypeUnsafe()->getElementType(); |
| 334 | TypeToConvert = TypeToConvert.withCVRQualifiers(CVR: CVRqualifiers); |
| 335 | return TypeToConvert; |
| 336 | } |
| 337 | |
| 338 | /// Checks if multilevel pointers' qualifiers compatibility continues on the |
| 339 | /// current pointer level. For multilevel pointers, C++ permits conversion, if |
| 340 | /// every cv-qualifier in ArgType also appears in the corresponding position in |
| 341 | /// ParamType, and if PramType has a cv-qualifier that's not in ArgType, then |
| 342 | /// every * in ParamType to the right of that cv-qualifier, except the last |
| 343 | /// one, must also be const-qualified. |
| 344 | static bool (QualType ArgType, QualType ParamType, |
| 345 | bool &IsParamContinuouslyConst, |
| 346 | const ASTContext &Ctx) { |
| 347 | // The types are compatible, if the parameter is at least as qualified as the |
| 348 | // argument, and if it is more qualified, it has to be const on upper pointer |
| 349 | // levels. |
| 350 | bool AreTypesQualCompatible = |
| 351 | ParamType.isAtLeastAsQualifiedAs(other: ArgType, Ctx) && |
| 352 | (!ParamType.hasQualifiers() || IsParamContinuouslyConst); |
| 353 | // Check whether the parameter's constness continues at the current pointer |
| 354 | // level. |
| 355 | IsParamContinuouslyConst &= ParamType.isConstQualified(); |
| 356 | |
| 357 | return AreTypesQualCompatible; |
| 358 | } |
| 359 | |
| 360 | /// Checks whether multilevel pointers are compatible in terms of levels, |
| 361 | /// qualifiers and pointee type. |
| 362 | static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType, |
| 363 | bool IsParamContinuouslyConst, |
| 364 | const ASTContext &Ctx) { |
| 365 | if (!arePointersStillQualCompatible(ArgType, ParamType, |
| 366 | IsParamContinuouslyConst, Ctx)) |
| 367 | return false; |
| 368 | |
| 369 | do { |
| 370 | // Step down one pointer level. |
| 371 | ArgType = convertToPointeeOrArrayElementQualType(TypeToConvert: ArgType); |
| 372 | ParamType = convertToPointeeOrArrayElementQualType(TypeToConvert: ParamType); |
| 373 | |
| 374 | // Check whether cv-qualifiers permit compatibility on |
| 375 | // current level. |
| 376 | if (!arePointersStillQualCompatible(ArgType, ParamType, |
| 377 | IsParamContinuouslyConst, Ctx)) |
| 378 | return false; |
| 379 | |
| 380 | if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) |
| 381 | return true; |
| 382 | |
| 383 | } while (ParamType->isPointerType() && ArgType->isPointerType()); |
| 384 | // The final type does not match, or pointer levels differ. |
| 385 | return false; |
| 386 | } |
| 387 | |
| 388 | /// Checks whether ArgType converts implicitly to ParamType. |
| 389 | static bool areTypesCompatible(QualType ArgType, QualType ParamType, |
| 390 | const ASTContext &Ctx) { |
| 391 | if (ArgType.isNull() || ParamType.isNull()) |
| 392 | return false; |
| 393 | |
| 394 | ArgType = ArgType.getCanonicalType(); |
| 395 | ParamType = ParamType.getCanonicalType(); |
| 396 | |
| 397 | if (ArgType == ParamType) |
| 398 | return true; |
| 399 | |
| 400 | // Check for constness and reference compatibility. |
| 401 | if (!areRefAndQualCompatible(ArgType, ParamType, Ctx)) |
| 402 | return false; |
| 403 | |
| 404 | bool IsParamReference = ParamType->isReferenceType(); |
| 405 | |
| 406 | // Reference-ness has already been checked and should be removed |
| 407 | // before further checking. |
| 408 | ArgType = ArgType.getNonReferenceType(); |
| 409 | ParamType = ParamType.getNonReferenceType(); |
| 410 | |
| 411 | if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) |
| 412 | return true; |
| 413 | |
| 414 | // Arithmetic types are interconvertible, except scoped enums. |
| 415 | if (ParamType->isArithmeticType() && ArgType->isArithmeticType()) { |
| 416 | if ((ParamType->isEnumeralType() && |
| 417 | ParamType->castAs<EnumType>()->getDecl()->isScoped()) || |
| 418 | (ArgType->isEnumeralType() && |
| 419 | ArgType->castAs<EnumType>()->getDecl()->isScoped())) |
| 420 | return false; |
| 421 | |
| 422 | return true; |
| 423 | } |
| 424 | |
| 425 | // Check if the argument and the param are both function types (the parameter |
| 426 | // decayed to a function pointer). |
| 427 | if (ArgType->isFunctionType() && ParamType->isFunctionPointerType()) { |
| 428 | ParamType = ParamType->getPointeeType(); |
| 429 | return ArgType == ParamType; |
| 430 | } |
| 431 | |
| 432 | // Arrays or pointer arguments convert to array or pointer parameters. |
| 433 | if (!(isPointerOrArray(TypeToCheck: ArgType) && isPointerOrArray(TypeToCheck: ParamType))) |
| 434 | return false; |
| 435 | |
| 436 | // When ParamType is an array reference, ArgType has to be of the same-sized |
| 437 | // array-type with cv-compatible element type. |
| 438 | if (IsParamReference && ParamType->isArrayType()) |
| 439 | return isCompatibleWithArrayReference(ArgType, ParamType, Ctx); |
| 440 | |
| 441 | bool IsParamContinuouslyConst = |
| 442 | !IsParamReference || ParamType.getNonReferenceType().isConstQualified(); |
| 443 | |
| 444 | // Remove the first level of indirection. |
| 445 | ArgType = convertToPointeeOrArrayElementQualType(TypeToConvert: ArgType); |
| 446 | ParamType = convertToPointeeOrArrayElementQualType(TypeToConvert: ParamType); |
| 447 | |
| 448 | // Check qualifier compatibility on the next level. |
| 449 | if (!ParamType.isAtLeastAsQualifiedAs(other: ArgType, Ctx)) |
| 450 | return false; |
| 451 | |
| 452 | if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) |
| 453 | return true; |
| 454 | |
| 455 | // At this point, all possible C language implicit conversion were checked. |
| 456 | if (!Ctx.getLangOpts().CPlusPlus) |
| 457 | return false; |
| 458 | |
| 459 | // Check whether ParamType and ArgType were both pointers to a class or a |
| 460 | // struct, and check for inheritance. |
| 461 | if (ParamType->isStructureOrClassType() && |
| 462 | ArgType->isStructureOrClassType()) { |
| 463 | const auto *ArgDecl = ArgType->getAsCXXRecordDecl(); |
| 464 | const auto *ParamDecl = ParamType->getAsCXXRecordDecl(); |
| 465 | if (!ArgDecl || !ArgDecl->hasDefinition() || !ParamDecl || |
| 466 | !ParamDecl->hasDefinition()) |
| 467 | return false; |
| 468 | |
| 469 | return ArgDecl->isDerivedFrom(Base: ParamDecl); |
| 470 | } |
| 471 | |
| 472 | // Unless argument and param are both multilevel pointers, the types are not |
| 473 | // convertible. |
| 474 | if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType())) |
| 475 | return false; |
| 476 | |
| 477 | return arePointerTypesCompatible(ArgType, ParamType, IsParamContinuouslyConst, |
| 478 | Ctx); |
| 479 | } |
| 480 | |
| 481 | static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD) { |
| 482 | switch (FD->getOverloadedOperator()) { |
| 483 | case OO_None: |
| 484 | case OO_Call: |
| 485 | case OO_Subscript: |
| 486 | case OO_New: |
| 487 | case OO_Delete: |
| 488 | case OO_Array_New: |
| 489 | case OO_Array_Delete: |
| 490 | case OO_Conditional: |
| 491 | case OO_Coawait: |
| 492 | return false; |
| 493 | |
| 494 | default: |
| 495 | return FD->getNumParams() <= 2; |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | SuspiciousCallArgumentCheck::SuspiciousCallArgumentCheck( |
| 500 | StringRef Name, ClangTidyContext *Context) |
| 501 | : ClangTidyCheck(Name, Context), |
| 502 | MinimumIdentifierNameLength(Options.get( |
| 503 | LocalName: "MinimumIdentifierNameLength" , Default: DefaultMinimumIdentifierNameLength)) { |
| 504 | auto GetToggleOpt = [this](Heuristic H) -> bool { |
| 505 | auto Idx = static_cast<std::size_t>(H); |
| 506 | assert(Idx < HeuristicCount); |
| 507 | return Options.get(LocalName: HeuristicToString[Idx], Default: Defaults[Idx].Enabled); |
| 508 | }; |
| 509 | auto GetBoundOpt = [this](Heuristic H, BoundKind BK) -> int8_t { |
| 510 | auto Idx = static_cast<std::size_t>(H); |
| 511 | assert(Idx < HeuristicCount); |
| 512 | |
| 513 | SmallString<32> Key = HeuristicToString[Idx]; |
| 514 | Key.append(RHS: BK == BoundKind::DissimilarBelow ? "DissimilarBelow" |
| 515 | : "SimilarAbove" ); |
| 516 | int8_t Default = BK == BoundKind::DissimilarBelow |
| 517 | ? Defaults[Idx].DissimilarBelow |
| 518 | : Defaults[Idx].SimilarAbove; |
| 519 | return Options.get(LocalName: Key, Default); |
| 520 | }; |
| 521 | for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) { |
| 522 | auto H = static_cast<Heuristic>(Idx); |
| 523 | if (GetToggleOpt(H)) |
| 524 | AppliedHeuristics.emplace_back(Args&: H); |
| 525 | ConfiguredBounds.emplace_back( |
| 526 | Args: std::make_pair(x: GetBoundOpt(H, BoundKind::DissimilarBelow), |
| 527 | y: GetBoundOpt(H, BoundKind::SimilarAbove))); |
| 528 | } |
| 529 | |
| 530 | for (StringRef Abbreviation : optutils::parseStringList( |
| 531 | Option: Options.get(LocalName: "Abbreviations" , Default: DefaultAbbreviations))) { |
| 532 | auto KeyAndValue = Abbreviation.split(Separator: "=" ); |
| 533 | assert(!KeyAndValue.first.empty() && !KeyAndValue.second.empty()); |
| 534 | AbbreviationDictionary.insert( |
| 535 | KV: std::make_pair(x&: KeyAndValue.first, y: KeyAndValue.second.str())); |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | void SuspiciousCallArgumentCheck::storeOptions( |
| 540 | ClangTidyOptions::OptionMap &Opts) { |
| 541 | Options.store(Options&: Opts, LocalName: "MinimumIdentifierNameLength" , |
| 542 | Value: MinimumIdentifierNameLength); |
| 543 | const auto &SetToggleOpt = [this, &Opts](Heuristic H) -> void { |
| 544 | auto Idx = static_cast<std::size_t>(H); |
| 545 | Options.store(Options&: Opts, LocalName: HeuristicToString[Idx], Value: isHeuristicEnabled(H)); |
| 546 | }; |
| 547 | const auto &SetBoundOpt = [this, &Opts](Heuristic H, BoundKind BK) -> void { |
| 548 | auto Idx = static_cast<std::size_t>(H); |
| 549 | assert(Idx < HeuristicCount); |
| 550 | if (!Defaults[Idx].hasBounds()) |
| 551 | return; |
| 552 | |
| 553 | SmallString<32> Key = HeuristicToString[Idx]; |
| 554 | Key.append(RHS: BK == BoundKind::DissimilarBelow ? "DissimilarBelow" |
| 555 | : "SimilarAbove" ); |
| 556 | Options.store(Options&: Opts, LocalName: Key, Value: *getBound(H, BK)); |
| 557 | }; |
| 558 | |
| 559 | for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) { |
| 560 | auto H = static_cast<Heuristic>(Idx); |
| 561 | SetToggleOpt(H); |
| 562 | SetBoundOpt(H, BoundKind::DissimilarBelow); |
| 563 | SetBoundOpt(H, BoundKind::SimilarAbove); |
| 564 | } |
| 565 | |
| 566 | SmallVector<std::string, 32> Abbreviations; |
| 567 | for (const auto &Abbreviation : AbbreviationDictionary) { |
| 568 | SmallString<32> EqualSignJoined; |
| 569 | EqualSignJoined.append(RHS: Abbreviation.first()); |
| 570 | EqualSignJoined.append(RHS: "=" ); |
| 571 | EqualSignJoined.append(RHS: Abbreviation.second); |
| 572 | |
| 573 | if (!Abbreviation.second.empty()) |
| 574 | Abbreviations.emplace_back(Args: EqualSignJoined.str()); |
| 575 | } |
| 576 | Options.store(Options&: Opts, LocalName: "Abbreviations" , |
| 577 | Value: optutils::serializeStringList(Strings: std::vector<StringRef>( |
| 578 | Abbreviations.begin(), Abbreviations.end()))); |
| 579 | } |
| 580 | |
| 581 | bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H) const { |
| 582 | return llvm::is_contained(Range: AppliedHeuristics, Element: H); |
| 583 | } |
| 584 | |
| 585 | std::optional<int8_t> |
| 586 | SuspiciousCallArgumentCheck::getBound(Heuristic H, BoundKind BK) const { |
| 587 | auto Idx = static_cast<std::size_t>(H); |
| 588 | assert(Idx < HeuristicCount); |
| 589 | |
| 590 | if (!Defaults[Idx].hasBounds()) |
| 591 | return std::nullopt; |
| 592 | |
| 593 | switch (BK) { |
| 594 | case BoundKind::DissimilarBelow: |
| 595 | return ConfiguredBounds[Idx].first; |
| 596 | case BoundKind::SimilarAbove: |
| 597 | return ConfiguredBounds[Idx].second; |
| 598 | } |
| 599 | llvm_unreachable("Unhandled Bound kind." ); |
| 600 | } |
| 601 | |
| 602 | void SuspiciousCallArgumentCheck::registerMatchers(MatchFinder *Finder) { |
| 603 | // Only match calls with at least 2 arguments. |
| 604 | Finder->addMatcher( |
| 605 | NodeMatch: functionDecl(forEachDescendant(callExpr(unless(anyOf(argumentCountIs(N: 0), |
| 606 | argumentCountIs(N: 1)))) |
| 607 | .bind(ID: "functionCall" ))) |
| 608 | .bind(ID: "callingFunc" ), |
| 609 | Action: this); |
| 610 | } |
| 611 | |
| 612 | void SuspiciousCallArgumentCheck::check( |
| 613 | const MatchFinder::MatchResult &Result) { |
| 614 | const auto *MatchedCallExpr = |
| 615 | Result.Nodes.getNodeAs<CallExpr>(ID: "functionCall" ); |
| 616 | const auto *Caller = Result.Nodes.getNodeAs<FunctionDecl>(ID: "callingFunc" ); |
| 617 | assert(MatchedCallExpr && Caller); |
| 618 | |
| 619 | const Decl *CalleeDecl = MatchedCallExpr->getCalleeDecl(); |
| 620 | if (!CalleeDecl) |
| 621 | return; |
| 622 | |
| 623 | const FunctionDecl *CalleeFuncDecl = CalleeDecl->getAsFunction(); |
| 624 | if (!CalleeFuncDecl) |
| 625 | return; |
| 626 | if (CalleeFuncDecl == Caller) |
| 627 | // Ignore recursive calls. |
| 628 | return; |
| 629 | if (isOverloadedUnaryOrBinarySymbolOperator(FD: CalleeFuncDecl)) |
| 630 | return; |
| 631 | |
| 632 | // Get param attributes. |
| 633 | setParamNamesAndTypes(CalleeFuncDecl); |
| 634 | |
| 635 | if (ParamNames.empty()) |
| 636 | return; |
| 637 | |
| 638 | // Get Arg attributes. |
| 639 | std::size_t InitialArgIndex = 0; |
| 640 | |
| 641 | if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(Val: CalleeFuncDecl)) { |
| 642 | if (MethodDecl->getParent()->isLambda()) |
| 643 | // Lambda functions' first Arg are the lambda object. |
| 644 | InitialArgIndex = 1; |
| 645 | else if (MethodDecl->getOverloadedOperator() == OO_Call) |
| 646 | // For custom operator()s, the first Arg is the called object. |
| 647 | InitialArgIndex = 1; |
| 648 | } |
| 649 | |
| 650 | setArgNamesAndTypes(MatchedCallExpr, InitialArgIndex); |
| 651 | |
| 652 | if (ArgNames.empty()) |
| 653 | return; |
| 654 | |
| 655 | std::size_t ParamCount = ParamNames.size(); |
| 656 | |
| 657 | // Check similarity. |
| 658 | for (std::size_t I = 0; I < ParamCount; ++I) { |
| 659 | for (std::size_t J = I + 1; J < ParamCount; ++J) { |
| 660 | // Do not check if param or arg names are short, or not convertible. |
| 661 | if (!areParamAndArgComparable(Position1: I, Position2: J, Ctx: *Result.Context)) |
| 662 | continue; |
| 663 | if (!areArgsSwapped(Position1: I, Position2: J)) |
| 664 | continue; |
| 665 | |
| 666 | // Warning at the call itself. |
| 667 | diag(MatchedCallExpr->getExprLoc(), |
| 668 | "%ordinal0 argument '%1' (passed to '%2') looks like it might be " |
| 669 | "swapped with the %ordinal3, '%4' (passed to '%5')" ) |
| 670 | << static_cast<unsigned>(I + 1) << ArgNames[I] << ParamNames[I] |
| 671 | << static_cast<unsigned>(J + 1) << ArgNames[J] << ParamNames[J] |
| 672 | << MatchedCallExpr->getArg(Arg: I)->getSourceRange() |
| 673 | << MatchedCallExpr->getArg(Arg: J)->getSourceRange(); |
| 674 | |
| 675 | // Note at the functions declaration. |
| 676 | SourceLocation IParNameLoc = |
| 677 | CalleeFuncDecl->getParamDecl(i: I)->getLocation(); |
| 678 | SourceLocation JParNameLoc = |
| 679 | CalleeFuncDecl->getParamDecl(i: J)->getLocation(); |
| 680 | |
| 681 | diag(CalleeFuncDecl->getLocation(), "in the call to %0, declared here" , |
| 682 | DiagnosticIDs::Note) |
| 683 | << CalleeFuncDecl |
| 684 | << CharSourceRange::getTokenRange(B: IParNameLoc, E: IParNameLoc) |
| 685 | << CharSourceRange::getTokenRange(B: JParNameLoc, E: JParNameLoc); |
| 686 | } |
| 687 | } |
| 688 | } |
| 689 | |
| 690 | void SuspiciousCallArgumentCheck::setParamNamesAndTypes( |
| 691 | const FunctionDecl *CalleeFuncDecl) { |
| 692 | // Reset vectors, and fill them with the currently checked function's |
| 693 | // parameters' data. |
| 694 | ParamNames.clear(); |
| 695 | ParamTypes.clear(); |
| 696 | |
| 697 | for (const ParmVarDecl *Param : CalleeFuncDecl->parameters()) { |
| 698 | ParamTypes.push_back(Elt: Param->getType()); |
| 699 | |
| 700 | if (IdentifierInfo *II = Param->getIdentifier()) |
| 701 | ParamNames.push_back(Elt: II->getName()); |
| 702 | else |
| 703 | ParamNames.push_back(Elt: StringRef()); |
| 704 | } |
| 705 | } |
| 706 | |
| 707 | void SuspiciousCallArgumentCheck::setArgNamesAndTypes( |
| 708 | const CallExpr *MatchedCallExpr, std::size_t InitialArgIndex) { |
| 709 | // Reset vectors, and fill them with the currently checked function's |
| 710 | // arguments' data. |
| 711 | ArgNames.clear(); |
| 712 | ArgTypes.clear(); |
| 713 | |
| 714 | for (std::size_t I = InitialArgIndex, J = MatchedCallExpr->getNumArgs(); |
| 715 | I < J; ++I) { |
| 716 | assert(ArgTypes.size() == I - InitialArgIndex && |
| 717 | ArgNames.size() == ArgTypes.size() && |
| 718 | "Every iteration must put an element into the vectors!" ); |
| 719 | |
| 720 | if (const auto *ArgExpr = dyn_cast<DeclRefExpr>( |
| 721 | Val: MatchedCallExpr->getArg(Arg: I)->IgnoreUnlessSpelledInSource())) { |
| 722 | if (const auto *Var = dyn_cast<VarDecl>(Val: ArgExpr->getDecl())) { |
| 723 | ArgTypes.push_back(Elt: Var->getType()); |
| 724 | ArgNames.push_back(Elt: Var->getName()); |
| 725 | continue; |
| 726 | } |
| 727 | if (const auto *FCall = dyn_cast<FunctionDecl>(Val: ArgExpr->getDecl())) { |
| 728 | if (FCall->getNameInfo().getName().isIdentifier()) { |
| 729 | ArgTypes.push_back(Elt: FCall->getType()); |
| 730 | ArgNames.push_back(Elt: FCall->getName()); |
| 731 | continue; |
| 732 | } |
| 733 | } |
| 734 | } |
| 735 | |
| 736 | ArgTypes.push_back(Elt: QualType()); |
| 737 | ArgNames.push_back(Elt: StringRef()); |
| 738 | } |
| 739 | } |
| 740 | |
| 741 | bool SuspiciousCallArgumentCheck::areParamAndArgComparable( |
| 742 | std::size_t Position1, std::size_t Position2, const ASTContext &Ctx) const { |
| 743 | if (Position1 >= ArgNames.size() || Position2 >= ArgNames.size()) |
| 744 | return false; |
| 745 | |
| 746 | // Do not report for too short strings. |
| 747 | if (ArgNames[Position1].size() < MinimumIdentifierNameLength || |
| 748 | ArgNames[Position2].size() < MinimumIdentifierNameLength || |
| 749 | ParamNames[Position1].size() < MinimumIdentifierNameLength || |
| 750 | ParamNames[Position2].size() < MinimumIdentifierNameLength) |
| 751 | return false; |
| 752 | |
| 753 | if (!areTypesCompatible(ArgType: ArgTypes[Position1], ParamType: ParamTypes[Position2], Ctx) || |
| 754 | !areTypesCompatible(ArgType: ArgTypes[Position2], ParamType: ParamTypes[Position1], Ctx)) |
| 755 | return false; |
| 756 | |
| 757 | return true; |
| 758 | } |
| 759 | |
| 760 | bool SuspiciousCallArgumentCheck::areArgsSwapped(std::size_t Position1, |
| 761 | std::size_t Position2) const { |
| 762 | for (Heuristic H : AppliedHeuristics) { |
| 763 | bool A1ToP2Similar = areNamesSimilar( |
| 764 | Arg: ArgNames[Position2], Param: ParamNames[Position1], H, BK: BoundKind::SimilarAbove); |
| 765 | bool A2ToP1Similar = areNamesSimilar( |
| 766 | Arg: ArgNames[Position1], Param: ParamNames[Position2], H, BK: BoundKind::SimilarAbove); |
| 767 | |
| 768 | bool A1ToP1Dissimilar = |
| 769 | !areNamesSimilar(Arg: ArgNames[Position1], Param: ParamNames[Position1], H, |
| 770 | BK: BoundKind::DissimilarBelow); |
| 771 | bool A2ToP2Dissimilar = |
| 772 | !areNamesSimilar(Arg: ArgNames[Position2], Param: ParamNames[Position2], H, |
| 773 | BK: BoundKind::DissimilarBelow); |
| 774 | |
| 775 | if ((A1ToP2Similar || A2ToP1Similar) && A1ToP1Dissimilar && |
| 776 | A2ToP2Dissimilar) |
| 777 | return true; |
| 778 | } |
| 779 | return false; |
| 780 | } |
| 781 | |
| 782 | bool SuspiciousCallArgumentCheck::areNamesSimilar(StringRef Arg, |
| 783 | StringRef Param, Heuristic H, |
| 784 | BoundKind BK) const { |
| 785 | int8_t Threshold = -1; |
| 786 | if (std::optional<int8_t> GotBound = getBound(H, BK)) |
| 787 | Threshold = *GotBound; |
| 788 | |
| 789 | switch (H) { |
| 790 | case Heuristic::Equality: |
| 791 | return applyEqualityHeuristic(Arg, Param); |
| 792 | case Heuristic::Abbreviation: |
| 793 | return applyAbbreviationHeuristic(AbbreviationDictionary, Arg, Param); |
| 794 | case Heuristic::Prefix: |
| 795 | return applyPrefixHeuristic(Arg, Param, Threshold); |
| 796 | case Heuristic::Suffix: |
| 797 | return applySuffixHeuristic(Arg, Param, Threshold); |
| 798 | case Heuristic::Substring: |
| 799 | return applySubstringHeuristic(Arg, Param, Threshold); |
| 800 | case Heuristic::Levenshtein: |
| 801 | return applyLevenshteinHeuristic(Arg, Param, Threshold); |
| 802 | case Heuristic::JaroWinkler: |
| 803 | return applyJaroWinklerHeuristic(Arg, Param, Threshold); |
| 804 | case Heuristic::Dice: |
| 805 | return applyDiceHeuristic(Arg, Param, Threshold); |
| 806 | } |
| 807 | llvm_unreachable("Unhandled heuristic kind" ); |
| 808 | } |
| 809 | |
| 810 | } // namespace clang::tidy::readability |
| 811 | |