donat.nagy updated this revision to Diff 175502. donat.nagy added a comment.
Remove braces, move if parent checking to ASTMatcher, other minor improvements Repository: rCTE Clang Tools Extra CHANGES SINCE LAST ACTION https://reviews.llvm.org/D54757/new/ https://reviews.llvm.org/D54757 Files: clang-tidy/bugprone/BranchCloneCheck.cpp clang-tidy/bugprone/BranchCloneCheck.h clang-tidy/bugprone/BugproneTidyModule.cpp clang-tidy/bugprone/CMakeLists.txt docs/ReleaseNotes.rst docs/clang-tidy/checks/bugprone-branch-clone.rst docs/clang-tidy/checks/list.rst test/clang-tidy/bugprone-branch-clone.cpp
Index: test/clang-tidy/bugprone-branch-clone.cpp =================================================================== --- /dev/null +++ test/clang-tidy/bugprone-branch-clone.cpp @@ -0,0 +1,760 @@ +// 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; +} + + +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); + } +} + +// We are not checking ternary operators (for now). +int ternary1(bool b, int x) { + return b ? x : x; +} + +int ternary2(bool b, int x) { + return b ? 42 : 42; +} Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -9,8 +9,8 @@ abseil-no-internal-dependencies abseil-no-namespace abseil-redundant-strcat-calls - abseil-string-find-startswith abseil-str-cat-append + abseil-string-find-startswith android-cloexec-accept android-cloexec-accept4 android-cloexec-creat @@ -28,6 +28,7 @@ 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 Index: docs/clang-tidy/checks/bugprone-branch-clone.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/bugprone-branch-clone.rst @@ -0,0 +1,77 @@ +.. title:: clang-tidy - bugprone-branch-clone + +bugprone-branch-clone +===================== + +Checks for repeated branches in ``if/else if/else`` chains and consecutive +repeated branches in ``switch`` statements. + + .. 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 checker 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 checker 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. Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -103,6 +103,12 @@ Flags uses of ``absl::StrCat()`` to append to a ``std::string``. Suggests ``absl::StrAppend()`` should be used instead. +- New :doc:`bugprone-branch-clone + <clang-tidy/checks/bugprone-branch-clone>` check. + + Checks for repeated branches in ``if/else if/else`` chains and consecutive + repeated branches in ``switch`` statements. + - New :doc:`modernize-concat-nested-namespaces <clang-tidy/checks/modernize-concat-nested-namespaces>` check. Index: clang-tidy/bugprone/CMakeLists.txt =================================================================== --- clang-tidy/bugprone/CMakeLists.txt +++ clang-tidy/bugprone/CMakeLists.txt @@ -4,6 +4,7 @@ ArgumentCommentCheck.cpp AssertSideEffectCheck.cpp BoolPointerImplicitConversionCheck.cpp + BranchCloneCheck.cpp BugproneTidyModule.cpp CopyConstructorInitCheck.cpp DanglingHandleCheck.cpp Index: clang-tidy/bugprone/BugproneTidyModule.cpp =================================================================== --- clang-tidy/bugprone/BugproneTidyModule.cpp +++ clang-tidy/bugprone/BugproneTidyModule.cpp @@ -14,6 +14,7 @@ #include "ArgumentCommentCheck.h" #include "AssertSideEffectCheck.h" #include "BoolPointerImplicitConversionCheck.h" +#include "BranchCloneCheck.h" #include "CopyConstructorInitCheck.h" #include "DanglingHandleCheck.h" #include "ExceptionEscapeCheck.h" @@ -64,6 +65,8 @@ "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>( Index: clang-tidy/bugprone/BranchCloneCheck.h =================================================================== --- /dev/null +++ clang-tidy/bugprone/BranchCloneCheck.h @@ -0,0 +1,38 @@ +//===--- 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) and for +/// detecting switch statements where two or more consecutive branches 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 Index: clang-tidy/bugprone/BranchCloneCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/bugprone/BranchCloneCheck.cpp @@ -0,0 +1,203 @@ +//===--- 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); +} + +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!"); + + // If there is no `else` branch, we may exit early because there is no + // chance of duplicated branches. + const Stmt *Else = Result.Nodes.getNodeAs<Stmt>("else"); + if (!Else) + return; + + 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()); + + // The chain ends if there is no `else` branch. + Else = Cur->getElse(); + if (!Else) + break; + + // Check if there is another `else if`... + Cur = dyn_cast<IfStmt>(Else); + if (!Cur) { + // ...this is just a plain `else` branch, concluding 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); + } + } + } else 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)); + diag(Lexer::getLocForEndOfToken((EndCurrent - 1)->back()->getEndLoc(), 0, + *Result.SourceManager, getLangOpts()), + "last of these clones ends here", DiagnosticIDs::Note); + } + BeginCurrent = EndCurrent; + } + } else { + llvm_unreachable("No if statement and no switch statement."); + } +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits