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