/* * Copyright (c) 2021-2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "runtime/include/class_linker.h" #include "os/filesystem.h" #include "runtime/bridge/bridge.h" #include "runtime/class_initializer.h" #include "runtime/include/coretypes/array.h" #include "runtime/include/coretypes/string.h" #include "runtime/include/field.h" #include "runtime/include/itable_builder.h" #include "runtime/include/method.h" #include "runtime/include/runtime.h" #include "runtime/include/runtime_notification.h" #include "libpandabase/macros.h" #include "libpandabase/mem/mem.h" #include "libpandabase/utils/bit_utils.h" #include "libpandabase/utils/span.h" #include "libpandabase/utils/utf.h" #include "libpandafile/class_data_accessor-inl.h" #include "libpandafile/code_data_accessor-inl.h" #include "libpandafile/field_data_accessor-inl.h" #include "libpandafile/method_data_accessor-inl.h" #include "libpandafile/modifiers.h" #include "libpandafile/panda_cache.h" #include "libpandafile/proto_data_accessor-inl.h" #include "runtime/include/tooling/debug_inf.h" #include "trace/trace.h" namespace panda { using Type = panda_file::Type; using SourceLang = panda_file::SourceLang; void ClassLinker::AddPandaFile(std::unique_ptr &&pf, ClassLinkerContext *context) { ASSERT(pf != nullptr); const panda_file::File *file = pf.get(); SCOPED_TRACE_STREAM << __FUNCTION__ << " " << file->GetFilename(); { os::memory::LockHolder lock {panda_files_lock_}; panda_files_.push_back({context, std::forward>(pf)}); } if (context == nullptr || context->IsBootContext()) { boot_panda_files_.push_back(file); } if (Runtime::GetCurrent()->IsInitialized()) { // LoadModule for initial boot files is called in runtime Runtime::GetCurrent()->GetNotificationManager()->LoadModuleEvent(file->GetFilename()); } tooling::DebugInf::AddCodeMetaInfo(file); } void ClassLinker::FreeClassData(Class *class_ptr) { Span fields = class_ptr->GetFields(); if (fields.Size() > 0) { allocator_->Free(fields.begin()); } Span methods = class_ptr->GetMethods(); size_t n = methods.Size() + class_ptr->GetNumCopiedMethods(); if (n > 0) { mem::InternalAllocatorPtr allocator = Runtime::GetCurrent()->GetInternalAllocator(); for (auto &method : methods) { // We create Profiling data in method class via InternalAllocator. // Therefore, we should delete it via InternalAllocator too. allocator->Free(method.GetProfilingData()); } allocator_->Free(methods.begin()); } bool has_own_itable = !class_ptr->IsArrayClass(); auto itable = class_ptr->GetITable().Get(); if (has_own_itable && !itable.Empty()) { for (size_t i = 0; i < itable.Size(); i++) { Span imethods = itable[i].GetMethods(); if (!imethods.Empty()) { allocator_->Free(imethods.begin()); } } allocator_->Free(itable.begin()); } Span interfaces = class_ptr->GetInterfaces(); if (!interfaces.Empty()) { allocator_->Free(interfaces.begin()); } } void ClassLinker::FreeClass(Class *class_ptr) { FreeClassData(class_ptr); GetExtension(class_ptr->GetSourceLang())->FreeClass(class_ptr); } ClassLinker::~ClassLinker() { for (auto &copied_name : copied_names_) { allocator_->Free(reinterpret_cast(const_cast(copied_name))); } } ClassLinker::ClassLinker(mem::InternalAllocatorPtr allocator, std::vector> &&extensions) : allocator_(allocator), copied_names_(allocator->Adapter()) { for (auto &ext : extensions) { extensions_[ToExtensionIndex(ext->GetLanguage())] = std::move(ext); } } template static T *InitializeMemory(T *mem, Args... args) { // CODECHECK-NOLINTNEXTLINE(CPP_RULE_ID_SMARTPOINTER_INSTEADOF_ORIGINPOINTER) return new (mem) T(std::forward(args)...); } bool ClassLinker::Initialize(bool compressed_string_enabled) { if (is_initialized_) { return true; } for (auto &ext : extensions_) { if (ext == nullptr) { continue; } if (!ext->Initialize(this, compressed_string_enabled)) { return false; } } is_initialized_ = true; return true; } bool ClassLinker::InitializeRoots(ManagedThread *thread) { for (auto &ext : extensions_) { if (ext == nullptr) { continue; } if (!ext->InitializeRoots(thread)) { return false; } } return true; } using ClassEntry = std::pair; using PandaFiles = PandaVector; static ClassEntry FindClassInPandaFiles(const uint8_t *descriptor, const PandaFiles &panda_files) { for (auto *pf : panda_files) { auto class_id = pf->GetClassId(descriptor); if (class_id.IsValid() && !pf->IsExternal(class_id)) { return {class_id, pf}; } } return {}; } Class *ClassLinker::FindLoadedClass(const uint8_t *descriptor, ClassLinkerContext *context) { ASSERT(context != nullptr); return context->FindClass(descriptor); } template static size_t GetClassSize(ClassDataAccessorT data_accessor, size_t vtable_size, size_t imt_size, size_t *out_num_sfields) { size_t num_8bit_sfields = 0; size_t num_16bit_sfields = 0; size_t num_32bit_sfields = 0; size_t num_64bit_sfields = 0; size_t num_ref_sfields = 0; size_t num_tagged_sfields = 0; size_t num_sfields = 0; // CODECHECK-NOLINTNEXTLINE(C_RULE_ID_HORIZON_SPACE) data_accessor.template EnumerateStaticFieldTypes([&num_8bit_sfields, &num_16bit_sfields, &num_32bit_sfields, &num_64bit_sfields, &num_ref_sfields, &num_tagged_sfields, &num_sfields](Type field_type) { ++num_sfields; switch (field_type.GetId()) { case Type::TypeId::U1: case Type::TypeId::I8: case Type::TypeId::U8: ++num_8bit_sfields; break; case Type::TypeId::I16: case Type::TypeId::U16: ++num_16bit_sfields; break; case Type::TypeId::I32: case Type::TypeId::U32: case Type::TypeId::F32: ++num_32bit_sfields; break; case Type::TypeId::I64: case Type::TypeId::U64: case Type::TypeId::F64: ++num_64bit_sfields; break; case Type::TypeId::REFERENCE: ++num_ref_sfields; break; case Type::TypeId::TAGGED: ++num_tagged_sfields; break; default: UNREACHABLE(); break; } }); *out_num_sfields = num_sfields; return ClassHelper::ComputeClassSize(vtable_size, imt_size, num_8bit_sfields, num_16bit_sfields, num_32bit_sfields, num_64bit_sfields, num_ref_sfields, num_tagged_sfields); } class ClassDataAccessorWrapper { public: explicit ClassDataAccessorWrapper(panda_file::ClassDataAccessor *data_accessor = nullptr) : data_accessor_(data_accessor) { } template void EnumerateStaticFieldTypes(const Callback &cb) const { data_accessor_->EnumerateFields([cb](panda_file::FieldDataAccessor &fda) { if (!fda.IsStatic()) { return; } cb(Type::GetTypeFromFieldEncoding(fda.GetType())); }); } ~ClassDataAccessorWrapper() = default; DEFAULT_COPY_SEMANTIC(ClassDataAccessorWrapper); DEFAULT_MOVE_SEMANTIC(ClassDataAccessorWrapper); private: panda_file::ClassDataAccessor *data_accessor_; }; ClassLinker::ClassInfo ClassLinker::GetClassInfo(panda_file::ClassDataAccessor *data_accessor, Class *base, Span interfaces, ClassLinkerContext *context) { LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(data_accessor); auto vtable_builder = ctx.CreateVTableBuilder(); auto itable_builder = ctx.CreateITableBuilder(); auto imtable_builder = ctx.CreateIMTableBuilder(); itable_builder->Build(this, base, interfaces, data_accessor->IsInterface()); vtable_builder->Build(data_accessor, base, itable_builder->GetITable(), context); imtable_builder->Build(data_accessor, itable_builder->GetITable()); ClassDataAccessorWrapper data_accessor_wrapper(data_accessor); size_t num_sfields = 0; size_t size = GetClassSize(data_accessor_wrapper, vtable_builder->GetVTableSize(), imtable_builder->GetIMTSize(), &num_sfields); return {size, num_sfields, std::move(vtable_builder), std::move(itable_builder), std::move(imtable_builder)}; } class ClassDataAccessor { public: explicit ClassDataAccessor(Span fields) : fields_(fields) {} template void EnumerateStaticFieldTypes(const Callback &cb) const { for (const auto &field : fields_) { if (!field.IsStatic()) { continue; } cb(field.GetType()); } } ~ClassDataAccessor() = default; DEFAULT_COPY_SEMANTIC(ClassDataAccessor); DEFAULT_MOVE_SEMANTIC(ClassDataAccessor); private: Span fields_; }; ClassLinker::ClassInfo ClassLinker::GetClassInfo(Span methods, Span fields, Class *base, Span interfaces, bool is_interface) { LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(*base); auto vtable_builder = ctx.CreateVTableBuilder(); auto itable_builder = ctx.CreateITableBuilder(); auto imtable_builder = ctx.CreateIMTableBuilder(); itable_builder->Build(this, base, interfaces, is_interface); vtable_builder->Build(methods, base, itable_builder->GetITable(), is_interface); imtable_builder->Build(itable_builder->GetITable(), is_interface); ClassDataAccessor data_accessor(fields); size_t num_sfields = 0; size_t size = GetClassSize(data_accessor, vtable_builder->GetVTableSize(), imtable_builder->GetIMTSize(), &num_sfields); return {size, num_sfields, std::move(vtable_builder), std::move(itable_builder), std::move(imtable_builder)}; } static void LoadMethod(Method *method, panda_file::MethodDataAccessor *method_data_accessor, Class *klass, LanguageContext ctx, const ClassLinkerExtension *ext) { const auto &pf = method_data_accessor->GetPandaFile(); panda_file::ProtoDataAccessor pda(pf, method_data_accessor->GetProtoId()); uint32_t access_flags = method_data_accessor->GetAccessFlags(); auto *method_name = pf.GetStringData(method_data_accessor->GetNameId()).data; if (utf::IsEqual(method_name, ctx.GetCtorName()) || utf::IsEqual(method_name, ctx.GetCctorName())) { access_flags |= ACC_CONSTRUCTOR; } auto code_id = method_data_accessor->GetCodeId(); size_t num_args = method_data_accessor->IsStatic() ? pda.GetNumArgs() : pda.GetNumArgs() + 1; if (!code_id.has_value()) { InitializeMemory(method, klass, &pf, method_data_accessor->GetMethodId(), panda_file::File::EntityId(0), access_flags, num_args, reinterpret_cast(pda.GetShorty().Data())); if (method_data_accessor->IsNative()) { method->SetCompiledEntryPoint(ext->GetNativeEntryPointFor(method)); } else { method->SetInterpreterEntryPoint(); } } else { InitializeMemory(method, klass, &pf, method_data_accessor->GetMethodId(), code_id.value(), access_flags, num_args, reinterpret_cast(pda.GetShorty().Data())); method->SetCompiledEntryPoint(GetCompiledCodeToInterpreterBridge(method)); } } bool ClassLinker::LoadMethods(Class *klass, ClassInfo *class_info, panda_file::ClassDataAccessor *data_accessor, [[maybe_unused]] ClassLinkerErrorHandler *error_handler) { uint32_t num_methods = data_accessor->GetMethodsNumber(); uint32_t num_vmethods = klass->GetNumVirtualMethods(); uint32_t num_smethods = num_methods - num_vmethods; auto &copied_methods = class_info->vtable_builder->GetCopiedMethods(); uint32_t n = num_methods + copied_methods.size(); if (n == 0) { return true; } Span methods {allocator_->AllocArray(n), n}; size_t smethod_idx = num_vmethods; size_t vmethod_idx = 0; LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(*klass); auto *ext = GetExtension(ctx); ASSERT(ext != nullptr); size_t method_index = 0; data_accessor->EnumerateMethods([klass, &smethod_idx, &vmethod_idx, &methods, ctx, ext, &method_index](panda_file::MethodDataAccessor &method_data_accessor) { Method *method = method_data_accessor.IsStatic() ? &methods[smethod_idx++] : &methods[vmethod_idx++]; LoadMethod(method, &method_data_accessor, klass, ctx, ext); method_index++; }); for (size_t i = 0; i < copied_methods.size(); i++) { size_t idx = num_methods + i; InitializeMemory(&methods[idx], copied_methods[i]); methods[idx].SetIsDefaultInterfaceMethod(); } klass->SetMethods(methods, num_vmethods, num_smethods); return true; } bool ClassLinker::LoadFields(Class *klass, panda_file::ClassDataAccessor *data_accessor, [[maybe_unused]] ClassLinkerErrorHandler *error_handler) { uint32_t num_fields = data_accessor->GetFieldsNumber(); if (num_fields == 0) { return true; } uint32_t num_sfields = klass->GetNumStaticFields(); Span fields {allocator_->AllocArray(num_fields), num_fields}; size_t sfields_idx = 0; size_t ifields_idx = num_sfields; data_accessor->EnumerateFields( [klass, &sfields_idx, &ifields_idx, &fields](panda_file::FieldDataAccessor &field_data_accessor) { Field *field = field_data_accessor.IsStatic() ? &fields[sfields_idx++] : &fields[ifields_idx++]; InitializeMemory(field, klass, &field_data_accessor.GetPandaFile(), field_data_accessor.GetFieldId(), field_data_accessor.GetAccessFlags(), panda_file::Type::GetTypeFromFieldEncoding(field_data_accessor.GetType())); }); klass->SetFields(fields, num_sfields); return true; } static void LayoutFieldsWithoutAlignment(size_t size, size_t *offset, size_t *space, PandaList *fields) { while ((space == nullptr || *space >= size) && !fields->empty()) { Field *field = fields->front(); field->SetOffset(*offset); *offset += size; if (space != nullptr) { *space -= size; } fields->pop_front(); } } static uint32_t LayoutReferenceFields(size_t size, size_t *offset, const PandaList &fields) { uint32_t volatile_fields_num = 0; // layout volatile fields firstly for (auto *field : fields) { if (field->IsVolatile()) { volatile_fields_num++; field->SetOffset(*offset); *offset += size; } } for (auto *field : fields) { if (!field->IsVolatile()) { field->SetOffset(*offset); *offset += size; } } return volatile_fields_num; } static size_t LayoutFields(Class *klass, PandaList *tagged_fields, PandaList *fields64, PandaList *fields32, PandaList *fields16, PandaList *fields8, PandaList *ref_fields, bool is_static) { constexpr size_t SIZE_64 = sizeof(uint64_t); constexpr size_t SIZE_32 = sizeof(uint32_t); constexpr size_t SIZE_16 = sizeof(uint16_t); constexpr size_t SIZE_8 = sizeof(uint8_t); size_t offset; if (is_static) { offset = klass->GetStaticFieldsOffset(); } else { offset = (klass->GetBase() != nullptr) ? klass->GetBase()->GetObjectSize() : static_cast(ObjectHeader::ObjectHeaderSize()); } if (!ref_fields->empty()) { offset = AlignUp(offset, ClassHelper::OBJECT_POINTER_SIZE); klass->SetRefFieldsNum(ref_fields->size(), is_static); klass->SetRefFieldsOffset(offset, is_static); auto volatile_num = LayoutReferenceFields(ClassHelper::OBJECT_POINTER_SIZE, &offset, *ref_fields); klass->SetVolatileRefFieldsNum(volatile_num, is_static); } static_assert(coretypes::TaggedValue::TaggedTypeSize() == SIZE_64, "Please fix alignment of the fields of type \"TaggedValue\""); if (!IsAligned(offset) && (!fields64->empty() || !tagged_fields->empty())) { size_t padding = AlignUp(offset, SIZE_64) - offset; LayoutFieldsWithoutAlignment(SIZE_32, &offset, &padding, fields32); LayoutFieldsWithoutAlignment(SIZE_16, &offset, &padding, fields16); LayoutFieldsWithoutAlignment(SIZE_8, &offset, &padding, fields8); offset += padding; } LayoutFieldsWithoutAlignment(coretypes::TaggedValue::TaggedTypeSize(), &offset, nullptr, tagged_fields); LayoutFieldsWithoutAlignment(SIZE_64, &offset, nullptr, fields64); if (!IsAligned(offset) && !fields32->empty()) { size_t padding = AlignUp(offset, SIZE_32) - offset; LayoutFieldsWithoutAlignment(SIZE_16, &offset, &padding, fields16); LayoutFieldsWithoutAlignment(SIZE_8, &offset, &padding, fields8); offset += padding; } LayoutFieldsWithoutAlignment(SIZE_32, &offset, nullptr, fields32); if (!IsAligned(offset) && !fields16->empty()) { size_t padding = AlignUp(offset, SIZE_16) - offset; LayoutFieldsWithoutAlignment(SIZE_8, &offset, &padding, fields8); offset += padding; } LayoutFieldsWithoutAlignment(SIZE_16, &offset, nullptr, fields16); LayoutFieldsWithoutAlignment(SIZE_8, &offset, nullptr, fields8); return offset; } /* static */ bool ClassLinker::LayoutFields(Class *klass, Span fields, bool is_static, [[maybe_unused]] ClassLinkerErrorHandler *error_handler) { PandaList tagged_fields; PandaList fields64; PandaList fields32; PandaList fields16; PandaList fields8; PandaList ref_fields; for (auto &field : fields) { auto type = field.GetType(); if (!type.IsPrimitive()) { ref_fields.push_back(&field); continue; } switch (type.GetId()) { case Type::TypeId::U1: case Type::TypeId::I8: case Type::TypeId::U8: fields8.push_back(&field); break; case Type::TypeId::I16: case Type::TypeId::U16: fields16.push_back(&field); break; case Type::TypeId::I32: case Type::TypeId::U32: case Type::TypeId::F32: fields32.push_back(&field); break; case Type::TypeId::I64: case Type::TypeId::U64: case Type::TypeId::F64: fields64.push_back(&field); break; case Type::TypeId::TAGGED: tagged_fields.push_back(&field); break; default: UNREACHABLE(); break; } } size_t size = panda::LayoutFields(klass, &tagged_fields, &fields64, &fields32, &fields16, &fields8, &ref_fields, is_static); if (!is_static && !klass->IsVariableSize()) { klass->SetObjectSize(size); } return true; } bool ClassLinker::LinkMethods(Class *klass, ClassInfo *class_info, [[maybe_unused]] ClassLinkerErrorHandler *error_handler) { class_info->vtable_builder->UpdateClass(klass); class_info->itable_builder->Resolve(klass); class_info->itable_builder->UpdateClass(klass); class_info->imtable_builder->UpdateClass(klass); return true; } bool ClassLinker::LinkFields(Class *klass, ClassLinkerErrorHandler *error_handler) { if (!LayoutFields(klass, klass->GetStaticFields(), true, error_handler)) { LOG(ERROR, CLASS_LINKER) << "Cannot layout static fields of class '" << klass->GetName() << "'"; return false; } if (!LayoutFields(klass, klass->GetInstanceFields(), false, error_handler)) { LOG(ERROR, CLASS_LINKER) << "Cannot layout instance fields of class '" << klass->GetName() << "'"; return false; } return true; } Class *ClassLinker::LoadBaseClass(panda_file::ClassDataAccessor *cda, LanguageContext ctx, ClassLinkerContext *context, ClassLinkerErrorHandler *error_handler) { auto base_class_id = cda->GetSuperClassId(); auto *ext = GetExtension(ctx); ASSERT(ext != nullptr); if (base_class_id.GetOffset() == 0) { return ext->GetClassRoot(ClassRoot::OBJECT); } auto &pf = cda->GetPandaFile(); auto *base_class = ext->GetClass(pf, base_class_id, context, error_handler); if (base_class == nullptr) { LOG(INFO, CLASS_LINKER) << "Cannot find base class '" << utf::Mutf8AsCString(pf.GetStringData(base_class_id).data) << "' of class '" << utf::Mutf8AsCString(pf.GetStringData(cda->GetClassId()).data) << "' in ctx " << context; return nullptr; } return base_class; } std::optional> ClassLinker::LoadInterfaces(panda_file::ClassDataAccessor *cda, ClassLinkerContext *context, ClassLinkerErrorHandler *error_handler) { ASSERT(context != nullptr); size_t ifaces_num = cda->GetIfacesNumber(); if (ifaces_num == 0) { return Span(nullptr, ifaces_num); } Span ifaces {allocator_->AllocArray(ifaces_num), ifaces_num}; for (size_t i = 0; i < ifaces_num; i++) { auto id = cda->GetInterfaceId(i); auto &pf = cda->GetPandaFile(); auto *iface = GetClass(pf, id, context, error_handler); if (iface == nullptr) { LOG(INFO, CLASS_LINKER) << "Cannot find interface '" << utf::Mutf8AsCString(pf.GetStringData(id).data) << "' of class '" << utf::Mutf8AsCString(pf.GetStringData(cda->GetClassId()).data) << "' in ctx " << context; ASSERT(!ifaces.Empty()); allocator_->Free(ifaces.begin()); return {}; } ifaces[i] = iface; } return ifaces; } // This class is required to clear static unordered_set on return class ClassScopeStaticSetAutoCleaner { public: ClassScopeStaticSetAutoCleaner() = default; explicit ClassScopeStaticSetAutoCleaner(std::unordered_set *set_ptr) { set_ptr_ = set_ptr; } ~ClassScopeStaticSetAutoCleaner() { set_ptr_->clear(); } NO_COPY_SEMANTIC(ClassScopeStaticSetAutoCleaner); NO_MOVE_SEMANTIC(ClassScopeStaticSetAutoCleaner); private: std::unordered_set *set_ptr_; }; static uint64_t GetClassUniqueHash(uint32_t panda_file_hash, uint32_t class_id) { const uint8_t BITS_TO_SHUFFLE = 32; return (static_cast(panda_file_hash) << BITS_TO_SHUFFLE) | static_cast(class_id); } Class *ClassLinker::LoadClass(panda_file::ClassDataAccessor *class_data_accessor, const uint8_t *descriptor, Class *base_class, Span interfaces, ClassLinkerContext *context, ClassLinkerExtension *ext, ClassLinkerErrorHandler *error_handler) { ASSERT(context != nullptr); ClassInfo class_info = GetClassInfo(class_data_accessor, base_class, interfaces, context); auto *klass = ext->CreateClass(descriptor, class_info.vtable_builder->GetVTableSize(), class_info.imtable_builder->GetIMTSize(), class_info.size); klass->SetLoadContext(context); klass->SetBase(base_class); klass->SetInterfaces(interfaces); klass->SetFileId(class_data_accessor->GetClassId()); klass->SetPandaFile(&class_data_accessor->GetPandaFile()); klass->SetAccessFlags(class_data_accessor->GetAccessFlags()); auto &pf = class_data_accessor->GetPandaFile(); auto class_id = class_data_accessor->GetClassId(); klass->SetClassIndex(pf.GetClassIndex(class_id)); klass->SetMethodIndex(pf.GetMethodIndex(class_id)); klass->SetFieldIndex(pf.GetFieldIndex(class_id)); klass->SetNumVirtualMethods(class_info.vtable_builder->GetNumVirtualMethods()); klass->SetNumCopiedMethods(class_info.vtable_builder->GetCopiedMethods().size()); klass->SetNumStaticFields(class_info.num_sfields); if (!LoadMethods(klass, &class_info, class_data_accessor, error_handler)) { FreeClass(klass); LOG(ERROR, CLASS_LINKER) << "Cannot load methods of class '" << descriptor << "'"; return nullptr; } if (!LoadFields(klass, class_data_accessor, error_handler)) { FreeClass(klass); LOG(ERROR, CLASS_LINKER) << "Cannot load fields of class '" << descriptor << "'"; return nullptr; } if (!LinkMethods(klass, &class_info, error_handler)) { FreeClass(klass); LOG(ERROR, CLASS_LINKER) << "Cannot link methods of class '" << descriptor << "'"; return nullptr; } if (!LinkFields(klass, error_handler)) { FreeClass(klass); LOG(ERROR, CLASS_LINKER) << "Cannot link fields of class '" << descriptor << "'"; return nullptr; } return klass; } // CODECHECK-NOLINTNEXTLINE(C_RULE_ID_FUNCTION_SIZE) Class *ClassLinker::LoadClass(const panda_file::File *pf, panda_file::File::EntityId class_id, const uint8_t *descriptor, ClassLinkerContext *context, ClassLinkerErrorHandler *error_handler) { ASSERT(!pf->IsExternal(class_id)); ASSERT(context != nullptr); panda_file::ClassDataAccessor class_data_accessor(*pf, class_id); LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(&class_data_accessor); // This set is used to find out if the class is its own superclass static thread_local std::unordered_set anti_circulation_id_set; ClassScopeStaticSetAutoCleaner class_set_auto_cleaner_on_return(&anti_circulation_id_set); auto *ext = GetExtension(ctx); if (ext == nullptr) { PandaStringStream ss; ss << "Cannot load class '" << descriptor << "' as class linker hasn't " << ctx << " language extension"; LOG(ERROR, CLASS_LINKER) << ss.str(); OnError(error_handler, Error::CLASS_NOT_FOUND, ss.str()); return nullptr; } Class *base_class = nullptr; bool need_load_base = IsInitialized() || !utf::IsEqual(ctx.GetObjectClassDescriptor(), descriptor); if (need_load_base) { uint32_t class_id_int = class_id.GetOffset(); uint32_t panda_file_hash = pf->GetFilenameHash(); if (anti_circulation_id_set.find(GetClassUniqueHash(panda_file_hash, class_id_int)) == anti_circulation_id_set.end()) { anti_circulation_id_set.insert(GetClassUniqueHash(panda_file_hash, class_id_int)); } else { ThrowClassCircularityError(utf::Mutf8AsCString(pf->GetStringData(class_data_accessor.GetClassId()).data), ctx); return nullptr; } base_class = LoadBaseClass(&class_data_accessor, ctx, context, error_handler); if (base_class == nullptr) { LOG(INFO, CLASS_LINKER) << "Cannot load base class of class '" << descriptor << "'"; return nullptr; } } auto res = LoadInterfaces(&class_data_accessor, context, error_handler); if (!res) { LOG(INFO, CLASS_LINKER) << "Cannot load interfaces of class '" << descriptor << "'"; return nullptr; } auto *klass = LoadClass(&class_data_accessor, descriptor, base_class, res.value(), context, ext, error_handler); if (klass == nullptr) { return nullptr; } if (LIKELY(ext->CanInitializeClasses())) { ext->InitializeClass(klass); klass->SetState(Class::State::LOADED); } Runtime::GetCurrent()->GetNotificationManager()->ClassLoadEvent(klass); auto *other_klass = context->InsertClass(klass); if (other_klass != nullptr) { // Someone has created the class in the other thread (increase the critical section?) FreeClass(klass); return other_klass; } RemoveCreatedClassInExtension(klass); Runtime::GetCurrent()->GetNotificationManager()->ClassPrepareEvent(klass); return klass; } static const uint8_t *CopyMutf8String(mem::InternalAllocatorPtr allocator, const uint8_t *descriptor) { size_t size = utf::Mutf8Size(descriptor) + 1; // + 1 - null terminate auto *ptr = allocator->AllocArray(size); (void)memcpy_s(ptr, size, descriptor, size); return ptr; } Class *ClassLinker::BuildClass(const uint8_t *descriptor, bool need_copy_descriptor, uint32_t access_flags, Span methods, Span fields, Class *base_class, Span interfaces, ClassLinkerContext *context, bool is_interface) { ASSERT(context != nullptr); if (need_copy_descriptor) { descriptor = CopyMutf8String(allocator_, descriptor); os::memory::LockHolder lock(copied_names_lock_); copied_names_.push_front(descriptor); } auto *ext = GetExtension(base_class->GetSourceLang()); ASSERT(ext != nullptr); ClassInfo class_info = GetClassInfo(methods, fields, base_class, interfaces, is_interface); // Need to protect ArenaAllocator and loaded_classes_ auto *klass = ext->CreateClass(descriptor, class_info.vtable_builder->GetVTableSize(), class_info.imtable_builder->GetIMTSize(), class_info.size); klass->SetLoadContext(context); klass->SetBase(base_class); klass->SetInterfaces(interfaces); klass->SetAccessFlags(access_flags); klass->SetNumVirtualMethods(class_info.vtable_builder->GetNumVirtualMethods()); klass->SetNumCopiedMethods(class_info.vtable_builder->GetCopiedMethods().size()); klass->SetNumStaticFields(class_info.num_sfields); ASSERT(klass->GetNumCopiedMethods() == 0); size_t num_smethods = methods.size() - klass->GetNumVirtualMethods(); klass->SetMethods(methods, klass->GetNumVirtualMethods(), num_smethods); klass->SetFields(fields, klass->GetNumStaticFields()); for (auto &method : methods) { method.SetClass(klass); } for (auto &field : fields) { field.SetClass(klass); } if (!LinkMethods(klass, &class_info, ext->GetErrorHandler())) { LOG(ERROR, CLASS_LINKER) << "Cannot link class methods '" << descriptor << "'"; return nullptr; } if (!LinkFields(klass, ext->GetErrorHandler())) { LOG(ERROR, CLASS_LINKER) << "Cannot link class fields '" << descriptor << "'"; return nullptr; } ext->InitializeClass(klass); klass->SetState(Class::State::LOADED); Runtime::GetCurrent()->GetNotificationManager()->ClassLoadEvent(klass); auto *other_klass = context->InsertClass(klass); if (other_klass != nullptr) { // Someone has created the class in the other thread (increase the critical section?) FreeClass(klass); return other_klass; } RemoveCreatedClassInExtension(klass); Runtime::GetCurrent()->GetNotificationManager()->ClassPrepareEvent(klass); return klass; } Class *ClassLinker::CreateArrayClass(ClassLinkerExtension *ext, const uint8_t *descriptor, bool need_copy_descriptor, Class *component_class) { if (need_copy_descriptor) { descriptor = CopyMutf8String(allocator_, descriptor); os::memory::LockHolder lock(copied_names_lock_); copied_names_.push_front(descriptor); } auto *array_class = ext->CreateClass(descriptor, ext->GetArrayClassVTableSize(), ext->GetArrayClassIMTSize(), ext->GetArrayClassSize()); array_class->SetLoadContext(component_class->GetLoadContext()); ext->InitializeArrayClass(array_class, component_class); return array_class; } Class *ClassLinker::LoadArrayClass(const uint8_t *descriptor, bool need_copy_descriptor, ClassLinkerContext *context, ClassLinkerErrorHandler *error_handler) { Span sp(descriptor, 1); Class *component_class = GetClass(sp.cend(), need_copy_descriptor, context, error_handler); if (component_class == nullptr) { return nullptr; } if (UNLIKELY(component_class->GetType().GetId() == panda_file::Type::TypeId::VOID)) { OnError(error_handler, Error::NO_CLASS_DEF, "Try to create array with void component type"); return nullptr; } auto *ext = GetExtension(component_class->GetSourceLang()); ASSERT(ext != nullptr); auto *component_class_context = component_class->GetLoadContext(); ASSERT(component_class_context != nullptr); if (component_class_context != context) { auto *loaded_class = FindLoadedClass(descriptor, component_class_context); if (loaded_class != nullptr) { return loaded_class; } } auto *array_class = CreateArrayClass(ext, descriptor, need_copy_descriptor, component_class); Runtime::GetCurrent()->GetNotificationManager()->ClassLoadEvent(array_class); auto *other_klass = component_class_context->InsertClass(array_class); if (other_klass != nullptr) { FreeClass(array_class); return other_klass; } RemoveCreatedClassInExtension(array_class); Runtime::GetCurrent()->GetNotificationManager()->ClassPrepareEvent(array_class); return array_class; } static PandaString PandaFilesToString(const PandaVector &panda_files) { PandaStringStream ss; ss << "["; size_t n = panda_files.size(); for (size_t i = 0; i < n; i++) { ss << panda_files[i]->GetFilename(); if (i < n - 1) { ss << ", "; } } ss << "]"; return ss.str(); } // CODECHECK-NOLINTNEXTLINE(C_RULE_ID_HORIZON_SPACE) Class *ClassLinker::GetClass(const uint8_t *descriptor, bool need_copy_descriptor, ClassLinkerContext *context, ClassLinkerErrorHandler *error_handler /* = nullptr */) { ASSERT(context != nullptr); Class *cls = FindLoadedClass(descriptor, context); if (cls != nullptr) { return cls; } if (ClassHelper::IsArrayDescriptor(descriptor)) { return LoadArrayClass(descriptor, need_copy_descriptor, context, error_handler); } if (context->IsBootContext()) { auto [class_id, panda_file] = FindClassInPandaFiles(descriptor, boot_panda_files_); if (!class_id.IsValid()) { PandaStringStream ss; ss << "Cannot find class " << descriptor << " in boot panda files: " << PandaFilesToString(boot_panda_files_); OnError(error_handler, Error::CLASS_NOT_FOUND, ss.str()); return nullptr; } return LoadClass(panda_file, class_id, panda_file->GetStringData(class_id).data, context, error_handler); } return context->LoadClass(descriptor, need_copy_descriptor, error_handler); } Class *ClassLinker::GetClass(const panda_file::File &pf, panda_file::File::EntityId id, ClassLinkerContext *context, ClassLinkerErrorHandler *error_handler /* = nullptr */) { ASSERT(context != nullptr); Class *cls = pf.GetPandaCache()->GetClassFromCache(id); if (cls != nullptr) { return cls; } const uint8_t *descriptor = pf.GetStringData(id).data; cls = FindLoadedClass(descriptor, context); if (cls != nullptr) { pf.GetPandaCache()->SetClassCache(id, cls); return cls; } if (ClassHelper::IsArrayDescriptor(descriptor)) { cls = LoadArrayClass(descriptor, false, context, error_handler); if (LIKELY(cls != nullptr)) { pf.GetPandaCache()->SetClassCache(id, cls); } return cls; } if (context->IsBootContext()) { const panda_file::File *pf_ptr = nullptr; panda_file::File::EntityId ext_id; std::tie(ext_id, pf_ptr) = FindClassInPandaFiles(descriptor, boot_panda_files_); if (!ext_id.IsValid()) { PandaStringStream ss; ss << "Cannot find class " << descriptor << " in boot panda files: " << PandaFilesToString(boot_panda_files_); OnError(error_handler, Error::CLASS_NOT_FOUND, ss.str()); return nullptr; } cls = LoadClass(pf_ptr, ext_id, descriptor, context, error_handler); if (LIKELY(cls != nullptr)) { pf.GetPandaCache()->SetClassCache(id, cls); } return cls; } return context->LoadClass(descriptor, false, error_handler); } Method *ClassLinker::GetMethod(const panda_file::File &pf, panda_file::File::EntityId id, // CODECHECK-NOLINTNEXTLINE(C_RULE_ID_HORIZON_SPACE) ClassLinkerContext *context /* = nullptr */, ClassLinkerErrorHandler *error_handler /* = nullptr */) { Method *method = pf.GetPandaCache()->GetMethodFromCache(id); if (method != nullptr) { return method; } panda_file::MethodDataAccessor method_data_accessor(pf, id); auto class_id = method_data_accessor.GetClassId(); if (context == nullptr) { panda_file::ClassDataAccessor class_data_accessor(pf, class_id); auto lang = class_data_accessor.GetSourceLang(); if (!lang) { LOG(INFO, CLASS_LINKER) << "Cannot resolve language context for class_id " << class_id << " in file " << pf.GetFilename(); return nullptr; } auto *extension = GetExtension(lang.value()); context = extension->GetBootContext(); } Class *klass = GetClass(pf, class_id, context, error_handler); if (klass == nullptr) { auto class_name = pf.GetStringData(class_id).data; LOG(INFO, CLASS_LINKER) << "Cannot find class '" << class_name << "' in ctx " << context; return nullptr; } method = GetMethod(klass, method_data_accessor, error_handler); if (LIKELY(method != nullptr)) { pf.GetPandaCache()->SetMethodCache(id, method); } return method; } Method *ClassLinker::GetMethod(const Method &caller, panda_file::File::EntityId id, ClassLinkerErrorHandler *error_handler /* = nullptr */) { auto *pf = caller.GetPandaFile(); Method *method = pf->GetPandaCache()->GetMethodFromCache(id); if (method != nullptr) { return method; } panda_file::MethodDataAccessor method_data_accessor(*pf, id); auto class_id = method_data_accessor.GetClassId(); auto *context = caller.GetClass()->GetLoadContext(); auto *ext = GetExtension(caller.GetClass()->GetSourceLang()); Class *klass = ext->GetClass(*pf, class_id, context, error_handler); if (klass == nullptr) { auto class_name = pf->GetStringData(class_id).data; LOG(INFO, CLASS_LINKER) << "Cannot find class '" << class_name << "' in ctx " << context; return nullptr; } method = GetMethod(klass, method_data_accessor, error_handler == nullptr ? ext->GetErrorHandler() : error_handler); if (LIKELY(method != nullptr)) { pf->GetPandaCache()->SetMethodCache(id, method); } return method; } Method *ClassLinker::GetMethod(const Class *klass, const panda_file::MethodDataAccessor &method_data_accessor, ClassLinkerErrorHandler *error_handler) { Method *method; auto id = method_data_accessor.GetMethodId(); const auto &pf = method_data_accessor.GetPandaFile(); if (!method_data_accessor.IsExternal() && klass->GetPandaFile() == &pf) { bool is_static = method_data_accessor.IsStatic(); auto pred = [id](const Method &m) { return m.GetFileId() == id; }; if (klass->IsInterface()) { method = is_static ? klass->FindStaticInterfaceMethod(pred) : klass->FindVirtualInterfaceMethod(pred); } else { method = is_static ? klass->FindStaticClassMethod(pred) : klass->FindVirtualClassMethod(pred); } if (method == nullptr) { PandaStringStream ss; ss << "Cannot find method '" << pf.GetStringData(method_data_accessor.GetNameId()).data << "' in class '" << klass->GetName() << "'"; OnError(error_handler, Error::METHOD_NOT_FOUND, ss.str()); return nullptr; } return method; } auto name = pf.GetStringData(method_data_accessor.GetNameId()); auto proto = Method::Proto(pf, method_data_accessor.GetProtoId()); auto pred = [name, &proto](const Method &m) { return m.GetName() == name && m.GetProto() == proto; }; if (klass->IsInterface()) { method = klass->FindInterfaceMethod(pred); } else { method = klass->FindClassMethod(pred); if (method == nullptr && klass->IsAbstract()) { method = klass->FindInterfaceMethod(pred); } } if (method == nullptr) { PandaStringStream ss; ss << "Cannot find method '" << pf.GetStringData(method_data_accessor.GetNameId()).data << "' in class '" << klass->GetName() << "'"; OnError(error_handler, Error::METHOD_NOT_FOUND, ss.str()); return nullptr; } LOG_IF(method->IsStatic() != method_data_accessor.IsStatic(), FATAL, CLASS_LINKER) << "Expected ACC_STATIC for method " << pf.GetStringData(method_data_accessor.GetNameId()).data << " in class " << klass->GetName() << " does not match loaded value"; return method; } Field *ClassLinker::GetFieldById(Class *klass, const panda_file::FieldDataAccessor &field_data_accessor, ClassLinkerErrorHandler *error_handler) { bool is_static = field_data_accessor.IsStatic(); auto &pf = field_data_accessor.GetPandaFile(); auto id = field_data_accessor.GetFieldId(); auto pred = [id](const Field &field) { return field.GetFileId() == id; }; Field *field = is_static ? klass->FindStaticField(pred) : klass->FindInstanceField(pred); if (field == nullptr) { PandaStringStream ss; ss << "Cannot find field '" << pf.GetStringData(field_data_accessor.GetNameId()).data << "' in class '" << klass->GetName() << "'"; OnError(error_handler, Error::FIELD_NOT_FOUND, ss.str()); return nullptr; } pf.GetPandaCache()->SetFieldCache(id, field); return field; } Field *ClassLinker::GetFieldBySignature(Class *klass, const panda_file::FieldDataAccessor &field_data_accessor, ClassLinkerErrorHandler *error_handler) { auto &pf = field_data_accessor.GetPandaFile(); auto id = field_data_accessor.GetFieldId(); auto field_name = pf.GetStringData(field_data_accessor.GetNameId()); auto field_type = panda_file::Type::GetTypeFromFieldEncoding(field_data_accessor.GetType()); Field *field = klass->FindField([&](const Field &fld) { if (field_type == fld.GetType() && field_name == fld.GetName()) { if (!field_type.IsReference()) { return true; } // compare field class type if (&pf == fld.GetPandaFile() && id == fld.GetFileId()) { return true; } panda_file::FieldDataAccessor fda(*fld.GetPandaFile(), fld.GetFileId()); if (pf.GetStringData(panda_file::File::EntityId(field_data_accessor.GetType())) == fld.GetPandaFile()->GetStringData(panda_file::File::EntityId(fda.GetType()))) { return true; } } return false; }); if (field == nullptr) { PandaStringStream ss; ss << "Cannot find field '" << field_name.data << "' in class '" << klass->GetName() << "'"; OnError(error_handler, Error::FIELD_NOT_FOUND, ss.str()); return nullptr; } pf.GetPandaCache()->SetFieldCache(id, field); return field; } Field *ClassLinker::GetField(const panda_file::File &pf, panda_file::File::EntityId id, // CODECHECK-NOLINTNEXTLINE(C_RULE_ID_HORIZON_SPACE) ClassLinkerContext *context /* = nullptr */, ClassLinkerErrorHandler *error_handler /* = nullptr */) { Field *field = pf.GetPandaCache()->GetFieldFromCache(id); if (field != nullptr) { return field; } panda_file::FieldDataAccessor field_data_accessor(pf, id); Class *klass = GetClass(pf, field_data_accessor.GetClassId(), context, error_handler); if (klass == nullptr) { auto class_name = pf.GetStringData(field_data_accessor.GetClassId()).data; LOG(INFO, CLASS_LINKER) << "Cannot find class '" << class_name << "' in ctx " << context; return nullptr; } if (!field_data_accessor.IsExternal() && klass->GetPandaFile() == &pf) { field = GetFieldById(klass, field_data_accessor, error_handler); } else { field = GetFieldBySignature(klass, field_data_accessor, error_handler); } return field; } Method *ClassLinker::GetMethod(std::string_view panda_file, panda_file::File::EntityId id) { os::memory::LockHolder lock(panda_files_lock_); for (auto &data : panda_files_) { if (data.pf->GetFilename() == panda_file) { return GetMethod(*data.pf, id, data.context); } } return nullptr; } bool ClassLinker::InitializeClass(ManagedThread *thread, Class *klass) { ASSERT(klass != nullptr); if (klass->IsInitialized()) { return true; } LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(*klass); return ctx.InitializeClass(this, thread, klass); } size_t ClassLinker::NumLoadedClasses() { size_t sum = 0; for (auto &ext : extensions_) { if (ext == nullptr) { continue; } sum += ext->NumLoadedClasses(); } return sum; } void ClassLinker::VisitLoadedClasses(size_t flag) { for (auto &ext : extensions_) { if (ext == nullptr) { continue; } ext->VisitLoadedClasses(flag); } } void ClassLinker::OnError(ClassLinkerErrorHandler *error_handler, ClassLinker::Error error, const PandaString &msg) { if (error_handler != nullptr) { error_handler->OnError(error, msg); } } Field *ClassLinker::GetField(const Method &caller, panda_file::File::EntityId id, ClassLinkerErrorHandler *error_handler /* = nullptr */) { Field *field = caller.GetPandaFile()->GetPandaCache()->GetFieldFromCache(id); if (field != nullptr) { return field; } auto *ext = GetExtension(caller.GetClass()->GetSourceLang()); field = GetField(*caller.GetPandaFile(), id, caller.GetClass()->GetLoadContext(), error_handler == nullptr ? ext->GetErrorHandler() : error_handler); if (LIKELY(field != nullptr)) { caller.GetPandaFile()->GetPandaCache()->SetFieldCache(id, field); } return field; } void ClassLinker::RemoveCreatedClassInExtension(Class *klass) { if (klass == nullptr) { return; } auto ext = GetExtension(klass->GetSourceLang()); if (ext != nullptr) { ext->OnClassPrepared(klass); } } } // namespace panda