This is an archive of the discontinued LLVM Phabricator instance.

[clang-tidy] Add check misc-braces-around-statements.
ClosedPublic

Authored by curdeius on Sep 18 2014, 7:45 AM.

Details

Reviewers
alexfh
Summary

This check looks for if statements and loops: for, range-for, while and do-while, and verifies that the inside statements are inside braces '{}'.
If not, proposes to add braces around them.

Example:

if (condition)
  action();

becomes

if (condition) {
  action();
}

This check ought to be used with the -format option, so that the braces be well-formatted.

Diff Detail

Event Timeline

curdeius updated this revision to Diff 13830.Sep 18 2014, 7:45 AM
curdeius retitled this revision from to Add check misc-braces-around-statements..
curdeius updated this object.
curdeius edited the test plan for this revision. (Show Details)
curdeius added subscribers: Unknown Object (MLST), curdeius.Sep 18 2014, 7:49 AM
alexfh added a subscriber: alexfh.Sep 19 2014, 1:46 AM

Thanks for working on this check!

Please try the suggestions in my comments and see whether it helps to resolve some of your issues.

clang-tidy/misc/BracesAroundStatementsCheck.cpp
72

Both options are wrong. You don't need to specify Offset explicitly. getLocEnd() returns the location of the first character of the last token, so you need to use Lexer::getLocForEndOfToken(S->getLocEnd()), which will get you a location right after the last token of the statement.

Also, if there's an else/while();, I'd better you put the closing brace right before them in case there are comments between the statement and else/while();.

Addressing this and the next comment will make it possible to maintain the LLVM-like style of arranging braces even without reformatting the changes.

75

If this check aims to work well for styles similar to the one used in LLVM, it needs to insert the opening brace right after the if()/else/for()/while()/do, in case there's a comment between if()/... and the statement, as clang-format doesn't rearrange tokens.

test/clang-tidy/misc-braces-around-statements.cpp
12

We don't usually check these notes, but I don't object, if you leave these checks.

27

Any reason for this #if? The check_clang_tidy_fix.sh script always runs clang-tidy with -std=c++11.

alexfh removed a subscriber: alexfh.
alexfh added inline comments.Sep 19 2014, 3:54 AM
clang-tidy/misc/BracesAroundStatementsCheck.cpp
72

And of course I meant:

SourceLocation EndLoc = Lexer::getLocForEndOfToken(
    S->getLocEnd(), 0, *Result.SourceManager,
    Result.Context->getLangOpts());
curdeius updated this revision to Diff 13878.Sep 19 2014, 9:20 AM
  • Fix: BracesAroundStatementsCheck.
curdeius updated this object.Sep 19 2014, 9:20 AM
curdeius updated this object.
alexfh edited edge metadata.Sep 22 2014, 2:57 AM

That's better, but still a few comments.

clang-tidy/misc/BracesAroundStatementsCheck.cpp
19

Please remove the empty line.

31

You can remove the "ast_matchers::" part as there's a "using namespace clang::ast_matchers;" above.

33

This is a perfect use case for "auto". The type is spelled on the RHS of the initialization, so you can safely skip it in the LHS.

34

I think, you can pass the starting location of the last token to checkStmt and call Lexer::getLocForEndOfToken inside the function. Then all these invocations will look more uniform and the total amount of code is going to be less.

56

Any reasons not to use isa<IfStmt>(Else)? This seems to be clearer to me.

83

If you insert " {", the result will be one step closer to correct formatting.

clang-tidy/misc/BracesAroundStatementsCheck.h
2

Please respect the 80 columns limit.

curdeius updated this revision to Diff 13932.Sep 22 2014, 7:20 AM
curdeius edited edge metadata.

Fix and clean as suggested by alexfk.

  • [clang-tidy] Fix BracesAroundStatementsCheck when processing 'if' and 'while' statements with comments inside condition.

Alex, thanks for the useful comments.
The code is much cleaner now.
Besides, trying to apply your suggestions, I stumbled upon one more case where previous implementation gave erroneous results, namely when there are comments or whitespace, or empty macro expansion inside the if/while condition.
I have therefore added a function backwardSkipWhitespacesAndComments (actually copied from clang-modernize/AddOverride/AddOverrideActions.cpp - maybe some refactoring is needed to pull up common utilities?).

clang-tidy/misc/BracesAroundStatementsCheck.h
34

This line does not hold the 80-column limit, but clang-format leaves it as it is.
What is the convention in this case?

