Author: szelethus Date: Wed May 15 08:06:25 2019 New Revision: 360779 URL: http://llvm.org/viewvc/llvm-project?rev=360779&view=rev Log: [clang-tidy] new check: bugprone-branch-clone
Implement a check for detecting if/else if/else chains where two or more branches are Type I clones of each other (that is, they contain identical code) and for detecting switch statements where two or more consecutive branches are Type I clones of each other. Patch by DonĂ¡t Nagy! Differential Revision: https://reviews.llvm.org/D54757 Added: clang-tools-extra/trunk/clang-tidy/bugprone/BranchCloneCheck.cpp clang-tools-extra/trunk/clang-tidy/bugprone/BranchCloneCheck.h clang-tools-extra/trunk/docs/clang-tidy/checks/bugprone-branch-clone.rst clang-tools-extra/trunk/test/clang-tidy/bugprone-branch-clone.cpp Modified: clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt clang-tools-extra/trunk/docs/ReleaseNotes.rst clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst Added: clang-tools-extra/trunk/clang-tidy/bugprone/BranchCloneCheck.cpp URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/BranchCloneCheck.cpp?rev=360779&view=auto ============================================================================== --- clang-tools-extra/trunk/clang-tidy/bugprone/BranchCloneCheck.cpp (added) +++ clang-tools-extra/trunk/clang-tidy/bugprone/BranchCloneCheck.cpp Wed May 15 08:06:25 2019 @@ -0,0 +1,226 @@ +//===--- BranchCloneCheck.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 "BranchCloneCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/CloneDetection.h" +#include "llvm/Support/Casting.h" + +using namespace clang; +using namespace clang::ast_matchers; + +/// Returns true when the statements are Type I clones of each other. +static bool areStatementsIdentical(const Stmt *LHS, const Stmt *RHS, + const ASTContext &Context) { + llvm::FoldingSetNodeID DataLHS, DataRHS; + LHS->Profile(DataLHS, Context, false); + RHS->Profile(DataRHS, Context, false); + return (DataLHS == DataRHS); +} + +namespace { +/// A branch in a switch may consist of several statements; while a branch in +/// an if/else if/else chain is one statement (which may be a CompoundStmt). +using SwitchBranch = llvm::SmallVector<const Stmt *, 2>; +} // anonymous namespace + +/// Determines if the bodies of two branches in a switch statements are Type I +/// clones of each other. This function only examines the body of the branch +/// and ignores the `case X:` or `default:` at the start of the branch. +static bool areSwitchBranchesIdentical(const SwitchBranch LHS, + const SwitchBranch RHS, + const ASTContext &Context) { + if (LHS.size() != RHS.size()) + return false; + + for (size_t i = 0, Size = LHS.size(); i < Size; i++) { + // NOTE: We strip goto labels and annotations in addition to stripping + // the `case X:` or `default:` labels, but it is very unlikely that this + // would casue false positives in real-world code. + if (!areStatementsIdentical(LHS[i]->stripLabelLikeStatements(), + RHS[i]->stripLabelLikeStatements(), Context)) { + return false; + } + } + + return true; +} + +namespace clang { +namespace tidy { +namespace bugprone { + +void BranchCloneCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + ifStmt(stmt().bind("if"), + hasParent(stmt(unless(ifStmt(hasElse(equalsBoundNode("if")))))), + hasElse(stmt().bind("else"))), + this); + Finder->addMatcher(switchStmt().bind("switch"), this); + Finder->addMatcher(conditionalOperator().bind("condOp"), this); +} + +void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) { + const ASTContext &Context = *Result.Context; + + if (const auto *IS = Result.Nodes.getNodeAs<IfStmt>("if")) { + const Stmt *Then = IS->getThen(); + assert(Then && "An IfStmt must have a `then` branch!"); + + const Stmt *Else = Result.Nodes.getNodeAs<Stmt>("else"); + assert(Else && "We only look for `if` statements with an `else` branch!"); + + if (!isa<IfStmt>(Else)) { + // Just a simple if with no `else if` branch. + if (areStatementsIdentical(Then->IgnoreContainers(), + Else->IgnoreContainers(), Context)) { + diag(IS->getBeginLoc(), "if with identical then and else branches"); + diag(IS->getElseLoc(), "else branch starts here", DiagnosticIDs::Note); + } + return; + } + + // This is the complicated case when we start an if/else if/else chain. + // To find all the duplicates, we collect all the branches into a vector. + llvm::SmallVector<const Stmt *, 4> Branches; + const IfStmt *Cur = IS; + while (true) { + // Store the `then` branch. + Branches.push_back(Cur->getThen()); + + Else = Cur->getElse(); + // The chain ends if there is no `else` branch. + if (!Else) + break; + + // Check if there is another `else if`... + Cur = dyn_cast<IfStmt>(Else); + if (!Cur) { + // ...this is just a plain `else` branch at the end of the chain. + Branches.push_back(Else); + break; + } + } + + size_t N = Branches.size(); + llvm::BitVector KnownAsClone(N); + + for (size_t i = 0; i + 1 < N; i++) { + // We have already seen Branches[i] as a clone of an earlier branch. + if (KnownAsClone[i]) + continue; + + int NumCopies = 1; + + for (size_t j = i + 1; j < N; j++) { + if (KnownAsClone[j] || + !areStatementsIdentical(Branches[i]->IgnoreContainers(), + Branches[j]->IgnoreContainers(), Context)) + continue; + + NumCopies++; + KnownAsClone[j] = true; + + if (NumCopies == 2) { + // We report the first occurence only when we find the second one. + diag(Branches[i]->getBeginLoc(), + "repeated branch in conditional chain"); + diag(Lexer::getLocForEndOfToken(Branches[i]->getEndLoc(), 0, + *Result.SourceManager, getLangOpts()), + "end of the original", DiagnosticIDs::Note); + } + + diag(Branches[j]->getBeginLoc(), "clone %0 starts here", + DiagnosticIDs::Note) + << (NumCopies - 1); + } + } + return; + } + + if (const auto *CO = Result.Nodes.getNodeAs<ConditionalOperator>("condOp")) { + // We do not try to detect chains of ?: operators. + if (areStatementsIdentical(CO->getTrueExpr(), CO->getFalseExpr(), Context)) + diag(CO->getQuestionLoc(), + "conditional operator with identical true and false expressions"); + + return; + } + + if (const auto *SS = Result.Nodes.getNodeAs<SwitchStmt>("switch")) { + const CompoundStmt *Body = dyn_cast_or_null<CompoundStmt>(SS->getBody()); + + // Code like + // switch (x) case 0: case 1: foobar(); + // is legal and calls foobar() if and only if x is either 0 or 1; + // but we do not try to distinguish branches in such code. + if (!Body) + return; + + // We will first collect the branches of the switch statements. For the + // sake of simplicity we say that branches are delimited by the SwitchCase + // (`case:` or `default:`) children of Body; that is, we ignore `case:` or + // `default:` labels embedded inside other statements and we do not follow + // the effects of `break` and other manipulation of the control-flow. + llvm::SmallVector<SwitchBranch, 4> Branches; + for (const Stmt *S : Body->body()) { + // If this is a `case` or `default`, we start a new, empty branch. + if (isa<SwitchCase>(S)) + Branches.emplace_back(); + + // There may be code before the first branch (which can be dead code + // and can be code reached either through goto or through case labels + // that are embedded inside e.g. inner compound statements); we do not + // store those statements in branches. + if (!Branches.empty()) + Branches.back().push_back(S); + } + + auto End = Branches.end(); + auto BeginCurrent = Branches.begin(); + while (BeginCurrent < End) { + auto EndCurrent = BeginCurrent + 1; + while (EndCurrent < End && + areSwitchBranchesIdentical(*BeginCurrent, *EndCurrent, Context)) { + ++EndCurrent; + } + // At this point the iterator range {BeginCurrent, EndCurrent} contains a + // complete family of consecutive identical branches. + if (EndCurrent > BeginCurrent + 1) { + diag(BeginCurrent->front()->getBeginLoc(), + "switch has %0 consecutive identical branches") + << static_cast<int>(std::distance(BeginCurrent, EndCurrent)); + + SourceLocation EndLoc = (EndCurrent - 1)->back()->getEndLoc(); + // If the case statement is generated from a macro, it's SourceLocation + // may be invalid, resuling in an assertation failure down the line. + // While not optimal, try the begin location in this case, it's still + // better then nothing. + if (EndLoc.isInvalid()) + EndLoc = (EndCurrent - 1)->back()->getBeginLoc(); + + if (EndLoc.isMacroID()) + EndLoc = Context.getSourceManager().getExpansionLoc(EndLoc); + + diag(Lexer::getLocForEndOfToken(EndLoc, 0, *Result.SourceManager, + getLangOpts()), + "last of these clones ends here", DiagnosticIDs::Note); + } + BeginCurrent = EndCurrent; + } + return; + } + + llvm_unreachable("No if statement and no switch statement."); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang Added: clang-tools-extra/trunk/clang-tidy/bugprone/BranchCloneCheck.h URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/BranchCloneCheck.h?rev=360779&view=auto ============================================================================== --- clang-tools-extra/trunk/clang-tidy/bugprone/BranchCloneCheck.h (added) +++ clang-tools-extra/trunk/clang-tidy/bugprone/BranchCloneCheck.h Wed May 15 08:06:25 2019 @@ -0,0 +1,39 @@ +//===--- BranchCloneCheck.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_BUGPRONE_BRANCHCLONECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BRANCHCLONECHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// A check for detecting if/else if/else chains where two or more branches are +/// Type I clones of each other (that is, they contain identical code), for +/// detecting switch statements where two or more consecutive branches are +/// Type I clones of each other, and for detecting conditional operators where +/// the true and false expressions are Type I clones of each other. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone-branch-clone.html +class BranchCloneCheck : public ClangTidyCheck { +public: + BranchCloneCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BRANCHCLONECHECK_H Modified: clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp?rev=360779&r1=360778&r2=360779&view=diff ============================================================================== --- clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp (original) +++ clang-tools-extra/trunk/clang-tidy/bugprone/BugproneTidyModule.cpp Wed May 15 08:06:25 2019 @@ -13,6 +13,7 @@ #include "ArgumentCommentCheck.h" #include "AssertSideEffectCheck.h" #include "BoolPointerImplicitConversionCheck.h" +#include "BranchCloneCheck.h" #include "CopyConstructorInitCheck.h" #include "DanglingHandleCheck.h" #include "ExceptionEscapeCheck.h" @@ -65,6 +66,8 @@ public: "bugprone-assert-side-effect"); CheckFactories.registerCheck<BoolPointerImplicitConversionCheck>( "bugprone-bool-pointer-implicit-conversion"); + CheckFactories.registerCheck<BranchCloneCheck>( + "bugprone-branch-clone"); CheckFactories.registerCheck<CopyConstructorInitCheck>( "bugprone-copy-constructor-init"); CheckFactories.registerCheck<DanglingHandleCheck>( Modified: clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt?rev=360779&r1=360778&r2=360779&view=diff ============================================================================== --- clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt (original) +++ clang-tools-extra/trunk/clang-tidy/bugprone/CMakeLists.txt Wed May 15 08:06:25 2019 @@ -4,6 +4,7 @@ add_clang_library(clangTidyBugproneModul ArgumentCommentCheck.cpp AssertSideEffectCheck.cpp BoolPointerImplicitConversionCheck.cpp + BranchCloneCheck.cpp BugproneTidyModule.cpp CopyConstructorInitCheck.cpp DanglingHandleCheck.cpp Modified: clang-tools-extra/trunk/docs/ReleaseNotes.rst URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/ReleaseNotes.rst?rev=360779&r1=360778&r2=360779&view=diff ============================================================================== --- clang-tools-extra/trunk/docs/ReleaseNotes.rst (original) +++ clang-tools-extra/trunk/docs/ReleaseNotes.rst Wed May 15 08:06:25 2019 @@ -108,6 +108,13 @@ Improvements to clang-tidy against self-assignment either by checking self-assignment explicitly or using the copy-and-swap or the copy-and-move method. +- New :doc:`bugprone-branch-clone + <clang-tidy/checks/bugprone-branch-clone>` check. + + Checks for repeated branches in ``if/else if/else`` chains, consecutive + repeated branches in ``switch`` statements and indentical true and false + branches in conditional operators. + - New :doc:`google-readability-avoid-underscore-in-googletest-name <clang-tidy/checks/google-readability-avoid-underscore-in-googletest-name>` check. Added: clang-tools-extra/trunk/docs/clang-tidy/checks/bugprone-branch-clone.rst URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/clang-tidy/checks/bugprone-branch-clone.rst?rev=360779&view=auto ============================================================================== --- clang-tools-extra/trunk/docs/clang-tidy/checks/bugprone-branch-clone.rst (added) +++ clang-tools-extra/trunk/docs/clang-tidy/checks/bugprone-branch-clone.rst Wed May 15 08:06:25 2019 @@ -0,0 +1,90 @@ +.. title:: clang-tidy - bugprone-branch-clone + +bugprone-branch-clone +===================== + +Checks for repeated branches in ``if/else if/else`` chains, consecutive +repeated branches in ``switch`` statements and indentical true and false +branches in conditional operators. + +.. code-block:: c++ + + if (test_value(x)) { + y++; + do_something(x, y); + } else { + y++; + do_something(x, y); + } + +In this simple example (which could arise e.g. as a copy-paste error) the +``then`` and ``else`` branches are identical and the code is equivalent the +following shorter and cleaner code: + +.. code-block:: c++ + + test_value(x); // can be omitted unless it has side effects + y++; + do_something(x, y); + + +If this is the inteded behavior, then there is no reason to use a conditional +statement; otherwise the issue can be solved by fixing the branch that is +handled incorrectly. + +The check also detects repeated branches in longer ``if/else if/else`` chains +where it would be even harder to notice the problem. + +In ``switch`` statements the check only reports repeated branches when they are +consecutive, because it is relatively common that the ``case:`` labels have +some natural ordering and rearranging them would decrease the readability of +the code. For example: + +.. code-block:: c++ + + switch (ch) { + case 'a': + return 10; + case 'A': + return 10; + case 'b': + return 11; + case 'B': + return 11; + default: + return 10; + } + +Here the check reports that the ``'a'`` and ``'A'`` branches are identical +(and that the ``'b'`` and ``'B'`` branches are also identical), but does not +report that the ``default:`` branch is also idenical to the first two branches. +If this is indeed the correct behavior, then it could be implemented as: + +.. code-block:: c++ + + switch (ch) { + case 'a': + case 'A': + return 10; + case 'b': + case 'B': + return 11; + default: + return 10; + } + +Here the check does not warn for the repeated ``return 10;``, which is good if +we want to preserve that ``'a'`` is before ``'b'`` and ``default:`` is the last +branch. + +Finally, the check also examines conditional operators and reports code like: + +.. code-block:: c++ + + return test_value(x) ? x : x; + +Unlike if statements, the check does not detect chains of conditional +operators. + +Note: This check also reports situations where branches become identical only +after preprocession. Modified: clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst?rev=360779&r1=360778&r2=360779&view=diff ============================================================================== --- clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst (original) +++ clang-tools-extra/trunk/docs/clang-tidy/checks/list.rst Wed May 15 08:06:25 2019 @@ -38,6 +38,7 @@ Clang-Tidy Checks bugprone-argument-comment bugprone-assert-side-effect bugprone-bool-pointer-implicit-conversion + bugprone-branch-clone bugprone-copy-constructor-init bugprone-dangling-handle bugprone-exception-escape Added: clang-tools-extra/trunk/test/clang-tidy/bugprone-branch-clone.cpp URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clang-tidy/bugprone-branch-clone.cpp?rev=360779&view=auto ============================================================================== --- clang-tools-extra/trunk/test/clang-tidy/bugprone-branch-clone.cpp (added) +++ clang-tools-extra/trunk/test/clang-tidy/bugprone-branch-clone.cpp Wed May 15 08:06:25 2019 @@ -0,0 +1,1026 @@ +// RUN: %check_clang_tidy %s bugprone-branch-clone %t + +void test_basic1(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + out++; + + out++; +} + +void test_basic2(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + } + else { +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + out++; + } + + out++; +} + +void test_basic3(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + } + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + out++; + + out++; +} + +void test_basic4(int in, int &out) { + if (in > 77) { + out--; + } + else { + out++; + } +} + +void test_basic5(int in, int &out) { + if (in > 77) { + out++; + } + else { + out++; + out++; + } +} + +void test_basic6(int in, int &out) { + if (in > 77) { + out++; + } + else { + out++, out++; + } +} + +void test_basic7(int in, int &out) { + if (in > 77) { + out++; + out++; + } + else + out++; + + out++; +} + +void test_basic8(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + out++; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + out++; + out++; + } + + if (in % 2) + out++; +} + +void test_basic9(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + if (in % 2) + out++; + else + out--; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + if (in % 2) + out++; + else + out--; + } +} + +// If we remove the braces from the previous example, the check recognizes it +// as an `else if`. +void test_basic10(int in, int &out) { + if (in > 77) + if (in % 2) + out++; + else + out--; + else + if (in % 2) + out++; + else + out--; + +} + +void test_basic11(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + if (in % 2) + out++; + else + out--; + if (in % 3) + out++; + else + out--; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + if (in % 2) + out++; + else + out--; + if (in % 3) + out++; + else + out--; + } +} + +void test_basic12(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + } +} + +void test_basic13(int in, int &out) { + if (in > 77) { + // Empty compound statement is not identical to null statement. + } else; +} + +// We use a comparison that ignores redundant parentheses: +void test_basic14(int in, int &out) { + if (in > 77) + out += 2; + else + (out) += (2); +} + +void test_basic15(int in, int &out) { + if (in > 77) + ((out += 2)); + else + out += 2; +} + +// ..but does not apply additional simplifications: +void test_basic16(int in, int &out) { + if (in > 77) + out += 2; + else + out += 1 + 1; +} + +// ..and does not forget important parentheses: +int test_basic17(int a, int b, int c, int mode) { + if (mode>8) + return (a + b) * c; + else + return a + b * c; +} + +//=========--------------------==========// + +#define PASTE_CODE(x) x + +void test_macro1(int in, int &out) { + PASTE_CODE( + if (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + out++; + ) + + out--; +} + +void test_macro2(int in, int &out) { + PASTE_CODE( + if (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + ) + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + out++; +} + +void test_macro3(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + PASTE_CODE( + else +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + out++; + ) +} + +void test_macro4(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + PASTE_CODE( + out++; + ) +} + +void test_macro5(int in, int &out) { + PASTE_CODE(if) (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + PASTE_CODE(else) +// CHECK-MESSAGES: :[[@LINE-1]]:14: note: else branch starts here + out++; +} + +#define OTHERWISE_INCREASE else out++ + +void test_macro6(int in, int &out) { + if (in > 77) + out++; +// CHECK-MESSAGES: :[[@LINE-2]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + OTHERWISE_INCREASE; +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here +// CHECK-MESSAGES: :[[@LINE-8]]:28: note: expanded from macro 'OTHERWISE_INCREASE' +} + +#define COND_INCR(a, b, c) \ + do { \ + if ((a)) \ + (b)++; \ + else \ + (c)++; \ + } while (0) + +void test_macro7(int in, int &out1, int &out2) { + COND_INCR(in, out1, out1); +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE-9]]:5: note: expanded from macro 'COND_INCR' +// CHECK-MESSAGES: :[[@LINE-3]]:3: note: else branch starts here +// CHECK-MESSAGES: :[[@LINE-9]]:5: note: expanded from macro 'COND_INCR' +} + +void test_macro8(int in, int &out1, int &out2) { + COND_INCR(in, out1, out2); +} + +void test_macro9(int in, int &out1, int &out2) { + COND_INCR(in, out2, out2); +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE-21]]:5: note: expanded from macro 'COND_INCR' +// CHECK-MESSAGES: :[[@LINE-3]]:3: note: else branch starts here +// CHECK-MESSAGES: :[[@LINE-21]]:5: note: expanded from macro 'COND_INCR' +} + +#define CONCAT(a, b) a##b + +void test_macro10(int in, int &out) { + CONCAT(i, f) (in > 77) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + CONCAT(el, se) +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + out++; +} + +#define PROBLEM (-1) + +int test_macro11(int count) { + if (!count) + return PROBLEM; + else if (count == 13) + return -1; + else + return count * 2; +} + +#define IF if ( +#define THEN ) { +#define ELSE } else { +#define END } + +void test_macro12(int in, int &out) { + IF in > 77 THEN +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE-8]]:12: note: expanded from macro 'IF' + out++; + out++; + ELSE +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here +// CHECK-MESSAGES: :[[@LINE-11]]:16: note: expanded from macro 'ELSE' + out++; + out++; + END +} + +// A hack for implementing a switch with no fallthrough: +#define SWITCH(x) switch (x) { +#define CASE(x) break; case (x): +#define DEFAULT break; default: + +void test_macro13(int in, int &out) { + SWITCH(in) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + CASE(1) + out++; + out++; + CASE(2) + out++; + out++; + CASE(3) + out++; + out++; +// CHECK-MESSAGES: :[[@LINE-15]]:24: note: expanded from macro 'CASE' +// CHECK-MESSAGES: :[[@LINE+1]]:9: note: last of these clones ends here + CASE(4) + out++; + CASE(5) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + CASE(6) + out--; + CASE(7) + out--; +// CHECK-MESSAGES: :[[@LINE-25]]:24: note: expanded from macro 'CASE' +// CHECK-MESSAGES: :[[@LINE+2]]:9: note: last of these clones ends here +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + CASE(8) + out++; + out++; + CASE(9) + out++; + out++; +// CHECK-MESSAGES: :[[@LINE-34]]:24: note: expanded from macro 'CASE' +// CHECK-MESSAGES: :[[@LINE+2]]:12: note: last of these clones ends here +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + DEFAULT + out--; + out--; + CASE(10) + out--; + out--; +// CHECK-MESSAGES: :[[@LINE-42]]:24: note: expanded from macro 'DEFAULT' +// CHECK-MESSAGES: :[[@LINE+1]]:9: note: last of these clones ends here + CASE(12) + out++; + CASE(13) + out++; + END +} + +//=========--------------------==========// + +void test_chain1(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: end of the original + else if (in > 55) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 1 starts here + out++; + + out++; +} + +void test_chain2(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: end of the original + else if (in > 55) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 1 starts here + out++; + else if (in > 42) + out--; + else if (in > 28) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 2 starts here + out++; + else if (in > 12) { + out++; + out *= 7; + } else if (in > 7) { +// CHECK-MESSAGES: :[[@LINE-1]]:22: note: clone 3 starts here + out++; + } +} + +void test_chain3(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; + out++; +// CHECK-MESSAGES: :[[@LINE+1]]:4: note: end of the original + } else if (in > 55) { +// CHECK-MESSAGES: :[[@LINE-1]]:23: note: clone 1 starts here + out++; + out++; + } else if (in > 42) + out--; + else if (in > 28) { +// CHECK-MESSAGES: :[[@LINE-1]]:21: note: clone 2 starts here + out++; + out++; + } else if (in > 12) { + out++; + out++; + out++; + out *= 7; + } else if (in > 7) { +// CHECK-MESSAGES: :[[@LINE-1]]:22: note: clone 3 starts here + out++; + out++; + } +} + +// In this chain there are two clone families; notice that the checker +// describes all branches of the first one before mentioning the second one. +void test_chain4(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; + out++; +// CHECK-MESSAGES: :[[@LINE+1]]:4: note: end of the original + } else if (in > 55) { +// CHECK-MESSAGES: :[[@LINE-1]]:23: note: clone 1 starts here +// CHECK-MESSAGES: :[[@LINE+8]]:21: note: clone 2 starts here +// CHECK-MESSAGES: :[[@LINE+15]]:22: note: clone 3 starts here + out++; + out++; + } else if (in > 42) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: repeated branch in conditional chain [bugprone-branch-clone] + out--; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: end of the original + else if (in > 28) { + out++; + out++; + } else if (in > 12) { + out++; + out++; + out++; + out *= 7; + } else if (in > 7) { + out++; + out++; + } else if (in > -3) { +// CHECK-MESSAGES: :[[@LINE-1]]:23: note: clone 1 starts here + out--; + } +} + +void test_chain5(int in, int &out) { + if (in > 77) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: end of the original + else if (in > 55) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 1 starts here + out++; + else if (in > 42) + out--; + else if (in > 28) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 2 starts here + out++; + else if (in > 12) { + out++; + out *= 7; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: clone 3 starts here + out++; + } +} + +void test_chain6(int in, int &out) { + if (in > 77) { +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; + out++; +// CHECK-MESSAGES: :[[@LINE+1]]:4: note: end of the original + } else if (in > 55) { +// CHECK-MESSAGES: :[[@LINE-1]]:23: note: clone 1 starts here + out++; + out++; + } else if (in > 42) + out--; + else if (in > 28) { +// CHECK-MESSAGES: :[[@LINE-1]]:21: note: clone 2 starts here + out++; + out++; + } else if (in > 12) { + out++; + out++; + out++; + out *= 7; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: clone 3 starts here + out++; + out++; + } +} + +void test_nested(int a, int b, int c, int &out) { + if (a > 5) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+27]]:5: note: else branch starts here + if (b > 5) { +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: repeated branch in conditional chain [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+9]]:6: note: end of the original +// CHECK-MESSAGES: :[[@LINE+8]]:24: note: clone 1 starts here +// CHECK-MESSAGES: :[[@LINE+14]]:12: note: clone 2 starts here + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } else if (b > 15) { + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } else { + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } + } else { + if (b > 5) { +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: repeated branch in conditional chain [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+9]]:6: note: end of the original +// CHECK-MESSAGES: :[[@LINE+8]]:24: note: clone 1 starts here +// CHECK-MESSAGES: :[[@LINE+14]]:12: note: clone 2 starts here + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } else if (b > 15) { + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } else { + if (c > 5) +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: if with identical then and else branches [bugprone-branch-clone] + out++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:7: note: else branch starts here + out++; + } + } +} + +//=========--------------------==========// + +template <class T> +void test_template_not_instantiated(const T &t) { + int a; + if (t) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + a++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + a++; +} + +template <class T> +void test_template_instantiated(const T &t) { + int a; + if (t) +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + a++; + else +// CHECK-MESSAGES: :[[@LINE-1]]:3: note: else branch starts here + a++; +} + +template void test_template_instantiated<int>(const int &t); + +template <class T> +void test_template2(T t, int a) { + if (a) { + T b(0); + a += b; + } else { + int b(0); + a += b; + } +} + +template void test_template2<int>(int t, int a); + +template <class T> +void test_template3(T t, int a) { + if (a) { + T b(0); + a += b; + } else { + int b(0); + a += b; + } +} + +template void test_template3<short>(short t, int a); + +template <class T> +void test_template_two_instances(T t, int &a) { + if (a) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + a += int(t); + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + a += int(t); + } +} + +template void test_template_two_instances<short>(short t, int &a); +template void test_template_two_instances<long>(long t, int &a); + +class C { + int member; + void inline_method(int arg) { + if (arg) +// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: if with identical then and else branches [bugprone-branch-clone] + member = 3; + else +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + member = 3; + } + int other_method(); +}; + +int C::other_method() { + if (member) { +// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: if with identical then and else branches [bugprone-branch-clone] + return 8; + } else { +// CHECK-MESSAGES: :[[@LINE-1]]:5: note: else branch starts here + return 8; + } +} + +//=========--------------------==========// + +int simple_switch(char ch) { + switch (ch) { +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + case 'a': + return 10; + case 'A': + return 10; +// CHECK-MESSAGES: :[[@LINE-1]]:14: note: last of these clones ends here +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + case 'b': + return 11; + case 'B': + return 11; +// CHECK-MESSAGES: :[[@LINE-1]]:14: note: last of these clones ends here +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + case 'c': + return 10; + case 'C': + return 10; +// CHECK-MESSAGES: :[[@LINE-1]]:14: note: last of these clones ends here + default: + return 0; + } +} + +int long_sequence_switch(char ch) { + switch (ch) { +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 7 consecutive identical branches [bugprone-branch-clone] + case 'a': + return 10; + case 'A': + return 10; + case 'b': + return 10; + case 'B': + return 10; + case 'c': + return 10; + case 'C': + return 10; + default: + return 10; +// CHECK-MESSAGES: :[[@LINE-1]]:14: note: last of these clones ends here + } +} + +int nested_switch(int a, int b, int c) { + switch (a) { +// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+114]]:6: note: last of these clones ends here + case 1: + switch (b) { +// CHECK-MESSAGES: :[[@LINE+2]]:5: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+33]]:8: note: last of these clones ends here + case 1: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + case 2: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + default: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + } + case 2: + switch (b) { +// CHECK-MESSAGES: :[[@LINE+2]]:5: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+33]]:8: note: last of these clones ends here + case 1: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + case 2: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + default: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + } + default: + switch (b) { +// CHECK-MESSAGES: :[[@LINE+2]]:5: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] +// CHECK-MESSAGES: :[[@LINE+33]]:8: note: last of these clones ends here + case 1: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + case 2: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + default: + switch (c) { +// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: switch has 3 consecutive identical branches [bugprone-branch-clone] + case 1: + return 42; + case 2: + return 42; + default: + return 42; +// CHECK-MESSAGES: :[[@LINE-1]]:18: note: last of these clones ends here + } + } + } +} + +//=========--------------------==========// + +// This should not produce warnings, as in switch statements we only report +// identical branches when they are consecutive. Also note that a branch +// terminated by a break is different from a branch terminated by the end of +// the switch statement. +int interleaved_cases(int a, int b) { + switch (a) { + case 3: + case 4: + b = 2; + break; + case 5: + b = 3; + break; + case 6: + b = 2; + break; + case 7: + if (b % 2) { + b++; + } else { + b++; + break; + } + b = 2; + break; + case 8: + b = 2; + case 9: + b = 3; + break; + default: + b = 3; + } + return b; +} + + +// A case: or default: is only considered to be the start of a branch if it is a direct child of the CompoundStmt forming the body of the switch +int buried_cases(int foo) { + switch (foo) { + { + case 36: + return 8; + default: + return 8; + } + } +} + +// Here the `case 7:` is a child statement of the GotoLabelStmt, so the checker +// thinks that it is part of the `case 9:` branch. While this result is +// counterintuitve, mixing goto labels and switch statements in this fashion is +// pretty rare, so it does not deserve a special case in the checker code. +int decorated_cases(int z) { + if (!(z % 777)) { + goto lucky; + } + switch (z) { +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + case 1: + case 2: + case 3: + z++; + break; + case 4: + case 5: + z++; + break; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: last of these clones ends here + case 9: + z++; + break; + lucky: + case 7: + z += 3; + z *= 2; + break; + case 92: + z += 3; + z *= 2; + break; + default: + z++; + } + return z + 92; +} + +// The child of the switch statement is not neccessarily a compound statement, +// do not crash in this unusual case. +char no_real_body(int in, int &out) { + switch (in) + case 42: + return 'A'; + + if (in > 77) +// CHECK-MESSAGES: :[[@LINE+1]]:5: warning: repeated branch in conditional chain [bugprone-branch-clone] + out++; +// CHECK-MESSAGES: :[[@LINE-1]]:10: note: end of the original + else if (in > 55) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 1 starts here + out++; + else if (in > 34) +// CHECK-MESSAGES: :[[@LINE+1]]:5: note: clone 2 starts here + out++; + + return '|'; +} + +// Duff's device [https://en.wikipedia.org/wiki/Duff's_device] +// The check does not try to distinguish branches in this sort of convoluted +// code, but it should avoid crashing. +void send(short *to, short *from, int count) +{ + int n = (count + 7) / 8; + switch (count % 8) { + case 0: do { *to = *from++; + case 7: *to = *from++; + case 6: *to = *from++; + case 5: *to = *from++; + case 4: *to = *from++; + case 3: *to = *from++; + case 2: *to = *from++; + case 1: *to = *from++; + } while (--n > 0); + } +} + +//=========--------------------==========// + +void ternary1(bool b, int &x) { +// CHECK-MESSAGES: :[[@LINE+1]]:6: warning: conditional operator with identical true and false expressions [bugprone-branch-clone] + (b ? x : x) = 42; +} + +int ternary2(bool b, int x) { +// CHECK-MESSAGES: :[[@LINE+1]]:12: warning: conditional operator with identical true and false expressions [bugprone-branch-clone] + return b ? 42 : 42; +} + +int ternary3(bool b, int x) { + return b ? 42 : 43; +} + +int ternary4(bool b, int x) { + return b ? true ? 45 : 44 : false ? 45 : 44; +} + +// We do not detect chains of conditional operators. +int ternary5(bool b1, bool b2, int x) { + return b1 ? 42 : b2 ? 43 : 42; +} + +#define SWITCH_WITH_LBRACE(b) switch (b) { +#define SEMICOLON_CASE_COLON(b) \ + ; \ + case b: +int d, e; +void dontCrash() { + SWITCH_WITH_LBRACE(d) +// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: switch has 2 consecutive identical branches [bugprone-branch-clone] + SEMICOLON_CASE_COLON(1) + e++; + e++; + SEMICOLON_CASE_COLON(2) + e++; + e++; + // CHECK-MESSAGES: :[[@LINE-11]]:3: note: expanded from macro 'SEMICOLON_CASE_COLON' + // CHECK-MESSAGES: :[[@LINE+1]]:23: note: last of these clones ends here + SEMICOLON_CASE_COLON(3); + } +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits