Skip to content

Commit

Permalink
Handle use of default member initializers before end of outermost class
Browse files Browse the repository at this point in the history
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 deferring instantiation of default member initializers until
they are needed during constructor analysis. This addresses a long
standing FIXME in the code.

Fixes PR19195.

Reviewed By: rsmith

Differential Revision: http://reviews.llvm.org/D5690

llvm-svn: 222192
  • Loading branch information
rnk committed Nov 17, 2014
1 parent f39c3b8 commit d60b82f
Show file tree
Hide file tree
Showing 15 changed files with 301 additions and 89 deletions.
7 changes: 7 additions & 0 deletions clang/include/clang/AST/DeclBase.h
Expand Up @@ -48,6 +48,7 @@ class ObjCInterfaceDecl;
class ObjCMethodDecl;
class ObjCProtocolDecl;
struct PrintingPolicy;
class RecordDecl;
class Stmt;
class StoredDeclsMap;
class TranslationUnitDecl;
Expand Down Expand Up @@ -1238,6 +1239,12 @@ class DeclContext {
return const_cast<DeclContext *>(this)->getEnclosingNamespaceContext();
}

/// \brief Retrieve the outermost lexically enclosing record context.
RecordDecl *getOuterLexicalRecordContext();
const RecordDecl *getOuterLexicalRecordContext() const {
return const_cast<DeclContext *>(this)->getOuterLexicalRecordContext();
}

/// \brief Test if this context is part of the enclosing namespace set of
/// the context NS, as defined in C++0x [namespace.def]p9. If either context
/// isn't a namespace, this is equivalent to Equals().
Expand Down
10 changes: 8 additions & 2 deletions clang/include/clang/AST/ExprCXX.h
Expand Up @@ -967,8 +967,14 @@ class CXXDefaultInitExpr : public Expr {
const FieldDecl *getField() const { return Field; }

/// \brief Get the initialization expression that will be used.
const Expr *getExpr() const { return Field->getInClassInitializer(); }
Expr *getExpr() { return Field->getInClassInitializer(); }
const Expr *getExpr() const {
assert(Field->getInClassInitializer() && "initializer hasn't been parsed");
return Field->getInClassInitializer();
}
Expr *getExpr() {
assert(Field->getInClassInitializer() && "initializer hasn't been parsed");
return Field->getInClassInitializer();
}

SourceLocation getLocStart() const LLVM_READONLY { return Loc; }
SourceLocation getLocEnd() const LLVM_READONLY { return Loc; }
Expand Down
11 changes: 8 additions & 3 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -3536,6 +3536,8 @@ def note_template_variable_def_here : Note<
"in instantiation of variable template specialization %q0 requested here">;
def note_template_enum_def_here : Note<
"in instantiation of enumeration %q0 requested here">;
def note_template_nsdmi_here : Note<
"in instantiation of default member initializer %q0 requested here">;
def note_template_type_alias_instantiation_here : Note<
"in instantiation of template type alias %0 requested here">;
def note_template_exception_spec_instantiation_here : Note<
Expand Down Expand Up @@ -6213,9 +6215,12 @@ def err_in_class_initializer_literal_type : Error<
"'constexpr' specifier">;
def err_in_class_initializer_non_constant : Error<
"in-class initializer for static data member is not a constant expression">;
def err_in_class_initializer_references_def_ctor : Error<
"defaulted default constructor of %0 cannot be used by non-static data "
"member initializer which appears before end of class definition">;
def err_in_class_initializer_not_yet_parsed
: Error<"cannot use defaulted default constructor of %0 within the class "
"outside of member functions because %1 has an initializer">;
def err_in_class_initializer_not_yet_parsed_outer_class
: Error<"cannot use defaulted default constructor of %0 within "
"%1 outside of member functions because %2 has an initializer">;

def ext_in_class_initializer_non_constant : Extension<
"in-class initializer for static data member is not a constant expression; "
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -3982,6 +3982,8 @@ class Sema {
bool IsStdInitListInitialization, bool RequiresZeroInit,
unsigned ConstructKind, SourceRange ParenRange);

ExprResult BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field);

