Unify the way destructor epilogues are generated for synthesized and regular destructors. Also fix PR5529.

llvm-svn: 89034
This commit is contained in:
Anders Carlsson 2009-11-17 04:44:12 +00:00
parent cfd3012756
commit dee9a30204
8 changed files with 159 additions and 330 deletions

View File

@ -1269,13 +1269,6 @@ public:
/// };
/// @endcode
class CXXDestructorDecl : public CXXMethodDecl {
public:
enum KindOfObjectToDestroy {
VBASE = 0x1,
DRCTNONVBASE = 0x2,
ANYBASE = 0x3
};
private:
/// ImplicitlyDefined - Whether this destructor was implicitly
/// defined by the compiler. When false, the destructor was defined
/// by the user. In C++03, this flag will have the same value as
@ -1284,27 +1277,15 @@ private:
/// @c !Implicit && ImplicitlyDefined.
bool ImplicitlyDefined : 1;
/// Support for base and member destruction.
/// BaseOrMemberDestructions - The arguments used to destruct the base
/// or member. Each uintptr_t value represents one of base classes (either
/// virtual or direct non-virtual base), or non-static data member
/// to be destroyed. The low two bits encode the kind of object
/// being destroyed.
uintptr_t *BaseOrMemberDestructions;
unsigned NumBaseOrMemberDestructions;
FunctionDecl *OperatorDelete;
CXXDestructorDecl(CXXRecordDecl *RD, SourceLocation L,
DeclarationName N, QualType T,
bool isInline, bool isImplicitlyDeclared)
: CXXMethodDecl(CXXDestructor, RD, L, N, T, /*DInfo=*/0, false, isInline),
ImplicitlyDefined(false),
BaseOrMemberDestructions(0), NumBaseOrMemberDestructions(0),
OperatorDelete(0) {
ImplicitlyDefined(false), OperatorDelete(0) {
setImplicit(isImplicitlyDeclared);
}
virtual void Destroy(ASTContext& C);
public:
static CXXDestructorDecl *Create(ASTContext &C, CXXRecordDecl *RD,
@ -1333,96 +1314,6 @@ public:
void setOperatorDelete(FunctionDecl *OD) { OperatorDelete = OD; }
const FunctionDecl *getOperatorDelete() const { return OperatorDelete; }
/// destr_iterator - Iterates through the member/base destruction list.
/// destr_const_iterator - Iterates through the member/base destruction list.
typedef uintptr_t const destr_const_iterator;
/// destr_begin() - Retrieve an iterator to the first destructed member/base.
uintptr_t* destr_begin() {
return BaseOrMemberDestructions;
}
/// destr_begin() - Retrieve an iterator to the first destructed member/base.
uintptr_t* destr_begin() const {
return BaseOrMemberDestructions;
}
/// destr_end() - Retrieve an iterator past the last destructed member/base.
uintptr_t* destr_end() {
return BaseOrMemberDestructions + NumBaseOrMemberDestructions;
}
/// destr_end() - Retrieve an iterator past the last destructed member/base.
uintptr_t* destr_end() const {
return BaseOrMemberDestructions + NumBaseOrMemberDestructions;
}
/// getNumBaseOrMemberDestructions - Number of base and non-static members
/// to destroy.
unsigned getNumBaseOrMemberDestructions() const {
return NumBaseOrMemberDestructions;
}
/// setNumBaseOrMemberDestructions - Set number of base and non-static members
/// to destroy.
void setNumBaseOrMemberDestructions(unsigned numBaseOrMemberDestructions) {
NumBaseOrMemberDestructions = numBaseOrMemberDestructions;
}
/// getBaseOrMemberToDestroy - get the generic 'member' representing either
/// the field or a base class.
uintptr_t* getBaseOrMemberToDestroy() const {
return BaseOrMemberDestructions;
}
/// setBaseOrMemberToDestroy - set the generic 'member' representing either
/// the field or a base class.
void setBaseOrMemberDestructions(uintptr_t* baseOrMemberDestructions) {
BaseOrMemberDestructions = baseOrMemberDestructions;
}
/// isVbaseToDestroy - returns true, if object is virtual base.
bool isVbaseToDestroy(uintptr_t Vbase) const {
return (Vbase & VBASE) != 0;
}
/// isDirectNonVBaseToDestroy - returns true, if object is direct non-virtual
/// base.
bool isDirectNonVBaseToDestroy(uintptr_t DrctNonVbase) const {
return (DrctNonVbase & DRCTNONVBASE) != 0;
}
/// isAnyBaseToDestroy - returns true, if object is any base (virtual or
/// direct non-virtual)
bool isAnyBaseToDestroy(uintptr_t AnyBase) const {
return (AnyBase & ANYBASE) != 0;
}
/// isMemberToDestroy - returns true if object is a non-static data member.
bool isMemberToDestroy(uintptr_t Member) const {
return (Member & ANYBASE) == 0;
}
/// getAnyBaseClassToDestroy - Get the type for the given base class object.
Type *getAnyBaseClassToDestroy(uintptr_t Base) const {
if (isAnyBaseToDestroy(Base))
return reinterpret_cast<Type*>(Base & ~0x03);
return 0;
}
/// getMemberToDestroy - Get the member for the given object.
FieldDecl *getMemberToDestroy(uintptr_t Member) const {
if (isMemberToDestroy(Member))
return reinterpret_cast<FieldDecl *>(Member);
return 0;
}
/// getVbaseClassToDestroy - Get the virtual base.
Type *getVbaseClassToDestroy(uintptr_t Vbase) const {
if (isVbaseToDestroy(Vbase))
return reinterpret_cast<Type*>(Vbase & ~0x01);
return 0;
}
/// getDirectNonVBaseClassToDestroy - Get the virtual base.
Type *getDirectNonVBaseClassToDestroy(uintptr_t Base) const {
if (isDirectNonVBaseToDestroy(Base))
return reinterpret_cast<Type*>(Base & ~0x02);
return 0;
}
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) {
return D->getKind() == CXXDestructor;

View File

@ -790,12 +790,6 @@ CXXDestructorDecl::Create(ASTContext &C, CXXRecordDecl *RD,
isImplicitlyDeclared);
}
void
CXXDestructorDecl::Destroy(ASTContext& C) {
C.Deallocate(BaseOrMemberDestructions);
CXXMethodDecl::Destroy(C);
}
void
CXXConstructorDecl::Destroy(ASTContext& C) {
C.Deallocate(BaseOrMemberInitializers);

View File

@ -401,37 +401,6 @@ void DeclPrinter::VisitFunctionDecl(FunctionDecl *D) {
}
}
}
else if (CXXDestructorDecl *DDecl = dyn_cast<CXXDestructorDecl>(D)) {
if (DDecl->getNumBaseOrMemberDestructions() > 0) {
// List order of base/member destruction for visualization purposes.
assert (D->isThisDeclarationADefinition() && "Destructor with dtor-list");
Proto += "/* : ";
for (CXXDestructorDecl::destr_const_iterator *B = DDecl->destr_begin(),
*E = DDecl->destr_end();
B != E; ++B) {
uintptr_t BaseOrMember = (*B);
if (B != DDecl->destr_begin())
Proto += ", ";
if (DDecl->isMemberToDestroy(BaseOrMember)) {
FieldDecl *FD = DDecl->getMemberToDestroy(BaseOrMember);
Proto += "~";
Proto += FD->getNameAsString();
}
else // FIXME. skip dependent types for now.
if (const RecordType *RT =
DDecl->getAnyBaseClassToDestroy(BaseOrMember)
->getAs<RecordType>()) {
const CXXRecordDecl *BaseDecl =
cast<CXXRecordDecl>(RT->getDecl());
Proto += "~";
Proto += BaseDecl->getNameAsString();
}
Proto += "()";
}
Proto += " */";
}
}
else
AFT->getResultType().getAsStringInternal(Proto, Policy);
} else {

View File

@ -1643,119 +1643,97 @@ void CodeGenFunction::EmitCtorPrologue(const CXXConstructorDecl *CD,
/// FIXME: This needs to take a CXXDtorType.
void CodeGenFunction::EmitDtorEpilogue(const CXXDestructorDecl *DD,
CXXDtorType DtorType) {
const CXXRecordDecl *ClassDecl = cast<CXXRecordDecl>(DD->getDeclContext());
assert(!ClassDecl->getNumVBases() &&
"FIXME: Destruction of virtual bases not supported");
(void)ClassDecl; // prevent warning.
assert(!DD->isTrivial() &&
"Should not emit dtor epilogue for trivial dtor!");
for (CXXDestructorDecl::destr_const_iterator *B = DD->destr_begin(),
*E = DD->destr_end(); B != E; ++B) {
uintptr_t BaseOrMember = (*B);
if (DD->isMemberToDestroy(BaseOrMember)) {
FieldDecl *FD = DD->getMemberToDestroy(BaseOrMember);
QualType FieldType = getContext().getCanonicalType((FD)->getType());
const ConstantArrayType *Array =
getContext().getAsConstantArrayType(FieldType);
if (Array)
FieldType = getContext().getBaseElementType(FieldType);
const RecordType *RT = FieldType->getAs<RecordType>();
CXXRecordDecl *FieldClassDecl = cast<CXXRecordDecl>(RT->getDecl());
if (FieldClassDecl->hasTrivialDestructor())
continue;
llvm::Value *LoadOfThis = LoadCXXThis();
LValue LHS = EmitLValueForField(LoadOfThis, FD, false, 0);
if (Array) {
const llvm::Type *BasePtr = ConvertType(FieldType);
BasePtr = llvm::PointerType::getUnqual(BasePtr);
llvm::Value *BaseAddrPtr =
Builder.CreateBitCast(LHS.getAddress(), BasePtr);
EmitCXXAggrDestructorCall(FieldClassDecl->getDestructor(getContext()),
Array, BaseAddrPtr);
}
else
EmitCXXDestructorCall(FieldClassDecl->getDestructor(getContext()),
Dtor_Complete, LHS.getAddress());
} else {
const RecordType *RT =
DD->getAnyBaseClassToDestroy(BaseOrMember)->getAs<RecordType>();
CXXRecordDecl *BaseClassDecl = cast<CXXRecordDecl>(RT->getDecl());
if (BaseClassDecl->hasTrivialDestructor())
continue;
llvm::Value *V = GetAddressCXXOfBaseClass(LoadCXXThis(),
ClassDecl, BaseClassDecl,
/*NullCheckValue=*/false);
EmitCXXDestructorCall(BaseClassDecl->getDestructor(getContext()),
DtorType, V);
}
}
if (DD->getNumBaseOrMemberDestructions() || DD->isTrivial())
return;
// Case of destructor synthesis with fields and base classes
// which have non-trivial destructors. They must be destructed in
// reverse order of their construction.
llvm::SmallVector<FieldDecl *, 16> DestructedFields;
const CXXRecordDecl *ClassDecl = DD->getParent();
for (CXXRecordDecl::field_iterator Field = ClassDecl->field_begin(),
FieldEnd = ClassDecl->field_end();
Field != FieldEnd; ++Field) {
QualType FieldType = getContext().getCanonicalType((*Field)->getType());
if (getContext().getAsConstantArrayType(FieldType))
FieldType = getContext().getBaseElementType(FieldType);
if (const RecordType *RT = FieldType->getAs<RecordType>()) {
CXXRecordDecl *FieldClassDecl = cast<CXXRecordDecl>(RT->getDecl());
if (FieldClassDecl->hasTrivialDestructor())
continue;
DestructedFields.push_back(*Field);
}
}
if (!DestructedFields.empty())
for (int i = DestructedFields.size() -1; i >= 0; --i) {
FieldDecl *Field = DestructedFields[i];
QualType FieldType = Field->getType();
const ConstantArrayType *Array =
getContext().getAsConstantArrayType(FieldType);
if (Array)
FieldType = getContext().getBaseElementType(FieldType);
const RecordType *RT = FieldType->getAs<RecordType>();
CXXRecordDecl *FieldClassDecl = cast<CXXRecordDecl>(RT->getDecl());
llvm::Value *LoadOfThis = LoadCXXThis();
LValue LHS = EmitLValueForField(LoadOfThis, Field, false, 0);
if (Array) {
const llvm::Type *BasePtr = ConvertType(FieldType);
BasePtr = llvm::PointerType::getUnqual(BasePtr);
llvm::Value *BaseAddrPtr =
Builder.CreateBitCast(LHS.getAddress(), BasePtr);
EmitCXXAggrDestructorCall(FieldClassDecl->getDestructor(getContext()),
Array, BaseAddrPtr);
}
else
EmitCXXDestructorCall(FieldClassDecl->getDestructor(getContext()),
Dtor_Complete, LHS.getAddress());
}
llvm::SmallVector<CXXRecordDecl*, 4> DestructedBases;
for (CXXRecordDecl::base_class_const_iterator Base = ClassDecl->bases_begin();
Base != ClassDecl->bases_end(); ++Base) {
// FIXME. copy assignment of virtual base NYI
if (Base->isVirtual())
// Collect the fields.
llvm::SmallVector<const FieldDecl *, 16> FieldDecls;
for (CXXRecordDecl::field_iterator I = ClassDecl->field_begin(),
E = ClassDecl->field_end(); I != E; ++I) {
const FieldDecl *Field = *I;
QualType FieldType = getContext().getCanonicalType(Field->getType());
FieldType = getContext().getBaseElementType(FieldType);
const RecordType *RT = FieldType->getAs<RecordType>();
if (!RT)
continue;
CXXRecordDecl *BaseClassDecl
= cast<CXXRecordDecl>(Base->getType()->getAs<RecordType>()->getDecl());
if (BaseClassDecl->hasTrivialDestructor())
continue;
DestructedBases.push_back(BaseClassDecl);
}
for (int i = DestructedBases.size(); i > 0; --i) {
CXXRecordDecl *BaseClassDecl = DestructedBases[i - 1];
llvm::Value *V = GetAddressCXXOfBaseClass(LoadCXXThis(),
ClassDecl,BaseClassDecl,
/*NullCheckValue=*/false);
EmitCXXDestructorCall(BaseClassDecl->getDestructor(getContext()),
Dtor_Complete, V);
CXXRecordDecl *FieldClassDecl = cast<CXXRecordDecl>(RT->getDecl());
if (FieldClassDecl->hasTrivialDestructor())
continue;
FieldDecls.push_back(Field);
}
// Now destroy the fields.
for (size_t i = FieldDecls.size(); i > 0; --i) {
const FieldDecl *Field = FieldDecls[i - 1];
QualType FieldType = Field->getType();
const ConstantArrayType *Array =
getContext().getAsConstantArrayType(FieldType);
if (Array)
FieldType = getContext().getBaseElementType(FieldType);
const RecordType *RT = FieldType->getAs<RecordType>();
CXXRecordDecl *FieldClassDecl = cast<CXXRecordDecl>(RT->getDecl());
llvm::Value *ThisPtr = LoadCXXThis();
LValue LHS = EmitLValueForField(ThisPtr, Field,
/*isUnion=*/false,
// FIXME: Qualifiers?
/*CVRQualifiers=*/0);
if (Array) {
const llvm::Type *BasePtr = ConvertType(FieldType);
BasePtr = llvm::PointerType::getUnqual(BasePtr);
llvm::Value *BaseAddrPtr =
Builder.CreateBitCast(LHS.getAddress(), BasePtr);
EmitCXXAggrDestructorCall(FieldClassDecl->getDestructor(getContext()),
Array, BaseAddrPtr);
} else
EmitCXXDestructorCall(FieldClassDecl->getDestructor(getContext()),
Dtor_Complete, LHS.getAddress());
}
// Destroy non-virtual bases.
for (CXXRecordDecl::reverse_base_class_const_iterator I =
ClassDecl->bases_rbegin(), E = ClassDecl->bases_rend(); I != E; ++I) {
const CXXBaseSpecifier &Base = *I;
// Ignore virtual bases.
if (Base.isVirtual())
continue;
CXXRecordDecl *BaseClassDecl
= cast<CXXRecordDecl>(Base.getType()->getAs<RecordType>()->getDecl());
// Ignore trivial destructors.
if (BaseClassDecl->hasTrivialDestructor())
continue;
llvm::Value *V = GetAddressCXXOfBaseClass(LoadCXXThis(),
ClassDecl, BaseClassDecl,
/*NullCheckValue=*/false);
EmitCXXDestructorCall(BaseClassDecl->getDestructor(getContext()),
Dtor_Base, V);
}
// If we're emitting a base destructor, we don't want to emit calls to the
// virtual bases.
if (DtorType == Dtor_Base)
return;
// FIXME: Handle virtual bases.
for (CXXRecordDecl::reverse_base_class_const_iterator I =
ClassDecl->vbases_rbegin(), E = ClassDecl->vbases_rend(); I != E; ++I) {
assert(false && "FIXME: Handle virtual bases.");
}
// If we have a deleting destructor, emit a call to the delete operator.
if (DtorType == Dtor_Deleting) {
const FunctionDecl *DeleteFD = DD->getOperatorDelete();
assert(DeleteFD && "deleting dtor did not have a delete operator!");
@ -1782,19 +1760,17 @@ void CodeGenFunction::SynthesizeDefaultDestructor(const CXXDestructorDecl *Dtor,
CXXDtorType DtorType,
llvm::Function *Fn,
const FunctionArgList &Args) {
const CXXRecordDecl *ClassDecl = Dtor->getParent();
assert(!ClassDecl->hasUserDeclaredDestructor() &&
assert(!Dtor->getParent()->hasUserDeclaredDestructor() &&
"SynthesizeDefaultDestructor - destructor has user declaration");
(void) ClassDecl;
StartFunction(GlobalDecl(Dtor, DtorType), Dtor->getResultType(), Fn, Args,
SourceLocation());
EmitDtorEpilogue(Dtor, DtorType);
FinishFunction();
}
// FIXME: Move this to CGCXXStmt.cpp
// FIXME: Move this to CGStmtCXX.cpp
void CodeGenFunction::EmitCXXTryStmt(const CXXTryStmt &S) {
// FIXME: We need to do more here.
EmitStmt(S.getTryBlock());

View File

@ -2407,11 +2407,10 @@ public:
unsigned NumInitializers,
bool IsImplicitConstructor);
/// computeBaseOrMembersToDestroy - Compute information in current
/// destructor decl's AST of bases and non-static data members which will be
/// implicitly destroyed. We are storing the destruction in the order that
/// they should occur (which is the reverse of construction order).
void computeBaseOrMembersToDestroy(CXXDestructorDecl *Destructor);
/// MarkBaseAndMemberDestructorsReferenced - Given a destructor decl,
/// mark all its non-trivial member and base destructor declarations
/// as referenced.
void MarkBaseAndMemberDestructorsReferenced(CXXDestructorDecl *Destructor);
void AddImplicitlyDeclaredMembersToClass(CXXRecordDecl *ClassDecl);

View File

@ -4052,7 +4052,7 @@ Sema::DeclPtrTy Sema::ActOnFinishFunctionBody(DeclPtrTy D, StmtArg BodyArg,
DiagnoseReturnInConstructorExceptionHandler(cast<CXXTryStmt>(Body));
if (CXXDestructorDecl *Destructor = dyn_cast<CXXDestructorDecl>(dcl))
computeBaseOrMembersToDestroy(Destructor);
MarkBaseAndMemberDestructorsReferenced(Destructor);
// If any errors have occurred, clear out any temporaries that may have
// been leftover. This ensures that these temporaries won't be picked up for

View File

@ -1633,77 +1633,63 @@ void Sema::ActOnMemInitializers(DeclPtrTy ConstructorDecl,
}
void
Sema::computeBaseOrMembersToDestroy(CXXDestructorDecl *Destructor) {
CXXRecordDecl *ClassDecl = cast<CXXRecordDecl>(Destructor->getDeclContext());
llvm::SmallVector<uintptr_t, 32> AllToDestruct;
Sema::MarkBaseAndMemberDestructorsReferenced(CXXDestructorDecl *Destructor) {
// Ignore dependent destructors.
if (Destructor->isDependentContext())
return;
CXXRecordDecl *ClassDecl = Destructor->getParent();
// Non-static data members.
for (CXXRecordDecl::field_iterator I = ClassDecl->field_begin(),
E = ClassDecl->field_end(); I != E; ++I) {
FieldDecl *Field = *I;
QualType FieldType = Context.getBaseElementType(Field->getType());
const RecordType* RT = FieldType->getAs<RecordType>();
if (!RT)
continue;
CXXRecordDecl *FieldClassDecl = cast<CXXRecordDecl>(RT->getDecl());
if (FieldClassDecl->hasTrivialDestructor())
continue;
const CXXDestructorDecl *Dtor = FieldClassDecl->getDestructor(Context);
MarkDeclarationReferenced(Destructor->getLocation(),
const_cast<CXXDestructorDecl*>(Dtor));
}
// Bases.
for (CXXRecordDecl::base_class_iterator Base = ClassDecl->bases_begin(),
E = ClassDecl->bases_end(); Base != E; ++Base) {
// Ignore virtual bases.
if (Base->isVirtual())
continue;
// Ignore trivial destructors.
CXXRecordDecl *BaseClassDecl
= cast<CXXRecordDecl>(Base->getType()->getAs<RecordType>()->getDecl());
if (BaseClassDecl->hasTrivialDestructor())
continue;
const CXXDestructorDecl *Dtor = BaseClassDecl->getDestructor(Context);
MarkDeclarationReferenced(Destructor->getLocation(),
const_cast<CXXDestructorDecl*>(Dtor));
}
// Virtual bases.
for (CXXRecordDecl::base_class_iterator VBase = ClassDecl->vbases_begin(),
E = ClassDecl->vbases_end(); VBase != E; ++VBase) {
if (VBase->getType()->isDependentType())
continue;
// Skip over virtual bases which have trivial destructors.
// Ignore trivial destructors.
CXXRecordDecl *BaseClassDecl
= cast<CXXRecordDecl>(VBase->getType()->getAs<RecordType>()->getDecl());
if (BaseClassDecl->hasTrivialDestructor())
continue;
if (const CXXDestructorDecl *Dtor = BaseClassDecl->getDestructor(Context))
MarkDeclarationReferenced(Destructor->getLocation(),
const_cast<CXXDestructorDecl*>(Dtor));
uintptr_t Member =
reinterpret_cast<uintptr_t>(VBase->getType().getTypePtr())
| CXXDestructorDecl::VBASE;
AllToDestruct.push_back(Member);
}
for (CXXRecordDecl::base_class_iterator Base =
ClassDecl->bases_begin(),
E = ClassDecl->bases_end(); Base != E; ++Base) {
if (Base->isVirtual())
continue;
if (Base->getType()->isDependentType())
continue;
// Skip over virtual bases which have trivial destructors.
CXXRecordDecl *BaseClassDecl
= cast<CXXRecordDecl>(Base->getType()->getAs<RecordType>()->getDecl());
if (BaseClassDecl->hasTrivialDestructor())
continue;
if (const CXXDestructorDecl *Dtor = BaseClassDecl->getDestructor(Context))
MarkDeclarationReferenced(Destructor->getLocation(),
const_cast<CXXDestructorDecl*>(Dtor));
uintptr_t Member =
reinterpret_cast<uintptr_t>(Base->getType().getTypePtr())
| CXXDestructorDecl::DRCTNONVBASE;
AllToDestruct.push_back(Member);
}
// non-static data members.
for (CXXRecordDecl::field_iterator Field = ClassDecl->field_begin(),
E = ClassDecl->field_end(); Field != E; ++Field) {
QualType FieldType = Context.getBaseElementType((*Field)->getType());
if (const RecordType* RT = FieldType->getAs<RecordType>()) {
// Skip over virtual bases which have trivial destructors.
CXXRecordDecl *FieldClassDecl = cast<CXXRecordDecl>(RT->getDecl());
if (FieldClassDecl->hasTrivialDestructor())
continue;
if (const CXXDestructorDecl *Dtor =
FieldClassDecl->getDestructor(Context))
MarkDeclarationReferenced(Destructor->getLocation(),
const_cast<CXXDestructorDecl*>(Dtor));
uintptr_t Member = reinterpret_cast<uintptr_t>(*Field);
AllToDestruct.push_back(Member);
}
}
unsigned NumDestructions = AllToDestruct.size();
if (NumDestructions > 0) {
Destructor->setNumBaseOrMemberDestructions(NumDestructions);
uintptr_t *BaseOrMemberDestructions =
new (Context) uintptr_t [NumDestructions];
// Insert in reverse order.
for (int Idx = NumDestructions-1, i=0 ; Idx >= 0; --Idx)
BaseOrMemberDestructions[i++] = AllToDestruct[Idx];
Destructor->setBaseOrMemberDestructions(BaseOrMemberDestructions);
const CXXDestructorDecl *Dtor = BaseClassDecl->getDestructor(Context);
MarkDeclarationReferenced(Destructor->getLocation(),
const_cast<CXXDestructorDecl*>(Dtor));
}
}

View File

@ -28,3 +28,17 @@ class A1 {
};
template<> A1<char>::~A1();
// PR5529
namespace PR5529 {
struct A {
~A();
};
A::~A() { }
struct B : A {
virtual ~B();
};
B::~B() {}
}