r347339 - [clang][Parse] Diagnose useless null statements / empty init-statements

Roman Lebedev via cfe-commits cfe-commits at lists.llvm.org
Fri Dec 28 01:54:23 PST 2018


On Fri, Dec 28, 2018 at 2:57 AM Nico Weber <thakis at chromium.org> wrote:
>
> Should this be in -Wextra-semi? It seems weird to have to pass both -Wextra-semi and -Wextra-semi-stmt.
It does seem somewhat weird, and my initial implementation did add it
into -Wextra-semi,
but the review https://reviews.llvm.org/D52695#inline-464922 said to
keep them separate.

> (Also, we generally try to not add DefaultIgnore warnings. I think this is a useful warning though, and other than others in this thread I think it'd be good to have -Wextra-semi in -Wall. But I don't feel strongly about that part.)
By -Wextra-semi here you mean -Wextra-semi + -Wextra-semi-stmt ?

Note that even -Wempty-init-stmt is only in -Wextra. I guess that one
can be realistically upgraded into -Wall.

-Wextra-semi{,-stmt} are less clear-cut. As we have seen, even having
-Wextra-semi-stmt in -Weverything
results in negative feedback. It might be possible to uplift them both
into -Wextra, but realistically i don't think
it will be possible to handle the feedback of making them part of -Wall.

