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