c++: Fix access checking of scoped non-static member [PR98515]
authorPatrick Palka <ppalka@redhat.com>
Fri, 8 Jan 2021 15:02:04 +0000 (10:02 -0500)
committerPatrick Palka <ppalka@redhat.com>
Fri, 8 Jan 2021 15:02:04 +0000 (10:02 -0500)
In the first testcase below, we incorrectly reject the use of the
protected non-static member A::var0 from C<int>::g() because
check_accessibility_of_qualified_id, at template parse time, determines
that the access doesn't go through 'this'.  (This happens because the
dependent base B<T> of C<T> doesn't have a binfo object, so it appears
to DERIVED_FROM_P that A is not an indirect base of C<T>.)  From there
we create the corresponding deferred access check, which we then
perform at instantiation time and which (expectedly) fails.

The problem ultimately seems to be that we can't in general determine
whether a use of a scoped non-static member goes through 'this' until
instantiation time, as the second testcase below illustrates.  So this
patch makes check_accessibility_of_qualified_id punt in such situations
to avoid creating a bogus deferred access check.

gcc/cp/ChangeLog:

PR c++/98515
* semantics.c (check_accessibility_of_qualified_id): Punt if
we're checking access of a scoped non-static member inside a
class template.

gcc/testsuite/ChangeLog:

PR c++/98515
* g++.dg/template/access32.C: New test.
* g++.dg/template/access33.C: New test.

gcc/cp/semantics.c
gcc/testsuite/g++.dg/template/access32.C [new file with mode: 0644]
gcc/testsuite/g++.dg/template/access33.C [new file with mode: 0644]

index b448efe024af8d57500fddc1f6cb42271ef06784..c6b4c70dc0f0c3b719ec3fad1d9261a1c6dc1b41 100644 (file)
@@ -2107,14 +2107,24 @@ check_accessibility_of_qualified_id (tree decl,
       /* If the reference is to a non-static member of the
         current class, treat it as if it were referenced through
         `this'.  */
-      tree ct;
       if (DECL_NONSTATIC_MEMBER_P (decl)
-         && current_class_ptr
-         && DERIVED_FROM_P (scope, ct = current_nonlambda_class_type ()))
-       qualifying_type = ct;
+         && current_class_ptr)
+       if (tree current = current_nonlambda_class_type ())
+         {
+           if (dependent_type_p (current))
+           /* In general we can't know whether this access goes through
+              `this' until instantiation time.  Punt now, or else we might
+              create a deferred access check that's not relative to `this'
+              when it ought to be.  We'll check this access again after
+              substitution, e.g. from tsubst_qualified_id.  */
+             return true;
+
+           if (DERIVED_FROM_P (scope, current))
+             qualifying_type = current;
+         }
       /* Otherwise, use the type indicated by the
         nested-name-specifier.  */
-      else
+      if (!qualifying_type)
        qualifying_type = nested_name_specifier;
     }
   else
diff --git a/gcc/testsuite/g++.dg/template/access32.C b/gcc/testsuite/g++.dg/template/access32.C
new file mode 100644 (file)
index 0000000..08faa9f
--- /dev/null
@@ -0,0 +1,8 @@
+// PR c++/98515
+// { dg-do compile }
+
+struct A { protected: int var0; };
+template <class> struct B : public A { };
+template <class T> struct C : public B<T> { void g(); };
+template <class T> void C<T>::g() { A::var0++; }
+template class C<int>;
diff --git a/gcc/testsuite/g++.dg/template/access33.C b/gcc/testsuite/g++.dg/template/access33.C
new file mode 100644 (file)
index 0000000..9fb9b9a
--- /dev/null
@@ -0,0 +1,9 @@
+// PR c++/98515
+// { dg-do compile }
+
+struct A { protected: int var0; };
+template <class> struct B : public A { };
+template <class T> struct C : public B<T> { void g(); };
+template <class T> void C<T>::g() { A::var0++; } // { dg-error "protected|invalid" }
+template <> struct B<char> { };
+template class C<char>;