(also, maybe "C++98 requires newline at end of file
[-Wc++98-compat-pedantic]" should be separate, and in -Wall, too.)

Roman.

> On Tue, Nov 20, 2018 at 2:01 PM Roman Lebedev via cfe-commits <cfe-commits at lists.llvm.org> wrote:
>>
>> Author: lebedevri
>> Date: Tue Nov 20 10:59:05 2018
>> New Revision: 347339
>>
>> URL: http://llvm.org/viewvc/llvm-project?rev=347339&view=rev
>> Log:
>> [clang][Parse] Diagnose useless null statements / empty init-statements
>>
>> Summary:
>> clang has `-Wextra-semi` (D43162), which is not dictated by the currently selected standard.
>> While that is great, there is at least one more source of need-less semis - 'null statements'.
>> Sometimes, they are needed:
>> ```
>> for(int x = 0; continueToDoWork(x); x++)
>>   ; // Ugly code, but the semi is needed here.
>> ```
>>
>> But sometimes they are just there for no reason:
>> ```
>> switch(X) {
>> case 0:
>>   return -2345;
>> case 5:
>>   return 0;
>> default:
>>   return 42;
>> }; // <- oops
>>
>> ;;;;;;;;;;; <- OOOOPS, still not diagnosed. Clearly this is junk.
>> ```
>>
>> Additionally:
>> ```
>> if(; // <- empty init-statement
>>    true)
>>   ;
>>
>> switch (; // empty init-statement
>>         x) {
>>   ...
>> }
>>
>> for (; // <- empty init-statement
>>      int y : S())
>>   ;
>> }
>>
>> As usual, things may or may not go sideways in the presence of macros.
>> While evaluating this diag on my codebase of interest, it was unsurprisingly
>> discovered that Google Test macros are *very* prone to this.
>> And it seems many issues are deep within the GTest itself, not
>> in the snippets passed from the codebase that uses GTest.
>>
>> So after some thought, i decided not do issue a diagnostic if the semi
>> is within *any* macro, be it either from the normal header, or system header.
>>
>> Fixes [[ https://bugs.llvm.org/show_bug.cgi?id=39111 | PR39111 ]]
>>
>> Reviewers: rsmith, aaron.ballman, efriedma
>>
>> Reviewed By: aaron.ballman
>>
>> Subscribers: cfe-commits
>>
>> Differential Revision: https://reviews.llvm.org/D52695
>>
>> Added:
>>     cfe/trunk/test/Parser/extra-semi-resulting-in-nullstmt-in-init-statement.cpp
>>     cfe/trunk/test/Parser/extra-semi-resulting-in-nullstmt.cpp
>> Modified:
>>     cfe/trunk/docs/ReleaseNotes.rst
>>     cfe/trunk/include/clang/Basic/DiagnosticGroups.td
>>     cfe/trunk/include/clang/Basic/DiagnosticParseKinds.td
>>     cfe/trunk/include/clang/Parse/Parser.h
>>     cfe/trunk/lib/Parse/ParseExprCXX.cpp
>>     cfe/trunk/lib/Parse/ParseStmt.cpp
>>
>> Modified: cfe/trunk/docs/ReleaseNotes.rst
>> URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/docs/ReleaseNotes.rst?rev=347339&r1=347338&r2=347339&view=diff
>> ==============================================================================
>> --- cfe/trunk/docs/ReleaseNotes.rst (original)
>> +++ cfe/trunk/docs/ReleaseNotes.rst Tue Nov 20 10:59:05 2018
>> @@ -55,6 +55,63 @@ Major New Features
>>  Improvements to Clang's diagnostics
>>  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>
>> +- ``-Wextra-semi-stmt`` is a new diagnostic that diagnoses extra semicolons,
>> +  much like ``-Wextra-semi``. This new diagnostic diagnoses all *unnecessary*
>> +  null statements (expression statements without an expression), unless: the
>> +  semicolon directly follows a macro that was expanded to nothing or if the
>> +  semicolon is within the macro itself. This applies to macros defined in system
>> +  headers as well as user-defined macros.
>> +
>> +  .. code-block:: c++
>> +
>> +      #define MACRO(x) int x;
>> +      #define NULLMACRO(varname)
>> +
>> +      void test() {
>> +        ; // <- warning: ';' with no preceding expression is a null statement
>> +
>> +        while (true)
>> +          ; // OK, it is needed.
>> +
>> +        switch (my_enum) {
>> +        case E1:
>> +          // stuff
>> +          break;
>> +        case E2:
>> +          ; // OK, it is needed.
>> +        }
>> +
>> +        MACRO(v0;) // Extra semicolon, but within macro, so ignored.
>> +
>> +        MACRO(v1); // <- warning: ';' with no preceding expression is a null statement
>> +
>> +        NULLMACRO(v2); // ignored, NULLMACRO expanded to nothing.
>> +      }
>> +
>> +- ``-Wempty-init-stmt`` is a new diagnostic that diagnoses empty init-statements
>> +  of ``if``, ``switch``, ``range-based for``, unless: the semicolon directly
>> +  follows a macro that was expanded to nothing or if the semicolon is within the
>> +  macro itself (both macros from system headers, and normal macros). This
>> +  diagnostic is in the ``-Wextra-semi-stmt`` group and is enabled in
>> +  ``-Wextra``.
>> +
>> +  .. code-block:: c++
>> +
>> +      void test() {
>> +        if(; // <- warning: init-statement of 'if' is a null statement
>> +           true)
>> +          ;
>> +
>> +        switch (; // <- warning: init-statement of 'switch' is a null statement
>> +                x) {
>> +          ...
>> +        }
>> +
>> +        for (; // <- warning: init-statement of 'range-based for' is a null statement
>> +             int y : S())
>> +          ;
>> +      }
>> +
>>
>>  Non-comprehensive list of changes in this release
>>  -------------------------------------------------
>>
>> Modified: cfe/trunk/include/clang/Basic/DiagnosticGroups.td
>> URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticGroups.td?rev=347339&r1=347338&r2=347339&view=diff
>> ==============================================================================
>> --- cfe/trunk/include/clang/Basic/DiagnosticGroups.td (original)
>> +++ cfe/trunk/include/clang/Basic/DiagnosticGroups.td Tue Nov 20 10:59:05 2018
>> @@ -162,6 +162,8 @@ def GNUEmptyStruct : DiagGroup<"gnu-empt
>>  def ExtraTokens : DiagGroup<"extra-tokens">;
>>  def CXX98CompatExtraSemi : DiagGroup<"c++98-compat-extra-semi">;
>>  def CXX11ExtraSemi : DiagGroup<"c++11-extra-semi">;
>> +def EmptyInitStatement : DiagGroup<"empty-init-stmt">;
>> +def ExtraSemiStmt : DiagGroup<"extra-semi-stmt", [EmptyInitStatement]>;
>>  def ExtraSemi : DiagGroup<"extra-semi", [CXX98CompatExtraSemi,
>>                                           CXX11ExtraSemi]>;
>>
>> @@ -768,7 +770,8 @@ def Extra : DiagGroup<"extra", [
>>      MissingMethodReturnType,
>>      SignCompare,
>>      UnusedParameter,
>> -    NullPointerArithmetic
>> +    NullPointerArithmetic,
>> +    EmptyInitStatement
>>    ]>;
>>
>>  def Most : DiagGroup<"most", [
>>
>> Modified: cfe/trunk/include/clang/Basic/DiagnosticParseKinds.td
>> URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticParseKinds.td?rev=347339&r1=347338&r2=347339&view=diff
>> ==============================================================================
>> --- cfe/trunk/include/clang/Basic/DiagnosticParseKinds.td (original)
>> +++ cfe/trunk/include/clang/Basic/DiagnosticParseKinds.td Tue Nov 20 10:59:05 2018
>> @@ -53,6 +53,10 @@ def ext_extra_semi_cxx11 : Extension<
>>  def warn_extra_semi_after_mem_fn_def : Warning<
>>    "extra ';' after member function definition">,
>>    InGroup<ExtraSemi>, DefaultIgnore;
>> +def warn_null_statement : Warning<
>> +  "empty expression statement has no effect; "
>> +  "remove unnecessary ';' to silence this warning">,
>> +  InGroup<ExtraSemiStmt>, DefaultIgnore;
>>
>>  def ext_thread_before : Extension<"'__thread' before '%0'">;
>>  def ext_keyword_as_ident : ExtWarn<
>> @@ -554,6 +558,9 @@ def ext_for_range_init_stmt : ExtWarn<
>>  def warn_cxx17_compat_for_range_init_stmt : Warning<
>>    "range-based for loop initialization statements are incompatible with "
>>    "C++ standards before C++2a">, DefaultIgnore, InGroup<CXXPre2aCompat>;
>> +def warn_empty_init_statement : Warning<
>> +  "empty initialization statement of '%select{if|switch|range-based for}0' "
>> +  "has no effect">, InGroup<EmptyInitStatement>, DefaultIgnore;
>>
>>  // C++ derived classes
>>  def err_dup_virtual : Error<"duplicate 'virtual' in base specifier">;
>>
>> Modified: cfe/trunk/include/clang/Parse/Parser.h
>> URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Parse/Parser.h?rev=347339&r1=347338&r2=347339&view=diff
>> ==============================================================================
>> --- cfe/trunk/include/clang/Parse/Parser.h (original)
>> +++ cfe/trunk/include/clang/Parse/Parser.h Tue Nov 20 10:59:05 2018
>> @@ -1888,6 +1888,7 @@ private:
>>    StmtResult ParseCompoundStatement(bool isStmtExpr,
>>                                      unsigned ScopeFlags);
>>    void ParseCompoundStatementLeadingPragmas();
>> +  bool ConsumeNullStmt(StmtVector &Stmts);
>>    StmtResult ParseCompoundStatementBody(bool isStmtExpr = false);
>>    bool ParseParenExprOrCondition(StmtResult *InitStmt,
>>                                   Sema::ConditionResult &CondResult,
>>
>> Modified: cfe/trunk/lib/Parse/ParseExprCXX.cpp
>> URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Parse/ParseExprCXX.cpp?rev=347339&r1=347338&r2=347339&view=diff
>> ==============================================================================
>> --- cfe/trunk/lib/Parse/ParseExprCXX.cpp (original)
>> +++ cfe/trunk/lib/Parse/ParseExprCXX.cpp Tue Nov 20 10:59:05 2018
>> @@ -1773,7 +1773,13 @@ Sema::ConditionResult Parser::ParseCXXCo
>>      //   if (; true);
>>      if (InitStmt && Tok.is(tok::semi)) {
>>        WarnOnInit();
>> -      SourceLocation SemiLoc = ConsumeToken();
>> +      SourceLocation SemiLoc = Tok.getLocation();
>> +      if (!Tok.hasLeadingEmptyMacro() && !SemiLoc.isMacroID()) {
>> +        Diag(SemiLoc, diag::warn_empty_init_statement)
>> +            << (CK == Sema::ConditionKind::Switch)
>> +            << FixItHint::CreateRemoval(SemiLoc);
>> +      }
>> +      ConsumeToken();
>>        *InitStmt = Actions.ActOnNullStmt(SemiLoc);
>>        return ParseCXXCondition(nullptr, Loc, CK);
>>      }
>>
>> Modified: cfe/trunk/lib/Parse/ParseStmt.cpp
>> URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Parse/ParseStmt.cpp?rev=347339&r1=347338&r2=347339&view=diff
>> ==============================================================================
>> --- cfe/trunk/lib/Parse/ParseStmt.cpp (original)
>> +++ cfe/trunk/lib/Parse/ParseStmt.cpp Tue Nov 20 10:59:05 2018
>> @@ -930,6 +930,34 @@ void Parser::ParseCompoundStatementLeadi
>>
>>  }
>>
>> +/// Consume any extra semi-colons resulting in null statements,
>> +/// returning true if any tok::semi were consumed.
>> +bool Parser::ConsumeNullStmt(StmtVector &Stmts) {
>> +  if (!Tok.is(tok::semi))
>> +    return false;
>> +
>> +  SourceLocation StartLoc = Tok.getLocation();
>> +  SourceLocation EndLoc;
>> +
>> +  while (Tok.is(tok::semi) && !Tok.hasLeadingEmptyMacro() &&
>> +         Tok.getLocation().isValid() && !Tok.getLocation().isMacroID()) {
>> +    EndLoc = Tok.getLocation();
>> +
>> +    // Don't just ConsumeToken() this tok::semi, do store it in AST.
>> +    StmtResult R = ParseStatementOrDeclaration(Stmts, ACK_Any);
>> +    if (R.isUsable())
>> +      Stmts.push_back(R.get());
>> +  }
>> +
>> +  // Did not consume any extra semi.
>> +  if (EndLoc.isInvalid())
>> +    return false;
>> +
>> +  Diag(StartLoc, diag::warn_null_statement)
>> +      << FixItHint::CreateRemoval(SourceRange(StartLoc, EndLoc));
>> +  return true;
>> +}
>> +
>>  /// ParseCompoundStatementBody - Parse a sequence of statements and invoke the
>>  /// ActOnCompoundStmt action.  This expects the '{' to be the current token, and
>>  /// consume the '}' at the end of the block.  It does not manipulate the scope
>> @@ -992,6 +1020,9 @@ StmtResult Parser::ParseCompoundStatemen
>>        continue;
>>      }
>>
>> +    if (ConsumeNullStmt(Stmts))
>> +      continue;
>> +
>>      StmtResult R;
>>      if (Tok.isNot(tok::kw___extension__)) {
>>        R = ParseStatementOrDeclaration(Stmts, ACK_Any);
>> @@ -1588,10 +1619,15 @@ StmtResult Parser::ParseForStatement(Sou
>>    ParsedAttributesWithRange attrs(AttrFactory);
>>    MaybeParseCXX11Attributes(attrs);
>>
>> +  SourceLocation EmptyInitStmtSemiLoc;
>> +
>>    // Parse the first part of the for specifier.
>>    if (Tok.is(tok::semi)) {  // for (;
>>      ProhibitAttributes(attrs);
>>      // no first part, eat the ';'.
>> +    SourceLocation SemiLoc = Tok.getLocation();
>> +    if (!Tok.hasLeadingEmptyMacro() && !SemiLoc.isMacroID())
>> +      EmptyInitStmtSemiLoc = SemiLoc;
>>      ConsumeToken();
>>    } else if (getLangOpts().CPlusPlus && Tok.is(tok::identifier) &&
>>               isForRangeIdentifier()) {
>> @@ -1723,6 +1759,11 @@ StmtResult Parser::ParseForStatement(Sou
>>                     : diag::ext_for_range_init_stmt)
>>                << (FirstPart.get() ? FirstPart.get()->getSourceRange()
>>                                    : SourceRange());
>> +          if (EmptyInitStmtSemiLoc.isValid()) {
>> +            Diag(EmptyInitStmtSemiLoc, diag::warn_empty_init_statement)
>> +                << /*for-loop*/ 2
>> +                << FixItHint::CreateRemoval(EmptyInitStmtSemiLoc);
>> +          }
>>          }
>>        } else {
>>          ExprResult SecondExpr = ParseExpression();
>>
>> Added: cfe/trunk/test/Parser/extra-semi-resulting-in-nullstmt-in-init-statement.cpp
>> URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Parser/extra-semi-resulting-in-nullstmt-in-init-statement.cpp?rev=347339&view=auto
>> ==============================================================================
>> --- cfe/trunk/test/Parser/extra-semi-resulting-in-nullstmt-in-init-statement.cpp (added)
>> +++ cfe/trunk/test/Parser/extra-semi-resulting-in-nullstmt-in-init-statement.cpp Tue Nov 20 10:59:05 2018
>> @@ -0,0 +1,64 @@
>> +// RUN: %clang_cc1 -fsyntax-only -Wextra -std=c++2a -verify %s
>> +// RUN: %clang_cc1 -fsyntax-only -Wextra-semi-stmt -std=c++2a -verify %s
>> +// RUN: %clang_cc1 -fsyntax-only -Wempty-init-stmt -std=c++2a -verify %s
>> +// RUN: cp %s %t
>> +// RUN: %clang_cc1 -x c++ -Wempty-init-stmt -std=c++2a -fixit %t
>> +// RUN: %clang_cc1 -x c++ -Wempty-init-stmt -std=c++2a -Werror %t
>> +
>> +struct S {
>> +  int *begin();
>> +  int *end();
>> +};
>> +
>> +void naive(int x) {
>> +  if (; true) // expected-warning {{empty initialization statement of 'if' has no effect}}
>> +    ;
>> +
>> +  switch (; x) { // expected-warning {{empty initialization statement of 'switch' has no effect}}
>> +  }
>> +
>> +  for (; int y : S()) // expected-warning {{empty initialization statement of 'range-based for' has no effect}}
>> +    ;
>> +
>> +  for (;;) // OK
>> +    ;
>> +}
>> +
>> +#define NULLMACRO
>> +
>> +void with_null_macro(int x) {
>> +  if (NULLMACRO; true)
>> +    ;
>> +
>> +  switch (NULLMACRO; x) {
>> +  }
>> +
>> +  for (NULLMACRO; int y : S())
>> +    ;
>> +}
>> +
>> +#define SEMIMACRO ;
>> +
>> +void with_semi_macro(int x) {
>> +  if (SEMIMACRO true)
>> +    ;
>> +
>> +  switch (SEMIMACRO x) {
>> +  }
>> +
>> +  for (SEMIMACRO int y : S())
>> +    ;
>> +}
>> +
>> +#define PASSTHROUGHMACRO(x) x
>> +
>> +void with_passthrough_macro(int x) {
>> +  if (PASSTHROUGHMACRO(;) true)
>> +    ;
>> +
>> +  switch (PASSTHROUGHMACRO(;) x) {
>> +  }
>> +
>> +  for (PASSTHROUGHMACRO(;) int y : S())
>> +    ;
>> +}
>>
>> Added: cfe/trunk/test/Parser/extra-semi-resulting-in-nullstmt.cpp
>> URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Parser/extra-semi-resulting-in-nullstmt.cpp?rev=347339&view=auto
>> ==============================================================================
>> --- cfe/trunk/test/Parser/extra-semi-resulting-in-nullstmt.cpp (added)
>> +++ cfe/trunk/test/Parser/extra-semi-resulting-in-nullstmt.cpp Tue Nov 20 10:59:05 2018
>> @@ -0,0 +1,96 @@
>> +// RUN: %clang_cc1 -fsyntax-only -Wextra-semi-stmt -verify %s
>> +// RUN: cp %s %t
>> +// RUN: %clang_cc1 -x c++ -Wextra-semi-stmt -fixit %t
>> +// RUN: %clang_cc1 -x c++ -Wextra-semi-stmt -Werror %t
>> +
>> +#define GOODMACRO(varname) int varname
>> +#define BETTERMACRO(varname) GOODMACRO(varname);
>> +#define NULLMACRO(varname)
>> +
>> +enum MyEnum {
>> +  E1,
>> +  E2
>> +};
>> +
>> +void test() {
>> +  ; // expected-warning {{empty expression statement has no effect; remove unnecessary ';' to silence this warning}}
>> +  ;
>> +
>> +  // This removal of extra semi also consumes all the comments.
>> +  // clang-format: off
>> +  ;;;
>> +  // clang-format: on
>> +
>> +  // clang-format: off
>> +  ;NULLMACRO(ZZ);
>> +  // clang-format: on
>> +
>> +  {}; // expected-warning {{empty expression statement has no effect; remove unnecessary ';' to silence this warning}}
>> +
>> +  {
>> +    ; // expected-warning {{empty expression statement has no effect; remove unnecessary ';' to silence this warning}}
>> +  }
>> +
>> +  if (true) {
>> +    ; // expected-warning {{empty expression statement has no effect; remove unnecessary ';' to silence this warning}}
>> +  }
>> +
>> +  GOODMACRO(v0); // OK
>> +
>> +  GOODMACRO(v1;) // OK
>> +
>> +  BETTERMACRO(v2) // OK
>> +
>> +  BETTERMACRO(v3;) // Extra ';', but within macro expansion, so ignored.
>> +
>> +  BETTERMACRO(v4); // expected-warning {{empty expression statement has no effect; remove unnecessary ';' to silence this warning}}
>> +
>> +  BETTERMACRO(v5;); // expected-warning {{empty expression statement has no effect; remove unnecessary ';' to silence this warning}}
>> +
>> +  NULLMACRO(v6) // OK
>> +
>> +  NULLMACRO(v7); // OK. This could be either GOODMACRO() or BETTERMACRO() situation, so we can't know we can drop it.
>> +
>> +  if (true)
>> +    ; // OK
>> +
>> +  while (true)
>> +    ; // OK
>> +
>> +  do
>> +    ; // OK
>> +  while (true);
>> +
>> +  for (;;) // OK
>> +    ;      // OK
>> +
>> +  MyEnum my_enum;
>> +  switch (my_enum) {
>> +  case E1:
>> +    // stuff
>> +    break;
>> +  case E2:; // OK
>> +  }
>> +
>> +  for (;;) {
>> +    for (;;) {
>> +      goto contin_outer;
>> +    }
>> +  contin_outer:; // OK
>> +  }
>> +}
>> +
>> +;
>> +
>> +namespace NS {};
>> +
>> +void foo(int x) {
>> +  switch (x) {
>> +  case 0:
>> +    [[fallthrough]];
>> +  case 1:
>> +    return;
>> +  }
>> +}
>> +
>> +[[]];
>>
>>
>> _______________________________________________
>> cfe-commits mailing list
>> cfe-commits at lists.llvm.org
>> http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


More information about the cfe-commits mailing list