diff mbox

New C++ warning option '-Wduplicated-access-specifiers'

Message ID tkrat.2284ed76aa47e2e7@netcologne.de
State New
Headers show

Commit Message

Volker Reichelt July 20, 2017, 4:35 p.m. UTC
Hi,

the following patch introduces a new C++ warning option
-Wduplicated-access-specifiers that warns about redundant
access-specifiers in classes, e.g.

  class B
  {
    public:
      B();

    private:
      void foo();
    private:
      int i;
  };

test.cc:8:5: warning: duplicate 'private' access-specifier [-Wduplicated-access-specifiers]
     private:
     ^~~~~~~
     -------
test.cc:6:5: note: access-specifier was previously given here
     private:
     ^~~~~~~

The test is implemented by tracking the location of the last
access-specifier together with the access-specifier itself.
The location serves two purposes: the obvious one is to be able to
print the location in the note that comes with the warning.
The second purpose is to be able to distinguish an access-specifier
given by the user from the default of the class type (i.e. public for
'struct' and private for 'class') where the location is set to
UNKNOWN_LOCATION. The warning is only issued if the user gives the
access-specifier twice, i.e. if the previous location is not
UNKNOWN_LOCATION.

One could actually make this a two-level warning so that on the
higher level also the default class-type settings are taken into
account. Would that be helpful? I could prepare a second patch for that.

Bootstrapped and regtested on x86_64-pc-linux-gnu.
OK for trunk?

Btw, there are about 50 places in our libstdc++ headers where this
warning triggers. I'll come up with a patch for this later.

Regards,
Volker


2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>

	* doc/invoke.texi (-Wduplicated-access-specifiers): Document new
	warning option.

Comments

David Malcolm July 21, 2017, 3:56 p.m. UTC | #1
On Thu, 2017-07-20 at 18:35 +0200, Volker Reichelt wrote:
> Hi,
> 
> the following patch introduces a new C++ warning option
> -Wduplicated-access-specifiers that warns about redundant
> access-specifiers in classes, e.g.
> 
>   class B
>   {
>     public:
>       B();
> 
>     private:
>       void foo();
>     private:
>       int i;
>   };
> 
> test.cc:8:5: warning: duplicate 'private' access-specifier [
> -Wduplicated-access-specifiers]
>      private:
>      ^~~~~~~
>      -------
> test.cc:6:5: note: access-specifier was previously given here
>      private:
>      ^~~~~~~

Thanks for working on this.

I'm not able to formally review, but you did CC me; various comments below throughout.

> The test is implemented by tracking the location of the last
> access-specifier together with the access-specifier itself.
> The location serves two purposes: the obvious one is to be able to
> print the location in the note that comes with the warning.
> The second purpose is to be able to distinguish an access-specifier
> given by the user from the default of the class type (i.e. public for
> 'struct' and private for 'class') where the location is set to
> UNKNOWN_LOCATION. The warning is only issued if the user gives the
> access-specifier twice, i.e. if the previous location is not
> UNKNOWN_LOCATION.

Presumably given

