On 12/4/18 5:04 PM, George Karpenkov via cfe-dev wrote:

On Dec 4, 2018, at 4:47 PM, Aaron Ballman <aa...@aaronballman.com> wrote:

On Tue, Dec 4, 2018 at 7:35 PM George Karpenkov <ekarpen...@apple.com> wrote:
Hi Roman,

I’m against this new warning.

A very common idiom is defining a “DEBUG” macro to be a no-op in release, and a 
logging function otherwise.
The new introduced warning warns on all such cases (e.g. 'DEBUG(“mystring”);')
Yeah, that's not good.

As noted in the review, Clang warnings should generally not be used to diagnose 
style issues — and an extra semicolon does not seem to be a common bug source.

It is a problem in practice for us since we have projects which enable all 
available warnings and also enable “-Werror”.
Would you be okay with the warning if it was only diagnosed when the
source location of the semi-colon is not immediately preceded by a
macro expansion location? e.g.,

EMPTY_EXPANSION(12); // Not diagnosed
EMPTY_EXPANSION; // Not diagnosed
; // Diagnosed
This could work provided that all empty space preceding the semicolon is 
ignored (as the macro could be separated by space(s) or newline(s).
I’m not sure the complexity would be worth it, as I haven’t seen bugs arising 
from extra semicolons and empty statements,
but it would take care of my use case.


Or rather when the *previous* semicolon is coming from a macro.

~Aaron

Regards,
George


On Nov 20, 2018, at 10:59 AM, Roman Lebedev via cfe-commits 
<cfe-commits@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@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
_______________________________________________
cfe-dev mailing list
cfe-...@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to