njames93 updated this revision to Diff 449048.
njames93 added a comment.

Rebase and Ping??


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D130181/new/

https://reviews.llvm.org/D130181

Files:
  clang-tools-extra/clang-tidy/readability/CMakeLists.txt
  clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
  clang-tools-extra/clang-tidy/readability/UseEarlyExitsCheck.cpp
  clang-tools-extra/clang-tidy/readability/UseEarlyExitsCheck.h
  clang-tools-extra/docs/ReleaseNotes.rst
  clang-tools-extra/docs/clang-tidy/checks/list.rst
  clang-tools-extra/docs/clang-tidy/checks/readability/use-early-exits.rst
  
clang-tools-extra/test/clang-tidy/checkers/readability/use-early-exits-braces.cpp
  
clang-tools-extra/test/clang-tidy/checkers/readability/use-early-exits-split.cpp
  clang-tools-extra/test/clang-tidy/checkers/readability/use-early-exits.cpp

Index: clang-tools-extra/test/clang-tidy/checkers/readability/use-early-exits.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/readability/use-early-exits.cpp
@@ -0,0 +1,164 @@
+// RUN: %check_clang_tidy %s readability-use-early-exits -format-style=llvm %t -- \
+// RUN:   -config="{CheckOptions: {readability-use-early-exits.LineCountThreshold: 2}}"
+
+// Run the check with the braces around statements check also enabled, but
+// ShortStatementLines is set so that we shouldn't add braces.
+// RUN: %check_clang_tidy %s readability-use-early-exits,readability-braces-around-statements -format-style=llvm %t -- \
+// RUN:   -config="{CheckOptions: {readability-use-early-exits.LineCountThreshold: 2, \
+// RUN:                            readability-braces-around-statements.ShortStatementLines: 2}}"
+
+// Just to hit the threshold
+void padLines();
+
+void nomralFunc(int *A) {
+  // CHECK-MESSAGES: [[@LINE+1]]:3: warning: use early exit
+  if (A) {
+    *A = 10;
+  }
+  //       CHECK-FIXES: if (!A)
+  //  CHECK-FIXES-NEXT: return;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT: *A = 10;
+}
+
+void funcWithTrailing(int *B) {
+  // CHECK-MESSAGES: [[@LINE+1]]:3: warning: use early exit
+  if (B) {
+    *B = 10;
+  }
+  return;
+  ;
+  //       CHECK-FIXES: if (!B)
+  //  CHECK-FIXES-NEXT: return;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT: *B = 10;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT: return;
+  //  CHECK-FIXES-NEXT: ;
+}
+
+void normal(int A, int B) {
+
+  while (true) { // CHECK-MESSAGES: [[@LINE+1]]:5: warning: use early exit
+    if (A == B) {
+      padLines();
+    }
+  }
+  //       CHECK-FIXES:  while (true) {
+  //  CHECK-FIXES-NEXT: if (A != B)
+  //  CHECK-FIXES-NEXT:   continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT: padLines();
+  //  CHECK-FIXES-NEXT: }
+
+  // Eat continue and empty nulls
+  while (true) { // CHECK-MESSAGES: [[@LINE+1]]:5: warning: use early exit
+    if (A != B) {
+      padLines();
+    }
+    continue;
+    ;
+    continue;
+  }
+  //       CHECK-FIXES:  while (true) {
+  //  CHECK-FIXES-NEXT: if (A == B)
+  //  CHECK-FIXES-NEXT:   continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT: padLines();
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   continue;
+  //  CHECK-FIXES-NEXT:   ;
+  //  CHECK-FIXES-NEXT:   continue;
+  //  CHECK-FIXES-NEXT: }
+}
+
+void toShort(int A, int B) {
+  while (true) {
+    if (A == B) {
+    }
+  }
+}
+
+void hasElse(int A, int B) {
+  while (true) {
+    if (A == B) {
+
+    } else {
+    }
+  }
+}
+
+void hasTrailingStmt(int A, int B) {
+  while (true) {
+    if (A == B) {
+    }
+    padLines();
+  }
+}
+
+void nested(int A, int B) {
+  // if (A > B) {
+  // CHECK-MESSAGES: [[@LINE+6]]:5: warning: use early exit
+  // if (B < A) {
+  // CHECK-MESSAGES: [[@LINE+5]]:7: warning: use early exit
+  // if (A == B) {
+  // CHECK-MESSAGES: [[@LINE+4]]:9: warning: use early exit
+  while (true) { // Nested
+    if (A > B) {
+      if (B < A) {
+        if (A != B) {
+          padLines();
+        }
+      }
+    }
+  } // EndLoop
+  //       CHECK-FIXES: while (true) { // Nested
+  //  CHECK-FIXES-NEXT: if (A <= B)
+  //  CHECK-FIXES-NEXT:   continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT: if (B >= A)
+  //  CHECK-FIXES-NEXT:   continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT: if (A == B)
+  //  CHECK-FIXES-NEXT:   continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT: padLines();
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT: } // EndLoop
+}
+
+void badNested(bool B) {
+  // Ensure check doesn't check for nested `if` if outer `if` has else.
+  while (true) {
+    if (B) {
+      if (B) {
+      }
+    } else {
+    }
+  }
+}
+
+void semiNested(int A, int B) {
+  // CHECK-MESSAGES: [[@LINE+2]]:5: warning: use early exit
+  while (true) { // SemiNested
+    if (A > B) {
+      if (B < A) {
+        if (A != B) {
+          padLines();
+        }
+      } else {
+      }
+    }
+  }
+  //       CHECK-FIXES: while (true) { // SemiNested
+  //  CHECK-FIXES-NEXT:   if (A <= B)
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   if (B < A) {
+  //  CHECK-FIXES-NEXT:     if (A != B) {
+  //  CHECK-FIXES-NEXT:       padLines();
+  //  CHECK-FIXES-NEXT:     }
+  //  CHECK-FIXES-NEXT:     } else {
+  //  CHECK-FIXES-NEXT:     }
+  //  CHECK-FIXES-NEXT: }
+}
Index: clang-tools-extra/test/clang-tidy/checkers/readability/use-early-exits-split.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/readability/use-early-exits-split.cpp
@@ -0,0 +1,88 @@
+// RUN: %check_clang_tidy %s readability-use-early-exits -format-style=llvm %t -- \
+// RUN:   -config="{CheckOptions: {readability-use-early-exits.LineCountThreshold: 2, \
+// RUN:                            readability-use-early-exits.SplitConjunctions: true}}"
+
+void padLines();
+
+bool A, B, C;
+
+void conjunction() {
+  // CHECK-MESSAGES: [[@LINE+2]]:5: warning: use early exit
+  while (true) {
+    if (A && B) {
+      padLines();
+    }
+  }
+
+  //       CHECK-FIXES: while (true) {
+  //  CHECK-FIXES-NEXT:   if (!A)
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   if (!B)
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   padLines();
+  //  CHECK-FIXES-NEXT: }
+
+  // CHECK-MESSAGES: [[@LINE+2]]:5: warning: use early exit
+  while (true) {
+    if (A && B && !C) {
+      padLines();
+    }
+  }
+
+  //       CHECK-FIXES: while (true) {
+  //  CHECK-FIXES-NEXT:   if (!A)
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   if (!B)
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   if (C)
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   padLines();
+  //  CHECK-FIXES-NEXT: }
+
+  // CHECK-MESSAGES: [[@LINE+3]]:5: warning: use early exit
+  // CHECK-MESSAGES: [[@LINE+3]]:7: warning: use early exit
+  while (true) {
+    if (A && B) {
+      if (B && !C) {
+        padLines();
+      }
+    }
+  }
+
+  //       CHECK-FIXES: while (true) {
+  //  CHECK-FIXES-NEXT:   if (!A)
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   if (!B)
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   if (!B)
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   if (C)
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   padLines();
+  //  CHECK-FIXES-NEXT: }
+}
+
+void disjunction() {
+  // CHECK-MESSAGES: [[@LINE+2]]:5: warning: use early exit
+  while (true) {
+    if (A || B) {
+      padLines();
+    }
+  }
+
+  //       CHECK-FIXES: while (true) {
+  //  CHECK-FIXES-NEXT:   if (!(A || B))
+  //  CHECK-FIXES-NEXT:     continue;
+  // CHECK-FIXES-EMPTY:
+  //  CHECK-FIXES-NEXT:   padLines();
+  //  CHECK-FIXES-NEXT: }
+}
Index: clang-tools-extra/test/clang-tidy/checkers/readability/use-early-exits-braces.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/readability/use-early-exits-braces.cpp
@@ -0,0 +1,16 @@
+// RUN: %check_clang_tidy %s readability-use-early-exits,google-readability-braces-around-statements -format-style=llvm %t -- \
+// RUN:   -config="{CheckOptions: {readability-use-early-exits.LineCountThreshold: 2, \
+// RUN:                            google-readability-braces-around-statements.ShortStatementLines: 0}}"
+
+// RUN: %check_clang_tidy %s readability-use-early-exits,hicpp-braces-around-statements -format-style=llvm %t -- \
+// RUN:   -config="{CheckOptions: {readability-use-early-exits.LineCountThreshold: 2}}"
+void nomralFunc(int *A) {
+  // CHECK-MESSAGES: [[@LINE+1]]:3: warning: use early exit
+  if (A) {
+    *A = 10;
+  }
+  //       CHECK-FIXES: if (!A) {
+  //  CHECK-FIXES-NEXT: return;
+  //  CHECK-FIXES-NEXT: }
+  //  CHECK-FIXES-NEXT: *A = 10;
+}
Index: clang-tools-extra/docs/clang-tidy/checks/readability/use-early-exits.rst
===================================================================
--- /dev/null
+++ clang-tools-extra/docs/clang-tidy/checks/readability/use-early-exits.rst
@@ -0,0 +1,108 @@
+.. title:: clang-tidy - readability-use-early-exits
+
+readability-use-early-exits
+===========================
+
+Finds ``if`` statements inside functions and loops that could be flipped to 
+make an early exit.
+
+Example:
+
+.. code-block:: c++
+
+  void Process(MyClass* C) {
+    if (C) {
+      // Do some long processing.
+    }
+  }
+
+  void Process(span<MyClass*> C){
+    for (MyClass *Item : C) {
+      if (Item) {
+        // Do some long processing.
+      }
+    }
+  }
+
+Would be transformed to:
+
+.. code-block:: c++
+
+  void Process(MyClass* C) {
+    if (!C)
+      return;
+    // Do some long processing.
+  }
+
+  void Process(span<MyClass*> C){
+    for (MyClass *Item : C) {
+      if (!Item)
+        continue;
+      // Do some long processing.
+    }
+  }
+
+Options
+-------
+
+.. option:: LineCountThreshold
+
+  Don't transform any ``if`` statement if the statement uses less than this 
+  many lines. Default value is `10`.
+
+.. option:: SplitConjunctions
+
+  If `true`, split up conditions with cunjunctions (``&&``) into multiple 
+  ``if`` statements. Default value is `false`.
+
+  When `true`:
+
+  .. code-block:: c++
+
+    void Process(bool A, bool B) {
+      if (A && B) {
+        // Long processing.
+      }
+    }
+
+  Would be transformed to:
+
+  .. code-block:: c++
+
+    void Process(bool A, bool B) {
+      if (!A)
+        return;
+
+      if (!B)
+        return;
+        
+      // Long processing.
+    }
+
+.. option:: WrapInBraces
+
+  If `true`, the early exit will be wrapped in braces.
+
+  If unspecified, the value will be inferred based on whether
+  :doc:`readability-braces-around-statements <braces-around-statements>` or one
+  of its alias' are active.
+
+  When `true`, The above exmples would be transformed to:
+
+  .. code-block:: c++
+
+    void Process(MyClass *C) {
+      if (!C) {
+        return;
+      }
+      // Do some long processing
+    }
+
+    void Process(span<MyClass*> C){
+      for (MyClass *Item : C) {
+        if (!Item) {
+          continue;
+        }
+        // Do some long processing.
+      }
+    }
Index: clang-tools-extra/docs/clang-tidy/checks/list.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -360,6 +360,7 @@
    `readability-uniqueptr-delete-release <readability/uniqueptr-delete-release.html>`_, "Yes"
    `readability-uppercase-literal-suffix <readability/uppercase-literal-suffix.html>`_, "Yes"
    `readability-use-anyofallof <readability/use-anyofallof.html>`_,
+   `readability-use-early-exits <readability/use-early-exits.html>`_, "Yes"
    `zircon-temporary-objects <zircon/temporary-objects.html>`_,
 
 
Index: clang-tools-extra/docs/ReleaseNotes.rst
===================================================================
--- clang-tools-extra/docs/ReleaseNotes.rst
+++ clang-tools-extra/docs/ReleaseNotes.rst
@@ -99,6 +99,12 @@
 New checks
 ^^^^^^^^^^
 
+- New :doc:`readability-use-early-exits
+  <clang-tidy/checks/readability/use-early-exits>` check.
+
+  Finds ``if`` statements inside functions and loops that could be flipped to 
+  make an early exit.
+
 New check aliases
 ^^^^^^^^^^^^^^^^^
 
Index: clang-tools-extra/clang-tidy/readability/UseEarlyExitsCheck.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/readability/UseEarlyExitsCheck.h
@@ -0,0 +1,44 @@
+//===--- UseEarlyExitsCheck.h - clang-tidy ----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEEARLYEXITSCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEEARLYEXITSCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang {
+namespace tidy {
+namespace readability {
+
+/// Finds `if` statements inside functions and loops that could be flipped to
+/// make an early exit.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/readability/use-early-exits.html
+class UseEarlyExitsCheck : public ClangTidyCheck {
+public:
+  UseEarlyExitsCheck(StringRef Name, ClangTidyContext *Context);
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+  unsigned getLineCountThreshold() const { return LineCountThreshold; }
+  bool getSplitConjunctions() const { return SplitConjunctions; }
+  bool getWrapInBraces() const { return WrapInBraces; }
+
+private:
+  const unsigned LineCountThreshold;
+  const bool SplitConjunctions;
+  const bool WrapInBraces;
+};
+
+} // namespace readability
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEEARLYEXITSCHECK_H
Index: clang-tools-extra/clang-tidy/readability/UseEarlyExitsCheck.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/readability/UseEarlyExitsCheck.cpp
@@ -0,0 +1,294 @@
+//===--- UseEarlyExitsCheck.cpp - clang-tidy ------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseEarlyExitsCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/OperationKinds.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace readability {
+
+static bool needsParensAfterUnaryNegation(const Expr *E) {
+  E = E->IgnoreImpCasts();
+  if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E))
+    return true;
+
+  if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E))
+    return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call &&
+           Op->getOperator() != OO_Subscript;
+
+  return false;
+}
+
+static void addReverseConditionFix(DiagnosticBuilder &Diag,
+                                   const Expr *Condition,
+                                   const ASTContext &Ctx) {
+  if (const auto *BO = dyn_cast<BinaryOperator>(Condition)) {
+    if (BO->isComparisonOp() && BO->getOpcode() != BO_Cmp) {
+      Diag << FixItHint::CreateReplacement(
+          BO->getOperatorLoc(),
+          BinaryOperator::getOpcodeStr(
+              BinaryOperator::negateComparisonOp(BO->getOpcode())));
+      return;
+    }
+  } else if (const auto *UO = dyn_cast<UnaryOperator>(Condition)) {
+    if (UO->getOpcode() == UO_LNot) {
+      Diag << FixItHint::CreateRemoval(UO->getOperatorLoc());
+      if (const auto *Parens = dyn_cast<ParenExpr>(UO->getSubExpr())) {
+        Diag << FixItHint::CreateRemoval(Parens->getLParen());
+        Diag << FixItHint::CreateRemoval(Parens->getRParen());
+      }
+      return;
+    }
+  } else if (const auto *BL = dyn_cast<CXXBoolLiteralExpr>(Condition)) {
+    Diag << FixItHint::CreateReplacement(BL->getSourceRange(),
+                                         BL->getValue() ? "false" : "true");
+    return;
+  } else if (const auto *IL = dyn_cast<IntegerLiteral>(Condition)) {
+    Diag << FixItHint::CreateReplacement(
+        IL->getSourceRange(), IL->getValue().getBoolValue() ? "0" : "1");
+    return;
+  }
+  if (needsParensAfterUnaryNegation(Condition)) {
+    Diag << FixItHint::CreateInsertion(Condition->getBeginLoc(), "!(")
+         << FixItHint::CreateInsertion(
+                Lexer::getLocForEndOfToken(Condition->getEndLoc(), 0,
+                                           Ctx.getSourceManager(),
+                                           Ctx.getLangOpts()),
+                ")");
+  } else {
+    Diag << FixItHint::CreateInsertion(Condition->getBeginLoc(), "!");
+  }
+}
+
+static void addConjunctionReverseOneStep(DiagnosticBuilder &Diag, const Expr *E,
+                                         const ASTContext &Ctx,
+                                         StringRef Replacement) {
+  const auto *BO = dyn_cast<BinaryOperator>(E);
+  if (BO && BO->getOpcode() == BO_LAnd) {
+    addConjunctionReverseOneStep(Diag, BO->getLHS(), Ctx, Replacement);
+    Diag << FixItHint::CreateReplacement(BO->getOperatorLoc(), Replacement);
+    addConjunctionReverseOneStep(Diag, BO->getRHS(), Ctx, Replacement);
+  } else {
+    addReverseConditionFix(Diag, E, Ctx);
+  }
+}
+
+static void addConjunctionReverseFix(DiagnosticBuilder &Diag,
+                                     const Expr *Condition,
+                                     const ASTContext &Ctx,
+                                     StringRef Continuation) {
+  const auto *BO = dyn_cast<BinaryOperator>(Condition);
+  if (!BO || BO->getOpcode() != BO_LAnd) {
+    addReverseConditionFix(Diag, Condition, Ctx);
+    return;
+  }
+  addConjunctionReverseOneStep(Diag, Condition, Ctx,
+                               SmallString<32>({") ", Continuation, "\nif ("}));
+}
+
+static llvm::iterator_range<CompoundStmt::const_reverse_body_iterator>
+getBodyReverse(const CompoundStmt *S) {
+  return {S->body_rbegin(), S->body_rend()};
+}
+
+namespace {
+
+class EarlyExitVisitor : public RecursiveASTVisitor<EarlyExitVisitor> {
+  UseEarlyExitsCheck &Check;
+  ASTContext &Ctx;
+
+public:
+  EarlyExitVisitor(UseEarlyExitsCheck &Check, ASTContext &Ctx)
+      : Check(Check), Ctx(Ctx) {}
+
+  bool traverse() { return TraverseAST(Ctx); }
+
+  template <typename TerminationStmt>
+  void diagnoseIf(const IfStmt *If, StringRef Continuation);
+
+  template <typename TerminationStmt>
+  void checkBody(const CompoundStmt *CS, StringRef Continuation) {
+    for (const Stmt *Item : getBodyReverse(CS)) {
+      // If the last statement in the function is a return, ignore it.
+      if (isa<TerminationStmt>(Item))
+        continue;
+      // While were here, we can safely ignore empty null stmts.
+      if (isa<NullStmt>(Item) && !cast<NullStmt>(Item)->hasLeadingEmptyMacro())
+        continue;
+      if (const auto *If = dyn_cast<IfStmt>(Item))
+        diagnoseIf<TerminationStmt>(If, Continuation);
+      break;
+    }
+  }
+
+  bool VisitFunctionDecl(const FunctionDecl *Func) {
+    // Skip any declarations.
+    if (!Func->doesThisDeclarationHaveABody())
+      return true;
+    // Just ignore no return functions.
+    if (Func->isNoReturn())
+      return true;
+    // Early exit logic only works for functions that return void.
+    // FIXME: Explore options where following the IfStmt there is a return
+    // value with a literal return.
+    // bool hasSomething(Class *S) {
+    //   if (S) {
+    //     return S->hasSomething();
+    //   }
+    //   return false;
+    // }
+    // bool hasSomething(Class *S) {
+    //   if (!S)
+    //     return false;
+    //   return S->hasSomething();
+    //   return false; // Ideally this would be removed too.
+    // }
+    if (!Func->getReturnType()->isVoidType())
+      return true;
+    // FIXME: explore if CoreturnStmt could work here also.
+    checkBody<ReturnStmt>(cast<CompoundStmt>(Func->getBody()), "return");
+    return true;
+  }
+
+  bool VisitForStmt(const ForStmt *For) {
+    if (const auto *CS = dyn_cast_or_null<CompoundStmt>(For->getBody())) {
+      checkBody<ContinueStmt>(CS, "continue");
+    }
+    return true;
+  }
+
+  bool VisitWhileStmt(const WhileStmt *While) {
+    if (const auto *CS = dyn_cast_or_null<CompoundStmt>(While->getBody())) {
+      checkBody<ContinueStmt>(CS, "continue");
+    }
+    return true;
+  }
+
+  bool VisitDoStmt(const DoStmt *Do) {
+    if (const auto *CS = dyn_cast_or_null<CompoundStmt>(Do->getBody())) {
+      checkBody<ContinueStmt>(CS, "continue");
+    }
+    return true;
+  }
+
+  bool VisitCXXForRangeStmt(const CXXForRangeStmt *ForRange) {
+    if (const auto *CS = dyn_cast_or_null<CompoundStmt>(ForRange->getBody())) {
+      checkBody<ContinueStmt>(CS, "continue");
+    }
+    return true;
+  }
+};
+
+template <typename TerminationStmt>
+void EarlyExitVisitor::diagnoseIf(const IfStmt *If, StringRef Continuation) {
+  // Ignore const(expr|eval) if statements.
+  if (If->isConsteval() || If->isConstexpr())
+    return;
+  // We can't transform if there is an else.
+  if (If->hasElseStorage())
+    return;
+  // If there is a variable in the condition, This transformation would mean it
+  // goes out of scope before the current then branch can use it.
+  if (If->hasVarStorage())
+    return;
+  // As above, only technically if the init doesn't actually have any decls, we
+  // could still do the transformation. But thats not exactly a common idiom.
+  if (If->hasInitStorage())
+    return;
+  const auto *BodyCS = llvm::dyn_cast_or_null<CompoundStmt>(If->getThen());
+  if (!BodyCS)
+    return;
+  const SourceManager &SM = Ctx.getSourceManager();
+  SourceLocation Begin = If->getBeginLoc();
+  SourceLocation End = If->getEndLoc();
+  FileID BeginFile = SM.getFileID(Begin);
+  FileID EndFile = SM.getFileID(End);
+  if (BeginFile != EndFile)
+    return;
+  unsigned BeginLine = SM.getSpellingLineNumber(Begin);
+  unsigned EndLine = SM.getSpellingLineNumber(End);
+  if (BeginLine > EndLine)
+    return; // This case can't be good.
+  if ((EndLine - BeginLine) < Check.getLineCountThreshold())
+    return;
+  {
+    SmallString<32> C2({(Check.getWrapInBraces() ? "{\n" : "\n"), Continuation,
+                        Check.getWrapInBraces() ? ";\n}" : ";\n"});
+    auto Diag = Check.diag(If->getBeginLoc(), "use early exit")
+                << FixItHint::CreateInsertion(If->getThen()->getBeginLoc(), C2)
+                << FixItHint::CreateRemoval(BodyCS->getLBracLoc())
+                << FixItHint::CreateRemoval(BodyCS->getRBracLoc());
+    if (Check.getSplitConjunctions())
+      addConjunctionReverseFix(Diag, If->getCond(), Ctx, C2);
+    else
+      addReverseConditionFix(Diag, If->getCond(), Ctx);
+  }
+  checkBody<TerminationStmt>(BodyCS, Continuation);
+}
+} // namespace
+
+static bool fallbackBracesCheckActive(llvm::Optional<bool> Cur,
+                                      ClangTidyContext *Context) {
+  if (Cur)
+    return *Cur;
+  static constexpr size_t BSize = 64; // Should only ever need 63 chars here.
+  char Buff[BSize];
+  constexpr llvm::StringLiteral OptName = ".ShortStatementLines";
+  constexpr llvm::StringLiteral CheckAliases[] = {
+      "google-readability-braces-around-statements",
+      "readability-braces-around-statements", "hicpp-braces-around-statements"};
+  for (StringRef CheckAlias : CheckAliases) {
+    if (!Context->isCheckEnabled(CheckAlias))
+      continue;
+    assert(BSize >= CheckAlias.size() + OptName.size());
+    memcpy(Buff, CheckAlias.data(), CheckAlias.size());
+    memcpy(Buff + CheckAlias.size(), OptName.data(), OptName.size());
+    auto Iter = Context->getOptions().CheckOptions.find(
+        {Buff, CheckAlias.size() + OptName.size()});
+    if (Iter == Context->getOptions().CheckOptions.end())
+      return true;
+    unsigned Value;
+    if (StringRef(Iter->getValue().Value).getAsInteger(10, Value) || Value < 2)
+      return true;
+  }
+  return false;
+}
+
+UseEarlyExitsCheck::UseEarlyExitsCheck(StringRef Name,
+                                       ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      LineCountThreshold(Options.get("LineCountThreshold", 10U)),
+      SplitConjunctions(Options.get("SplitConjunctions", false)),
+      WrapInBraces(fallbackBracesCheckActive(Options.get<bool>("WrapInBraces"),
+                                             Context)) {}
+
+void UseEarlyExitsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "LineCountThreshold", LineCountThreshold);
+  Options.store(Opts, "SplitConjunctions", SplitConjunctions);
+  Options.store(Opts, "WrapInBraces", WrapInBraces);
+}
+
+void UseEarlyExitsCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(translationUnitDecl(), this);
+}
+
+void UseEarlyExitsCheck::check(const MatchFinder::MatchResult &Result) {
+  EarlyExitVisitor(*this, *Result.Context).traverse();
+}
+} // namespace readability
+} // namespace tidy
+} // namespace clang
Index: clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
===================================================================
--- clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -51,6 +51,7 @@
 #include "UniqueptrDeleteReleaseCheck.h"
 #include "UppercaseLiteralSuffixCheck.h"
 #include "UseAnyOfAllOfCheck.h"
+#include "UseEarlyExitsCheck.h"
 
 namespace clang {
 namespace tidy {
@@ -143,6 +144,8 @@
         "readability-uppercase-literal-suffix");
     CheckFactories.registerCheck<UseAnyOfAllOfCheck>(
         "readability-use-anyofallof");
+    CheckFactories.registerCheck<UseEarlyExitsCheck>(
+        "readability-use-early-exits");
   }
 };
 
Index: clang-tools-extra/clang-tidy/readability/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/readability/CMakeLists.txt
+++ clang-tools-extra/clang-tidy/readability/CMakeLists.txt
@@ -48,6 +48,7 @@
   UniqueptrDeleteReleaseCheck.cpp
   UppercaseLiteralSuffixCheck.cpp
   UseAnyOfAllOfCheck.cpp
+  UseEarlyExitsCheck.cpp
 
   LINK_LIBS
   clangTidy
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to