diff mbox series

c++: Private parent access check for using decls [PR19377]

Message ID CA+Lh_AkNPsou8AwPrFSHNd-=w5bVpo=ssiv_HeOkCd=oZjV8_Q@mail.gmail.com
State New
Headers show
Series c++: Private parent access check for using decls [PR19377] | expand

Commit Message

Anthony Sharp Feb. 4, 2021, 3:02 p.m. UTC
Hello,

New bugfix for PR19377
(https://gcc.gnu.org/bugzilla/show_bug.cgi?id=19377). This is
basically an extension of what I did before for PR17314 except it also
fixes this other bug.

I hope I didn't over-comment in the code ... better to say too much
than too little! It's a niche bug so I thought it could do with a
little explanation.

Added 1 new regression test.

Bootstraps fine on x86_64-pc-linux-gnu for x86_64-pc-linux-gnu.

Hopefully no formatting problems; checked with git gcc-verify and
check_GNU_style.sh.

----------- REGRESSION ANALYSIS -----------

No regressions reported.

I only ran the c++ regression tests since this is a c++ front-end
diagnostics code bug (i.e. it should have no effect on compilation, or
any other languages).

G++ (CLEAN) RESULTS

# of expected passes        203705
# of unexpected failures    2
# of expected failures        989
# of unsupported tests        8714

G++ (PATCHED) RESULTS

# of expected passes        203717
# of unexpected failures    2
# of expected failures        989
# of unsupported tests        8714

The extra passes are from my new regression test.

Let me know if there are any issues.

Kind regards,
Anthony Sharp

Comments

Jason Merrill Feb. 4, 2021, 4:24 p.m. UTC | #1
On 2/4/21 10:02 AM, Anthony Sharp via Gcc-patches wrote:
> Hello,
> 
> New bugfix for PR19377
> (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=19377). This is
> basically an extension of what I did before for PR17314 except it also
> fixes this other bug.
> 
> I hope I didn't over-comment in the code ... better to say too much
> than too little! It's a niche bug so I thought it could do with a
> little explanation.

Yes, thanks; it would take a lot to make me request less comments.

> +      if (TREE_CODE (parent_field) == USING_DECL)
> +       {
> +         if (cp_tree_equal (decl,
> +                            lookup_member (parent_binfo,
> +                                           DECL_NAME (parent_field),
> +                                           /*protect=*/0,
> +                                           /*want_type=*/false,
> +                                           tf_warning_or_error)))

Isn't it sufficient to check that the names match?

>           tree parent_binfo = get_parent_with_private_access (decl,
> -                                                             basetype_path);
> +                                                              basetype_path);
...
> +             diag_location = get_class_access_diagnostic_decl (parent_binfo,
> +                                                                diag_decl);

The second lines of arguments are indented one space too far in both 
these calls.

Jason
Jason Merrill Feb. 4, 2021, 4:33 p.m. UTC | #2
On 2/4/21 11:24 AM, Jason Merrill wrote:
> On 2/4/21 10:02 AM, Anthony Sharp via Gcc-patches wrote:
>> Hello,
>>
>> New bugfix for PR19377
>> (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=19377). This is
>> basically an extension of what I did before for PR17314 except it also
>> fixes this other bug.
>>
>> I hope I didn't over-comment in the code ... better to say too much
>> than too little! It's a niche bug so I thought it could do with a
>> little explanation.
> 
> Yes, thanks; it would take a lot to make me request less comments.
> 
>> +      if (TREE_CODE (parent_field) == USING_DECL)
>> +       {
>> +         if (cp_tree_equal (decl,
>> +                            lookup_member (parent_binfo,
>> +                                           DECL_NAME (parent_field),
>> +                                           /*protect=*/0,
>> +                                           /*want_type=*/false,
>> +                                           tf_warning_or_error)))
> 
> Isn't it sufficient to check that the names match?

Well, I guess it could be using a declaration of the same name from 
another base.  But in that case you could end up with an overload set 
containing both the decl we're complaining about and another of the same 
name from another base, in which case the lookup result would include 
both, and so the comparison would fail and we would fall through to the 
private base assumption.

But checking the name is a simple way to skip irrelevant usings.
Maybe also check that the using is TREE_PRIVATE?

>>           tree parent_binfo = get_parent_with_private_access (decl,
>> -                                                             
>> basetype_path);
>> +                                                              
>> basetype_path);
> ...
>> +             diag_location = get_class_access_diagnostic_decl 
>> (parent_binfo,
>> +                                                                
>> diag_decl);
> 
> The second lines of arguments are indented one space too far in both 
> these calls.
> 
> Jason
Anthony Sharp Feb. 4, 2021, 5:46 p.m. UTC | #3
> Yes, thanks; it would take a lot to make me request less comments.

Awesome.

> The second lines of arguments are indented one space too far in both these calls.

Oops! I will fix that.

> Well, I guess it could be using a declaration of the same name from another base.

 Yes I had been worrying about that.

> But in that case you could end up with an overload set
> containing both the decl we're complaining about and another of the same
> name from another base, in which case the lookup result would include
> both, and so the comparison would fail and we would fall through to the
> private base assumption.

I think technically yes ... but also no since such code would not
compile anyways, plus oddly it seems to work anyway. For instance
(assuming I'm understanding correctly), if you do this (with the patch
applied):

class A
{
  protected:
  int i;
};

class A2
{
  protected:
  int i;
};

class B:public A, public A2
{
  private:
  using A::i, A2::i;
};

class C:public B
{
  void f()
  {
    A::i = 0;
  }
};

You get:

error: redeclaration of ‘using A::i’
   using A::i;

note: previous declaration ‘using A2::i’
    using A2::i;

error: redeclaration of ‘using A2::i’
   using A::i, A2::i;

previous declaration ‘using A::i’
   using A::i, A2::i;

In member function ‘void C::f()’:
error: ‘int A::i’ is private within this context
   A::i = 0;

note: declared private here
   using A::i, A2::i;

Which seems to work (well ... more like fail to compile ... as
expected). Maybe you're imagining a different situation to me?

You can even use void f() { i = 0; } and void f() { A2::i = 0; } and
you seem to get the same results either which way (although, to be
fair, if you do A2::i = 0, it suddenly doesn't complain about it being
private anymore [no idea why], it just complains about the
redeclaration , and once you fix the redeclaration, it THEN complains
about being private, so it's got a bit of a quirk - don't think that's
related to the patch though).

> But checking the name is a simple way to skip irrelevant usings.

That does sound like a better way of doing it. Would I just do
cp_tree_equal (DECL_NAME (blah1), DECL_NAME (blah2)) [assuming
DECL_NAME returns a tree], or perhaps DECL_NAME (blah1) == DECL_NAME
(blah2)?

> Maybe also check that the using is TREE_PRIVATE?

Would that be necessary? Maybe if you wanted to sanity-check it I
suppose. We know for sure that PARENT_BINFO has private access to
DECL. If we find a using statement introducing DECL in PARENT_BINFO,
then surely the using statement must (by definition) have been
private? If it were not private, then the child class would have been
able to access it, and enforce_access wouldn't have thrown an error.
It doesn't seem to be the case that DECL could be private for any
other reason other than the using decl being private.

Let me know your thoughts and I will update the patch. Thanks for your help.

Anthony


On Thu, 4 Feb 2021 at 16:33, Jason Merrill <jason@redhat.com> wrote:
>
> On 2/4/21 11:24 AM, Jason Merrill wrote:
> > On 2/4/21 10:02 AM, Anthony Sharp via Gcc-patches wrote:
> >> Hello,
> >>
> >> New bugfix for PR19377
> >> (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=19377). This is
> >> basically an extension of what I did before for PR17314 except it also
> >> fixes this other bug.
> >>
> >> I hope I didn't over-comment in the code ... better to say too much
> >> than too little! It's a niche bug so I thought it could do with a
> >> little explanation.
> >
> > Yes, thanks; it would take a lot to make me request less comments.
> >
> >> +      if (TREE_CODE (parent_field) == USING_DECL)
> >> +       {
> >> +         if (cp_tree_equal (decl,
> >> +                            lookup_member (parent_binfo,
> >> +                                           DECL_NAME (parent_field),
> >> +                                           /*protect=*/0,
> >> +                                           /*want_type=*/false,
> >> +                                           tf_warning_or_error)))
> >
> > Isn't it sufficient to check that the names match?
>
> Well, I guess it could be using a declaration of the same name from
> another base.  But in that case you could end up with an overload set
> containing both the decl we're complaining about and another of the same
> name from another base, in which case the lookup result would include
> both, and so the comparison would fail and we would fall through to the
> private base assumption.
>
> But checking the name is a simple way to skip irrelevant usings.
> Maybe also check that the using is TREE_PRIVATE?
>
> >>           tree parent_binfo = get_parent_with_private_access (decl,
> >> -
> >> basetype_path);
> >> +
> >> basetype_path);
> > ...
> >> +             diag_location = get_class_access_diagnostic_decl
> >> (parent_binfo,
> >> +
> >> diag_decl);
> >
> > The second lines of arguments are indented one space too far in both
> > these calls.
> >
> > Jason
>
Jason Merrill Feb. 4, 2021, 8:54 p.m. UTC | #4
On 2/4/21 12:46 PM, Anthony Sharp wrote:
>> Yes, thanks; it would take a lot to make me request less comments.
> 
> Awesome.
> 
>> The second lines of arguments are indented one space too far in both these calls.
> 
> Oops! I will fix that.
> 
>> Well, I guess it could be using a declaration of the same name from another base.
> 
>   Yes I had been worrying about that.
> 
>> But in that case you could end up with an overload set
>> containing both the decl we're complaining about and another of the same
>> name from another base, in which case the lookup result would include
>> both, and so the comparison would fail and we would fall through to the
>> private base assumption.
> 
> I think technically yes ... but also no since such code would not
> compile anyways, plus oddly it seems to work anyway. For instance
> (assuming I'm understanding correctly), if you do this (with the patch
> applied):
> 
> class A
> {
>    protected:
>    int i;
> };
> 
> class A2
> {
>    protected:
>    int i;
> };
> 
> class B:public A, public A2
> {
>    private:
>    using A::i, A2::i;
> };
> 
> class C:public B
> {
>    void f()
>    {
>      A::i = 0;
>    }
> };
> 
> You get:
> 
> error: redeclaration of ‘using A::i’
>     using A::i;
> 
> note: previous declaration ‘using A2::i’
>      using A2::i;
> 
> error: redeclaration of ‘using A2::i’
>     using A::i, A2::i;
> 
> previous declaration ‘using A::i’
>     using A::i, A2::i;
> 
> In member function ‘void C::f()’:
> error: ‘int A::i’ is private within this context
>     A::i = 0;
> 
> note: declared private here
>     using A::i, A2::i;
> 
> Which seems to work (well ... more like fail to compile ... as
> expected). Maybe you're imagining a different situation to me?

I'm imagining member functions, i.e. A::f() and A2::f(int).

> You can even use void f() { i = 0; } and void f() { A2::i = 0; } and
> you seem to get the same results either which way (although, to be
> fair, if you do A2::i = 0, it suddenly doesn't complain about it being
> private anymore [no idea why], it just complains about the
> redeclaration , and once you fix the redeclaration, it THEN complains
> about being private, so it's got a bit of a quirk - don't think that's
> related to the patch though).

That sounds fine, one error can hide another.

>> But checking the name is a simple way to skip irrelevant usings.
> 
> That does sound like a better way of doing it. Would I just do
> cp_tree_equal (DECL_NAME (blah1), DECL_NAME (blah2)) [assuming
> DECL_NAME returns a tree], or perhaps DECL_NAME (blah1) == DECL_NAME
> (blah2)?

== is enough, identifiers are unique.

>> Maybe also check that the using is TREE_PRIVATE?
> 
> Would that be necessary? Maybe if you wanted to sanity-check it I
> suppose. We know for sure that PARENT_BINFO has private access to
> DECL. If we find a using statement introducing DECL in PARENT_BINFO,
> then surely the using statement must (by definition) have been
> private? If it were not private, then the child class would have been
> able to access it, and enforce_access wouldn't have thrown an error.
> It doesn't seem to be the case that DECL could be private for any
> other reason other than the using decl being private.

Agreed, but the using-declaration might not be introducing DECL.  This 
would be another way to skip irrelevant usings.

> Let me know your thoughts and I will update the patch. Thanks for your help.
> 
> Anthony
> 
> 
> On Thu, 4 Feb 2021 at 16:33, Jason Merrill <jason@redhat.com> wrote:
>>
>> On 2/4/21 11:24 AM, Jason Merrill wrote:
>>> On 2/4/21 10:02 AM, Anthony Sharp via Gcc-patches wrote:
>>>> Hello,
>>>>
>>>> New bugfix for PR19377
>>>> (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=19377). This is
>>>> basically an extension of what I did before for PR17314 except it also
>>>> fixes this other bug.
>>>>
>>>> I hope I didn't over-comment in the code ... better to say too much
>>>> than too little! It's a niche bug so I thought it could do with a
>>>> little explanation.
>>>
>>> Yes, thanks; it would take a lot to make me request less comments.
>>>
>>>> +      if (TREE_CODE (parent_field) == USING_DECL)
>>>> +       {
>>>> +         if (cp_tree_equal (decl,
>>>> +                            lookup_member (parent_binfo,
>>>> +                                           DECL_NAME (parent_field),
>>>> +                                           /*protect=*/0,
>>>> +                                           /*want_type=*/false,
>>>> +                                           tf_warning_or_error)))
>>>
>>> Isn't it sufficient to check that the names match?
>>
>> Well, I guess it could be using a declaration of the same name from
>> another base.  But in that case you could end up with an overload set
>> containing both the decl we're complaining about and another of the same
>> name from another base, in which case the lookup result would include
>> both, and so the comparison would fail and we would fall through to the
>> private base assumption.
>>
>> But checking the name is a simple way to skip irrelevant usings.
>> Maybe also check that the using is TREE_PRIVATE?
>>
>>>>            tree parent_binfo = get_parent_with_private_access (decl,
>>>> -
>>>> basetype_path);
>>>> +
>>>> basetype_path);
>>> ...
>>>> +             diag_location = get_class_access_diagnostic_decl
>>>> (parent_binfo,
>>>> +
>>>> diag_decl);
>>>
>>> The second lines of arguments are indented one space too far in both
>>> these calls.
>>>
>>> Jason
>>
>
Anthony Sharp Feb. 5, 2021, 3:39 p.m. UTC | #5
> I'm imagining member functions, i.e. A::f() and A2::f(int).

You're right - good spot.

> == is enough, identifiers are unique.

 Done.

> Agreed, but the using-declaration might not be introducing DECL.  This
> would be another way to skip irrelevant usings.

Okie dokie.

New patch attached (that rhymes?). C++ (compiled only) compiles fine
and fixes the bug.

Also modified the new regression test to use your overloaded function
testcase. I made the testcase c++17 only ... I could also add a
pre-c++17 testcase but not sure if that would really be beneficial?
Anyways, regression tests (C++ only) now give:

-- G++ --

# of expected passes        203708
# of unexpected failures    2
# of expected failures        989
# of unsupported tests        8714

Anthony


On Thu, 4 Feb 2021 at 20:55, Jason Merrill <jason@redhat.com> wrote:
>
> On 2/4/21 12:46 PM, Anthony Sharp wrote:
> >> Yes, thanks; it would take a lot to make me request less comments.
> >
> > Awesome.
> >
> >> The second lines of arguments are indented one space too far in both these calls.
> >
> > Oops! I will fix that.
> >
> >> Well, I guess it could be using a declaration of the same name from another base.
> >
> >   Yes I had been worrying about that.
> >
> >> But in that case you could end up with an overload set
> >> containing both the decl we're complaining about and another of the same
> >> name from another base, in which case the lookup result would include
> >> both, and so the comparison would fail and we would fall through to the
> >> private base assumption.
> >
> > I think technically yes ... but also no since such code would not
> > compile anyways, plus oddly it seems to work anyway. For instance
> > (assuming I'm understanding correctly), if you do this (with the patch
> > applied):
> >
> > class A
> > {
> >    protected:
> >    int i;
> > };
> >
> > class A2
> > {
> >    protected:
> >    int i;
> > };
> >
> > class B:public A, public A2
> > {
> >    private:
> >    using A::i, A2::i;
> > };
> >
> > class C:public B
> > {
> >    void f()
> >    {
> >      A::i = 0;
> >    }
> > };
> >
> > You get:
> >
> > error: redeclaration of ‘using A::i’
> >     using A::i;
> >
> > note: previous declaration ‘using A2::i’
> >      using A2::i;
> >
> > error: redeclaration of ‘using A2::i’
> >     using A::i, A2::i;
> >
> > previous declaration ‘using A::i’
> >     using A::i, A2::i;
> >
> > In member function ‘void C::f()’:
> > error: ‘int A::i’ is private within this context
> >     A::i = 0;
> >
> > note: declared private here
> >     using A::i, A2::i;
> >
> > Which seems to work (well ... more like fail to compile ... as
> > expected). Maybe you're imagining a different situation to me?
>
> I'm imagining member functions, i.e. A::f() and A2::f(int).
>
> > You can even use void f() { i = 0; } and void f() { A2::i = 0; } and
> > you seem to get the same results either which way (although, to be
> > fair, if you do A2::i = 0, it suddenly doesn't complain about it being
> > private anymore [no idea why], it just complains about the
> > redeclaration , and once you fix the redeclaration, it THEN complains
> > about being private, so it's got a bit of a quirk - don't think that's
> > related to the patch though).
>
> That sounds fine, one error can hide another.
>
> >> But checking the name is a simple way to skip irrelevant usings.
> >
> > That does sound like a better way of doing it. Would I just do
> > cp_tree_equal (DECL_NAME (blah1), DECL_NAME (blah2)) [assuming
> > DECL_NAME returns a tree], or perhaps DECL_NAME (blah1) == DECL_NAME
> > (blah2)?
>
> == is enough, identifiers are unique.
>
> >> Maybe also check that the using is TREE_PRIVATE?
> >
> > Would that be necessary? Maybe if you wanted to sanity-check it I
> > suppose. We know for sure that PARENT_BINFO has private access to
> > DECL. If we find a using statement introducing DECL in PARENT_BINFO,
> > then surely the using statement must (by definition) have been
> > private? If it were not private, then the child class would have been
> > able to access it, and enforce_access wouldn't have thrown an error.
> > It doesn't seem to be the case that DECL could be private for any
> > other reason other than the using decl being private.
>
> Agreed, but the using-declaration might not be introducing DECL.  This
> would be another way to skip irrelevant usings.
>
> > Let me know your thoughts and I will update the patch. Thanks for your help.
> >
> > Anthony
> >
> >
> > On Thu, 4 Feb 2021 at 16:33, Jason Merrill <jason@redhat.com> wrote:
> >>
> >> On 2/4/21 11:24 AM, Jason Merrill wrote:
> >>> On 2/4/21 10:02 AM, Anthony Sharp via Gcc-patches wrote:
> >>>> Hello,
> >>>>
> >>>> New bugfix for PR19377
> >>>> (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=19377). This is
> >>>> basically an extension of what I did before for PR17314 except it also
> >>>> fixes this other bug.
> >>>>
> >>>> I hope I didn't over-comment in the code ... better to say too much
> >>>> than too little! It's a niche bug so I thought it could do with a
> >>>> little explanation.
> >>>
> >>> Yes, thanks; it would take a lot to make me request less comments.
> >>>
> >>>> +      if (TREE_CODE (parent_field) == USING_DECL)
> >>>> +       {
> >>>> +         if (cp_tree_equal (decl,
> >>>> +                            lookup_member (parent_binfo,
> >>>> +                                           DECL_NAME (parent_field),
> >>>> +                                           /*protect=*/0,
> >>>> +                                           /*want_type=*/false,
> >>>> +                                           tf_warning_or_error)))
> >>>
> >>> Isn't it sufficient to check that the names match?
> >>
> >> Well, I guess it could be using a declaration of the same name from
> >> another base.  But in that case you could end up with an overload set
> >> containing both the decl we're complaining about and another of the same
> >> name from another base, in which case the lookup result would include
> >> both, and so the comparison would fail and we would fall through to the
> >> private base assumption.
> >>
> >> But checking the name is a simple way to skip irrelevant usings.
> >> Maybe also check that the using is TREE_PRIVATE?
> >>
> >>>>            tree parent_binfo = get_parent_with_private_access (decl,
> >>>> -
> >>>> basetype_path);
> >>>> +
> >>>> basetype_path);
> >>> ...
> >>>> +             diag_location = get_class_access_diagnostic_decl
> >>>> (parent_binfo,
> >>>> +
> >>>> diag_decl);
> >>>
> >>> The second lines of arguments are indented one space too far in both
> >>> these calls.
> >>>
> >>> Jason
> >>
> >
>
Anthony Sharp Feb. 5, 2021, 3:45 p.m. UTC | #6
Sorry - last one had a formatting error. This one might be better.
Linux text editor doesn't consistently show whitespace for me!

On Fri, 5 Feb 2021 at 15:39, Anthony Sharp <anthonysharp15@gmail.com> wrote:
>
> > I'm imagining member functions, i.e. A::f() and A2::f(int).
>
> You're right - good spot.
>
> > == is enough, identifiers are unique.
>
>  Done.
>
> > Agreed, but the using-declaration might not be introducing DECL.  This
> > would be another way to skip irrelevant usings.
>
> Okie dokie.
>
> New patch attached (that rhymes?). C++ (compiled only) compiles fine
> and fixes the bug.
>
> Also modified the new regression test to use your overloaded function
> testcase. I made the testcase c++17 only ... I could also add a
> pre-c++17 testcase but not sure if that would really be beneficial?
> Anyways, regression tests (C++ only) now give:
>
> -- G++ --
>
> # of expected passes        203708
> # of unexpected failures    2
> # of expected failures        989
> # of unsupported tests        8714
>
> Anthony
>
>
> On Thu, 4 Feb 2021 at 20:55, Jason Merrill <jason@redhat.com> wrote:
> >
> > On 2/4/21 12:46 PM, Anthony Sharp wrote:
> > >> Yes, thanks; it would take a lot to make me request less comments.
> > >
> > > Awesome.
> > >
> > >> The second lines of arguments are indented one space too far in both these calls.
> > >
> > > Oops! I will fix that.
> > >
> > >> Well, I guess it could be using a declaration of the same name from another base.
> > >
> > >   Yes I had been worrying about that.
> > >
> > >> But in that case you could end up with an overload set
> > >> containing both the decl we're complaining about and another of the same
> > >> name from another base, in which case the lookup result would include
> > >> both, and so the comparison would fail and we would fall through to the
> > >> private base assumption.
> > >
> > > I think technically yes ... but also no since such code would not
> > > compile anyways, plus oddly it seems to work anyway. For instance
> > > (assuming I'm understanding correctly), if you do this (with the patch
> > > applied):
> > >
> > > class A
> > > {
> > >    protected:
> > >    int i;
> > > };
> > >
> > > class A2
> > > {
> > >    protected:
> > >    int i;
> > > };
> > >
> > > class B:public A, public A2
> > > {
> > >    private:
> > >    using A::i, A2::i;
> > > };
> > >
> > > class C:public B
> > > {
> > >    void f()
> > >    {
> > >      A::i = 0;
> > >    }
> > > };
> > >
> > > You get:
> > >
> > > error: redeclaration of ‘using A::i’
> > >     using A::i;
> > >
> > > note: previous declaration ‘using A2::i’
> > >      using A2::i;
> > >
> > > error: redeclaration of ‘using A2::i’
> > >     using A::i, A2::i;
> > >
> > > previous declaration ‘using A::i’
> > >     using A::i, A2::i;
> > >
> > > In member function ‘void C::f()’:
> > > error: ‘int A::i’ is private within this context
> > >     A::i = 0;
> > >
> > > note: declared private here
> > >     using A::i, A2::i;
> > >
> > > Which seems to work (well ... more like fail to compile ... as
> > > expected). Maybe you're imagining a different situation to me?
> >
> > I'm imagining member functions, i.e. A::f() and A2::f(int).
> >
> > > You can even use void f() { i = 0; } and void f() { A2::i = 0; } and
> > > you seem to get the same results either which way (although, to be
> > > fair, if you do A2::i = 0, it suddenly doesn't complain about it being
> > > private anymore [no idea why], it just complains about the
> > > redeclaration , and once you fix the redeclaration, it THEN complains
> > > about being private, so it's got a bit of a quirk - don't think that's
> > > related to the patch though).
> >
> > That sounds fine, one error can hide another.
> >
> > >> But checking the name is a simple way to skip irrelevant usings.
> > >
> > > That does sound like a better way of doing it. Would I just do
> > > cp_tree_equal (DECL_NAME (blah1), DECL_NAME (blah2)) [assuming
> > > DECL_NAME returns a tree], or perhaps DECL_NAME (blah1) == DECL_NAME
> > > (blah2)?
> >
> > == is enough, identifiers are unique.
> >
> > >> Maybe also check that the using is TREE_PRIVATE?
> > >
> > > Would that be necessary? Maybe if you wanted to sanity-check it I
> > > suppose. We know for sure that PARENT_BINFO has private access to
> > > DECL. If we find a using statement introducing DECL in PARENT_BINFO,
> > > then surely the using statement must (by definition) have been
> > > private? If it were not private, then the child class would have been
> > > able to access it, and enforce_access wouldn't have thrown an error.
> > > It doesn't seem to be the case that DECL could be private for any
> > > other reason other than the using decl being private.
> >
> > Agreed, but the using-declaration might not be introducing DECL.  This
> > would be another way to skip irrelevant usings.
> >
> > > Let me know your thoughts and I will update the patch. Thanks for your help.
> > >
> > > Anthony
> > >
> > >
> > > On Thu, 4 Feb 2021 at 16:33, Jason Merrill <jason@redhat.com> wrote:
> > >>
> > >> On 2/4/21 11:24 AM, Jason Merrill wrote:
> > >>> On 2/4/21 10:02 AM, Anthony Sharp via Gcc-patches wrote:
> > >>>> Hello,
> > >>>>
> > >>>> New bugfix for PR19377
> > >>>> (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=19377). This is
> > >>>> basically an extension of what I did before for PR17314 except it also
> > >>>> fixes this other bug.
> > >>>>
> > >>>> I hope I didn't over-comment in the code ... better to say too much
> > >>>> than too little! It's a niche bug so I thought it could do with a
> > >>>> little explanation.
> > >>>
> > >>> Yes, thanks; it would take a lot to make me request less comments.
> > >>>
> > >>>> +      if (TREE_CODE (parent_field) == USING_DECL)
> > >>>> +       {
> > >>>> +         if (cp_tree_equal (decl,
> > >>>> +                            lookup_member (parent_binfo,
> > >>>> +                                           DECL_NAME (parent_field),
> > >>>> +                                           /*protect=*/0,
> > >>>> +                                           /*want_type=*/false,
> > >>>> +                                           tf_warning_or_error)))
> > >>>
> > >>> Isn't it sufficient to check that the names match?
> > >>
> > >> Well, I guess it could be using a declaration of the same name from
> > >> another base.  But in that case you could end up with an overload set
> > >> containing both the decl we're complaining about and another of the same
> > >> name from another base, in which case the lookup result would include
> > >> both, and so the comparison would fail and we would fall through to the
> > >> private base assumption.
> > >>
> > >> But checking the name is a simple way to skip irrelevant usings.
> > >> Maybe also check that the using is TREE_PRIVATE?
> > >>
> > >>>>            tree parent_binfo = get_parent_with_private_access (decl,
> > >>>> -
> > >>>> basetype_path);
> > >>>> +
> > >>>> basetype_path);
> > >>> ...
> > >>>> +             diag_location = get_class_access_diagnostic_decl
> > >>>> (parent_binfo,
> > >>>> +
> > >>>> diag_decl);
> > >>>
> > >>> The second lines of arguments are indented one space too far in both
> > >>> these calls.
> > >>>
> > >>> Jason
> > >>
> > >
> >
Marek Polacek Feb. 5, 2021, 4:06 p.m. UTC | #7
Hi,

a few comments:

On Fri, Feb 05, 2021 at 03:39:25PM +0000, Anthony Sharp via Gcc-patches wrote:
> 2021-02-05  Anthony Sharp  <anthonysharp15@gmail.com>
> 
> 	* semantics.c (get_class_access_diagnostic_decl): New function.
> 	(enforce_access): Altered function.

Pedantically, "Altered function." is not very good, it should say what
in enforce_access changed.

> gcc/testsuite/ChangeLog:
> 
> 2021-02-05  Anthony Sharp  <anthonysharp15@gmail.com>
> 
> 	* g++.dg/pr19377.C: New test.

Let's move the test into g++.dg/cpp1z and call it using9.C.

>  gcc/cp/semantics.c             | 89 ++++++++++++++++++++++++++--------
>  gcc/testsuite/g++.dg/pr19377.C | 28 +++++++++++
>  2 files changed, 98 insertions(+), 19 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/pr19377.C
> 
> diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
> index 73834467fca..ffb627f08ea 100644
> --- a/gcc/cp/semantics.c
> +++ b/gcc/cp/semantics.c
> @@ -256,6 +256,58 @@ pop_to_parent_deferring_access_checks (void)
>      }
>  }
>  
> +/* Called from enforce_access.  A class has attempted (but failed) to access
> +   DECL.  It is already established that a baseclass of that class,
> +   PARENT_BINFO, has private access to DECL.  Examine certain special cases to
> +   generate a diagnostic decl location.  If no special cases are found, simply

I don't understand the "generate a diagnostic decl location".  Maybe just
"generate a diagnostic?"

> +   return DECL.  */
> +
> +static tree
> +get_class_access_diagnostic_decl (tree parent_binfo, tree decl)
> +{
> +  /* When a class is denied access to a decl in a baseclass, most of the
> +     time it is because the decl itself was declared as private at the point
> +     of declaration.  So, by default, DECL is at fault.
> +
> +     However, in C++, there are (at least) two situations in which a decl
> +     can be private even though it was not originally defined as such.  */
> +
> +  /* These two situations only apply if a baseclass had private access to
> +     DECL (this function is only called if that is the case).  We must however
> +     also ensure that the reason the parent had private access wasn't simply
> +     because it was declared as private in the parent.  */

These two comments can be merged into one.

> +  if (context_for_name_lookup (decl) == BINFO_TYPE (parent_binfo))
> +    return decl;

I think for comparing a binfo with a type we should use SAME_BINFO_TYPE_P.

> +  /* 1.  If the "using" keyword is used to inherit DECL within a baseclass,
> +     this may cause DECL to be private, so we return the using statement as
> +     the source of the problem.
> +
> +     Scan the fields of PARENT_BINFO and see if there are any using decls.  If
> +     there are, see if they inherit DECL.  If they do, that's where DECL was
> +     truly declared private.  */
> +  for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (parent_binfo));
> +      parent_field;
> +      parent_field = DECL_CHAIN (parent_field))
> +    {
> +      if ((TREE_CODE (parent_field) == USING_DECL)

This first term doesn't need to be wrapped in ().

> +	 && TREE_PRIVATE (parent_field))
> +	{
> +	  if (DECL_NAME (decl) == DECL_NAME (parent_field))

This could be part of the if above.  And then we can drop the braces (in the
if and for both).

> +	    return parent_field;
> +	}
> +    }
> +
> +  /* 2.  If decl was privately inherited by a baseclass of the current class,
> +     then decl will be inaccessible, even though it may originally have
> +     been accessible to deriving classes.  In that case, the fault lies with
> +     the baseclass that used a private inheritance, so we return the
> +     baseclass type as the source of the problem.
> +
> +     Since this is the last check, we just assume it's true.  */
> +  return TYPE_NAME (BINFO_TYPE (parent_binfo));
> +}
> +
>  /* If the current scope isn't allowed to access DECL along
>     BASETYPE_PATH, give an error, or if we're parsing a function or class
>     template, defer the access check to be performed at instantiation time.
> @@ -317,34 +369,33 @@ enforce_access (tree basetype_path, tree decl, tree diag_decl,
>  	diag_decl = strip_inheriting_ctors (diag_decl);
>        if (complain & tf_error)
>  	{
> -	  /* We will usually want to point to the same place as
> -	     diag_decl but not always.  */
> +	  access_kind access_failure_reason = ak_none;
> +
> +	  /* By default, using the decl as the source of the problem will
> +	     usually give correct results.  */
>  	  tree diag_location = diag_decl;
> -	  access_kind parent_access = ak_none;
>  
> -	  /* See if any of BASETYPE_PATH's parents had private access
> -	     to DECL.  If they did, that will tell us why we don't.  */
> +	  /* However, if a parent of BASETYPE_PATH had private access to decl,
> +	     then it actually might be the case that the source of the problem
> +	     is not DECL.  */
>  	  tree parent_binfo = get_parent_with_private_access (decl,
> -							      basetype_path);
> +							       basetype_path);
>  
> -	  /* If a parent had private access, then the diagnostic
> -	     location DECL should be that of the parent class, since it
> -	     failed to give suitable access by using a private
> -	     inheritance.  But if DECL was actually defined in the parent,
> -	     it wasn't privately inherited, and so we don't need to do
> -	     this, and complain_about_access will figure out what to
> -	     do.  */
> -	  if (parent_binfo != NULL_TREE
> -	      && (context_for_name_lookup (decl)
> -		  != BINFO_TYPE (parent_binfo)))
> +	  /* So if a parent did had private access, then we need to do special

Just "had" instead of "did had"?

> +	     checks to obtain the best diagnostic location decl.  */
> +	  if (parent_binfo != NULL_TREE)
>  	    {
> -	      diag_location = TYPE_NAME (BINFO_TYPE (parent_binfo));
> -	      parent_access = ak_private;
> +	      diag_location = get_class_access_diagnostic_decl (parent_binfo,
> +							       diag_decl);
> +
> +	      /* We also at this point know that the reason access failed was
> +		 because decl was private.  */
> +		 access_failure_reason = ak_private;

Looks like this line is indented too much (even in the newer patch).

>  
>  	  /* Finally, generate an error message.  */
>  	  complain_about_access (decl, diag_decl, diag_location, true,
> -				 parent_access);
> +				access_failure_reason);
>  	}
>        if (afi)
>  	afi->record_access_failure (basetype_path, decl, diag_decl);
> diff --git a/gcc/testsuite/g++.dg/pr19377.C b/gcc/testsuite/g++.dg/pr19377.C
> new file mode 100644
> index 00000000000..fb023a33f82
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/pr19377.C
> @@ -0,0 +1,28 @@
> +/* { dg-do assemble } */

