Index: clang-tools-extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp =================================================================== --- clang-tools-extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp +++ clang-tools-extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "BracesAroundStatementsCheck.h" +#include "../utils/LexerUtils.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Lex/Lexer.h" @@ -16,10 +17,9 @@ namespace clang { namespace tidy { namespace readability { -namespace { -tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, - const ASTContext *Context) { +static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, + const ASTContext *Context) { Token Tok; SourceLocation Beginning = Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts()); @@ -33,9 +33,9 @@ return Tok.getKind(); } -SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc, - const SourceManager &SM, - const ASTContext *Context) { +static SourceLocation +forwardSkipWhitespaceAndComments(SourceLocation Loc, const SourceManager &SM, + const ASTContext *Context) { assert(Loc.isValid()); for (;;) { while (isWhitespace(*SM.getCharacterData(Loc))) @@ -50,31 +50,15 @@ } } -SourceLocation findEndLocation(SourceLocation LastTokenLoc, - const SourceManager &SM, - const ASTContext *Context) { +static SourceLocation findEndLocation(const Stmt &S, const SourceManager &SM, + const ASTContext *Context) { SourceLocation Loc = - Lexer::GetBeginningOfToken(LastTokenLoc, SM, Context->getLangOpts()); - // Loc points to the beginning of the last (non-comment non-ws) token - // before end or ';'. - assert(Loc.isValid()); - bool SkipEndWhitespaceAndComments = true; - tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); - if (TokKind == tok::NUM_TOKENS || TokKind == tok::semi || - TokKind == tok::r_brace) { - // If we are at ";" or "}", we found the last token. We could use as well - // `if (isa(S))`, but it wouldn't work for nested statements. - SkipEndWhitespaceAndComments = false; - } + utils::lexer::getUnifiedEndLoc(S, SM, Context->getLangOpts()); + if (!Loc.isValid()) + return Loc; - Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); - // Loc points past the last token before end or after ';'. - if (SkipEndWhitespaceAndComments) { - Loc = forwardSkipWhitespaceAndComments(Loc, SM, Context); - tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); - if (TokKind == tok::semi) - Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); - } + // Start searching right after S. + Loc = Loc.getLocWithOffset(1); for (;;) { assert(Loc.isValid()); @@ -109,8 +93,6 @@ return Loc; } -} // namespace - BracesAroundStatementsCheck::BracesAroundStatementsCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), @@ -249,13 +231,16 @@ EndLoc = EndLocHint; ClosingInsertion = "} "; } else { - const auto FREnd = FileRange.getEnd().getLocWithOffset(-1); - EndLoc = findEndLocation(FREnd, SM, Context); + EndLoc = findEndLocation(*S, SM, Context); + if (!EndLoc.isValid()) + // This could happen somehow when S is e.g. at file end, so abort + // gracefully. + return false; ClosingInsertion = "\n}"; } assert(StartLoc.isValid()); - assert(EndLoc.isValid()); + // Don't require braces for statements spanning less than certain number of // lines. if (ShortStatementLines && !ForceBracesStmts.erase(S)) { Index: clang-tools-extra/clang-tidy/utils/LexerUtils.h =================================================================== --- clang-tools-extra/clang-tidy/utils/LexerUtils.h +++ clang-tools-extra/clang-tidy/utils/LexerUtils.h @@ -14,6 +14,9 @@ #include "clang/Lex/Lexer.h" namespace clang { + +class Stmt; + namespace tidy { namespace utils { namespace lexer { @@ -104,6 +107,11 @@ const ASTContext &Context, const SourceManager &SM); +/// Stmt->getEndLoc does not always behave the same way depending on Token type. +/// See implementation for exceptions. +SourceLocation getUnifiedEndLoc(const Stmt &S, const SourceManager &SM, + const LangOptions &LangOpts); + } // namespace lexer } // namespace utils } // namespace tidy Index: clang-tools-extra/clang-tidy/utils/LexerUtils.cpp =================================================================== --- clang-tools-extra/clang-tidy/utils/LexerUtils.cpp +++ clang-tools-extra/clang-tidy/utils/LexerUtils.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "LexerUtils.h" +#include "clang/AST/AST.h" namespace clang { namespace tidy { @@ -147,6 +148,38 @@ return LastMatchAfterTemplate != None ? LastMatchAfterTemplate : LastMatchBeforeTemplate; } + +static bool breakAndReturnEnd(const Stmt &S) { + // Important: InitListExpr does not behave as an Expr. + // Call this before breakAndReturnEndPlus1Token. + return isa(S) || isa(S) || isa(S) || + isa(S); +} +static bool breakAndReturnEndPlus1Token(const Stmt &S) { + return isa(S) || isa(S) || isa(S) || + isa(S) || isa(S); +} +SourceLocation getUnifiedEndLoc(const Stmt &S, const SourceManager &SM, + const LangOptions &LangOpts) { + + const Stmt *LastChild = &S; + while (!LastChild->children().empty() && !breakAndReturnEnd(*LastChild) && + !breakAndReturnEndPlus1Token(*LastChild)) { + for (const Stmt *Child : LastChild->children()) + LastChild = Child; + } + + if (!breakAndReturnEnd(*LastChild) && + breakAndReturnEndPlus1Token(*LastChild)) { + const Optional NextTok = + findNextTokenSkippingComments(S.getEndLoc(), SM, LangOpts); + return NextTok ? NextTok->getEndLoc().getLocWithOffset(-1) + : SourceLocation(); + } + + return S.getEndLoc(); +} + } // namespace lexer } // namespace utils } // namespace tidy Index: clang-tools-extra/test/clang-tidy/checkers/readability-braces-around-statements.cpp =================================================================== --- clang-tools-extra/test/clang-tidy/checkers/readability-braces-around-statements.cpp +++ clang-tools-extra/test/clang-tidy/checkers/readability-braces-around-statements.cpp @@ -74,30 +74,41 @@ do_something("for"); // CHECK-MESSAGES: :[[@LINE-2]]:11: warning: statement should be inside braces // CHECK-FIXES: for (;;) { - // CHECK-FIXES: } + // CHECK-FIXES-NEXT: do_something("for"); + // CHECK-FIXES-NEXT: } + for (;;) { - do_something("for"); + do_something("for-ok"); } for (;;) ; // CHECK-MESSAGES: :[[@LINE-2]]:11: warning: statement should be inside braces // CHECK-FIXES: for (;;) { - // CHECK-FIXES: } + // CHECK-FIXES-NEXT: ; + // CHECK-FIXES-NEXT: } int arr[4] = {1, 2, 3, 4}; for (int a : arr) do_something("for-range"); // CHECK-MESSAGES: :[[@LINE-2]]:20: warning: statement should be inside braces // CHECK-FIXES: for (int a : arr) { - // CHECK-FIXES: } - for (int a : arr) { + // CHECK-FIXES-NEXT: do_something("for-range"); + // CHECK-FIXES-NEXT: } + for (int &assign : arr) + assign = 7; + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: for (int &assign : arr) { + // CHECK-FIXES-NEXT: assign = 7; + // CHECK-FIXES-NEXT: } + for (int ok : arr) { do_something("for-range"); } - for (int a : arr) + for (int NullStmt : arr) ; - // CHECK-MESSAGES: :[[@LINE-2]]:20: warning: statement should be inside braces - // CHECK-FIXES: for (int a : arr) { - // CHECK-FIXES: } + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: for (int NullStmt : arr) { + // CHECK-FIXES-NEXT: ; + // CHECK-FIXES-NEXT: } while (cond("while1")) do_something("while"); @@ -143,14 +154,19 @@ // CHECK-FIXES-NEXT: } if (cond("ifif3")) - // comment + // comment1 if (cond("ifif4")) { - // comment - /*comment*/; // comment + // comment2 + /*comment3*/; // comment4 } // CHECK-MESSAGES: :[[@LINE-6]]:21: warning: statement should be inside braces // CHECK-FIXES: if (cond("ifif3")) { - // CHECK-FIXES: } + // CHECK-FIXES-NEXT: // comment1 + // CHECK-FIXES-NEXT: if (cond("ifif4")) { + // CHECK-FIXES-NEXT: // comment2 + // CHECK-FIXES-NEXT: /*comment3*/; // comment4 + // CHECK-FIXES-NEXT: } + // CHECK-FIXES-NEXT: } if (cond("ifif5")) ; /* multi-line @@ -170,6 +186,93 @@ // CHECK-FIXES-NEXT: } // CHECK-FIXES-NEXT: } // CHECK-FIXES-NEXT: } + + int S; + if (cond("assign with brackets")) + S = {5}; + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: if (cond("assign with brackets")) { + // CHECK-FIXES-NEXT: S = {5}; + // CHECK-FIXES-NEXT: } + + if (cond("assign with brackets 2")) + S = { 5 } /* comment1 */ ; /* comment2 */ + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: if (cond("assign with brackets 2")) { + // CHECK-FIXES-NEXT: S = { 5 } /* comment1 */ ; /* comment2 */ + // CHECK-FIXES-NEXT: } + + if (cond("return")) + return; + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: if (cond("return")) { + // CHECK-FIXES-NEXT: return; + // CHECK-FIXES-NEXT: } + + while (cond("break and continue")) { + // CHECK-FIXES: while (cond("break and continue")) { + if (true) + break; + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: if (true) { + // CHECK-FIXES-NEXT: break; + // CHECK-FIXES-NEXT: } + if (false) + continue; + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: if (false) { + // CHECK-FIXES-NEXT: continue; + // CHECK-FIXES-NEXT: } + } //end + // CHECK-FIXES: } //end + + if (cond("decl 1")) + int s; + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: if (cond("decl 1")) { + // CHECK-FIXES-NEXT: int s; + // CHECK-FIXES-NEXT: } + + if (cond("decl 2")) + int s = (5); + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: if (cond("decl 2")) { + // CHECK-FIXES-NEXT: int s = (5); + // CHECK-FIXES-NEXT: } + + if (cond("decl 3")) + int s = {6}; + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: if (cond("decl 3")) { + // CHECK-FIXES-NEXT: int s = {6}; + // CHECK-FIXES-NEXT: } + + // Use-case: LLVM_DEBUG({ for(...) do_something(); }); + #define WRAP(X) { X; } + WRAP({ + for (;;) + do_something("for in wrapping macro"); + }); + // CHECK-MESSAGES: :[[@LINE-3]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: for (;;) { + // CHECK-FIXES-NEXT: do_something("for in wrapping macro"); + // CHECK-FIXES-NEXT: } +} + +int test_return_int() { + if (cond("return5")) + return 5; + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: if (cond("return5")) { + // CHECK-FIXES-NEXT: return 5; + // CHECK-FIXES-NEXT: } + + if (cond("return{6}")) + return {6}; + // CHECK-MESSAGES: :[[@LINE-2]]:{{[0-9]+}}: warning: statement should be inside braces + // CHECK-FIXES: if (cond("return{6}")) { + // CHECK-FIXES-NEXT: return {6}; + // CHECK-FIXES-NEXT: } } void f(const char *p) { Index: clang/include/clang/Lex/Lexer.h =================================================================== --- clang/include/clang/Lex/Lexer.h +++ clang/include/clang/Lex/Lexer.h @@ -515,7 +515,7 @@ /// Finds the token that comes right after the given location. /// - /// Returns the next token, or none if the location is inside a macro. + /// Returns the next token, or None on failure. static Optional findNextToken(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts); Index: clang/lib/Lex/Lexer.cpp =================================================================== --- clang/lib/Lex/Lexer.cpp +++ clang/lib/Lex/Lexer.cpp @@ -1239,7 +1239,8 @@ const LangOptions &LangOpts) { if (Loc.isMacroID()) { if (!Lexer::isAtEndOfMacroExpansion(Loc, SM, LangOpts, &Loc)) - return None; + // Move Loc into Macro as the next token in also part of the Macro. + Loc = SM.getSpellingLoc(Loc); } Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts);