whisperity created this revision.
whisperity added reviewers: aaron.ballman, alexfh, hokein, JonasToth.
whisperity added projects: clang, clang-tools-extra.
Herald added subscribers: gamesh411, Szelethus, rnkovacs, xazax.hun.
whisperity requested review of this revision.

While the original check's (D69560 <https://reviews.llvm.org/D69560>) purpose 
is to identify potentially dangerous functions based on the parameter types (as 
identifier names do not mean //anything// when it comes to the language rules), 
unfortunately, such a plain interface check rule can be incredibly noisy. While 
the previous //"filtering heuristic"// in D78652 
<https://reviews.llvm.org/D78652> is able to find many similar usages, there is 
an entire class of parameters that should not be warned about very easily mixed 
by that check: parameters that have a name and their name follows a pattern, 
e.g. `text1, text2, text3, ...`. [1]

This patch implements a simple, but powerful rule, that allows us to detect 
such cases and ensure that no warnings are emitted for parameter sequences that 
follow a pattern, even if their types allow for them to be potentially mixed at 
a call site.

Given a threshold `k`, warnings about two parameters are filtered from the 
result set if the names of the parameters are either prefixes or suffixes of 
each other, with at most `k` letters difference on the non-common end. 
(Assuming that the names themselves are at least `k` long.)

The above `text1, text2` is an example of this. (Live finding from Xerces.)
`LHS` and `RHS` are also fitting the bill here. (Live finding from... virtually 
any project.)
So does `Qmat, Tmat, Rmat`. (Live finding from I think OpenCV.)

On average, **15%** reduction in findings were achieved with this feature 
turned on.

Similarly to the other filtering patch on the "relatedness modelling", this 
option is implemented as a check option, 
`NamePrefixSuffixSilenceDissimilarityTreshold`. If `0`, the heuristic is turned 
off. **Defaults to `1`**, i.e. a single letter difference ("pattern") on either 
end of the parameter name suffice for silence. This is the most reasonable 
default.

----

[1] It has been identified before by other research that such patterned names 
carry no viable information and such names are useless for a name-based 
analysis where argument vs. parameter names are contrasted with one another. 
[Rice2017]

//[Rice2017]//: Andrew Rice, et al.: Detecting argument selection defects. In: 
Proceedings of the ACM on Programming Languages, 1, pp. 104:1-104:23, 2017.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D97297

Files:
  clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp
  clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.h
  
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-ignore.cpp
  
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp
  
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len3.cpp
  
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-prefixsuffixname.cpp
  
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters.c

Index: clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters.c
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters.c
+++ clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters.c
@@ -2,7 +2,8 @@
 // RUN:   -config='{CheckOptions: [ \
 // RUN:     {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
 // RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
-// RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: "bool;MyBool;struct U;MAKE_LOGICAL_TYPE(int)"} \
+// RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: "bool;MyBool;struct U;MAKE_LOGICAL_TYPE(int)"}, \
+// RUN:     {key: bugprone-easily-swappable-parameters.NamePrefixSuffixSilenceDissimilarityTreshold, value: 0} \
 // RUN:  ]}' -- -x c
 
 #define bool _Bool
Index: clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-prefixsuffixname.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-prefixsuffixname.cpp
@@ -0,0 +1,53 @@
+// RUN: %check_clang_tidy %s bugprone-easily-swappable-parameters %t \
+// RUN:   -config='{CheckOptions: [ \
+// RUN:     {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
+// RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
+// RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""}, \
+// RUN:     {key: bugprone-easily-swappable-parameters.NamePrefixSuffixSilenceDissimilarityTreshold, value: 1} \
+// RUN:  ]}' --
+
+namespace std {
+struct string {};
+} // namespace std
+class Matrix {};
+
+void test1(int Foo, int Bar) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: 2 adjacent parameters of 'test1' of similar type ('int') are easily swapped by mistake [bugprone-easily-swappable-parameters]
+// CHECK-MESSAGES: :[[@LINE-2]]:16: note: the first parameter in the range is 'Foo'
+// CHECK-MESSAGES: :[[@LINE-3]]:25: note: the last parameter in the range is 'Bar'
+
+void test2(int A, int B) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: 2 adjacent parameters of 'test2' of similar type ('int')
+// CHECK-MESSAGES: :[[@LINE-2]]:16: note: the first parameter in the range is 'A'
+// CHECK-MESSAGES: :[[@LINE-3]]:23: note: the last parameter in the range is 'B'
+
+void test3(int Val1, int Val2) {} // NO-WARN.
+
+void test4(int ValA, int Valb) {} // NO-WARN.
+
+void test5(int Val1, int ValZ) {} // NO-WARN.
+
+void test6(int PValue, int QValue) {} // NO-WARN.
+
+void test7(std::string Astr, std::string Bstr) {} // NO-WARN.
+
+void test8(int Aladdin, int Alabaster) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: 2 adjacent parameters of 'test8' of similar type ('int')
+// CHECK-MESSAGES: :[[@LINE-2]]:16: note: the first parameter in the range is 'Aladdin'
+// CHECK-MESSAGES: :[[@LINE-3]]:29: note: the last parameter in the range is 'Alabaster'
+
+void test9(Matrix Qmat, Matrix Rmat, Matrix Tmat) {} // NO-WARN.
+
+void test10(int Something, int Other, int Foo, int Bar1, int Bar2, int Baz, int Qux) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: 4 adjacent parameters of 'test10' of similar type ('int') are
+// CHECK-MESSAGES: :[[@LINE-2]]:17: note: the first parameter in the range is 'Something'
+// CHECK-MESSAGES: :[[@LINE-3]]:52: note: the last parameter in the range is 'Bar1'
+//
+// CHECK-MESSAGES: :[[@LINE-5]]:58: warning: 3 adjacent parameters of 'test10' of similar type ('int') are
+// CHECK-MESSAGES: :[[@LINE-6]]:62: note: the first parameter in the range is 'Bar2'
+// CHECK-MESSAGES: :[[@LINE-7]]:81: note: the last parameter in the range is 'Qux'
+
+void test11(int Foobar, int Foo) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: 2 adjacent parameters of 'test11' of similar type ('int')
+// CHECK-MESSAGES: :[[@LINE-2]]:17: note: the first parameter in the range is 'Foobar'
+// CHECK-MESSAGES: :[[@LINE-3]]:29: note: the last parameter in the range is 'Foo'
Index: clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len3.cpp
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len3.cpp
+++ clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len3.cpp
@@ -2,7 +2,8 @@
 // RUN:   -config='{CheckOptions: [ \
 // RUN:     {key: bugprone-easily-swappable-parameters.MinimumLength, value: 3}, \
 // RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
-// RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""} \
+// RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""}, \
+// RUN:     {key: bugprone-easily-swappable-parameters.NamePrefixSuffixSilenceDissimilarityTreshold, value: 0} \
 // RUN:  ]}' --
 
 int add(int Left, int Right) { return Left + Right; } // NO-WARN: Only 2 parameters.
Index: clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp
+++ clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp
@@ -2,7 +2,8 @@
 // RUN:   -config='{CheckOptions: [ \
 // RUN:     {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
 // RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
-// RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""} \
+// RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""}, \
+// RUN:     {key: bugprone-easily-swappable-parameters.NamePrefixSuffixSilenceDissimilarityTreshold, value: 0} \
 // RUN:  ]}' --
 
 #define assert(X) ((void)(X))
Index: clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-ignore.cpp
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-ignore.cpp
+++ clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-ignore.cpp
@@ -2,7 +2,8 @@
 // RUN:   -config='{CheckOptions: [ \
 // RUN:     {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
 // RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: "\"\";Foo;Bar"}, \
-// RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: "T"} \
+// RUN:     {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: "T"}, \
+// RUN:     {key: bugprone-easily-swappable-parameters.NamePrefixSuffixSilenceDissimilarityTreshold, value: 0} \
 // RUN:  ]}' --
 
 void ignoredUnnamed(int I, int, int) {} // NO-WARN: No >= 2 length of non-unnamed.
Index: clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.h
===================================================================
--- clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.h
+++ clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.h
@@ -38,6 +38,12 @@
   /// The parameter typename suffixes (as written in the source code) to be
   /// ignored.
   const std::vector<std::string> IgnoredParameterTypeSuffixes;
+
+  /// The number of characters two parameter names might be dissimilar at
+  /// either end for the report about the parameters to be silenced.
+  /// E.g. the names "LHS" and "RHS" are 1-dissimilar suffixes of each other,
+  /// while "Text1" and "Text2" are 1-dissimilar prefixes of each other.
+  const std::size_t NamePrefixSuffixSilenceDissimilarityTreshold;
 };
 
 } // namespace bugprone
