Index: clang/include/clang/Basic/DiagnosticGroups.td =================================================================== --- clang/include/clang/Basic/DiagnosticGroups.td +++ clang/include/clang/Basic/DiagnosticGroups.td @@ -693,6 +693,7 @@ def GNUZeroLineDirective : DiagGroup<"gnu-zero-line-directive">; def GNUZeroVariadicMacroArguments : DiagGroup<"gnu-zero-variadic-macro-arguments">; def Fallback : DiagGroup<"fallback">; +def MisleadingIndentation : DiagGroup<"misleading-indentation">; // This covers both the deprecated case (in C++98) // and the extension case (in C++11 onwards). @@ -884,7 +885,7 @@ // Note that putting warnings in -Wall will not disable them by default. If a // warning should be active _only_ when -Wall is passed in, mark it as // DefaultIgnore in addition to putting it here. -def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool]>; +def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool, MisleadingIndentation]>; // Warnings that should be in clang-cl /w4. def : DiagGroup<"CL4", [All, Extra]>; Index: clang/include/clang/Basic/DiagnosticParseKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticParseKinds.td +++ clang/include/clang/Basic/DiagnosticParseKinds.td @@ -61,6 +61,13 @@ "remove unnecessary ';' to silence this warning">, InGroup, DefaultIgnore; +def warn_misleading_indentation : Warning< + "misleading indentation; %select{statement|declaration}0 is not part of " + "the previous '%select{if|else|for|while}1'">, + InGroup, DefaultIgnore; +def note_previous_statement : Note< + "previous statement is here">; + def ext_thread_before : Extension<"'__thread' before '%0'">; def ext_keyword_as_ident : ExtWarn< "keyword '%0' will be made available as an identifier " Index: clang/include/clang/Parse/Parser.h =================================================================== --- clang/include/clang/Parse/Parser.h +++ clang/include/clang/Parse/Parser.h @@ -2266,11 +2266,13 @@ return isTypeSpecifierQualifier(); } +public: /// isCXXDeclarationStatement - C++-specialized function that disambiguates /// between a declaration or an expression statement, when parsing function /// bodies. Returns true for declaration, false for expression. bool isCXXDeclarationStatement(); +private: /// isCXXSimpleDeclaration - C++-specialized function that disambiguates /// between a simple-declaration or an expression-statement. /// If during the disambiguation process a parsing error is encountered, Index: clang/lib/Parse/ParseStmt.cpp =================================================================== --- clang/lib/Parse/ParseStmt.cpp +++ clang/lib/Parse/ParseStmt.cpp @@ -1191,6 +1191,27 @@ return false; } +enum MisleadingStatementKind { MSK_if, MSK_else, MSK_for, MSK_while }; + +static void +MaybeDiagnoseMisleadingIndentation(Parser &P, SourceLocation PrevLoc, + SourceLocation StmtLoc, + MisleadingStatementKind StmtKind) { + Token Tok = P.getCurToken(); + if (Tok.is(tok::semi)) + return; + SourceManager &SM = P.getPreprocessor().getSourceManager(); + unsigned PrevColNum = SM.getSpellingColumnNumber(PrevLoc); + unsigned CurColNum = SM.getSpellingColumnNumber(Tok.getLocation()); + unsigned StmtColNum = SM.getSpellingColumnNumber(StmtLoc); + if (!Tok.isAtStartOfLine() || + (PrevColNum != 0 && CurColNum != 0 && StmtColNum != 0 && + PrevColNum > StmtColNum && PrevColNum == CurColNum)) { + P.Diag(Tok.getLocation(), diag::warn_misleading_indentation) + << P.isCXXDeclarationStatement() << StmtKind; + P.Diag(StmtLoc, diag::note_previous_statement); + } +} /// ParseIfStatement /// if-statement: [C99 6.8.4.1] @@ -1281,6 +1302,9 @@ // Pop the 'if' scope if needed. InnerScope.Exit(); + if (Tok.isNot(tok::kw_else)) + MaybeDiagnoseMisleadingIndentation(*this, ThenStmtLoc, IfLoc, MSK_if); + // If it has an else, parse it. SourceLocation ElseLoc; SourceLocation ElseStmtLoc; @@ -1313,6 +1337,9 @@ // Pop the 'else' scope if needed. InnerScope.Exit(); + if (ElseStmt.isUsable()) + MaybeDiagnoseMisleadingIndentation(*this, ElseStmt.get()->getBeginLoc(), + ElseLoc, MSK_else); } else if (Tok.is(tok::code_completion)) { Actions.CodeCompleteAfterIf(getCurScope()); cutOffParsing(); @@ -1491,6 +1518,10 @@ InnerScope.Exit(); WhileScope.Exit(); + if (Body.isUsable()) + MaybeDiagnoseMisleadingIndentation(*this, Body.get()->getBeginLoc(), + WhileLoc, MSK_while); + if (Cond.isInvalid() || Body.isInvalid()) return StmtError(); @@ -1927,6 +1958,10 @@ // Leave the for-scope. ForScope.Exit(); + if (Body.isUsable()) + MaybeDiagnoseMisleadingIndentation(*this, Body.get()->getBeginLoc(), ForLoc, + MSK_for); + if (Body.isInvalid()) return StmtError(); Index: clang/test/Index/pragma-diag-reparse.c =================================================================== --- clang/test/Index/pragma-diag-reparse.c +++ clang/test/Index/pragma-diag-reparse.c @@ -11,6 +11,7 @@ return x; } +#pragma clang diagnostic ignored "-Wmisleading-indentation" void foo() { int b=0; while (b==b); } // RUN: env CINDEXTEST_EDITING=1 CINDEXTEST_FAILONERROR=1 c-index-test -test-load-source-reparse 5 local \ Index: clang/test/Misc/warning-wall.c =================================================================== --- clang/test/Misc/warning-wall.c +++ clang/test/Misc/warning-wall.c @@ -90,6 +90,7 @@ CHECK-NEXT: -Wdangling-else CHECK-NEXT: -Wswitch CHECK-NEXT: -Wswitch-bool +CHECK-NEXT: -Wmisleading-indentation CHECK-NOT:-W Index: clang/test/Preprocessor/pragma_diagnostic_sections.cpp =================================================================== --- clang/test/Preprocessor/pragma_diagnostic_sections.cpp +++ clang/test/Preprocessor/pragma_diagnostic_sections.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -Wall -Wunused-macros -Wunused-parameter -Wno-uninitialized -verify %s +// RUN: %clang_cc1 -fsyntax-only -Wall -Wunused-macros -Wunused-parameter -Wno-uninitialized -Wno-misleading-indentation -verify %s // rdar://8365684 struct S { Index: clang/test/SemaCXX/warn-misleading-indentation.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-misleading-indentation.cpp @@ -0,0 +1,108 @@ +// RUN: %clang_cc1 -x c -fsyntax-only -verify %s +// RUN: %clang_cc1 -x c -fsyntax-only -verify -Wmisleading-indentation -DWITH_WARN %s +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -Wall -Wno-unused -DWITH_WARN -DCXX17 %s +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify -Wall -Wno-unused -Wno-misleading-indentation -DCXX17 %s + +#ifndef WITH_WARN +// expected-no-diagnostics +#endif + +void f0(int i) { + if (i) +#ifdef WITH_WARN +// expected-note@-2 {{here}} +#endif + i = i + 1; + int x = 0; +#ifdef WITH_WARN +// expected-warning@-2 {{declaration is not part of the previous 'if'}} +#endif + return; +#ifdef CXX17 + if constexpr (false) +#ifdef WITH_WARN +// expected-note@-2 {{here}} +#endif + i = 0; + i += 1; +#ifdef WITH_WARN +// expected-warning@-2 {{misleading indentation; statement}} +#endif +#endif +} + +void f1(int i) { + for (;i;) +#ifdef WITH_WARN +// expected-note@-2 {{here}} +#endif + i = i + 1; + i *= 2; +#ifdef WITH_WARN +// expected-warning@-2 {{ is not part of the previous 'for'}} +#endif + return; +} + +void f2(int i) { + while (i) +#ifdef WITH_WARN +// expected-note@-2 {{here}} +#endif + i = i + 1; i *= 2; +#ifdef WITH_WARN +// expected-warning@-2 {{is not part of the previous 'while'}} +#endif + return; +} + +void f3(int i) { + if (i) + i = i + 1; + else +#ifdef WITH_WARN +// expected-note@-2 {{here}} +#endif + i *= 2; + const int x = 0; +#ifdef WITH_WARN +// expected-warning@-2 {{declaration is not part of the previous 'else'}} +#endif +} + +#ifdef CXX17 +struct Range { + int *begin() {return nullptr;} + int *end() {return nullptr;} +}; +#endif + +void f4(int i) { + if (i) + i *= 2; + return; + if (i) + i *= 2; + ; + if (i) +#ifdef WITH_WARN +// expected-note@-2 {{here}} +#endif + i *= 2; + typedef int Int; +#ifdef WITH_WARN +// expected-warning@-2 {{misleading indentation}} +#endif +#ifdef CXX17 + Range R; + for (auto e : R) +#ifdef WITH_WARN +// expected-note@-2 {{here}} +#endif + i *= 2; + using Int2 = int; +#ifdef WITH_WARN +// expected-warning@-2 {{misleading indentation; declaration}} +#endif +#endif +}