struct foo
{
public:
  /* ... *
};

we could issue something like:

  warning: access-specifier 'public' is redundant within 'struct'

for the first; similarly for:

class bar
{
private:
};

we could issue:

  warning: access-specifier 'private' is redundant within 'class'


> One could actually make this a two-level warning so that on the
> higher level also the default class-type settings are taken into
> account. Would that be helpful? I could prepare a second patch for
> that.

I'm not sure how best to structure it.

FWIW, when I first saw the patch, I wasn't a big fan of the warning, as I like to use access-specifiers to break up the kinds of entities within a class.

For example, our coding guidelines 
  https://gcc.gnu.org/codingconventions.html#Class_Form
recommend:

"first define all public types,
then define all non-public types,
then declare all public constructors,
...
then declare all non-public member functions, and
then declare all non-public member variables."

I find it's useful to put a redundant "private:" between the last two,
e.g.:

class baz
{
 public:
  ...

 private:
  void some_private_member ();

 private:
  int m_some_field;
};

to provide a subdivision between the private member functions and the
private data fields.

This might be a violation of our guidelines (though if so, I'm not sure
it's explicitly stated), but I find it useful, and the patch would warn
about it.

Having said that, looking at e.g. the "jit" subdir, I see lots of
places where the warning would fire.  In some of these, the code has a
bit of a "smell", so maybe I like the warning after all... in that it
can be good for a new warning to draw attention to code that might need
work.

Sorry that this is rambling; comments on the patch inline below.

Bootstrapped and regtested on x86_64-pc-linux-gnu.
> OK for trunk?
> 
> Btw, there are about 50 places in our libstdc++ headers where this
> warning triggers. I'll come up with a patch for this later.
> 
> Regards,
> Volker
> 
> 
> 2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>
> 
>         * doc/invoke.texi (-Wduplicated-access-specifiers): Document
> new
>         warning option.
> 
> Index: gcc/doc/invoke.texi
> ===================================================================
> --- gcc/doc/invoke.texi (revision 250356)
> +++ gcc/doc/invoke.texi (working copy)
> @@ -275,7 +275,7 @@
>  -Wdisabled-optimization @gol
>  -Wno-discarded-qualifiers  -Wno-discarded-array-qualifiers @gol
>  -Wno-div-by-zero  -Wdouble-promotion @gol
> --Wduplicated-branches  -Wduplicated-cond @gol
> +-Wduplicated-access-specifiers  -Wduplicated-branches  -Wduplicated
> -cond @gol
>  -Wempty-body  -Wenum-compare  -Wno-endif-labels  -Wexpansion-to
> -defined @gol
>  -Werror  -Werror=*  -Wextra-semi  -Wfatal-errors @gol
>  -Wfloat-equal  -Wformat  -Wformat=2 @gol
> @@ -5388,6 +5388,12 @@
>  
>  This warning is enabled by @option{-Wall}.
>  
> +@item -Wduplicated-access-specifiers
> +@opindex Wno-duplicated-access-specifiers
> +@opindex Wduplicated-access-specifiers
> +Warn when an access-specifier is redundant because it was already
> given
> +before.

Presumably this should be marked as C++-specific.

I think it's best to give an example for any warning, though we don't
do that currently.

>  @item -Wduplicated-branches
>  @opindex Wno-duplicated-branches
>  @opindex Wduplicated-branches
> ===================================================================
> 
> 
> 2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>
> 
>         * c.opt (Wduplicated-access-specifiers): New C++ warning
> flag.
> 
> Index: gcc/c-family/c.opt
> ===================================================================
> --- gcc/c-family/c.opt  (revision 250356)
> +++ gcc/c-family/c.opt  (working copy)
> @@ -476,6 +476,10 @@
>  C ObjC C++ ObjC++ Var(warn_div_by_zero) Init(1) Warning
>  Warn about compile-time integer division by zero.
>  
> +Wduplicated-access-specifiers
> +C++ ObjC++ Var(warn_duplicated_access) Warning
> +Warn about duplicated access-specifiers.
> +
>  Wduplicated-branches
>  C ObjC C++ ObjC++ Var(warn_duplicated_branches) Init(0) Warning
>  Warn about duplicated branches in if-else statements.
> ===================================================================
> 
> 
> 2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>
> 
>         * cp-tree.h (struct saved_scope): New access_specifier_loc
> variable.
>         (current_access_specifier_loc): New macro.
>         * class.c (struct class_stack_node): New access_loc variable.
>         (pushclass): Push current_access_specifier_loc.  Set new
>         initial value.
>         (popclass): Pop current_access_specifier_loc.
>         * parser.c (cp_parser_member_specification_opt): Warn about
>         duplicated access-specifier.  Set
> current_access_specifier_loc.
> 
> Index: gcc/cp/cp-tree.h
> ===================================================================
> --- gcc/cp/cp-tree.h    (revision 250356)
> +++ gcc/cp/cp-tree.h    (working copy)
> @@ -1531,6 +1531,7 @@
>    tree class_name;
>    tree class_type;
>    tree access_specifier;
> +  source_location access_specifier_loc;
>    tree function_decl;
>    vec<tree, va_gc> *lang_base;
>    tree lang_name;
> @@ -1592,6 +1593,7 @@
>     class, or union).  */
>  
>  #define current_access_specifier scope_chain->access_specifier
> +#define current_access_specifier_loc scope_chain
> ->access_specifier_loc
>  
>  /* Pointer to the top of the language name stack.  */
>  
> Index: gcc/cp/class.c
> ===================================================================
> --- gcc/cp/class.c      (revision 250356)
> +++ gcc/cp/class.c      (working copy)
> @@ -60,6 +60,7 @@
>    /* The access specifier pending for new declarations in the scope
> of
>       this class.  */
>    tree access;
> +  location_t access_loc;
>  
>    /* If were defining TYPE, the names used in this class.  */
>    splay_tree names_used;
> @@ -7723,6 +7724,7 @@
>    csn->name = current_class_name;
>    csn->type = current_class_type;
>    csn->access = current_access_specifier;
> +  csn->access_loc = current_access_specifier_loc;
>    csn->names_used = 0;
>    csn->hidden = 0;
>    current_class_depth++;
> @@ -7738,6 +7740,7 @@
>    current_access_specifier = (CLASSTYPE_DECLARED_CLASS (type)
>                               ? access_private_node
>                               : access_public_node);
> +  current_access_specifier_loc = UNKNOWN_LOCATION;
>  
>    if (previous_class_level
>        && type != previous_class_level->this_entity
> @@ -7777,6 +7780,7 @@
>    current_class_name =
> current_class_stack[current_class_depth].name;
>    current_class_type =
> current_class_stack[current_class_depth].type;
>    current_access_specifier =
> current_class_stack[current_class_depth].access;
> +  current_access_specifier_loc =
> current_class_stack[current_class_depth].access_loc;
>    if (current_class_stack[current_class_depth].names_used)
>      splay_tree_delete
> (current_class_stack[current_class_depth].names_used);
>  }
> Index: gcc/cp/parser.c
> ===================================================================
> --- gcc/cp/parser.c     (revision 250356)
> +++ gcc/cp/parser.c     (working copy)
> @@ -23113,8 +23113,24 @@
>         case RID_PRIVATE:
>           /* Consume the access-specifier.  */
>           cp_lexer_consume_token (parser->lexer);
> +
> +         /* Warn if the same access-specifier has been given
> already.  */
> +         if (warn_duplicated_access
> +             && current_access_specifier == token->u.value
> +             && current_access_specifier_loc != UNKNOWN_LOCATION)
> +           {
> +             gcc_rich_location richloc (token->location);
> +             richloc.add_fixit_remove ();

If the fix-it hint is to work, it presumably needs to remove two
tokens: both the "private" (or whatever), *and* the colon.

You can probably do this via:

  richloc.add_fixit_remove ();  /* for the primary location */
  richloc.add_fixit_remove (colon_loc);  /* for the colon */

and the rich_location ought to do the right thing, consolidating the removals if they are adjacent (and coping with e.g. a comment between them if they're not).

> +             warning_at_rich_loc (&richloc,
> OPT_Wduplicated_access_specifiers,
> +                                  "duplicate %qE access-specifier",
> +                                  token->u.value);
> +             inform (current_access_specifier_loc,
> +                     "access-specifier was previously given here");
> +           }
> +
>           /* Remember which access-specifier is active.  */
>           current_access_specifier = token->u.value;
> +         current_access_specifier_loc = token->location;
>           /* Look for the `:'.  */
>           cp_parser_require (parser, CPP_COLON, RT_COLON);
>           break;
> ===================================================================
> 
> 
> 2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>
> 
>         * g++.dg/warn/Wduplicated-access-specifiers-1.C: New test.
> 
> Index: gcc/testsuite/g++.dg/warn/Wduplicated-access-specifiers-1.C
> ===================================================================
> --- gcc/testsuite/g++.dg/warn/Wduplicated-access-specifiers-1.C 2017
> -07-20
> +++ gcc/testsuite/g++.dg/warn/Wduplicated-access-specifiers-1.C 2017
> -07-20
> @@ -0,0 +1,35 @@
> +// { dg-options "-Wduplicated-access-specifiers" }
> +
> +struct A
> +{
> +    int i;
> +  public:       // { dg-message "previously" }
> +    int j;
> +  public:       // { dg-warning "access-specifier" }
> +    int k;
> +  protected:    // { dg-message "previously" }
> +
> +    class B
> +    {
> +      private:  // { dg-message "previously" }
> +      private:  // { dg-warning "access-specifier" }
> +      public:
> +    };
> +
> +  protected:    // { dg-warning "access-specifier" }
> +    void a();
> +  public:
> +    void b();
> +  private:      // { dg-message "previously" }
> +    void c();
> +  private:      // { dg-warning "access-specifier" }
> +    void d();
> +  public:
> +    void e();
> +  protected:    // { dg-message "previously" }
> +    void f();
> +  protected:    // { dg-warning "access-specifier" }
> +                // { dg-message "previously" "" { target *-*-* } .-1
> }
> +    void g();
> +  protected:    // { dg-warning "access-specifier" }
> +};

If you're going to implement a fix-it hint for this, there should be a
test case that tests it (probably as a separate file, e.g. Wduplicated
-access-specifiers-2.C, to allow for the extra option --fdiagnostics
-show-caret, covering just one instance of the diagnostic firing, for
simplicity).

Hope this is constructive.
Dave
David Malcolm July 21, 2017, 4:47 p.m. UTC | #2
On Fri, 2017-07-21 at 11:56 -0400, David Malcolm wrote:
> On Thu, 2017-07-20 at 18:35 +0200, Volker Reichelt wrote:
> > Hi,
> > 
> > the following patch introduces a new C++ warning option
> > -Wduplicated-access-specifiers that warns about redundant
> > access-specifiers in classes, e.g.
> > 
> >   class B
> >   {
> >     public:
> >       B();
> > 
> >     private:
> >       void foo();
> >     private:
> >       int i;
> >   };
> > 
> > test.cc:8:5: warning: duplicate 'private' access-specifier [
> > -Wduplicated-access-specifiers]
> >      private:
> >      ^~~~~~~
> >      -------
> > test.cc:6:5: note: access-specifier was previously given here
> >      private:
> >      ^~~~~~~
> 
> Thanks for working on this.
> 
> I'm not able to formally review, but you did CC me; various comments
> below throughout.
> 
> > The test is implemented by tracking the location of the last
> > access-specifier together with the access-specifier itself.
> > The location serves two purposes: the obvious one is to be able to
> > print the location in the note that comes with the warning.
> > The second purpose is to be able to distinguish an access-specifier
> > given by the user from the default of the class type (i.e. public
> > for
> > 'struct' and private for 'class') where the location is set to
> > UNKNOWN_LOCATION. The warning is only issued if the user gives the
> > access-specifier twice, i.e. if the previous location is not
> > UNKNOWN_LOCATION.
> 
> Presumably given
> 
> struct foo
> {
> public:
>   /* ... *
> };
> 
> we could issue something like:
> 
>   warning: access-specifier 'public' is redundant within 'struct'
> 
> for the first; similarly for:
> 
> class bar
> {
> private:
> };
> 
> we could issue:
> 
>   warning: access-specifier 'private' is redundant within 'class'
> 
> 
> > One could actually make this a two-level warning so that on the
> > higher level also the default class-type settings are taken into
> > account. Would that be helpful? I could prepare a second patch for
> > that.
> 
> I'm not sure how best to structure it.

Maybe combine the two ideas, and call it
  -Wredundant-access-specifiers
?

Or just "-Waccess-specifiers" ?

[...snip...]

Dave
Volker Reichelt July 21, 2017, 5:17 p.m. UTC | #3
On 21 Jul, David Malcolm wrote:
> On Fri, 2017-07-21 at 11:56 -0400, David Malcolm wrote:
>> On Thu, 2017-07-20 at 18:35 +0200, Volker Reichelt wrote:
>> > Hi,
>> > 
>> > the following patch introduces a new C++ warning option
>> > -Wduplicated-access-specifiers that warns about redundant
>> > access-specifiers in classes, e.g.
>> > 
>> >   class B
>> >   {
>> >     public:
>> >       B();
>> > 
>> >     private:
>> >       void foo();
>> >     private:
>> >       int i;
>> >   };
>> > 
>> > test.cc:8:5: warning: duplicate 'private' access-specifier [
>> > -Wduplicated-access-specifiers]
>> >      private:
>> >      ^~~~~~~
>> >      -------
>> > test.cc:6:5: note: access-specifier was previously given here
>> >      private:
>> >      ^~~~~~~
>> 
>> Thanks for working on this.
>> 
>> I'm not able to formally review, but you did CC me; various comments
>> below throughout.
>> 
>> > The test is implemented by tracking the location of the last
>> > access-specifier together with the access-specifier itself.
>> > The location serves two purposes: the obvious one is to be able to
>> > print the location in the note that comes with the warning.
>> > The second purpose is to be able to distinguish an access-specifier
>> > given by the user from the default of the class type (i.e. public
>> > for
>> > 'struct' and private for 'class') where the location is set to
>> > UNKNOWN_LOCATION. The warning is only issued if the user gives the
>> > access-specifier twice, i.e. if the previous location is not
>> > UNKNOWN_LOCATION.
>> 
>> Presumably given
>> 
>> struct foo
>> {
>> public:
>>   /* ... *
>> };
>> 
>> we could issue something like:
>> 
>>   warning: access-specifier 'public' is redundant within 'struct'
>> 
>> for the first; similarly for:
>> 
>> class bar
>> {
>> private:
>> };
>> 
>> we could issue:
>> 
>>   warning: access-specifier 'private' is redundant within 'class'
>> 
>> 
>> > One could actually make this a two-level warning so that on the
>> > higher level also the default class-type settings are taken into
>> > account. Would that be helpful? I could prepare a second patch for
>> > that.
>> 
>> I'm not sure how best to structure it.
> 
> Maybe combine the two ideas, and call it
>   -Wredundant-access-specifiers
> ?
> 
> Or just "-Waccess-specifiers" ?
> 
> [...snip...]
> 
> Dave

Thanks for having a look at this!

My favorite version would be to use '-Waccess-specifiers=1' for the
original warning and '-Waccess-specifiers=2' for the stricter version
that also checks against the class-type default.

We could then let '-Waccess-specifiers' default to any of those two.
I'm afraid that level 2 will fire way too often for legacy code
(and there might be coding conventions that require an access specifier
at the beginning of a class/struct). So I'd vote for level 1 as default.

The last argument is also the reason why I'd like to see a two-lveel
warning instead of just different error messages (although these are
very helpful for seeing what's really goiing on with your code).

What do you think?

Regards,
Volker
Martin Sebor July 21, 2017, 5:41 p.m. UTC | #4
On 07/20/2017 10:35 AM, Volker Reichelt wrote:
> Hi,
>
> the following patch introduces a new C++ warning option
> -Wduplicated-access-specifiers that warns about redundant
> access-specifiers in classes, e.g.
>
>   class B
>   {
>     public:
>       B();
>
>     private:
>       void foo();
>     private:
>       int i;
>   };

I'm very fond of warnings that help find real bugs, or even that
provide an opportunity to review code for potential problems or
inefficiencies and suggest a possibility to improve it in some
way (make it clearer, or easier for humans or compilers to find
real bugs in, or faster, etc.), even if the code isn't strictly
incorrect.

In this case I'm having trouble coming up with an example where
the warning would have this effect.  What do you have in mind?
(Duplicate access specifiers tend to crop up in large classes
and/or as a result of preprocessor conditionals.)

Btw., there are examples of poor coding practices where I can
imagine a warning focused on access specifiers being helpful.
For instance, a class whose member functions maintain non-trivial
invariants among its data members should declare the data private
to prevent clients from inadvertently breaking those invariants.
A specific example might be a container class (like string or
vector) that owns the block of memory it allocates.  A warning
that detected when such members are publicly accessible could
help improve encapsulation.  I suspect this would be challenging
to implement and would likely require some non-trivial analysis
in the middle end.

Another example is a class that defines an inaccessible member
that isn't used (e.g., class A { int i; }; that Clang detects
with -Wunused-private-field; or class A { void f () { } };).
I would expect this to be doable in the front end.

Martin
Volker Reichelt July 21, 2017, 5:58 p.m. UTC | #5
On 21 Jul, David Malcolm wrote:
> On Thu, 2017-07-20 at 18:35 +0200, Volker Reichelt wrote:
>> Hi,
>> 
>> the following patch introduces a new C++ warning option
>> -Wduplicated-access-specifiers that warns about redundant
>> access-specifiers in classes, e.g.
>> 
>>   class B
>>   {
>>     public:
>>       B();
>> 
>>     private:
>>       void foo();
>>     private:
>>       int i;
>>   };
>> 
>> test.cc:8:5: warning: duplicate 'private' access-specifier [
>> -Wduplicated-access-specifiers]
>>      private:
>>      ^~~~~~~
>>      -------
>> test.cc:6:5: note: access-specifier was previously given here
>>      private:
>>      ^~~~~~~
> 
> Thanks for working on this.
> 
> I'm not able to formally review, but you did CC me; various comments below throughout.
> 
>> The test is implemented by tracking the location of the last
>> access-specifier together with the access-specifier itself.
>> The location serves two purposes: the obvious one is to be able to
>> print the location in the note that comes with the warning.
>> The second purpose is to be able to distinguish an access-specifier
>> given by the user from the default of the class type (i.e. public for
>> 'struct' and private for 'class') where the location is set to
>> UNKNOWN_LOCATION. The warning is only issued if the user gives the
>> access-specifier twice, i.e. if the previous location is not
>> UNKNOWN_LOCATION.
> 
> Presumably given
> 
> struct foo
> {
> public:
>   /* ... *
> };
> 
> we could issue something like:
> 
>   warning: access-specifier 'public' is redundant within 'struct'
> 
> for the first; similarly for:
> 
> class bar
> {
> private:
> };
> 
> we could issue:
> 
>   warning: access-specifier 'private' is redundant within 'class'
> 
> 
>> One could actually make this a two-level warning so that on the
>> higher level also the default class-type settings are taken into
>> account. Would that be helpful? I could prepare a second patch for
>> that.
> 
> I'm not sure how best to structure it.
> 
> FWIW, when I first saw the patch, I wasn't a big fan of the warning, as I like to use access-specifiers to break up the kinds of entities within a class.
> 
> For example, our coding guidelines 
>   https://gcc.gnu.org/codingconventions.html#Class_Form
> recommend:
> 
> "first define all public types,
> then define all non-public types,
> then declare all public constructors,
> ...
> then declare all non-public member functions, and
> then declare all non-public member variables."
> 
> I find it's useful to put a redundant "private:" between the last two,
> e.g.:
> 
> class baz
> {
>  public:
>   ...
> 
>  private:
>   void some_private_member ();
> 
>  private:
>   int m_some_field;
> };
> 
> to provide a subdivision between the private member functions and the
> private data fields.

That's what also can be seen in our libstdc++ to some extent.
The other half of the warnings indicate redundant access-specifiers.

It's up to the user to keep those duplicate access-specifiers as
subdividers or to use something else (like comments) to do that
and to switch on the warning for her/his code.
Because the subdivider usage seems to be relatively common,
I don't want to enable the warning by -Wall or -Wextra.

> This might be a violation of our guidelines (though if so, I'm not sure
> it's explicitly stated), but I find it useful, and the patch would warn
> about it.
> 
> Having said that, looking at e.g. the "jit" subdir, I see lots of
> places where the warning would fire.  In some of these, the code has a
> bit of a "smell", so maybe I like the warning after all... in that it
> can be good for a new warning to draw attention to code that might need
> work.
> 
> Sorry that this is rambling; comments on the patch inline below.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu.
>> OK for trunk?
>> 
>> Btw, there are about 50 places in our libstdc++ headers where this
>> warning triggers. I'll come up with a patch for this later.
>> 
>> Regards,
>> Volker
>> 
>> 
>> 2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>
>> 
>>         * doc/invoke.texi (-Wduplicated-access-specifiers): Document
>> new
>>         warning option.
>> 
>> Index: gcc/doc/invoke.texi
>> ===================================================================
>> --- gcc/doc/invoke.texi (revision 250356)
>> +++ gcc/doc/invoke.texi (working copy)
>> @@ -275,7 +275,7 @@
>>  -Wdisabled-optimization @gol
>>  -Wno-discarded-qualifiers  -Wno-discarded-array-qualifiers @gol
>>  -Wno-div-by-zero  -Wdouble-promotion @gol
>> --Wduplicated-branches  -Wduplicated-cond @gol
>> +-Wduplicated-access-specifiers  -Wduplicated-branches  -Wduplicated
>> -cond @gol
>>  -Wempty-body  -Wenum-compare  -Wno-endif-labels  -Wexpansion-to
>> -defined @gol
>>  -Werror  -Werror=*  -Wextra-semi  -Wfatal-errors @gol
>>  -Wfloat-equal  -Wformat  -Wformat=2 @gol
>> @@ -5388,6 +5388,12 @@
>>  
>>  This warning is enabled by @option{-Wall}.
>>  
>> +@item -Wduplicated-access-specifiers
>> +@opindex Wno-duplicated-access-specifiers
>> +@opindex Wduplicated-access-specifiers
>> +Warn when an access-specifier is redundant because it was already
>> given
>> +before.
> 
> Presumably this should be marked as C++-specific.

Oops, good point!

> I think it's best to give an example for any warning, though we don't
> do that currently.

Sounds reasonable. Especially because 'access-specifier' is a very
technical term that is not linked to 'public/protected/private'
by everybody.

[...snip...]

>> Index: gcc/cp/parser.c
>> ===================================================================
>> --- gcc/cp/parser.c     (revision 250356)
>> +++ gcc/cp/parser.c     (working copy)
>> @@ -23113,8 +23113,24 @@
>>         case RID_PRIVATE:
>>           /* Consume the access-specifier.  */
>>           cp_lexer_consume_token (parser->lexer);
>> +
>> +         /* Warn if the same access-specifier has been given
>> already.  */
>> +         if (warn_duplicated_access
>> +             && current_access_specifier == token->u.value
>> +             && current_access_specifier_loc != UNKNOWN_LOCATION)
>> +           {
>> +             gcc_rich_location richloc (token->location);
>> +             richloc.add_fixit_remove ();
> 
> If the fix-it hint is to work, it presumably needs to remove two
> tokens: both the "private" (or whatever), *and* the colon.

I did not do this because I did not want to reorder the code.
OTOH just moving the cp_parser_require line a little bit up
should not be a big deal for better diagnostics.

> You can probably do this via:
> 
>   richloc.add_fixit_remove ();  /* for the primary location */
>   richloc.add_fixit_remove (colon_loc);  /* for the colon */
> 
> and the rich_location ought to do the right thing, consolidating the removals if they are adjacent (and coping with e.g. a comment between them if they're not).
>
>> +             warning_at_rich_loc (&richloc,
>> OPT_Wduplicated_access_specifiers,
>> +                                  "duplicate %qE access-specifier",
>> +                                  token->u.value);
>> +             inform (current_access_specifier_loc,
>> +                     "access-specifier was previously given here");
>> +           }
>> +
>>           /* Remember which access-specifier is active.  */
>>           current_access_specifier = token->u.value;
>> +         current_access_specifier_loc = token->location;
>>           /* Look for the `:'.  */
>>           cp_parser_require (parser, CPP_COLON, RT_COLON);
>>           break;
>> ===================================================================
>> 
>> 
>> 2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>
>> 
>>         * g++.dg/warn/Wduplicated-access-specifiers-1.C: New test.

{...snip...]

> If you're going to implement a fix-it hint for this, there should be a
> test case that tests it (probably as a separate file, e.g. Wduplicated
> -access-specifiers-2.C, to allow for the extra option --fdiagnostics
> -show-caret, covering just one instance of the diagnostic firing, for
> simplicity).

I actually did try that, but dejagnu somehow gets confused.
To me it looks like the reason for this is that both multi-line messages
are so similar. I'll give it a second try, though.

> Hope this is constructive.

Yes, it is!

> Dave
David Malcolm July 22, 2017, 12:56 a.m. UTC | #6
On Fri, 2017-07-21 at 19:58 +0200, Volker Reichelt wrote:
> On 21 Jul, David Malcolm wrote:
> > On Thu, 2017-07-20 at 18:35 +0200, Volker Reichelt wrote:
> >> Hi,
> >> 
> >> the following patch introduces a new C++ warning option
> >> -Wduplicated-access-specifiers that warns about redundant
> >> access-specifiers in classes, e.g.
> >> 
> >>   class B
> >>   {
> >>     public:
> >>       B();
> >> 
> >>     private:
> >>       void foo();
> >>     private:
> >>       int i;
> >>   };
> >> 
> >> test.cc:8:5: warning: duplicate 'private' access-specifier [
> >> -Wduplicated-access-specifiers]
> >>      private:
> >>      ^~~~~~~
> >>      -------
> >> test.cc:6:5: note: access-specifier was previously given here
> >>      private:
> >>      ^~~~~~~
> > 
> > Thanks for working on this.
> > 
> > I'm not able to formally review, but you did CC me; various
> comments below throughout.
> > 
> >> The test is implemented by tracking the location of the last
> >> access-specifier together with the access-specifier itself.
> >> The location serves two purposes: the obvious one is to be able to
> >> print the location in the note that comes with the warning.
> >> The second purpose is to be able to distinguish an access
> -specifier
> >> given by the user from the default of the class type (i.e. public
> for
> >> 'struct' and private for 'class') where the location is set to
> >> UNKNOWN_LOCATION. The warning is only issued if the user gives the
> >> access-specifier twice, i.e. if the previous location is not
> >> UNKNOWN_LOCATION.
> > 
> > Presumably given
> > 
> > struct foo
> > {
> > public:
> >   /* ... *
> > };
> > 
> > we could issue something like:
> > 
> >   warning: access-specifier 'public' is redundant within 'struct'
> > 
> > for the first; similarly for:
> > 
> > class bar
> > {
> > private:
> > };
> > 
> > we could issue:
> > 
> >   warning: access-specifier 'private' is redundant within 'class'
> > 
> > 
> >> One could actually make this a two-level warning so that on the
> >> higher level also the default class-type settings are taken into
> >> account. Would that be helpful? I could prepare a second patch for
> >> that.
> > 
> > I'm not sure how best to structure it.
> > 
> > FWIW, when I first saw the patch, I wasn't a big fan of the
> warning, as I like to use access-specifiers to break up the kinds of
> entities within a class.
> > 
> > For example, our coding guidelines 
> >   https://gcc.gnu.org/codingconventions.html#Class_Form
> > recommend:
> > 
> > "first define all public types,
> > then define all non-public types,
> > then declare all public constructors,
> > ...
> > then declare all non-public member functions, and
> > then declare all non-public member variables."
> > 
> > I find it's useful to put a redundant "private:" between the last
> two,
> > e.g.:
> > 
> > class baz
> > {
> >  public:
> >   ...
> > 
> >  private:
> >   void some_private_member ();
> > 
> >  private:
> >   int m_some_field;
> > };
> > 
> > to provide a subdivision between the private member functions and
> the
> > private data fields.
> 
> That's what also can be seen in our libstdc++ to some extent.
> The other half of the warnings indicate redundant access-specifiers.
> 
> It's up to the user to keep those duplicate access-specifiers as
> subdividers or to use something else (like comments) to do that
> and to switch on the warning for her/his code.
> Because the subdivider usage seems to be relatively common,
> I don't want to enable the warning by -Wall or -Wextra.
> 
> > This might be a violation of our guidelines (though if so, I'm not
> sure
> > it's explicitly stated), but I find it useful, and the patch would
> warn
> > about it.
> > 
> > Having said that, looking at e.g. the "jit" subdir, I see lots of
> > places where the warning would fire.  In some of these, the code
> has a
> > bit of a "smell", so maybe I like the warning after all... in that
> it
> > can be good for a new warning to draw attention to code that might
> need
> > work.
> > 
> > Sorry that this is rambling; comments on the patch inline below.
> > 
> > Bootstrapped and regtested on x86_64-pc-linux-gnu.
> >> OK for trunk?
> >> 
> >> Btw, there are about 50 places in our libstdc++ headers where this
> >> warning triggers. I'll come up with a patch for this later.
> >> 
> >> Regards,
> >> Volker
> >> 
> >> 
> >> 2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>
> >> 
> >>         * doc/invoke.texi (-Wduplicated-access-specifiers):
> Document
> >> new
> >>         warning option.
> >> 
> >> Index: gcc/doc/invoke.texi
> >>
> ===================================================================
> >> --- gcc/doc/invoke.texi (revision 250356)
> >> +++ gcc/doc/invoke.texi (working copy)
> >> @@ -275,7 +275,7 @@
> >>  -Wdisabled-optimization @gol
> >>  -Wno-discarded-qualifiers  -Wno-discarded-array-qualifiers @gol
> >>  -Wno-div-by-zero  -Wdouble-promotion @gol
> >> --Wduplicated-branches  -Wduplicated-cond @gol
> >> +-Wduplicated-access-specifiers  -Wduplicated-branches  
> -Wduplicated
> >> -cond @gol
> >>  -Wempty-body  -Wenum-compare  -Wno-endif-labels  -Wexpansion-to
> >> -defined @gol
> >>  -Werror  -Werror=*  -Wextra-semi  -Wfatal-errors @gol
> >>  -Wfloat-equal  -Wformat  -Wformat=2 @gol
> >> @@ -5388,6 +5388,12 @@
> >>  
> >>  This warning is enabled by @option{-Wall}.
> >>  
> >> +@item -Wduplicated-access-specifiers
> >> +@opindex Wno-duplicated-access-specifiers
> >> +@opindex Wduplicated-access-specifiers
> >> +Warn when an access-specifier is redundant because it was already
> >> given
> >> +before.
> > 
> > Presumably this should be marked as C++-specific.
> 
> Oops, good point!
> 
> > I think it's best to give an example for any warning, though we
> don't
> > do that currently.
> 
> Sounds reasonable. Especially because 'access-specifier' is a very
> technical term that is not linked to 'public/protected/private'
> by everybody.
> 
> [...snip...]
> 
> >> Index: gcc/cp/parser.c
> >>
> ===================================================================
> >> --- gcc/cp/parser.c     (revision 250356)
> >> +++ gcc/cp/parser.c     (working copy)
> >> @@ -23113,8 +23113,24 @@
> >>         case RID_PRIVATE:
> >>           /* Consume the access-specifier.  */
> >>           cp_lexer_consume_token (parser->lexer);
> >> +
> >> +         /* Warn if the same access-specifier has been given
> >> already.  */
> >> +         if (warn_duplicated_access
> >> +             && current_access_specifier == token->u.value
> >> +             && current_access_specifier_loc != UNKNOWN_LOCATION)
> >> +           {
> >> +             gcc_rich_location richloc (token->location);
> >> +             richloc.add_fixit_remove ();
> > 
> > If the fix-it hint is to work, it presumably needs to remove two
> > tokens: both the "private" (or whatever), *and* the colon.
> 
> I did not do this because I did not want to reorder the code.
> OTOH just moving the cp_parser_require line a little bit up
> should not be a big deal for better diagnostics.
> 
> > You can probably do this via:
> > 
> >   richloc.add_fixit_remove ();  /* for the primary location */
> >   richloc.add_fixit_remove (colon_loc);  /* for the colon */
> > 
> > and the rich_location ought to do the right thing, consolidating
> the removals if they are adjacent (and coping with e.g. a comment
> between them if they're not).
> >
> >> +             warning_at_rich_loc (&richloc,
> >> OPT_Wduplicated_access_specifiers,
> >> +                                  "duplicate %qE access
> -specifier",
> >> +                                  token->u.value);
> >> +             inform (current_access_specifier_loc,
> >> +                     "access-specifier was previously given
> here");
> >> +           }
> >> +
> >>           /* Remember which access-specifier is active.  */
> >>           current_access_specifier = token->u.value;
> >> +         current_access_specifier_loc = token->location;
> >>           /* Look for the `:'.  */
> >>           cp_parser_require (parser, CPP_COLON, RT_COLON);
> >>           break;
> >>
> ===================================================================
> >> 
> >> 
> >> 2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>
> >> 
> >>         * g++.dg/warn/Wduplicated-access-specifiers-1.C: New test.
> 
> {...snip...]
> 
> > If you're going to implement a fix-it hint for this, there should
> be a
> > test case that tests it (probably as a separate file, e.g.
> Wduplicated
> > -access-specifiers-2.C, to allow for the extra option -
> -fdiagnostics
> > -show-caret, covering just one instance of the diagnostic firing,
> for
> > simplicity).
> 
> I actually did try that, but dejagnu somehow gets confused.
> To me it looks like the reason for this is that both multi-line
> messages
> are so similar. I'll give it a second try, though.

I'm not sure what you mean by "both" multi-line messages; shouldn't
there be just one multi-line message for the fix-it hint.

Isn't this like e.g. g++.dg/cpp0x/auto1.C ?  (an example of a testcase
that verifies a deletion fix-it hint)

Dave
Volker Reichelt July 23, 2017, 8:42 p.m. UTC | #7
On 21 Jul, Martin Sebor wrote:
> On 07/20/2017 10:35 AM, Volker Reichelt wrote:
>> Hi,
>>
>> the following patch introduces a new C++ warning option
>> -Wduplicated-access-specifiers that warns about redundant
>> access-specifiers in classes, e.g.
>>
>>   class B
>>   {
>>     public:
>>       B();
>>
>>     private:
>>       void foo();
>>     private:
>>       int i;
>>   };
> 
> I'm very fond of warnings that help find real bugs, or even that
> provide an opportunity to review code for potential problems or
> inefficiencies and suggest a possibility to improve it in some
> way (make it clearer, or easier for humans or compilers to find
> real bugs in, or faster, etc.), even if the code isn't strictly
> incorrect.
> 
> In this case I'm having trouble coming up with an example where
> the warning would have this effect.  What do you have in mind?
> (Duplicate access specifiers tend to crop up in large classes
> and/or as a result of preprocessor conditionals.)

This warning fights the "tend to crop up" effect that clutters the
code. After some time these redundant access specifiers creep in
and make your code harder to read. If I see something like

  ...
    void foo() const;

  private:
    void bar();
  ...

on the screen I tend to think that 'foo' has a different access
level than bar. If that's not the case because the access-specifier
is redundant, then that's just confusing and distracting.
The warning helps to maintain readability of your code.

The benefit might not be big, but the warning comes at relatively
low cost. It passes a location around through the class stack and
checks less than a handful of tokens.

My personal motivation to implement this warning was the fact that
I changed a big Qt application suite from Qt's legacy SIGNAL-SLOT
mechanism to the new function pointer syntax of Qt5. In the old
version you had to mark slots in the following fashion:

  public slots:
    void foo();
    void bar();

But now you can use any function making the 'slots' macro obsolete.
Therefore I ended up with hundreds of redundant access-specifiers
which this warning helped to clean up. Doing this sort of thing in the
compiler with a proper parser is way safer than to write some script
to achieve this.

Btw, the SonarAnalyzer also provides this warning to clean up code:

https://sonarcloud.io/organizations/default/rules#rule_key=cpp%3AS3539

> Btw., there are examples of poor coding practices where I can
> imagine a warning focused on access specifiers being helpful.
> For instance, a class whose member functions maintain non-trivial
> invariants among its data members should declare the data private
> to prevent clients from inadvertently breaking those invariants.
> A specific example might be a container class (like string or
> vector) that owns the block of memory it allocates.  A warning
> that detected when such members are publicly accessible could
> help improve encapsulation.  I suspect this would be challenging
> to implement and would likely require some non-trivial analysis
> in the middle end.

That's way beyond the scope of what my warning is trying to achieve.

> Another example is a class that defines an inaccessible member
> that isn't used (e.g., class A { int i; }; that Clang detects
> with -Wunused-private-field; or class A { void f () { } };).
> I would expect this to be doable in the front end.

That's indeed a nice warning, too.

> Martin

Regards,
Volker
Volker Reichelt July 23, 2017, 8:48 p.m. UTC | #8
On 21 Jul, David Malcolm wrote:
> On Fri, 2017-07-21 at 19:58 +0200, Volker Reichelt wrote:
>> On 21 Jul, David Malcolm wrote:
>> > On Thu, 2017-07-20 at 18:35 +0200, Volker Reichelt wrote:
>> >> Hi,
>> >> 
>> >> the following patch introduces a new C++ warning option
>> >> -Wduplicated-access-specifiers that warns about redundant
>> >> access-specifiers in classes, e.g.
>> >> 
>> >>   class B
>> >>   {
>> >>     public:
>> >>       B();
>> >> 
>> >>     private:
>> >>       void foo();
>> >>     private:
>> >>       int i;
>> >>   };
>> >> 
>> >> test.cc:8:5: warning: duplicate 'private' access-specifier [
>> >> -Wduplicated-access-specifiers]
>> >>      private:
>> >>      ^~~~~~~
>> >>      -------
>> >> test.cc:6:5: note: access-specifier was previously given here
>> >>      private:
>> >>      ^~~~~~~

{...snip...]

>> > If you're going to implement a fix-it hint for this, there should
>> be a
>> > test case that tests it (probably as a separate file, e.g.
>> Wduplicated
>> > -access-specifiers-2.C, to allow for the extra option -
>> -fdiagnostics
>> > -show-caret, covering just one instance of the diagnostic firing,
>> for
>> > simplicity).
>> 
>> I actually did try that, but dejagnu somehow gets confused.
>> To me it looks like the reason for this is that both multi-line
>> messages
>> are so similar. I'll give it a second try, though.
> 
> I'm not sure what you mean by "both" multi-line messages; shouldn't
> there be just one multi-line message for the fix-it hint.
> 
> Isn't this like e.g. g++.dg/cpp0x/auto1.C ?  (an example of a testcase
> that verifies a deletion fix-it hint)
> 
> Dave

There are two multi-line messages. One for the warning and one for the
note, see the example above the "[...snip...]". The message after the
note consists of the first two lines of the message after the warning.
This seems to confuse dejagnu. However, I managed to work around this
problem. I'll post an updated patch soon.

Regards,
Volker
Martin Sebor July 23, 2017, 11:52 p.m. UTC | #9
On 07/23/2017 02:42 PM, Volker Reichelt wrote:
> On 21 Jul, Martin Sebor wrote:
>> On 07/20/2017 10:35 AM, Volker Reichelt wrote:
>>> Hi,
>>>
>>> the following patch introduces a new C++ warning option
>>> -Wduplicated-access-specifiers that warns about redundant
>>> access-specifiers in classes, e.g.
>>>
>>>   class B
>>>   {
>>>     public:
>>>       B();
>>>
>>>     private:
>>>       void foo();
>>>     private:
>>>       int i;
>>>   };
>>
>> I'm very fond of warnings that help find real bugs, or even that
>> provide an opportunity to review code for potential problems or
>> inefficiencies and suggest a possibility to improve it in some
>> way (make it clearer, or easier for humans or compilers to find
>> real bugs in, or faster, etc.), even if the code isn't strictly
>> incorrect.
>>
>> In this case I'm having trouble coming up with an example where
>> the warning would have this effect.  What do you have in mind?
>> (Duplicate access specifiers tend to crop up in large classes
>> and/or as a result of preprocessor conditionals.)
>
> This warning fights the "tend to crop up" effect that clutters the
> code. After some time these redundant access specifiers creep in
> and make your code harder to read. If I see something like
>
>   ...
>     void foo() const;
>
>   private:
>     void bar();
>   ...
>
> on the screen I tend to think that 'foo' has a different access
> level than bar. If that's not the case because the access-specifier
> is redundant, then that's just confusing and distracting.
> The warning helps to maintain readability of your code.
>
> The benefit might not be big, but the warning comes at relatively
> low cost. It passes a location around through the class stack and
> checks less than a handful of tokens.
>
> My personal motivation to implement this warning was the fact that
> I changed a big Qt application suite from Qt's legacy SIGNAL-SLOT
> mechanism to the new function pointer syntax of Qt5. In the old
> version you had to mark slots in the following fashion:
>
>   public slots:
>     void foo();
>     void bar();
>
> But now you can use any function making the 'slots' macro obsolete.
> Therefore I ended up with hundreds of redundant access-specifiers
> which this warning helped to clean up. Doing this sort of thing in the
> compiler with a proper parser is way safer than to write some script
> to achieve this.

Okay, thanks for clarifying that.  I think what's distracting to
one could be helpful to another.  For example, it's not uncommon
for classes with many members to use redundant access specifiers
to group blocks of related declarations.  Or, in a class with many
lines of comments (e.g., Doxygen), repeating the access specifier
every so often could be seen as helpful because otherwise there
would be no way to tell what its access is without scrolling up
or down.  It's debatable what approach to dealing with this is
best.  Java, for instance, requires every non-public member to
be declared with its own access specifier.  Some other languages
(I think D) do as well.  An argument could be made that that's
a far clearer style than using the specifier only when changing
access.  It seems to me that the most suitable approach will be
different from one project to another, if not from one person to
another.  A diagnostic that recommends a particular style (or that
helps with a specific kind of a project like the one you did for
Qt) might be a good candidate for a plugin, but enshrining any
one style (or a solution to a project-specific problem) in GCC
as a general-purpose warning doesn't seem appropriate or in line
with the definition of warnings in GCC:

   constructions that are not inherently erroneous but that are
   risky or suggest there may have been an error

Martin

PS There are other redundancies that some might say unnecessarily
clutter code.  For instance, declaring a symbol static in
an unnamed namespace, or explicitly declaring a member function
inline that's also defined within the body of a class, or
explicitly declaring a function virtual that overrides one
declared in a base class.  None of these is diagnosed, and I'd
say for good reason: they are all benign and do not suggest any
sort of a coding mistake or present an opportunity for improvement.
In fact, warning for some of them (especially the virtual function)
might even be detrimental

> Btw, the SonarAnalyzer also provides this warning to clean up code:
>
> https://sonarcloud.io/organizations/default/rules#rule_key=cpp%3AS3539

Thanks, that's an interesting data point.

Martin

>
>> Btw., there are examples of poor coding practices where I can
>> imagine a warning focused on access specifiers being helpful.
>> For instance, a class whose member functions maintain non-trivial
>> invariants among its data members should declare the data private
>> to prevent clients from inadvertently breaking those invariants.
>> A specific example might be a container class (like string or
>> vector) that owns the block of memory it allocates.  A warning
>> that detected when such members are publicly accessible could
>> help improve encapsulation.  I suspect this would be challenging
>> to implement and would likely require some non-trivial analysis
>> in the middle end.
>
> That's way beyond the scope of what my warning is trying to achieve.
>
>> Another example is a class that defines an inaccessible member
>> that isn't used (e.g., class A { int i; }; that Clang detects
>> with -Wunused-private-field; or class A { void f () { } };).
>> I would expect this to be doable in the front end.
>
> That's indeed a nice warning, too.
>
>> Martin
>
> Regards,
> Volker
>
Volker Reichelt July 25, 2017, 6:55 p.m. UTC | #10
On 23 Jul, Martin Sebor wrote:
> On 07/23/2017 02:42 PM, Volker Reichelt wrote:
>> On 21 Jul, Martin Sebor wrote:
>>> On 07/20/2017 10:35 AM, Volker Reichelt wrote:
>>>> Hi,
>>>>
>>>> the following patch introduces a new C++ warning option
>>>> -Wduplicated-access-specifiers that warns about redundant
>>>> access-specifiers in classes, e.g.
>>>>
>>>>   class B
>>>>   {
>>>>     public:
>>>>       B();
>>>>
>>>>     private:
>>>>       void foo();
>>>>     private:
>>>>       int i;
>>>>   };
>>>
>>> I'm very fond of warnings that help find real bugs, or even that
>>> provide an opportunity to review code for potential problems or
>>> inefficiencies and suggest a possibility to improve it in some
>>> way (make it clearer, or easier for humans or compilers to find
>>> real bugs in, or faster, etc.), even if the code isn't strictly
>>> incorrect.
>>>
>>> In this case I'm having trouble coming up with an example where
>>> the warning would have this effect.  What do you have in mind?
>>> (Duplicate access specifiers tend to crop up in large classes
>>> and/or as a result of preprocessor conditionals.)
>>
>> This warning fights the "tend to crop up" effect that clutters the
>> code. After some time these redundant access specifiers creep in
>> and make your code harder to read. If I see something like
>>
>>   ...
>>     void foo() const;
>>
>>   private:
>>     void bar();
>>   ...
>>
>> on the screen I tend to think that 'foo' has a different access
>> level than bar. If that's not the case because the access-specifier
>> is redundant, then that's just confusing and distracting.
>> The warning helps to maintain readability of your code.
>>
>> The benefit might not be big, but the warning comes at relatively
>> low cost. It passes a location around through the class stack and
>> checks less than a handful of tokens.
>>
>> My personal motivation to implement this warning was the fact that
>> I changed a big Qt application suite from Qt's legacy SIGNAL-SLOT
>> mechanism to the new function pointer syntax of Qt5. In the old
>> version you had to mark slots in the following fashion:
>>
>>   public slots:
>>     void foo();
>>     void bar();
>>
>> But now you can use any function making the 'slots' macro obsolete.
>> Therefore I ended up with hundreds of redundant access-specifiers
>> which this warning helped to clean up. Doing this sort of thing in the
>> compiler with a proper parser is way safer than to write some script
>> to achieve this.
> 
> Okay, thanks for clarifying that.  I think what's distracting to
> one could be helpful to another.  For example, it's not uncommon
> for classes with many members to use redundant access specifiers
> to group blocks of related declarations.  Or, in a class with many
> lines of comments (e.g., Doxygen), repeating the access specifier
> every so often could be seen as helpful because otherwise there
> would be no way to tell what its access is without scrolling up
> or down.  It's debatable what approach to dealing with this is
> best.  Java, for instance, requires every non-public member to
> be declared with its own access specifier.  Some other languages
> (I think D) do as well.  An argument could be made that that's
> a far clearer style than using the specifier only when changing
> access.  It seems to me that the most suitable approach will be
> different from one project to another, if not from one person to
> another.  A diagnostic that recommends a particular style (or that
> helps with a specific kind of a project like the one you did for
> Qt) might be a good candidate for a plugin, but enshrining any
> one style (or a solution to a project-specific problem) in GCC
> as a general-purpose warning doesn't seem appropriate or in line
> with the definition of warnings in GCC:
> 
>    constructions that are not inherently erroneous but that are
>    risky or suggest there may have been an error
> 
> Martin
> 
> PS There are other redundancies that some might say unnecessarily
> clutter code.  For instance, declaring a symbol static in
> an unnamed namespace, or explicitly declaring a member function
> inline that's also defined within the body of a class, or
> explicitly declaring a function virtual that overrides one
> declared in a base class.  None of these is diagnosed, and I'd
> say for good reason: they are all benign and do not suggest any
> sort of a coding mistake or present an opportunity for improvement.
> In fact, warning for some of them (especially the virtual function)
> might even be detrimental
> 
>> Btw, the SonarAnalyzer also provides this warning to clean up code:
>>
>> https://sonarcloud.io/organizations/default/rules#rule_key=cpp%3AS3539
> 
> Thanks, that's an interesting data point.
> 
> Martin

Maybe the whole thing of diagnostics that refer more to style instead of
correctness or potential bugs shoould be discussed (independent of my
patch) by a broader audience.

There are several tools out there like clang-tidy, SonarAnalyzer etc.
that not only check for bugs but also flag stylistic issues. For GCC I
could imagine a set of such wanings prefixed by -Wstyle- or something
similar to make the intent clear. I'll post something on the main
mailing list in the next days.

Regards,
Volker
Richard Sandiford July 27, 2017, 7:13 p.m. UTC | #11
Martin Sebor <msebor@gmail.com> writes:
> On 07/23/2017 02:42 PM, Volker Reichelt wrote:
>> On 21 Jul, Martin Sebor wrote:
>>> On 07/20/2017 10:35 AM, Volker Reichelt wrote:
>>>> Hi,
>>>>
>>>> the following patch introduces a new C++ warning option
>>>> -Wduplicated-access-specifiers that warns about redundant
>>>> access-specifiers in classes, e.g.
>>>>
>>>>   class B
>>>>   {
>>>>     public:
>>>>       B();
>>>>
>>>>     private:
>>>>       void foo();
>>>>     private:
>>>>       int i;
>>>>   };
>>>
>>> I'm very fond of warnings that help find real bugs, or even that
>>> provide an opportunity to review code for potential problems or
>>> inefficiencies and suggest a possibility to improve it in some
>>> way (make it clearer, or easier for humans or compilers to find
>>> real bugs in, or faster, etc.), even if the code isn't strictly
>>> incorrect.
>>>
>>> In this case I'm having trouble coming up with an example where
>>> the warning would have this effect.  What do you have in mind?
>>> (Duplicate access specifiers tend to crop up in large classes
>>> and/or as a result of preprocessor conditionals.)
>>
>> This warning fights the "tend to crop up" effect that clutters the
>> code. After some time these redundant access specifiers creep in
>> and make your code harder to read. If I see something like
>>
>>   ...
>>     void foo() const;
>>
>>   private:
>>     void bar();
>>   ...
>>
>> on the screen I tend to think that 'foo' has a different access
>> level than bar. If that's not the case because the access-specifier
>> is redundant, then that's just confusing and distracting.
>> The warning helps to maintain readability of your code.
>>
>> The benefit might not be big, but the warning comes at relatively
>> low cost. It passes a location around through the class stack and
>> checks less than a handful of tokens.
>>
>> My personal motivation to implement this warning was the fact that
>> I changed a big Qt application suite from Qt's legacy SIGNAL-SLOT
>> mechanism to the new function pointer syntax of Qt5. In the old
>> version you had to mark slots in the following fashion:
>>
>>   public slots:
>>     void foo();
>>     void bar();
>>
>> But now you can use any function making the 'slots' macro obsolete.
>> Therefore I ended up with hundreds of redundant access-specifiers
>> which this warning helped to clean up. Doing this sort of thing in the
>> compiler with a proper parser is way safer than to write some script
>> to achieve this.
>
> Okay, thanks for clarifying that.  I think what's distracting to
> one could be helpful to another.  For example, it's not uncommon
> for classes with many members to use redundant access specifiers
> to group blocks of related declarations.  Or, in a class with many
> lines of comments (e.g., Doxygen), repeating the access specifier
> every so often could be seen as helpful because otherwise there
> would be no way to tell what its access is without scrolling up
> or down.  It's debatable what approach to dealing with this is
> best.  Java, for instance, requires every non-public member to
> be declared with its own access specifier.  Some other languages
> (I think D) do as well.  An argument could be made that that's
> a far clearer style than using the specifier only when changing
> access.  It seems to me that the most suitable approach will be
> different from one project to another, if not from one person to
> another.  A diagnostic that recommends a particular style (or that
> helps with a specific kind of a project like the one you did for
> Qt) might be a good candidate for a plugin, but enshrining any
> one style (or a solution to a project-specific problem) in GCC
> as a general-purpose warning doesn't seem appropriate or in line
> with the definition of warnings in GCC:
>
>    constructions that are not inherently erroneous but that are
>    risky or suggest there may have been an error

I think there are some circumstances in which the warning would count,
especially if you're working to a coding convention that requires all
public members followed by all protected members followed by all private
members.  Having a duplicated specifier in that context might then
indicate that you've got a cut-&-paste error.

I think both that scenario and the ones Volker gave are enough
justification for the warning to be useful, but not enough for
including it in Wall or Wextra (which isn't being proposed).

> PS There are other redundancies that some might say unnecessarily
> clutter code.  For instance, declaring a symbol static in
> an unnamed namespace, or explicitly declaring a member function
> inline that's also defined within the body of a class, or
> explicitly declaring a function virtual that overrides one
> declared in a base class.  None of these is diagnosed, and I'd
> say for good reason: they are all benign and do not suggest any
> sort of a coding mistake or present an opportunity for improvement.
> In fact, warning for some of them (especially the virtual function)
> might even be detrimental

Actually, I've wanted one for the "static in an anonymous namespace"
in the past :-)

But I think you could also say the same about things like
-Wmissing-declarations.  That warns about something as simple as:

void foo(void) {}

which I think is even harder to justify as being inherently suspicious.
But in the ANSI C days, this was very useful in enforcing GCC's own coding
convention, and so was enabled during bootstrap.

Thanks,
Richard
Martin Sebor July 28, 2017, 4:07 p.m. UTC | #12
On 07/27/2017 01:13 PM, Richard Sandiford wrote:
> Martin Sebor <msebor@gmail.com> writes:
>> On 07/23/2017 02:42 PM, Volker Reichelt wrote:
>>> On 21 Jul, Martin Sebor wrote:
>>>> On 07/20/2017 10:35 AM, Volker Reichelt wrote:
>>>>> Hi,
>>>>>
>>>>> the following patch introduces a new C++ warning option
>>>>> -Wduplicated-access-specifiers that warns about redundant
>>>>> access-specifiers in classes, e.g.
>>>>>
>>>>>   class B
>>>>>   {
>>>>>     public:
>>>>>       B();
>>>>>
>>>>>     private:
>>>>>       void foo();
>>>>>     private:
>>>>>       int i;
>>>>>   };
>>>>
>>>> I'm very fond of warnings that help find real bugs, or even that
>>>> provide an opportunity to review code for potential problems or
>>>> inefficiencies and suggest a possibility to improve it in some
>>>> way (make it clearer, or easier for humans or compilers to find
>>>> real bugs in, or faster, etc.), even if the code isn't strictly
>>>> incorrect.
>>>>
>>>> In this case I'm having trouble coming up with an example where
>>>> the warning would have this effect.  What do you have in mind?
>>>> (Duplicate access specifiers tend to crop up in large classes
>>>> and/or as a result of preprocessor conditionals.)
>>>
>>> This warning fights the "tend to crop up" effect that clutters the
>>> code. After some time these redundant access specifiers creep in
>>> and make your code harder to read. If I see something like
>>>
>>>   ...
>>>     void foo() const;
>>>
>>>   private:
>>>     void bar();
>>>   ...
>>>
>>> on the screen I tend to think that 'foo' has a different access
>>> level than bar. If that's not the case because the access-specifier
>>> is redundant, then that's just confusing and distracting.
>>> The warning helps to maintain readability of your code.
>>>
>>> The benefit might not be big, but the warning comes at relatively
>>> low cost. It passes a location around through the class stack and
>>> checks less than a handful of tokens.
>>>
>>> My personal motivation to implement this warning was the fact that
>>> I changed a big Qt application suite from Qt's legacy SIGNAL-SLOT
>>> mechanism to the new function pointer syntax of Qt5. In the old
>>> version you had to mark slots in the following fashion:
>>>
>>>   public slots:
>>>     void foo();
>>>     void bar();
>>>
>>> But now you can use any function making the 'slots' macro obsolete.
>>> Therefore I ended up with hundreds of redundant access-specifiers
>>> which this warning helped to clean up. Doing this sort of thing in the
>>> compiler with a proper parser is way safer than to write some script
>>> to achieve this.
>>
>> Okay, thanks for clarifying that.  I think what's distracting to
>> one could be helpful to another.  For example, it's not uncommon
>> for classes with many members to use redundant access specifiers
>> to group blocks of related declarations.  Or, in a class with many
>> lines of comments (e.g., Doxygen), repeating the access specifier
>> every so often could be seen as helpful because otherwise there
>> would be no way to tell what its access is without scrolling up
>> or down.  It's debatable what approach to dealing with this is
>> best.  Java, for instance, requires every non-public member to
>> be declared with its own access specifier.  Some other languages
>> (I think D) do as well.  An argument could be made that that's
>> a far clearer style than using the specifier only when changing
>> access.  It seems to me that the most suitable approach will be
>> different from one project to another, if not from one person to
>> another.  A diagnostic that recommends a particular style (or that
>> helps with a specific kind of a project like the one you did for
>> Qt) might be a good candidate for a plugin, but enshrining any
>> one style (or a solution to a project-specific problem) in GCC
>> as a general-purpose warning doesn't seem appropriate or in line
>> with the definition of warnings in GCC:
>>
>>    constructions that are not inherently erroneous but that are
>>    risky or suggest there may have been an error
>
> I think there are some circumstances in which the warning would count,
> especially if you're working to a coding convention that requires all
> public members followed by all protected members followed by all private
> members.  Having a duplicated specifier in that context might then
> indicate that you've got a cut-&-paste error.

Perhaps.  It sounds like a stretch to me.  But my argument isn't
that no styles exist that the warning might work for but rather
that it tries to enforce a unique (and IMO, rare) style to the
exclusion some common and arguably more useful ones.  It's a wide
spread convention to use an access specifier to introduce a block
of related declarations.  For example:

   class X {
     ...
     public:   // public types
     ...
     public:   // public functions
     ...
     private:  // private functions
     ...
     private:  // private data
     ...
   };

Although this is a reasonable convention I think it would be fair
to consider a warning that diagnosed it if it had a good chance
of finding real bugs in some other code.  But this is not the case
here (nor was the purpose of the warning to find such bugs).

> I think both that scenario and the ones Volker gave are enough
> justification for the warning to be useful, but not enough for
> including it in Wall or Wextra (which isn't being proposed).
>
>> PS There are other redundancies that some might say unnecessarily
>> clutter code.  For instance, declaring a symbol static in
>> an unnamed namespace, or explicitly declaring a member function
>> inline that's also defined within the body of a class, or
>> explicitly declaring a function virtual that overrides one
>> declared in a base class.  None of these is diagnosed, and I'd
>> say for good reason: they are all benign and do not suggest any
>> sort of a coding mistake or present an opportunity for improvement.
>> In fact, warning for some of them (especially the virtual function)
>> might even be detrimental
>
> Actually, I've wanted one for the "static in an anonymous namespace"
> in the past :-)

Out of curiosity, why?

But again, I'm not arguing that there is no use case for finding
these patterns.  Just that they are benign and not suggestive of
bugs or opportunities for improvements, and so not appropriate
for inclusion in the form of warnings whose purpose is to point
out likely coding mistakes that could be the source of bugs.

> But I think you could also say the same about things like
> -Wmissing-declarations.  That warns about something as simple as:
>
> void foo(void) {}
>
> which I think is even harder to justify as being inherently suspicious.
> But in the ANSI C days, this was very useful in enforcing GCC's own coding
> convention, and so was enabled during bootstrap.

Defining a function that's incompatible with its declaration in
another translation unit is a common mistake in C (and undefined
behavior).  Using the function leads to hard to debug problems.
Coding standard rules have been written (such as rule DCL40-C of
the CERT Secure Coding Standard) and static analysis tools have
been developed to detect this problem (see the Automated Detection
section in the CERT rule).  Many compilers implement this warning
for this reason.

Martin
diff mbox

Patch

Index: gcc/doc/invoke.texi
===================================================================
--- gcc/doc/invoke.texi	(revision 250356)
+++ gcc/doc/invoke.texi	(working copy)
@@ -275,7 +275,7 @@ 
 -Wdisabled-optimization @gol
 -Wno-discarded-qualifiers  -Wno-discarded-array-qualifiers @gol
 -Wno-div-by-zero  -Wdouble-promotion @gol
--Wduplicated-branches  -Wduplicated-cond @gol
+-Wduplicated-access-specifiers  -Wduplicated-branches  -Wduplicated-cond @gol
 -Wempty-body  -Wenum-compare  -Wno-endif-labels  -Wexpansion-to-defined @gol
 -Werror  -Werror=*  -Wextra-semi  -Wfatal-errors @gol
 -Wfloat-equal  -Wformat  -Wformat=2 @gol
@@ -5388,6 +5388,12 @@ 
 
 This warning is enabled by @option{-Wall}.
 
+@item -Wduplicated-access-specifiers
+@opindex Wno-duplicated-access-specifiers
+@opindex Wduplicated-access-specifiers
+Warn when an access-specifier is redundant because it was already given
+before.
+
 @item -Wduplicated-branches
 @opindex Wno-duplicated-branches
 @opindex Wduplicated-branches
===================================================================


2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>

	* c.opt (Wduplicated-access-specifiers): New C++ warning flag.

Index: gcc/c-family/c.opt
===================================================================
--- gcc/c-family/c.opt	(revision 250356)
+++ gcc/c-family/c.opt	(working copy)
@@ -476,6 +476,10 @@ 
 C ObjC C++ ObjC++ Var(warn_div_by_zero) Init(1) Warning
 Warn about compile-time integer division by zero.
 
+Wduplicated-access-specifiers
+C++ ObjC++ Var(warn_duplicated_access) Warning
+Warn about duplicated access-specifiers.
+
 Wduplicated-branches
 C ObjC C++ ObjC++ Var(warn_duplicated_branches) Init(0) Warning
 Warn about duplicated branches in if-else statements.
===================================================================


2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>

	* cp-tree.h (struct saved_scope): New access_specifier_loc variable.
	(current_access_specifier_loc): New macro.
	* class.c (struct class_stack_node): New access_loc variable.
	(pushclass): Push current_access_specifier_loc.  Set new
	initial	value.
	(popclass): Pop current_access_specifier_loc.
	* parser.c (cp_parser_member_specification_opt): Warn about
	duplicated access-specifier.  Set current_access_specifier_loc.

Index: gcc/cp/cp-tree.h
===================================================================
--- gcc/cp/cp-tree.h	(revision 250356)
+++ gcc/cp/cp-tree.h	(working copy)
@@ -1531,6 +1531,7 @@ 
   tree class_name;
   tree class_type;
   tree access_specifier;
+  source_location access_specifier_loc;
   tree function_decl;
   vec<tree, va_gc> *lang_base;
   tree lang_name;
@@ -1592,6 +1593,7 @@ 
    class, or union).  */
 