Index: clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp
===================================================================
--- clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp
+++ clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp
@@ -18,7 +18,7 @@
 namespace optutils = clang::tidy::utils::options;
 
 /// The default value for the MinimumLength check option.
-static constexpr unsigned DefaultMinimumLength = 2;
+static constexpr std::size_t DefaultMinimumLength = 2;
 
 /// The default value for ignored parameter names.
 static const std::string DefaultIgnoredParameterNames =
@@ -53,6 +53,11 @@
                                    "reverse_iterator",
                                    "reverse_const_iterator"});
 
+/// The default value for the NamePrefixSuffixSilenceDissimilarityTreshold
+/// check option.
+static constexpr std::size_t
+    DefaultNamePrefixSuffixSilenceDissimilarityTreshold = 1;
+
 #ifndef NDEBUG
 
 template <std::size_t Width>
@@ -93,6 +98,8 @@
 
 namespace filter {
 static bool isIgnoredParameter(const TheCheck &Check, const ParmVarDecl *Node);
+static bool prefixSuffixCoverUnderThreshold(std::size_t Threshold,
+                                            StringRef Str1, StringRef Str2);
 } // namespace filter
 
 namespace model {
@@ -222,13 +229,25 @@
 
   for (std::size_t I = StartIndex + 1; I < NumParams; ++I) {
     const ParmVarDecl *Ith = FD->getParamDecl(I);
-    LLVM_DEBUG(llvm::dbgs() << "Check param #" << I << "...\n");
-
+    StringRef ParamName = Ith->getName();
+    LLVM_DEBUG(llvm::dbgs()
+               << "Check param #" << I << " '" << ParamName << "'...\n");
     if (filter::isIgnoredParameter(Check, Ith)) {
       LLVM_DEBUG(llvm::dbgs() << "Param #" << I << " is ignored. Break!\n");
       break;
     }
 
+    StringRef PrevParamName = FD->getParamDecl(I - 1)->getName();
+    if (!ParamName.empty() && !PrevParamName.empty() &&
+        filter::prefixSuffixCoverUnderThreshold(
+            Check.NamePrefixSuffixSilenceDissimilarityTreshold, PrevParamName,
+            ParamName)) {
+      LLVM_DEBUG(llvm::dbgs() << "Parameter '" << ParamName
+                              << "' follows a pattern with previous parameter '"
+                              << PrevParamName << "'. Break!\n");
+      break;
+    }
+
     // Now try to go forward and build the range of [Start, ..., I, I + 1, ...]
     // parameters that can be messed up at a call site.
     MixableParameterRange::MixVector MixesOfIth;
@@ -329,6 +348,60 @@
 
   return false;
 }
+
+static void padStringAtEnd(SmallVectorImpl<char> &Str, std::size_t ToLen) {
+  while (Str.size() < ToLen)
+    Str.emplace_back('\0');
+}
+
+static void padStringAtBegin(SmallVectorImpl<char> &Str, std::size_t ToLen) {
+  while (Str.size() < ToLen)
+    Str.insert(Str.begin(), '\0');
+}
+
+static bool isCommonPrefixWithoutSomeCharacters(std::size_t N, StringRef S1,
+                                                StringRef S2) {
+  StringRef S1Prefix = S1.take_front(S1.size() - N),
+            S2Prefix = S2.take_front(S2.size() - N);
+  return S1Prefix == S2Prefix && !S1Prefix.empty();
+}
+
+static bool isCommonSuffixWithoutSomeCharacters(std::size_t N, StringRef S1,
+                                                StringRef S2) {
+  StringRef S1Suffix = S1.take_back(S1.size() - N),
+            S2Suffix = S2.take_back(S2.size() - N);
+  return S1Suffix == S2Suffix && !S1Suffix.empty();
+}
+
+/// Returns whether the two strings are prefixes or suffixes of each other with
+/// at most Threshold characters differing on the non-common end.
+static bool prefixSuffixCoverUnderThreshold(std::size_t Threshold,
+                                            StringRef Str1, StringRef Str2) {
+  if (Threshold == 0)
+    return false;
+
+  // Pad the two strings to the longer length.
+  std::size_t BiggerLength = std::max(Str1.size(), Str2.size());
+  SmallString<32> S1PadE{Str1}, S2PadE{Str2};
+  padStringAtEnd(S1PadE, BiggerLength);
+  padStringAtEnd(S2PadE, BiggerLength);
+
+  if (isCommonPrefixWithoutSomeCharacters(
+          Threshold, StringRef{S1PadE.begin(), BiggerLength},
+          StringRef{S2PadE.begin(), BiggerLength}))
+    return true;
+
+  SmallString<32> S1PadB{Str1}, S2PadB{Str2};
+  padStringAtBegin(S1PadB, BiggerLength);
+  padStringAtBegin(S2PadB, BiggerLength);
+
+  if (isCommonSuffixWithoutSomeCharacters(
+          Threshold, StringRef{S1PadB.begin(), BiggerLength},
+          StringRef{S2PadB.begin(), BiggerLength}))
+    return true;
+
+  return false;
+}
 } // namespace filter
 
 /// Matches functions that have at least the specified amount of parameters.