We usually use dg-do compile.

> +// { dg-options "-std=c++17" }

Best to replace both with

// { dg-do compile { target c++17 } }

> +class A
> +{
> +  protected:
> +  int i();
> +};
> +
> +class A2
> +{
> +  protected:
> +  int i(int a);
> +};
> +
> +class B:private A, private A2
> +{
> +  // Comma separated list only officially supported in c++17 and later.
> +  using A::i, A2::i; // { dg-message "declared" }
> +};
> +
> +class C:public B
> +{
> +  void f()
> +  {
> +    i(); // { dg-error "private" }
> +  }
> +};
> \ No newline at end of file
> -- 
> 2.25.1
> 

Marek
Anthony Sharp Feb. 5, 2021, 5:28 p.m. UTC | #8
Hi Marek,

> Pedantically, "Altered function." is not very good, it should say what
> in enforce_access changed.

I would normally 100% agree, but the changes are a bit too complicated
to be concisely (and helpfully) described there. I think the patch
description covers it well enough; not sure what I would say without
having to write a big paragraph there.

> Let's move the test into g++.dg/cpp1z and call it using9.C.

Okie dokie - it's a bit hard to know where stuff's supposed to go in
that folder. I'll put a comment in the testcase mentioning pr19377
just in case there's ever a regression.

> I don't understand the "generate a diagnostic decl location".  Maybe just
> "generate a diagnostic?"

get_class_access_diagnostic_decl returns a decl which is used as the
location that the error-producing code points to as the source of the
problem. It could be better - I will change it to say "Examine certain
special cases to find the decl that is the source of the problem" to
make it a bit clearer.

> These two comments can be merged into one.

Technically yes ... but I like how it is since in a very subtle way it
creates a sense of separation between the first two paragraphs and the
third. The first two paras are sort of an introduction and the third
begins to describe the code.

> I think for comparing a binfo with a type we should use SAME_BINFO_TYPE_P.

Okay, I think that simplifies the code a bit aswell.

> This first term doesn't need to be wrapped in ().

I normally wouldn't use the (), but I think that's how Jason likes it
so that's why I did it. I guess it makes it more readable.

> This could be part of the if above.

Oops - I forgot to change that when I modified the code.

> Just "had" instead of "did had"?

Good spot - that's a spelling mistake on my part. Should be "did have".

> Looks like this line is indented too much (even in the newer patch).

You're right (if you meant access_failure_reason = ak_private), I will change.

If you mean get_class_access_diagnostic_decl (parent_binfo, diag_decl)
then I donno, because Linux Text Editor says both are on column 64.

To be honest, I'm sure there is a way to do it, but I'm not really
sure how to consistently align code. Every text/code editor/browser
seems to give different column numbers (and display it differently)
since they seem to all treat whitespace differently.

> We usually use dg-do compile.

True, but wouldn't that technically be slower? I would agree if it
were more than just error-handling code.

> Best to replace both with
> // { dg-do compile { target c++17 } }

Okie dokie. I did see both being used and I wasn't sure which to go for.

I'll probably send another patch over tomorrow.

Anthony


On Fri, 5 Feb 2021 at 16:06, Marek Polacek <polacek@redhat.com> wrote:
>
> Hi,
>
> a few comments:
>
> On Fri, Feb 05, 2021 at 03:39:25PM +0000, Anthony Sharp via Gcc-patches wrote:
> > 2021-02-05  Anthony Sharp  <anthonysharp15@gmail.com>
> >
> >       * semantics.c (get_class_access_diagnostic_decl): New function.
> >       (enforce_access): Altered function.
>
> Pedantically, "Altered function." is not very good, it should say what
> in enforce_access changed.
>
> > gcc/testsuite/ChangeLog:
> >
> > 2021-02-05  Anthony Sharp  <anthonysharp15@gmail.com>
> >
> >       * g++.dg/pr19377.C: New test.
>
> Let's move the test into g++.dg/cpp1z and call it using9.C.
>
> >  gcc/cp/semantics.c             | 89 ++++++++++++++++++++++++++--------
> >  gcc/testsuite/g++.dg/pr19377.C | 28 +++++++++++
> >  2 files changed, 98 insertions(+), 19 deletions(-)
> >  create mode 100644 gcc/testsuite/g++.dg/pr19377.C
> >
> > diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
> > index 73834467fca..ffb627f08ea 100644
> > --- a/gcc/cp/semantics.c
> > +++ b/gcc/cp/semantics.c
> > @@ -256,6 +256,58 @@ pop_to_parent_deferring_access_checks (void)
> >      }
> >  }
> >
> > +/* Called from enforce_access.  A class has attempted (but failed) to access
> > +   DECL.  It is already established that a baseclass of that class,
> > +   PARENT_BINFO, has private access to DECL.  Examine certain special cases to
> > +   generate a diagnostic decl location.  If no special cases are found, simply
>
> I don't understand the "generate a diagnostic decl location".  Maybe just
> "generate a diagnostic?"
>
> > +   return DECL.  */
> > +
> > +static tree
> > +get_class_access_diagnostic_decl (tree parent_binfo, tree decl)
> > +{
> > +  /* When a class is denied access to a decl in a baseclass, most of the
> > +     time it is because the decl itself was declared as private at the point
> > +     of declaration.  So, by default, DECL is at fault.
> > +
> > +     However, in C++, there are (at least) two situations in which a decl
> > +     can be private even though it was not originally defined as such.  */
> > +
> > +  /* These two situations only apply if a baseclass had private access to
> > +     DECL (this function is only called if that is the case).  We must however
> > +     also ensure that the reason the parent had private access wasn't simply
> > +     because it was declared as private in the parent.  */
>
> These two comments can be merged into one.
>
> > +  if (context_for_name_lookup (decl) == BINFO_TYPE (parent_binfo))
> > +    return decl;
>
> I think for comparing a binfo with a type we should use SAME_BINFO_TYPE_P.
>
> > +  /* 1.  If the "using" keyword is used to inherit DECL within a baseclass,
> > +     this may cause DECL to be private, so we return the using statement as
> > +     the source of the problem.
> > +
> > +     Scan the fields of PARENT_BINFO and see if there are any using decls.  If
> > +     there are, see if they inherit DECL.  If they do, that's where DECL was
> > +     truly declared private.  */
> > +  for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (parent_binfo));
> > +      parent_field;
> > +      parent_field = DECL_CHAIN (parent_field))
> > +    {
> > +      if ((TREE_CODE (parent_field) == USING_DECL)
>
> This first term doesn't need to be wrapped in ().
>
> > +      && TREE_PRIVATE (parent_field))
> > +     {
> > +       if (DECL_NAME (decl) == DECL_NAME (parent_field))
>
> This could be part of the if above.  And then we can drop the braces (in the
> if and for both).
>
> > +         return parent_field;
> > +     }
> > +    }
> > +
> > +  /* 2.  If decl was privately inherited by a baseclass of the current class,
> > +     then decl will be inaccessible, even though it may originally have
> > +     been accessible to deriving classes.  In that case, the fault lies with
> > +     the baseclass that used a private inheritance, so we return the
> > +     baseclass type as the source of the problem.
> > +
> > +     Since this is the last check, we just assume it's true.  */
> > +  return TYPE_NAME (BINFO_TYPE (parent_binfo));
> > +}
> > +
> >  /* If the current scope isn't allowed to access DECL along
> >     BASETYPE_PATH, give an error, or if we're parsing a function or class
> >     template, defer the access check to be performed at instantiation time.
> > @@ -317,34 +369,33 @@ enforce_access (tree basetype_path, tree decl, tree diag_decl,
> >       diag_decl = strip_inheriting_ctors (diag_decl);
> >        if (complain & tf_error)
> >       {
> > -       /* We will usually want to point to the same place as
> > -          diag_decl but not always.  */
> > +       access_kind access_failure_reason = ak_none;
> > +
> > +       /* By default, using the decl as the source of the problem will
> > +          usually give correct results.  */
> >         tree diag_location = diag_decl;
> > -       access_kind parent_access = ak_none;
> >
> > -       /* See if any of BASETYPE_PATH's parents had private access
> > -          to DECL.  If they did, that will tell us why we don't.  */
> > +       /* However, if a parent of BASETYPE_PATH had private access to decl,
> > +          then it actually might be the case that the source of the problem
> > +          is not DECL.  */
> >         tree parent_binfo = get_parent_with_private_access (decl,
> > -                                                           basetype_path);
> > +                                                            basetype_path);
> >
> > -       /* If a parent had private access, then the diagnostic
> > -          location DECL should be that of the parent class, since it
> > -          failed to give suitable access by using a private
> > -          inheritance.  But if DECL was actually defined in the parent,
> > -          it wasn't privately inherited, and so we don't need to do
> > -          this, and complain_about_access will figure out what to
> > -          do.  */
> > -       if (parent_binfo != NULL_TREE
> > -           && (context_for_name_lookup (decl)
> > -               != BINFO_TYPE (parent_binfo)))
> > +       /* So if a parent did had private access, then we need to do special
>
> Just "had" instead of "did had"?
>
> > +          checks to obtain the best diagnostic location decl.  */
> > +       if (parent_binfo != NULL_TREE)
> >           {
> > -           diag_location = TYPE_NAME (BINFO_TYPE (parent_binfo));
> > -           parent_access = ak_private;
> > +           diag_location = get_class_access_diagnostic_decl (parent_binfo,
> > +                                                            diag_decl);
> > +
> > +           /* We also at this point know that the reason access failed was
> > +              because decl was private.  */
> > +              access_failure_reason = ak_private;
>
> Looks like this line is indented too much (even in the newer patch).
>
> >
> >         /* Finally, generate an error message.  */
> >         complain_about_access (decl, diag_decl, diag_location, true,
> > -                              parent_access);
> > +                             access_failure_reason);
> >       }
> >        if (afi)
> >       afi->record_access_failure (basetype_path, decl, diag_decl);
> > diff --git a/gcc/testsuite/g++.dg/pr19377.C b/gcc/testsuite/g++.dg/pr19377.C
> > new file mode 100644
> > index 00000000000..fb023a33f82
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/pr19377.C
> > @@ -0,0 +1,28 @@
> > +/* { dg-do assemble } */
>
> We usually use dg-do compile.
>
> > +// { dg-options "-std=c++17" }
>
> Best to replace both with
>
> // { dg-do compile { target c++17 } }
>
> > +class A
> > +{
> > +  protected:
> > +  int i();
> > +};
> > +
> > +class A2
> > +{
> > +  protected:
> > +  int i(int a);
> > +};
> > +
> > +class B:private A, private A2
> > +{
> > +  // Comma separated list only officially supported in c++17 and later.
> > +  using A::i, A2::i; // { dg-message "declared" }
> > +};
> > +
> > +class C:public B
> > +{
> > +  void f()
> > +  {
> > +    i(); // { dg-error "private" }
> > +  }
> > +};
> > \ No newline at end of file
> > --
> > 2.25.1
> >
>
> Marek
>
Marek Polacek Feb. 5, 2021, 5:46 p.m. UTC | #9
On Fri, Feb 05, 2021 at 05:28:07PM +0000, Anthony Sharp wrote:
> Hi Marek,
> 
> > Pedantically, "Altered function." is not very good, it should say what
> > in enforce_access changed.
> 
> I would normally 100% agree, but the changes are a bit too complicated
> to be concisely (and helpfully) described there. I think the patch
> description covers it well enough; not sure what I would say without
> having to write a big paragraph there.

I think at least something like "Improve private access checking." would
be better.  No need to be verbose in the ChangeLog.  :)
 
> > Let's move the test into g++.dg/cpp1z and call it using9.C.
> 
> Okie dokie - it's a bit hard to know where stuff's supposed to go in
> that folder. I'll put a comment in the testcase mentioning pr19377
> just in case there's ever a regression.

Yeah, it's customary to start a test with
// PR c++/19377

> > I don't understand the "generate a diagnostic decl location".  Maybe just
> > "generate a diagnostic?"
> 
> get_class_access_diagnostic_decl returns a decl which is used as the
> location that the error-producing code points to as the source of the
> problem. It could be better - I will change it to say "Examine certain
> special cases to find the decl that is the source of the problem" to
> make it a bit clearer.

Oh, I see now.
 
> > These two comments can be merged into one.
> 
> Technically yes ... but I like how it is since in a very subtle way it
> creates a sense of separation between the first two paragraphs and the
> third. The first two paras are sort of an introduction and the third
> begins to describe the code.

Fair enough.
 
> > I think for comparing a binfo with a type we should use SAME_BINFO_TYPE_P.
> 
> Okay, I think that simplifies the code a bit aswell.
> 
> > This first term doesn't need to be wrapped in ().
> 
> I normally wouldn't use the (), but I think that's how Jason likes it
> so that's why I did it. I guess it makes it more readable.
> 
> > This could be part of the if above.
> 
> Oops - I forgot to change that when I modified the code.
> 
> > Just "had" instead of "did had"?
> 
> Good spot - that's a spelling mistake on my part. Should be "did have".
> 
> > Looks like this line is indented too much (even in the newer patch).
> 
> You're right (if you meant access_failure_reason = ak_private), I will change.

Yup, this one.

> If you mean get_class_access_diagnostic_decl (parent_binfo, diag_decl)
> then I donno, because Linux Text Editor says both are on column 64.
> 
> To be honest, I'm sure there is a way to do it, but I'm not really
> sure how to consistently align code. Every text/code editor/browser
> seems to give different column numbers (and display it differently)
> since they seem to all treat whitespace differently.

Yeah, that can be a pain.  Best if your editor does it for you.  If you
use vim, you can use gcc/contrib/vimrc and then vim will do most of the
formatting for you.
 
> > We usually use dg-do compile.
> 
> True, but wouldn't that technically be slower? I would agree if it
> were more than just error-handling code.
> 
> > Best to replace both with
> > // { dg-do compile { target c++17 } }
> 
> Okie dokie. I did see both being used and I wasn't sure which to go for.
> 
> I'll probably send another patch over tomorrow.

Sounds good, thanks!

> On Fri, 5 Feb 2021 at 16:06, Marek Polacek <polacek@redhat.com> wrote:
> >
> > Hi,
> >
> > a few comments:
> >
> > On Fri, Feb 05, 2021 at 03:39:25PM +0000, Anthony Sharp via Gcc-patches wrote:
> > > 2021-02-05  Anthony Sharp  <anthonysharp15@gmail.com>
> > >
> > >       * semantics.c (get_class_access_diagnostic_decl): New function.
> > >       (enforce_access): Altered function.
> >
> > Pedantically, "Altered function." is not very good, it should say what
> > in enforce_access changed.
> >
> > > gcc/testsuite/ChangeLog:
> > >
> > > 2021-02-05  Anthony Sharp  <anthonysharp15@gmail.com>
> > >
> > >       * g++.dg/pr19377.C: New test.
> >
> > Let's move the test into g++.dg/cpp1z and call it using9.C.
> >
> > >  gcc/cp/semantics.c             | 89 ++++++++++++++++++++++++++--------
> > >  gcc/testsuite/g++.dg/pr19377.C | 28 +++++++++++
> > >  2 files changed, 98 insertions(+), 19 deletions(-)
> > >  create mode 100644 gcc/testsuite/g++.dg/pr19377.C
> > >
> > > diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
> > > index 73834467fca..ffb627f08ea 100644
> > > --- a/gcc/cp/semantics.c
> > > +++ b/gcc/cp/semantics.c
> > > @@ -256,6 +256,58 @@ pop_to_parent_deferring_access_checks (void)
> > >      }
> > >  }
> > >
> > > +/* Called from enforce_access.  A class has attempted (but failed) to access
> > > +   DECL.  It is already established that a baseclass of that class,
> > > +   PARENT_BINFO, has private access to DECL.  Examine certain special cases to
> > > +   generate a diagnostic decl location.  If no special cases are found, simply
> >
> > I don't understand the "generate a diagnostic decl location".  Maybe just
> > "generate a diagnostic?"
> >
> > > +   return DECL.  */
> > > +
> > > +static tree
> > > +get_class_access_diagnostic_decl (tree parent_binfo, tree decl)
> > > +{
> > > +  /* When a class is denied access to a decl in a baseclass, most of the
> > > +     time it is because the decl itself was declared as private at the point
> > > +     of declaration.  So, by default, DECL is at fault.
> > > +
> > > +     However, in C++, there are (at least) two situations in which a decl
> > > +     can be private even though it was not originally defined as such.  */
> > > +
> > > +  /* These two situations only apply if a baseclass had private access to
> > > +     DECL (this function is only called if that is the case).  We must however
> > > +     also ensure that the reason the parent had private access wasn't simply
> > > +     because it was declared as private in the parent.  */
> >
> > These two comments can be merged into one.
> >
> > > +  if (context_for_name_lookup (decl) == BINFO_TYPE (parent_binfo))
> > > +    return decl;
> >
> > I think for comparing a binfo with a type we should use SAME_BINFO_TYPE_P.
> >
> > > +  /* 1.  If the "using" keyword is used to inherit DECL within a baseclass,
> > > +     this may cause DECL to be private, so we return the using statement as
> > > +     the source of the problem.
> > > +
> > > +     Scan the fields of PARENT_BINFO and see if there are any using decls.  If
> > > +     there are, see if they inherit DECL.  If they do, that's where DECL was
> > > +     truly declared private.  */
> > > +  for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (parent_binfo));
> > > +      parent_field;
> > > +      parent_field = DECL_CHAIN (parent_field))
> > > +    {
> > > +      if ((TREE_CODE (parent_field) == USING_DECL)
> >
> > This first term doesn't need to be wrapped in ().
> >
> > > +      && TREE_PRIVATE (parent_field))
> > > +     {
> > > +       if (DECL_NAME (decl) == DECL_NAME (parent_field))
> >
> > This could be part of the if above.  And then we can drop the braces (in the
> > if and for both).
> >
> > > +         return parent_field;
> > > +     }
> > > +    }
> > > +
> > > +  /* 2.  If decl was privately inherited by a baseclass of the current class,
> > > +     then decl will be inaccessible, even though it may originally have
> > > +     been accessible to deriving classes.  In that case, the fault lies with
> > > +     the baseclass that used a private inheritance, so we return the
> > > +     baseclass type as the source of the problem.
> > > +
> > > +     Since this is the last check, we just assume it's true.  */
> > > +  return TYPE_NAME (BINFO_TYPE (parent_binfo));
> > > +}
> > > +
> > >  /* If the current scope isn't allowed to access DECL along
> > >     BASETYPE_PATH, give an error, or if we're parsing a function or class
> > >     template, defer the access check to be performed at instantiation time.
> > > @@ -317,34 +369,33 @@ enforce_access (tree basetype_path, tree decl, tree diag_decl,
> > >       diag_decl = strip_inheriting_ctors (diag_decl);
> > >        if (complain & tf_error)
> > >       {
> > > -       /* We will usually want to point to the same place as
> > > -          diag_decl but not always.  */
> > > +       access_kind access_failure_reason = ak_none;
> > > +
> > > +       /* By default, using the decl as the source of the problem will
> > > +          usually give correct results.  */
> > >         tree diag_location = diag_decl;
> > > -       access_kind parent_access = ak_none;
> > >
> > > -       /* See if any of BASETYPE_PATH's parents had private access
> > > -          to DECL.  If they did, that will tell us why we don't.  */
> > > +       /* However, if a parent of BASETYPE_PATH had private access to decl,
> > > +          then it actually might be the case that the source of the problem
> > > +          is not DECL.  */
> > >         tree parent_binfo = get_parent_with_private_access (decl,
> > > -                                                           basetype_path);
> > > +                                                            basetype_path);
> > >
> > > -       /* If a parent had private access, then the diagnostic
> > > -          location DECL should be that of the parent class, since it
> > > -          failed to give suitable access by using a private
> > > -          inheritance.  But if DECL was actually defined in the parent,
> > > -          it wasn't privately inherited, and so we don't need to do
> > > -          this, and complain_about_access will figure out what to
> > > -          do.  */
> > > -       if (parent_binfo != NULL_TREE
> > > -           && (context_for_name_lookup (decl)
> > > -               != BINFO_TYPE (parent_binfo)))
> > > +       /* So if a parent did had private access, then we need to do special
> >
> > Just "had" instead of "did had"?
> >
> > > +          checks to obtain the best diagnostic location decl.  */
> > > +       if (parent_binfo != NULL_TREE)
> > >           {
> > > -           diag_location = TYPE_NAME (BINFO_TYPE (parent_binfo));
> > > -           parent_access = ak_private;
> > > +           diag_location = get_class_access_diagnostic_decl (parent_binfo,
> > > +                                                            diag_decl);
> > > +
> > > +           /* We also at this point know that the reason access failed was
> > > +              because decl was private.  */
> > > +              access_failure_reason = ak_private;
> >
> > Looks like this line is indented too much (even in the newer patch).
> >
> > >
> > >         /* Finally, generate an error message.  */
> > >         complain_about_access (decl, diag_decl, diag_location, true,
> > > -                              parent_access);
> > > +                             access_failure_reason);
> > >       }
> > >        if (afi)
> > >       afi->record_access_failure (basetype_path, decl, diag_decl);
> > > diff --git a/gcc/testsuite/g++.dg/pr19377.C b/gcc/testsuite/g++.dg/pr19377.C
> > > new file mode 100644
> > > index 00000000000..fb023a33f82
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/pr19377.C
> > > @@ -0,0 +1,28 @@
> > > +/* { dg-do assemble } */
> >
> > We usually use dg-do compile.
> >
> > > +// { dg-options "-std=c++17" }
> >
> > Best to replace both with
> >
> > // { dg-do compile { target c++17 } }
> >
> > > +class A
> > > +{
> > > +  protected:
> > > +  int i();
> > > +};
> > > +
> > > +class A2
> > > +{
> > > +  protected:
> > > +  int i(int a);
> > > +};
> > > +
> > > +class B:private A, private A2
> > > +{
> > > +  // Comma separated list only officially supported in c++17 and later.
> > > +  using A::i, A2::i; // { dg-message "declared" }
> > > +};
> > > +
> > > +class C:public B
> > > +{
> > > +  void f()
> > > +  {
> > > +    i(); // { dg-error "private" }
> > > +  }
> > > +};
> > > \ No newline at end of file
> > > --
> > > 2.25.1
> > >
> >
> > Marek
> >
> 

Marek
Anthony Sharp Feb. 6, 2021, 1:09 p.m. UTC | #10
>
> I think at least something like "Improve private access checking." would
> be better.  No need to be verbose in the ChangeLog.  :)


That sounds like a good idea, I will change it.

Yup, this one.


Awesome.

Yeah, that can be a pain.  Best if your editor does it for you.  If you
> use vim, you can use gcc/contrib/vimrc and then vim will do most of the
> formatting for you.


Aah I didn't know that, thanks for the tip!

Anthony



On Fri, 5 Feb 2021 at 17:46, Marek Polacek <polacek@redhat.com> wrote:

> On Fri, Feb 05, 2021 at 05:28:07PM +0000, Anthony Sharp wrote:
> > Hi Marek,
> >
> > > Pedantically, "Altered function." is not very good, it should say what
> > > in enforce_access changed.
> >
> > I would normally 100% agree, but the changes are a bit too complicated
> > to be concisely (and helpfully) described there. I think the patch
> > description covers it well enough; not sure what I would say without
> > having to write a big paragraph there.
>
> I think at least something like "Improve private access checking." would
> be better.  No need to be verbose in the ChangeLog.  :)
>
> > > Let's move the test into g++.dg/cpp1z and call it using9.C.
> >
> > Okie dokie - it's a bit hard to know where stuff's supposed to go in
> > that folder. I'll put a comment in the testcase mentioning pr19377
> > just in case there's ever a regression.
>
> Yeah, it's customary to start a test with
> // PR c++/19377
>
> > > I don't understand the "generate a diagnostic decl location".  Maybe
> just
> > > "generate a diagnostic?"
> >
> > get_class_access_diagnostic_decl returns a decl which is used as the
> > location that the error-producing code points to as the source of the
> > problem. It could be better - I will change it to say "Examine certain
> > special cases to find the decl that is the source of the problem" to
> > make it a bit clearer.
>
> Oh, I see now.
>
> > > These two comments can be merged into one.
> >
> > Technically yes ... but I like how it is since in a very subtle way it
> > creates a sense of separation between the first two paragraphs and the
> > third. The first two paras are sort of an introduction and the third
> > begins to describe the code.
>
> Fair enough.
>
> > > I think for comparing a binfo with a type we should use
> SAME_BINFO_TYPE_P.
> >
> > Okay, I think that simplifies the code a bit aswell.
> >
> > > This first term doesn't need to be wrapped in ().
> >
> > I normally wouldn't use the (), but I think that's how Jason likes it
> > so that's why I did it. I guess it makes it more readable.
> >
> > > This could be part of the if above.
> >
> > Oops - I forgot to change that when I modified the code.
> >
> > > Just "had" instead of "did had"?
> >
> > Good spot - that's a spelling mistake on my part. Should be "did have".
> >
> > > Looks like this line is indented too much (even in the newer patch).
> >
> > You're right (if you meant access_failure_reason = ak_private), I will
> change.
>
> Yup, this one.
>
> > If you mean get_class_access_diagnostic_decl (parent_binfo, diag_decl)
> > then I donno, because Linux Text Editor says both are on column 64.
> >
> > To be honest, I'm sure there is a way to do it, but I'm not really
> > sure how to consistently align code. Every text/code editor/browser
> > seems to give different column numbers (and display it differently)
> > since they seem to all treat whitespace differently.
>
> Yeah, that can be a pain.  Best if your editor does it for you.  If you
> use vim, you can use gcc/contrib/vimrc and then vim will do most of the
> formatting for you.
>
> > > We usually use dg-do compile.
> >
> > True, but wouldn't that technically be slower? I would agree if it
> > were more than just error-handling code.
> >
> > > Best to replace both with
> > > // { dg-do compile { target c++17 } }
> >
> > Okie dokie. I did see both being used and I wasn't sure which to go for.
> >
> > I'll probably send another patch over tomorrow.
>
> Sounds good, thanks!
>
> > On Fri, 5 Feb 2021 at 16:06, Marek Polacek <polacek@redhat.com> wrote:
> > >
> > > Hi,
> > >
> > > a few comments:
> > >
> > > On Fri, Feb 05, 2021 at 03:39:25PM +0000, Anthony Sharp via
> Gcc-patches wrote:
> > > > 2021-02-05  Anthony Sharp  <anthonysharp15@gmail.com>
> > > >
> > > >       * semantics.c (get_class_access_diagnostic_decl): New function.
> > > >       (enforce_access): Altered function.
> > >
> > > Pedantically, "Altered function." is not very good, it should say what
> > > in enforce_access changed.
> > >
> > > > gcc/testsuite/ChangeLog:
> > > >
> > > > 2021-02-05  Anthony Sharp  <anthonysharp15@gmail.com>
> > > >
> > > >       * g++.dg/pr19377.C: New test.
> > >
> > > Let's move the test into g++.dg/cpp1z and call it using9.C.
> > >
> > > >  gcc/cp/semantics.c             | 89
> ++++++++++++++++++++++++++--------
> > > >  gcc/testsuite/g++.dg/pr19377.C | 28 +++++++++++
> > > >  2 files changed, 98 insertions(+), 19 deletions(-)
> > > >  create mode 100644 gcc/testsuite/g++.dg/pr19377.C
> > > >
> > > > diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
> > > > index 73834467fca..ffb627f08ea 100644
> > > > --- a/gcc/cp/semantics.c
> > > > +++ b/gcc/cp/semantics.c
> > > > @@ -256,6 +256,58 @@ pop_to_parent_deferring_access_checks (void)
> > > >      }
> > > >  }
> > > >
> > > > +/* Called from enforce_access.  A class has attempted (but failed)
> to access
> > > > +   DECL.  It is already established that a baseclass of that class,
> > > > +   PARENT_BINFO, has private access to DECL.  Examine certain
> special cases to
> > > > +   generate a diagnostic decl location.  If no special cases are
> found, simply
> > >
> > > I don't understand the "generate a diagnostic decl location".  Maybe
> just
> > > "generate a diagnostic?"
> > >
> > > > +   return DECL.  */
> > > > +
> > > > +static tree
> > > > +get_class_access_diagnostic_decl (tree parent_binfo, tree decl)
> > > > +{
> > > > +  /* When a class is denied access to a decl in a baseclass, most
> of the
> > > > +     time it is because the decl itself was declared as private at
> the point
> > > > +     of declaration.  So, by default, DECL is at fault.
> > > > +
> > > > +     However, in C++, there are (at least) two situations in which
> a decl
> > > > +     can be private even though it was not originally defined as
> such.  */
> > > > +
> > > > +  /* These two situations only apply if a baseclass had private
> access to
> > > > +     DECL (this function is only called if that is the case).  We
> must however
> > > > +     also ensure that the reason the parent had private access
> wasn't simply
> > > > +     because it was declared as private in the parent.  */
> > >
> > > These two comments can be merged into one.
> > >
> > > > +  if (context_for_name_lookup (decl) == BINFO_TYPE (parent_binfo))
> > > > +    return decl;
> > >
> > > I think for comparing a binfo with a type we should use
> SAME_BINFO_TYPE_P.
> > >
> > > > +  /* 1.  If the "using" keyword is used to inherit DECL within a
> baseclass,
> > > > +     this may cause DECL to be private, so we return the using
> statement as
> > > > +     the source of the problem.
> > > > +
> > > > +     Scan the fields of PARENT_BINFO and see if there are any using
> decls.  If
> > > > +     there are, see if they inherit DECL.  If they do, that's where
> DECL was
> > > > +     truly declared private.  */
> > > > +  for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (parent_binfo));
> > > > +      parent_field;
> > > > +      parent_field = DECL_CHAIN (parent_field))
> > > > +    {
> > > > +      if ((TREE_CODE (parent_field) == USING_DECL)
> > >
> > > This first term doesn't need to be wrapped in ().
> > >
> > > > +      && TREE_PRIVATE (parent_field))
> > > > +     {
> > > > +       if (DECL_NAME (decl) == DECL_NAME (parent_field))
> > >
> > > This could be part of the if above.  And then we can drop the braces
> (in the
> > > if and for both).
> > >
> > > > +         return parent_field;
> > > > +     }
> > > > +    }
> > > > +
> > > > +  /* 2.  If decl was privately inherited by a baseclass of the
> current class,
> > > > +     then decl will be inaccessible, even though it may originally
> have
> > > > +     been accessible to deriving classes.  In that case, the fault
> lies with
> > > > +     the baseclass that used a private inheritance, so we return the
> > > > +     baseclass type as the source of the problem.
> > > > +
> > > > +     Since this is the last check, we just assume it's true.  */
> > > > +  return TYPE_NAME (BINFO_TYPE (parent_binfo));
> > > > +}
> > > > +
> > > >  /* If the current scope isn't allowed to access DECL along
> > > >     BASETYPE_PATH, give an error, or if we're parsing a function or
> class
> > > >     template, defer the access check to be performed at
> instantiation time.
> > > > @@ -317,34 +369,33 @@ enforce_access (tree basetype_path, tree decl,
> tree diag_decl,
> > > >       diag_decl = strip_inheriting_ctors (diag_decl);
> > > >        if (complain & tf_error)
> > > >       {
> > > > -       /* We will usually want to point to the same place as
> > > > -          diag_decl but not always.  */
> > > > +       access_kind access_failure_reason = ak_none;
> > > > +
> > > > +       /* By default, using the decl as the source of the problem
> will
> > > > +          usually give correct results.  */
> > > >         tree diag_location = diag_decl;
> > > > -       access_kind parent_access = ak_none;
> > > >
> > > > -       /* See if any of BASETYPE_PATH's parents had private access
> > > > -          to DECL.  If they did, that will tell us why we don't.  */
> > > > +       /* However, if a parent of BASETYPE_PATH had private access
> to decl,
> > > > +          then it actually might be the case that the source of the
> problem
> > > > +          is not DECL.  */
> > > >         tree parent_binfo = get_parent_with_private_access (decl,
> > > > -
>  basetype_path);
> > > > +
> basetype_path);
> > > >
> > > > -       /* If a parent had private access, then the diagnostic
> > > > -          location DECL should be that of the parent class, since it
> > > > -          failed to give suitable access by using a private
> > > > -          inheritance.  But if DECL was actually defined in the
> parent,
> > > > -          it wasn't privately inherited, and so we don't need to do
> > > > -          this, and complain_about_access will figure out what to
> > > > -          do.  */
> > > > -       if (parent_binfo != NULL_TREE
> > > > -           && (context_for_name_lookup (decl)
> > > > -               != BINFO_TYPE (parent_binfo)))
> > > > +       /* So if a parent did had private access, then we need to do
> special
> > >
> > > Just "had" instead of "did had"?
> > >
> > > > +          checks to obtain the best diagnostic location decl.  */
> > > > +       if (parent_binfo != NULL_TREE)
> > > >           {
> > > > -           diag_location = TYPE_NAME (BINFO_TYPE (parent_binfo));
> > > > -           parent_access = ak_private;
> > > > +           diag_location = get_class_access_diagnostic_decl
> (parent_binfo,
> > > > +
> diag_decl);
> > > > +
> > > > +           /* We also at this point know that the reason access
> failed was
> > > > +              because decl was private.  */
> > > > +              access_failure_reason = ak_private;
> > >
> > > Looks like this line is indented too much (even in the newer patch).
> > >
> > > >
> > > >         /* Finally, generate an error message.  */
> > > >         complain_about_access (decl, diag_decl, diag_location, true,
> > > > -                              parent_access);
> > > > +                             access_failure_reason);
> > > >       }
> > > >        if (afi)
> > > >       afi->record_access_failure (basetype_path, decl, diag_decl);
> > > > diff --git a/gcc/testsuite/g++.dg/pr19377.C
> b/gcc/testsuite/g++.dg/pr19377.C
> > > > new file mode 100644
> > > > index 00000000000..fb023a33f82
> > > > --- /dev/null
> > > > +++ b/gcc/testsuite/g++.dg/pr19377.C
> > > > @@ -0,0 +1,28 @@
> > > > +/* { dg-do assemble } */
> > >
> > > We usually use dg-do compile.
> > >
> > > > +// { dg-options "-std=c++17" }
> > >
> > > Best to replace both with
> > >
> > > // { dg-do compile { target c++17 } }
> > >
> > > > +class A
> > > > +{
> > > > +  protected:
> > > > +  int i();
> > > > +};
> > > > +
> > > > +class A2
> > > > +{
> > > > +  protected:
> > > > +  int i(int a);
> > > > +};
> > > > +
> > > > +class B:private A, private A2
> > > > +{
> > > > +  // Comma separated list only officially supported in c++17 and
> later.
> > > > +  using A::i, A2::i; // { dg-message "declared" }
> > > > +};
> > > > +
> > > > +class C:public B
> > > > +{
> > > > +  void f()
> > > > +  {
> > > > +    i(); // { dg-error "private" }
> > > > +  }
> > > > +};
> > > > \ No newline at end of file
> > > > --
> > > > 2.25.1
> > > >
> > >
> > > Marek
> > >
> >
>
> Marek
>
>
Anthony Sharp Feb. 6, 2021, 8:25 p.m. UTC | #11
Hi all,

I tried both:

(SAME_BINFO_TYPE_P (context_for_name_lookup (decl), parent_binfo))
and
(SAME_BINFO_TYPE_P (TYPE_BINFO (context_for_name_lookup (decl)),
parent_binfo))

but both caused around 80 regressions because it was returning false when
it should have been returning true. No idea why. In the end I used

(same_type_p (context_for_name_lookup (decl), BINFO_TYPE (parent_binfo)))

which works. New patch attached.

Anthony

On Sat, 6 Feb 2021 at 13:09, Anthony Sharp <anthonysharp15@gmail.com> wrote:

> I think at least something like "Improve private access checking." would
>> be better.  No need to be verbose in the ChangeLog.  :)
>
>
> That sounds like a good idea, I will change it.
>
> Yup, this one.
>
>
> Awesome.
>
> Yeah, that can be a pain.  Best if your editor does it for you.  If you
>> use vim, you can use gcc/contrib/vimrc and then vim will do most of the
>> formatting for you.
>
>
> Aah I didn't know that, thanks for the tip!
>
> Anthony
>
>
>
> On Fri, 5 Feb 2021 at 17:46, Marek Polacek <polacek@redhat.com> wrote:
>
>> On Fri, Feb 05, 2021 at 05:28:07PM +0000, Anthony Sharp wrote:
>> > Hi Marek,
>> >
>> > > Pedantically, "Altered function." is not very good, it should say what
>> > > in enforce_access changed.
>> >
>> > I would normally 100% agree, but the changes are a bit too complicated
>> > to be concisely (and helpfully) described there. I think the patch
>> > description covers it well enough; not sure what I would say without
>> > having to write a big paragraph there.
>>
>> I think at least something like "Improve private access checking." would
>> be better.  No need to be verbose in the ChangeLog.  :)
>>
>> > > Let's move the test into g++.dg/cpp1z and call it using9.C.
>> >
>> > Okie dokie - it's a bit hard to know where stuff's supposed to go in
>> > that folder. I'll put a comment in the testcase mentioning pr19377
>> > just in case there's ever a regression.
>>
>> Yeah, it's customary to start a test with
>> // PR c++/19377
>>
>> > > I don't understand the "generate a diagnostic decl location".  Maybe
>> just
>> > > "generate a diagnostic?"
>> >
>> > get_class_access_diagnostic_decl returns a decl which is used as the
>> > location that the error-producing code points to as the source of the
>> > problem. It could be better - I will change it to say "Examine certain
>> > special cases to find the decl that is the source of the problem" to
>> > make it a bit clearer.
>>
>> Oh, I see now.
>>
>> > > These two comments can be merged into one.
>> >
>> > Technically yes ... but I like how it is since in a very subtle way it
>> > creates a sense of separation between the first two paragraphs and the
>> > third. The first two paras are sort of an introduction and the third
>> > begins to describe the code.
>>
>> Fair enough.
>>
>> > > I think for comparing a binfo with a type we should use
>> SAME_BINFO_TYPE_P.
>> >
>> > Okay, I think that simplifies the code a bit aswell.
>> >
>> > > This first term doesn't need to be wrapped in ().
>> >
>> > I normally wouldn't use the (), but I think that's how Jason likes it
>> > so that's why I did it. I guess it makes it more readable.
>> >
>> > > This could be part of the if above.
>> >
>> > Oops - I forgot to change that when I modified the code.
>> >
>> > > Just "had" instead of "did had"?
>> >
>> > Good spot - that's a spelling mistake on my part. Should be "did have".
>> >
>> > > Looks like this line is indented too much (even in the newer patch).
>> >
>> > You're right (if you meant access_failure_reason = ak_private), I will
>> change.
>>
>> Yup, this one.
>>
>> > If you mean get_class_access_diagnostic_decl (parent_binfo, diag_decl)
>> > then I donno, because Linux Text Editor says both are on column 64.
>> >
>> > To be honest, I'm sure there is a way to do it, but I'm not really
>> > sure how to consistently align code. Every text/code editor/browser
>> > seems to give different column numbers (and display it differently)
>> > since they seem to all treat whitespace differently.
>>
>> Yeah, that can be a pain.  Best if your editor does it for you.  If you
>> use vim, you can use gcc/contrib/vimrc and then vim will do most of the
>> formatting for you.
>>
>> > > We usually use dg-do compile.
>> >
>> > True, but wouldn't that technically be slower? I would agree if it
>> > were more than just error-handling code.
>> >
>> > > Best to replace both with
>> > > // { dg-do compile { target c++17 } }
>> >
>> > Okie dokie. I did see both being used and I wasn't sure which to go for.
>> >
>> > I'll probably send another patch over tomorrow.
>>
>> Sounds good, thanks!
>>
>> > On Fri, 5 Feb 2021 at 16:06, Marek Polacek <polacek@redhat.com> wrote:
>> > >
>> > > Hi,
>> > >
>> > > a few comments:
>> > >
>> > > On Fri, Feb 05, 2021 at 03:39:25PM +0000, Anthony Sharp via
>> Gcc-patches wrote:
>> > > > 2021-02-05  Anthony Sharp  <anthonysharp15@gmail.com>
>> > > >
>> > > >       * semantics.c (get_class_access_diagnostic_decl): New
>> function.
>> > > >       (enforce_access): Altered function.
>> > >
>> > > Pedantically, "Altered function." is not very good, it should say what
>> > > in enforce_access changed.
>> > >
>> > > > gcc/testsuite/ChangeLog:
>> > > >
>> > > > 2021-02-05  Anthony Sharp  <anthonysharp15@gmail.com>
>> > > >
>> > > >       * g++.dg/pr19377.C: New test.
>> > >
>> > > Let's move the test into g++.dg/cpp1z and call it using9.C.
>> > >
>> > > >  gcc/cp/semantics.c             | 89
>> ++++++++++++++++++++++++++--------
>> > > >  gcc/testsuite/g++.dg/pr19377.C | 28 +++++++++++
>> > > >  2 files changed, 98 insertions(+), 19 deletions(-)
>> > > >  create mode 100644 gcc/testsuite/g++.dg/pr19377.C
>> > > >
>> > > > diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
>> > > > index 73834467fca..ffb627f08ea 100644
>> > > > --- a/gcc/cp/semantics.c
>> > > > +++ b/gcc/cp/semantics.c
>> > > > @@ -256,6 +256,58 @@ pop_to_parent_deferring_access_checks (void)
>> > > >      }
>> > > >  }
>> > > >
>> > > > +/* Called from enforce_access.  A class has attempted (but failed)
>> to access
>> > > > +   DECL.  It is already established that a baseclass of that class,
>> > > > +   PARENT_BINFO, has private access to DECL.  Examine certain
>> special cases to
>> > > > +   generate a diagnostic decl location.  If no special cases are
>> found, simply
>> > >
>> > > I don't understand the "generate a diagnostic decl location".  Maybe
>> just
>> > > "generate a diagnostic?"
>> > >
>> > > > +   return DECL.  */
>> > > > +
>> > > > +static tree
>> > > > +get_class_access_diagnostic_decl (tree parent_binfo, tree decl)
>> > > > +{
>> > > > +  /* When a class is denied access to a decl in a baseclass, most
>> of the
>> > > > +     time it is because the decl itself was declared as private at
>> the point
>> > > > +     of declaration.  So, by default, DECL is at fault.
>> > > > +
>> > > > +     However, in C++, there are (at least) two situations in which
>> a decl
>> > > > +     can be private even though it was not originally defined as
>> such.  */
>> > > > +
>> > > > +  /* These two situations only apply if a baseclass had private
>> access to
>> > > > +     DECL (this function is only called if that is the case).  We
>> must however
>> > > > +     also ensure that the reason the parent had private access
>> wasn't simply
>> > > > +     because it was declared as private in the parent.  */
>> > >
>> > > These two comments can be merged into one.
>> > >
>> > > > +  if (context_for_name_lookup (decl) == BINFO_TYPE (parent_binfo))
>> > > > +    return decl;
>> > >
>> > > I think for comparing a binfo with a type we should use
>> SAME_BINFO_TYPE_P.
>> > >
>> > > > +  /* 1.  If the "using" keyword is used to inherit DECL within a
>> baseclass,
>> > > > +     this may cause DECL to be private, so we return the using
>> statement as
>> > > > +     the source of the problem.
>> > > > +
>> > > > +     Scan the fields of PARENT_BINFO and see if there are any
>> using decls.  If
>> > > > +     there are, see if they inherit DECL.  If they do, that's
>> where DECL was
>> > > > +     truly declared private.  */
>> > > > +  for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (parent_binfo));
>> > > > +      parent_field;
>> > > > +      parent_field = DECL_CHAIN (parent_field))
>> > > > +    {
>> > > > +      if ((TREE_CODE (parent_field) == USING_DECL)
>> > >
>> > > This first term doesn't need to be wrapped in ().
>> > >
>> > > > +      && TREE_PRIVATE (parent_field))
>> > > > +     {
>> > > > +       if (DECL_NAME (decl) == DECL_NAME (parent_field))
>> > >
>> > > This could be part of the if above.  And then we can drop the braces
>> (in the
>> > > if and for both).
>> > >
>> > > > +         return parent_field;
>> > > > +     }
>> > > > +    }
>> > > > +
>> > > > +  /* 2.  If decl was privately inherited by a baseclass of the
>> current class,
>> > > > +     then decl will be inaccessible, even though it may originally
>> have
>> > > > +     been accessible to deriving classes.  In that case, the fault
>> lies with
>> > > > +     the baseclass that used a private inheritance, so we return
>> the
>> > > > +     baseclass type as the source of the problem.
>> > > > +
>> > > > +     Since this is the last check, we just assume it's true.  */
>> > > > +  return TYPE_NAME (BINFO_TYPE (parent_binfo));
>> > > > +}
>> > > > +
>> > > >  /* If the current scope isn't allowed to access DECL along
>> > > >     BASETYPE_PATH, give an error, or if we're parsing a function or
>> class
>> > > >     template, defer the access check to be performed at
>> instantiation time.
>> > > > @@ -317,34 +369,33 @@ enforce_access (tree basetype_path, tree
>> decl, tree diag_decl,
>> > > >       diag_decl = strip_inheriting_ctors (diag_decl);
>> > > >        if (complain & tf_error)
>> > > >       {
>> > > > -       /* We will usually want to point to the same place as
>> > > > -          diag_decl but not always.  */
>> > > > +       access_kind access_failure_reason = ak_none;
>> > > > +
>> > > > +       /* By default, using the decl as the source of the problem
>> will
>> > > > +          usually give correct results.  */
>> > > >         tree diag_location = diag_decl;
>> > > > -       access_kind parent_access = ak_none;
>> > > >
>> > > > -       /* See if any of BASETYPE_PATH's parents had private access
>> > > > -          to DECL.  If they did, that will tell us why we don't.
>> */
>> > > > +       /* However, if a parent of BASETYPE_PATH had private access
>> to decl,
>> > > > +          then it actually might be the case that the source of
>> the problem
>> > > > +          is not DECL.  */
>> > > >         tree parent_binfo = get_parent_with_private_access (decl,
>> > > > -
>>  basetype_path);
>> > > > +
>> basetype_path);
>> > > >
>> > > > -       /* If a parent had private access, then the diagnostic
>> > > > -          location DECL should be that of the parent class, since
>> it
>> > > > -          failed to give suitable access by using a private
>> > > > -          inheritance.  But if DECL was actually defined in the
>> parent,
>> > > > -          it wasn't privately inherited, and so we don't need to do
>> > > > -          this, and complain_about_access will figure out what to
>> > > > -          do.  */
>> > > > -       if (parent_binfo != NULL_TREE
>> > > > -           && (context_for_name_lookup (decl)
>> > > > -               != BINFO_TYPE (parent_binfo)))
>> > > > +       /* So if a parent did had private access, then we need to
>> do special
>> > >
>> > > Just "had" instead of "did had"?
>> > >
>> > > > +          checks to obtain the best diagnostic location decl.  */
>> > > > +       if (parent_binfo != NULL_TREE)
>> > > >           {
>> > > > -           diag_location = TYPE_NAME (BINFO_TYPE (parent_binfo));
>> > > > -           parent_access = ak_private;
>> > > > +           diag_location = get_class_access_diagnostic_decl
>> (parent_binfo,
>> > > > +
>> diag_decl);
>> > > > +
>> > > > +           /* We also at this point know that the reason access
>> failed was
>> > > > +              because decl was private.  */
>> > > > +              access_failure_reason = ak_private;
>> > >
>> > > Looks like this line is indented too much (even in the newer patch).
>> > >
>> > > >
>> > > >         /* Finally, generate an error message.  */
>> > > >         complain_about_access (decl, diag_decl, diag_location, true,
>> > > > -                              parent_access);
>> > > > +                             access_failure_reason);
>> > > >       }
>> > > >        if (afi)
>> > > >       afi->record_access_failure (basetype_path, decl, diag_decl);
>> > > > diff --git a/gcc/testsuite/g++.dg/pr19377.C
>> b/gcc/testsuite/g++.dg/pr19377.C
>> > > > new file mode 100644
>> > > > index 00000000000..fb023a33f82
>> > > > --- /dev/null
>> > > > +++ b/gcc/testsuite/g++.dg/pr19377.C
>> > > > @@ -0,0 +1,28 @@
>> > > > +/* { dg-do assemble } */
>> > >
>> > > We usually use dg-do compile.
>> > >
>> > > > +// { dg-options "-std=c++17" }
>> > >
>> > > Best to replace both with
>> > >
>> > > // { dg-do compile { target c++17 } }
>> > >
>> > > > +class A
>> > > > +{
>> > > > +  protected:
>> > > > +  int i();
>> > > > +};
>> > > > +
>> > > > +class A2
>> > > > +{
>> > > > +  protected:
>> > > > +  int i(int a);
>> > > > +};
>> > > > +
>> > > > +class B:private A, private A2
>> > > > +{
>> > > > +  // Comma separated list only officially supported in c++17 and
>> later.
>> > > > +  using A::i, A2::i; // { dg-message "declared" }
>> > > > +};
>> > > > +
>> > > > +class C:public B
>> > > > +{
>> > > > +  void f()
>> > > > +  {
>> > > > +    i(); // { dg-error "private" }
>> > > > +  }
>> > > > +};
>> > > > \ No newline at end of file
>> > > > --
>> > > > 2.25.1
>> > > >
>> > >
>> > > Marek
>> > >
>> >
>>
>> Marek
>>
>>
Jason Merrill Feb. 9, 2021, 4:54 a.m. UTC | #12
On 2/5/21 12:28 PM, Anthony Sharp wrote:
> Hi Marek,
> 
>>> +      if ((TREE_CODE (parent_field) == USING_DECL)
>>
>> This first term doesn't need to be wrapped in ().
> 
> I normally wouldn't use the (), but I think that's how Jason likes it
> so that's why I did it. I guess it makes it more readable.

Ah, no, I don't see any need for the extra () there.  When I asked for 
added parens previously:

>> +       if (parent_binfo != NULL_TREE
>> +           && context_for_name_lookup (decl)
>> +           != BINFO_TYPE (parent_binfo))
> 
> Here you want parens around the second != expression and its != token
> aligned with "context"

The parens are to help the editor line up the last line properly.  If 
the subexpression fits on one line, the parens aren't needed.

>> We usually use dg-do compile.
> 
> True, but wouldn't that technically be slower? I would agree if it
> were more than just error-handling code.

No; "compile" means translate from C++ to assembly, "assemble" means 
that, plus call the assembler to produce an object file.  Though since 
compilation errors out, assembling never happens.

> +      if ((TREE_CODE (parent_field) == USING_DECL)
> +        && TREE_PRIVATE (parent_field)
> +        && (DECL_NAME (decl) == DECL_NAME (parent_field)))
> +       return parent_field;

Following our discussion about an earlier revision, this will often 
produce the right using-declaration, but might not if two functions of 
the same name are imported from different bases.  If I split the 
using-declaration in using9.C into two, i.e.

>   using A2::i; // { dg-message "declared" }                                                                    
>   using A::i;

then I get

> wa.ii: In member function ‘void C::f()’:
> wa.ii:28:7: error: ‘int A::i()’ is private within this context
>    28 |     i();
>       |       ^
> wa.ii:20:13: note: declared private here
>    20 |   using A2::i;

pointing out the wrong using-declaration because it happens to be first. 
  You could bring back your lookup from the earlier patch and look 
through the result to see if the function we're complaining about is 
part of what the particular using-decl brings in.

> I tried both: 
> (SAME_BINFO_TYPE_P (context_for_name_lookup (decl), parent_binfo))
> and
> (SAME_BINFO_TYPE_P (TYPE_BINFO (context_for_name_lookup (decl)), parent_binfo))

I think you want

SAME_BINFO_TYPE_P (context_for_name_lookup (decl),
                    BINFO_TYPE (parent_binfo))

i.e. just change same_type_p to SAME_BINFO_TYPE_P.

>           tree parent_binfo = get_parent_with_private_access (decl,
> -                                                             basetype_path);
> +                                                              basetype_path);

This line was indented properly before, and is changed to be indented 
one space too far.

> +             diag_location = get_class_access_diagnostic_decl (parent_binfo,
> +                                                              diag_decl);

This line needs one more space.

>           complain_about_access (decl, diag_decl, diag_location, true,
> -                                parent_access);
> +                               access_failure_reason);

This line, too.

> +};
> \ No newline at end of file

Let's add a newline.

Jason
Anthony Sharp Feb. 9, 2021, 11:18 a.m. UTC | #13
>
> The parens are to help the editor line up the last line properly.  If
> the subexpression fits on one line, the parens aren't needed.


Ahhhh okay.

No; "compile" means translate from C++ to assembly, "assemble" means
> that, plus call the assembler to produce an object file.  Though since
> compilation errors out, assembling never happens.


lol I was being dumb and thinking it was the other way around. I will
change it.

  You could bring back your lookup from the earlier patch and look
> through the result to see if the function we're complaining about is
> part of what the particular using-decl brings in.


I will give that a try.

I think you want
> SAME_BINFO_TYPE_P (context_for_name_lookup (decl),
>                     BINFO_TYPE (parent_binfo))


Am I reading this wrong then? :

/* Compare a BINFO_TYPE with another type for equality.
For a binfo, this is functionally equivalent to using same_type_p, but
measurably faster.
 At least one of the arguments must be a BINFO_TYPE.  The other can be a
BINFO_TYPE
or a regular type.  If BINFO_TYPE(T) ever stops being the main variant of
the class the
binfo is for, this macro must change.  */
#define SAME_BINFO_TYPE_P(A, B) ((A) == (B))

That leads me to believe that arguments A and B can be: BINFO, BINFO ... or
BINFO, TYPE ... or TYPE, BINFO. If so I don't see why this doesn't work:

(SAME_BINFO_TYPE_P (context_for_name_lookup (decl), parent_binfo))
and
(SAME_BINFO_TYPE_P (TYPE_BINFO (context_for_name_lookup (decl)),
parent_binfo))

Unless "BINFO_TYPE" is actually just how you refer to a regular class type
and not a BINFO. Seems a bit confusing to me.

This line needs one more space.


