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