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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp