Skip to content

[Clang][Sema] Diagnose current instantiation used as an incomplete base class #92597

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ struct X {
// CHECK-MESSAGES: :[[@LINE-1]]:5: error: field has incomplete type 'X' [clang-diagnostic-error]
int a = 10;
};

template <typename T> class NoCrash {
// CHECK-MESSAGES: :[[@LINE+2]]:20: error: base class has incomplete type
// CHECK-MESSAGES: :[[@LINE-2]]:29: note: definition of 'NoCrash<T>' is not complete until the closing '}'
class B : public NoCrash {
template <typename U> B(U u) {}
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -463,12 +463,6 @@ struct NegativeIncompleteArrayMember {
char e[];
};

template <typename T> class NoCrash {
class B : public NoCrash {
template <typename U> B(U u) {}
};
};

struct PositiveBitfieldMember {
PositiveBitfieldMember() {}
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: constructor does not initialize these fields: F
Expand Down
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ Bug Fixes to C++ Support
the ``constexpr`` specifier. Fixes (#GH61004).
- Clang no longer transforms dependent qualified names into implicit class member access expressions
until it can be determined whether the name is that of a non-static member.
- Clang now correctly diagnoses when the current instantiation is used as an incomplete base class.

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 0 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -9464,8 +9464,6 @@ def err_static_data_member_not_allowed_in_local_class : Error<
def err_base_clause_on_union : Error<"unions cannot have base classes">;
def err_base_must_be_class : Error<"base specifier must name a class">;
def err_union_as_base_class : Error<"unions cannot be base classes">;
def err_circular_inheritance : Error<
"circular inheritance between %0 and %1">;
def err_base_class_has_flexible_array_member : Error<
"base class %0 has a flexible array member">;
def err_incomplete_base_class : Error<"base class has incomplete type">;
Expand Down
8 changes: 8 additions & 0 deletions clang/lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2372,6 +2372,14 @@ bool Type::isIncompleteType(NamedDecl **Def) const {
*Def = Rec;
return !Rec->isCompleteDefinition();
}
case InjectedClassName: {
CXXRecordDecl *Rec = cast<InjectedClassNameType>(CanonicalType)->getDecl();
if (!Rec->isBeingDefined())
return false;
if (Def)
*Def = Rec;
return true;
}
case ConstantArray:
case VariableArray:
// An array is incomplete if its element type is incomplete
Expand Down
249 changes: 95 additions & 154 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2656,188 +2656,122 @@ bool Sema::isCurrentClassNameTypo(IdentifierInfo *&II, const CXXScopeSpec *SS) {
return false;
}

/// Determine whether the given class is a base class of the given
/// class, including looking at dependent bases.
static bool findCircularInheritance(const CXXRecordDecl *Class,
const CXXRecordDecl *Current) {
SmallVector<const CXXRecordDecl*, 8> Queue;

Class = Class->getCanonicalDecl();
while (true) {
for (const auto &I : Current->bases()) {
CXXRecordDecl *Base = I.getType()->getAsCXXRecordDecl();
if (!Base)
continue;

Base = Base->getDefinition();
if (!Base)
continue;

if (Base->getCanonicalDecl() == Class)
return true;

Queue.push_back(Base);
}

if (Queue.empty())
return false;

Current = Queue.pop_back_val();
}

return false;
}

/// Check the validity of a C++ base class specifier.
///
/// \returns a new CXXBaseSpecifier if well-formed, emits diagnostics
/// and returns NULL otherwise.
CXXBaseSpecifier *
Sema::CheckBaseSpecifier(CXXRecordDecl *Class,
SourceRange SpecifierRange,
bool Virtual, AccessSpecifier Access,
TypeSourceInfo *TInfo,
SourceLocation EllipsisLoc) {
// In HLSL, unspecified class access is public rather than private.
if (getLangOpts().HLSL && Class->getTagKind() == TagTypeKind::Class &&
Access == AS_none)
Access = AS_public;

CXXBaseSpecifier *Sema::CheckBaseSpecifier(CXXRecordDecl *Class,
SourceRange SpecifierRange,
bool Virtual, AccessSpecifier Access,
TypeSourceInfo *TInfo,
SourceLocation EllipsisLoc) {
QualType BaseType = TInfo->getType();
SourceLocation BaseLoc = TInfo->getTypeLoc().getBeginLoc();
if (BaseType->containsErrors()) {
// Already emitted a diagnostic when parsing the error type.
return nullptr;
}
// C++ [class.union]p1:
// A union shall not have base classes.
if (Class->isUnion()) {
Diag(Class->getLocation(), diag::err_base_clause_on_union)
<< SpecifierRange;
return nullptr;
}

if (EllipsisLoc.isValid() &&
!TInfo->getType()->containsUnexpandedParameterPack()) {
if (EllipsisLoc.isValid() && !BaseType->containsUnexpandedParameterPack()) {
Diag(EllipsisLoc, diag::err_pack_expansion_without_parameter_packs)
<< TInfo->getTypeLoc().getSourceRange();
EllipsisLoc = SourceLocation();
}

SourceLocation BaseLoc = TInfo->getTypeLoc().getBeginLoc();

if (BaseType->isDependentType()) {
// Make sure that we don't have circular inheritance among our dependent
// bases. For non-dependent bases, the check for completeness below handles
// this.
if (CXXRecordDecl *BaseDecl = BaseType->getAsCXXRecordDecl()) {
if (BaseDecl->getCanonicalDecl() == Class->getCanonicalDecl() ||
((BaseDecl = BaseDecl->getDefinition()) &&
findCircularInheritance(Class, BaseDecl))) {
Diag(BaseLoc, diag::err_circular_inheritance)
<< BaseType << Context.getTypeDeclType(Class);

if (BaseDecl->getCanonicalDecl() != Class->getCanonicalDecl())
Diag(BaseDecl->getLocation(), diag::note_previous_decl)
<< BaseType;
auto *BaseDecl =
dyn_cast_if_present<CXXRecordDecl>(computeDeclContext(BaseType));
// C++ [class.derived.general]p2:
// A class-or-decltype shall denote a (possibly cv-qualified) class type
// that is not an incompletely defined class; any cv-qualifiers are
// ignored.
if (BaseDecl) {
// C++ [class.union.general]p4:
// [...] A union shall not be used as a base class.
if (BaseDecl->isUnion()) {
Diag(BaseLoc, diag::err_union_as_base_class) << SpecifierRange;
return nullptr;
}

return nullptr;
// For the MS ABI, propagate DLL attributes to base class templates.
if (Context.getTargetInfo().getCXXABI().isMicrosoft() ||
Context.getTargetInfo().getTriple().isPS()) {
if (Attr *ClassAttr = getDLLAttr(Class)) {
if (auto *BaseSpec =
dyn_cast<ClassTemplateSpecializationDecl>(BaseDecl)) {
propagateDLLAttrToBaseClassTemplate(Class, ClassAttr, BaseSpec,
BaseLoc);
}
}
}

if (RequireCompleteType(BaseLoc, BaseType, diag::err_incomplete_base_class,
SpecifierRange)) {
Class->setInvalidDecl();
return nullptr;
}

BaseDecl = BaseDecl->getDefinition();
assert(BaseDecl && "Base type is not incomplete, but has no definition");

// Microsoft docs say:
// "If a base-class has a code_seg attribute, derived classes must have the
// same attribute."
const auto *BaseCSA = BaseDecl->getAttr<CodeSegAttr>();
const auto *DerivedCSA = Class->getAttr<CodeSegAttr>();
if ((DerivedCSA || BaseCSA) &&
(!BaseCSA || !DerivedCSA ||
BaseCSA->getName() != DerivedCSA->getName())) {
Diag(Class->getLocation(), diag::err_mismatched_code_seg_base);
Diag(BaseDecl->getLocation(), diag::note_base_class_specified_here)
<< BaseDecl;
return nullptr;
}

// A class which contains a flexible array member is not suitable for use as
// a base class:
// - If the layout determines that a base comes before another base,
// the flexible array member would index into the subsequent base.
// - If the layout determines that base comes before the derived class,
// the flexible array member would index into the derived class.
if (BaseDecl->hasFlexibleArrayMember()) {
Diag(BaseLoc, diag::err_base_class_has_flexible_array_member)
<< BaseDecl->getDeclName();
return nullptr;
}

// C++ [class]p3:
// If a class is marked final and it appears as a base-type-specifier in
// base-clause, the program is ill-formed.
if (FinalAttr *FA = BaseDecl->getAttr<FinalAttr>()) {
Diag(BaseLoc, diag::err_class_marked_final_used_as_base)
<< BaseDecl->getDeclName() << FA->isSpelledAsSealed();
Diag(BaseDecl->getLocation(), diag::note_entity_declared_at)
<< BaseDecl->getDeclName() << FA->getRange();
return nullptr;
}

// If the base class is invalid the derived class is as well.
if (BaseDecl->isInvalidDecl())
Class->setInvalidDecl();
} else if (BaseType->isDependentType()) {
// Make sure that we don't make an ill-formed AST where the type of the
// Class is non-dependent and its attached base class specifier is an
// dependent type, which violates invariants in many clang code paths (e.g.
// constexpr evaluator). If this case happens (in errory-recovery mode), we
// explicitly mark the Class decl invalid. The diagnostic was already
// emitted.
if (!Class->getTypeForDecl()->isDependentType())
if (!Class->isDependentContext())
Class->setInvalidDecl();
return new (Context) CXXBaseSpecifier(
SpecifierRange, Virtual, Class->getTagKind() == TagTypeKind::Class,
Access, TInfo, EllipsisLoc);
}

// Base specifiers must be record types.
if (!BaseType->isRecordType()) {
} else {
// The base class is some non-dependent non-class type.
Diag(BaseLoc, diag::err_base_must_be_class) << SpecifierRange;
return nullptr;
}

// C++ [class.union]p1:
// A union shall not be used as a base class.
if (BaseType->isUnionType()) {
Diag(BaseLoc, diag::err_union_as_base_class) << SpecifierRange;
return nullptr;
}

// For the MS ABI, propagate DLL attributes to base class templates.
if (Context.getTargetInfo().getCXXABI().isMicrosoft() ||
Context.getTargetInfo().getTriple().isPS()) {
if (Attr *ClassAttr = getDLLAttr(Class)) {
if (auto *BaseTemplate = dyn_cast_or_null<ClassTemplateSpecializationDecl>(
BaseType->getAsCXXRecordDecl())) {
propagateDLLAttrToBaseClassTemplate(Class, ClassAttr, BaseTemplate,
BaseLoc);
}
}
}

// C++ [class.derived]p2:
// The class-name in a base-specifier shall not be an incompletely
// defined class.
if (RequireCompleteType(BaseLoc, BaseType,
diag::err_incomplete_base_class, SpecifierRange)) {
Class->setInvalidDecl();
return nullptr;
}

// If the base class is polymorphic or isn't empty, the new one is/isn't, too.
RecordDecl *BaseDecl = BaseType->castAs<RecordType>()->getDecl();
assert(BaseDecl && "Record type has no declaration");
BaseDecl = BaseDecl->getDefinition();
assert(BaseDecl && "Base type is not incomplete, but has no definition");
CXXRecordDecl *CXXBaseDecl = cast<CXXRecordDecl>(BaseDecl);
assert(CXXBaseDecl && "Base type is not a C++ type");

// Microsoft docs say:
// "If a base-class has a code_seg attribute, derived classes must have the
// same attribute."
const auto *BaseCSA = CXXBaseDecl->getAttr<CodeSegAttr>();
const auto *DerivedCSA = Class->getAttr<CodeSegAttr>();
if ((DerivedCSA || BaseCSA) &&
(!BaseCSA || !DerivedCSA || BaseCSA->getName() != DerivedCSA->getName())) {
Diag(Class->getLocation(), diag::err_mismatched_code_seg_base);
Diag(CXXBaseDecl->getLocation(), diag::note_base_class_specified_here)
<< CXXBaseDecl;
return nullptr;
}

// A class which contains a flexible array member is not suitable for use as a
// base class:
// - If the layout determines that a base comes before another base,
// the flexible array member would index into the subsequent base.
// - If the layout determines that base comes before the derived class,
// the flexible array member would index into the derived class.
if (CXXBaseDecl->hasFlexibleArrayMember()) {
Diag(BaseLoc, diag::err_base_class_has_flexible_array_member)
<< CXXBaseDecl->getDeclName();
return nullptr;
}

// C++ [class]p3:
// If a class is marked final and it appears as a base-type-specifier in
// base-clause, the program is ill-formed.
if (FinalAttr *FA = CXXBaseDecl->getAttr<FinalAttr>()) {
Diag(BaseLoc, diag::err_class_marked_final_used_as_base)
<< CXXBaseDecl->getDeclName()
<< FA->isSpelledAsSealed();
Diag(CXXBaseDecl->getLocation(), diag::note_entity_declared_at)
<< CXXBaseDecl->getDeclName() << FA->getRange();
return nullptr;
}

if (BaseDecl->isInvalidDecl())
Class->setInvalidDecl();
// In HLSL, unspecified class access is public rather than private.
if (getLangOpts().HLSL && Class->getTagKind() == TagTypeKind::Class &&
Access == AS_none)
Access = AS_public;

// Create the base specifier.
return new (Context) CXXBaseSpecifier(
Expand Down Expand Up @@ -2887,13 +2821,20 @@ BaseResult Sema::ActOnBaseSpecifier(Decl *classdecl, SourceRange SpecifierRange,
UPPC_BaseType))
return true;

// C++ [class.union.general]p4:
// [...] A union shall not have base classes.
if (Class->isUnion()) {
Diag(Class->getLocation(), diag::err_base_clause_on_union)
<< SpecifierRange;
return true;
}

if (CXXBaseSpecifier *BaseSpec = CheckBaseSpecifier(Class, SpecifierRange,
Virtual, Access, TInfo,
EllipsisLoc))
return BaseSpec;
else
Class->setInvalidDecl();

Class->setInvalidDecl();
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,15 @@ namespace InhCtor {
// ill-formed.
template<typename T>
struct S : T {
struct U : S { // expected-note 6{{candidate}}
using S::S;
};
struct U; // expected-note 6{{candidate}}
using T::T;
};

template<typename T>
struct S<T>::U : S {
using S::S;
};

S<A>::U ua(0); // expected-error {{no match}}
S<B>::U ub(0); // expected-error {{no match}}

Expand Down
Loading
Loading