curdeius retitled this revision from Add check misc-braces-around-statements. to [clang-tidy] Add check misc-braces-around-statements..Sep 22 2014, 8:09 AM
alexfh added inline comments.Sep 24 2014, 7:37 AM
clang-tidy/misc/BracesAroundStatementsCheck.cpp
82

Please add:

else {
  llvm_unreachable("Invalid match"):
}

or something similar.

103

Unfortunately, this is wrong. Consider these examples:

if (x) f(); // here it will work
if (x) f() /* here it won't */ ;
do if(y) {}while (x); // and here too

I don't know if the AST provides a convenient way to find out if there's a semicolon behind the end of the statement and where it is located. I also don't know why the statements that end with a semicolon do not store its location or just include it in the source range. We need to ask someone more familiar with the AST.

clang-tidy/misc/BracesAroundStatementsCheck.h
34

It's fine here, as there's not much you can do. I was only talking about removing extra dashes from the file header comments.

curdeius updated this object.Sep 27 2014, 6:50 AM
curdeius updated this revision to Diff 14145.Sep 27 2014, 6:52 AM

Fix BracesAroundStatementsCheck with nested statements. Apply comments from alexfh.

  • Add llvm_unreachable.
  • [clang-tidy] Fix BracesAroundStatementsCheck with nested statements.

Thanks again for the hard work on this patch! It's now almost fine, but there's still one issue, which clang-format won't be able to fix. I'd slightly prefer that it be fixed before submitting the patch. See the comment inline.

clang-tidy/misc/BracesAroundStatementsCheck.cpp
58

s/Whitespaces/Whitespace/

75

nit: I'd prefer "// namespace" for consistency with how the llvm-namespace-comment check does it.

97

I think, RParenLoc should be stored inside WhileStmt and IfStmt, then this code will be much easier. This doesn't have to be in this patch, but please add a FIXME.

test/clang-tidy/misc-braces-around-statements.cpp
148

This looks slightly wrong to put a closing brace before a trailing comment, and clang-format won't fix this. I think, the check should try to keep the trailing comment next to the statement it belongs to. I'm not sure if there can be a 100% fool-proof solution, but I hope, we can come up with a decent heuristic.

Assuming the coding style is similar to the one used in LLVM, we could do the following:

  1. if there's a corresponding "else" or "while", the check should insert "} " right before that token.
  2. if there's a multi-line block comment starting on the same line after the location we're inserting the closing brace at, or there's a non-comment token, the check should insert "} " right before that token.
  3. otherwise the check should find the end of line (possibly after some block or line comments) and insert "}\n" right after that EOL. It could also insert some spaces to make the indentation nice, but this could be done by clang-format as well.

Do you see any problems with this plan?

Thanks for your comments Alex.
Comments inline.

clang-tidy/misc/BracesAroundStatementsCheck.cpp
58

Ok.

75

Ok.

97

Ok for a FIXME.
I'd prefer creating a new patch for this, as this can have wider repercussions, not only on clang-tidy.

test/clang-tidy/misc-braces-around-statements.cpp
148

Looks like a plan!

  1. is trivial (already done), 2. and 3. should be quite easy too.

In 3. I would leave indentation to clang-format, anyway one should run it after using clang-tidy.

Is there any easy way of checking if a block-comment spans over multiple lines?

alexfh added inline comments.Sep 29 2014, 10:51 AM
clang-tidy/misc/BracesAroundStatementsCheck.cpp
97

Sure. I've prepared D5528 to address this. Not sure though, whether we'll go this way or not.

test/clang-tidy/misc-braces-around-statements.cpp
148

In 3. I would leave indentation to clang-format, anyway one should run it after using clang-tidy.

Sounds good.

Is there any easy way of checking if a block-comment spans over multiple lines?

I'd try something like this:

Lexer::getSourceText(CharSourceRange::getTokenRange(Tok.getLocation(), Tok.getLocation()), Result.SourceManager, Result.Context->getLangOpts()).find('\n') != StringRef::npos
curdeius updated this revision to Diff 14208.Sep 30 2014, 4:30 AM

Add heuristics for treating comments at end of statements.

  • [clang-tidy] BracesAroundStatementsCheck: add EndLocHint (before 'else' in if-else stmt and 'while' in do-while loop).
  • [clang-tidy] BracesAroundStatementsCheck: refactoring.
  • [clang-tidy] BracesAroundStatementsCheck: heuristics for treating comments.

Alex, I have applied a heuristics as you suggested.
I have done a minor change, for cases 2. (multi-line comments or non-comment token) and 3. (trailing comment), I put "\n}" before found end of line. I do not separate the two cases by adding or not a space after '}'. I think that in any case, a run of clang-format is needed here. Tell me if I'm wrong.

