Hi alexfh, djasper,

As this is very dependent on the code base it has some ways of configuration.
It's possible to pick between 3 modes of operation:

- Line counting: number of lines including whitespace and comments
- Statement counting: number of statements within compoundStmts.
- Cyclomatic complexity: calculated from the CFG.

In addition a threshold can be picked, warnings are only emitted when it is met.
Sadly this is not configurable at runtime yet as there is some plumbing missing.

http://reviews.llvm.org/D4986

Files:
  clang-tidy/misc/CMakeLists.txt
  clang-tidy/misc/FunctionLength.cpp
  clang-tidy/misc/FunctionLength.h
  clang-tidy/misc/MiscTidyModule.cpp
  unittests/clang-tidy/CMakeLists.txt
  unittests/clang-tidy/FunctionLengthCheckTest.cpp
Index: clang-tidy/misc/CMakeLists.txt
===================================================================
--- clang-tidy/misc/CMakeLists.txt
+++ clang-tidy/misc/CMakeLists.txt
@@ -3,6 +3,7 @@
 add_clang_library(clangTidyMiscModule
   ArgumentCommentCheck.cpp
   BoolPointerImplicitConversion.cpp
+  FunctionLength.cpp
   MiscTidyModule.cpp
   RedundantSmartptrGet.cpp
   SwappedArgumentsCheck.cpp
Index: clang-tidy/misc/FunctionLength.cpp
===================================================================
--- /dev/null
+++ clang-tidy/misc/FunctionLength.cpp
@@ -0,0 +1,115 @@
+//===--- FunctionLength.cpp - clang-tidy ----------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "FunctionLength.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Analysis/CFG.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+
+void FunctionLengthCheck::registerMatchers(MatchFinder *Finder) {
+  if (Type == FL_STATEMENTS) {
+    auto InTemplateInstantiation = hasAncestor(
+        decl(anyOf(recordDecl(ast_matchers::isTemplateInstantiation()),
+                   functionDecl(ast_matchers::isTemplateInstantiation()))));
+    Finder->addMatcher(functionDecl(unless(InTemplateInstantiation),
+                                    forEachDescendant(compoundStmt().bind(
+                                        "block"))).bind("func"),
+                       this);
+  } else {
+    Finder->addMatcher(functionDecl().bind("func"), this);
+  }
+}
+
+void FunctionLengthCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
+
+  // Count the lines including whitespace and comments. Really simple.
+  if (Type == FL_LINES) {
+    if (const Stmt *Body = Func->getBody()) {
+      SourceManager *SM = Result.SourceManager;
+      if (SM->isWrittenInSameFile(Body->getLocStart(), Body->getLocEnd())) {
+        FunctionLengths[Func] = SM->getPresumedLineNumber(Body->getLocEnd()) -
+                                SM->getPresumedLineNumber(Body->getLocStart());
+      }
+    }
+    return;
+  }
+
+  // Calculate the number of statements in this function.
+  if (Type == FL_STATEMENTS) {
+    // Add the number of statements in this block. We'll automatically recurse
+    // into it with the ast matcher.
+    const auto *Block = Result.Nodes.getNodeAs<CompoundStmt>("block");
+    FunctionLengths[Func] +=
+        std::distance(Block->child_begin(), Block->child_end());
+    return;
+  }
+
+  // Now a more complex analysis. Create a CFG and use it to calculate
+  // cyclomatic complexity. The CFG analysis does not work on template
+  // definitions so do the analysis at instantiation time.
+  if (!Func->getBody() || Func->isDependentContext())
+    return;
+
+  // Generate a CFG for the function.
+  // FIXME: Can we reuse the CFG from other analyses somehow?
+  CFG::BuildOptions BO;
+  BO.PruneTriviallyFalseEdges = false;
+  BO.AddEHEdges = false;
+  BO.AddInitializers = true;
+  BO.AddImplicitDtors = false;
+  BO.AddTemporaryDtors = false;
+  BO.AddCXXNewAllocator = false;
+  BO.setAllAlwaysAdd();
+  std::unique_ptr<CFG> G(
+      CFG::buildCFG(Func, Func->getBody(), Result.Context, BO));
+
+  // Count the number of blocks and edges in this graph.
+  unsigned NumBlocks = 0;
+  unsigned NumEdges = 0;
+  for (CFG::iterator I = G->begin(), E = G->end(); I != E; ++I) {
+    ++NumBlocks;
+    NumEdges += (*I)->succ_size();
+  }
+
+  FunctionLengths[Func] = NumEdges - NumBlocks + 2;
+}
+
+void FunctionLengthCheck::onEndOfTranslationUnit() {
+  // If we're above the limit emit a warning.
+  for (const auto &P : FunctionLengths) {
+    if (P.second > Threshold) {
+      const char *T;
+      switch (Type) {
+      case FL_LINES:
+        T = "lines including whitespace";
+        break;
+      case FL_STATEMENTS:
+        T = "statements";
+        break;
+      case FL_CYCLOMATIC:
+        T = "in cyclomatic complexity";
+        break;
+      }
+
+      diag(P.first->getLocation(),
+           "function is very large, %0 %1 (threshold %2)")
+          << P.second << T << Threshold;
+    }
+  }
+
+  FunctionLengths.clear();
+}
+
+} // namespace tidy
+} // namespace clang
Index: clang-tidy/misc/FunctionLength.h
===================================================================
--- /dev/null
+++ clang-tidy/misc/FunctionLength.h
@@ -0,0 +1,46 @@
+//===--- FunctionLength.h - clang-tidy --------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FUNCTIONLENGTH_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FUNCTIONLENGTH_H
+
+#include "../ClangTidy.h"
+
+namespace clang {
+namespace tidy {
+
+/// \brief Checks for large functions based on various metrics.
+class FunctionLengthCheck : public ClangTidyCheck {
+public:
+  enum AnalysisType {
+    FL_LINES,      ///< Count lines, including comments and whitespace.
+    FL_STATEMENTS, ///< Count statements in the function.
+    FL_CYCLOMATIC  ///< Calculate cyclomatic complexity for the function.
+  };
+
+private:
+  const AnalysisType Type;
+  const unsigned Threshold;
+  llvm::DenseMap<const FunctionDecl *, unsigned> FunctionLengths;
+
+public:
+  FunctionLengthCheck(AnalysisType Type = FL_STATEMENTS,
+                      unsigned Threshold = 800)
+      : Type(Type), Threshold(Threshold) {}
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  void onEndOfTranslationUnit() override;
+};
+
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_FUNCTIONLENGTH_H
+
Index: clang-tidy/misc/MiscTidyModule.cpp
===================================================================
--- clang-tidy/misc/MiscTidyModule.cpp
+++ clang-tidy/misc/MiscTidyModule.cpp
@@ -12,6 +12,7 @@
 #include "../ClangTidyModuleRegistry.h"
 #include "ArgumentCommentCheck.h"
 #include "BoolPointerImplicitConversion.h"
+#include "FunctionLength.h"
 #include "RedundantSmartptrGet.h"
 #include "SwappedArgumentsCheck.h"
 #include "UndelegatedConstructor.h"
@@ -31,6 +32,9 @@
         "misc-bool-pointer-implicit-conversion",
         new ClangTidyCheckFactory<BoolPointerImplicitConversion>());
     CheckFactories.addCheckFactory(
+        "misc-function-length",
+        new ClangTidyCheckFactory<FunctionLengthCheck>());
+    CheckFactories.addCheckFactory(
         "misc-redundant-smartptr-get",
         new ClangTidyCheckFactory<RedundantSmartptrGet>());
     CheckFactories.addCheckFactory(
Index: unittests/clang-tidy/CMakeLists.txt
===================================================================
--- unittests/clang-tidy/CMakeLists.txt
+++ unittests/clang-tidy/CMakeLists.txt
@@ -9,8 +9,9 @@
 add_extra_unittest(ClangTidyTests
   ClangTidyDiagnosticConsumerTest.cpp
   ClangTidyOptionsTest.cpp
-  LLVMModuleTest.cpp
+  FunctionLengthCheckTest.cpp
   GoogleModuleTest.cpp
+  LLVMModuleTest.cpp
   MiscModuleTest.cpp)
 
 target_link_libraries(ClangTidyTests
Index: unittests/clang-tidy/FunctionLengthCheckTest.cpp
===================================================================
--- /dev/null
+++ unittests/clang-tidy/FunctionLengthCheckTest.cpp
@@ -0,0 +1,97 @@
+#include "ClangTidyTest.h"
+#include "misc/FunctionLength.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace test {
+
+namespace {
+std::string getWarning(ClangTidyCheck &Check, StringRef Code) {
+  ClangTidyContext Context(
+      new DefaultOptionsProvider(ClangTidyGlobalOptions(), ClangTidyOptions()));
+  ClangTidyDiagnosticConsumer DiagConsumer(Context);
+  Check.setContext(&Context);
+  std::vector<std::string> Args;
+
+  ast_matchers::MatchFinder Finder;
+  Check.registerMatchers(&Finder);
+  std::unique_ptr<tooling::FrontendActionFactory> Factory(
+      tooling::newFrontendActionFactory(&Finder));
+  if (!tooling::runToolOnCodeWithArgs(Factory->create(), Code, Args))
+    return "tooling error";
+  DiagConsumer.finish();
+
+  if (Context.getErrors().size() == 0)
+    return "no errors";
+
+  if (Context.getErrors().size() != 1)
+    return "wrong number of errors";
+
+  return StringRef(Context.getErrors().back().Message.Message).rtrim(" []");
+}
+} // namespace
+
+static const char *Code1 = "void foo() {\n}";
+static const char *Code2 = "void foo() {;}";
+static const char *Code3 = "void foo() {\n;\n\n}";
+static const char *Code4 = "void foo(int i) { if (i) {} else; {}\n}";
+static const char *Code5 = "void foo(int i) { switch (i) { case 1: break;\n"
+                           "case 2: break;}\n}";
+static const char *Code6 = "template <typename T> T foo(T i) {return i;\n}\n"
+                           "int x = foo(0);";
+
+TEST(FunctionLengthCheckTest, LinesTest) {
+  FunctionLengthCheck C(FunctionLengthCheck::FL_LINES, 0);
+
+  EXPECT_EQ("function is very large, 1 lines including whitespace (threshold 0)",
+            getWarning(C, Code1));
+  EXPECT_EQ("no errors",
+            getWarning(C, Code2));
+  EXPECT_EQ("function is very large, 3 lines including whitespace (threshold 0)",
+            getWarning(C, Code3));
+  EXPECT_EQ("function is very large, 1 lines including whitespace (threshold 0)",
+            getWarning(C, Code4));
+  EXPECT_EQ("function is very large, 2 lines including whitespace (threshold 0)",
+            getWarning(C, Code5));
+  EXPECT_EQ("function is very large, 1 lines including whitespace (threshold 0)",
+            getWarning(C, Code6));
+}
+
+TEST(FunctionLengthCheckTest, StatementsTest) {
+  FunctionLengthCheck C(FunctionLengthCheck::FL_STATEMENTS, 0);
+
+  EXPECT_EQ("no errors",
+            getWarning(C, Code1));
+  EXPECT_EQ("function is very large, 1 statements (threshold 0)",
+            getWarning(C, Code2));
+  EXPECT_EQ("function is very large, 1 statements (threshold 0)",
+            getWarning(C, Code3));
+  EXPECT_EQ("function is very large, 2 statements (threshold 0)",
+            getWarning(C, Code4));
+  EXPECT_EQ("function is very large, 3 statements (threshold 0)",
+            getWarning(C, Code5));
+  EXPECT_EQ("function is very large, 1 statements (threshold 0)",
+            getWarning(C, Code6));
+}
+
+TEST(FunctionLengthCheckTest, CyclomaticTest) {
+  FunctionLengthCheck C(FunctionLengthCheck::FL_CYCLOMATIC, 0);
+
+  EXPECT_EQ("function is very large, 1 in cyclomatic complexity (threshold 0)",
+            getWarning(C, Code1));
+  EXPECT_EQ("function is very large, 1 in cyclomatic complexity (threshold 0)",
+            getWarning(C, Code2));
+  EXPECT_EQ("function is very large, 1 in cyclomatic complexity (threshold 0)",
+            getWarning(C, Code3));
+  EXPECT_EQ("function is very large, 2 in cyclomatic complexity (threshold 0)",
+            getWarning(C, Code4));
+  EXPECT_EQ("function is very large, 3 in cyclomatic complexity (threshold 0)",
+            getWarning(C, Code5));
+  EXPECT_EQ("function is very large, 1 in cyclomatic complexity (threshold 0)",
+            getWarning(C, Code6));
+}
+
+} // namespace test
+} // namespace tidy
+} // namespace clang
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to