 #define current_access_specifier scope_chain->access_specifier
+#define current_access_specifier_loc scope_chain->access_specifier_loc
 
 /* Pointer to the top of the language name stack.  */
 
Index: gcc/cp/class.c
===================================================================
--- gcc/cp/class.c	(revision 250356)
+++ gcc/cp/class.c	(working copy)
@@ -60,6 +60,7 @@ 
   /* The access specifier pending for new declarations in the scope of
      this class.  */
   tree access;
+  location_t access_loc;
 
   /* If were defining TYPE, the names used in this class.  */
   splay_tree names_used;
@@ -7723,6 +7724,7 @@ 
   csn->name = current_class_name;
   csn->type = current_class_type;
   csn->access = current_access_specifier;
+  csn->access_loc = current_access_specifier_loc;
   csn->names_used = 0;
   csn->hidden = 0;
   current_class_depth++;
@@ -7738,6 +7740,7 @@ 
   current_access_specifier = (CLASSTYPE_DECLARED_CLASS (type)
 			      ? access_private_node
 			      : access_public_node);
+  current_access_specifier_loc = UNKNOWN_LOCATION;
 
   if (previous_class_level
       && type != previous_class_level->this_entity
@@ -7777,6 +7780,7 @@ 
   current_class_name = current_class_stack[current_class_depth].name;
   current_class_type = current_class_stack[current_class_depth].type;
   current_access_specifier = current_class_stack[current_class_depth].access;
+  current_access_specifier_loc = current_class_stack[current_class_depth].access_loc;
   if (current_class_stack[current_class_depth].names_used)
     splay_tree_delete (current_class_stack[current_class_depth].names_used);
 }
Index: gcc/cp/parser.c
===================================================================
--- gcc/cp/parser.c	(revision 250356)
+++ gcc/cp/parser.c	(working copy)
@@ -23113,8 +23113,24 @@ 
 	case RID_PRIVATE:
 	  /* Consume the access-specifier.  */
 	  cp_lexer_consume_token (parser->lexer);
+
+	  /* Warn if the same access-specifier has been given already.  */
+	  if (warn_duplicated_access
+	      && current_access_specifier == token->u.value
+	      && current_access_specifier_loc != UNKNOWN_LOCATION)
+	    {
+	      gcc_rich_location richloc (token->location);
+	      richloc.add_fixit_remove ();
+	      warning_at_rich_loc (&richloc, OPT_Wduplicated_access_specifiers,
+				   "duplicate %qE access-specifier",
+				   token->u.value);
+	      inform (current_access_specifier_loc,
+		      "access-specifier was previously given here");
+	    }
+
 	  /* Remember which access-specifier is active.  */
 	  current_access_specifier = token->u.value;
+	  current_access_specifier_loc = token->location;
 	  /* Look for the `:'.  */
 	  cp_parser_require (parser, CPP_COLON, RT_COLON);
 	  break;
===================================================================


2017-07-20  Volker Reichelt  <v.reichelt@netcologne.de>