@@ -384,7 +457,10 @@
           Options.get("IgnoredParameterNames", DefaultIgnoredParameterNames))),
       IgnoredParameterTypeSuffixes(optutils::parseStringList(
           Options.get("IgnoredParameterTypeSuffixes",
-                      DefaultIgnoredParameterTypeSuffixes))) {}
+                      DefaultIgnoredParameterTypeSuffixes))),
+      NamePrefixSuffixSilenceDissimilarityTreshold(
+          Options.get("NamePrefixSuffixSilenceDissimilarityTreshold",
+                      DefaultNamePrefixSuffixSilenceDissimilarityTreshold)) {}
 
 void EasilySwappableParametersCheck::storeOptions(
     ClangTidyOptions::OptionMap &Opts) {
@@ -393,6 +469,8 @@
                 optutils::serializeStringList(IgnoredParameterNames));
   Options.store(Opts, "IgnoredParameterTypeSuffixes",
                 optutils::serializeStringList(IgnoredParameterTypeSuffixes));
+  Options.store(Opts, "NamePrefixSuffixSilenceDissimilarityTreshold",
+                NamePrefixSuffixSilenceDissimilarityTreshold);
 }
 
 void EasilySwappableParametersCheck::registerMatchers(MatchFinder *Finder) {
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to