This is an archive of the discontinued LLVM Phabricator instance.

Check a class doesn't have a dependent type before iterating over its base classes
Needs ReviewPublic

Authored by ahatanak on Apr 23 2020, 2:13 PM.

Details

Reviewers
rsmith
Summary

This fixes a crash when a class defined in a method of a templated class inherits from a class that is forward-declared.

Diff Detail

Event Timeline

ahatanak created this revision.Apr 23 2020, 2:13 PM

I'm not sure why this shouldn't be caught in Sema::CheckBaseSpecifier. But there is a check for the definition of the class before findCircularInheritance is called, so I'm guessing there is a reason the code shouldn't be rejected here.

We should reject a definition of a class that has a non-dependent base class that's an incomplete type. We need non-dependent bases to be complete in order to be able to do name lookup into them when processing the template definition. (The C++ standard permits us to reject such cases but does not require it, because "a hypothetical instantiation of [the templated class] immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter".)

Then there's a question of error recovery and AST invariants: should we permit a base specifier to name a non-dependent type that's not a complete class type? If so, we'll need to make sure that all code that iterates over base classes checks for this condition (I bet there are more cases than the two that you found). But tooling use cases probably do want to know that such base specifiers were written. So perhaps we should allow such base classes but mark the class definition as invalid if we find them -- in which case we would want something like this patch as part of the error recovery.

I think the non-dependent base class you mention in this case is B0, but it seems that the type of B0 in the AST is dependent as isDependentType returns true. Should we fix this? Or does isDependentType mean something different?

TagDecl::isDependentType() is returning true.

If so, we'll need to make sure that all code that iterates over base classes checks for this condition (I bet there are more cases than the two that you found).

Just a potentially related thought: I was recently trying to understand under what circumstances should I expect CXXBaseSpecifier::getType() to return QualType instance for which isNull() could be true. I gave up and don't make any assumption.
Would the suggested change affect this?

TagDecl::isDependentType() is returning true.

Oh, sorry, we do already do the thing I was suggesting. But in your testcase the issue is that the base class B0 is dependent but is also resolved during template instantiation to a (dependent) class type. We should handle this by skipping dependent base classes, not by skipping incomplete base classes. For example, consider this testcase (which currently asserts):

struct A {};
template<typename T> struct X {
  struct B : A {};
  struct C : A, B {};
};

here, we can resolve the B base of C to the member C of the current instantiation, but we should not traverse to B's base class list when considering the base classes of C, because we do not know that that definition of B will be used by any particular instantiation of C. For example:

template<> struct X<int>::B {};
X<int>::C c; // has only one `A` base class
ahatanak updated this revision to Diff 264992.May 19 2020, 12:21 PM
ahatanak retitled this revision from Check a class has a definition before iterating over its base classes to Check a class doesn't have a dependent type before iterating over its base classes.
ahatanak edited the summary of this revision. (Show Details)

Does the updated patch look okay?

ahatanak updated this revision to Diff 310728.Dec 9 2020, 5:31 PM

Rebase and ping.