alexfh accepted this revision.Sep 30 2014, 6:09 AM
alexfh edited edge metadata.

This looks good for the initial version once you address the comments.

I'd also ask you to run the check over LLVM source code as a smoke test and take a look at the changes the script produces (you can use the run-clang-tidy.py script for this). This can be done after submitting the code.

Thanks again for the great job!

clang-tidy/misc/BracesAroundStatementsCheck.cpp
170

Sorry for missing this previously: here and in the next branch you could find the right parenthesis by skipping forward starting from Lexer::getLocForEndOfToken(WS->getCond()->getLocEnd(), 0, ...). This would probably be more efficient and use only one skip function.

test/clang-tidy/misc-braces-around-statements.cpp
180

What happens with the comment that was after the semicolon? Could you please add it to the appropriate check line?

This revision is now accepted and ready to land.Sep 30 2014, 6:09 AM

I wanted to apply your suggestion about skip functions, but stumbled upon a new problem: how should we handle macros when searching for brace location (in skip function)?
They are not expanded on the moment when clang-tidy runs over the file, so how can I expand them to check if they're empty?
I tried Token::hasLeadingEmptyMacro(), but it doesn't work as I expected.

test/clang-tidy/misc-braces-around-statements.cpp
180

I deleted it somehow. Restored.

curdeius updated this object.Sep 30 2014, 7:27 AM
curdeius edited edge metadata.
curdeius updated this object.
In D5395#30, @curdeius wrote:

I wanted to apply your suggestion about skip functions, but stumbled upon a new problem: how should we handle macros when searching for brace location (in skip function)?

I think, you could iterate over all tokens located from after the WS->getCond()->getLocEnd(), but before the WS->getBody()->getLocStart(). If the first non-comment and non-whitespace token you find is not a closing parenthesis, you can bail out, because something weird is going on. I don't think you'll need to handle macros here.

They are not expanded on the moment when clang-tidy runs over the file,

This is not exactly true, but it doesn't matter for our problem.

so how can I expand them to check if they're empty?
I tried Token::hasLeadingEmptyMacro(), but it doesn't work as I expected.

curdeius updated this revision to Diff 14286.Oct 1 2014, 8:41 AM

Fixed treating macros and variable declarations in conditions.

  • [clang-tidy] Skipping macros. Refactoring. Fixed treating variable declarations inside conditions.

Hi Alex,
I've done some further modifications to correctly handle statements with a variable declaration in it (like while (auto x = f()) ;) and handling macro-expanded statements (ignoring them).
If you think that it's ok now, can you please land this patch (I don't have write access).

Oh, and I've run this check over llvm source code, that's how I've added all these checks like SourceLocation::isValid() and skipped macros.

In D5395#36, @curdeius wrote:

Hi Alex,
I've done some further modifications to correctly handle statements with a variable declaration in it (like while (auto x = f()) ;) and handling macro-expanded statements (ignoring them).
If you think that it's ok now, can you please land this patch (I don't have write access).

Hi Marek,

The check looks good now! I've made a few minor changes (spelling, comments, braces, etc.) and committing the patch now. I've added a few inline comments to document what I'm changing in the patch and why.

I've also added a FIXME to add some configurability to the check to make it useful for Google and LLVM. Both allow if/while/... bodies without braces with certain conditions, so it would be incorrect to just insert braces everywhere.

Thank you again for the great work!

clang-tidy/misc/BracesAroundStatementsCheck.cpp
30

Here and elsewhere: we normally don't use braces around single-line if/while/for/... bodies. It's not explicitly written down, but all examples in the Coding Standards document are written this way: http://llvm.org/docs/CodingStandards.html#use-early-exits-and-continue-to-simplify-code

49

Here and elsewhere: the comments should be correct sentences, in particular, start with a capital character and end with a period.

54

These two methods do a very similar stuff and are only used once. I've inlined them.

62

nit: newline is a single word, hence s/hasNewLine/hasNewline/

112

It's a bit too defensive. The condition is checked in the previous statement.

121

nit: s/new line/newline/

test/clang-tidy/misc-braces-around-statements.cpp
177

There's no need to check this line.

unittests/clang-tidy/MiscModuleTest.cpp
78

These comments document facts that are obvious from a single glance at the code. I've removed the comments.

117

We don't commit commented-out code. I've added a FIXME and a test showing current behavior.

alexfh closed this revision.Oct 2 2014, 12:20 PM

Committed as r218898.