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 | |
16 | using namespace clang::ast_matchers; |
17 | namespace optutils = clang::tidy::utils::options; |
18 | |
19 | namespace clang::tidy::readability { |
20 | |
21 | namespace { |
22 | struct 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 | |
43 | static constexpr std::size_t DefaultMinimumIdentifierNameLength = 3; |
44 | |
45 | static constexpr StringRef HeuristicToString[] = { |
46 | "Equality", "Abbreviation", "Prefix", "Suffix", |
47 | "Substring", "Levenshtein", "JaroWinkler", "Dice"}; |
48 | |
49 | static 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 | |
60 | static_assert( |
61 | sizeof(HeuristicToString) / sizeof(HeuristicToString[0]) == |
62 | SuspiciousCallArgumentCheck::HeuristicCount, |
63 | "Ensure that every heuristic has a corresponding stringified name"); |
64 | static_assert(sizeof(Defaults) / sizeof(Defaults[0]) == |
65 | SuspiciousCallArgumentCheck::HeuristicCount, |
66 | "Ensure that every heuristic has a default configuration."); |
67 | |
68 | namespace { |
69 | template <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 | |
76 | template <std::size_t I> struct HasWellConfiguredBoundsFold { |
77 | static constexpr bool Value = HasWellConfiguredBounds<I>::Value && |
78 | HasWellConfiguredBoundsFold<I - 1>::Value; |
79 | }; |
80 | |
81 | template <> struct HasWellConfiguredBoundsFold<0> { |
82 | static constexpr bool Value = HasWellConfiguredBounds<0>::Value; |
83 | }; |
84 | |
85 | struct AllHeuristicsBoundsWellConfigured { |
86 | static constexpr bool Value = |
87 | HasWellConfiguredBoundsFold<SuspiciousCallArgumentCheck::HeuristicCount - |
88 | 1>::Value; |
89 | }; |
90 | |
91 | static_assert(AllHeuristicsBoundsWellConfigured::Value); |
92 | } // namespace |
93 | |
94 | static 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 | |
126 | static constexpr std::size_t SmallVectorSize = |
127 | SuspiciousCallArgumentCheck::SmallVectorSize; |
128 | |
129 | /// Returns how many % X is of Y. |
130 | static inline double percentage(double X, double Y) { return X / Y * 100.0; } |
131 | |
132 | static bool applyEqualityHeuristic(StringRef Arg, StringRef Param) { |
133 | return Arg.equals_insensitive(RHS: Param); |
134 | } |
135 | |
136 | static 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. |
151 | static 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. |
163 | static 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 | |
174 | static 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 | |
203 | static 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. |
212 | static 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 |
273 | static 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. |
301 | static 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 | |
308 | static 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. |
314 | static 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 | |
325 | static 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. |
344 | static 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. |
362 | static 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. |
389 | static bool areTypesCompatible(QualType ArgType, QualType ParamType, |
390 | const ASTContext &Ctx) { |
391 | if (ArgType.isNull() || ParamType.isNull()) |
392 | return false; |
393 | |
394 | ArgType = ArgType.getCanonicalType(); |
395 | ParamType = ParamType.getCanonicalType(); |
396 | |
397 | if (ArgType == ParamType) |
398 | return true; |
399 | |
400 | // Check for constness and reference compatibility. |
401 | if (!areRefAndQualCompatible(ArgType, ParamType, 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 | |
481 | static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD) { |
482 | switch (FD->getOverloadedOperator()) { |
483 | case OO_None: |
484 | case OO_Call: |
485 | case OO_Subscript: |
486 | case OO_New: |
487 | case OO_Delete: |
488 | case OO_Array_New: |
489 | case OO_Array_Delete: |
490 | case OO_Conditional: |
491 | case OO_Coawait: |
492 | return false; |
493 | |
494 | default: |
495 | return FD->getNumParams() <= 2; |
496 | } |
497 | } |
498 | |
499 | SuspiciousCallArgumentCheck::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 | |
539 | void 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 | |
581 | bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H) const { |
582 | return llvm::is_contained(Range: AppliedHeuristics, Element: H); |
583 | } |
584 | |
585 | std::optional<int8_t> |
586 | SuspiciousCallArgumentCheck::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 | |
602 | void 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 | |
612 | void 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 | |
690 | void 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 | |
707 | void 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 | |
741 | bool 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 | |
760 | bool 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 | |
782 | bool 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 |
Definitions
- DefaultHeuristicConfiguration
- hasBounds
- DefaultMinimumIdentifierNameLength
- HeuristicToString
- Defaults
- HasWellConfiguredBounds
- Value
- HasWellConfiguredBoundsFold
- Value
- HasWellConfiguredBoundsFold
- Value
- AllHeuristicsBoundsWellConfigured
- Value
- DefaultAbbreviations
- SmallVectorSize
- percentage
- applyEqualityHeuristic
- applyAbbreviationHeuristic
- applyPrefixHeuristic
- applySuffixHeuristic
- applySubstringHeuristic
- applyLevenshteinHeuristic
- applyJaroWinklerHeuristic
- applyDiceHeuristic
- areRefAndQualCompatible
- isPointerOrArray
- isCompatibleWithArrayReference
- convertToPointeeOrArrayElementQualType
- arePointersStillQualCompatible
- arePointerTypesCompatible
- areTypesCompatible
- isOverloadedUnaryOrBinarySymbolOperator
- SuspiciousCallArgumentCheck
- storeOptions
- isHeuristicEnabled
- getBound
- registerMatchers
- check
- setParamNamesAndTypes
- setArgNamesAndTypes
- areParamAndArgComparable
- areArgsSwapped
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more