Oh I see ... so second line's arguments need to line up with the first
argument, not the first (.

I will send over a new patch later/tomorrow.

Anthony

On Tue, 9 Feb 2021 at 04:55, Jason Merrill <jason@redhat.com> wrote:

> On 2/5/21 12:28 PM, Anthony Sharp wrote:
> > Hi Marek,
> >
> >>> +      if ((TREE_CODE (parent_field) == USING_DECL)
> >>
> >> This first term doesn't need to be wrapped in ().
> >
> > I normally wouldn't use the (), but I think that's how Jason likes it
> > so that's why I did it. I guess it makes it more readable.
>
> Ah, no, I don't see any need for the extra () there.  When I asked for
> added parens previously:
>
> >> +       if (parent_binfo != NULL_TREE
> >> +           && context_for_name_lookup (decl)
> >> +           != BINFO_TYPE (parent_binfo))
> >
> > Here you want parens around the second != expression and its != token
> > aligned with "context"
>
> The parens are to help the editor line up the last line properly.  If
> the subexpression fits on one line, the parens aren't needed.
>
> >> We usually use dg-do compile.
> >
> > True, but wouldn't that technically be slower? I would agree if it
> > were more than just error-handling code.
>
> No; "compile" means translate from C++ to assembly, "assemble" means
> that, plus call the assembler to produce an object file.  Though since
> compilation errors out, assembling never happens.
>
> > +      if ((TREE_CODE (parent_field) == USING_DECL)
> > +        && TREE_PRIVATE (parent_field)
> > +        && (DECL_NAME (decl) == DECL_NAME (parent_field)))
> > +       return parent_field;
>
> Following our discussion about an earlier revision, this will often
> produce the right using-declaration, but might not if two functions of
> the same name are imported from different bases.  If I split the
> using-declaration in using9.C into two, i.e.
>
> >   using A2::i; // { dg-message "declared" }
>
> >   using A::i;
>
> then I get
>
> > wa.ii: In member function ‘void C::f()’:
> > wa.ii:28:7: error: ‘int A::i()’ is private within this context
> >    28 |     i();
> >       |       ^
> > wa.ii:20:13: note: declared private here
> >    20 |   using A2::i;
>
> pointing out the wrong using-declaration because it happens to be first.
>   You could bring back your lookup from the earlier patch and look
> through the result to see if the function we're complaining about is
> part of what the particular using-decl brings in.
>
> > I tried both:
> > (SAME_BINFO_TYPE_P (context_for_name_lookup (decl), parent_binfo))
> > and
> > (SAME_BINFO_TYPE_P (TYPE_BINFO (context_for_name_lookup (decl)),
> parent_binfo))
>
> I think you want
>
> SAME_BINFO_TYPE_P (context_for_name_lookup (decl),
>                     BINFO_TYPE (parent_binfo))
>
> i.e. just change same_type_p to SAME_BINFO_TYPE_P.
>
> >           tree parent_binfo = get_parent_with_private_access (decl,
> > -
>  basetype_path);
> > +
> basetype_path);
>
> This line was indented properly before, and is changed to be indented
> one space too far.
>
> > +             diag_location = get_class_access_diagnostic_decl
> (parent_binfo,
> > +
> diag_decl);
>
> This line needs one more space.
>
> >           complain_about_access (decl, diag_decl, diag_location, true,
> > -                                parent_access);
> > +                               access_failure_reason);
>
> This line, too.
>
> > +};
> > \ No newline at end of file
>
> Let's add a newline.
>
> Jason
>
>
Jason Merrill Feb. 9, 2021, 8:07 p.m. UTC | #14
On 2/9/21 6:18 AM, Anthony Sharp wrote:
>     The parens are to help the editor line up the last line properly.  If
>     the subexpression fits on one line, the parens aren't needed.
> 
> 
> Ahhhh okay.
> 
>     No; "compile" means translate from C++ to assembly, "assemble" means
>     that, plus call the assembler to produce an object file.  Though since
>     compilation errors out, assembling never happens.
> 
> 
> lol I was being dumb and thinking it was the other way around. I will 
> change it.
> 
>        You could bring back your lookup from the earlier patch and look
>     through the result to see if the function we're complaining about is
>     part of what the particular using-decl brings in.
> 
> 
> I will give that a try.
> 
>     I think you want
>     SAME_BINFO_TYPE_P (context_for_name_lookup (decl),
>                          BINFO_TYPE (parent_binfo))
> 
> 
> Am I reading this wrong then? :
> 
> /* Compare a BINFO_TYPE with another type for equality.
> For a binfo, this is functionally equivalent to using same_type_p, but 
> measurably faster.
>   At least one of the arguments must be a BINFO_TYPE.  The other can be 
> a BINFO_TYPE
> or a regular type.  If BINFO_TYPE(T) ever stops being the main variant 
> of the class the
> binfo is for, this macro must change.  */
> #define SAME_BINFO_TYPE_P(A, B) ((A) == (B))
> 
> That leads me to believe that arguments A and B can be: BINFO, BINFO ... 
> or BINFO, TYPE ... or TYPE, BINFO. If so I don't see why this doesn't work:
> 
> (SAME_BINFO_TYPE_P (context_for_name_lookup (decl), parent_binfo))
> and
> (SAME_BINFO_TYPE_P (TYPE_BINFO (context_for_name_lookup (decl)), 
> parent_binfo))
> 
> Unless "BINFO_TYPE" is actually just how you refer to a regular class 
> type and not a BINFO. Seems a bit confusing to me.

The comment is pretty conusing, I agree.  But what it's saying is that 
both arguments must be types, and one of those types must be BINFO_TYPE 
(some_binfo).

Your first line doesn't work because you're comparing a type and a 
binfo.  The second one doesn't work because you're comparing the binfo 
for a most-derived object of the type to the binfo for a base subobject 
of the same type, and those are different, because binfos represent 
nodes in inheritance graphs.

>     This line needs one more space.
> 
> 
> Oh I see ... so second line's arguments need to line up with the first 
> argument, not the first (.
> 
> I will send over a new patch later/tomorrow.
> 
> Anthony
> 
> On Tue, 9 Feb 2021 at 04:55, Jason Merrill <jason@redhat.com 
> <mailto:jason@redhat.com>> wrote:
> 
>     On 2/5/21 12:28 PM, Anthony Sharp wrote:
>      > Hi Marek,
>      >
>      >>> +      if ((TREE_CODE (parent_field) == USING_DECL)
>      >>
>      >> This first term doesn't need to be wrapped in ().
>      >
>      > I normally wouldn't use the (), but I think that's how Jason likes it
>      > so that's why I did it. I guess it makes it more readable.
> 
>     Ah, no, I don't see any need for the extra () there.  When I asked for
>     added parens previously:
> 
>      >> +       if (parent_binfo != NULL_TREE
>      >> +           && context_for_name_lookup (decl)
>      >> +           != BINFO_TYPE (parent_binfo))
>      >
>      > Here you want parens around the second != expression and its != token
>      > aligned with "context"
> 
>     The parens are to help the editor line up the last line properly.  If
>     the subexpression fits on one line, the parens aren't needed.
> 
>      >> We usually use dg-do compile.
>      >
>      > True, but wouldn't that technically be slower? I would agree if it
>      > were more than just error-handling code.
> 
>     No; "compile" means translate from C++ to assembly, "assemble" means
>     that, plus call the assembler to produce an object file.  Though since
>     compilation errors out, assembling never happens.
> 
>      > +      if ((TREE_CODE (parent_field) == USING_DECL)
>      > +        && TREE_PRIVATE (parent_field)
>      > +        && (DECL_NAME (decl) == DECL_NAME (parent_field)))
>      > +       return parent_field;
> 
>     Following our discussion about an earlier revision, this will often
>     produce the right using-declaration, but might not if two functions of
>     the same name are imported from different bases.  If I split the
>     using-declaration in using9.C into two, i.e.
> 
>      >   using A2::i; // { dg-message "declared" }
>      >   using A::i;
> 
>     then I get
> 
>      > wa.ii: In member function ‘void C::f()’:
>      > wa.ii:28:7: error: ‘int A::i()’ is private within this context
>      >    28 |     i();
>      >       |       ^
>      > wa.ii:20:13: note: declared private here
>      >    20 |   using A2::i;
> 
>     pointing out the wrong using-declaration because it happens to be
>     first.
>        You could bring back your lookup from the earlier patch and look
>     through the result to see if the function we're complaining about is
>     part of what the particular using-decl brings in.
> 
>      > I tried both:
>      > (SAME_BINFO_TYPE_P (context_for_name_lookup (decl), parent_binfo))
>      > and
>      > (SAME_BINFO_TYPE_P (TYPE_BINFO (context_for_name_lookup (decl)),
>     parent_binfo))
> 
>     I think you want
> 
>     SAME_BINFO_TYPE_P (context_for_name_lookup (decl),
>                          BINFO_TYPE (parent_binfo))
> 
>     i.e. just change same_type_p to SAME_BINFO_TYPE_P.
> 
>      >           tree parent_binfo = get_parent_with_private_access (decl,
>      > -                                                           
>       basetype_path);
>      > +                                                             
>     basetype_path);
> 
>     This line was indented properly before, and is changed to be indented
>     one space too far.
> 
>      > +             diag_location = get_class_access_diagnostic_decl
>     (parent_binfo,
>      > +                                                             
>     diag_decl);
> 
>     This line needs one more space.
> 
>      >           complain_about_access (decl, diag_decl, diag_location,
>     true,
>      > -                                parent_access);
>      > +                               access_failure_reason);
> 
>     This line, too.
> 
>      > +};
>      > \ No newline at end of file
> 
>     Let's add a newline.
> 
>     Jason
>
Anthony Sharp Feb. 15, 2021, 12:45 p.m. UTC | #15
Hi all,

This overloaded functions issue has been a pain in the ass but I have
found a solution that seems to work. I could really do with some
feedback on it though since I'm not sure if there's a better way to do
it that I am missing.

Here's the problem. When a using statement causes an access failure,
we need to find out WHICH using statement caused the access failure
(this information is not given to enforce_access; it merely gets the
ORIGINAL decl [i.e. the one that the using statement inherited]). To
make matters worse, a USING_DECL does not seem to store any
information about where it "came from", e.g. there's no ORIGINAL_DECL
(USING_DECL) macro or anything like that. DECL_INITIAL doesn't seem to
help (and is probably totally not related to the issue).

So we need to do a long-winded lookup.

First, we iterate through the USING_DECLs in the class where access
failed (in the context of a parent that has caused the access failure
because it has private access). For normal field variables, we COULD
simply do a name lookup on the USING_DECL and compare it to the
original DECL. That would be easy, since there can only be one
variable field with the same name.

However, that doesn't work for overloaded functions. Name lookup would
return multiple results, making comparison meaningless.

What we need is therefore not a name lookup, but a decl lookup. It
sounds stupid, because what that basically means is looking for an
exact match of a decl, which is intuitively stupid, because why would
you look for an exact match of something you already have? But
actually we can take advantage of a quirk that USING_DECLs have, which
is that, when stripped, cp_tree_equal (stripped_using_decl,
original_decl) returns true, even through stripped_using_decl and
original_decl exist on different lines and in different places. In
other words, a USING_DECL is the same as the original DECL it
inherits, even though they are in different places (Unless I am just
being really dumb?).

Anyways, to summarise ...

1) We iterate through USING_DECLs in the class that caused a private
access failure.
2) For each USING_DECL, we find the DECL that USING_DECL inherited.
  2.1) To do this, we iterate through all fields of all base classes
(possibly slow? but it is just diagnostics code afterall,
         so idk if that's a big deal)
  2.2)  We compare each FIELD against the USING_DECL. if equal, then
we return FIELD.
3) if the DECL that USING_DECL inherited is equal to the diagnostic
decl we were given in enforce_access, we return USING_DECL as being
the source of the problem.

In a perfect world, I guess the USING_DECL would store somewhere what
it inherited as it was parsed. But I'm not sure if that's practical to
do and I'm not sure it would be worth the effort considering it would
probably be used only for this niche scenario. Donno.

I have not regression tested this, but it does seem to work on the
test case at least (which I've also included). Also please ignore
formatting issues ATM.

If you think this is a reasonable way to do it then I will tidy up the
code, test it and make a patch and send it over. If anyone has any
better ideas (probably), then please let me know. I did try the name
lookup as Jason suggested but, as I say, in the case of overloaded
functions it returns multiple values, so it didn't help in determining
what DECL a USING_DECL inherited.

BTW, I will eventually put find_decl_using_decl_inherits and
lookup_decl_exact_match in search.c.

Just for proof, here's the output from the testcase (hopefully it
formats this correctly):

/home/anthony/Desktop/using9.C: In member function ‘void C::f()’:
/home/anthony/Desktop/using9.C:34:11: error: ‘int A1::comma()’ is
private within this context
   34 |     comma();
      |                   ^
/home/anthony/Desktop/using9.C:22:24: note: declared private here
   22 |   using A2::comma, A1::comma;  // { dg-message "declared" }
      |                                   ^~~~~
/home/anthony/Desktop/using9.C:35:14: error: ‘int A1::separate()’ is
private within this context
   35 |     separate(); // { dg-error "private" }
      |                     ^
/home/anthony/Desktop/using9.C:25:13: note: declared private here
   25 |   using A1::separate; // { dg-message "declared" }
      |                      ^~~~~~~~
/home/anthony/Desktop/using9.C:36:5: error: ‘int A2::alone’ is private
within this context
   36 |     alone = 5; // { dg-error "private" }
      |       ^~~~~
/home/anthony/Desktop/using9.C:27:13: note: declared private here
   27 |   using A2::alone;
      |                    ^~~~~

Actual code attached.

Anthony


On Tue, 9 Feb 2021 at 20:07, Jason Merrill <jason@redhat.com> wrote:
>
> On 2/9/21 6:18 AM, Anthony Sharp wrote:
> >     The parens are to help the editor line up the last line properly.  If
> >     the subexpression fits on one line, the parens aren't needed.
> >
> >
> > Ahhhh okay.
> >
> >     No; "compile" means translate from C++ to assembly, "assemble" means
> >     that, plus call the assembler to produce an object file.  Though since
> >     compilation errors out, assembling never happens.
> >
> >
> > lol I was being dumb and thinking it was the other way around. I will
> > change it.
> >
> >        You could bring back your lookup from the earlier patch and look
> >     through the result to see if the function we're complaining about is
> >     part of what the particular using-decl brings in.
> >
> >
> > I will give that a try.
> >
> >     I think you want
> >     SAME_BINFO_TYPE_P (context_for_name_lookup (decl),
> >                          BINFO_TYPE (parent_binfo))
> >
> >
> > Am I reading this wrong then? :
> >
> > /* Compare a BINFO_TYPE with another type for equality.
> > For a binfo, this is functionally equivalent to using same_type_p, but
> > measurably faster.
> >   At least one of the arguments must be a BINFO_TYPE.  The other can be
> > a BINFO_TYPE
> > or a regular type.  If BINFO_TYPE(T) ever stops being the main variant
> > of the class the
> > binfo is for, this macro must change.  */
> > #define SAME_BINFO_TYPE_P(A, B) ((A) == (B))
> >
> > That leads me to believe that arguments A and B can be: BINFO, BINFO ...
> > or BINFO, TYPE ... or TYPE, BINFO. If so I don't see why this doesn't work:
> >
> > (SAME_BINFO_TYPE_P (context_for_name_lookup (decl), parent_binfo))
> > and
> > (SAME_BINFO_TYPE_P (TYPE_BINFO (context_for_name_lookup (decl)),
> > parent_binfo))
> >
> > Unless "BINFO_TYPE" is actually just how you refer to a regular class
> > type and not a BINFO. Seems a bit confusing to me.
>
> The comment is pretty conusing, I agree.  But what it's saying is that
> both arguments must be types, and one of those types must be BINFO_TYPE
> (some_binfo).
>
> Your first line doesn't work because you're comparing a type and a
> binfo.  The second one doesn't work because you're comparing the binfo
> for a most-derived object of the type to the binfo for a base subobject
> of the same type, and those are different, because binfos represent
> nodes in inheritance graphs.
>
> >     This line needs one more space.
> >
> >
> > Oh I see ... so second line's arguments need to line up with the first
> > argument, not the first (.
> >
> > I will send over a new patch later/tomorrow.
> >
> > Anthony
> >
> > On Tue, 9 Feb 2021 at 04:55, Jason Merrill <jason@redhat.com
> > <mailto:jason@redhat.com>> wrote:
> >
> >     On 2/5/21 12:28 PM, Anthony Sharp wrote:
> >      > Hi Marek,
> >      >
> >      >>> +      if ((TREE_CODE (parent_field) == USING_DECL)
> >      >>
> >      >> This first term doesn't need to be wrapped in ().
> >      >
> >      > I normally wouldn't use the (), but I think that's how Jason likes it
> >      > so that's why I did it. I guess it makes it more readable.
> >
> >     Ah, no, I don't see any need for the extra () there.  When I asked for
> >     added parens previously:
> >
> >      >> +       if (parent_binfo != NULL_TREE
> >      >> +           && context_for_name_lookup (decl)
> >      >> +           != BINFO_TYPE (parent_binfo))
> >      >
> >      > Here you want parens around the second != expression and its != token
> >      > aligned with "context"
> >
> >     The parens are to help the editor line up the last line properly.  If
> >     the subexpression fits on one line, the parens aren't needed.
> >
> >      >> We usually use dg-do compile.
> >      >
> >      > True, but wouldn't that technically be slower? I would agree if it
> >      > were more than just error-handling code.
> >
> >     No; "compile" means translate from C++ to assembly, "assemble" means
> >     that, plus call the assembler to produce an object file.  Though since
> >     compilation errors out, assembling never happens.
> >
> >      > +      if ((TREE_CODE (parent_field) == USING_DECL)
> >      > +        && TREE_PRIVATE (parent_field)
> >      > +        && (DECL_NAME (decl) == DECL_NAME (parent_field)))
> >      > +       return parent_field;
> >
> >     Following our discussion about an earlier revision, this will often
> >     produce the right using-declaration, but might not if two functions of
> >     the same name are imported from different bases.  If I split the
> >     using-declaration in using9.C into two, i.e.
> >
> >      >   using A2::i; // { dg-message "declared" }
> >      >   using A::i;
> >
> >     then I get
> >
> >      > wa.ii: In member function ‘void C::f()’:
> >      > wa.ii:28:7: error: ‘int A::i()’ is private within this context
> >      >    28 |     i();
> >      >       |       ^
> >      > wa.ii:20:13: note: declared private here
> >      >    20 |   using A2::i;
> >
> >     pointing out the wrong using-declaration because it happens to be
> >     first.
> >        You could bring back your lookup from the earlier patch and look
> >     through the result to see if the function we're complaining about is
> >     part of what the particular using-decl brings in.
> >
> >      > I tried both:
> >      > (SAME_BINFO_TYPE_P (context_for_name_lookup (decl), parent_binfo))
> >      > and
> >      > (SAME_BINFO_TYPE_P (TYPE_BINFO (context_for_name_lookup (decl)),
> >     parent_binfo))
> >
> >     I think you want
> >
> >     SAME_BINFO_TYPE_P (context_for_name_lookup (decl),
> >                          BINFO_TYPE (parent_binfo))
> >
> >     i.e. just change same_type_p to SAME_BINFO_TYPE_P.
> >
> >      >           tree parent_binfo = get_parent_with_private_access (decl,
> >      > -
> >       basetype_path);
> >      > +
> >     basetype_path);
> >
> >     This line was indented properly before, and is changed to be indented
> >     one space too far.
> >
> >      > +             diag_location = get_class_access_diagnostic_decl
> >     (parent_binfo,
> >      > +
> >     diag_decl);
> >
> >     This line needs one more space.
> >
> >      >           complain_about_access (decl, diag_decl, diag_location,
> >     true,
> >      > -                                parent_access);
> >      > +                               access_failure_reason);
> >
> >     This line, too.
> >
> >      > +};
> >      > \ No newline at end of file
> >
> >     Let's add a newline.
> >
> >     Jason
> >
>
/* { dg-do compile } */
// Created for c++ PR19377

class A1
{
  protected:
  int separate();
  int comma();
};

class A2
{
  protected:
  int separate(int a);
  int comma(int a);
  int alone;
};

class B:private A1, private A2
{
  // Using decls in a comma-separated list.
  using A2::comma, A1::comma;  // { dg-message "declared" }
  // Separate using statements.
  using A2::separate;
  using A1::separate; // { dg-message "declared" }
  // No ambiguity, just for the sake of it.
  using A2::alone;
};

class C:public B
{
  void f()
  {
    comma(); // { dg-error "private" }
    separate(); // { dg-error "private" }
    alone = 5; // { dg-error "private" }
  }
};