	* g++.dg/warn/Wduplicated-access-specifiers-1.C: New test.

Index: gcc/testsuite/g++.dg/warn/Wduplicated-access-specifiers-1.C
===================================================================
--- gcc/testsuite/g++.dg/warn/Wduplicated-access-specifiers-1.C	2017-07-20
+++ gcc/testsuite/g++.dg/warn/Wduplicated-access-specifiers-1.C	2017-07-20
@@ -0,0 +1,35 @@ 
+// { dg-options "-Wduplicated-access-specifiers" }
+
+struct A
+{
+    int i;
+  public:       // { dg-message "previously" }
+    int j;
+  public:       // { dg-warning "access-specifier" }
+    int k;
+  protected:    // { dg-message "previously" }
+
+    class B
+    {
+      private:  // { dg-message "previously" }
+      private:  // { dg-warning "access-specifier" }
+      public:
+    };
+
+  protected:    // { dg-warning "access-specifier" }
+    void a();
+  public:
+    void b();
+  private:      // { dg-message "previously" }
+    void c();
+  private:      // { dg-warning "access-specifier" }
+    void d();
+  public:
+    void e();
+  protected:    // { dg-message "previously" }
+    void f();
+  protected:    // { dg-warning "access-specifier" }
+                // { dg-message "previously" "" { target *-*-* } .-1 }
+    void g();
+  protected:    // { dg-warning "access-specifier" }
+};