/// BuildCXXDefaultArgExpr - Creates a CXXDefaultArgExpr, instantiating
/// the default expr if needed.
ExprResult BuildCXXDefaultArgExpr(SourceLocation CallLoc,
Expand Down Expand Up @@ -6831,6 +6833,10 @@ class Sema {
const MultiLevelTemplateArgumentList &TemplateArgs,
TemplateSpecializationKind TSK);

bool InstantiateInClassInitializer(
SourceLocation PointOfInstantiation, FieldDecl *Instantiation,
FieldDecl *Pattern, const MultiLevelTemplateArgumentList &TemplateArgs);

struct LateInstantiatedAttribute {
const Attr *TmplAttr;
LocalInstantiationScope *Scope;
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/AST/DeclBase.cpp
Expand Up @@ -1441,6 +1441,17 @@ DeclContext *DeclContext::getEnclosingNamespaceContext() {
return Ctx->getPrimaryContext();
}

RecordDecl *DeclContext::getOuterLexicalRecordContext() {
// Loop until we find a non-record context.
RecordDecl *OutermostRD = nullptr;
DeclContext *DC = this;
while (DC->isRecord()) {
OutermostRD = cast<RecordDecl>(DC);
DC = DC->getLexicalParent();
}
return OutermostRD;
}

bool DeclContext::InEnclosingNamespaceSetOf(const DeclContext *O) const {
// For non-file contexts, this is equivalent to Equals.
if (!isFileContext())
Expand Down
6 changes: 4 additions & 2 deletions clang/lib/AST/Expr.cpp
Expand Up @@ -2985,11 +2985,13 @@ bool Expr::HasSideEffects(const ASTContext &Ctx) const {
case CXXDefaultArgExprClass:
return cast<CXXDefaultArgExpr>(this)->getExpr()->HasSideEffects(Ctx);

case CXXDefaultInitExprClass:
if (const Expr *E = cast<CXXDefaultInitExpr>(this)->getExpr())
case CXXDefaultInitExprClass: {
const FieldDecl *FD = cast<CXXDefaultInitExpr>(this)->getField();
if (const Expr *E = FD->getInClassInitializer())
return E->HasSideEffects(Ctx);
// If we've not yet parsed the initializer, assume it has side-effects.
return true;
}

case CXXDynamicCastExprClass: {
// A dynamic_cast expression has side-effects if it can throw.
Expand Down
90 changes: 61 additions & 29 deletions clang/lib/Sema/SemaDeclCXX.cpp
Expand Up @@ -36,6 +36,7 @@
#include "clang/Sema/ParsedTemplate.h"
#include "clang/Sema/Scope.h"
#include "clang/Sema/ScopeInfo.h"
#include "clang/Sema/Template.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallString.h"
#include <map>
Expand Down Expand Up @@ -3730,19 +3731,19 @@ static bool CollectFieldInitializer(Sema &SemaRef, BaseAndFieldInfo &Info,
return false;

if (Field->hasInClassInitializer() && !Info.isImplicitCopyOrMove()) {
Expr *DIE = CXXDefaultInitExpr::Create(SemaRef.Context,
Info.Ctor->getLocation(), Field);
ExprResult DIE =
SemaRef.BuildCXXDefaultInitExpr(Info.Ctor->getLocation(), Field);
if (DIE.isInvalid())
return true;
CXXCtorInitializer *Init;
if (Indirect)
Init = new (SemaRef.Context) CXXCtorInitializer(SemaRef.Context, Indirect,
SourceLocation(),
SourceLocation(), DIE,
SourceLocation());
Init = new (SemaRef.Context)
CXXCtorInitializer(SemaRef.Context, Indirect, SourceLocation(),
SourceLocation(), DIE.get(), SourceLocation());
else
Init = new (SemaRef.Context) CXXCtorInitializer(SemaRef.Context, Field,
SourceLocation(),
SourceLocation(), DIE,
SourceLocation());
Init = new (SemaRef.Context)
CXXCtorInitializer(SemaRef.Context, Field, SourceLocation(),
SourceLocation(), DIE.get(), SourceLocation());
return Info.addFieldInitializer(Init);
}

Expand Down Expand Up @@ -8579,22 +8580,6 @@ Sema::ComputeDefaultedDefaultCtorExceptionSpec(SourceLocation Loc,
if (F->hasInClassInitializer()) {
if (Expr *E = F->getInClassInitializer())
ExceptSpec.CalledExpr(E);
else if (!F->isInvalidDecl())
// DR1351:
// If the brace-or-equal-initializer of a non-static data member
// invokes a defaulted default constructor of its class or of an
// enclosing class in a potentially evaluated subexpression, the
// program is ill-formed.
//
// This resolution is unworkable: the exception specification of the
// default constructor can be needed in an unevaluated context, in
// particular, in the operand of a noexcept-expression, and we can be
// unable to compute an exception specification for an enclosed class.
//
// We do not allow an in-class initializer to require the evaluation
// of the exception specification for any in-class initializer whose
// definition is not lexically complete.
Diag(Loc, diag::err_in_class_initializer_references_def_ctor) << MD;
} else if (const RecordType *RecordTy
= Context.getBaseElementType(F->getType())->getAs<RecordType>()) {
CXXRecordDecl *FieldRecDecl = cast<CXXRecordDecl>(RecordTy->getDecl());
Expand Down Expand Up @@ -8662,9 +8647,6 @@ Sema::ComputeInheritingCtorExceptionSpec(CXXConstructorDecl *CD) {
if (F->hasInClassInitializer()) {
if (Expr *E = F->getInClassInitializer())
ExceptSpec.CalledExpr(E);
else if (!F->isInvalidDecl())
Diag(CD->getLocation(),
diag::err_in_class_initializer_references_def_ctor) << CD;
} else if (const RecordType *RecordTy
= Context.getBaseElementType(F->getType())->getAs<RecordType>()) {
CXXRecordDecl *FieldRecDecl = cast<CXXRecordDecl>(RecordTy->getDecl());
Expand Down Expand Up @@ -11129,6 +11111,56 @@ Sema::BuildCXXConstructExpr(SourceLocation ConstructLoc, QualType DeclInitType,
ParenRange);
}

ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
assert(Field->hasInClassInitializer());

// If we already have the in-class initializer nothing needs to be done.
if (Field->getInClassInitializer())
return CXXDefaultInitExpr::Create(Context, Loc, Field);

// Maybe we haven't instantiated the in-class initializer. Go check the
// pattern FieldDecl to see if it has one.
CXXRecordDecl *ParentRD = cast<CXXRecordDecl>(Field->getParent());

if (isTemplateInstantiation(ParentRD->getTemplateSpecializationKind())) {
CXXRecordDecl *ClassPattern = ParentRD->getTemplateInstantiationPattern();
DeclContext::lookup_result Lookup =
ClassPattern->lookup(Field->getDeclName());
assert(Lookup.size() == 1);
FieldDecl *Pattern = cast<FieldDecl>(Lookup[0]);
if (InstantiateInClassInitializer(Loc, Field, Pattern,
getTemplateInstantiationArgs(Field)))
return ExprError();
return CXXDefaultInitExpr::Create(Context, Loc, Field);
}

// DR1351:
// If the brace-or-equal-initializer of a non-static data member
// invokes a defaulted default constructor of its class or of an
// enclosing class in a potentially evaluated subexpression, the
// program is ill-formed.
//
// This resolution is unworkable: the exception specification of the
// default constructor can be needed in an unevaluated context, in
// particular, in the operand of a noexcept-expression, and we can be
// unable to compute an exception specification for an enclosed class.
//
// Any attempt to resolve the exception specification of a defaulted default
// constructor before the initializer is lexically complete will ultimately
// come here at which point we can diagnose it.
RecordDecl *OutermostClass = ParentRD->getOuterLexicalRecordContext();
if (OutermostClass == ParentRD) {
Diag(Field->getLocEnd(), diag::err_in_class_initializer_not_yet_parsed)
<< ParentRD << Field;
} else {
Diag(Field->getLocEnd(),
diag::err_in_class_initializer_not_yet_parsed_outer_class)
<< ParentRD << OutermostClass << Field;
}

return ExprError();
}

void Sema::FinalizeVarWithDestructor(VarDecl *VD, const RecordType *Record) {
if (VD->isInvalidDecl()) return;

Expand Down
10 changes: 7 additions & 3 deletions clang/lib/Sema/SemaInit.cpp
Expand Up @@ -466,11 +466,15 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field,
// members in the aggregate, then each member not explicitly initialized
// shall be initialized from its brace-or-equal-initializer [...]
if (Field->hasInClassInitializer()) {
Expr *DIE = CXXDefaultInitExpr::Create(SemaRef.Context, Loc, Field);
ExprResult DIE = SemaRef.BuildCXXDefaultInitExpr(Loc, Field);
if (DIE.isInvalid()) {
hadError = true;
return;
}
if (Init < NumInits)
ILE->setInit(Init, DIE);
ILE->setInit(Init, DIE.get());
else {
ILE->updateInit(SemaRef.Context, Init, DIE);
ILE->updateInit(SemaRef.Context, Init, DIE.get());
RequiresSecondPass = true;
}
return;
Expand Down

0 comments on commit d60b82f

Please sign in to comment.