/* This function may seem dumb, but actually it is possible for two decls that
   are equal to really be slightly different.  For example, they might be
   declared in different places, such as is the case when a using statement
   inherits a decl.
   
   Scan the fields of BINFO for an exact match of DECL.  If found, return
   DECL.  Otherwise, return NULL_TREE.  DECL is really the type "tree". */

tree
lookup_decl_exact_match(tree binfo, void *decl)
{
  for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (binfo));
      parent_field;
      parent_field = DECL_CHAIN (parent_field))
    {
      if(cp_tree_equal ((tree)decl, parent_field))
        return parent_field;
    }
    
  return NULL_TREE;
}





/* Return the decl that DECL (which is a non-stripped using decl) inherits.
   DECL is a member of DECLS_CLASS, which is a BINFO.  On failure, return
   NULL_TREE.  */

tree
find_decl_using_decl_inherits (tree decl, tree decls_class)
{  
  /* Strip DECL to extract the actual decl.  Contrary to the name, it only
     returns 1 decl.  */
  tree decl_stripped = strip_using_decl (decl);

  /* The only way to find the decl is to walk all the members of all the
     bases.  */
  tree result = dfs_walk_once (decls_class, lookup_decl_exact_match, NULL,
			       decl_stripped);

  /*  If nothing found.  */
  if (!result)
    return NULL_TREE;
    
  return result;
}








/* Called from enforce_access.  A class has attempted (but failed) to access
   DECL.  It is already established that a baseclass of that class,
   PARENT_BINFO, has private access to DECL.  Examine certain sepcial cases
   to find the decl that is the source of the problem.  If no special cases
   are found, simply return DECL.  */

static tree
get_class_access_diagnostic_decl (tree parent_binfo, tree decl)
{
  /* When a class is denied access to a decl in a baseclass, most of the
     time it is because the decl itself was declared as private at the point
     of declaration.  So, by default, DECL is at fault.

     However, in C++, there are (at least) two situations in which a decl
     can be private even though it was not originally defined as such.  */

  /* These two situations only apply if a baseclass had private access to
     DECL (this function is only called if that is the case).  We must however
     also ensure that the reason the parent had private access wasn't simply
     because it was declared as private in that parent.  */
  if (SAME_BINFO_TYPE_P (context_for_name_lookup (decl),
  			 BINFO_TYPE (parent_binfo)))
    return decl;

  /* 1.  If the "using" keyword is used to inherit DECL within a baseclass,
     this may cause DECL to be private, so we return the using statement as
     the source of the problem.

     Scan the fields of PARENT_BINFO and see if there are any using decls.  If
     there are, see if they inherit DECL.  If they do, that's where DECL must
     have been declared private.  */
  for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (parent_binfo));
      parent_field;
      parent_field = DECL_CHAIN (parent_field))
    {
      if (TREE_CODE (parent_field) == USING_DECL
	  && TREE_PRIVATE (parent_field))
	{
	  /* Find what this using decl inherited.  */
	  tree original_decl =
	    find_decl_using_decl_inherits (parent_field, parent_binfo);

	  /* If parent_field inherits DECL, it is the source of the problem,
	     so return it.  */
	  if (cp_tree_equal(original_decl, decl))
	    return parent_field;
	}
    }

  /* 2.  If decl was privately inherited by a baseclass of the current class,
     then decl will be inaccessible, even though it may originally have
     been accessible to deriving classes.  In that case, the fault lies with
     the baseclass that used a private inheritance, so we return the
     baseclass type as the source of the problem.

     Since this is the last check, we just assume it's true.  */
  return TYPE_NAME (BINFO_TYPE (parent_binfo));
}







/* If the current scope isn't allowed to access DECL along
   BASETYPE_PATH, give an error, or if we're parsing a function or class
   template, defer the access check to be performed at instantiation time.
   The most derived class in BASETYPE_PATH is the one used to qualify DECL.
   DIAG_DECL is the declaration to use in the error diagnostic.  */

static bool
enforce_access (tree basetype_path, tree decl, tree diag_decl,
		tsubst_flags_t complain, access_failure_info *afi = NULL)
{
  gcc_assert (TREE_CODE (basetype_path) == TREE_BINFO);

  if (flag_new_inheriting_ctors
      && DECL_INHERITED_CTOR (decl))
    {
      /* 7.3.3/18: The additional constructors are accessible if they would be
	 accessible when used to construct an object of the corresponding base
	 class.  */
      decl = strip_inheriting_ctors (decl);
      basetype_path = lookup_base (basetype_path, DECL_CONTEXT (decl),
				   ba_any, NULL, complain);
    }

  tree cs = current_scope ();
  if (processing_template_decl
      && (CLASS_TYPE_P (cs) || TREE_CODE (cs) == FUNCTION_DECL))
    if (tree template_info = get_template_info (cs))
      {
	/* When parsing a function or class template, we in general need to
	   defer access checks until template instantiation time, since a friend
	   declaration may grant access only to a particular specialization of
	   the template.  */

	if (accessible_p (basetype_path, decl, /*consider_local_p=*/true))
	  /* But if the member is deemed accessible at parse time, then we can
	     assume it'll be accessible at instantiation time.  */
	  return true;

	/* Access of a dependent decl should be rechecked after tsubst'ing
	   into the user of the decl, rather than explicitly deferring the
	   check here.  */
	gcc_assert (!uses_template_parms (decl));
	if (TREE_CODE (decl) == FIELD_DECL)
	  gcc_assert (!uses_template_parms (DECL_CONTEXT (decl)));

	/* Defer this access check until instantiation time.  */
	deferred_access_check access_check;
	access_check.binfo = basetype_path;
	access_check.decl = decl;
	access_check.diag_decl = diag_decl;
	access_check.loc = input_location;
	vec_safe_push (TI_DEFERRED_ACCESS_CHECKS (template_info), access_check);
	return true;
      }

  if (!accessible_p (basetype_path, decl, /*consider_local_p=*/true))
    {
      if (flag_new_inheriting_ctors)
	diag_decl = strip_inheriting_ctors (diag_decl);
      if (complain & tf_error)
	{
	  access_kind access_failure_reason = ak_none;

	  /* By default, using the decl as the source of the problem will
	     usually give correct results.  */
	  tree diag_location = diag_decl;

	  /* However, if a parent of BASETYPE_PATH had private access to decl,
	     then it actually might be the case that the source of the problem
	     is not DECL.  */
	  tree parent_binfo = get_parent_with_private_access (decl,
							      basetype_path);

	  /* So if a parent did have private access, then we need to do
	     special checks to obtain the best diagnostic location decl.  */
	  if (parent_binfo != NULL_TREE)
	    {
	      diag_location = get_class_access_diagnostic_decl (parent_binfo,
							        diag_decl);

	      /* We also at this point know that the reason access failed was
		 because decl was private.  */
	      access_failure_reason = ak_private;
	    }

	  /* Finally, generate an error message.  */
	  complain_about_access (decl, diag_decl, diag_location, true,
				 access_failure_reason);
	}
      if (afi)
	afi->record_access_failure (basetype_path, decl, diag_decl);
      return false;
    }

  return true;
}
Anthony Sharp Feb. 15, 2021, 12:53 p.m. UTC | #16
Scan the fields of BINFO for an exact match of DECL.  If found, return
   DECL.  Otherwise, return NULL_TREE.  DECL is really the type "tree". */

Should say

Scan the fields of BINFO for an exact match of DECL.  If found, return
   the field.  Otherwise, return NULL_TREE.  DECL is really the type "tree". */

On Mon, 15 Feb 2021 at 12:45, Anthony Sharp <anthonysharp15@gmail.com> wrote:
>
> Hi all,
>
> This overloaded functions issue has been a pain in the ass but I have
> found a solution that seems to work. I could really do with some
> feedback on it though since I'm not sure if there's a better way to do
> it that I am missing.
>
> Here's the problem. When a using statement causes an access failure,
> we need to find out WHICH using statement caused the access failure
> (this information is not given to enforce_access; it merely gets the
> ORIGINAL decl [i.e. the one that the using statement inherited]). To
> make matters worse, a USING_DECL does not seem to store any
> information about where it "came from", e.g. there's no ORIGINAL_DECL
> (USING_DECL) macro or anything like that. DECL_INITIAL doesn't seem to
> help (and is probably totally not related to the issue).
>
> So we need to do a long-winded lookup.
>
> First, we iterate through the USING_DECLs in the class where access
> failed (in the context of a parent that has caused the access failure
> because it has private access). For normal field variables, we COULD
> simply do a name lookup on the USING_DECL and compare it to the
> original DECL. That would be easy, since there can only be one
> variable field with the same name.
>
> However, that doesn't work for overloaded functions. Name lookup would
> return multiple results, making comparison meaningless.
>
> What we need is therefore not a name lookup, but a decl lookup. It
> sounds stupid, because what that basically means is looking for an
> exact match of a decl, which is intuitively stupid, because why would
> you look for an exact match of something you already have? But
> actually we can take advantage of a quirk that USING_DECLs have, which
> is that, when stripped, cp_tree_equal (stripped_using_decl,
> original_decl) returns true, even through stripped_using_decl and
> original_decl exist on different lines and in different places. In
> other words, a USING_DECL is the same as the original DECL it
> inherits, even though they are in different places (Unless I am just
> being really dumb?).
>
> Anyways, to summarise ...
>
> 1) We iterate through USING_DECLs in the class that caused a private
> access failure.
> 2) For each USING_DECL, we find the DECL that USING_DECL inherited.
>   2.1) To do this, we iterate through all fields of all base classes
> (possibly slow? but it is just diagnostics code afterall,
>          so idk if that's a big deal)
>   2.2)  We compare each FIELD against the USING_DECL. if equal, then
> we return FIELD.
> 3) if the DECL that USING_DECL inherited is equal to the diagnostic
> decl we were given in enforce_access, we return USING_DECL as being
> the source of the problem.
>
> In a perfect world, I guess the USING_DECL would store somewhere what
> it inherited as it was parsed. But I'm not sure if that's practical to
> do and I'm not sure it would be worth the effort considering it would
> probably be used only for this niche scenario. Donno.
>
> I have not regression tested this, but it does seem to work on the
> test case at least (which I've also included). Also please ignore
> formatting issues ATM.
>
> If you think this is a reasonable way to do it then I will tidy up the
> code, test it and make a patch and send it over. If anyone has any
> better ideas (probably), then please let me know. I did try the name
> lookup as Jason suggested but, as I say, in the case of overloaded
> functions it returns multiple values, so it didn't help in determining
> what DECL a USING_DECL inherited.
>
> BTW, I will eventually put find_decl_using_decl_inherits and
> lookup_decl_exact_match in search.c.
>
> Just for proof, here's the output from the testcase (hopefully it
> formats this correctly):
>
> /home/anthony/Desktop/using9.C: In member function ‘void C::f()’:
> /home/anthony/Desktop/using9.C:34:11: error: ‘int A1::comma()’ is
> private within this context
>    34 |     comma();
>       |                   ^
> /home/anthony/Desktop/using9.C:22:24: note: declared private here
>    22 |   using A2::comma, A1::comma;  // { dg-message "declared" }
>       |                                   ^~~~~
> /home/anthony/Desktop/using9.C:35:14: error: ‘int A1::separate()’ is
> private within this context
>    35 |     separate(); // { dg-error "private" }
>       |                     ^
> /home/anthony/Desktop/using9.C:25:13: note: declared private here
>    25 |   using A1::separate; // { dg-message "declared" }
>       |                      ^~~~~~~~
> /home/anthony/Desktop/using9.C:36:5: error: ‘int A2::alone’ is private
> within this context
>    36 |     alone = 5; // { dg-error "private" }
>       |       ^~~~~
> /home/anthony/Desktop/using9.C:27:13: note: declared private here
>    27 |   using A2::alone;
>       |                    ^~~~~
>
> Actual code attached.
>
> Anthony
>
>
> On Tue, 9 Feb 2021 at 20:07, Jason Merrill <jason@redhat.com> wrote:
> >
> > On 2/9/21 6:18 AM, Anthony Sharp wrote:
> > >     The parens are to help the editor line up the last line properly.  If
> > >     the subexpression fits on one line, the parens aren't needed.
> > >
> > >
> > > Ahhhh okay.
> > >
> > >     No; "compile" means translate from C++ to assembly, "assemble" means
> > >     that, plus call the assembler to produce an object file.  Though since
> > >     compilation errors out, assembling never happens.
> > >
> > >
> > > lol I was being dumb and thinking it was the other way around. I will
> > > change it.
> > >
> > >        You could bring back your lookup from the earlier patch and look
> > >     through the result to see if the function we're complaining about is
> > >     part of what the particular using-decl brings in.
> > >
> > >
> > > I will give that a try.
> > >
> > >     I think you want
> > >     SAME_BINFO_TYPE_P (context_for_name_lookup (decl),
> > >                          BINFO_TYPE (parent_binfo))
> > >
> > >
> > > Am I reading this wrong then? :
> > >
> > > /* Compare a BINFO_TYPE with another type for equality.
> > > For a binfo, this is functionally equivalent to using same_type_p, but
> > > measurably faster.
> > >   At least one of the arguments must be a BINFO_TYPE.  The other can be
> > > a BINFO_TYPE
> > > or a regular type.  If BINFO_TYPE(T) ever stops being the main variant
> > > of the class the
> > > binfo is for, this macro must change.  */
> > > #define SAME_BINFO_TYPE_P(A, B) ((A) == (B))
> > >
> > > That leads me to believe that arguments A and B can be: BINFO, BINFO ...
> > > or BINFO, TYPE ... or TYPE, BINFO. If so I don't see why this doesn't work:
> > >
> > > (SAME_BINFO_TYPE_P (context_for_name_lookup (decl), parent_binfo))
> > > and
> > > (SAME_BINFO_TYPE_P (TYPE_BINFO (context_for_name_lookup (decl)),
> > > parent_binfo))
> > >
> > > Unless "BINFO_TYPE" is actually just how you refer to a regular class
> > > type and not a BINFO. Seems a bit confusing to me.
> >
> > The comment is pretty conusing, I agree.  But what it's saying is that
> > both arguments must be types, and one of those types must be BINFO_TYPE
> > (some_binfo).
> >
> > Your first line doesn't work because you're comparing a type and a
> > binfo.  The second one doesn't work because you're comparing the binfo
> > for a most-derived object of the type to the binfo for a base subobject
> > of the same type, and those are different, because binfos represent
> > nodes in inheritance graphs.
> >
> > >     This line needs one more space.
> > >
> > >
> > > Oh I see ... so second line's arguments need to line up with the first
> > > argument, not the first (.
> > >
> > > I will send over a new patch later/tomorrow.
> > >
> > > Anthony
> > >
> > > On Tue, 9 Feb 2021 at 04:55, Jason Merrill <jason@redhat.com
> > > <mailto:jason@redhat.com>> wrote:
> > >
> > >     On 2/5/21 12:28 PM, Anthony Sharp wrote:
> > >      > Hi Marek,
> > >      >
> > >      >>> +      if ((TREE_CODE (parent_field) == USING_DECL)
> > >      >>
> > >      >> This first term doesn't need to be wrapped in ().
> > >      >
> > >      > I normally wouldn't use the (), but I think that's how Jason likes it
> > >      > so that's why I did it. I guess it makes it more readable.
> > >
> > >     Ah, no, I don't see any need for the extra () there.  When I asked for
> > >     added parens previously:
> > >
> > >      >> +       if (parent_binfo != NULL_TREE
> > >      >> +           && context_for_name_lookup (decl)
> > >      >> +           != BINFO_TYPE (parent_binfo))
> > >      >
> > >      > Here you want parens around the second != expression and its != token
> > >      > aligned with "context"
> > >
> > >     The parens are to help the editor line up the last line properly.  If
> > >     the subexpression fits on one line, the parens aren't needed.
> > >
> > >      >> We usually use dg-do compile.
> > >      >
> > >      > True, but wouldn't that technically be slower? I would agree if it
> > >      > were more than just error-handling code.
> > >
> > >     No; "compile" means translate from C++ to assembly, "assemble" means
> > >     that, plus call the assembler to produce an object file.  Though since
> > >     compilation errors out, assembling never happens.
> > >
> > >      > +      if ((TREE_CODE (parent_field) == USING_DECL)
> > >      > +        && TREE_PRIVATE (parent_field)
> > >      > +        && (DECL_NAME (decl) == DECL_NAME (parent_field)))
> > >      > +       return parent_field;
> > >
> > >     Following our discussion about an earlier revision, this will often
> > >     produce the right using-declaration, but might not if two functions of
> > >     the same name are imported from different bases.  If I split the
> > >     using-declaration in using9.C into two, i.e.
> > >
> > >      >   using A2::i; // { dg-message "declared" }
> > >      >   using A::i;
> > >
> > >     then I get
> > >
> > >      > wa.ii: In member function ‘void C::f()’:
> > >      > wa.ii:28:7: error: ‘int A::i()’ is private within this context
> > >      >    28 |     i();
> > >      >       |       ^
> > >      > wa.ii:20:13: note: declared private here
> > >      >    20 |   using A2::i;
> > >
> > >     pointing out the wrong using-declaration because it happens to be
> > >     first.
> > >        You could bring back your lookup from the earlier patch and look
> > >     through the result to see if the function we're complaining about is
> > >     part of what the particular using-decl brings in.
> > >
> > >      > I tried both:
> > >      > (SAME_BINFO_TYPE_P (context_for_name_lookup (decl), parent_binfo))
> > >      > and
> > >      > (SAME_BINFO_TYPE_P (TYPE_BINFO (context_for_name_lookup (decl)),
> > >     parent_binfo))
> > >
> > >     I think you want
> > >
> > >     SAME_BINFO_TYPE_P (context_for_name_lookup (decl),
> > >                          BINFO_TYPE (parent_binfo))
> > >
> > >     i.e. just change same_type_p to SAME_BINFO_TYPE_P.
> > >
> > >      >           tree parent_binfo = get_parent_with_private_access (decl,
> > >      > -
> > >       basetype_path);
> > >      > +
> > >     basetype_path);
> > >
> > >     This line was indented properly before, and is changed to be indented
> > >     one space too far.
> > >
> > >      > +             diag_location = get_class_access_diagnostic_decl
> > >     (parent_binfo,
> > >      > +
> > >     diag_decl);
> > >
> > >     This line needs one more space.
> > >
> > >      >           complain_about_access (decl, diag_decl, diag_location,
> > >     true,
> > >      > -                                parent_access);
> > >      > +                               access_failure_reason);
> > >
> > >     This line, too.
> > >
> > >      > +};
> > >      > \ No newline at end of file
> > >
> > >     Let's add a newline.
> > >
> > >     Jason
> > >
> >
Jason Merrill Feb. 23, 2021, 3:11 a.m. UTC | #17
On 2/15/21 7:45 AM, Anthony Sharp wrote:
>     PARENT_BINFO, has private access to DECL.  Examine certain sepcial cases

