clang-tools 19.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.equals(AbbreviationDictionary.lookup(Arg)))
142 return true;
143
144 if (AbbreviationDictionary.contains(Param) &&
145 Arg.equals(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 return !ParamType->isReferenceType() ||
304 ParamType.getNonReferenceType().isAtLeastAsQualifiedAs(
305 ArgType.getNonReferenceType());
306}
307
308static bool isPointerOrArray(QualType TypeToCheck) {
309 return TypeToCheck->isPointerType() || TypeToCheck->isArrayType();
310}
311
312/// Checks whether ArgType is an array type identical to ParamType's array type.
313/// Enforces array elements' qualifier compatibility as well.
314static bool isCompatibleWithArrayReference(QualType ArgType,
315 QualType ParamType) {
316 if (!ArgType->isArrayType())
317 return false;
318 // Here, qualifiers belong to the elements of the arrays.
319 if (!ParamType.isAtLeastAsQualifiedAs(ArgType))
320 return false;
321
322 return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType();
323}
324
325static QualType convertToPointeeOrArrayElementQualType(QualType TypeToConvert) {
326 unsigned CVRqualifiers = 0;
327 // Save array element qualifiers, since getElementType() removes qualifiers
328 // from array elements.
329 if (TypeToConvert->isArrayType())
330 CVRqualifiers = TypeToConvert.getLocalQualifiers().getCVRQualifiers();
331 TypeToConvert = TypeToConvert->isPointerType()
332 ? TypeToConvert->getPointeeType()
333 : TypeToConvert->getAsArrayTypeUnsafe()->getElementType();
334 TypeToConvert = TypeToConvert.withCVRQualifiers(CVRqualifiers);
335 return TypeToConvert;
336}
337
338/// Checks if multilevel pointers' qualifiers compatibility continues on the
339/// current pointer level. For multilevel pointers, C++ permits conversion, if
340/// every cv-qualifier in ArgType also appears in the corresponding position in
341/// ParamType, and if PramType has a cv-qualifier that's not in ArgType, then
342/// every * in ParamType to the right of that cv-qualifier, except the last
343/// one, must also be const-qualified.
344static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType,
345 bool &IsParamContinuouslyConst) {
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 bool AreTypesQualCompatible =
350 ParamType.isAtLeastAsQualifiedAs(ArgType) &&
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 if (!arePointersStillQualCompatible(ArgType, ParamType,
364 IsParamContinuouslyConst))
365 return false;
366
367 do {
368 // Step down one pointer level.
370 ParamType = convertToPointeeOrArrayElementQualType(ParamType);
371
372 // Check whether cv-qualifiers permit compatibility on
373 // current level.
374 if (!arePointersStillQualCompatible(ArgType, ParamType,
375 IsParamContinuouslyConst))
376 return false;
377
378 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
379 return true;
380
381 } while (ParamType->isPointerType() && ArgType->isPointerType());
382 // The final type does not match, or pointer levels differ.
383 return false;
384}
385
386/// Checks whether ArgType converts implicitly to ParamType.
387static bool areTypesCompatible(QualType ArgType, QualType ParamType,
388 const ASTContext &Ctx) {
389 if (ArgType.isNull() || ParamType.isNull())
390 return false;
391
392 ArgType = ArgType.getCanonicalType();
393 ParamType = ParamType.getCanonicalType();
394
395 if (ArgType == ParamType)
396 return true;
397
398 // Check for constness and reference compatibility.
399 if (!areRefAndQualCompatible(ArgType, ParamType))
400 return false;
401
402 bool IsParamReference = ParamType->isReferenceType();
403
404 // Reference-ness has already been checked and should be removed
405 // before further checking.
406 ArgType = ArgType.getNonReferenceType();
407 ParamType = ParamType.getNonReferenceType();
408
409 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
410 return true;
411
412 // Arithmetic types are interconvertible, except scoped enums.
413 if (ParamType->isArithmeticType() && ArgType->isArithmeticType()) {
414 if ((ParamType->isEnumeralType() &&
415 ParamType->castAs<EnumType>()->getDecl()->isScoped()) ||
416 (ArgType->isEnumeralType() &&
417 ArgType->castAs<EnumType>()->getDecl()->isScoped()))
418 return false;
419
420 return true;
421 }
422
423 // Check if the argument and the param are both function types (the parameter
424 // decayed to a function pointer).
425 if (ArgType->isFunctionType() && ParamType->isFunctionPointerType()) {
426 ParamType = ParamType->getPointeeType();
427 return ArgType == ParamType;
428 }
429
430 // Arrays or pointer arguments convert to array or pointer parameters.
431 if (!(isPointerOrArray(ArgType) && isPointerOrArray(ParamType)))
432 return false;
433
434 // When ParamType is an array reference, ArgType has to be of the same-sized
435 // array-type with cv-compatible element type.
436 if (IsParamReference && ParamType->isArrayType())
437 return isCompatibleWithArrayReference(ArgType, ParamType);
438
439 bool IsParamContinuouslyConst =
440 !IsParamReference || ParamType.getNonReferenceType().isConstQualified();
441
442 // Remove the first level of indirection.
444 ParamType = convertToPointeeOrArrayElementQualType(ParamType);
445
446 // Check qualifier compatibility on the next level.
447 if (!ParamType.isAtLeastAsQualifiedAs(ArgType))
448 return false;
449
450 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
451 return true;
452
453 // At this point, all possible C language implicit conversion were checked.
454 if (!Ctx.getLangOpts().CPlusPlus)
455 return false;
456
457 // Check whether ParamType and ArgType were both pointers to a class or a
458 // struct, and check for inheritance.
459 if (ParamType->isStructureOrClassType() &&
460 ArgType->isStructureOrClassType()) {
461 const auto *ArgDecl = ArgType->getAsCXXRecordDecl();
462 const auto *ParamDecl = ParamType->getAsCXXRecordDecl();
463 if (!ArgDecl || !ArgDecl->hasDefinition() || !ParamDecl ||
464 !ParamDecl->hasDefinition())
465 return false;
466
467 return ArgDecl->isDerivedFrom(ParamDecl);
468 }
469
470 // Unless argument and param are both multilevel pointers, the types are not
471 // convertible.
472 if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType()))
473 return false;
474
475 return arePointerTypesCompatible(ArgType, ParamType,
476 IsParamContinuouslyConst);
477}
478
479static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD) {
480 switch (FD->getOverloadedOperator()) {
481 case OO_None:
482 case OO_Call:
483 case OO_Subscript:
484 case OO_New:
485 case OO_Delete:
486 case OO_Array_New:
487 case OO_Array_Delete:
488 case OO_Conditional:
489 case OO_Coawait:
490 return false;
491
492 default:
493 return FD->getNumParams() <= 2;
494 }
495}
496
498 StringRef Name, ClangTidyContext *Context)
499 : ClangTidyCheck(Name, Context),
500 MinimumIdentifierNameLength(Options.get(
501 "MinimumIdentifierNameLength", DefaultMinimumIdentifierNameLength)) {
502 auto GetToggleOpt = [this](Heuristic H) -> bool {
503 auto Idx = static_cast<std::size_t>(H);
504 assert(Idx < HeuristicCount);
505 return Options.get(HeuristicToString[Idx], Defaults[Idx].Enabled);
506 };
507 auto GetBoundOpt = [this](Heuristic H, BoundKind BK) -> int8_t {
508 auto Idx = static_cast<std::size_t>(H);
509 assert(Idx < HeuristicCount);
510
511 SmallString<32> Key = HeuristicToString[Idx];
512 Key.append(BK == BoundKind::DissimilarBelow ? "DissimilarBelow"
513 : "SimilarAbove");
514 int8_t Default = BK == BoundKind::DissimilarBelow
515 ? Defaults[Idx].DissimilarBelow
516 : Defaults[Idx].SimilarAbove;
517 return Options.get(Key, Default);
518 };
519 for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) {
520 auto H = static_cast<Heuristic>(Idx);
521 if (GetToggleOpt(H))
522 AppliedHeuristics.emplace_back(H);
523 ConfiguredBounds.emplace_back(
524 std::make_pair(GetBoundOpt(H, BoundKind::DissimilarBelow),
525 GetBoundOpt(H, BoundKind::SimilarAbove)));
526 }
527
528 for (StringRef Abbreviation : optutils::parseStringList(
529 Options.get("Abbreviations", DefaultAbbreviations))) {
530 auto KeyAndValue = Abbreviation.split("=");
531 assert(!KeyAndValue.first.empty() && !KeyAndValue.second.empty());
532 AbbreviationDictionary.insert(
533 std::make_pair(KeyAndValue.first, KeyAndValue.second.str()));
534 }
535}
536
539 Options.store(Opts, "MinimumIdentifierNameLength",
540 MinimumIdentifierNameLength);
541 const auto &SetToggleOpt = [this, &Opts](Heuristic H) -> void {
542 auto Idx = static_cast<std::size_t>(H);
543 Options.store(Opts, HeuristicToString[Idx], isHeuristicEnabled(H));
544 };
545 const auto &SetBoundOpt = [this, &Opts](Heuristic H, BoundKind BK) -> void {
546 auto Idx = static_cast<std::size_t>(H);
547 assert(Idx < HeuristicCount);
548 if (!Defaults[Idx].hasBounds())
549 return;
550
551 SmallString<32> Key = HeuristicToString[Idx];
552 Key.append(BK == BoundKind::DissimilarBelow ? "DissimilarBelow"
553 : "SimilarAbove");
554 Options.store(Opts, Key, *getBound(H, BK));
555 };
556
557 for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) {
558 auto H = static_cast<Heuristic>(Idx);
559 SetToggleOpt(H);
560 SetBoundOpt(H, BoundKind::DissimilarBelow);
561 SetBoundOpt(H, BoundKind::SimilarAbove);
562 }
563
564 SmallVector<std::string, 32> Abbreviations;
565 for (const auto &Abbreviation : AbbreviationDictionary) {
566 SmallString<32> EqualSignJoined;
567 EqualSignJoined.append(Abbreviation.first());
568 EqualSignJoined.append("=");
569 EqualSignJoined.append(Abbreviation.second);
570
571 if (!Abbreviation.second.empty())
572 Abbreviations.emplace_back(EqualSignJoined.str());
573 }
574 Options.store(Opts, "Abbreviations",
575 optutils::serializeStringList(std::vector<StringRef>(
576 Abbreviations.begin(), Abbreviations.end())));
577}
578
579bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H) const {
580 return llvm::is_contained(AppliedHeuristics, H);
581}
582
583std::optional<int8_t>
584SuspiciousCallArgumentCheck::getBound(Heuristic H, BoundKind BK) const {
585 auto Idx = static_cast<std::size_t>(H);
586 assert(Idx < HeuristicCount);
587
588 if (!Defaults[Idx].hasBounds())
589 return std::nullopt;
590
591 switch (BK) {
592 case BoundKind::DissimilarBelow:
593 return ConfiguredBounds[Idx].first;
594 case BoundKind::SimilarAbove:
595 return ConfiguredBounds[Idx].second;
596 }
597 llvm_unreachable("Unhandled Bound kind.");
598}
599
601 // Only match calls with at least 2 arguments.
602 Finder->addMatcher(
603 functionDecl(forEachDescendant(callExpr(unless(anyOf(argumentCountIs(0),
604 argumentCountIs(1))))
605 .bind("functionCall")))
606 .bind("callingFunc"),
607 this);
608}
609
611 const MatchFinder::MatchResult &Result) {
612 const auto *MatchedCallExpr =
613 Result.Nodes.getNodeAs<CallExpr>("functionCall");
614 const auto *Caller = Result.Nodes.getNodeAs<FunctionDecl>("callingFunc");
615 assert(MatchedCallExpr && Caller);
616
617 const Decl *CalleeDecl = MatchedCallExpr->getCalleeDecl();
618 if (!CalleeDecl)
619 return;
620
621 const FunctionDecl *CalleeFuncDecl = CalleeDecl->getAsFunction();
622 if (!CalleeFuncDecl)
623 return;
624 if (CalleeFuncDecl == Caller)
625 // Ignore recursive calls.
626 return;
627 if (isOverloadedUnaryOrBinarySymbolOperator(CalleeFuncDecl))
628 return;
629
630 // Get param attributes.
631 setParamNamesAndTypes(CalleeFuncDecl);
632
633 if (ParamNames.empty())
634 return;
635
636 // Get Arg attributes.
637 std::size_t InitialArgIndex = 0;
638
639 if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(CalleeFuncDecl)) {
640 if (MethodDecl->getParent()->isLambda())
641 // Lambda functions' first Arg are the lambda object.
642 InitialArgIndex = 1;
643 else if (MethodDecl->getOverloadedOperator() == OO_Call)
644 // For custom operator()s, the first Arg is the called object.
645 InitialArgIndex = 1;
646 }
647
648 setArgNamesAndTypes(MatchedCallExpr, InitialArgIndex);
649
650 if (ArgNames.empty())
651 return;
652
653 std::size_t ParamCount = ParamNames.size();
654
655 // Check similarity.
656 for (std::size_t I = 0; I < ParamCount; ++I) {
657 for (std::size_t J = I + 1; J < ParamCount; ++J) {
658 // Do not check if param or arg names are short, or not convertible.
659 if (!areParamAndArgComparable(I, J, *Result.Context))
660 continue;
661 if (!areArgsSwapped(I, J))
662 continue;
663
664 // Warning at the call itself.
665 diag(MatchedCallExpr->getExprLoc(),
666 "%ordinal0 argument '%1' (passed to '%2') looks like it might be "
667 "swapped with the %ordinal3, '%4' (passed to '%5')")
668 << static_cast<unsigned>(I + 1) << ArgNames[I] << ParamNames[I]
669 << static_cast<unsigned>(J + 1) << ArgNames[J] << ParamNames[J]
670 << MatchedCallExpr->getArg(I)->getSourceRange()
671 << MatchedCallExpr->getArg(J)->getSourceRange();
672
673 // Note at the functions declaration.
674 SourceLocation IParNameLoc =
675 CalleeFuncDecl->getParamDecl(I)->getLocation();
676 SourceLocation JParNameLoc =
677 CalleeFuncDecl->getParamDecl(J)->getLocation();
678
679 diag(CalleeFuncDecl->getLocation(), "in the call to %0, declared here",
680 DiagnosticIDs::Note)
681 << CalleeFuncDecl
682 << CharSourceRange::getTokenRange(IParNameLoc, IParNameLoc)
683 << CharSourceRange::getTokenRange(JParNameLoc, JParNameLoc);
684 }
685 }
686}
687
688void SuspiciousCallArgumentCheck::setParamNamesAndTypes(
689 const FunctionDecl *CalleeFuncDecl) {
690 // Reset vectors, and fill them with the currently checked function's
691 // parameters' data.
692 ParamNames.clear();
693 ParamTypes.clear();
694
695 for (const ParmVarDecl *Param : CalleeFuncDecl->parameters()) {
696 ParamTypes.push_back(Param->getType());
697
698 if (IdentifierInfo *II = Param->getIdentifier())
699 ParamNames.push_back(II->getName());
700 else
701 ParamNames.push_back(StringRef());
702 }
703}
704
705void SuspiciousCallArgumentCheck::setArgNamesAndTypes(
706 const CallExpr *MatchedCallExpr, std::size_t InitialArgIndex) {
707 // Reset vectors, and fill them with the currently checked function's
708 // arguments' data.
709 ArgNames.clear();
710 ArgTypes.clear();
711
712 for (std::size_t I = InitialArgIndex, J = MatchedCallExpr->getNumArgs();
713 I < J; ++I) {
714 assert(ArgTypes.size() == I - InitialArgIndex &&
715 ArgNames.size() == ArgTypes.size() &&
716 "Every iteration must put an element into the vectors!");
717
718 if (const auto *ArgExpr = dyn_cast<DeclRefExpr>(
719 MatchedCallExpr->getArg(I)->IgnoreUnlessSpelledInSource())) {
720 if (const auto *Var = dyn_cast<VarDecl>(ArgExpr->getDecl())) {
721 ArgTypes.push_back(Var->getType());
722 ArgNames.push_back(Var->getName());
723 continue;
724 }
725 if (const auto *FCall = dyn_cast<FunctionDecl>(ArgExpr->getDecl())) {
726 if (FCall->getNameInfo().getName().isIdentifier()) {
727 ArgTypes.push_back(FCall->getType());
728 ArgNames.push_back(FCall->getName());
729 continue;
730 }
731 }
732 }
733
734 ArgTypes.push_back(QualType());
735 ArgNames.push_back(StringRef());
736 }
737}
738
739bool SuspiciousCallArgumentCheck::areParamAndArgComparable(
740 std::size_t Position1, std::size_t Position2, const ASTContext &Ctx) const {
741 if (Position1 >= ArgNames.size() || Position2 >= ArgNames.size())
742 return false;
743
744 // Do not report for too short strings.
745 if (ArgNames[Position1].size() < MinimumIdentifierNameLength ||
746 ArgNames[Position2].size() < MinimumIdentifierNameLength ||
747 ParamNames[Position1].size() < MinimumIdentifierNameLength ||
748 ParamNames[Position2].size() < MinimumIdentifierNameLength)
749 return false;
750
751 if (!areTypesCompatible(ArgTypes[Position1], ParamTypes[Position2], Ctx) ||
752 !areTypesCompatible(ArgTypes[Position2], ParamTypes[Position1], Ctx))
753 return false;
754
755 return true;
756}
757
758bool SuspiciousCallArgumentCheck::areArgsSwapped(std::size_t Position1,
759 std::size_t Position2) const {
760 for (Heuristic H : AppliedHeuristics) {
761 bool A1ToP2Similar = areNamesSimilar(
762 ArgNames[Position2], ParamNames[Position1], H, BoundKind::SimilarAbove);
763 bool A2ToP1Similar = areNamesSimilar(
764 ArgNames[Position1], ParamNames[Position2], H, BoundKind::SimilarAbove);
765
766 bool A1ToP1Dissimilar =
767 !areNamesSimilar(ArgNames[Position1], ParamNames[Position1], H,
768 BoundKind::DissimilarBelow);
769 bool A2ToP2Dissimilar =
770 !areNamesSimilar(ArgNames[Position2], ParamNames[Position2], H,
771 BoundKind::DissimilarBelow);
772
773 if ((A1ToP2Similar || A2ToP1Similar) && A1ToP1Dissimilar &&
774 A2ToP2Dissimilar)
775 return true;
776 }
777 return false;
778}
779
780bool SuspiciousCallArgumentCheck::areNamesSimilar(StringRef Arg,
781 StringRef Param, Heuristic H,
782 BoundKind BK) const {
783 int8_t Threshold = -1;
784 if (std::optional<int8_t> GotBound = getBound(H, BK))
785 Threshold = *GotBound;
786
787 switch (H) {
788 case Heuristic::Equality:
789 return applyEqualityHeuristic(Arg, Param);
790 case Heuristic::Abbreviation:
791 return applyAbbreviationHeuristic(AbbreviationDictionary, Arg, Param);
792 case Heuristic::Prefix:
793 return applyPrefixHeuristic(Arg, Param, Threshold);
794 case Heuristic::Suffix:
795 return applySuffixHeuristic(Arg, Param, Threshold);
796 case Heuristic::Substring:
797 return applySubstringHeuristic(Arg, Param, Threshold);
798 case Heuristic::Levenshtein:
799 return applyLevenshteinHeuristic(Arg, Param, Threshold);
800 case Heuristic::JaroWinkler:
801 return applyJaroWinklerHeuristic(Arg, Param, Threshold);
802 case Heuristic::Dice:
803 return applyDiceHeuristic(Arg, Param, Threshold);
804 }
805 llvm_unreachable("Unhandled heuristic kind");
806}
807
808} // 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 constexpr llvm::StringLiteral DefaultAbbreviations
static bool applyDiceHeuristic(StringRef Arg, StringRef Param, int8_t Threshold)
static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType, bool &IsParamContinuouslyConst)
Checks if multilevel pointers' qualifiers compatibility continues on the current pointer level.
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 arePointerTypesCompatible(QualType ArgType, QualType ParamType, bool IsParamContinuouslyConst)
Checks whether multilevel pointers are compatible in terms of levels, qualifiers and pointee type.
static bool isCompatibleWithArrayReference(QualType ArgType, QualType ParamType)
Checks whether ArgType is an array type identical to ParamType's array type.
static bool areTypesCompatible(QualType ArgType, QualType ParamType, const ASTContext &Ctx)
Checks whether ArgType converts implicitly to ParamType.
static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType)
Checks if ArgType binds to ParamType regarding reference-ness and cv-qualifiers.
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 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