Files
carbon-lang/toolchain/check/class.cpp
T
ATHARVAandDana Jansens 804359dce0 Fix crash when lowering Carbon derived class with C++ virtual base class (#7745)
If a Carbon class overrides virtual functions from a C++ base class but
is never referenced from C++, it is never exported to Clang. During
lowering, `BuildVtable` then fails to find a `CXXRecordDecl` and crashes
when attempting to get the vtable from Clang's code generator.

Ensure dynamic classes with foreign vtables are exported to Clang when
completing the class definition in `CheckCompleteClassType`, and look up
`first_decl_id()` in `BuildVtable`.

Fixes #7721

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-09-18 18:08:53 +00:00

477 lines
20 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/class.h"
#include "toolchain/check/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/cpp/export.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/function.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/impl.h"
#include "toolchain/check/import_ref.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/name_lookup.h"
#include "toolchain/check/pattern.h"
#include "toolchain/check/pattern_match.h"
#include "toolchain/check/thunk.h"
#include "toolchain/check/type.h"
#include "toolchain/diagnostics/format_providers.h"
#include "toolchain/parse/node_ids.h"
#include "toolchain/sem_ir/builtin_function_kind.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
auto SetClassSelfType(Context& context, SemIR::ClassId class_id) -> void {
auto& class_info = context.classes().Get(class_id);
auto specific_id = context.generics().GetSelfSpecific(class_info.generic_id);
class_info.self_type_id = GetClassType(context, class_id, specific_id);
}
auto StartClassDefinition(Context& context, SemIR::Class& class_info,
SemIR::InstId definition_id) -> void {
// Track that this declaration is the definition.
CARBON_CHECK(!class_info.has_definition_started());
class_info.definition_id = definition_id;
class_info.scope_id = context.name_scopes().Add(
definition_id, SemIR::NameId::None, class_info.parent_scope_id);
// Introduce `Self`.
auto self_type_inst_id =
context.types().GetTypeInstId(class_info.self_type_id);
context.name_scopes().AddRequiredName(
class_info.scope_id, SemIR::NameId::SelfType, self_type_inst_id);
context.name_scopes()
.Get(class_info.scope_id)
.set_self_type_id(self_type_inst_id);
}
// Checks that the specified finished adapter definition is valid and builds and
// returns a corresponding complete type witness instruction.
static auto CheckCompleteAdapterClassType(
Context& context, Parse::NodeId node_id, SemIR::ClassId class_id,
llvm::ArrayRef<SemIR::InstId> field_decls,
llvm::ArrayRef<SemIR::InstId> body) -> SemIR::InstId {
const auto& class_info = context.classes().Get(class_id);
if (class_info.base_id.has_value()) {
CARBON_DIAGNOSTIC(AdaptWithBase, Error, "adapter with base class");
CARBON_DIAGNOSTIC(AdaptWithBaseHere, Note, "`base` declaration is here");
context.emitter()
.Build(class_info.adapt_id, AdaptWithBase)
.Note(class_info.base_id, AdaptWithBaseHere)
.Emit();
return SemIR::ErrorInst::InstId;
}
if (!field_decls.empty()) {
CARBON_DIAGNOSTIC(AdaptWithFields, Error, "adapter with fields");
CARBON_DIAGNOSTIC(AdaptWithFieldHere, Note,
"first field declaration is here");
context.emitter()
.Build(class_info.adapt_id, AdaptWithFields)
.Note(field_decls.front(), AdaptWithFieldHere)
.Emit();
return SemIR::ErrorInst::InstId;
}
for (auto inst_id : body) {
if (auto function_decl =
context.insts().TryGetAs<SemIR::FunctionDecl>(inst_id)) {
auto& function = context.functions().Get(function_decl->function_id);
if (function.virtual_modifier ==
SemIR::Function::VirtualModifier::Virtual) {
CARBON_DIAGNOSTIC(AdaptWithVirtual, Error,
"adapter with virtual function");
CARBON_DIAGNOSTIC(AdaptWithVirtualHere, Note,
"first virtual function declaration is here");
context.emitter()
.Build(class_info.adapt_id, AdaptWithVirtual)
.Note(inst_id, AdaptWithVirtualHere)
.Emit();
return SemIR::ErrorInst::InstId;
}
}
}
// The object representation of the adapter is the object representation
// of the adapted type.
auto adapted_type_id =
class_info.GetAdaptedType(context.sem_ir(), SemIR::SpecificId::None);
auto object_repr_id = context.types().GetObjectRepr(adapted_type_id);
return AddInst<SemIR::CompleteTypeWitness>(
context, node_id,
{.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
// TODO: Use InstId from the adapt declaration.
.object_repr_type_inst_id =
context.types().GetTypeInstId(object_repr_id)});
}
static auto AddStructTypeFields(
Context& context,
llvm::SmallVector<SemIR::StructTypeField>& struct_type_fields,
llvm::ArrayRef<SemIR::InstId> field_decls) -> SemIR::StructTypeFieldsId {
for (auto field_decl_id : field_decls) {
auto field_decl = context.insts().GetAs<SemIR::FieldDecl>(field_decl_id);
auto& field = context.fields().Get(field_decl.field_id);
field.index =
SemIR::ElementIndex{static_cast<int>(struct_type_fields.size())};
if (field_decl.type_id == SemIR::ErrorInst::TypeId) {
struct_type_fields.push_back(
{.name_id = field.name_id,
.type_inst_id = SemIR::ErrorInst::TypeInstId});
continue;
}
auto unbound_element_type =
context.sem_ir().types().GetAs<SemIR::UnboundElementType>(
field_decl.type_id);
struct_type_fields.push_back(
{.name_id = field.name_id,
.type_inst_id = unbound_element_type.element_type_inst_id});
}
auto fields_id =
context.struct_type_fields().AddCanonical(struct_type_fields);
return fields_id;
}
// Result of comparing a virtual function in a base class with a potential
// overrider in a derived class.
enum class OverrideMatchResult : uint8_t {
// The functions match.
Match,
// The potential overrider is not marked `override`.
NotAnOverride,
// The names do not match.
NameMismatch,
// The arity (number of explicit parameters) does not match.
ArityMismatch,
};
// Compares a virtual function in a base class with a potential overrider in a
// derived class.
static auto CompareVirtualWithOverrider(const SemIR::Function& base_fn,
const SemIR::Function& derived_fn)
-> OverrideMatchResult {
if (derived_fn.virtual_modifier !=
SemIR::FunctionFields::VirtualModifier::Override) {
return OverrideMatchResult::NotAnOverride;
}
if (derived_fn.name_id != base_fn.name_id) {
return OverrideMatchResult::NameMismatch;
}
if (derived_fn.call_param_ranges.explicit_size() !=
base_fn.call_param_ranges.explicit_size()) {
return OverrideMatchResult::ArityMismatch;
}
// TODO: We should check more thoroughly for compatibility between the two
// functions here, so that we can determine which function is being overridden
// if the base function is in a C++ overload set.
return OverrideMatchResult::Match;
}
// Builds and returns a vtable for the current class, along with a bool
// indicating whether it is a Carbon-native vtable (false for a foreign vtable
// inherited from a C++ base class). Assumes that the virtual functions for the
// class are listed as the top element of the `vtable_stack`.
static auto BuildVtable(Context& context, Parse::ClassDefinitionId node_id,
SemIR::ClassId class_id,
std::optional<SemIR::ClassType> base_class_type,
llvm::ArrayRef<SemIR::InstId> vtable_contents)
-> std::pair<SemIR::VtableId, bool> {
auto base_vtable_id = SemIR::VtableId::None;
auto base_class_specific_id = SemIR::SpecificId::None;
// Get some base class/type/specific info.
if (base_class_type) {
auto& base_class_info = context.classes().Get(base_class_type->class_id);
auto base_vtable_decl_inst_id = base_class_info.vtable_decl_id;
if (base_vtable_decl_inst_id.has_value()) {
LoadImportRef(context, base_vtable_decl_inst_id);
auto canonical_base_vtable_inst_id =
context.constant_values().GetConstantInstId(base_vtable_decl_inst_id);
const auto& base_vtable_decl_inst =
context.insts().GetAs<SemIR::VtableDecl>(
canonical_base_vtable_inst_id);
base_vtable_id = base_vtable_decl_inst.vtable_id;
base_class_specific_id = base_class_type->specific_id;
}
}
const auto& class_info = context.classes().Get(class_id);
auto class_generic_id = class_info.generic_id;
// Wrap vtable entries in SpecificFunctions as needed/in generic classes.
auto build_specific_function =
[&](SemIR::InstId fn_decl_id) -> SemIR::InstId {
if (!class_generic_id.has_value()) {
return fn_decl_id;
}
const auto& fn_decl =
context.insts().GetAs<SemIR::FunctionDecl>(fn_decl_id);
const auto& function = context.functions().Get(fn_decl.function_id);
return GetOrAddInst<SemIR::SpecificFunction>(
context, node_id,
{.type_id =
GetSingletonType(context, SemIR::SpecificFunctionType::TypeInstId),
.callee_id = fn_decl_id,
.specific_id =
context.generics().GetSelfSpecific(function.generic_id)});
};
llvm::SmallVector<SemIR::InstId> vtable;
Set<SemIR::FunctionId, 16> implemented_impls;
bool carbon_native_vtable = true;
// Add vtable entries from the base class, updating them to point to a derived
// class overrider if there is one.
if (base_vtable_id.has_value()) {
const auto& base_vtable = context.vtables().Get(base_vtable_id);
carbon_native_vtable = base_vtable.carbon_native_vtable;
auto base_vtable_inst_block =
context.inst_blocks().Get(base_vtable.virtual_functions_id);
// TODO: Avoid quadratic search. Perhaps build a map from `NameId` to the
// elements of the top of `vtable_stack`.
for (auto base_vtable_entry_id : base_vtable_inst_block) {
if (!base_vtable_entry_id.has_value()) {
// Foreign vtables may have holes in them for information that we don't
// use. Just skip those entries.
CARBON_CHECK(
!context.vtables().Get(base_vtable_id).carbon_native_vtable);
vtable.push_back(SemIR::InstId::None);
continue;
}
auto [derived_vtable_entry_id, derived_vtable_entry_const_id, fn_id,
specific_id] =
DecomposeVirtualFunction(context.sem_ir(), base_vtable_entry_id,
base_class_specific_id);
const auto& fn = context.sem_ir().functions().Get(fn_id);
const auto* i = llvm::find_if(
vtable_contents, [&](SemIR::InstId override_fn_decl_id) -> bool {
const auto& override_fn = context.functions().Get(
context.insts()
.GetAs<SemIR::FunctionDecl>(override_fn_decl_id)
.function_id);
return CompareVirtualWithOverrider(fn, override_fn) ==
OverrideMatchResult::Match;
});
if (i != vtable_contents.end()) {
auto override_fn_id =
context.insts().GetAs<SemIR::FunctionDecl>(*i).function_id;
implemented_impls.Insert(override_fn_id);
// TODO: When the base class is a C++ class, we could have multiple
// potential functions to override. Check against each of them rather
// than trying to override them all.
auto override_or_thunk_id =
BuildThunk(context, fn_id, specific_id, class_info.self_type_id, *i,
/*defer_definition=*/true);
if (override_or_thunk_id != SemIR::ErrorInst::InstId) {
auto override_or_thunk_fn_id =
context.insts()
.GetAs<SemIR::FunctionDecl>(override_or_thunk_id)
.function_id;
auto& override_or_thunk_fn =
context.functions().Get(override_or_thunk_fn_id);
derived_vtable_entry_id =
build_specific_function(override_or_thunk_id);
override_or_thunk_fn.virtual_index = vtable.size();
CARBON_CHECK(override_or_thunk_fn.virtual_index == fn.virtual_index);
}
} else if (auto base_vtable_specific_function =
context.insts().TryGetAs<SemIR::SpecificFunction>(
derived_vtable_entry_id)) {
if (derived_vtable_entry_const_id.is_symbolic()) {
// Create a new instruction here that is otherwise identical to
// `derived_vtable_entry_id` but is dependent within the derived
// class. This ensures we can `GetConstantValueInSpecific` for it
// with the derived class's specific (when forming further derived
// classes, lowering the vtable, etc).
derived_vtable_entry_id = GetOrAddInst<SemIR::SpecificFunction>(
context, node_id,
{.type_id = GetSingletonType(
context, SemIR::SpecificFunctionType::TypeInstId),
.callee_id = base_vtable_specific_function->callee_id,
.specific_id = base_vtable_specific_function->specific_id});
}
}
vtable.push_back(derived_vtable_entry_id);
}
}
// Add any remaining virtual functions from the derived class to the vtable,
// and diagnose any `override fn`s that didn't override anything.
for (auto inst_id : vtable_contents) {
auto fn_decl = context.insts().GetAs<SemIR::FunctionDecl>(inst_id);
auto& fn = context.functions().Get(fn_decl.function_id);
if (fn.virtual_modifier !=
SemIR::FunctionFields::VirtualModifier::Override) {
fn.virtual_index = vtable.size();
vtable.push_back(build_specific_function(inst_id));
} else if (!implemented_impls.Lookup(fn_decl.function_id)) {
CARBON_DIAGNOSTIC(OverrideWithoutVirtualInBase, Error,
"override without compatible virtual in base class");
CARBON_DIAGNOSTIC(OverrideCandidateArityMismatch, Note,
"base class function has {2:more|fewer} parameters "
"({0} vs {1} excluding `self`)",
Diagnostics::IntAsSelect, Diagnostics::IntAsSelect,
Diagnostics::BoolAsSelect);
auto builder = context.emitter().Build(SemIR::LocId(inst_id),
OverrideWithoutVirtualInBase);
if (base_vtable_id.has_value()) {
const auto& base_vtable = context.vtables().Get(base_vtable_id);
auto base_vtable_inst_block =
context.inst_blocks().Get(base_vtable.virtual_functions_id);
for (auto base_vtable_entry_id : base_vtable_inst_block) {
if (!base_vtable_entry_id.has_value()) {
continue;
}
auto [derived_vtable_entry_id, derived_vtable_entry_const_id, fn_id,
specific_id] =
DecomposeVirtualFunction(context.sem_ir(), base_vtable_entry_id,
base_class_specific_id);
const auto& base_fn = context.sem_ir().functions().Get(fn_id);
switch (CompareVirtualWithOverrider(base_fn, fn)) {
case OverrideMatchResult::ArityMismatch:
builder.Note(base_fn.first_owning_decl_id,
OverrideCandidateArityMismatch,
base_fn.call_param_ranges.explicit_size() - 1,
fn.call_param_ranges.explicit_size() - 1,
base_fn.call_param_ranges.explicit_size() >
fn.call_param_ranges.explicit_size());
break;
case OverrideMatchResult::NameMismatch:
// TODO: If the name is similar enough and the overrider otherwise
// matches, emit a note about the potential misspelling.
break;
case OverrideMatchResult::Match:
CARBON_FATAL("Unexpectedly found a matching overrider");
case OverrideMatchResult::NotAnOverride:
CARBON_FATAL("Should only consider `override fn`s here");
}
}
}
builder.Emit();
}
}
auto vtable_id = context.vtables().Add(
{{.class_id = class_id,
.virtual_functions_id = context.inst_blocks().Add(vtable),
.carbon_native_vtable = carbon_native_vtable}});
return {vtable_id, carbon_native_vtable};
}
// Checks that the specified finished class definition is valid and builds and
// returns a corresponding complete type witness instruction.
static auto CheckCompleteClassType(
Context& context, Parse::ClassDefinitionId node_id, SemIR::ClassId class_id,
llvm::ArrayRef<SemIR::InstId> field_decls,
llvm::ArrayRef<SemIR::InstId> vtable_contents,
llvm::ArrayRef<SemIR::InstId> body) -> SemIR::InstId {
auto& class_info = context.classes().Get(class_id);
if (class_info.adapt_id.has_value()) {
return CheckCompleteAdapterClassType(context, node_id, class_id,
field_decls, body);
}
bool defining_vptr = class_info.is_dynamic;
auto base_type_id =
class_info.GetBaseType(context.sem_ir(), SemIR::SpecificId::None);
// TODO: Use InstId from base declaration.
auto base_type_inst_id = context.types().GetTypeInstId(base_type_id);
std::optional<SemIR::ClassType> base_class_type;
if (base_type_id.has_value()) {
// TODO: If the base class is template dependent, we will need to decide
// whether to add a vptr as part of instantiation.
base_class_type = context.types().TryGetAs<SemIR::ClassType>(base_type_id);
if (base_class_type &&
context.classes().Get(base_class_type->class_id).is_dynamic) {
defining_vptr = false;
}
}
llvm::SmallVector<SemIR::StructTypeField> struct_type_fields;
struct_type_fields.reserve(defining_vptr + class_info.base_id.has_value() +
field_decls.size());
if (defining_vptr) {
struct_type_fields.push_back(
{.name_id = SemIR::NameId::Vptr,
.type_inst_id = context.types().GetTypeInstId(
GetPointerType(context, SemIR::VtableType::TypeInstId))});
}
if (base_type_id.has_value()) {
auto base_decl = context.insts().GetAs<SemIR::BaseDecl>(class_info.base_id);
base_decl.index =
SemIR::ElementIndex{static_cast<int>(struct_type_fields.size())};
ReplaceInstPreservingConstantValue(context, class_info.base_id, base_decl);
struct_type_fields.push_back(
{.name_id = SemIR::NameId::Base, .type_inst_id = base_type_inst_id});
}
bool foreign_vtable = false;
if (class_info.is_dynamic) {
auto [vtable_id, carbon_native_vtable] = BuildVtable(
context, node_id, class_id, base_class_type, vtable_contents);
foreign_vtable = !carbon_native_vtable;
auto vptr_type_id = GetPointerType(context, SemIR::VtableType::TypeInstId);
class_info.vtable_decl_id = AddInst<SemIR::VtableDecl>(
context, node_id, {.type_id = vptr_type_id, .vtable_id = vtable_id});
}
auto struct_type_id = GetStructType(
context, AddStructTypeFields(context, struct_type_fields, field_decls));
auto complete_type_witness_id = AddInst<SemIR::CompleteTypeWitness>(
context, node_id,
{.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
.object_repr_type_inst_id =
context.types().GetTypeInstId(struct_type_id)});
class_info.complete_type_witness_id = complete_type_witness_id;
if (foreign_vtable) {
if (class_info.generic_id.has_value()) {
context.TODO(class_info.first_decl_id(),
"generic class deriving from C++ virtual class");
} else {
ExportAndCompleteClassToCpp(
context,
context.types().GetAs<SemIR::ClassType>(class_info.self_type_id));
}
}
return complete_type_witness_id;
}
auto ComputeClassObjectRepr(Context& context, Parse::ClassDefinitionId node_id,
SemIR::ClassId class_id,
llvm::ArrayRef<SemIR::InstId> field_decls,
llvm::ArrayRef<SemIR::InstId> vtable_contents,
llvm::ArrayRef<SemIR::InstId> body) -> void {
auto complete_type_witness_id = CheckCompleteClassType(
context, node_id, class_id, field_decls, vtable_contents, body);
auto& class_info = context.classes().Get(class_id);
class_info.complete_type_witness_id = complete_type_witness_id;
}
auto InNonStaticFieldDecl(Context& context) -> bool {
return context.full_pattern_stack().IsCurrentKindClassScopeVarDecl() &&
!context.decl_introducer_state_stack()
.innermost()
.modifier_set.HasAnyOf(KeywordModifierSet::Static);
}
auto InStaticClassScopeVar(Context& context) -> bool {
return context.full_pattern_stack().IsCurrentKindClassScopeVarDecl() &&
context.decl_introducer_state_stack()
.innermost()
.modifier_set.HasAnyOf(KeywordModifierSet::Static);
}
} // namespace Carbon::Check