This is an archive of the discontinued LLVM Phabricator instance.

Handle use of default member initializers before end of outermost class
ClosedPublic

Authored by rnk on Oct 8 2014, 6:31 PM.

Details

Summary

Specifically, when we have this situation:

struct A {
  template <typename T> struct B {
    int m1 = sizeof(A);
  };
  B<int> m2;
};

We can't parse m1's initializer eagerly because we need A to be
complete. Therefore we wait until the end of A's class scope to parse
it. However, we can trigger instantiation of B before the end of A,
which will attempt to instantiate the field decls eagerly, and it would
build a bad field decl instantiation that said it had an initializer but
actually lacked one.

Fixed by doing template instantiation when building a CXXDefaultInitExpr
and erorring out if the initializer is missing for any other reason.
Fixes PR19195.

This does *not* defer all field instantiation until later, as that
causes assertions in the test suite. Fixing that might be the right
thing to do, but I wanted feedback on the approach so far first.

Diff Detail

Repository
rL LLVM

Event Timeline

rnk updated this revision to Diff 14620.Oct 8 2014, 6:31 PM
rnk retitled this revision from to Handle use of default member initializers before end of outermost class.
rnk updated this object.
rnk added a reviewer: rsmith.
rnk added a subscriber: Unknown Object (MLST).
rsmith added inline comments.Oct 9 2014, 4:52 PM
include/clang/Basic/DiagnosticSemaKinds.td
6189 ↗(On Diff #14620)

"default initialize" is the wrong term here; it means something else.

The existing diagnostic was specifically targeted at http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1397, and its wording was intended to explain what the relevant rule is. I'm not sure how best to update the diagnostic text, but this wording needs more work.

lib/Sema/SemaDeclCXX.cpp
8420–8435 ↗(On Diff #14620)

You can trigger the computation of the exception specification for a defaulted constructor without causing it to be defined (or at least, in principle; it looks like Clang doesn't get this right). If you do so from a default initializer, we're required to reject the program under DR1397, and we shouldn't just ignore the fact that there was a default initializer.

10979–10981 ↗(On Diff #14620)

This isn't right. You need to handle two cases:

  1. The class is an instantiation of a class template or partial specialization; cast to ClassTemplateSpecializationDecl and call getInstantiatedFrom().
  2. The class is a member of a class template (recursively), and is not a ClassTemplateSpecializationDecl. In this case, call getInstantiatedFromMemberClass to find the pattern.

In the first case, you may also need to walk from a member template or member partial specailization back to its pattern (and be careful to stop when you hit a member specialization). See hasVisibleDefinition in SemaType.cpp and FunctionDecl::getTemplateInstantiationPattern for examples of where we already do this.

It seems like a good idea to move this into a new CXXRecordDecl::getTemplateInstantiationPattern.

10992–10995 ↗(On Diff #14620)

You should walk over lexically-enclosing contexts, and stop when you hit the first context that's not a CXXRecordDecl. For a local class, surrounding classes don't matter, and for an out-of-line definition of a nested class, its containing class doesn't matter.

lib/Sema/SemaTemplateInstantiate.cpp
2033–2040 ↗(On Diff #14620)

Do we still set the 'this field has an initializer' flag somewhere?

2199 ↗(On Diff #14620)

I'd prefer for this function to have a name related to in-class initializers, since that's the specific thing it's dealing with.

2228–2235 ↗(On Diff #14620)

You should create an InstantiatingTemplate surrounding this for proper handling of instantiation backtraces. (Note that you're currently not using PointOfInstantiation.)

You also need a ContextRAII and LocalInstantiationScope here so that (for instance) lambdas within the initializer are created in the right DeclContext, and we perform proper access checking, and so on.

You should also EnterExpressionEvaluationContext to ensure we're in a PotentiallyEvaluated context (the instantiation could be triggered by an aggregate initialization in an unevaluated operand).

rnk added inline comments.Oct 14 2014, 3:03 PM
include/clang/Basic/DiagnosticSemaKinds.td
6189 ↗(On Diff #14620)

The other diagnostic was much more specific, but it fired too late, so we ended up with both diagnostics.

lib/Sema/SemaDeclCXX.cpp
8420–8435 ↗(On Diff #14620)

I wonder if r218466 is related: "Move calls to ResolveExceptionSpec into DefineImplicit*".

10979–10981 ↗(On Diff #14620)

I think this is addressed now. I can't test the explicit and partial specialization cases because I can't seem to declare such specializations before leaving class scope, which hides this codepath.

10992–10995 ↗(On Diff #14620)

Should be fixed with DeclContext::getOuterLexicalRecordContext().

lib/Sema/SemaTemplateInstantiate.cpp
2033–2040 ↗(On Diff #14620)

Yes, TemplateDeclInstantiator::VisitFieldDecl() copies it from the pattern to the instantiation.

2199 ↗(On Diff #14620)

Feel free to pick one if you like it:

InstantiateFieldInitializer
InstantiateDefaultInitializer
InstantiateInClassInitializer

I'm going with InstantiateInClassInitializer for now, since it's grep-able in relation to InClassInitializer.

2228–2235 ↗(On Diff #14620)

Nice! This fixed everything and allowed me to completely defer instantiation of NSDMIs until we form CXXDefaultInitExprs.

rnk updated this revision to Diff 14895.Oct 14 2014, 3:04 PM
  • Fix NSDMI instantiation and fix eager instantiation of NSDMIs
  • Still needs work on diagnostic wording and source locations
rnk updated this revision to Diff 14955.Oct 15 2014, 11:28 AM
  • Adjust wording
rnk added inline comments.Oct 15 2014, 11:35 AM
lib/Sema/SemaTemplateInstantiate.cpp
452 ↗(On Diff #14955)

This has created some extra "instantiation requested here" notes which are basically redundant with "default constructor first requested here" notes. What can I do about that?

rnk updated this revision to Diff 14974.Oct 15 2014, 6:16 PM
  • wording tweak
rsmith edited edge metadata.Oct 16 2014, 4:30 PM

Two more things I'd like to see tested:

1: Explicit instantiation of a class should presumably *not* instantiate default initializers unless they're actually used by some constructor that is explicitly instantiated. For instance:

template<typename T> struct X {
  X();
  int n = T::error;
};
template struct X<int>; // ok
template<typename T> X<T>::X() {}
template struct X<float>; // error in instantiation of X<float>::n's initializer from X<float>::X()

2: Instantiation of a function with a local class should always instantiate the default initializers, even if they're not used. For instance:

template<typename T> void f() {
  struct X {
    int n = T::error;
  };
}
void g() { f<int>(); } // error
include/clang/Basic/DiagnosticSemaKinds.td
6194–6195 ↗(On Diff #14974)

"non-static data member initializer" is a term the GCC folks made up; I don't like it =) How about:

"... because %1 has an initializer"?
rnk added a comment.Oct 21 2014, 3:38 PM
In D5690#13, @rsmith wrote:

Two more things I'd like to see tested:

1: Explicit instantiation of a class should presumably *not* instantiate default initializers unless they're actually used by some constructor that is explicitly instantiated. For instance:

template<typename T> struct X {
  X();
  int n = T::error;
};
template struct X<int>; // ok
template<typename T> X<T>::X() {}
template struct X<float>; // error in instantiation of X<float>::n's initializer from X<float>::X()

OK, that works.

2: Instantiation of a function with a local class should always instantiate the default initializers, even if they're not used. For instance:

template<typename T> void f() {
  struct X {
    int n = T::error;
  };
}
void g() { f<int>(); } // error

I can probably go add this logic, but what's the reason?

include/clang/Basic/DiagnosticSemaKinds.td
6194–6195 ↗(On Diff #14974)

Sure, but see the other template instantiation note which uses this terminology.

rnk updated this revision to Diff 15223.Oct 21 2014, 4:45 PM
rnk edited edge metadata.
  • wording tweak, more tests
rsmith accepted this revision.Nov 13 2014, 7:26 PM
rsmith edited edge metadata.

LGTM with some minor changes.

lib/Sema/SemaTemplateInstantiate.cpp
2230 ↗(On Diff #15223)

MergeWithParentScope should be false here; there's not necessarily any connection between whatever we were instantiating before and this scope.

2246 ↗(On Diff #15223)

You currently return false even when ActOnFinishCXXInClassMemberInitializer fails; you should probably either return true in that case or remove your return value and check for whether there actually is an in-class initialzer in the caller afterwards.

This revision is now accepted and ready to land.Nov 13 2014, 7:26 PM
rnk added a comment.Nov 17 2014, 3:36 PM

Thanks!

lib/Sema/SemaTemplateInstantiate.cpp
2230 ↗(On Diff #15223)

OK.

2246 ↗(On Diff #15223)

I think the Instantiate* methods should all return consistent error codes, so I'd like to keep this. I can do return !Instantiation->getInClassInitializer(), though, since that is the error case.

Should I be marking the Instantiation FieldDecl invalid if instantiation fails? Otherwise the next time we hit a constructor I think we'll try to reinstantiate. I'll handle this in a followup commit if you think this is better.

rnk closed this revision.Nov 17 2014, 3:37 PM
rnk updated this revision to Diff 16309.

Closed by commit rL222192 (authored by @rnk).