[clang-tools-extra] 8a944d8 - [clang-tidy] Add option to ignore macros in readability-function-cognitive-complexity check.

Alexander Kornienko via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 12 09:46:53 PDT 2021


Author: Jens Massberg
Date: 2021-04-12T18:46:12+02:00
New Revision: 8a944d82cd14001a92ef088229041ee0fb1fd1e6

URL: https://github.com/llvm/llvm-project/commit/8a944d82cd14001a92ef088229041ee0fb1fd1e6
DIFF: https://github.com/llvm/llvm-project/commit/8a944d82cd14001a92ef088229041ee0fb1fd1e6.diff

LOG: [clang-tidy] Add option to ignore macros in readability-function-cognitive-complexity check.

(this was originally part of https://reviews.llvm.org/D96281 and has been split off into its own patch)

If a macro is used within a function, the code inside the macro
doesn't make the code less readable. Instead, for a reader a macro is
more like a function that is called. Thus the code inside a macro
shouldn't increase the complexity of the function in which it is called.
Thus the flag 'IgnoreMacros' is added. If set to 'true' code inside
macros isn't considered during analysis.

This isn't perfect, as now the code of a macro isn't considered at all,
even if it has a high cognitive complexity itself. It might be better if
a macro is considered in the analysis like a function and gets its own
cognitive complexity. Implementing such an analysis seems to be very
complex (if possible at all with the given AST), so we give the user the
option to either ignore macros completely or to let the expanded code
count to the calling function's complexity.

See the code example from vgeof (originally added as note in https://reviews.llvm.org/D96281)

   bool doStuff(myClass* objectPtr){
         if(objectPtr == nullptr){
             LOG_WARNING("empty object");
             return false;
         }
         if(objectPtr->getAttribute() == nullptr){
             LOG_WARNING("empty object");
             return false;
         }
         use(objectPtr->getAttribute());
     }

The LOG_WARNING macro itself might have a high complexity, but it do not make the
the function more complex to understand like e.g. a 'printf'.

By default 'IgnoreMacros' is set to 'false', which is the original behavior of the check.

Reviewed By: lebedev.ri, alexfh

Differential Revision: https://reviews.llvm.org/D98070

Added: 
    

Modified: 
    clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp
    clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h
    clang-tools-extra/docs/clang-tidy/checks/readability-function-cognitive-complexity.rst
    clang-tools-extra/test/clang-tidy/checkers/readability-function-cognitive-complexity-flags.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp
index 94ff38dcec05..0e931c708e4a 100644
--- a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp
@@ -213,6 +213,9 @@ class FunctionASTVisitor final
     : public RecursiveASTVisitor<FunctionASTVisitor> {
   using Base = RecursiveASTVisitor<FunctionASTVisitor>;
 
+  // If set to true, macros are ignored during analysis.
+  const bool IgnoreMacros;
+
   // The current nesting level (increased by Criteria::IncrementNesting).
   unsigned short CurrentNestingLevel = 0;
 
@@ -223,6 +226,9 @@ class FunctionASTVisitor final
   std::stack<OBO, SmallVector<OBO, 4>> BinaryOperatorsStack;
 
 public:
+  explicit FunctionASTVisitor(const bool IgnoreMacros)
+      : IgnoreMacros(IgnoreMacros){};
+
   bool traverseStmtWithIncreasedNestingLevel(Stmt *Node) {
     ++CurrentNestingLevel;
     bool ShouldContinue = Base::TraverseStmt(Node);
@@ -364,6 +370,9 @@ class FunctionASTVisitor final
     if (!Node)
       return Base::TraverseStmt(Node);
 
+    if (IgnoreMacros && Node->getBeginLoc().isMacroID())
+      return true;
+
     // Three following switch()'es have huge duplication, but it is better to
     // keep them separate, to simplify comparing them with the Specification.
 
@@ -493,12 +502,14 @@ FunctionCognitiveComplexityCheck::FunctionCognitiveComplexityCheck(
     StringRef Name, ClangTidyContext *Context)
     : ClangTidyCheck(Name, Context),
       Threshold(Options.get("Threshold", CognitiveComplexity::DefaultLimit)),
-      DescribeBasicIncrements(Options.get("DescribeBasicIncrements", true)) {}
+      DescribeBasicIncrements(Options.get("DescribeBasicIncrements", true)),
+      IgnoreMacros(Options.get("IgnoreMacros", false)) {}
 
 void FunctionCognitiveComplexityCheck::storeOptions(
     ClangTidyOptions::OptionMap &Opts) {
   Options.store(Opts, "Threshold", Threshold);
   Options.store(Opts, "DescribeBasicIncrements", DescribeBasicIncrements);
+  Options.store(Opts, "IgnoreMacros", IgnoreMacros);
 }
 
 void FunctionCognitiveComplexityCheck::registerMatchers(MatchFinder *Finder) {
@@ -513,7 +524,7 @@ void FunctionCognitiveComplexityCheck::registerMatchers(MatchFinder *Finder) {
 void FunctionCognitiveComplexityCheck::check(
     const MatchFinder::MatchResult &Result) {
 
-  FunctionASTVisitor Visitor;
+  FunctionASTVisitor Visitor(IgnoreMacros);
   SourceLocation Loc;
 
   const auto *TheDecl = Result.Nodes.getNodeAs<FunctionDecl>("func");

diff  --git a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h
index 01244ab0ecf0..2262a067d600 100644
--- a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h
+++ b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h
@@ -26,6 +26,8 @@ namespace readability {
 ///     diagnostics on every piece of code (loop, `if` statement, etc.) which
 ///     contributes to that complexity.
 //      Default is `true`
+///   * `IgnoreMacros` - if set to `true`, the check will ignore code inside
+///     macros. Default is `false`.
 ///
 /// For the user-facing documentation see:
 /// http://clang.llvm.org/extra/clang-tidy/checks/readability-function-cognitive-complexity.html
@@ -43,6 +45,7 @@ class FunctionCognitiveComplexityCheck : public ClangTidyCheck {
 private:
   const unsigned Threshold;
   const bool DescribeBasicIncrements;
+  const bool IgnoreMacros;
 };
 
 } // namespace readability

diff  --git a/clang-tools-extra/docs/clang-tidy/checks/readability-function-cognitive-complexity.rst b/clang-tools-extra/docs/clang-tidy/checks/readability-function-cognitive-complexity.rst
index b14c684f57f5..5337a2997d37 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/readability-function-cognitive-complexity.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability-function-cognitive-complexity.rst
@@ -24,6 +24,13 @@ Options
    `if` statement, etc.) which contributes to that complexity. See also the
    examples below. Default is `true`.
 
+.. option:: IgnoreMacros
+
+   If set to `true`, the check will ignore code inside macros. Note, that also
+   any macro arguments are ignored, even if they should count to the complexity.
+   As this might change in the future, this option isn't guarantueed to be
+   forward-compatible. Default is `false`.
+
 Building blocks
 ---------------
 

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/readability-function-cognitive-complexity-flags.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability-function-cognitive-complexity-flags.cpp
index c52e7bcf1adc..51b84ccf391e 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability-function-cognitive-complexity-flags.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability-function-cognitive-complexity-flags.cpp
@@ -10,9 +10,27 @@
 // RUN:               value: 5}, \
 // RUN:              {key: readability-function-cognitive-complexity.DescribeBasicIncrements, \
 // RUN:               value: "false"} ]}'
+// RUN: %check_clang_tidy -check-suffix=IGNORE-MACROS %s readability-function-cognitive-complexity %t -- \
+// RUN:   -config='{CheckOptions: \
+// RUN:             [{key: readability-function-cognitive-complexity.Threshold, \
+// RUN:               value: 0}, \
+// RUN:              {key: readability-function-cognitive-complexity.IgnoreMacros, \
+// RUN:               value: "true"}, \
+// RUN:              {key: readability-function-cognitive-complexity.DescribeBasicIncrements, \
+// RUN:               value: "false"} ]}'
+// RUN: %check_clang_tidy -check-suffix=GLOBAL-IGNORE-MACROS %s readability-function-cognitive-complexity %t -- \
+// RUN:   -config='{CheckOptions: \
+// RUN:             [{key: readability-function-cognitive-complexity.Threshold, \
+// RUN:               value: 0}, \
+// RUN:              {key: IgnoreMacros, \
+// RUN:               value: "true"}, \
+// RUN:              {key: readability-function-cognitive-complexity.DescribeBasicIncrements, \
+// RUN:               value: "false"} ]}'
 
 void func_of_complexity_4() {
   // CHECK-NOTES: :[[@LINE-1]]:6: warning: function 'func_of_complexity_4' has cognitive complexity of 4 (threshold 0) [readability-function-cognitive-complexity]
+  // CHECK-NOTES-IGNORE-MACROS: :[[@LINE-2]]:6: warning: function 'func_of_complexity_4' has cognitive complexity of 4 (threshold 0) [readability-function-cognitive-complexity]
+  // CHECK-NOTES-GLOBAL-IGNORE-MACROS: :[[@LINE-3]]:6: warning: function 'func_of_complexity_4' has cognitive complexity of 4 (threshold 0) [readability-function-cognitive-complexity]
   if (1) {
     if (1) {
     }
@@ -34,9 +52,44 @@ void func_of_complexity_4() {
 void function_with_macro() {
   // CHECK-NOTES: :[[@LINE-1]]:6: warning: function 'function_with_macro' has cognitive complexity of 11 (threshold 0) [readability-function-cognitive-complexity]
   // CHECK-NOTES-THRESHOLD5: :[[@LINE-2]]:6: warning: function 'function_with_macro' has cognitive complexity of 11 (threshold 5) [readability-function-cognitive-complexity]
+  // CHECK-NOTES-IGNORE-MACROS: :[[@LINE-3]]:6: warning: function 'function_with_macro' has cognitive complexity of 1 (threshold 0) [readability-function-cognitive-complexity]
+  // CHECK-NOTES-GLOBAL-IGNORE-MACROS: :[[@LINE-4]]:6: warning: function 'function_with_macro' has cognitive complexity of 11 (threshold 0) [readability-function-cognitive-complexity]
 
   MacroOfComplexity10;
 
   if (1) {
   }
 }
+
+#define noop \
+  {}
+
+#define SomeMacro(x) \
+  if (1) {           \
+    x;               \
+  }
+
+void func_macro_1() {
+  // CHECK-NOTES: :[[@LINE-1]]:6: warning: function 'func_macro_1' has cognitive complexity of 2 (threshold 0) [readability-function-cognitive-complexity]
+  // CHECK-NOTES-IGNORE-MACROS: :[[@LINE-2]]:6: warning: function 'func_macro_1' has cognitive complexity of 1 (threshold 0) [readability-function-cognitive-complexity]
+  // CHECK-NOTES-GLOBAL-IGNORE-MACROS: :[[@LINE-3]]:6: warning: function 'func_macro_1' has cognitive complexity of 2 (threshold 0) [readability-function-cognitive-complexity]
+
+  if (1) {
+  }
+  SomeMacro(noop);
+}
+
+void func_macro_2() {
+  // CHECK-NOTES: :[[@LINE-1]]:6: warning: function 'func_macro_2' has cognitive complexity of 4 (threshold 0) [readability-function-cognitive-complexity]
+  // CHECK-NOTES-IGNORE-MACROS: :[[@LINE-2]]:6: warning: function 'func_macro_2' has cognitive complexity of 1 (threshold 0) [readability-function-cognitive-complexity]
+  // CHECK-NOTES-GLOBAL-IGNORE-MACROS: :[[@LINE-3]]:6: warning: function 'func_macro_2' has cognitive complexity of 4 (threshold 0) [readability-function-cognitive-complexity]
+
+  if (1) {
+  }
+  // Note that if the IgnoreMacro option is set to 'true', currently also macro
+  // arguments are ignored. Optimally, macros should be treated like function
+  // calls, i.e. the arguments account to the complexity so that the overall
+  // complexity of this function is 2 (1 for the if statement above + 1 for
+  // the if statement in the argument).
+  SomeMacro(if (1) { noop; });
+}


        


More information about the cfe-commits mailing list