"special"

> I did try the name
> lookup as Jason suggested but, as I say, in the case of overloaded
> functions it returns multiple values, so it didn't help in determining
> what DECL a USING_DECL inherited.

I was thinking you could walk through the overload set to see if it 
contains DECL.

Jason
Anthony Sharp Feb. 24, 2021, 9:17 p.m. UTC | #18
> "special"


It wouldn't be my code if it didn't have sp3ling mstakes innit!
Actually to be fair I already changed that spelling mistake a few days
ago in my local code ;)

I was actually thinking about this last night as I was falling asleep
(as you do) and I realised that the whole of my using decl lookup is
redundant. I can simply do this (formatting probably messes up here):

/* 1.  If the "using" keyword is used to inherit DECL within the parent,
     this may cause DECL to be private, so we should return the using
     statement as the source of the problem.

     Scan the fields of PARENT_BINFO and see if there are any using decls.  If
     there are, see if they inherit DECL.  If they do, that's where DECL must
     have been declared private.  */

  for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (parent_binfo));
       parent_field;
       parent_field = DECL_CHAIN (parent_field))
    {
      /* Not necessary, but also check TREE_PRIVATE for the sake of
      eliminating obviously non-relevant using decls.  */
      if (TREE_CODE (parent_field) == USING_DECL
 && TREE_PRIVATE (parent_field))
{
/* If the using statement inherits DECL, it is the source of the
 access failure, so return it.  */
 if (cp_tree_equal (strip_using_decl (parent_field), decl))
   return parent_field;
}
    }

I was wrong to say that the using decl does not store "where it came
from/what it inherits" - that's exactly what strip_using_decl
achieves. I think the problem was that when I did my initial testing
in trying out ways to get the original decl, I didn't strip it, so the
comparison failed, which led me to make the whole redundant lookup,
blah blah blah.

I've run a quick test and it seems to work, even with the overloads.

Will test it some more and if all's good I will probably send a new
patch some time this weekend.

> I was thinking you could walk through the overload set to see if it
> contains DECL.

I did try that ... sort of. I did a name lookup on the using decl and
that returned a baselink (no idea why, since the lookup function says
it returns a tree list [probably me being dumb]), which then gave me a
bunch of overloads. But that didn't seem to help since if multiple
using decls give me the answer I'm looking for (a match for DECL)
because they were overloaded, then there was no way for me to tell
which using decl was actually the correct one. Kind of like if three
cakes are equally as tasty, then how are you supposed to tell which
one is the most delicious?

Anthony
Jason Merrill Feb. 25, 2021, 3:37 a.m. UTC | #19
On 2/24/21 4:17 PM, Anthony Sharp wrote:
>> "special"
> 
> 
> It wouldn't be my code if it didn't have sp3ling mstakes innit!
> Actually to be fair I already changed that spelling mistake a few days
> ago in my local code ;)
> 
> I was actually thinking about this last night as I was falling asleep
> (as you do) and I realised that the whole of my using decl lookup is
> redundant. I can simply do this (formatting probably messes up here):
> 
> /* 1.  If the "using" keyword is used to inherit DECL within the parent,
>       this may cause DECL to be private, so we should return the using
>       statement as the source of the problem.
> 
>       Scan the fields of PARENT_BINFO and see if there are any using decls.  If
>       there are, see if they inherit DECL.  If they do, that's where DECL must
>       have been declared private.  */
> 
>    for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (parent_binfo));
>         parent_field;
>         parent_field = DECL_CHAIN (parent_field))
>      {
>        /* Not necessary, but also check TREE_PRIVATE for the sake of
>        eliminating obviously non-relevant using decls.  */
>        if (TREE_CODE (parent_field) == USING_DECL
>   && TREE_PRIVATE (parent_field))
> {
> /* If the using statement inherits DECL, it is the source of the
>   access failure, so return it.  */
>   if (cp_tree_equal (strip_using_decl (parent_field), decl))
>     return parent_field;
> }
>      }
> 
> I was wrong to say that the using decl does not store "where it came
> from/what it inherits" - that's exactly what strip_using_decl
> achieves. I think the problem was that when I did my initial testing
> in trying out ways to get the original decl, I didn't strip it, so the
> comparison failed, which led me to make the whole redundant lookup,
> blah blah blah.
> 
> I've run a quick test and it seems to work, even with the overloads.
> 
> Will test it some more and if all's good I will probably send a new
> patch some time this weekend.

Sounds good, though strip_using_decl (parent_field) may be overloaded if 
the using-decl brings in multiple functions with that name.

Jason
Anthony Sharp March 1, 2021, 10:11 p.m. UTC | #20
Hi all,

Here is the patch as promised. Regression tested on the c++ side and
everything seems okay. Compiles fine.

Sounds good, though strip_using_decl (parent_field) may be overloaded if
> the using-decl brings in multiple functions with that name.


Could you give my new regression test a quick glance over then just to make
sure I've not forgotten about something? It definitely seems to work but
I'm no expert in all the different ways using statements can be
constructed. If you were to use 'using comma' (in the testcase), it
generates another error because it's ambiguous, so that's okay. And if you
use a comma-separated list like I have in the test (i.e. using A2::comma,
A1::comma) it seems to find the correct bits just fine. Unless I'm getting
really lucky and it's actually just a coincidence.

It seems that strip_using_decl doesn't return an overloaded list. Or, if it
does, the first entry in that list just so happens to always be the right
answer, so it can be treated like it's a regular decl for this purpose. For
example, even if we change up the order of baseclasses, using statements
and class definitions, it still seems to work, e.g. the following *seems*
to work just fine:

class A2
{
  protected:
  int separate(int a);
  int comma(int a);
  int alone;
};

class A1
{
  protected:
  int separate();
  int comma();
};

class A3
{
  protected:
  int comma(int a, int b);
};

class B:private A3, private A1, private A2
{
  // Using decls in a comma-separated list.
  using A2::comma, A3::comma, A1::comma;  // { dg-message "declared" }
  // Separate using statements.
  using A2::separate;
  using A1::separate; // { dg-message "declared" }
  // No ambiguity, just for the sake of it.
  using A2::alone; // { dg-message "declared" }
};

class C:public B
{
  void f()
  {
    comma(); // { dg-error "private" }
    separate(); // { dg-error "private" }
    alone = 5; // { dg-error "private" }
  }
};

Anthony


On Thu, 25 Feb 2021 at 03:37, Jason Merrill <jason@redhat.com> wrote:

> On 2/24/21 4:17 PM, Anthony Sharp wrote:
> >> "special"
> >
> >
> > It wouldn't be my code if it didn't have sp3ling mstakes innit!
> > Actually to be fair I already changed that spelling mistake a few days
> > ago in my local code ;)
> >
> > I was actually thinking about this last night as I was falling asleep
> > (as you do) and I realised that the whole of my using decl lookup is
> > redundant. I can simply do this (formatting probably messes up here):
> >
> > /* 1.  If the "using" keyword is used to inherit DECL within the parent,
> >       this may cause DECL to be private, so we should return the using
> >       statement as the source of the problem.
> >
> >       Scan the fields of PARENT_BINFO and see if there are any using
> decls.  If
> >       there are, see if they inherit DECL.  If they do, that's where
> DECL must
> >       have been declared private.  */
> >
> >    for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (parent_binfo));
> >         parent_field;
> >         parent_field = DECL_CHAIN (parent_field))
> >      {
> >        /* Not necessary, but also check TREE_PRIVATE for the sake of
> >        eliminating obviously non-relevant using decls.  */
> >        if (TREE_CODE (parent_field) == USING_DECL
> >   && TREE_PRIVATE (parent_field))
> > {
> > /* If the using statement inherits DECL, it is the source of the
> >   access failure, so return it.  */
> >   if (cp_tree_equal (strip_using_decl (parent_field), decl))
> >     return parent_field;
> > }
> >      }
> >
> > I was wrong to say that the using decl does not store "where it came
> > from/what it inherits" - that's exactly what strip_using_decl
> > achieves. I think the problem was that when I did my initial testing
> > in trying out ways to get the original decl, I didn't strip it, so the
> > comparison failed, which led me to make the whole redundant lookup,
> > blah blah blah.
> >
> > I've run a quick test and it seems to work, even with the overloads.
> >
> > Will test it some more and if all's good I will probably send a new
> > patch some time this weekend.
>
> Sounds good, though strip_using_decl (parent_field) may be overloaded if
> the using-decl brings in multiple functions with that name.
>
> Jason
>
>
Jason Merrill March 2, 2021, 9:34 p.m. UTC | #21
On 3/1/21 5:11 PM, Anthony Sharp wrote:
> Hi all,
> 
> Here is the patch as promised. Regression tested on the c++ side and 
> everything seems okay. Compiles fine.
> 
>     Sounds good, though strip_using_decl (parent_field) may be overloaded if
>     the using-decl brings in multiple functions with that name.
> 
> 
> Could you give my new regression test a quick glance over then just to 
> make sure I've not forgotten about something? It definitely seems to 
> work but I'm no expert in all the different ways using statements can be 
> constructed. If you were to use 'using comma' (in the testcase), it 
> generates another error because it's ambiguous, so that's okay. And if 
> you use a comma-separated list like I have in the test (i.e. using 
> A2::comma, A1::comma) it seems to find the correct bits just fine. 
> Unless I'm getting really lucky and it's actually just a coincidence.
> 
> It seems that strip_using_decl doesn't return an overloaded list. Or, if 
> it does, the first entry in that list just so happens to always be the 
> right answer, so it can be treated like it's a regular decl for this 
> purpose. For example, even if we change up the order of baseclasses, 
> using statements and class definitions, it still seems to work, e.g. the 
> following *seems* to work just fine:

That's because none of the names are overloaded within a single base 
class.  But if I add

> class A2
> {
>    protected:
>    int separate(int a);
>    int comma(int a);
>    int alone;
> };
> 
> class A1
> {
>    protected:
>    int separate();
      int separate(int,int,int);

then strip_using_decl for A1::separate gives an OVERLOAD.

You can iterate over the result of strip_using decl with the

         for (ovl_iterator iter (fns); iter; ++iter)
           {
             tree fn = *iter;

pattern.

Also, you can use == instead of cp_tree_equal for comparing FUNCTION_DECLs.

Jason
Anthony Sharp March 10, 2021, 9:14 p.m. UTC | #22
Hiya

> That's because none of the names are overloaded within a single base
> class.

Ah, thanks. Thought there must be something I wasn't thinking of.

> Also, you can use == instead of cp_tree_equal for comparing FUNCTION_DECLs.

Changed it.

Latest patch is attached. Compiles fine and no regressions.

Anthony
Jason Merrill March 18, 2021, 9:10 p.m. UTC | #23
On 3/10/21 4:14 PM, Anthony Sharp wrote:
> Hiya
> 
>> That's because none of the names are overloaded within a single base
>> class.
> 
> Ah, thanks. Thought there must be something I wasn't thinking of.
> 
>> Also, you can use == instead of cp_tree_equal for comparing FUNCTION_DECLs.
> 
> Changed it.
> 
> Latest patch is attached. Compiles fine and no regressions.

Great!  You may have already noticed that I applied the patch with a 
little simplification: we can use ovl_iterator for non-overloaded decls 
as well.

Thanks,
Jason
diff mbox series

Patch

From e064f8d010baee288c47cce1981be80515101f0d Mon Sep 17 00:00:00 2001
From: Anthony Sharp <anthonysharp15@gmail.com>
Date: Thu, 4 Feb 2021 12:01:59 +0000
Subject: [PATCH] c++: Check for using decl in private parent access [PR19377]

This bug was already mostly fixed by the patch for PR17314. This
patch continues that by ensuring that where a using decl is used,
causing an access failure to a child class because the using decl is
private, the compiler correctly points to the using decl as the
source of the problem.

Checks for the use of using decls in a parent that had
private access to decl (but the child had no access) in order
to ascertain the best diagnostic decl location.

gcc/cp/ChangeLog:

	* semantics.c (get_class_access_diagnostic_decl): New function.
	(enforce_access): Altered function.

gcc/testsuite/ChangeLog:

	* g++.dg/pr19377.C: New test.
---
 gcc/cp/semantics.c             | 93 +++++++++++++++++++++++++++-------
 gcc/testsuite/g++.dg/pr19377.C | 21 ++++++++
 2 files changed, 95 insertions(+), 19 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/pr19377.C

diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
index 73834467fca..6d4ef683d65 100644
--- a/gcc/cp/semantics.c
+++ b/gcc/cp/semantics.c
@@ -256,6 +256,62 @@  pop_to_parent_deferring_access_checks (void)
     }
 }
 
+/* Called from enforce_access.  A class has attempted (but failed) to access
+   DECL.  It is already established that a baseclass of that class,
+   PARENT_BINFO, has private access to DECL.  Examine certain special cases to
+   generate a diagnostic decl location.  If no special cases are found, simply
+   return DECL.  */
+
+static tree
+get_class_access_diagnostic_decl (tree parent_binfo, tree decl)
+{
+  /* When a class is denied access to a decl in a baseclass, most of the
+     time it is because the decl itself was declared as private at the point
+     of declaration.  So, by default, DECL is at fault.
+
+     However, in C++, there are (at least) two situations in which a decl
+     can be private even though it was not originally defined as such.  */
+
+  /* These two situations only apply if a baseclass had private access to
+     DECL (this function is only called if that is the case).  We must however
+     also ensure that the reason the parent had private access wasn't simply
+     because it was declared as private in the parent.  */
+  if (context_for_name_lookup (decl) == BINFO_TYPE (parent_binfo))
+    return decl;
+
+  /* 1.  If the "using" keyword is used to inherit DECL within a baseclass,
+     this may cause DECL to be private, so we return the using statement as
+     the source of the problem.
+
+     Scan the fields of PARENT_BINFO and see if there are any using decls.  If
+     there are, see if they inherit DECL.  If they do, that's where DECL was
+     truly declared private.  */
+  for (tree parent_field = TYPE_FIELDS (BINFO_TYPE (parent_binfo));
+      parent_field;
+      parent_field = DECL_CHAIN (parent_field))
+    {
+      if (TREE_CODE (parent_field) == USING_DECL)
+	{
+	  if (cp_tree_equal (decl,
+			     lookup_member (parent_binfo,
+					    DECL_NAME (parent_field),
+					    /*protect=*/0,
+					    /*want_type=*/false,
+					    tf_warning_or_error)))
+	    return parent_field;
+	}
+    }
+
+  /* 2.  If decl was privately inherited by a baseclass of the current class,
+     then decl will be inaccessible, even though it may originally have
+     been accessible to deriving classes.  In that case, the fault lies with
+     the baseclass that used a private inheritance, so we return the
+     baseclass type as the source of the problem.
+
+     Since this is the last check, we just assume it's true.  */
+  return TYPE_NAME (BINFO_TYPE (parent_binfo));
+}
+
 /* If the current scope isn't allowed to access DECL along
    BASETYPE_PATH, give an error, or if we're parsing a function or class
    template, defer the access check to be performed at instantiation time.
@@ -317,34 +373,33 @@  enforce_access (tree basetype_path, tree decl, tree diag_decl,
 	diag_decl = strip_inheriting_ctors (diag_decl);
       if (complain & tf_error)
 	{
-	  /* We will usually want to point to the same place as
-	     diag_decl but not always.  */
+	  access_kind access_failure_reason = ak_none;
+
+	  /* By default, using the decl as the source of the problem will
+	     usually give correct results.  */
 	  tree diag_location = diag_decl;
-	  access_kind parent_access = ak_none;
 
-	  /* See if any of BASETYPE_PATH's parents had private access
-	     to DECL.  If they did, that will tell us why we don't.  */
+	  /* However, if a parent of BASETYPE_PATH had private access to decl,
+	     then it actually might be the case that the source of the problem
+	     is not DECL.  */
 	  tree parent_binfo = get_parent_with_private_access (decl,
-							      basetype_path);
+							       basetype_path);
 
-	  /* If a parent had private access, then the diagnostic
-	     location DECL should be that of the parent class, since it
-	     failed to give suitable access by using a private
-	     inheritance.  But if DECL was actually defined in the parent,
-	     it wasn't privately inherited, and so we don't need to do
-	     this, and complain_about_access will figure out what to
-	     do.  */
-	  if (parent_binfo != NULL_TREE
-	      && (context_for_name_lookup (decl)
-		  != BINFO_TYPE (parent_binfo)))
+	  /* So if a parent did had private access, then we need to do special
+	     checks to obtain the best diagnostic location decl.  */
+	  if (parent_binfo != NULL_TREE)
 	    {
-	      diag_location = TYPE_NAME (BINFO_TYPE (parent_binfo));
-	      parent_access = ak_private;
+	      diag_location = get_class_access_diagnostic_decl (parent_binfo,
+								 diag_decl);
+
+	      /* We also at this point know that the reason access failed was
+		 because decl was private.  */
+		 access_failure_reason = ak_private;
 	    }
 
 	  /* Finally, generate an error message.  */
 	  complain_about_access (decl, diag_decl, diag_location, true,
-				 parent_access);
+				 access_failure_reason);
 	}
       if (afi)
 	afi->record_access_failure (basetype_path, decl, diag_decl);
diff --git a/gcc/testsuite/g++.dg/pr19377.C b/gcc/testsuite/g++.dg/pr19377.C
new file mode 100644
index 00000000000..356329801ac
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pr19377.C
@@ -0,0 +1,21 @@ 
+/* { dg-do assemble } */
+
+class A
+{
+  protected:
+  int i;
+};
+
+class B:public A
+{
+  private:
+  using A::i; // { dg-message "declared" }
+};
+
+class C:public B
+{
+  void f()
+  {
+    A::i = 0; // { dg-error "private" }
+  }
+};
\ No newline at end of file
-- 
2.25.1