From 7f7d7fd74d77bc0fc19a4107411509f262577e37 Mon Sep 17 00:00:00 2001 From: Anna Antipina Date: Wed, 28 May 2025 11:35:38 +0300 Subject: [PATCH 1/3] Provide pandafile type descriptor for union types - Provide pandafile type descriptors for union types. Format: '{' 'U' TypeDescriptor+ '}', U stands for union, while { ... } notation may be extended. - Transfer semi-LUB reduction for union descriptor type from FE to Runtime - Implement processing the descriptor in assembler, disassembler, verifier. - Implement canonicalization for the descriptor and checking this in verifier. - Implement union descriptors in ANI - Add tests for ClassLinker, verifier, assembler, disassembler Testing: ninja all tests Change-Id: I2908cd7233a1ecacd55f9089ba2a7721330b00d8 Signed-off-by: Anna Antipina Signed-off-by: Denis Slynko Signed-off-by: Redkin Mikhail --- static_core/assembler/assembly-emitter.cpp | 2 +- static_core/assembler/assembly-parser.cpp | 117 +++++++++++++-- static_core/assembler/assembly-parser.h | 4 + static_core/assembler/assembly-type.cpp | 141 ++++++++++++++---- static_core/assembler/assembly-type.h | 103 +++++++++++-- static_core/assembler/tests/emitter_test.cpp | 91 ++++++++++- static_core/assembler/tests/parser_test.cpp | 90 +++++++++++ static_core/codecheck_ignore.json | 1 + static_core/disassembler/disassembler.cpp | 2 +- .../disassembler/tests/records_test.cpp | 89 ++++++++++- static_core/libpandafile/file.h | 5 + static_core/libpandafile/templates/type.h.erb | 1 + .../plugins/ets/runtime/ani/ani_mangle.cpp | 46 ++++++ .../runtime/ets_class_linker_extension.cpp | 61 ++++++++ .../ets/runtime/ets_class_linker_extension.h | 5 + .../ets/runtime/ets_vtable_builder.cpp | 135 +++++++++-------- .../plugins/ets/runtime/ets_vtable_builder.h | 78 +++++++++- .../ets/runtime/types/ets_method_signature.h | 17 ++- static_core/plugins/ets/tests/CMakeLists.txt | 1 + .../tests/mangling/mangle_signature_test.cpp | 77 ++++++++++ .../tests/mangling/mangle_signature_test.ets | 8 + .../object_ops/object_instance_of_test.cpp | 3 +- .../ets/tests/runtime/types/CMakeLists.txt | 1 + .../runtime/types/ets_runtime_linker_test.cpp | 91 ++++++++++- .../tests/runtime/types/ets_union_test.cpp | 116 ++++++++++++++ .../ets/tests/verify_unions/CMakeLists.txt | 68 +++++++++ .../tests/verify_unions/correct_union_arg.pa | 37 +++++ .../verify_unions/correct_union_arg_2.pa | 35 +++++ .../verify_unions/correct_union_override.pa | 57 +++++++ .../verify_unions/correct_union_override_2.pa | 52 +++++++ .../ets/tests/verify_unions/neg_union_arg.pa | 37 +++++ .../verify_unions/neg_union_arg_redecl.pa | 49 ++++++ .../verify_unions/neg_union_override_multi.pa | 57 +++++++ static_core/runtime/BUILD.gn | 1 + static_core/runtime/CMakeLists.txt | 2 +- static_core/runtime/class_helper.cpp | 72 ++++++++- static_core/runtime/class_linker.cpp | 16 ++ static_core/runtime/include/class_helper.h | 7 +- static_core/runtime/include/class_linker.h | 4 + .../runtime/include/class_linker_extension.h | 12 ++ .../include/vtable_builder_variance-inl.h | 5 + static_core/verification/messages.yaml | 7 + static_core/verification/type/type_system.cpp | 6 + 43 files changed, 1680 insertions(+), 129 deletions(-) create mode 100644 static_core/plugins/ets/tests/runtime/types/ets_union_test.cpp create mode 100644 static_core/plugins/ets/tests/verify_unions/CMakeLists.txt create mode 100644 static_core/plugins/ets/tests/verify_unions/correct_union_arg.pa create mode 100644 static_core/plugins/ets/tests/verify_unions/correct_union_arg_2.pa create mode 100644 static_core/plugins/ets/tests/verify_unions/correct_union_override.pa create mode 100644 static_core/plugins/ets/tests/verify_unions/correct_union_override_2.pa create mode 100644 static_core/plugins/ets/tests/verify_unions/neg_union_arg.pa create mode 100644 static_core/plugins/ets/tests/verify_unions/neg_union_arg_redecl.pa create mode 100644 static_core/plugins/ets/tests/verify_unions/neg_union_override_multi.pa diff --git a/static_core/assembler/assembly-emitter.cpp b/static_core/assembler/assembly-emitter.cpp index ad37aaf4ca..72bf258578 100644 --- a/static_core/assembler/assembly-emitter.cpp +++ b/static_core/assembler/assembly-emitter.cpp @@ -1958,7 +1958,7 @@ TypeItem *AsmEmitter::GetTypeItem( return Find(primitiveTypes, type.GetId()); } - if (type.IsArray()) { + if (type.IsArray() || type.IsUnion()) { return items->GetOrCreateForeignClassItem(type.GetDescriptor()); } diff --git a/static_core/assembler/assembly-parser.cpp b/static_core/assembler/assembly-parser.cpp index 50e6b5b904..2eb0f483e6 100644 --- a/static_core/assembler/assembly-parser.cpp +++ b/static_core/assembler/assembly-parser.cpp @@ -104,7 +104,8 @@ bool Parser::ParseType(Type *type) { ASSERT(TypeValidName()); - std::string componentName(context_.GiveToken()); + std::string componentName; + GetName(&componentName); size_t rank = 0; ++context_; @@ -1212,12 +1213,9 @@ bool Parser::ParamValidName() return context_.ValidateParameterName(currFunc_->GetParamsNum()); } -bool Parser::PrefixedValidName(BracketOptions options) +bool Parser::PrefixedValidNameToken(BracketOptions options, bool isUnion) { auto s = context_.GiveToken(); - if (!IsNonDigit(s[0])) { - return false; - } for (size_t i = 1; i < s.size(); ++i) { char const c = s[i]; if (c == '.') { @@ -1241,8 +1239,70 @@ bool Parser::PrefixedValidName(BracketOptions options) if (IsAllowAngleBrackets(options) && (c == '<' || c == '>')) { continue; } + return isUnion && (c == ','); + } + return true; +} + +bool Parser::PrefixedValidUnionName(BracketOptions options) +{ + context_++; + while (*context_ != Token::Type::DEL_BRACE_R) { + if (*context_ == Token::Type::DEL_BRACE_L) { + if (!PrefixedValidUnionName(options)) { + return false; + } + } else { + if (!PrefixedValidNameToken(options, true)) { + return false; + } + } + context_++; + } + return true; +} + +bool Parser::PrefixedValidName(BracketOptions options) +{ + if (!IsNonDigit(context_.GiveToken()[0])) { return false; } + if (*context_ != Token::Type::DEL_BRACE_L) { + return PrefixedValidNameToken(options); + } + if (!PrefixedValidUnionName(options)) { + return false; + } + context_++; + if (*context_ == Token::Type::DEL_SQUARE_BRACKET_L) { + // process '[' and ']' + if (!PrefixedValidNameToken(options)) { + return false; + } + } else { + context_--; + } + + // move to the start of union name + auto idx = [&]() { + if (*context_ == Token::Type::DEL_BRACE_R) { + context_--; + return 1; + } + return 0; + }(); + while (*context_ != Token::Type::DEL_BRACE_L || idx != 0) { + if (*context_ == Token::Type::DEL_BRACE_R) { + idx++; + } + if (*context_ == Token::Type::DEL_BRACE_L) { + idx--; + if (idx == 0) { + break; + } + } + context_--; + } return true; } @@ -1483,7 +1543,8 @@ bool Parser::ParseOperandSignatureTypesList(std::string *sign) return true; } - if (*context_ != Token::Type::DEL_COMMA && *context_ != Token::Type::ID) { + if (*context_ != Token::Type::DEL_COMMA && *context_ != Token::Type::ID && + *context_ != Token::Type::DEL_BRACE_L) { break; } @@ -1495,7 +1556,7 @@ bool Parser::ParseOperandSignatureTypesList(std::string *sign) return false; } - if (*context_ != Token::Type::ID) { + if (*context_ != Token::Type::ID && *context_ != Token::Type::DEL_BRACE_L) { context_.err = GetError("Expected signature arguments", Error::ErrorType::ERR_BAD_SIGNATURE_PARAMETERS); return false; } @@ -2093,6 +2154,31 @@ bool Parser::ParseArrayFullSign() return true; } +void Parser::GetUnionName(std::string *name) +{ + ASSERT(*context_ == Token::Type::DEL_BRACE_L); + context_++; + while (*context_ != Token::Type::DEL_BRACE_R) { + if (*context_ == Token::Type::DEL_BRACE_L) { + (*name) += std::string(context_.GiveToken()); + GetUnionName(name); + } else { + (*name) += std::string(context_.GiveToken()); + } + context_++; + } + (*name) += std::string(context_.GiveToken()); +} + +void Parser::GetName(std::string *name) +{ + auto isUnion = (*context_ == Token::Type::DEL_BRACE_L); + *name = std::string(context_.GiveToken()); + if (isUnion) { + GetUnionName(name); + } +} + bool Parser::ParseRecordName() { LOG(DEBUG, ASSEMBLER) << "started searching for record name (line " << lineStric_ @@ -2106,8 +2192,8 @@ bool Parser::ParseRecordName() context_.err = GetError("Invalid name of the record.", Error::ErrorType::ERR_BAD_RECORD_NAME); return false; } - - auto recordName = std::string(context_.GiveToken()); + std::string recordName; + GetName(&recordName); context_++; if (*context_ == Token::Type::DEL_SQUARE_BRACKET_L) { auto dim = ParseMultiArrayHallmark(); @@ -2122,9 +2208,10 @@ bool Parser::ParseRecordName() context_--; } - auto iter = program_.recordTable.find(recordName); + auto recordType = Type::FromName(recordName); + auto iter = program_.recordTable.find(recordType.GetName()); if (iter == program_.recordTable.end() || !iter->second.fileLocation->isDefined) { - SetRecordInformation(recordName); + SetRecordInformation(recordType.GetName()); } else { context_.err = GetError("This record already exists.", Error::ErrorType::ERR_BAD_ID_RECORD); return false; @@ -2267,7 +2354,8 @@ bool Parser::ParseFunctionReturn() bool Parser::TypeValidName() { - if (Type::GetId(context_.GiveToken()) != panda_file::Type::TypeId::REFERENCE) { + if (Type::GetId(context_.GiveToken()) != panda_file::Type::TypeId::REFERENCE && + (*context_ != Token::Type::DEL_BRACE_L)) { return true; } @@ -2276,7 +2364,7 @@ bool Parser::TypeValidName() bool Parser::ParseFunctionArg() { - if (*context_ != Token::Type::ID) { + if (*context_ != Token::Type::ID && *context_ != Token::Type::DEL_BRACE_L) { context_.err = GetError("Expected identifier.", Error::ErrorType::ERR_BAD_FUNCTION_PARAMETERS); return false; } @@ -2347,7 +2435,8 @@ bool Parser::ParseFunctionArgs() return false; } - if (context_.id != Token::Type::DEL_COMMA && context_.id != Token::Type::ID) { + if (context_.id != Token::Type::DEL_COMMA && context_.id != Token::Type::ID && + context_.id != Token::Type::DEL_BRACE_L) { break; } diff --git a/static_core/assembler/assembly-parser.h b/static_core/assembler/assembly-parser.h index 7120ff5194..3fd2d651e2 100644 --- a/static_core/assembler/assembly-parser.h +++ b/static_core/assembler/assembly-parser.h @@ -181,7 +181,11 @@ private: bool ParseFunctionArgComma(bool &comma); bool ParseFunctionArgs(); bool ParseType(Type *type); + bool PrefixedValidUnionName(BracketOptions options); bool PrefixedValidName(BracketOptions options = BracketOptions::NOT_ALLOW_BRACKETS); + bool PrefixedValidNameToken(BracketOptions options, bool isUnion = false); + void GetName(std::string *name); + void GetUnionName(std::string *name); bool ParseMetaListComma(bool &comma, bool eq); bool MeetExpMetaList(bool eq); bool BuildMetaListAttr(bool &eq, std::string &attributeName, std::string &attributeValue); diff --git a/static_core/assembler/assembly-type.cpp b/static_core/assembler/assembly-type.cpp index a91277e844..2807748218 100644 --- a/static_core/assembler/assembly-type.cpp +++ b/static_core/assembler/assembly-type.cpp @@ -24,20 +24,76 @@ static std::unordered_map g_primitiveTypes = {"u1", "Z"}, {"i8", "B"}, {"u8", "H"}, {"i16", "S"}, {"u16", "C"}, {"i32", "I"}, {"u32", "U"}, {"f32", "F"}, {"f64", "D"}, {"i64", "J"}, {"u64", "Q"}, {"void", "V"}, {"any", "A"}}; -std::string Type::GetDescriptor(bool ignorePrimitive) const +std::string Type::GetComponentDescriptor(bool ignorePrimitive) const { if (!ignorePrimitive) { - auto it = g_primitiveTypes.find(componentName_); + auto it = g_primitiveTypes.find(GetComponentName()); if (it != g_primitiveTypes.cend()) { - return std::string(rank_, '[') + it->second.data(); + return it->second.data(); } } - - std::string res = std::string(rank_, '[') + "L" + componentName_ + ";"; + auto res = "L" + GetComponentName() + ";"; std::replace(res.begin(), res.end(), '.', '/'); return res; } +std::string GetDescriptorImpl(const std::string &componentName, bool ignorePrimitive) +{ + if (!ignorePrimitive) { + auto it = g_primitiveTypes.find(componentName); + if (it != g_primitiveTypes.cend()) { + return it->second.data(); + } + } + + auto compTypeRaw = Type::FromName(componentName); + auto compType = Type(compTypeRaw.GetNameWithoutRank(), 0); + auto res = std::string(compTypeRaw.GetRank(), '['); + if (compType.IsUnion()) { + return res + compType.GetDescriptor(); + } + return res + compType.GetComponentDescriptor(ignorePrimitive); +} + +std::string Type::GetDescriptor(bool ignorePrimitive) const +{ + std::string res = std::string(rank_, '['); + if ((componentNames_.size() == 1)) { + return res + GetDescriptorImpl(componentNames_[0], ignorePrimitive); + } + res += "{U"; + for (const auto &it : componentNames_) { + res += GetDescriptorImpl(it, ignorePrimitive); + } + res += "}"; + return res; +} + +void Type::Canonicalize() +{ + if (!IsUnion()) { + return; + } + for (auto &componentName : componentNames_) { + Type rawCompType = Type::FromName(componentName); + Type compType = Type(rawCompType.GetNameWithoutRank(), 0); + componentName = Type(compType.GetName(), rawCompType.GetRank()).GetName(); + } + + std::sort(componentNames_.begin(), componentNames_.end()); + auto duplicateBeginIt = unique(componentNames_.begin(), componentNames_.end()); + componentNames_.erase(duplicateBeginIt, componentNames_.end()); + name_ = GetName(GetComponentName(), GetRank()); +} + +/* static */ +std::string Type::CanonicalizeDescriptor(std::string_view descriptor) +{ + auto type = Type::FromDescriptor(descriptor); + type.Canonicalize(); + return type.GetDescriptor(); +} + /* static */ panda_file::Type::TypeId Type::GetId(std::string_view name, bool ignorePrimitive) { @@ -85,53 +141,86 @@ std::string Type::GetName(std::string_view componentName, size_t rank) return name; } -/* static */ -Type Type::FromDescriptor(std::string_view descriptor) +static std::pair FromDescriptorComponent(std::string_view descriptor) { static std::unordered_map reversePrimitiveTypes = { {"Z", "u1"}, {"B", "i8"}, {"H", "u8"}, {"S", "i16"}, {"C", "u16"}, {"I", "i32"}, {"U", "u32"}, {"F", "f32"}, {"D", "f64"}, {"J", "i64"}, {"Q", "u64"}, {"V", "void"}, {"A", "any"}}; - size_t i = 0; - while (descriptor[i] == '[') { - ++i; + bool isRefType = descriptor[0] == 'L'; + if (isRefType) { + auto len = descriptor.find(';'); + return {descriptor.substr(1, len - 1), len + 1}; } - size_t rank = i; - bool isRefType = descriptor[i] == 'L'; - if (isRefType) { - descriptor.remove_suffix(1); /* Remove semicolon */ - ++i; + auto prim = reversePrimitiveTypes.find(descriptor.substr(0, 1)); + if (prim == reversePrimitiveTypes.end()) { + LOG(FATAL, ASSEMBLER) << "The map 'reversePrimitiveTypes' don't contain the descriptor [" << descriptor << "]."; + return {"", 0}; } - descriptor.remove_prefix(i); + return {prim->second, 1}; +} - if (isRefType) { - return Type(descriptor, rank); +static std::pair FromDescriptorImpl(std::string_view descriptor) +{ + bool isUnionType = descriptor[0] == '{'; + if (!isUnionType) { + auto [name, len] = FromDescriptorComponent(descriptor); + return {std::string(name), len}; } - auto it = reversePrimitiveTypes.find(descriptor); - if (it == reversePrimitiveTypes.end()) { - LOG(FATAL, ASSEMBLER) << "The map 'reversePrimitiveTypes' don't contain the descriptor [" << descriptor << "]."; + std::string name = "{U"; + size_t unionLen = 3; + descriptor.remove_prefix(Type::UNION_PREFIX_LEN); + while (descriptor[0] != '}') { + size_t rank = 0; + while (descriptor[rank] == '[') { + ++rank; + } + unionLen += rank; + descriptor.remove_prefix(rank); + auto [componentDesc, len] = FromDescriptorImpl(descriptor); + unionLen += len; + name += componentDesc; + while (rank-- > 0) { + name += "[]"; + } + name += ","; + descriptor.remove_prefix(len); } - return Type(reversePrimitiveTypes[descriptor], rank); + name.pop_back(); // remove the extra comma + name += "}"; + return {name, unionLen}; } /* static */ -Type Type::FromName(std::string_view name, bool ignorePrimitive) +Type Type::FromDescriptor(std::string_view descriptor) { - constexpr size_t STEP = 2; + size_t i = 0; + while (descriptor[i] == '[') { + ++i; + } + + size_t rank = i; + descriptor.remove_prefix(rank); + auto [name, _] = FromDescriptorImpl(descriptor); + return Type(name, rank); +} +/* static */ +Type Type::FromName(std::string_view name, bool ignorePrimitive) +{ size_t size = name.size(); size_t i = 0; while (name[size - i - 1] == ']') { - i += STEP; + i += Type::RANK_STEP; } name.remove_suffix(i); - return Type(name, i / STEP, ignorePrimitive); + return Type(name, i / Type::RANK_STEP, ignorePrimitive); } /* static */ diff --git a/static_core/assembler/assembly-type.h b/static_core/assembler/assembly-type.h index 3693b54206..56b5d7cd03 100644 --- a/static_core/assembler/assembly-type.h +++ b/static_core/assembler/assembly-type.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021-2025 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 @@ -37,11 +37,12 @@ public: DEFAULT_COPY_SEMANTIC(Type); ~Type() = default; - Type(std::string_view componentName, size_t rank, bool ignorePrimitive = false) - : componentName_(componentName), rank_(rank) + Type(std::string_view componentName, size_t rank, bool ignorePrimitive = false) : rank_(rank) { - name_ = GetName(componentName_, rank_); + name_ = GetName(componentName, rank_); typeId_ = GetId(name_, ignorePrimitive); + FillComponentNames(componentName); + Canonicalize(); } Type(const Type &componentType, size_t rank) @@ -49,12 +50,71 @@ public: { } + static constexpr size_t UNION_PREFIX_LEN = 2; // CC-OFF(G.NAM.03-CPP) project code style + static constexpr size_t RANK_STEP = 2; // CC-OFF(G.NAM.03-CPP) project code style + PANDA_PUBLIC_API std::string GetDescriptor(bool ignorePrimitive = false) const; PANDA_PUBLIC_API std::string GetName() const { return name_; } + size_t SkipUnion(std::string_view name) + { + auto unionLength = 2; + while (name[unionLength++] != '}') { + if (name[unionLength] == '{') { + unionLength += SkipUnion(name.substr(unionLength)); + } + } + return unionLength; + } + + std::pair GetComponentName(std::string_view name) + { + if (name[0] != '{') { + auto delimPos = name.find(','); + if (delimPos != std::string::npos) { + return {std::string(name).substr(0, delimPos), delimPos}; + } + return {std::string(name), name.length()}; + } + + auto unionLen = SkipUnion(name); + while ((name.length() > unionLen) && (name[unionLen] == '[')) { + unionLen += Type::RANK_STEP; + } + return {std::string(name).substr(0, unionLen), unionLen}; + } + + void FillComponentNames(std::string_view componentNamesStr) + { + if (!IsUnion()) { + componentNames_.emplace_back(componentNamesStr); + return; + } + componentNamesStr.remove_prefix(Type::UNION_PREFIX_LEN); + componentNamesStr.remove_suffix(1); + while (!componentNamesStr.empty()) { + auto [component, length] = GetComponentName(componentNamesStr); + componentNames_.push_back(component); + if (componentNamesStr.length() == length || componentNamesStr[length] != ',') { + break; + } + componentNamesStr.remove_prefix(length + 1); + } + ASSERT(componentNames_.size() > 1); + } + + std::string GetNameWithoutRank() const + { + auto idx = name_.length() - 1; + while (name_[idx] == ']') { + idx -= Type::RANK_STEP; + } + return name_.substr(0, idx + 1); + } + std::string GetPandasmName() const { std::string namePa {name_}; @@ -64,9 +124,24 @@ public: std::string GetComponentName() const { - return componentName_; + if (componentNames_.size() == 1) { + return componentNames_[0]; + } + std::string res = "{U"; + for (const auto &comp : componentNames_) { + res += comp + ","; + } + res.pop_back(); + return res + "}"; + } + + const std::vector &GetComponentNames() const + { + return componentNames_; } + std::string GetComponentDescriptor(bool ignorePrimitive) const; + size_t GetRank() const { return rank_; @@ -74,7 +149,8 @@ public: Type GetComponentType() const { - return Type(componentName_, rank_ > 0 ? rank_ - 1 : 0); + ASSERT(componentNames_.size() == 1); + return Type(componentNames_[0], rank_ > 0 ? rank_ - 1 : 0); } panda_file::Type::TypeId GetId() const @@ -84,13 +160,14 @@ public: bool IsArrayContainsPrimTypes() const { - auto elem = GetId(componentName_); + ASSERT(componentNames_.size() == 1); + auto elem = GetId(componentNames_[0]); return elem != panda_file::Type::TypeId::REFERENCE; } bool IsValid() const { - return !componentName_.empty(); + return !componentNames_.empty(); } bool IsArray() const @@ -155,6 +232,11 @@ public: return typeId_ == panda_file::Type::TypeId::VOID; } + bool IsUnion() const + { + return (name_.substr(0, Type::UNION_PREFIX_LEN) == "{U") && (name_.back() == '}'); + } + PANDA_PUBLIC_API static panda_file::Type::TypeId GetId(std::string_view name, bool ignorePrimitive = false); PANDA_PUBLIC_API static pandasm::Type FromPrimitiveId(panda_file::Type::TypeId id); @@ -176,10 +258,13 @@ public: static PANDA_PUBLIC_API bool IsPandaPrimitiveType(const std::string &name); static bool IsStringType(const std::string &name, ark::panda_file::SourceLang lang); + PANDA_PUBLIC_API void Canonicalize(); + static PANDA_PUBLIC_API std::string CanonicalizeDescriptor(std::string_view descriptor); + private: static PANDA_PUBLIC_API std::string GetName(std::string_view componentName, size_t rank); - std::string componentName_; + std::vector componentNames_; size_t rank_ {0}; std::string name_; panda_file::Type::TypeId typeId_ {panda_file::Type::TypeId::VOID}; diff --git a/static_core/assembler/tests/emitter_test.cpp b/static_core/assembler/tests/emitter_test.cpp index 2479b67f10..2ccfd4dbcb 100644 --- a/static_core/assembler/tests/emitter_test.cpp +++ b/static_core/assembler/tests/emitter_test.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021-2025 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 @@ -1044,4 +1044,93 @@ TEST(emittertests, final_modifier) } } +TEST(emittertests, test_union_canonicalization) +{ + Parser p; + std::string source = + ".record std.core.Object \n" + ".record A {}\n" + ".record B {}\n" + ".record C {}\n" + ".record D {}\n" + + ".record {UC,C,B,C,B} \n" + ".record {UD,C,B,A} \n" + ".record {UA,A,A,B} \n" + ".record {UA,std.core.Double} \n" + ".record {Ustd.core.Double,std.core.Double,std.core.Long} \n" + ".record {Ustd.core.Double,std.core.Int,std.core.Long} \n" + ".record {UA,B,C,B,A} \n" + ".record {UA,D,D,D} "; + + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, Error::ErrorType::ERR_NONE); + + auto pf = AsmEmitter::Emit(res.Value()); + ASSERT_NE(pf, nullptr); + + std::vector classDescriptors = {"{ULB;LC;}", + "{ULA;LB;LC;LD;}", + "{ULA;LB;}", + "{ULA;Lstd/core/Double;}", + "{ULstd/core/Double;Lstd/core/Long;}", + "{ULstd/core/Double;Lstd/core/Int;Lstd/core/Long;}", + "{ULA;LB;LC;}", + "{ULA;LD;}"}; + + for (const auto &desc : classDescriptors) { + auto classId = pf->GetClassId(utf::CStringAsMutf8(desc)); + ASSERT_TRUE(classId.IsValid()); + ASSERT_TRUE(pf->IsExternal(classId)); + } +} + +TEST(emittertests, test_union_canonicalization_arrays) +{ + Parser p; + std::string source = + ".record std.core.Object \n" + ".record A {}\n" + ".record B {}\n" + ".record C {}\n" + ".record D {}\n" + + ".record {UB,C,{UA,D,std.core.Double}[],A} \n" + ".record {U{UD,A,std.core.Int}[],{UA,D,std.core.Double}[],{Ustd.core.Long,D,A}[]} \n" + ".record {UB,C,{UA,B}[],A} \n" + ".record {UB,{UA,D}[],{UD,B}[],{UC,B}[],{UC,D}[]} \n" + ".record {U{UA,D}[],{UD,B}[],{UD,B}[],{UC,D}[]} \n" + ".record {U{UA,D}[],{UD,B}[][],{UD,B}[],{UC,D}[]} \n" + ".record {U{UA,D}[][],{UD,B}[]} \n" + ".record {U{UD,C}[],{UC,B}[]} \n" + ".record {U{UD,C}[],C,B} \n" + ".record {U{Ustd.core.Int,std.core.Double}[],std.core.Long,std.core.Double} \n" + ".record {U{Ustd.core.Int[],std.core.Double[]}[],std.core.Long[],std.core.Double[]} "; + + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, Error::ErrorType::ERR_NONE); + + auto pf = AsmEmitter::Emit(res.Value()); + ASSERT_NE(pf, nullptr); + + std::vector classDescriptors = { + "{ULA;LB;LC;[{ULA;LD;Lstd/core/Double;}}", + "{U[{ULA;LD;Lstd/core/Double;}[{ULA;LD;Lstd/core/Int;}[{ULA;LD;Lstd/core/Long;}}", + "{ULA;LB;LC;[{ULA;LB;}}", + "{ULB;[{ULA;LD;}[{ULB;LC;}[{ULB;LD;}[{ULC;LD;}}", + "{U[{ULA;LD;}[{ULB;LD;}[{ULC;LD;}}", + "{U[{ULA;LD;}[{ULB;LD;}[[{ULB;LD;}[{ULC;LD;}}", + "{U[[{ULA;LD;}[{ULB;LD;}}", + "{U[{ULB;LC;}[{ULC;LD;}}", + "{ULB;LC;[{ULC;LD;}}", + "{ULstd/core/Double;Lstd/core/Long;[{ULstd/core/Double;Lstd/core/Int;}}", + "{U[Lstd/core/Double;[Lstd/core/Long;[{U[Lstd/core/Double;[Lstd/core/Int;}}"}; + + for (const auto &desc : classDescriptors) { + auto classId = pf->GetClassId(utf::CStringAsMutf8(desc)); + ASSERT_TRUE(classId.IsValid()); + ASSERT_TRUE(pf->IsExternal(classId)); + } +} + } // namespace ark::test diff --git a/static_core/assembler/tests/parser_test.cpp b/static_core/assembler/tests/parser_test.cpp index fb71b9434a..7550105665 100644 --- a/static_core/assembler/tests/parser_test.cpp +++ b/static_core/assembler/tests/parser_test.cpp @@ -4818,4 +4818,94 @@ TEST(parsertests, test_final) } } +TEST(parsertests, test_union_canonicalization) +{ + Parser p; + std::string source = + ".record std.core.Object \n" + ".record A {}\n" + ".record B {}\n" + ".record C {}\n" + ".record D {}\n" + + ".record {UC,C,B,C,B} \n" + ".record {UD,C,B,A} \n" + ".record {UA,A,A,B} \n" + ".record {UA,std.core.Double} \n" + ".record {Ustd.core.Double,std.core.Double,std.core.Long} \n" + ".record {Ustd.core.Long,std.core.Double,std.core.Int} \n" + ".record {UA,B,C,B,A} \n" + ".record {UA,D,D,D} "; + + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, Error::ErrorType::ERR_NONE); + + auto &program = res.Value(); + + std::map records { + {"{UC,C,B,C,B}", "{UB,C}"}, + {"{UD,C,B,A}", "{UA,B,C,D}"}, + {"{UA,A,A,B}", "{UA,B}"}, + {"", "{UA,std.core.Double}"}, + {"{Ustd.core.Double,std.core.Double,std.core.Long}", "{Ustd.core.Double,std.core.Long}"}, + {"{Ustd.core.Long,std.core.Double,std.core.Int}", "{Ustd.core.Double,std.core.Int,std.core.Long}"}, + {"{UA,B,C,B,A}", "{UA,B,C}"}, + {"{UA,D,D,D}", "{UA,D}"}}; + + for (const auto &[notCanonRec, canonRec] : records) { + ASSERT_EQ(program.recordTable.find(notCanonRec), program.recordTable.end()); + ASSERT_NE(program.recordTable.find(canonRec), program.recordTable.end()); + } +} + +TEST(parsertests, test_union_canonicalization_arrays) +{ + Parser p; + + std::string source = + ".record std.core.Object \n" + ".record A {}\n" + ".record B {}\n" + ".record C {}\n" + ".record D {}\n" + + ".record {UB,C,{UA,D,std.core.Double}[],A} \n" + ".record {U{UD,A,std.core.Int}[],{UA,D,std.core.Double}[],{Ustd.core.Long,D,A}[]} \n" + ".record {UB,C,{UA,B}[],A} \n" + ".record {UB,{UA,D}[],{UD,B}[],{UC,B}[],{UC,D}[]} \n" + ".record {U{UA,D}[],{UD,B}[],{UD,B}[],{UC,D}[]} \n" + ".record {U{UA,D}[],{UD,B}[][],{UD,B}[],{UC,D}[]} \n" + ".record {U{UA,D}[][],{UD,B}[]} \n" + ".record {U{UD,C}[],{UC,B}[]} \n" + ".record {U{UD,C}[],C,B} \n" + ".record {U{Ustd.core.Int,std.core.Double}[],std.core.Long,std.core.Double} \n" + ".record {U{Ustd.core.Int[],std.core.Double[]}[],std.core.Long[],std.core.Double[]} "; + + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, Error::ErrorType::ERR_NONE); + + auto &program = res.Value(); + + std::map records { + {"{UB,C,{UA,D,std.core.Double}[],A}", "{UA,B,C,{UA,D,std.core.Double}[]}"}, + {"{U{UD,A,std.core.Int}[],{UA,D,std.core.Double}[],{Ustd.core.Long,D,A}[]}", + "{U{UA,D,std.core.Double}[],{UA,D,std.core.Int}[],{UA,D,std.core.Long}[]}"}, + {"{UB,C,{UA,B}[],A}", "{UA,B,C,{UA,B}[]}"}, + {"{UB,{UA,D}[],{UD,B}[],{UC,B}[],{UC,D}[]}", "{UB,{UA,D}[],{UB,C}[],{UB,D}[],{UC,D}[]}"}, + {"{U{UA,D}[],{UD,B}[],{UD,B}[],{UC,D}[]}", "{U{UA,D}[],{UB,D}[],{UC,D}[]}"}, + {"{U{UA,D}[],{UD,B}[][],{UD,B}[],{UC,D}[]}", "{U{UA,D}[],{UB,D}[],{UB,D}[][],{UC,D}[]}"}, + {"{U{UA,D}[][],{UD,B}[]}", "{U{UA,D}[][],{UB,D}[]}"}, + {"{U{UD,C}[],{UC,B}[]}", "{U{UB,C}[],{UC,D}[]}"}, + {"{U{UD,C}[],C,B}", "{UB,C,{UC,D}[]}"}, + {"{U{Ustd.core.Int,std.core.Double}[],std.core.Long,std.core.Double}", + "{Ustd.core.Double,std.core.Long,{Ustd.core.Double,std.core.Int}[]}"}, + {"{U{Ustd.core.Int[],std.core.Double[]}[],std.core.Long[],std.core.Double[]}", + "{Ustd.core.Double[],std.core.Long[],{Ustd.core.Double[],std.core.Int[]}[]}"}}; + + for (const auto &[notCanonRec, canonRec] : records) { + ASSERT_EQ(program.recordTable.find(notCanonRec), program.recordTable.end()); + ASSERT_NE(program.recordTable.find(canonRec), program.recordTable.end()); + } +} + } // namespace ark::test diff --git a/static_core/codecheck_ignore.json b/static_core/codecheck_ignore.json index bc385e8745..5992274e42 100644 --- a/static_core/codecheck_ignore.json +++ b/static_core/codecheck_ignore.json @@ -158,6 +158,7 @@ "plugins/ets/tests/scripts": "*", "plugins/ets/tests/stdlib-templates": "*", "plugins/ets/tests/ets_ts_subset": "*", + "plugins/ets/tests/verify_unions": "*", "plugins/ets/tools": "*", "plugins/ets/verification": "*", "quickener": "*", diff --git a/static_core/disassembler/disassembler.cpp b/static_core/disassembler/disassembler.cpp index 14319b0acb..91af93e437 100644 --- a/static_core/disassembler/disassembler.cpp +++ b/static_core/disassembler/disassembler.cpp @@ -1254,7 +1254,7 @@ std::string Disassembler::GetFullRecordName(const panda_file::File::EntityId &cl std::string name = StringDataToString(file_->GetStringData(classId)); auto type = pandasm::Type::FromDescriptor(name); - type = pandasm::Type(type.GetComponentName(), type.GetRank()); + type = pandasm::Type(type.GetNameWithoutRank(), type.GetRank()); return type.GetPandasmName(); } diff --git a/static_core/disassembler/tests/records_test.cpp b/static_core/disassembler/tests/records_test.cpp index 8ffe840c9c..bad3c3b00d 100644 --- a/static_core/disassembler/tests/records_test.cpp +++ b/static_core/disassembler/tests/records_test.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021-2025 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 @@ -156,6 +156,93 @@ TEST(RecordTest, RecordWithRecord) EXPECT_EQ("\ti64 aw", line); } +TEST(RecordTest, TestUnionCanonicalization) +{ + auto program = ark::pandasm::Parser().Parse( + ".record std.core.Object \n" + ".record A {}\n" + ".record B {}\n" + ".record C {}\n" + ".record D {}\n" + + ".record {UC,C,B,C,B} \n" + ".record {UD,C,B,A} \n" + ".record {UA,A,A,B} \n" + ".record {UA,std.core.Double} \n" + ".record {Ustd.core.Double,std.core.Double,std.core.Long} \n" + ".record {Ustd.core.Double,std.core.Int,std.core.Long} \n" + ".record {UA,B,C,B,A} \n" + ".record {UA,D,D,D} "); + + ASSERT(program); + auto pf = ark::pandasm::AsmEmitter::Emit(program.Value()); + ASSERT(pf); + + ark::disasm::Disassembler d {}; + std::stringstream ss {}; + + d.Disassemble(pf); + d.Serialize(ss); + + EXPECT_TRUE(ss.str().find(".record {UB,C} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {UA,B,C,D} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {UA,B} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {UA,std.core.Double} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {Ustd.core.Double,std.core.Long} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {Ustd.core.Double,std.core.Int,std.core.Long} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {UA,B,C} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {UA,D} ") != std::string::npos); +} + +TEST(RecordTest, TestUnionCanonicalizationArrays) +{ + auto program = ark::pandasm::Parser().Parse( + ".record std.core.Object \n" + ".record A {}\n" + ".record B {}\n" + ".record C {}\n" + ".record D {}\n" + + ".record {UB,C,{UA,D,std.core.Double}[],A} \n" + ".record {U{UD,A,std.core.Int}[],{UA,D,std.core.Double}[],{Ustd.core.Long,D,A}[]} \n" + ".record {UB,C,{UA,B}[],A} \n" + ".record {UB,{UA,D}[],{UD,B}[],{UC,B}[],{UC,D}[]} \n" + ".record {U{UA,D}[],{UD,B}[],{UD,B}[],{UC,D}[]} \n" + ".record {U{UA,D}[],{UD,B}[][],{UD,B}[],{UC,D}[]} \n" + ".record {U{UA,D}[][],{UD,B}[]} \n" + ".record {U{UD,C}[],{UC,B}[]} \n" + ".record {U{UD,C}[],C,B} \n" + ".record {U{Ustd.core.Int,std.core.Double}[],std.core.Long,std.core.Double} \n" + ".record {U{Ustd.core.Int[],std.core.Double[]}[],std.core.Long[],std.core.Double[]} "); + ASSERT(program); + auto pf = ark::pandasm::AsmEmitter::Emit(program.Value()); + ASSERT(pf); + + ark::disasm::Disassembler d {}; + std::stringstream ss {}; + + d.Disassemble(pf); + d.Serialize(ss); + + EXPECT_TRUE(ss.str().find(".record {UA,B,C,{UA,B}[]} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {UA,B,C,{UA,D,std.core.Double}[]} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {UB,C,{UC,D}[]} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {UB,{UA,D}[],{UB,C}[],{UB,D}[],{UC,D}[]} ") != std::string::npos); + EXPECT_TRUE( + ss.str().find(".record {Ustd.core.Double,std.core.Long,{Ustd.core.Double,std.core.Int}[]} ") != + std::string::npos); + EXPECT_TRUE(ss.str().find( + ".record {Ustd.core.Double[],std.core.Long[],{Ustd.core.Double[],std.core.Int[]}[]} ") != + std::string::npos); + EXPECT_TRUE( + ss.str().find(".record {U{UA,D,std.core.Double}[],{UA,D,std.core.Int}[],{UA,D,std.core.Long}[]} ") != + std::string::npos); + EXPECT_TRUE(ss.str().find(".record {U{UA,D}[],{UB,D}[],{UC,D}[]} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {U{UA,D}[],{UB,D}[],{UB,D}[][],{UC,D}[]} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {U{UA,D}[][],{UB,D}[]} ") != std::string::npos); + EXPECT_TRUE(ss.str().find(".record {U{UB,C}[],{UC,D}[]} ") != std::string::npos); +} + #undef DISASM_BIN_DIR } // namespace ark::disasm::test diff --git a/static_core/libpandafile/file.h b/static_core/libpandafile/file.h index 4060d178f9..0a36e3a780 100644 --- a/static_core/libpandafile/file.h +++ b/static_core/libpandafile/file.h @@ -128,6 +128,11 @@ public: return l.offset_ == r.offset_; } + friend bool operator!=(const EntityId &l, const EntityId &r) + { + return l.offset_ != r.offset_; + } + friend std::ostream &operator<<(std::ostream &stream, const EntityId &id) { return stream << id.offset_; diff --git a/static_core/libpandafile/templates/type.h.erb b/static_core/libpandafile/templates/type.h.erb index 0ec1543265..d54809cce6 100644 --- a/static_core/libpandafile/templates/type.h.erb +++ b/static_core/libpandafile/templates/type.h.erb @@ -189,6 +189,7 @@ public: return Type(panda_file::Type::TypeId::TAGGED); case 'L': case '[': + case '{': return Type(panda_file::Type::TypeId::REFERENCE); default: UNREACHABLE(); diff --git a/static_core/plugins/ets/runtime/ani/ani_mangle.cpp b/static_core/plugins/ets/runtime/ani/ani_mangle.cpp index 821c552930..cece7d0759 100644 --- a/static_core/plugins/ets/runtime/ani/ani_mangle.cpp +++ b/static_core/plugins/ets/runtime/ani/ani_mangle.cpp @@ -13,6 +13,7 @@ * limitations under the License. */ +#include "assembler/assembly-type.h" #include "plugins/ets/runtime/ani/ani_mangle.h" #include "plugins/ets/runtime/ets_panda_file_items.h" @@ -54,6 +55,50 @@ static constexpr size_t MIN_BODY_SIZE = sizeof('{') + 1 + sizeof('}'); static size_t ParseType(char type, const std::string_view data, PandaStringStream &ss); +static size_t ParseUnionBody(const std::string_view data, PandaStringStream &ss) +{ + if (data.size() < MIN_BODY_SIZE || data[0] != '{') { + return std::string_view::npos; + } + PandaStringStream unionStream; + unionStream << "{U"; + + bool containsNull = false; + std::string_view previousConstituentTypes; + size_t size = 1; + while (size < data.size() && data[size] != '}') { + std::string_view substr = data.substr(size); + size_t sz = ParseType(data[size], substr, unionStream); + if (sz == std::string_view::npos) { + // The 'descriptor' does not have a new format, so no conversion is required. + return std::string_view::npos; + } + // NOTE(dslynko, #26222): do not replace union to `Object` when `null` will have its own publically fixed type + std::string_view parsedType = substr.substr(0, sz); + if (parsedType == "N") { + containsNull = true; + } + // NOTE(dslynko, #26223): move constituent types order check into VerifyANI + if (previousConstituentTypes > parsedType) { + // Constituent types must be ordered in alphabetical order with respect to ANI encodings. + return std::string_view::npos; + } + size += sz; + } + if (size >= data.size() || data[size] != '}') { + // Union descriptor must end with '}'. + return std::string_view::npos; + } + unionStream << '}'; + + if (containsNull) { + ss << panda_file_items::class_descriptors::OBJECT; + } else { + ss << pandasm::Type::CanonicalizeDescriptor(unionStream.str()); + } + return size + sizeof('}'); +} + static size_t ParseArrayBody(const std::string_view data, PandaStringStream &ss) { if (data.size() < MIN_BODY_SIZE || data[0] != '{') { @@ -107,6 +152,7 @@ static size_t ParseType(char type, const std::string_view data, PandaStringStrea case 'N': ss << panda_file_items::class_descriptors::OBJECT; return 1; case 'U': ss << panda_file_items::class_descriptors::OBJECT; return 1; case 'A': bodySize = ParseArrayBody(data.substr(1), ss); break; + case 'X': bodySize = ParseUnionBody(data.substr(1), ss); break; case 'C': case 'E': case 'P': bodySize = ParseBody(type, data.substr(1), ss); break; diff --git a/static_core/plugins/ets/runtime/ets_class_linker_extension.cpp b/static_core/plugins/ets/runtime/ets_class_linker_extension.cpp index 0941a2a0ee..d731239868 100644 --- a/static_core/plugins/ets/runtime/ets_class_linker_extension.cpp +++ b/static_core/plugins/ets/runtime/ets_class_linker_extension.cpp @@ -24,6 +24,7 @@ #include "plugins/ets/runtime/ets_exceptions.h" #include "plugins/ets/runtime/ets_panda_file_items.h" #include "plugins/ets/runtime/ets_vm.h" +#include "plugins/ets/runtime/ets_vtable_builder.h" #include "plugins/ets/runtime/napi/ets_napi_helpers.h" #include "plugins/ets/runtime/types/ets_abc_runtime_linker.h" #include "plugins/ets/runtime/types/ets_method.h" @@ -86,6 +87,7 @@ static std::string_view GetClassLinkerErrorDescriptor(ClassLinker::Error error) case ClassLinker::Error::OVERRIDES_FINAL: case ClassLinker::Error::MULTIPLE_OVERRIDE: case ClassLinker::Error::MULTIPLE_IMPLEMENT: + case ClassLinker::Error::REDECL_BY_TYPE_SIG: return panda_file_items::class_descriptors::LINKER_METHOD_CONFLICT_ERROR; default: LOG(FATAL, CLASS_LINKER) << "Unhandled class linker error (" << helpers::ToUnderlying(error) << "): "; @@ -707,4 +709,63 @@ ClassLinkerContext *EtsClassLinkerExtension::CreateApplicationClassLinkerContext return ctx; } +const uint8_t *EtsClassLinkerExtension::ComputeLUB(const ClassLinkerContext *ctx, const uint8_t *descriptor) +{ + auto [pf, id] = ComputeLUBInfo(ctx, descriptor); + return RefTypeLink(ctx, pf, id).GetDescriptor(); +} + +std::pair GetClassInfo(ClassLinker *cl, + const ClassLinkerContext *ctx, + const uint8_t *desc) +{ + auto cda = GetClassDataAccessor(cl, const_cast(ctx), desc); + return {&(cda.GetPandaFile()), cda.GetClassId()}; +} + +std::pair EtsClassLinkerExtension::ComputeLUBInfo( + const ClassLinkerContext *ctx, const uint8_t *descriptor) +{ + // Union descriptor format: '{' 'U' TypeDescriptor+ '}' + RefTypeLink lub(ctx, nullptr); + auto idx = 2; + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + while (descriptor[idx] != '}') { + auto typeSp = ClassHelper::GetUnionComponent(&(descriptor[idx])); + if (ClassHelper::IsPrimitive(typeSp.begin()) || ClassHelper::IsArrayDescriptor(typeSp.begin())) { + // NOTE(aantipina): Process arrays: + // compute GetClosestCommonAncestor for component type, lub = GetClosestCommonAncestor[] + return GetClassInfo(GetClassLinker(), ctx, langCtx_.GetObjectClassDescriptor()); + } + idx += typeSp.Size(); + + PandaString typeDescCopy(utf::Mutf8AsCString(typeSp.Data()), typeSp.Size()); + auto [typePf, typeClassId] = GetClassInfo(GetClassLinker(), ctx, utf::CStringAsMutf8(typeDescCopy.c_str())); + + if (lub.GetDescriptor() == nullptr) { + lub = RefTypeLink(ctx, typePf, typeClassId); + continue; + } + + auto type = RefTypeLink(ctx, typePf, typeClassId); + if (RefTypeLink::AreEqual(lub, type)) { + continue; + } + + if (ClassHelper::IsReference(type.GetDescriptor()) && ClassHelper::IsReference(lub.GetDescriptor())) { + auto lubDescOpt = GetClosestCommonAncestor(GetClassLinker(), ctx, lub, type); + if (!lubDescOpt.has_value()) { + return GetClassInfo(GetClassLinker(), ctx, langCtx_.GetObjectClassDescriptor()); + } + lub = lubDescOpt.value(); + continue; + } + + return GetClassInfo(GetClassLinker(), ctx, langCtx_.GetObjectClassDescriptor()); + } + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + return {lub.GetPandaFile(), lub.GetId()}; +} + } // namespace ark::ets diff --git a/static_core/plugins/ets/runtime/ets_class_linker_extension.h b/static_core/plugins/ets/runtime/ets_class_linker_extension.h index a63e0281b7..6fdfd69ae7 100644 --- a/static_core/plugins/ets/runtime/ets_class_linker_extension.h +++ b/static_core/plugins/ets/runtime/ets_class_linker_extension.h @@ -117,6 +117,11 @@ public: /// @brief Removes reference to `RuntimeLinker` from `BootContext` and `EtsClassLinkerContext`. static void RemoveRefToLinker(ClassLinkerContext *ctx); + /// @brief Compute LUB type for union. + const uint8_t *ComputeLUB(const ClassLinkerContext *ctx, const uint8_t *descriptor) override; + std::pair ComputeLUBInfo(const ClassLinkerContext *ctx, + const uint8_t *descriptor) override; + NO_COPY_SEMANTIC(EtsClassLinkerExtension); NO_MOVE_SEMANTIC(EtsClassLinkerExtension); diff --git a/static_core/plugins/ets/runtime/ets_vtable_builder.cpp b/static_core/plugins/ets/runtime/ets_vtable_builder.cpp index 81ffd11fd4..0dc0e168d2 100644 --- a/static_core/plugins/ets/runtime/ets_vtable_builder.cpp +++ b/static_core/plugins/ets/runtime/ets_vtable_builder.cpp @@ -20,6 +20,8 @@ namespace ark::ets { +static constexpr uint32_t ASSIGNABILITY_MAX_DEPTH = 256U; + bool EtsVTableOverridePred::IsInSamePackage(const MethodInfo &info1, const MethodInfo &info2) const { if (info1.GetLoadContext() != info2.GetLoadContext()) { @@ -58,71 +60,25 @@ bool EtsVTableOverridePred::IsInSamePackage(const MethodInfo &info1, const Metho return isSamePackage; } -class RefTypeLink { -public: - explicit RefTypeLink(const ClassLinkerContext *ctx, uint8_t const *descr) : ctx_(ctx), descriptor_(descr) {} - RefTypeLink(const ClassLinkerContext *ctx, panda_file::File const *pf, panda_file::File::EntityId idx) - : ctx_(ctx), pf_(pf), id_(idx), descriptor_(pf->GetStringData(idx).data) - { - } - - static RefTypeLink InPDA(const ClassLinkerContext *ctx, panda_file::ProtoDataAccessor &pda, uint32_t idx) - { - return RefTypeLink(ctx, &pda.GetPandaFile(), pda.GetReferenceType(idx)); - } - - uint8_t const *GetDescriptor() - { - return descriptor_; - } - - ALWAYS_INLINE static bool AreEqual(RefTypeLink const &a, RefTypeLink const &b) - { - if (LIKELY(a.pf_ == b.pf_ && a.pf_ != nullptr)) { - return a.id_ == b.id_; - } - return utf::IsEqual(a.descriptor_, b.descriptor_); - } - - ALWAYS_INLINE std::optional CreateCDA() - { - if (UNLIKELY(!Resolve())) { - return std::nullopt; - } - return panda_file::ClassDataAccessor(*pf_, id_); +bool RefTypeLink::Resolve() +{ + if (IsResolved()) { + return true; } -private: - bool Resolve() - { - if (IsResolved()) { - return true; + // Need to traverse `RuntimeLinker` chain, which is why `EnumeratePandaFilesInChain` is used + // NOTE(vpukhov): speedup lookup with tls cache + ctx_->EnumeratePandaFilesInChain([this](panda_file::File const &itpf) { + auto itClassId = itpf.GetClassId(descriptor_); + if (itClassId.IsValid() && !itpf.IsExternal(itClassId)) { + pf_ = &itpf; + id_ = itClassId; + return false; } - - // Need to traverse `RuntimeLinker` chain, which is why `EnumeratePandaFilesInChain` is used - // NOTE(vpukhov): speedup lookup with tls cache - ctx_->EnumeratePandaFilesInChain([this](panda_file::File const &itpf) { - auto itClassId = itpf.GetClassId(descriptor_); - if (itClassId.IsValid() && !itpf.IsExternal(itClassId)) { - pf_ = &itpf; - id_ = itClassId; - return false; - } - return true; - }); - return IsResolved(); - } - - bool IsResolved() const - { - return (pf_ != nullptr) && !pf_->IsExternal(id_); - } - - const ClassLinkerContext *ctx_ {}; - panda_file::File const *pf_ {}; - panda_file::File::EntityId id_ {}; - uint8_t const *descriptor_ {}; -}; + return true; + }); + return IsResolved(); +} static inline bool IsPrimitveDescriptor(uint8_t const *descr) { @@ -196,11 +152,26 @@ static bool RefIsAssignableToImpl(const ClassLinkerContext *ctx, RefTypeLink sub static inline bool RefIsAssignableTo(const ClassLinkerContext *ctx, RefTypeLink sub, RefTypeLink super) { - static constexpr uint32_t ASSIGNABILITY_MAX_DEPTH = 256U; return RefIsAssignableToImpl(ctx, sub, super, ASSIGNABILITY_MAX_DEPTH); } -bool ETSProtoIsOverriddenBy(const ClassLinkerContext *ctx, Method::ProtoId const &base, Method::ProtoId const &derv) +static inline bool IsAssignableRefs(const ClassLinkerContext *ctx, const RefTypeLink &dervRef, + const RefTypeLink &baseRef, size_t idx, bool isStrict) +{ + if (isStrict) { + if ((dervRef.GetId()) != baseRef.GetId()) { + return false; + } + } else { + if (!(idx == 0 ? RefIsAssignableTo(ctx, dervRef, baseRef) : RefIsAssignableTo(ctx, baseRef, dervRef))) { + return false; + } + } + return true; +} + +bool ETSProtoIsOverriddenBy(const ClassLinkerContext *ctx, Method::ProtoId const &base, Method::ProtoId const &derv, + bool isStrict) { ASSERT(ctx != nullptr); auto basePDA = panda_file::ProtoDataAccessor(base.GetPandaFile(), base.GetEntityId()); @@ -217,8 +188,7 @@ bool ETSProtoIsOverriddenBy(const ClassLinkerContext *ctx, Method::ProtoId const if (dervPDA.GetType(i).IsReference()) { auto dervRef = RefTypeLink::InPDA(ctx, dervPDA, refIdx); auto baseRef = RefTypeLink::InPDA(ctx, basePDA, refIdx); - auto res = i == 0 ? RefIsAssignableTo(ctx, dervRef, baseRef) : RefIsAssignableTo(ctx, baseRef, dervRef); - if (!res) { + if (!IsAssignableRefs(ctx, dervRef, baseRef, i, isStrict)) { return false; } refIdx++; @@ -227,4 +197,37 @@ bool ETSProtoIsOverriddenBy(const ClassLinkerContext *ctx, Method::ProtoId const return true; } +panda_file::ClassDataAccessor GetClassDataAccessor(ClassLinker *cl, ClassLinkerContext *ctx, const uint8_t *desc) +{ + auto cda = RefTypeLink(ctx, desc).CreateCDA(); + if (cda.has_value()) { + return cda.value(); + } + auto *klass = cl->GetClass(desc, false, ctx); + ASSERT(klass != nullptr); + const auto *pf = klass->GetPandaFile(); + ASSERT(pf != nullptr); + return panda_file::ClassDataAccessor(*pf, pf->GetClassId(desc)); +} + +std::optional GetClosestCommonAncestor(ClassLinker *cl, const ClassLinkerContext *ctx, RefTypeLink source, + RefTypeLink target) +{ + panda_file::ClassDataAccessor const &targetCDA = + GetClassDataAccessor(cl, const_cast(ctx), target.GetDescriptor()); + + if (targetCDA.IsInterface() || targetCDA.GetSuperClassId().GetOffset() == 0) { + return std::nullopt; + } + + auto targetSuper = RefTypeLink(ctx, &targetCDA.GetPandaFile(), targetCDA.GetSuperClassId()); + if (RefTypeLink::AreEqual(source, targetSuper)) { + return targetSuper; + } + if (RefExtendsOrImplements(ctx, source, targetSuper, ASSIGNABILITY_MAX_DEPTH)) { + return targetSuper; + } + return GetClosestCommonAncestor(cl, ctx, source, targetSuper); +} + } // namespace ark::ets diff --git a/static_core/plugins/ets/runtime/ets_vtable_builder.h b/static_core/plugins/ets/runtime/ets_vtable_builder.h index d39eeb7fa6..1c8891740b 100644 --- a/static_core/plugins/ets/runtime/ets_vtable_builder.h +++ b/static_core/plugins/ets/runtime/ets_vtable_builder.h @@ -25,15 +25,21 @@ namespace ark::ets { -bool ETSProtoIsOverriddenBy(const ClassLinkerContext *ctx, Method::ProtoId const &base, Method::ProtoId const &derv); +class RefTypeLink; + +bool ETSProtoIsOverriddenBy(const ClassLinkerContext *ctx, Method::ProtoId const &base, Method::ProtoId const &derv, + bool isStrict = false); +std::optional GetClosestCommonAncestor(ClassLinker *cl, const ClassLinkerContext *ctx, RefTypeLink source, + RefTypeLink target); +panda_file::ClassDataAccessor GetClassDataAccessor(ClassLinker *cl, ClassLinkerContext *ctx, const uint8_t *desc); class EtsVTableCompatibleSignatures { public: explicit EtsVTableCompatibleSignatures(const ClassLinkerContext *ctx) : ctx_(ctx) {} - bool operator()(const Method::ProtoId &base, const Method::ProtoId &derv) const + bool operator()(const Method::ProtoId &base, const Method::ProtoId &derv, bool isStrict = false) const { - return ETSProtoIsOverriddenBy(ctx_, base, derv); + return ETSProtoIsOverriddenBy(ctx_, base, derv, isStrict); } private: @@ -59,6 +65,72 @@ struct EtsVTableOverridePred { using EtsVTableBuilder = VarianceVTableBuilder; +class RefTypeLink { +public: + explicit RefTypeLink(const ClassLinkerContext *ctx, uint8_t const *descr) : ctx_(ctx), descriptor_(descr) {} + RefTypeLink(const ClassLinkerContext *ctx, panda_file::File const *pf, panda_file::File::EntityId idx) + : ctx_(ctx), pf_(pf), id_(idx), descriptor_(pf->GetStringData(idx).data) + { + } + + static RefTypeLink InPDA(const ClassLinkerContext *ctx, panda_file::ProtoDataAccessor &pda, uint32_t idx) + { + uint8_t const *refDescriptor = pda.GetPandaFile().GetStringData(pda.GetReferenceType(idx)).data; + if (ClassHelper::IsUnion(refDescriptor)) { + auto langCtx = + Runtime::GetCurrent()->GetLanguageContext(const_cast(ctx)->GetSourceLang()); + auto *ext = Runtime::GetCurrent()->GetClassLinker()->GetExtension(langCtx); + auto [pf, id] = ext->ComputeLUBInfo(ctx, refDescriptor); + return RefTypeLink(ctx, pf, id); + } + return RefTypeLink(ctx, &pda.GetPandaFile(), pda.GetReferenceType(idx)); + } + + uint8_t const *GetDescriptor() + { + return descriptor_; + } + + panda_file::File::EntityId GetId() const + { + return id_; + } + + panda_file::File const *GetPandaFile() const + { + return pf_; + } + + ALWAYS_INLINE static bool AreEqual(RefTypeLink const &a, RefTypeLink const &b) + { + if (LIKELY(a.pf_ == b.pf_ && a.pf_ != nullptr)) { + return a.id_ == b.id_; + } + return utf::IsEqual(a.descriptor_, b.descriptor_); + } + + ALWAYS_INLINE std::optional CreateCDA() + { + if (UNLIKELY(!Resolve())) { + return std::nullopt; + } + return panda_file::ClassDataAccessor(*pf_, id_); + } + +private: + bool Resolve(); + + bool IsResolved() const + { + return (pf_ != nullptr) && !pf_->IsExternal(id_); + } + + const ClassLinkerContext *ctx_ {}; + panda_file::File const *pf_ {}; + panda_file::File::EntityId id_ {}; + uint8_t const *descriptor_ {}; +}; + } // namespace ark::ets #endif // !PANDA_PLUGINS_ETS_RUNTIME_ETS_ITABLE_BUILDER_H_ diff --git a/static_core/plugins/ets/runtime/types/ets_method_signature.h b/static_core/plugins/ets/runtime/types/ets_method_signature.h index d183d4f323..8c5a9b112d 100644 --- a/static_core/plugins/ets/runtime/types/ets_method_signature.h +++ b/static_core/plugins/ets/runtime/types/ets_method_signature.h @@ -23,7 +23,10 @@ namespace ark::ets { -// Arguments type separated from return type by ":". Object names bounded by 'L' and ';' +/** + * @brief Represents arguments type separated from return type by ":". + * Object names bounded by 'L' and ';', and union constituent types are bounded by "{U" and '}'. + */ class EtsMethodSignature { public: explicit EtsMethodSignature(const std::string_view sign, bool isANIFormat = false) @@ -91,6 +94,7 @@ private: size_t ProcessObjectParameter(const PandaString &signature, size_t i) { + ASSERT(i < signature.size()); while (signature[i] == '[') { ++i; if (i >= signature.size()) { @@ -108,6 +112,16 @@ private: if (i == PandaString::npos || i == (prevI + 1)) { return PandaString::npos; } + } else if (signature[i] == '{') { + if (i == signature.size() - 1 || signature[i + 1] != 'U') { + return PandaString::npos; + } + // Get union constituent types bounded between "{U" and '}' + size_t prevI = i + 1; + i = signature.find('}', prevI); + if (i == PandaString::npos || i == (prevI + 1)) { + return PandaString::npos; + } } return i; } @@ -115,6 +129,7 @@ private: { switch (c) { case 'L': + case '{': case '[': return EtsType::OBJECT; case 'Z': diff --git a/static_core/plugins/ets/tests/CMakeLists.txt b/static_core/plugins/ets/tests/CMakeLists.txt index eb0ba7a9d8..a05cad83e5 100644 --- a/static_core/plugins/ets/tests/CMakeLists.txt +++ b/static_core/plugins/ets/tests/CMakeLists.txt @@ -502,6 +502,7 @@ add_subdirectory(runtime) add_subdirectory(napi) add_subdirectory(ani) add_subdirectory(libani_helpers) +add_subdirectory(verify_unions) if(NOT (PANDA_ENABLE_ADDRESS_SANITIZER OR PANDA_ENABLE_THREAD_SANITIZER)) add_subdirectory(micro-benchmarks) endif() diff --git a/static_core/plugins/ets/tests/ani/tests/mangling/mangle_signature_test.cpp b/static_core/plugins/ets/tests/ani/tests/mangling/mangle_signature_test.cpp index c1bed415bd..21a1537b12 100644 --- a/static_core/plugins/ets/tests/ani/tests/mangling/mangle_signature_test.cpp +++ b/static_core/plugins/ets/tests/ani/tests/mangling/mangle_signature_test.cpp @@ -13,6 +13,8 @@ * limitations under the License. */ +#include +#include "ani.h" #include "ani_gtest.h" #include "plugins/ets/runtime/ani/ani_mangle.h" @@ -20,6 +22,17 @@ namespace ark::ets::ani::testing { class MangleSignatureTest : public AniTest {}; +// type F = (u: number | string | FixedArray) => (E | B) | Partial +static constexpr std::string_view FOO_UNION_SIGNATURE = + "X{A{c}C{std.core.Double}C{std.core.String}}:X{C{msig.B}E{msig.E}P{msig.A}}"; +static constexpr std::string_view NAMESPACE_FOO_UNION_SIGNATURE = + "X{A{c}C{std.core.Double}C{std.core.String}}:X{C{msig.rls.B}E{msig.rls.E}P{msig.A}}"; +// type F = (u: T | V | A | number): FixedArray | null | V +static constexpr std::string_view FOO1_UNION_SIGNATURE = + "X{C{msig.A}C{std.core.String}C{std.core.Double}}:X{A{C{msig.A}}NX{C{msig.A}C{std.core.String}}}"; +static constexpr std::string_view NAMESPACE_FOO1_UNION_SIGNATURE = + "X{C{msig.rls.A}C{std.core.String}C{std.core.Double}}:X{A{C{msig.rls.A}}NX{C{msig.rls.A}C{std.core.String}}}"; + TEST_F(MangleSignatureTest, FormatVoid_NewToOld) { PandaString desc; @@ -326,6 +339,28 @@ TEST_F(MangleSignatureTest, FormatReferencesFixedArray_OldToOld) EXPECT_STREQ(desc.c_str(), "[Lstd/core/String;D[[La/b/Color;:[La/b/X$partial;"); } +TEST_F(MangleSignatureTest, FormatUnion_NewToRuntime) +{ + PandaString desc; + + // type F = (u: a.b | double | FixedArray) => null | e + desc = Mangle::ConvertSignature("X{C{a.b}C{std.core.Double}A{i}}:X{NE{e}}"); + // NOTE(dslynko, #26222): do not replace union to `Object` when `null` will have its own publically fixed type + EXPECT_STREQ(desc.c_str(), "{ULa/b;[ILstd/core/Double;}:Lstd/core/Object;"); + + // type F = (u: (e | double) | FixedArray) => void + desc = Mangle::ConvertSignature("X{X{E{e}C{std.core.Double}}A{X{A{C{std.core.String}}C{std.core.FunctionR1}}}}:"); + EXPECT_STREQ(desc.c_str(), "{U{ULe;Lstd/core/Double;}[{ULstd/core/FunctionR1;[Lstd/core/String;}}:V"); + + // type F = (u: number | string | FixedArray) => (msig.E | msig.B) | Partial + desc = Mangle::ConvertSignature(FOO_UNION_SIGNATURE); + EXPECT_STREQ(desc.c_str(), "{ULstd/core/Double;Lstd/core/String;[C}:{ULmsig/A$partial;Lmsig/B;Lmsig/E;}"); + + // type F = (u: T | V | A | number): FixedArray | null | V + desc = Mangle::ConvertSignature(FOO1_UNION_SIGNATURE); + EXPECT_STREQ(desc.c_str(), "{ULmsig/A;Lstd/core/Double;Lstd/core/String;}:Lstd/core/Object;"); +} + TEST_F(MangleSignatureTest, Format_Wrong) { PandaString desc; @@ -368,6 +403,8 @@ TEST_F(MangleSignatureTest, Module_FindFunction) // Check references EXPECT_EQ(env_->Module_FindFunction(m, "foo", "dC{msig.A}C{msig.B}:E{msig.E}", &fn), ANI_OK); EXPECT_EQ(env_->Module_FindFunction(m, "foo", "P{msig.A}C{escompat.Array}:", &fn), ANI_OK); + EXPECT_EQ(env_->Module_FindFunction(m, "foo", FOO_UNION_SIGNATURE.data(), &fn), ANI_OK); + EXPECT_EQ(env_->Module_FindFunction(m, "foo1", FOO1_UNION_SIGNATURE.data(), &fn), ANI_OK); } TEST_F(MangleSignatureTest, Module_FindFunction_OldFormat) @@ -416,6 +453,8 @@ TEST_F(MangleSignatureTest, Namespace_FindFunction) // Check references EXPECT_EQ(env_->Namespace_FindFunction(ns, "foo", "dC{msig.rls.A}C{msig.rls.B}:E{msig.rls.E}", &fn), ANI_OK); EXPECT_EQ(env_->Namespace_FindFunction(ns, "foo", "P{msig.A}C{escompat.Array}:", &fn), ANI_OK); + EXPECT_EQ(env_->Namespace_FindFunction(ns, "foo", NAMESPACE_FOO_UNION_SIGNATURE.data(), &fn), ANI_OK); + EXPECT_EQ(env_->Namespace_FindFunction(ns, "foo1", NAMESPACE_FOO1_UNION_SIGNATURE.data(), &fn), ANI_OK); } TEST_F(MangleSignatureTest, Namespace_FindFunction_OldFormat) @@ -464,6 +503,8 @@ TEST_F(MangleSignatureTest, Class_FindMethod) // Check references EXPECT_EQ(env_->Class_FindMethod(cls, "foo", "dC{msig.A}C{msig.B}:E{msig.E}", &method), ANI_OK); EXPECT_EQ(env_->Class_FindMethod(cls, "foo", "P{msig.A}C{escompat.Array}:", &method), ANI_OK); + EXPECT_EQ(env_->Class_FindMethod(cls, "foo", FOO_UNION_SIGNATURE.data(), &method), ANI_OK); + EXPECT_EQ(env_->Class_FindMethod(cls, "foo1", FOO1_UNION_SIGNATURE.data(), &method), ANI_OK); } TEST_F(MangleSignatureTest, Class_FindMethod_OldFormat) @@ -512,6 +553,8 @@ TEST_F(MangleSignatureTest, Class_FindStaticMethod) // Check references EXPECT_EQ(env_->Class_FindStaticMethod(cls, "foo", "dC{msig.A}C{msig.B}:E{msig.E}", &method), ANI_OK); EXPECT_EQ(env_->Class_FindStaticMethod(cls, "foo", "P{msig.A}C{escompat.Array}:", &method), ANI_OK); + EXPECT_EQ(env_->Class_FindStaticMethod(cls, "foo", FOO_UNION_SIGNATURE.data(), &method), ANI_OK); + EXPECT_EQ(env_->Class_FindStaticMethod(cls, "foo1", FOO1_UNION_SIGNATURE.data(), &method), ANI_OK); } TEST_F(MangleSignatureTest, Class_FindStaticMethod_OldFormat) @@ -614,6 +657,23 @@ TEST_F(MangleSignatureTest, Class_CallStaticMethodByName) // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) ASSERT_EQ(env_->Class_CallStaticMethodByName_Int(cls, "foo", "iii:i", &result, 1, 2U, 3U), ANI_OK); ASSERT_EQ(result, 1 + 2U + 3U); + + // Check correct union type is returned from `Class_CallStaticMethodByName_Ref` + static constexpr std::string_view SAMPLE_STRING = "sample"; + ani_string sampleStr {}; + ASSERT_EQ(env_->String_NewUTF8(SAMPLE_STRING.data(), SAMPLE_STRING.size(), &sampleStr), ANI_OK); + ani_ref unionResult {}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + ASSERT_EQ(env_->Class_CallStaticMethodByName_Ref(cls, "foo", FOO_UNION_SIGNATURE.data(), &unionResult, sampleStr), + ANI_OK); + // Check returned object is of correct type + ani_boolean booleanResult = ANI_FALSE; + ASSERT_EQ(env_->Reference_IsUndefined(unionResult, &booleanResult), ANI_OK); + ASSERT_EQ(booleanResult, ANI_FALSE); + ani_enum enumClass {}; + ASSERT_EQ(env_->FindEnum("msig.E", &enumClass), ANI_OK); + ASSERT_EQ(env_->Object_InstanceOf(static_cast(unionResult), enumClass, &booleanResult), ANI_OK); + ASSERT_EQ(booleanResult, ANI_TRUE); } TEST_F(MangleSignatureTest, Class_CallStaticMethodByName_OldFormat) @@ -647,6 +707,23 @@ TEST_F(MangleSignatureTest, Object_CallMethodByName) // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) ASSERT_EQ(env_->Object_CallMethodByName_Int(object, "foo", "ii:i", &result, 1, 2U), ANI_OK); ASSERT_EQ(result, 1 + 2U); + + // Check correct union type is returned from `Object_CallMethodByName_Ref` + static constexpr std::string_view SAMPLE_STRING = "sample"; + ani_string sampleStr {}; + ASSERT_EQ(env_->String_NewUTF8(SAMPLE_STRING.data(), SAMPLE_STRING.size(), &sampleStr), ANI_OK); + ani_ref unionResult {}; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + ASSERT_EQ(env_->Object_CallMethodByName_Ref(object, "foo", FOO_UNION_SIGNATURE.data(), &unionResult, sampleStr), + ANI_OK); + // Check returned object is of correct type + ani_boolean booleanResult = ANI_FALSE; + ASSERT_EQ(env_->Reference_IsUndefined(unionResult, &booleanResult), ANI_OK); + ASSERT_EQ(booleanResult, ANI_FALSE); + ani_enum enumClass {}; + ASSERT_EQ(env_->FindEnum("msig.E", &enumClass), ANI_OK); + ASSERT_EQ(env_->Object_InstanceOf(static_cast(unionResult), enumClass, &booleanResult), ANI_OK); + ASSERT_EQ(booleanResult, ANI_TRUE); } TEST_F(MangleSignatureTest, Object_CallMethodByName_OldFormat) diff --git a/static_core/plugins/ets/tests/ani/tests/mangling/mangle_signature_test.ets b/static_core/plugins/ets/tests/ani/tests/mangling/mangle_signature_test.ets index 7d0b966967..1d2d4590c4 100644 --- a/static_core/plugins/ets/tests/ani/tests/mangling/mangle_signature_test.ets +++ b/static_core/plugins/ets/tests/ani/tests/mangling/mangle_signature_test.ets @@ -31,6 +31,8 @@ class F { foo(x: double) {} foo(x: double, y: A, z: B): E { return E.R } foo(p: Partial, a: Array) {} + foo(u: number | string | FixedArray): (E | B) | Partial { return E.R } + foo1(u: T | V | A | number): FixedArray | null | V { return null } $_get(index: number): int { return 0 } @@ -59,6 +61,8 @@ class G { static foo(x: double) {} static foo(x: double, y: A, z: B): E { return E.R } static foo(p: Partial, a: Array) {} + static foo(u: number | string | FixedArray): (E | B) | Partial { return E.R } + static foo1(u: T | V | A | number): FixedArray | null | V { return null } static foo(a: int, b: int, c: int): int {return a + b + c } } @@ -74,6 +78,8 @@ function foo(x: float) {} function foo(x: double) {} function foo(x: double, y: A, z: B): E { return E.R } function foo(p: Partial, a: Array) {} +function foo(u: number | string | FixedArray): (E | B) | Partial { return E.R } +function foo1(u: T | V | A | number): FixedArray | null | V { return null } namespace rls { interface A {} @@ -91,4 +97,6 @@ namespace rls { function foo(x: double) {} function foo(x: double, y: A, z: B): E { return E.R } function foo(p: Partial, a: Array) {} + function foo(u: number | string | FixedArray): (E | B) | Partial { return E.R } + function foo1(u: T | V | A | number): FixedArray | null | V { return null } }; diff --git a/static_core/plugins/ets/tests/ani/tests/object_ops/object_instance_of_test.cpp b/static_core/plugins/ets/tests/ani/tests/object_ops/object_instance_of_test.cpp index 2e4c5d1175..6fe69acbfc 100644 --- a/static_core/plugins/ets/tests/ani/tests/object_ops/object_instance_of_test.cpp +++ b/static_core/plugins/ets/tests/ani/tests/object_ops/object_instance_of_test.cpp @@ -232,7 +232,8 @@ TEST_F(ObjectInstanceOfTest, object_boxed_primitive_instance_of) { ani_object objectInt; ani_class classF; - GetMethodData(&objectInt, &classF, "Lobject_instance_of_test/F;", "new_Boxed_Primitive", ":Lstd/core/Object;"); + GetMethodData(&objectInt, &classF, "Lobject_instance_of_test/F;", "new_Boxed_Primitive", + ":X{C{std.core.Int}C{std.core.String}}"); ani_boolean res; ani_class classInt; diff --git a/static_core/plugins/ets/tests/runtime/types/CMakeLists.txt b/static_core/plugins/ets/tests/runtime/types/CMakeLists.txt index 946f0c6d00..1c9d694768 100644 --- a/static_core/plugins/ets/tests/runtime/types/CMakeLists.txt +++ b/static_core/plugins/ets/tests/runtime/types/CMakeLists.txt @@ -45,6 +45,7 @@ panda_ets_add_gtest( ets_promise_test.cpp ets_job_test.cpp ets_class_test.cpp + ets_union_test.cpp ets_string_from_char_code_test.cpp ets_sync_primitives_test.cpp ets_arraybuf_test.cpp diff --git a/static_core/plugins/ets/tests/runtime/types/ets_runtime_linker_test.cpp b/static_core/plugins/ets/tests/runtime/types/ets_runtime_linker_test.cpp index 45fe7e17c6..d925170a7d 100644 --- a/static_core/plugins/ets/tests/runtime/types/ets_runtime_linker_test.cpp +++ b/static_core/plugins/ets/tests/runtime/types/ets_runtime_linker_test.cpp @@ -15,6 +15,8 @@ #include +#include "assembly-emitter.h" +#include "assembly-parser.h" #include "types/ets_runtime_linker.h" #include "tests/runtime/types/ets_test_mirror_classes.h" @@ -88,10 +90,19 @@ TEST_F(EtsRuntimeLinkerTest, RuntimeLinkerMemoryLayout) } class SuppressErrorHandler : public ark::ClassLinkerErrorHandler { - void OnError([[maybe_unused]] ark::ClassLinker::Error error, - [[maybe_unused]] const ark::PandaString &message) override + void OnError([[maybe_unused]] ark::ClassLinker::Error error, const ark::PandaString &message) override { + message_ = message; } + +public: + const ark::PandaString &GetMessage() const + { + return message_; + } + +private: + ark::PandaString message_; }; TEST_F(EtsRuntimeLinkerTest, GetClassReturnsNullWhenErrorSuppressed) @@ -106,4 +117,80 @@ TEST_F(EtsRuntimeLinkerTest, GetClassReturnsNullWhenErrorSuppressed) ASSERT_EQ(klass, nullptr); } +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassRedecl) +{ + pandasm::Parser p; + + const char *source = + ".language eTS\n" + + ".record A {}\n" + ".record B {}\n" + ".record C {}\n" + ".record D {}\n" + ".record std.core.Object \n" + ".record {UA,B} \n" + ".record {UA,C} \n" + + ".function void D.foo(D a0, {UA,B} a1) { return.void }\n" + ".function void D.foo(D a0, {UA,C} a1) { return.void }"; + + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, pandasm::Error::ErrorType::ERR_NONE); + auto pf = pandasm::AsmEmitter::Emit(res.Value()); + + auto classLinker = Runtime::GetCurrent()->GetClassLinker(); + ASSERT(classLinker); + classLinker->AddPandaFile(std::move(pf)); + + auto *ext = static_cast( + ark::Runtime::GetCurrent()->GetClassLinker()->GetExtension(ark::panda_file::SourceLang::ETS)); + SuppressErrorHandler handler; + const auto *classWithError = reinterpret_cast("LD;"); + Class *klass = ext->GetClass(classWithError, true, nullptr, &handler); + ASSERT_EQ(klass, nullptr); + ASSERT_EQ(handler.GetMessage(), "Method is redeclarated LD;foo LD;foo"); +} + +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassMultiOverriding) +{ + pandasm::Parser p; + + const char *source = + ".language eTS\n" + + ".record A {}\n" + ".record AA {}\n" + ".record B {}\n" + ".record BB {}\n" + ".record C {}\n" + ".record CC {}\n" + ".record D {}\n" + ".record E {}\n" + ".record std.core.Object \n" + ".record {UA,C} \n" + ".record {UAA,BB} \n" + ".record {UB,C} \n" + ".function void D.foo(D a0, {UA,C} a1) { return.void }\n" + ".function void D.foo(D a0, {UB,C} a1) { return.void }\n" + ".function void E.foo(E a0, {UBB,AA} a1) { return.void }\n" + ".function void E.foo(E a0, {UB,C} a1) { return.void }"; + + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, pandasm::Error::ErrorType::ERR_NONE); + auto pf = pandasm::AsmEmitter::Emit(res.Value()); + + auto classLinker = Runtime::GetCurrent()->GetClassLinker(); + ASSERT(classLinker); + classLinker->AddPandaFile(std::move(pf)); + + auto *ext = static_cast( + ark::Runtime::GetCurrent()->GetClassLinker()->GetExtension(ark::panda_file::SourceLang::ETS)); + SuppressErrorHandler handler; + const auto *classWithError = reinterpret_cast("LE;"); + Class *klass = ext->GetClass(classWithError, true, nullptr, &handler); + ASSERT_EQ(klass, nullptr); + ASSERT_EQ(handler.GetMessage(), "Multiple override LE;foo LD;foo"); +} + } // namespace ark::ets::test diff --git a/static_core/plugins/ets/tests/runtime/types/ets_union_test.cpp b/static_core/plugins/ets/tests/runtime/types/ets_union_test.cpp new file mode 100644 index 0000000000..a04719b44a --- /dev/null +++ b/static_core/plugins/ets/tests/runtime/types/ets_union_test.cpp @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2025 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 + +#include "assembly-emitter.h" +#include "assembly-parser.h" +#include "plugins/ets/runtime/ets_class_linker_extension.h" +#include "plugins/ets/runtime/ets_coroutine.h" +#include "plugins/ets/runtime/ets_vm.h" +#include "utils/utf.h" + +namespace ark::ets::test { + +class EtsUnionTest : public testing::Test { +public: + EtsUnionTest() + { + options_.SetShouldLoadBootPandaFiles(true); + options_.SetShouldInitializeIntrinsics(false); + options_.SetCompilerEnableJit(false); + options_.SetGcType("epsilon"); + options_.SetLoadRuntimes({"ets"}); + + auto stdlib = std::getenv("PANDA_STD_LIB"); + if (stdlib == nullptr) { + std::cerr << "PANDA_STD_LIB env variable should be set and point to mock_stdlib.abc" << std::endl; + std::abort(); + } + options_.SetBootPandaFiles({stdlib}); + + Runtime::Create(options_); + } + + ~EtsUnionTest() override + { + Runtime::Destroy(); + } + + NO_COPY_SEMANTIC(EtsUnionTest); + NO_MOVE_SEMANTIC(EtsUnionTest); + + void SetUp() override + { + coroutine_ = EtsCoroutine::GetCurrent(); + vm_ = coroutine_->GetPandaVM(); + coroutine_->ManagedCodeBegin(); + } + + void TearDown() override + { + coroutine_->ManagedCodeEnd(); + } + +protected: + PandaEtsVM *vm_ = nullptr; // NOLINT(misc-non-private-member-variables-in-classes) + +private: + RuntimeOptions options_; + EtsCoroutine *coroutine_ = nullptr; +}; + +TEST_F(EtsUnionTest, CreateUnionClass) +{ + pandasm::Parser p; + + const char *source = + ".language eTS\n" + ".record std.core.Object \n" + ".record A {}\n" + ".record B {}\n" + ".record C {}\n" + ".record D {}\n" + + ".record {UA,B} \n" + ".record {UB,C} \n" + ".record {UA,B,C} \n" + ".record {UA,D} \n" + ".record {UA,{UB,C}[],D} "; + + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, pandasm::Error::ErrorType::ERR_NONE); + auto pf = pandasm::AsmEmitter::Emit(res.Value()); + + auto classLinker = Runtime::GetCurrent()->GetClassLinker(); + ASSERT(classLinker); + classLinker->AddPandaFile(std::move(pf)); + + EtsClassLinker *etsClassLinker = vm_->GetClassLinker(); + + std::map descMap {{"{ULA;LB;}", "LA;"}, + {"{ULB;LC;}", "LA;"}, + {"{ULA;LB;LC;}", "LA;"}, + {"{ULA;LB;[LC;}", "Lstd/core/Object;"}, + {"{ULA;LD;[{ULB;LC;}}", "Lstd/core/Object;"}}; + + for (const auto &[descFull, descLUB] : descMap) { + EtsClass *klass = etsClassLinker->GetClass(descFull.c_str()); + ASSERT_NE(klass, nullptr); + EXPECT_EQ(PandaString(klass->GetDescriptor()), descLUB); + } +} + +} // namespace ark::ets::test diff --git a/static_core/plugins/ets/tests/verify_unions/CMakeLists.txt b/static_core/plugins/ets/tests/verify_unions/CMakeLists.txt new file mode 100644 index 0000000000..9567e4dbb4 --- /dev/null +++ b/static_core/plugins/ets/tests/verify_unions/CMakeLists.txt @@ -0,0 +1,68 @@ +# Copyright (c) 2025 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. + +function(add_ets_verifier_test) + set(prefix ARG) + set(noValues VERIFIER_FAIL_TEST) + set(singleValues FILE) + cmake_parse_arguments(${prefix} + "${noValues}" + "${singleValues}" + "" + ${ARGN}) + cmake_parse_arguments(PARSE_ARGV 3 + ${prefix} + "" + "SEARCH_STDERR" + "") + if (ARG_VERIFIER_FAIL_TEST) + set(VERIFIER_FAIL_TEST VERIFIER_FAIL_TEST) + else() + set(VERIFIER_FAIL_TEST) + endif() + + set(error_file) + + verifier_add_asm_file( + FILE ${PANDA_ETS_PLUGIN_SOURCE}/tests/verify_unions/${ARG_FILE}.pa + TARGET verify_unions_${ARG_FILE}-verify + ${VERIFIER_FAIL_TEST} + SEARCH_STDERR "${ARG_SEARCH_STDERR}" + ERROR_FILE_VARIABLE error_file + DEPENDS etsstdlib + LANGUAGE_CONTEXT ets + STDLIBS $ + ) + add_dependencies(ets_union_asm_verify verify_unions_${ARG_FILE}-verify) + + if (DEFINED ARG_SEARCH_STDERR AND NOT (CMAKE_BUILD_TYPE MATCHES Release)) + add_custom_target(verify_unions_${ARG_FILE}-check-logmsg + COMMENT "Check verify_unions_${ARG_FILE} log message" + COMMAND grep -zo \"${ARG_SEARCH_STDERR}\" ${error_file} >/dev/null + DEPENDS verify_unions_${ARG_FILE}-verify) + + add_dependencies(ets_union_asm_verify verify_unions_${ARG_FILE}-check-logmsg) + endif() +endfunction() + +add_custom_target(ets_union_asm_verify) +add_dependencies(ets_union_asm_verify verifier) +add_dependencies(ets_tests ets_union_asm_verify) + +add_ets_verifier_test(FILE "correct_union_arg") +add_ets_verifier_test(FILE "correct_union_arg_2") +add_ets_verifier_test(FILE "correct_union_override") +add_ets_verifier_test(FILE "correct_union_override_2") +add_ets_verifier_test(FILE "neg_union_arg" VERIFIER_FAIL_TEST SEARCH_STDERR "Bad call incompatible parameter") +add_ets_verifier_test(FILE "neg_union_override_multi" VERIFIER_FAIL_TEST SEARCH_STDERR "Cannot link class: Multiple override LE\;foo LD\;foo") +add_ets_verifier_test(FILE "neg_union_arg_redecl" VERIFIER_FAIL_TEST SEARCH_STDERR "Cannot link class: Method is redeclarated LD\;foo LD\;foo") \ No newline at end of file diff --git a/static_core/plugins/ets/tests/verify_unions/correct_union_arg.pa b/static_core/plugins/ets/tests/verify_unions/correct_union_arg.pa new file mode 100644 index 0000000000..a1f60c1d65 --- /dev/null +++ b/static_core/plugins/ets/tests/verify_unions/correct_union_arg.pa @@ -0,0 +1,37 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record A {} +.record B {} +.record C {} +.record D {} +.record std.core.Object +.record {UB,C} + +.function void A._ctor_(A a0) { + return.void +} + +.function void foo({UB,C} a0) { + return.void +} + +.function i32 main() { + initobj.short A._ctor_:(A) + sta.obj v1 + call.short foo, v1 + ldai 0 + return +} diff --git a/static_core/plugins/ets/tests/verify_unions/correct_union_arg_2.pa b/static_core/plugins/ets/tests/verify_unions/correct_union_arg_2.pa new file mode 100644 index 0000000000..21a441571b --- /dev/null +++ b/static_core/plugins/ets/tests/verify_unions/correct_union_arg_2.pa @@ -0,0 +1,35 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record A {} +.record B {} +.record std.core.Object +.record {UA,B} + +.function void A._ctor_(A a0) { + return.void +} + +.function void foo({UA,B} a0) { + return.void +} + +.function i32 main() { + initobj.short A._ctor_:(A) + sta.obj v1 + call.short foo, v1 + ldai 0 + return +} diff --git a/static_core/plugins/ets/tests/verify_unions/correct_union_override.pa b/static_core/plugins/ets/tests/verify_unions/correct_union_override.pa new file mode 100644 index 0000000000..3519a67b40 --- /dev/null +++ b/static_core/plugins/ets/tests/verify_unions/correct_union_override.pa @@ -0,0 +1,57 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record std.core.Object +.record E {} +.record D {} +.record C {} +.record I {} +.record A {} +.record {UC,E} +.record {UD,E} + +.function void A._ctor_(A a0) { + return.void +} +.function void D._ctor_(D a0) { + return.void +} + +.record std.core.Console +.record std.core.String +.record std.core.ETSGLOBAL { + std.core.Console console +} +.function void std.core.Console.log(std.core.Console a0, std.core.String a1) +.function void I.bar(I a0, {UC,E} a1) + +.function void A.bar(A a0, {UD,E} a1) { + ldstatic.obj std.core.ETSGLOBAL.console + sta.obj v0 + lda.str "bar" + sta.obj v1 + call.short std.core.Console.log:(std.core.Console,std.core.String), v0, v1 + return.void +} + +.function i32 main() { + initobj.short A._ctor_:(A) + sta.obj v0 + initobj.short D._ctor_:(D) + sta.obj v1 + call.virt.short A.bar, v0, v1 + ldai 0 + return +} diff --git a/static_core/plugins/ets/tests/verify_unions/correct_union_override_2.pa b/static_core/plugins/ets/tests/verify_unions/correct_union_override_2.pa new file mode 100644 index 0000000000..4596d4d5fa --- /dev/null +++ b/static_core/plugins/ets/tests/verify_unions/correct_union_override_2.pa @@ -0,0 +1,52 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record A {} +.record B {} +.record C {} +.record D {} +.record E {} +.record std.core.Object +.record {UA,B} +.record {UA,C} + +.function void C._ctor_(C a0) { + return.void +} +.function void E._ctor_(E a0) { + return.void +} +.function void D.foo(D a0, {UB,C} a1) { + return.void +} +.function void D.foo(D a0, {UA,C} a1) { + return.void +} +.function void E.foo(E a0, {UA,B} a1) { + return.void +} + +.record ETSGLOBAL { +} + +.function i32 ETSGLOBAL.main() { + initobj.short E._ctor_:(E) + sta.obj v0 + initobj.short C._ctor_:(C) + sta.obj v1 + call.virt.short E.foo, v0, v1 + ldai 0 + return +} diff --git a/static_core/plugins/ets/tests/verify_unions/neg_union_arg.pa b/static_core/plugins/ets/tests/verify_unions/neg_union_arg.pa new file mode 100644 index 0000000000..b903da4920 --- /dev/null +++ b/static_core/plugins/ets/tests/verify_unions/neg_union_arg.pa @@ -0,0 +1,37 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record A {} +.record B {} +.record C {} +.record D {} +.record std.core.Object +.record {UB,C} + +.function void D._ctor_(D a0) { + return.void +} + +.function void foo({UB,C} a0) { + return.void +} + +.function i32 main() { + initobj.short D._ctor_:(D) + sta.obj v1 + call.short foo, v1 + ldai 1 + return +} diff --git a/static_core/plugins/ets/tests/verify_unions/neg_union_arg_redecl.pa b/static_core/plugins/ets/tests/verify_unions/neg_union_arg_redecl.pa new file mode 100644 index 0000000000..9dd6a36855 --- /dev/null +++ b/static_core/plugins/ets/tests/verify_unions/neg_union_arg_redecl.pa @@ -0,0 +1,49 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record A {} +.record B {} +.record C {} +.record D {} +.record E {} +.record std.core.Object +.record {UA,B} +.record {UA,C} + +.function void C._ctor_(C a0) { + return.void +} +.function void E._ctor_(E a0) { + return.void +} +.function void D.foo(D a0, {UA,B} a1) { + return.void +} +.function void D.foo(D a0, {UA,C} a1) { + return.void +} +.function void E.foo(E a0, {UA,C} a1) { + return.void +} + +.function i32 main() { + initobj.short E._ctor_:(E) + sta.obj v0 + initobj.short C._ctor_:(C) + sta.obj v1 + call.virt.short E.foo:(E,{UA,C}), v0, v1 + ldai 1 + return +} diff --git a/static_core/plugins/ets/tests/verify_unions/neg_union_override_multi.pa b/static_core/plugins/ets/tests/verify_unions/neg_union_override_multi.pa new file mode 100644 index 0000000000..b72febd6eb --- /dev/null +++ b/static_core/plugins/ets/tests/verify_unions/neg_union_override_multi.pa @@ -0,0 +1,57 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record A {} +.record AA {} +.record B {} +.record BB {} +.record C {} +.record CC {} +.record D {} +.record E {} +.record std.core.Object +.record {UA,C} +.record {UAA,BB} +.record {UB,C} +.record ETSGLOBAL {} + +.function void BB._ctor_(BB a0) { + return.void +} +.function void E._ctor_(E a0) { + return.void +} +.function void D.foo(D a0, {UA,C} a1) { + return.void +} +.function void D.foo(D a0, {UB,C} a1) { + return.void +} +.function void E.foo(E a0, {UBB,AA} a1) { + return.void +} +.function void E.foo(E a0, {UB,C} a1) { + return.void +} + +.function i32 ETSGLOBAL.main() { + initobj.short E._ctor_:(E) + sta.obj v0 + initobj.short BB._ctor_:(BB) + sta.obj v1 + call.virt.short E.foo:(E,{UBB,AA}), v0, v1 + ldai 1 + return +} diff --git a/static_core/runtime/BUILD.gn b/static_core/runtime/BUILD.gn index 9a85818826..a411e7642c 100644 --- a/static_core/runtime/BUILD.gn +++ b/static_core/runtime/BUILD.gn @@ -404,6 +404,7 @@ ohos_source_set("libarkruntime_set_static") { ":arkruntime_interpreter_impl", ":libpcre2_16", ":libpcre2_8", + "$ark_root/assembler:libarktsassembler", "$ark_root/compiler:libarkcompiler_intrinsics_gen_inl_entrypoints_bridge_asm_macro_inl", "$ark_root/compiler:libarkcompiler_intrinsics_gen_inl_intrinsics_enum_inl", "$ark_root/compiler:libarktscompiler", diff --git a/static_core/runtime/CMakeLists.txt b/static_core/runtime/CMakeLists.txt index 8a5005a67c..3d9fbe712a 100644 --- a/static_core/runtime/CMakeLists.txt +++ b/static_core/runtime/CMakeLists.txt @@ -594,7 +594,7 @@ panda_target_include_directories(arkruntime_obj PUBLIC ${VERIFIER_INCLUDE_DIR} ) -panda_target_link_libraries(arkruntime_obj arkbase arkfile arkcompiler dprof arkaotmanager arktarget_options) +panda_target_link_libraries(arkruntime_obj arkbase arkfile arkcompiler dprof arkaotmanager arktarget_options arkassembler) if (NOT PANDA_TARGET_OHOS) panda_target_link_libraries(arkruntime_obj atomic) endif() diff --git a/static_core/runtime/class_helper.cpp b/static_core/runtime/class_helper.cpp index eda09c7c25..53107d8a4a 100644 --- a/static_core/runtime/class_helper.cpp +++ b/static_core/runtime/class_helper.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021-2025 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 @@ -119,4 +119,74 @@ const uint8_t *ClassHelper::GetTypeDescriptor(const PandaString &name, PandaStri return utf::CStringAsMutf8(storage->c_str()); } +/* static */ +bool ClassHelper::IsPrimitive(const uint8_t *descriptor) +{ + switch (*descriptor) { + case 'V': + case 'Z': + case 'B': + case 'H': + case 'S': + case 'C': + case 'I': + case 'U': + case 'J': + case 'Q': + case 'F': + case 'D': + case 'A': + return true; + default: + return false; + } +} + +/* static */ +bool ClassHelper::IsReference(const uint8_t *descriptor) +{ + Span sp(descriptor, 1); + return sp[0] == 'L'; +} + +/* static */ +bool ClassHelper::IsUnion(const uint8_t *descriptor) +{ + Span sp(descriptor, 2); + return sp[0] == '{' && sp[1] == 'U'; +} + +static size_t GetUnionTypeComponentsNumber(const uint8_t *descriptor) +{ + size_t length = 1; + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + while (descriptor[length] != '}') { + if (descriptor[length] == '{') { + length += GetUnionTypeComponentsNumber(&(descriptor[length])); + } else { + length += 1; + } + } + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return length; +} + +/* static */ +Span ClassHelper::GetUnionComponent(const uint8_t *descriptor) +{ + if (IsPrimitive(descriptor)) { + return Span(descriptor, 1); + } + if (IsArrayDescriptor(descriptor)) { + auto dim = GetDimensionality(descriptor); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return Span(descriptor, dim + GetUnionComponent(&(descriptor[dim])).Size()); + } + if (IsUnion(descriptor)) { + return Span(descriptor, GetUnionTypeComponentsNumber(descriptor)); + } + ASSERT(IsReference(descriptor)); + return Span(descriptor, std::string(utf::Mutf8AsCString(descriptor)).find(';') + 1); +} + } // namespace ark diff --git a/static_core/runtime/class_linker.cpp b/static_core/runtime/class_linker.cpp index cad9d9876e..c3c3e7b826 100644 --- a/static_core/runtime/class_linker.cpp +++ b/static_core/runtime/class_linker.cpp @@ -1201,6 +1201,14 @@ Class *ClassLinker::CreateArrayClass(ClassLinkerExtension *ext, const uint8_t *d return arrayClass; } +Class *ClassLinker::LoadUnionClass(const uint8_t *descriptor, bool needCopyDescriptor, ClassLinkerContext *context, + ClassLinkerErrorHandler *errorHandler) +{ + auto langCtx = Runtime::GetCurrent()->GetLanguageContext(context->GetSourceLang()); + const uint8_t *descriptorLUB = GetExtension(langCtx)->ComputeLUB(context, descriptor); + return GetClass(descriptorLUB, needCopyDescriptor, context, errorHandler); +} + Class *ClassLinker::LoadArrayClass(const uint8_t *descriptor, bool needCopyDescriptor, ClassLinkerContext *context, ClassLinkerErrorHandler *errorHandler) { @@ -1279,6 +1287,10 @@ Class *ClassLinker::GetClass(const uint8_t *descriptor, bool needCopyDescriptor, return cls; } + if (ClassHelper::IsUnion(descriptor)) { + return LoadUnionClass(descriptor, needCopyDescriptor, context, errorHandler); + } + if (ClassHelper::IsArrayDescriptor(descriptor)) { return LoadArrayClass(descriptor, needCopyDescriptor, context, errorHandler); } @@ -1322,7 +1334,11 @@ Class *ClassLinker::GetClass(const panda_file::File &pf, panda_file::File::Entit if (cls != nullptr) { return cls; } + const uint8_t *descriptor = pf.GetStringData(id).data; + if (ClassHelper::IsUnion(descriptor)) { + return LoadUnionClass(descriptor, false, context, errorHandler); + } cls = FindLoadedClass(descriptor, context); if (cls != nullptr) { diff --git a/static_core/runtime/include/class_helper.h b/static_core/runtime/include/class_helper.h index 6791ebc54d..0e15b97680 100644 --- a/static_core/runtime/include/class_helper.h +++ b/static_core/runtime/include/class_helper.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. + * Copyright (c) 2021-2025 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 @@ -86,6 +86,11 @@ public: return dim; } + static bool IsPrimitive(const uint8_t *descriptor); + static bool IsReference(const uint8_t *descriptor); + static bool IsUnion(const uint8_t *descriptor); + static Span GetUnionComponent(const uint8_t *descriptor); + private: template static void AppendType(const uint8_t *descriptor, int rank, Str &result); diff --git a/static_core/runtime/include/class_linker.h b/static_core/runtime/include/class_linker.h index ac5e23b8bb..3e306c4f30 100644 --- a/static_core/runtime/include/class_linker.h +++ b/static_core/runtime/include/class_linker.h @@ -58,6 +58,7 @@ public: MULTIPLE_OVERRIDE, MULTIPLE_IMPLEMENT, INVALID_LAMBDA_CLASS, + REDECL_BY_TYPE_SIG }; ClassLinker(mem::InternalAllocatorPtr allocator, std::vector> &&extensions); @@ -346,6 +347,9 @@ private: Class *LoadArrayClass(const uint8_t *descriptor, bool needCopyDescriptor, ClassLinkerContext *context, ClassLinkerErrorHandler *errorHandler); + Class *LoadUnionClass(const uint8_t *descriptor, bool needCopyDescriptor, ClassLinkerContext *context, + ClassLinkerErrorHandler *errorHandler); + Class *LoadClass(const panda_file::File *pf, panda_file::File::EntityId classId, const uint8_t *descriptor, ClassLinkerContext *context, ClassLinkerErrorHandler *errorHandler, bool addToRuntime = true); diff --git a/static_core/runtime/include/class_linker_extension.h b/static_core/runtime/include/class_linker_extension.h index f922c47824..3542258941 100644 --- a/static_core/runtime/include/class_linker_extension.h +++ b/static_core/runtime/include/class_linker_extension.h @@ -90,6 +90,18 @@ public: virtual ClassLinkerContext *CreateApplicationClassLinkerContext(const PandaVector &path); + virtual const uint8_t *ComputeLUB([[maybe_unused]] const ClassLinkerContext *ctx, + [[maybe_unused]] const uint8_t *descriptor) + { + UNREACHABLE(); + } + + virtual std::pair ComputeLUBInfo( + [[maybe_unused]] const ClassLinkerContext *ctx, [[maybe_unused]] const uint8_t *descriptor) + { + UNREACHABLE(); + } + Class *GetClassRoot(ClassRoot root) const { return classRoots_[ToIndex(root)]; diff --git a/static_core/runtime/include/vtable_builder_variance-inl.h b/static_core/runtime/include/vtable_builder_variance-inl.h index 92512fce63..883eb13ecb 100644 --- a/static_core/runtime/include/vtable_builder_variance-inl.h +++ b/static_core/runtime/include/vtable_builder_variance-inl.h @@ -52,6 +52,11 @@ bool VarianceVTableBuilder::ProcessClassMethod auto &[itInfo, itEntry] = it.Value(); if (!itInfo->IsBase()) { + if (UNLIKELY(ProtoCompatibility(ctx)(itInfo->GetProtoId(), info->GetProtoId(), true))) { + OnVTableConflict(errorHandler_, ClassLinker::Error::REDECL_BY_TYPE_SIG, "Method is redeclarated", info, + itInfo); + return false; + } continue; } if (IsOverriddenBy(ctx, itInfo->GetProtoId(), info->GetProtoId()) && OverridePred()(itInfo, info)) { diff --git a/static_core/verification/messages.yaml b/static_core/verification/messages.yaml index 1143c2b643..218ae395ad 100644 --- a/static_core/verification/messages.yaml +++ b/static_core/verification/messages.yaml @@ -565,6 +565,13 @@ messages: short_message: > Expected instance field. + NotCanonicalizedUnionType: + number: 71 + args: type, expected_type + level: error + short_message: > + Union type is not canonized: '${type}'. Expected: '${expected_type}'. + CflowInvalidJumpOutsideMethodBody: number: 1000 args: method_name, jump_target_offset, jump_instruction_offset diff --git a/static_core/verification/type/type_system.cpp b/static_core/verification/type/type_system.cpp index fef57e95b7..874a348c54 100644 --- a/static_core/verification/type/type_system.cpp +++ b/static_core/verification/type/type_system.cpp @@ -15,6 +15,7 @@ #include "verification/type/type_system.h" +#include "assembler/assembly-type.h" #include "runtime/include/thread_scopes.h" #include "verification/jobs/job.h" #include "verification/jobs/service.h" @@ -37,6 +38,11 @@ static PandaString ClassNameToDescriptorString(char const *name) static Type DescriptorToType(ClassLinker *linker, ClassLinkerContext *ctx, uint8_t const *descr) { + PandaString strDesc = utf::Mutf8AsCString(descr); + auto canonDesc = pandasm::Type::CanonicalizeDescriptor(strDesc); + if (canonDesc != std::string(strDesc)) { + LOG_VERIFIER_NOT_CANONICALIZED_UNION_TYPE(strDesc, canonDesc); + } Job::ErrorHandler handler; auto klass = linker->GetClass(descr, true, ctx, &handler); if (klass != nullptr) { -- Gitee From e97751ccd5b210c341413f27a86f36583f998a91 Mon Sep 17 00:00:00 2001 From: Anna Antipina Date: Fri, 13 Jun 2025 18:43:31 +0300 Subject: [PATCH 2/3] Runtime union type implementation - Implement layout, linkage, subtyping for new union class - Add stubs in compiler and verifier that use lub calculation for optimizing and verifying - Add realization for stdlib UnionType, fix TypeApi for Unions. - Add tests for classlinker, verifier Testing: ninja all tests Change-Id: I81392e74cd19ac20052b6f7babed1e0a8541ec61 Signed-off-by: Anna Antipina Signed-off-by: Redkin Mikhail --- .../runtime/ets_class_linker_extension.cpp | 89 ++++++- .../ets/runtime/ets_class_linker_extension.h | 6 + .../ets/runtime/ets_vtable_builder.cpp | 108 ++++++++- .../plugins/ets/runtime/ets_vtable_builder.h | 14 +- .../ets/runtime/intrinsics/std_core_Type.cpp | 7 +- .../plugins/ets/runtime/types/ets_class.h | 17 ++ .../plugins/ets/runtime/types/ets_type.h | 1 + static_core/plugins/ets/tests/CMakeLists.txt | 1 - .../ets/tests/ets_test_suite/CMakeLists.txt | 1 + .../tests/ets_test_suite/gc/CMakeLists.txt | 1 + .../unions}/CMakeLists.txt | 54 ++++- .../unions}/correct_union_arg.pa | 0 .../unions}/correct_union_arg_2.pa | 0 .../unions/correct_union_arg_redecl.pa} | 0 .../unions}/correct_union_override.pa | 0 .../unions}/correct_union_override_2.pa | 0 .../unions}/neg_union_arg.pa | 0 .../unions/neg_union_override_multi1.pa} | 5 +- .../unions/neg_union_override_multi2.pa | 39 ++++ .../unions/neg_union_override_multi3.pa | 39 ++++ .../unions/neg_union_override_multi4.pa | 42 ++++ .../unions/neg_union_override_multi5.pa | 41 ++++ .../ets_test_suite/unions/unions_general.ets | 95 ++++++++ .../runtime/types/ets_runtime_linker_test.cpp | 217 +++++++++++++++++- .../tests/runtime/types/ets_union_test.cpp | 56 ++++- static_core/runtime/class_helper.cpp | 36 ++- static_core/runtime/class_linker.cpp | 157 +++++++++++-- static_core/runtime/compiler.cpp | 50 +++- .../core/core_class_linker_extension.cpp | 18 ++ .../core/core_class_linker_extension.h | 2 + static_core/runtime/include/class-inl.h | 98 +++++++- static_core/runtime/include/class.h | 20 ++ static_core/runtime/include/class_helper.h | 12 +- static_core/runtime/include/class_linker.h | 9 + .../runtime/include/class_linker_extension.h | 11 + static_core/runtime/include/hclass.h | 10 +- static_core/verification/absint/abs_int_inl.h | 8 +- .../gen/templates/job_fill_gen.h.erb | 8 +- static_core/verification/jobs/job.cpp | 32 ++- static_core/verification/jobs/job.h | 6 + static_core/verification/type/type_system.cpp | 23 +- 41 files changed, 1234 insertions(+), 99 deletions(-) rename static_core/plugins/ets/tests/{verify_unions => ets_test_suite/unions}/CMakeLists.txt (47%) rename static_core/plugins/ets/tests/{verify_unions => ets_test_suite/unions}/correct_union_arg.pa (100%) rename static_core/plugins/ets/tests/{verify_unions => ets_test_suite/unions}/correct_union_arg_2.pa (100%) rename static_core/plugins/ets/tests/{verify_unions/neg_union_arg_redecl.pa => ets_test_suite/unions/correct_union_arg_redecl.pa} (100%) rename static_core/plugins/ets/tests/{verify_unions => ets_test_suite/unions}/correct_union_override.pa (100%) rename static_core/plugins/ets/tests/{verify_unions => ets_test_suite/unions}/correct_union_override_2.pa (100%) rename static_core/plugins/ets/tests/{verify_unions => ets_test_suite/unions}/neg_union_arg.pa (100%) rename static_core/plugins/ets/tests/{verify_unions/neg_union_override_multi.pa => ets_test_suite/unions/neg_union_override_multi1.pa} (94%) create mode 100644 static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi2.pa create mode 100644 static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi3.pa create mode 100644 static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi4.pa create mode 100644 static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi5.pa create mode 100644 static_core/plugins/ets/tests/ets_test_suite/unions/unions_general.ets diff --git a/static_core/plugins/ets/runtime/ets_class_linker_extension.cpp b/static_core/plugins/ets/runtime/ets_class_linker_extension.cpp index d731239868..8bbe1ba25f 100644 --- a/static_core/plugins/ets/runtime/ets_class_linker_extension.cpp +++ b/static_core/plugins/ets/runtime/ets_class_linker_extension.cpp @@ -205,6 +205,38 @@ bool EtsClassLinkerExtension::InitializeArrayClass(Class *arrayClass, Class *com return true; } +bool EtsClassLinkerExtension::InitializeUnionClass(Class *unionClass, Span constituentClasses) +{ + ASSERT(IsInitialized()); + + ASSERT(!unionClass->IsInitialized()); + ASSERT(unionClass->GetConstituentTypes().begin() == nullptr); + + auto *objectClass = GetClassRoot(ClassRoot::OBJECT); + unionClass->SetBase(objectClass); + unionClass->SetConstituentTypes(constituentClasses); + + uint32_t accessFlags = ACC_FILE_MASK; + for (auto cl : constituentClasses) { + accessFlags &= cl->GetAccessFlags(); + } + accessFlags &= ~ACC_INTERFACE; + accessFlags |= ACC_FINAL | ACC_ABSTRACT; + + unionClass->SetAccessFlags(accessFlags); + + auto objectClassVtable = objectClass->GetVTable(); + auto unionClassVtable = unionClass->GetVTable(); + for (size_t i = 0; i < objectClassVtable.size(); i++) { + unionClassVtable[i] = objectClassVtable[i]; + } + + unionClass->SetState(Class::State::INITIALIZED); + + ASSERT(unionClass->IsUnionClass()); // After init, we give out a well-formed union class. + return true; +} + bool EtsClassLinkerExtension::InitializeClass(Class *klass) { return InitializeClass(klass, GetErrorHandler()); @@ -709,18 +741,59 @@ ClassLinkerContext *EtsClassLinkerExtension::CreateApplicationClassLinkerContext return ctx; } -const uint8_t *EtsClassLinkerExtension::ComputeLUB(const ClassLinkerContext *ctx, const uint8_t *descriptor) +/* static */ +ClassLinkerContext *EtsClassLinkerExtension::GetParentContext(ClassLinkerContext *ctx) { - auto [pf, id] = ComputeLUBInfo(ctx, descriptor); - return RefTypeLink(ctx, pf, id).GetDescriptor(); + auto *linker = GetOrCreateEtsRuntimeLinker(ctx); + auto *abcRuntimeLinker = EtsAbcRuntimeLinker::FromEtsObject(linker); + auto *parentLinker = abcRuntimeLinker->GetParentLinker(); + return parentLinker->GetClassLinkerContext(); } -std::pair GetClassInfo(ClassLinker *cl, - const ClassLinkerContext *ctx, - const uint8_t *desc) +ClassLinkerContext *EtsClassLinkerExtension::GetCommonContext(ClassLinkerContext *ctx1, ClassLinkerContext *ctx2) { - auto cda = GetClassDataAccessor(cl, const_cast(ctx), desc); - return {&(cda.GetPandaFile()), cda.GetClassId()}; + if (ctx1 == ctx2) { + return ctx1; + } + + auto *parentCtx1 = GetParentContext(ctx1); + while (!parentCtx1->IsBootContext()) { + auto *parentCtx2 = GetParentContext(ctx2); + while (!parentCtx2->IsBootContext()) { + if (parentCtx1 == parentCtx2) { + return parentCtx2; + } + parentCtx2 = GetParentContext(parentCtx2); + } + parentCtx1 = GetParentContext(parentCtx1); + } + return parentCtx1; +} + +ClassLinkerContext *EtsClassLinkerExtension::GetCommonContext(Span classes) +{ + ClassLinkerContext *commonCtx {nullptr}; + for (auto *klass : classes) { + auto *ctx = klass->GetLoadContext(); + if (ctx->IsBootContext()) { + return ctx; + } + if (commonCtx == nullptr) { + commonCtx = ctx; + continue; + } + commonCtx = GetCommonContext(commonCtx, ctx); + if (commonCtx->IsBootContext()) { + return commonCtx; + } + } + return commonCtx; +} + +const uint8_t *EtsClassLinkerExtension::ComputeLUB(const ClassLinkerContext *ctx, const uint8_t *descriptor) +{ + auto [pf, id] = ComputeLUBInfo(ctx, descriptor); + return RefTypeLink(ctx, pf, id).GetDescriptor(); } std::pair EtsClassLinkerExtension::ComputeLUBInfo( diff --git a/static_core/plugins/ets/runtime/ets_class_linker_extension.h b/static_core/plugins/ets/runtime/ets_class_linker_extension.h index 6fdfd69ae7..7d3d02f744 100644 --- a/static_core/plugins/ets/runtime/ets_class_linker_extension.h +++ b/static_core/plugins/ets/runtime/ets_class_linker_extension.h @@ -49,6 +49,8 @@ public: bool InitializeArrayClass(Class *arrayClass, Class *componentClass) override; + bool InitializeUnionClass(Class *unionClass, Span constituentClasses) override; + void InitializePrimitiveClass(Class *primitiveClass) override; size_t GetClassVTableSize(ClassRoot root) override; @@ -114,6 +116,10 @@ public: static EtsRuntimeLinker *GetOrCreateEtsRuntimeLinker(ClassLinkerContext *ctx); + ClassLinkerContext *GetCommonContext(Span classes) override; + ClassLinkerContext *GetCommonContext(ClassLinkerContext *ctx1, ClassLinkerContext *ctx2); + static ClassLinkerContext *GetParentContext(ClassLinkerContext *ctx); + /// @brief Removes reference to `RuntimeLinker` from `BootContext` and `EtsClassLinkerContext`. static void RemoveRefToLinker(ClassLinkerContext *ctx); diff --git a/static_core/plugins/ets/runtime/ets_vtable_builder.cpp b/static_core/plugins/ets/runtime/ets_vtable_builder.cpp index 0dc0e168d2..d22e583f54 100644 --- a/static_core/plugins/ets/runtime/ets_vtable_builder.cpp +++ b/static_core/plugins/ets/runtime/ets_vtable_builder.cpp @@ -119,6 +119,85 @@ static bool RefExtendsOrImplements(const ClassLinkerContext *ctx, RefTypeLink su return RefIsAssignableToImpl(ctx, RefTypeLink(ctx, &subCDA.GetPandaFile(), subCDA.GetSuperClassId()), super, depth); } +// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) +template +static std::pair UnionIsAssignableToRef(const ClassLinkerContext *ctx, RefTypeLink sub, + RefTypeLink unionRef, uint32_t depth, uint32_t processIdx = 0) +{ + auto [res, idx] = [processIdx]() { + if (processIdx == 0) { + return std::pair {false, 2U}; + } + return std::pair {true, processIdx}; + }(); + const auto *descriptor = unionRef.GetDescriptor(); + auto *classLinker = Runtime::GetCurrent()->GetClassLinker(); + while (descriptor[idx] != '}') { + auto typeSp = ClassHelper::GetUnionComponent(&(descriptor[idx])); + idx += typeSp.Size(); + PandaString typeDescCopy(utf::Mutf8AsCString(typeSp.Data()), typeSp.Size()); + auto [typePf, typeClassId] = GetClassInfo(classLinker, ctx, utf::CStringAsMutf8(typeDescCopy.c_str())); + auto type = RefTypeLink(ctx, typePf, typeClassId); + + bool isAssign; + if constexpr (IS_UNION_SUPER) { + isAssign = RefIsAssignableToImpl(ctx, sub, type, depth); + } else { + isAssign = RefIsAssignableToImpl(ctx, type, sub, depth); + } + + if constexpr (IS_STRICT) { + if (!isAssign) { + return {false, idx}; + } + } + res |= isAssign; + } + return {res, idx}; +} + +Class *GetClass(ClassLinker *cl, ClassLinkerContext *ctx, const uint8_t *desc, ClassLinkerErrorHandler *errorHandler); + +bool UnionIsAssignableToUnion(const ClassLinkerContext *ctx, RefTypeLink sub, RefTypeLink super, uint32_t depth) +{ + auto idx = 2; + const auto *descriptor = sub.GetDescriptor(); + auto *classLinker = Runtime::GetCurrent()->GetClassLinker(); + while (descriptor[idx] != '}') { + auto typeSp = ClassHelper::GetUnionComponent(&(descriptor[idx])); + idx += typeSp.Size(); + + PandaString typeDescCopy(utf::Mutf8AsCString(typeSp.Data()), typeSp.Size()); + auto [typePf, typeClassId] = GetClassInfo(classLinker, ctx, utf::CStringAsMutf8(typeDescCopy.c_str())); + auto type = RefTypeLink(ctx, typePf, typeClassId); + auto [res, processIdx] = UnionIsAssignableToRef(ctx, type, super, depth); + if (res) { + return true; + } + std::tie(res, processIdx) = UnionIsAssignableToRef(ctx, type, super, depth, processIdx); + if (!res) { + return false; + } + } + return true; +} +// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + +bool IsAssignableToUnion([[maybe_unused]] const ClassLinkerContext *ctx, [[maybe_unused]] RefTypeLink sub, + [[maybe_unused]] RefTypeLink super, [[maybe_unused]] uint32_t depth) +{ + if (!ClassHelper::IsUnionDescriptor(super.GetDescriptor())) { + return std::get(UnionIsAssignableToRef(ctx, super, sub, depth)); + } + + if (!ClassHelper::IsUnionDescriptor(sub.GetDescriptor())) { + return std::get(UnionIsAssignableToRef(ctx, sub, super, depth)); + } + + return UnionIsAssignableToUnion(ctx, sub, super, depth); + return true; +} + static bool RefIsAssignableToImpl(const ClassLinkerContext *ctx, RefTypeLink sub, RefTypeLink super, uint32_t depth) { if (UNLIKELY(depth-- == 0)) { @@ -142,6 +221,9 @@ static bool RefIsAssignableToImpl(const ClassLinkerContext *ctx, RefTypeLink sub RefTypeLink superComp(ctx, ClassHelper::GetComponentDescriptor(super.GetDescriptor())); return RefIsAssignableToImpl(ctx, subComp, superComp, depth); } + if (ClassHelper::IsUnionDescriptor(super.GetDescriptor()) || ClassHelper::IsUnionDescriptor(sub.GetDescriptor())) { + return IsAssignableToUnion(ctx, sub, super, depth); + } // Assume array does not implement interfaces if (ClassHelper::IsArrayDescriptor(sub.GetDescriptor())) { return false; @@ -197,13 +279,37 @@ bool ETSProtoIsOverriddenBy(const ClassLinkerContext *ctx, Method::ProtoId const return true; } +Class *GetClass(ClassLinker *cl, ClassLinkerContext *ctx, const uint8_t *desc, ClassLinkerErrorHandler *errorHandler) +{ + auto cda = RefTypeLink(ctx, desc).CreateCDA(); + if (cda.has_value()) { + cl->GetClass(cda.value().GetPandaFile(), cda.value().GetClassId(), ctx, errorHandler); + } + return cl->GetClass(desc, false, ctx); +} + +std::pair GetClassInfo(ClassLinker *cl, + const ClassLinkerContext *ctx, + const uint8_t *desc) +{ + auto cda = RefTypeLink(ctx, desc).CreateCDA(); + if (cda.has_value()) { + return {&cda.value().GetPandaFile(), cda.value().GetClassId()}; + } + auto *klass = cl->GetClass(desc, false, const_cast(ctx)); + ASSERT(klass != nullptr); + const auto *pf = klass->GetPandaFile(); + ASSERT(pf != nullptr); + return {pf, pf->GetClassId(desc)}; +} + panda_file::ClassDataAccessor GetClassDataAccessor(ClassLinker *cl, ClassLinkerContext *ctx, const uint8_t *desc) { auto cda = RefTypeLink(ctx, desc).CreateCDA(); if (cda.has_value()) { return cda.value(); } - auto *klass = cl->GetClass(desc, false, ctx); + auto *klass = cl->GetClass(desc, false, const_cast(ctx)); ASSERT(klass != nullptr); const auto *pf = klass->GetPandaFile(); ASSERT(pf != nullptr); diff --git a/static_core/plugins/ets/runtime/ets_vtable_builder.h b/static_core/plugins/ets/runtime/ets_vtable_builder.h index 1c8891740b..0fe51b2655 100644 --- a/static_core/plugins/ets/runtime/ets_vtable_builder.h +++ b/static_core/plugins/ets/runtime/ets_vtable_builder.h @@ -31,7 +31,11 @@ bool ETSProtoIsOverriddenBy(const ClassLinkerContext *ctx, Method::ProtoId const bool isStrict = false); std::optional GetClosestCommonAncestor(ClassLinker *cl, const ClassLinkerContext *ctx, RefTypeLink source, RefTypeLink target); -panda_file::ClassDataAccessor GetClassDataAccessor(ClassLinker *cl, ClassLinkerContext *ctx, const uint8_t *desc); +std::pair GetClassInfo(ClassLinker *cl, + const ClassLinkerContext *ctx, + const uint8_t *desc); +Class *GetClass(ClassLinker *cl, ClassLinkerContext *ctx, const uint8_t *desc, + ClassLinkerErrorHandler *errorHandler = nullptr); class EtsVTableCompatibleSignatures { public: @@ -75,14 +79,6 @@ public: static RefTypeLink InPDA(const ClassLinkerContext *ctx, panda_file::ProtoDataAccessor &pda, uint32_t idx) { - uint8_t const *refDescriptor = pda.GetPandaFile().GetStringData(pda.GetReferenceType(idx)).data; - if (ClassHelper::IsUnion(refDescriptor)) { - auto langCtx = - Runtime::GetCurrent()->GetLanguageContext(const_cast(ctx)->GetSourceLang()); - auto *ext = Runtime::GetCurrent()->GetClassLinker()->GetExtension(langCtx); - auto [pf, id] = ext->ComputeLUBInfo(ctx, refDescriptor); - return RefTypeLink(ctx, pf, id); - } return RefTypeLink(ctx, &pda.GetPandaFile(), pda.GetReferenceType(idx)); } diff --git a/static_core/plugins/ets/runtime/intrinsics/std_core_Type.cpp b/static_core/plugins/ets/runtime/intrinsics/std_core_Type.cpp index 71b2f7c3dc..063b705708 100644 --- a/static_core/plugins/ets/runtime/intrinsics/std_core_Type.cpp +++ b/static_core/plugins/ets/runtime/intrinsics/std_core_Type.cpp @@ -88,8 +88,10 @@ static EtsByte GetRefTypeKind(const EtsClass *refType) result = static_cast(EtsTypeAPIKind::STRING); } else if (refType->IsNullValue()) { result = static_cast(EtsTypeAPIKind::NUL); + } else if (refType->IsUnionClass()) { + result = static_cast(EtsTypeAPIKind::UNION); } else { - // NOTE(vpukhov): EtsTypeAPIKind:: UNION, TUPLE are not implemented + // NOTE(vpukhov): EtsTypeAPIKind:: TUPLE are not implemented ASSERT(refType->IsClass()); result = static_cast(EtsTypeAPIKind::CLASS); } @@ -163,7 +165,8 @@ EtsByte TypeAPIGetTypeKind(EtsString *td, EtsRuntimeLinker *contextLinker) return static_cast(EtsTypeAPIKind::VOID); } // Is RefType? - if (typeDesc[0] == CLASS_TYPE_PREFIX || typeDesc[0] == ARRAY_TYPE_PREFIX) { + if (typeDesc[0] == CLASS_TYPE_PREFIX || typeDesc[0] == ARRAY_TYPE_PREFIX || + typeDesc[0] == UNION_OR_ENUM_TYPE_PREFIX) { auto *refType = TypeAPIGetClass(td, contextLinker); if (refType == nullptr) { return static_cast(EtsTypeAPIKind::NONE); diff --git a/static_core/plugins/ets/runtime/types/ets_class.h b/static_core/plugins/ets/runtime/types/ets_class.h index 0c4b44f89c..749039275a 100644 --- a/static_core/plugins/ets/runtime/types/ets_class.h +++ b/static_core/plugins/ets/runtime/types/ets_class.h @@ -221,6 +221,11 @@ public: return GetRuntimeClass()->IsArrayClass(); } + bool IsUnionClass() const + { + return GetRuntimeClass()->IsUnionClass(); + } + bool IsInterface() const { return GetRuntimeClass()->IsInterface(); @@ -305,6 +310,18 @@ public: } } + template + void EnumerateConstituentClasses(const Callback &callback) + { + for (Class *runtimeClasses : GetRuntimeClass()->GetConstituentTypes()) { + EtsClass *klass = EtsClass::FromRuntimeClass(runtimeClasses); + bool finished = callback(klass); + if (finished) { + break; + } + } + } + template void EnumerateBaseClasses(const Callback &callback) { diff --git a/static_core/plugins/ets/runtime/types/ets_type.h b/static_core/plugins/ets/runtime/types/ets_type.h index 70e497723c..a03c52946d 100644 --- a/static_core/plugins/ets/runtime/types/ets_type.h +++ b/static_core/plugins/ets/runtime/types/ets_type.h @@ -26,6 +26,7 @@ namespace ark::ets { // plugins/ecmascript/es2panda/compiler/scripts/signatures.yaml static constexpr char ARRAY_TYPE_PREFIX = '['; static constexpr char CLASS_TYPE_PREFIX = 'L'; +static constexpr char UNION_OR_ENUM_TYPE_PREFIX = '{'; static constexpr char METHOD_PREFIX = 'M'; static constexpr const char *TYPE_API_UNDEFINED_TYPE_DESC = "__TYPE_API_UNDEFINED"; static constexpr const char *INVOKE_METHOD_NAME = "$_invoke"; diff --git a/static_core/plugins/ets/tests/CMakeLists.txt b/static_core/plugins/ets/tests/CMakeLists.txt index a05cad83e5..eb0ba7a9d8 100644 --- a/static_core/plugins/ets/tests/CMakeLists.txt +++ b/static_core/plugins/ets/tests/CMakeLists.txt @@ -502,7 +502,6 @@ add_subdirectory(runtime) add_subdirectory(napi) add_subdirectory(ani) add_subdirectory(libani_helpers) -add_subdirectory(verify_unions) if(NOT (PANDA_ENABLE_ADDRESS_SANITIZER OR PANDA_ENABLE_THREAD_SANITIZER)) add_subdirectory(micro-benchmarks) endif() diff --git a/static_core/plugins/ets/tests/ets_test_suite/CMakeLists.txt b/static_core/plugins/ets/tests/ets_test_suite/CMakeLists.txt index 6c5aef969c..977a6d5b49 100644 --- a/static_core/plugins/ets/tests/ets_test_suite/CMakeLists.txt +++ b/static_core/plugins/ets/tests/ets_test_suite/CMakeLists.txt @@ -86,6 +86,7 @@ add_subdirectory(gc) add_subdirectory(linker) add_subdirectory(modules) add_subdirectory(strings) +add_subdirectory(unions) add_subdirectory(functions) add_subdirectory(object_literal) add_subdirectory(profiler) diff --git a/static_core/plugins/ets/tests/ets_test_suite/gc/CMakeLists.txt b/static_core/plugins/ets/tests/ets_test_suite/gc/CMakeLists.txt index c3f394ee98..d2d092871e 100644 --- a/static_core/plugins/ets/tests/ets_test_suite/gc/CMakeLists.txt +++ b/static_core/plugins/ets/tests/ets_test_suite/gc/CMakeLists.txt @@ -82,6 +82,7 @@ add_dependencies(mm_tests ets_test_suite_coroutines ets_test_suite_gc ets_test_suite_strings + ets_test_suite_unions bouncing_pandas_ets-ets-int bouncing_pandas_ets-ets-int-cpp bouncing_pandas_ets-ets-jit diff --git a/static_core/plugins/ets/tests/verify_unions/CMakeLists.txt b/static_core/plugins/ets/tests/ets_test_suite/unions/CMakeLists.txt similarity index 47% rename from static_core/plugins/ets/tests/verify_unions/CMakeLists.txt rename to static_core/plugins/ets/tests/ets_test_suite/unions/CMakeLists.txt index 9567e4dbb4..2dd56d0c9a 100644 --- a/static_core/plugins/ets/tests/verify_unions/CMakeLists.txt +++ b/static_core/plugins/ets/tests/ets_test_suite/unions/CMakeLists.txt @@ -11,6 +11,36 @@ # See the License for the specific language governing permissions and # limitations under the License. + +set(unions_tests + unions_general +) + +set(unions_tests_in_dir "${CMAKE_CURRENT_SOURCE_DIR}") +set(unions_tests_out_dir "${CMAKE_CURRENT_BINARY_DIR}") + +add_custom_target(ets_test_suite_unions) + +foreach(test ${unions_tests}) + set(test_out_dir "${unions_tests_out_dir}/${test}") + + set(test_in "${unions_tests_in_dir}/${test}.ets") + set(target ets_test_suite_unions_${test}) + + if (PANDA_TARGET_ARM32) + # failure in regalloc, see #13413 + set(extra_options RUNTIME_EXTRA_OPTIONS "--compiler-inlining-blacklist=std.core.ETSGLOBAL::copyTo,std.core.String::codePointAt" "--compiler-regex='(?!std.core.ETSGLOBAL::copyTo)&(?!std.core.String::codePointAt).*'" "--compiler-ignore-failures=true") + endif() + + run_int_jit_aot_ets_code(${test_in} ${test_out_dir} ${target} ${extra_options}) + add_dependencies(ets_test_suite_unions + ${target}-ets-jit + ${target}-ets-int) + if (NOT CMAKE_CROSSCOMPILING) + add_dependencies(ets_test_suite_unions ${target}-ets-aot) + endif() +endforeach() + function(add_ets_verifier_test) set(prefix ARG) set(noValues VERIFIER_FAIL_TEST) @@ -34,8 +64,8 @@ function(add_ets_verifier_test) set(error_file) verifier_add_asm_file( - FILE ${PANDA_ETS_PLUGIN_SOURCE}/tests/verify_unions/${ARG_FILE}.pa - TARGET verify_unions_${ARG_FILE}-verify + FILE ${PANDA_ETS_PLUGIN_SOURCE}/tests/ets_test_suite/unions/${ARG_FILE}.pa + TARGET unions_${ARG_FILE}-verify ${VERIFIER_FAIL_TEST} SEARCH_STDERR "${ARG_SEARCH_STDERR}" ERROR_FILE_VARIABLE error_file @@ -43,26 +73,30 @@ function(add_ets_verifier_test) LANGUAGE_CONTEXT ets STDLIBS $ ) - add_dependencies(ets_union_asm_verify verify_unions_${ARG_FILE}-verify) + add_dependencies(ets_union_asm_verify unions_${ARG_FILE}-verify) if (DEFINED ARG_SEARCH_STDERR AND NOT (CMAKE_BUILD_TYPE MATCHES Release)) - add_custom_target(verify_unions_${ARG_FILE}-check-logmsg - COMMENT "Check verify_unions_${ARG_FILE} log message" + add_custom_target(unions_${ARG_FILE}-check-logmsg + COMMENT "Check unions_${ARG_FILE} log message" COMMAND grep -zo \"${ARG_SEARCH_STDERR}\" ${error_file} >/dev/null - DEPENDS verify_unions_${ARG_FILE}-verify) + DEPENDS unions_${ARG_FILE}-verify) - add_dependencies(ets_union_asm_verify verify_unions_${ARG_FILE}-check-logmsg) + add_dependencies(ets_union_asm_verify unions_${ARG_FILE}-check-logmsg) endif() endfunction() add_custom_target(ets_union_asm_verify) add_dependencies(ets_union_asm_verify verifier) -add_dependencies(ets_tests ets_union_asm_verify) +add_dependencies(ets_test_suite_unions ets_union_asm_verify) add_ets_verifier_test(FILE "correct_union_arg") add_ets_verifier_test(FILE "correct_union_arg_2") add_ets_verifier_test(FILE "correct_union_override") add_ets_verifier_test(FILE "correct_union_override_2") add_ets_verifier_test(FILE "neg_union_arg" VERIFIER_FAIL_TEST SEARCH_STDERR "Bad call incompatible parameter") -add_ets_verifier_test(FILE "neg_union_override_multi" VERIFIER_FAIL_TEST SEARCH_STDERR "Cannot link class: Multiple override LE\;foo LD\;foo") -add_ets_verifier_test(FILE "neg_union_arg_redecl" VERIFIER_FAIL_TEST SEARCH_STDERR "Cannot link class: Method is redeclarated LD\;foo LD\;foo") \ No newline at end of file +add_ets_verifier_test(FILE "neg_union_override_multi1" VERIFIER_FAIL_TEST SEARCH_STDERR "Cannot link class: Multiple override LE\;foo LD\;foo") +add_ets_verifier_test(FILE "neg_union_override_multi2" VERIFIER_FAIL_TEST SEARCH_STDERR "Cannot link class: Multiple override LE\;foo LD\;foo") +add_ets_verifier_test(FILE "neg_union_override_multi3" VERIFIER_FAIL_TEST SEARCH_STDERR "Cannot link class: Multiple override LE\;foo LD\;foo") +add_ets_verifier_test(FILE "neg_union_override_multi4" VERIFIER_FAIL_TEST SEARCH_STDERR "Cannot link class: Multiple override LE\;foo LD\;foo") +add_ets_verifier_test(FILE "neg_union_override_multi5" VERIFIER_FAIL_TEST SEARCH_STDERR "Cannot link class: Multiple override LE\;foo LD\;foo") +add_ets_verifier_test(FILE "correct_union_arg_redecl") \ No newline at end of file diff --git a/static_core/plugins/ets/tests/verify_unions/correct_union_arg.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_arg.pa similarity index 100% rename from static_core/plugins/ets/tests/verify_unions/correct_union_arg.pa rename to static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_arg.pa diff --git a/static_core/plugins/ets/tests/verify_unions/correct_union_arg_2.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_arg_2.pa similarity index 100% rename from static_core/plugins/ets/tests/verify_unions/correct_union_arg_2.pa rename to static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_arg_2.pa diff --git a/static_core/plugins/ets/tests/verify_unions/neg_union_arg_redecl.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_arg_redecl.pa similarity index 100% rename from static_core/plugins/ets/tests/verify_unions/neg_union_arg_redecl.pa rename to static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_arg_redecl.pa diff --git a/static_core/plugins/ets/tests/verify_unions/correct_union_override.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_override.pa similarity index 100% rename from static_core/plugins/ets/tests/verify_unions/correct_union_override.pa rename to static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_override.pa diff --git a/static_core/plugins/ets/tests/verify_unions/correct_union_override_2.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_override_2.pa similarity index 100% rename from static_core/plugins/ets/tests/verify_unions/correct_union_override_2.pa rename to static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_override_2.pa diff --git a/static_core/plugins/ets/tests/verify_unions/neg_union_arg.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_arg.pa similarity index 100% rename from static_core/plugins/ets/tests/verify_unions/neg_union_arg.pa rename to static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_arg.pa diff --git a/static_core/plugins/ets/tests/verify_unions/neg_union_override_multi.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi1.pa similarity index 94% rename from static_core/plugins/ets/tests/verify_unions/neg_union_override_multi.pa rename to static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi1.pa index b72febd6eb..0569d4d0ed 100644 --- a/static_core/plugins/ets/tests/verify_unions/neg_union_override_multi.pa +++ b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi1.pa @@ -45,13 +45,12 @@ .function void E.foo(E a0, {UB,C} a1) { return.void } - -.function i32 ETSGLOBAL.main() { +.function i32 main() { initobj.short E._ctor_:(E) sta.obj v0 initobj.short BB._ctor_:(BB) sta.obj v1 - call.virt.short E.foo:(E,{UBB,AA}), v0, v1 + call.virt.short E.foo:(E,{UB,C}), v0, v1 ldai 1 return } diff --git a/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi2.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi2.pa new file mode 100644 index 0000000000..c8a84f9b4a --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi2.pa @@ -0,0 +1,39 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record Base {} +.record I {} +.record A {} +.record D {} +.record E {} +.record std.core.Object +.function void D.foo(D a0, A a1) { return.void } +.function void E.foo(E a0, {UD,Base,I} a1) { return.void } +.function void E.foo(E a0, I a1) { return.void } +.function void E._ctor_(E a0) { + return.void +} +.function void D._ctor_(D a0) { + return.void +} +.function i32 main() { + initobj.short E._ctor_:(E) + sta.obj v0 + initobj.short D._ctor_:(D) + sta.obj v1 + call.virt.short E.foo:(E,{UBase,I,D}), v0, v1 + ldai 1 + return +} \ No newline at end of file diff --git a/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi3.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi3.pa new file mode 100644 index 0000000000..0434b53acb --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi3.pa @@ -0,0 +1,39 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record Base {} +.record I {} +.record A {} +.record D {} +.record E {} +.record std.core.Object +.function void D.foo(D a0, A a1) { return.void } +.function void E.foo(E a0, {UBase,I} a1) { return.void } +.function void E.foo(E a0, I a1) { return.void } +.function void E._ctor_(E a0) { + return.void +} +.function void Base._ctor_(Base a0) { + return.void +} +.function i32 main() { + initobj.short E._ctor_:(E) + sta.obj v0 + initobj.short Base._ctor_:(Base) + sta.obj v1 + call.virt.short E.foo:(E,{UBase,I}), v0, v1 + ldai 1 + return +} \ No newline at end of file diff --git a/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi4.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi4.pa new file mode 100644 index 0000000000..0e596e4b53 --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi4.pa @@ -0,0 +1,42 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record Base {} +.record Derv1 {} +.record Derv2 {} +.record Derv3 {} +.record Derv4 {} +.record Derv11 {} +.record D {} +.record E {} +.record std.core.Object +.function void D.foo(D a0, {UDerv11,Derv4} a1) { return.void } +.function void E.foo(E a0, {UDerv11,Derv3} a1) { return.void } +.function void E.foo(E a0, Derv1 a1) { return.void } +.function void E._ctor_(E a0) { + return.void +} +.function void Derv11._ctor_(Derv11 a0) { + return.void +} +.function i32 main() { + initobj.short E._ctor_:(E) + sta.obj v0 + initobj.short Derv11._ctor_:(Derv11) + sta.obj v1 + call.virt.short E.foo:(E,{UDerv11,Derv3}), v0, v1 + ldai 1 + return +} diff --git a/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi5.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi5.pa new file mode 100644 index 0000000000..66a7ffe9ea --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi5.pa @@ -0,0 +1,41 @@ +# Copyright (c) 2025 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. + +.language eTS + +.record Base {} +.record I {} +.record A {} +.record BB {} +.record B {} +.record D {} +.record E {} +.record std.core.Object +.function void D.foo(D a0, A a1) { return.void } +.function void E.foo(E a0, {UBase,I,B} a1) { return.void } +.function void E.foo(E a0, I a1) { return.void } +.function void E._ctor_(E a0) { + return.void +} +.function void Base._ctor_(Base a0) { + return.void +} +.function i32 main() { + initobj.short E._ctor_:(E) + sta.obj v0 + initobj.short Base._ctor_:(Base) + sta.obj v1 + call.virt.short E.foo:(E,{UBase,I,B}), v0, v1 + ldai 1 + return +} \ No newline at end of file diff --git a/static_core/plugins/ets/tests/ets_test_suite/unions/unions_general.ets b/static_core/plugins/ets/tests/ets_test_suite/unions/unions_general.ets new file mode 100644 index 0000000000..a5ed1a6c7a --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/unions/unions_general.ets @@ -0,0 +1,95 @@ +class A {} +class B {} +class C extends B {} +class D1 { + foo(a: A | C) { + return 1; + } + foo(a: A | B) { + return 2; + } +} +class E1 extends D1 { + override foo(a: A | B) { + return 3; + } + //1 override foo(a: A | C1 | D1) { // not override, should fail with TypeError => move to cts-tests + // return 4; + // } + //2 override foo(a: A | B | D1) { // multiple override, should fail with TypeError => move to cts-tests + // return 4; + // } +} + +interface I {} +class Base {} +class Derv extends Base implements I {} +class D2 { + foo(a: Derv) { + return 1; + } +} +class E2 extends D2 { + foo(a: Base | I) { + return 2; + } +} +class II implements I {} + +class Derv1 extends Base {} +class Derv2 extends Derv1 {} +class Derv3 extends Derv2 {} +class Derv4 extends Derv3 {} +class Derv11 extends Derv1 {} +class D3 { + foo(a: Derv11 | Derv4) { + return 1; + } + // ambigues overriding, should fail with TypeError => move to cts-tests + // foo(a: Derv11 | Derv3) { + // return 2; + // } +} +class E3 extends D3 { + // ambigues overriding + foo(a: Derv1) { + return 3; + } +} + +class IBase extends Base implements I {} +class ID extends D1 implements I {} +class D4 { + foo(a: IBase) { + return 1; + } +} +class E4 extends D4 { + foo(a: I | D4) { + return 2; + } +} + +function main(): void { + let d1 = new D1() + let e1 = new E1() + assertEQ(e1.foo(new B()), 3) + assertEQ(e1.foo(new C()), 3) + assertEQ(d1.foo(new B()), 2) + assertEQ(d1.foo(new C()), 1) + // e1.foo(new A1()) // ambigues, should fail with TypeError => move to cts-tests + + let e2 = new E2() + assertEQ(e2.foo(new Base()), 2) + assertEQ(e2.foo(new II()), 2) + assertEQ(e2.foo(new Derv()), 2) + + let e3 = new E3() + assertEQ(e3.foo(new Derv1()), 3) + assertEQ(e3.foo(new Derv11()), 3) + assertEQ(e3.foo(new Derv4()), 3) + + let e4 = new E4() + assertEQ(e4.foo(new ID()), 2) + assertEQ(e4.foo(new IBase()), 2) +} \ No newline at end of file diff --git a/static_core/plugins/ets/tests/runtime/types/ets_runtime_linker_test.cpp b/static_core/plugins/ets/tests/runtime/types/ets_runtime_linker_test.cpp index d925170a7d..a151389258 100644 --- a/static_core/plugins/ets/tests/runtime/types/ets_runtime_linker_test.cpp +++ b/static_core/plugins/ets/tests/runtime/types/ets_runtime_linker_test.cpp @@ -117,7 +117,7 @@ TEST_F(EtsRuntimeLinkerTest, GetClassReturnsNullWhenErrorSuppressed) ASSERT_EQ(klass, nullptr); } -TEST_F(EtsRuntimeLinkerTest, CreateUnionClassRedecl) +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassNotRedecl) { pandasm::Parser p; @@ -148,11 +148,11 @@ TEST_F(EtsRuntimeLinkerTest, CreateUnionClassRedecl) SuppressErrorHandler handler; const auto *classWithError = reinterpret_cast("LD;"); Class *klass = ext->GetClass(classWithError, true, nullptr, &handler); - ASSERT_EQ(klass, nullptr); - ASSERT_EQ(handler.GetMessage(), "Method is redeclarated LD;foo LD;foo"); + ASSERT_NE(klass, nullptr); + ASSERT_EQ(handler.GetMessage(), ""); } -TEST_F(EtsRuntimeLinkerTest, CreateUnionClassMultiOverriding) +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassMultiOverriding1) { pandasm::Parser p; @@ -193,4 +193,213 @@ TEST_F(EtsRuntimeLinkerTest, CreateUnionClassMultiOverriding) ASSERT_EQ(handler.GetMessage(), "Multiple override LE;foo LD;foo"); } +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassMultiOverriding2) +{ + pandasm::Parser p; + + const char *source = + ".language eTS\n" + + ".record Base {}\n" + ".record I {}\n" + ".record A {}\n" + ".record D {}\n" + ".record E {}\n" + ".record std.core.Object \n" + ".function void D.foo(D a0, A a1) { return.void }\n" + ".function void E.foo(E a0, {UBase,I,D} a1) { return.void }\n" + ".function void E.foo(E a0, I a1) { return.void }\n"; + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, pandasm::Error::ErrorType::ERR_NONE); + auto pf = pandasm::AsmEmitter::Emit(res.Value()); + + auto classLinker = Runtime::GetCurrent()->GetClassLinker(); + ASSERT(classLinker); + classLinker->AddPandaFile(std::move(pf)); + + auto *ext = static_cast( + ark::Runtime::GetCurrent()->GetClassLinker()->GetExtension(ark::panda_file::SourceLang::ETS)); + SuppressErrorHandler handler; + const auto *classWithError = reinterpret_cast("LE;"); + Class *klass = ext->GetClass(classWithError, true, nullptr, &handler); + ASSERT_EQ(klass, nullptr); + ASSERT_EQ(handler.GetMessage(), "Multiple override LE;foo LD;foo"); +} + +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassMultiOverriding3) +{ + pandasm::Parser p; + + const char *source = + ".language eTS\n" + + ".record Base {}\n" + ".record I {}\n" + ".record A {}\n" + ".record D {}\n" + ".record E {}\n" + ".record std.core.Object \n" + ".function void D.foo(D a0, A a1) { return.void }\n" + ".function void E.foo(E a0, {UBase,I} a1) { return.void }\n" + ".function void E.foo(E a0, I a1) { return.void }\n"; + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, pandasm::Error::ErrorType::ERR_NONE); + auto pf = pandasm::AsmEmitter::Emit(res.Value()); + + auto classLinker = Runtime::GetCurrent()->GetClassLinker(); + ASSERT(classLinker); + classLinker->AddPandaFile(std::move(pf)); + + auto *ext = static_cast( + ark::Runtime::GetCurrent()->GetClassLinker()->GetExtension(ark::panda_file::SourceLang::ETS)); + SuppressErrorHandler handler; + const auto *classWithError = reinterpret_cast("LE;"); + Class *klass = ext->GetClass(classWithError, true, nullptr, &handler); + ASSERT_EQ(klass, nullptr); + ASSERT_EQ(handler.GetMessage(), "Multiple override LE;foo LD;foo"); +} + +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassMultiOverriding4) +{ + pandasm::Parser p; + + const char *source = + ".language eTS\n" + + ".record Base {}\n" + ".record Derv1 {}\n" + ".record Derv2 {}\n" + ".record Derv3 {}\n" + ".record Derv4 {}\n" + ".record Derv11 {}\n" + ".record D {}\n" + ".record E {}\n" + ".record std.core.Object \n" + ".function void D.foo(D a0, {UDerv11,Derv4} a1) { return.void }\n" + ".function void E.foo(E a0, {UDerv11,Derv3} a1) { return.void }\n" + ".function void E.foo(E a0, Derv1 a1) { return.void }\n"; + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, pandasm::Error::ErrorType::ERR_NONE); + auto pf = pandasm::AsmEmitter::Emit(res.Value()); + + auto classLinker = Runtime::GetCurrent()->GetClassLinker(); + ASSERT(classLinker); + classLinker->AddPandaFile(std::move(pf)); + + auto *ext = static_cast( + ark::Runtime::GetCurrent()->GetClassLinker()->GetExtension(ark::panda_file::SourceLang::ETS)); + SuppressErrorHandler handler; + const auto *classWithError = reinterpret_cast("LE;"); + Class *klass = ext->GetClass(classWithError, true, nullptr, &handler); + ASSERT_EQ(klass, nullptr); + ASSERT_EQ(handler.GetMessage(), "Multiple override LE;foo LD;foo"); +} + +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassMultiOverriding5) +{ + pandasm::Parser p; + + const char *source = + ".language eTS\n" + + ".record Base {}\n" + ".record I {}\n" + ".record A {}\n" + ".record BB {}\n" + ".record B {}\n" + ".record D {}\n" + ".record E {}\n" + ".record std.core.Object \n" + ".function void D.foo(D a0, A a1) { return.void }\n" + ".function void E.foo(E a0, {UBase,I,B} a1) { return.void }\n" + ".function void E.foo(E a0, I a1) { return.void }\n"; + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, pandasm::Error::ErrorType::ERR_NONE); + auto pf = pandasm::AsmEmitter::Emit(res.Value()); + + auto classLinker = Runtime::GetCurrent()->GetClassLinker(); + ASSERT(classLinker); + classLinker->AddPandaFile(std::move(pf)); + + auto *ext = static_cast( + ark::Runtime::GetCurrent()->GetClassLinker()->GetExtension(ark::panda_file::SourceLang::ETS)); + SuppressErrorHandler handler; + const auto *classWithError = reinterpret_cast("LE;"); + Class *klass = ext->GetClass(classWithError, true, nullptr, &handler); + ASSERT_EQ(klass, nullptr); + ASSERT_EQ(handler.GetMessage(), "Multiple override LE;foo LD;foo"); +} + +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassOverload) +{ + pandasm::Parser p; + + const char *source = + ".language eTS\n" + + ".record Base {}\n" + ".record I {}\n" + ".record A {}\n" + ".record BI {}\n" + ".record BB {}\n" + ".record B {}\n" + ".record D {}\n" + ".record E {}\n" + ".record std.core.Object \n" + ".function void D.foo(D a0, A a1) { return.void }\n" + ".function void E.foo(E a0, {UD,I,B} a1) { return.void }\n" + ".function void E.foo(E a0, BI a1) { return.void }\n"; + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, pandasm::Error::ErrorType::ERR_NONE); + auto pf = pandasm::AsmEmitter::Emit(res.Value()); + + auto classLinker = Runtime::GetCurrent()->GetClassLinker(); + ASSERT(classLinker); + classLinker->AddPandaFile(std::move(pf)); + + auto *ext = static_cast( + ark::Runtime::GetCurrent()->GetClassLinker()->GetExtension(ark::panda_file::SourceLang::ETS)); + SuppressErrorHandler handler; + const auto *classWithError = reinterpret_cast("LE;"); + Class *klass = ext->GetClass(classWithError, true, nullptr, &handler); + ASSERT_NE(klass, nullptr); + ASSERT_EQ(handler.GetMessage(), ""); +} + +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassOverriding) +{ + pandasm::Parser p; + + const char *source = + ".language eTS\n" + + ".record Base {}\n" + ".record Derv1 {}\n" + ".record Derv2 {}\n" + ".record Derv3 {}\n" + ".record Derv4 {}\n" + ".record Derv11 {}\n" + ".record D {}\n" + ".record E {}\n" + ".record std.core.Object \n" + ".function void D.foo(D a0, {UDerv11,Derv4} a1) { return.void }\n" + ".function void D.foo(D a0, {UDerv11,Derv3} a1) { return.void }\n" + ".function void E.foo(E a0, Derv1 a1) { return.void }\n"; + auto res = p.Parse(source); + ASSERT_EQ(p.ShowError().err, pandasm::Error::ErrorType::ERR_NONE); + auto pf = pandasm::AsmEmitter::Emit(res.Value()); + + auto classLinker = Runtime::GetCurrent()->GetClassLinker(); + ASSERT(classLinker); + classLinker->AddPandaFile(std::move(pf)); + + auto *ext = static_cast( + ark::Runtime::GetCurrent()->GetClassLinker()->GetExtension(ark::panda_file::SourceLang::ETS)); + SuppressErrorHandler handler; + const auto *classWithError = reinterpret_cast("LE;"); + Class *klass = ext->GetClass(classWithError, true, nullptr, &handler); + ASSERT_NE(klass, nullptr); + ASSERT_EQ(handler.GetMessage(), ""); +} + } // namespace ark::ets::test diff --git a/static_core/plugins/ets/tests/runtime/types/ets_union_test.cpp b/static_core/plugins/ets/tests/runtime/types/ets_union_test.cpp index a04719b44a..05773f8240 100644 --- a/static_core/plugins/ets/tests/runtime/types/ets_union_test.cpp +++ b/static_core/plugins/ets/tests/runtime/types/ets_union_test.cpp @@ -64,6 +64,25 @@ public: coroutine_->ManagedCodeEnd(); } + bool CheckConstituentClasses(EtsClass *unionClass, const std::vector &consClassesList) + { + size_t idx = 0; + bool res = true; + unionClass->EnumerateConstituentClasses([&](EtsClass *consClass) { + res &= consClass->GetRuntimeClass()->IsLoaded(); + if (PandaString(consClass->GetDescriptor()) != consClassesList[idx++]) { + res = false; + return true; + } + if (PandaString(consClass->GetDescriptor()) == "[{ULB;LC;}") { + res &= consClass->IsArrayClass() && consClass->GetComponentType()->IsUnionClass(); + res &= CheckConstituentClasses(consClass->GetComponentType(), {"LB;", "LC;"}); + } + return false; + }); + return res; + } + protected: PandaEtsVM *vm_ = nullptr; // NOLINT(misc-non-private-member-variables-in-classes) @@ -100,16 +119,35 @@ TEST_F(EtsUnionTest, CreateUnionClass) EtsClassLinker *etsClassLinker = vm_->GetClassLinker(); - std::map descMap {{"{ULA;LB;}", "LA;"}, - {"{ULB;LC;}", "LA;"}, - {"{ULA;LB;LC;}", "LA;"}, - {"{ULA;LB;[LC;}", "Lstd/core/Object;"}, - {"{ULA;LD;[{ULB;LC;}}", "Lstd/core/Object;"}}; - - for (const auto &[descFull, descLUB] : descMap) { - EtsClass *klass = etsClassLinker->GetClass(descFull.c_str()); + std::map>> descMap { + {"LA;", {false, "", {}}}, + {"{ULA;LB;}", {true, "LA;", {"LA;", "LB;"}}}, + {"{ULB;LC;}", {true, "LA;", {"LB;", "LC;"}}}, + {"{ULA;LB;LC;}", {true, "LA;", {"LA;", "LB;", "LC;"}}}, + {"{ULA;LB;[LC;}", {true, "Lstd/core/Object;", {"LA;", "LB;", "[LC;"}}}, + {"{ULA;Lstd/core/String;[LC;}", {true, "Lstd/core/Object;", {"LA;", "Lstd/core/String;", "[LC;"}}}, + {"{ULA;LD;[{ULB;LC;}}", {true, "Lstd/core/Object;", {"LA;", "LD;", "[{ULB;LC;}"}}}}; + + auto *ext = static_cast( + ark::Runtime::GetCurrent()->GetClassLinker()->GetExtension(ark::panda_file::SourceLang::ETS)); + for (const auto &[desc, infoList] : descMap) { + EtsClass *klass = etsClassLinker->GetClass(desc.c_str()); ASSERT_NE(klass, nullptr); - EXPECT_EQ(PandaString(klass->GetDescriptor()), descLUB); + ASSERT_TRUE(klass->GetRuntimeClass()->IsLoaded()); + + auto isUnion = std::get(infoList); + auto classLUB = std::get(infoList); + const auto &consClassesList = std::get>(infoList); + EXPECT_EQ(klass->IsUnionClass(), isUnion); + EXPECT_EQ(PandaString(klass->GetDescriptor()), desc); + if (!isUnion) { + continue; + } + ASSERT_TRUE(klass->IsInitialized()); + + const uint8_t *descLUB = ext->ComputeLUB(klass->GetLoadContext(), utf::CStringAsMutf8(klass->GetDescriptor())); + EXPECT_EQ(PandaString(utf::Mutf8AsCString(descLUB)), classLUB); + ASSERT_TRUE(CheckConstituentClasses(klass, consClassesList)); } } diff --git a/static_core/runtime/class_helper.cpp b/static_core/runtime/class_helper.cpp index 53107d8a4a..46da6fec28 100644 --- a/static_core/runtime/class_helper.cpp +++ b/static_core/runtime/class_helper.cpp @@ -17,9 +17,11 @@ #include +#include "include/class_linker_extension.h" #include "libpandabase/mem/mem.h" #include "libpandabase/utils/bit_utils.h" #include "runtime/include/mem/panda_string.h" +#include "runtime/include/class_linker.h" namespace ark { @@ -150,12 +152,40 @@ bool ClassHelper::IsReference(const uint8_t *descriptor) } /* static */ -bool ClassHelper::IsUnion(const uint8_t *descriptor) +bool ClassHelper::IsUnionDescriptor(const uint8_t *descriptor) { Span sp(descriptor, 2); return sp[0] == '{' && sp[1] == 'U'; } +/* static */ +bool ClassHelper::IsUnionOrArrayUnionDescriptor(const uint8_t *descriptor) +{ + if (IsUnionDescriptor(descriptor)) { + return true; + } + if (IsArrayDescriptor(descriptor)) { + return IsUnionDescriptor(&descriptor[GetDimensionality(descriptor)]); + } + return false; +} + +/* static */ +Class *ClassHelper::GetUnionLUBClass(const uint8_t *descriptor, ClassLinker *classLinker, + ClassLinkerContext *classLinkerCtx, ClassLinkerExtension *ext, + ClassLinkerErrorHandler *handler) +{ + if (ClassHelper::IsUnionDescriptor(descriptor)) { + const auto *handledDescr = ext->ComputeLUB(classLinkerCtx, descriptor); + return classLinker->GetClass(handledDescr, true, classLinkerCtx, handler); + } + auto dim = ClassHelper::GetDimensionality(descriptor); + const auto *handledDescrComponent = ext->ComputeLUB(classLinkerCtx, &descriptor[dim]); + PandaString dimCopy(utf::Mutf8AsCString(descriptor), dim); + auto descrCopy = dimCopy + utf::Mutf8AsCString(handledDescrComponent); + return classLinker->GetClass(utf::CStringAsMutf8(descrCopy.c_str()), true, classLinkerCtx, handler); +} + static size_t GetUnionTypeComponentsNumber(const uint8_t *descriptor) { size_t length = 1; @@ -168,7 +198,7 @@ static size_t GetUnionTypeComponentsNumber(const uint8_t *descriptor) } } // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) - return length; + return length + 1; } /* static */ @@ -182,7 +212,7 @@ Span ClassHelper::GetUnionComponent(const uint8_t *descriptor) // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) return Span(descriptor, dim + GetUnionComponent(&(descriptor[dim])).Size()); } - if (IsUnion(descriptor)) { + if (IsUnionDescriptor(descriptor)) { return Span(descriptor, GetUnionTypeComponentsNumber(descriptor)); } ASSERT(IsReference(descriptor)); diff --git a/static_core/runtime/class_linker.cpp b/static_core/runtime/class_linker.cpp index c3c3e7b826..2d7c8c405f 100644 --- a/static_core/runtime/class_linker.cpp +++ b/static_core/runtime/class_linker.cpp @@ -132,6 +132,11 @@ void ClassLinker::FreeClassData(Class *classPtr) allocator_->Free(interfaces.begin()); classPtr->SetInterfaces(Span()); } + Span consTypes = classPtr->GetConstituentTypes(); + if (!consTypes.Empty()) { + allocator_->Free(consTypes.begin()); + classPtr->SetConstituentTypes(Span()); + } } void ClassLinker::FreeClass(Class *classPtr) @@ -1176,6 +1181,117 @@ Class *ClassLinker::BuildClass(const uint8_t *descriptor, bool needCopyDescripto return klass; } +Class *ClassLinker::CreateUnionClass(ClassLinkerExtension *ext, const uint8_t *descriptor, bool needCopyDescriptor, + Span constituentClasses, ClassLinkerContext *commonContext) +{ + if (needCopyDescriptor) { + descriptor = CopyMutf8String(allocator_, descriptor); + os::memory::LockHolder lock(copiedNamesLock_); + copiedNames_.push_front(descriptor); + } + + auto *unionClass = ext->CreateClass(descriptor, ext->GetArrayClassVTableSize(), ext->GetArrayClassIMTSize(), + ext->GetArrayClassSize()); + + if (UNLIKELY(unionClass == nullptr)) { + return nullptr; + } + + unionClass->SetLoadContext(commonContext); + + if (UNLIKELY(!ext->InitializeUnionClass(unionClass, constituentClasses))) { + return nullptr; + } + + return unionClass; +} + +// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) +std::optional> ClassLinker::LoadConstituentClasses(const uint8_t *descriptor, bool needCopyDescriptor, + ClassLinkerContext *context, + ClassLinkerErrorHandler *errorHandler) +{ + size_t idx = 2; + size_t elementsSize = 0; + while (descriptor[idx] != '}') { + auto typeSp = ClassHelper::GetUnionComponent(&(descriptor[idx])); + elementsSize += 1; + idx += typeSp.Size(); + } + + Span klasses {allocator_->AllocArray(elementsSize), elementsSize}; + size_t i = 0; + idx = 2; + while (descriptor[idx] != '}') { + auto typeSp = ClassHelper::GetUnionComponent(&(descriptor[idx])); + PandaString typeDescCopy(utf::Mutf8AsCString(typeSp.Data()), typeSp.Size()); + idx += typeSp.Size(); + const uint8_t *separateDesc = utf::CStringAsMutf8(typeDescCopy.c_str()); + + Class *klass = GetClass(separateDesc, ClassHelper::IsArrayDescriptor(separateDesc) ? true : needCopyDescriptor, + context, errorHandler); + if (klass == nullptr) { + LOG(INFO, CLASS_LINKER) << "Cannot find substituent class '" << typeDescCopy << "' of union class '" + << utf::Mutf8AsCString(descriptor) << "' in context " << context; + ASSERT(!klasses.Empty()); + allocator_->Free(klasses.begin()); + return {}; + } + + klasses[i++] = klass; + } + ASSERT(klasses.Size() > 1); + return klasses; +} +// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + +Class *ClassLinker::LoadUnionClass(const uint8_t *descriptor, bool needCopyDescriptor, ClassLinkerContext *context, + ClassLinkerErrorHandler *errorHandler) +{ + auto constituentClasses = LoadConstituentClasses(descriptor, needCopyDescriptor, context, errorHandler); + if (!constituentClasses.has_value()) { + LOG(INFO, CLASS_LINKER) << "Cannot load constituent classes of union class '" << descriptor << "'"; + return nullptr; + } + + ASSERT(constituentClasses.value().Size() > 1); + + auto *ext = GetExtension((*constituentClasses.value().begin())->GetSourceLang()); + ASSERT(ext != nullptr); + auto *commonContext = ext->GetCommonContext(constituentClasses.value()); + ASSERT(commonContext != nullptr); + + if (commonContext != context) { + auto *loadedClass = FindLoadedClass(descriptor, commonContext); + if (loadedClass != nullptr) { + ASSERT(!constituentClasses.value().Empty()); + allocator_->Free(constituentClasses.value().begin()); + return loadedClass; + } + } + + auto *unionClass = CreateUnionClass(ext, descriptor, needCopyDescriptor, constituentClasses.value(), commonContext); + + if (UNLIKELY(unionClass == nullptr)) { + ASSERT(!constituentClasses.value().Empty()); + allocator_->Free(constituentClasses.value().begin()); + return nullptr; + } + + Runtime::GetCurrent()->GetNotificationManager()->ClassLoadEvent(unionClass); + + auto *otherKlass = commonContext->InsertClass(unionClass); + if (otherKlass != nullptr) { + FreeClass(unionClass); + return otherKlass; + } + + RemoveCreatedClassInExtension(unionClass); + Runtime::GetCurrent()->GetNotificationManager()->ClassPrepareEvent(unionClass); + + return unionClass; +} + Class *ClassLinker::CreateArrayClass(ClassLinkerExtension *ext, const uint8_t *descriptor, bool needCopyDescriptor, Class *componentClass) { @@ -1201,14 +1317,6 @@ Class *ClassLinker::CreateArrayClass(ClassLinkerExtension *ext, const uint8_t *d return arrayClass; } -Class *ClassLinker::LoadUnionClass(const uint8_t *descriptor, bool needCopyDescriptor, ClassLinkerContext *context, - ClassLinkerErrorHandler *errorHandler) -{ - auto langCtx = Runtime::GetCurrent()->GetLanguageContext(context->GetSourceLang()); - const uint8_t *descriptorLUB = GetExtension(langCtx)->ComputeLUB(context, descriptor); - return GetClass(descriptorLUB, needCopyDescriptor, context, errorHandler); -} - Class *ClassLinker::LoadArrayClass(const uint8_t *descriptor, bool needCopyDescriptor, ClassLinkerContext *context, ClassLinkerErrorHandler *errorHandler) { @@ -1287,7 +1395,7 @@ Class *ClassLinker::GetClass(const uint8_t *descriptor, bool needCopyDescriptor, return cls; } - if (ClassHelper::IsUnion(descriptor)) { + if (ClassHelper::IsUnionDescriptor(descriptor)) { return LoadUnionClass(descriptor, needCopyDescriptor, context, errorHandler); } @@ -1336,16 +1444,20 @@ Class *ClassLinker::GetClass(const panda_file::File &pf, panda_file::File::Entit } const uint8_t *descriptor = pf.GetStringData(id).data; - if (ClassHelper::IsUnion(descriptor)) { - return LoadUnionClass(descriptor, false, context, errorHandler); - } - cls = FindLoadedClass(descriptor, context); if (cls != nullptr) { pf.GetPandaCache()->SetClassCache(id, cls); return cls; } + if (ClassHelper::IsUnionDescriptor(descriptor)) { + cls = LoadUnionClass(descriptor, false, context, errorHandler); + if (LIKELY(cls != nullptr)) { + pf.GetPandaCache()->SetClassCache(id, cls); + } + return cls; + } + if (ClassHelper::IsArrayDescriptor(descriptor)) { cls = LoadArrayClass(descriptor, false, context, errorHandler); if (LIKELY(cls != nullptr)) { @@ -1641,6 +1753,25 @@ Field *ClassLinker::GetField(const Method &caller, panda_file::File::EntityId id return field; } +Field *ClassLinker::GetField(Class *klass, const panda_file::FieldDataAccessor &fda, bool isStatic, + ClassLinkerErrorHandler *errorHandler) +{ + if (klass == nullptr) { + return nullptr; + } + Field *field {nullptr}; + auto pf = &fda.GetPandaFile(); + if (!fda.IsExternal() && (klass->GetPandaFile() == pf)) { + field = GetFieldById(klass, fda, errorHandler, isStatic); + } else { + field = GetFieldBySignature(klass, fda, errorHandler, isStatic); + } + if (LIKELY(field != nullptr)) { + pf->GetPandaCache()->SetFieldCache(fda.GetFieldId(), field); + } + return field; +} + void ClassLinker::RemoveCreatedClassInExtension(Class *klass) { if (klass == nullptr) { diff --git a/static_core/runtime/compiler.cpp b/static_core/runtime/compiler.cpp index 6d585c402b..a5ce831bbe 100644 --- a/static_core/runtime/compiler.cpp +++ b/static_core/runtime/compiler.cpp @@ -131,18 +131,42 @@ compiler::RuntimeInterface::IdType PandaRuntimeInterface::GetMethodArgReferenceT return pda.GetReferenceType(num).GetOffset(); } +Class *HandleUnionClass(Class *klass, ClassLinker *classLinker, ClassLinkerContext *classLinkerCtx, + ErrorHandler *handler = nullptr) +{ + if (klass == nullptr) { + return klass; + } + const auto *desc = klass->GetDescriptor(); + if (ClassHelper::IsUnionOrArrayUnionDescriptor(desc)) { + return ClassHelper::GetUnionLUBClass(desc, classLinker, classLinkerCtx, + classLinker->GetExtension(klass->GetSourceLang()), handler); + } + return klass; +} + compiler::RuntimeInterface::ClassPtr PandaRuntimeInterface::GetClass(MethodPtr method, IdType id) const { auto *caller = MethodCast(method); - Class *loadedClass = Runtime::GetCurrent()->GetClassLinker()->GetLoadedClass( - *caller->GetPandaFile(), panda_file::File::EntityId(id), caller->GetClass()->GetLoadContext()); + auto *classLinker = Runtime::GetCurrent()->GetClassLinker(); + Class *loadedClass = classLinker->GetLoadedClass(*caller->GetPandaFile(), panda_file::File::EntityId(id), + caller->GetClass()->GetLoadContext()); if (LIKELY(loadedClass != nullptr)) { + loadedClass = HandleUnionClass(loadedClass, classLinker, caller->GetClass()->GetLoadContext()); + if (ClassHelper::IsUnionOrArrayUnionDescriptor(loadedClass->GetDescriptor())) { + UNREACHABLE(); + } return loadedClass; } ErrorHandler handler; ScopedMutatorLock lock; - return Runtime::GetCurrent()->GetClassLinker()->GetClass(*caller->GetPandaFile(), panda_file::File::EntityId(id), - caller->GetClass()->GetLoadContext(), &handler); + auto *cls = classLinker->GetClass(*caller->GetPandaFile(), panda_file::File::EntityId(id), + caller->GetClass()->GetLoadContext(), &handler); + cls = HandleUnionClass(cls, classLinker, caller->GetClass()->GetLoadContext(), &handler); + if (cls != nullptr && ClassHelper::IsUnionOrArrayUnionDescriptor(cls->GetDescriptor())) { + UNREACHABLE(); + } + return cls; } compiler::RuntimeInterface::ClassPtr PandaRuntimeInterface::GetStringClass(MethodPtr method, uint32_t *typeId) const @@ -163,8 +187,13 @@ compiler::RuntimeInterface::ClassPtr PandaRuntimeInterface::GetNumberClass(Metho auto *caller = MethodCast(method); LanguageContext ctx = Runtime::GetCurrent()->GetLanguageContext(*caller); const uint8_t *classDescriptor = utf::CStringAsMutf8(name); - auto classLinker = Runtime::GetCurrent()->GetClassLinker()->GetExtension(ctx); - auto *classPtr = classLinker->GetClass(classDescriptor, false, classLinker->GetBootContext(), nullptr); + auto classLinker = Runtime::GetCurrent()->GetClassLinker(); + auto classLinkerExt = Runtime::GetCurrent()->GetClassLinker()->GetExtension(ctx); + auto *classPtr = classLinker->GetClass(classDescriptor, false, classLinkerExt->GetBootContext(), nullptr); + classPtr = HandleUnionClass(classPtr, classLinker, classLinkerExt->GetBootContext()); + if (ClassHelper::IsUnionOrArrayUnionDescriptor(classPtr->GetDescriptor())) { + UNREACHABLE(); + } *typeId = classPtr->GetFileId().GetOffset(); return classPtr; } @@ -416,6 +445,10 @@ PandaRuntimeInterface::MethodPtr PandaRuntimeInterface::GetMethodAsIntrinsic(Met return nullptr; } + klass = HandleUnionClass(klass, classLinker, classLinker->GetExtension(ctx)->GetBootContext()); + if (ClassHelper::IsUnionOrArrayUnionDescriptor(klass->GetDescriptor())) { + UNREACHABLE(); + } auto name = pf->GetStringData(mda.GetNameId()); bool isArrayClone = ClassHelper::IsArrayDescriptor(className) && (utf::CompareMUtf8ToMUtf8(name.data, utf::CStringAsMutf8("clone")) == 0); @@ -1060,7 +1093,10 @@ uint8_t CompileMethodImpl(coretypes::String *fullMethodName, ClassLinkerContext static constexpr uint8_t CLASS_IS_NULL = 2; return CLASS_IS_NULL; } - + cls = HandleUnionClass(cls, classLinker, ctx); + if (ClassHelper::IsUnionOrArrayUnionDescriptor(cls->GetDescriptor())) { + UNREACHABLE(); + } auto method = cls->GetDirectMethod(methodNameBytes); if (method == nullptr) { static constexpr uint8_t METHOD_IS_NULL = 3; diff --git a/static_core/runtime/core/core_class_linker_extension.cpp b/static_core/runtime/core/core_class_linker_extension.cpp index 0a2588a17f..94c69ff6db 100644 --- a/static_core/runtime/core/core_class_linker_extension.cpp +++ b/static_core/runtime/core/core_class_linker_extension.cpp @@ -143,6 +143,24 @@ bool CoreClassLinkerExtension::InitializeArrayClass(Class *arrayClass, Class *co return true; } +bool CoreClassLinkerExtension::InitializeUnionClass(Class *unionClass, Span constituentClasses) +{ + ASSERT(IsInitialized()); + + auto *objectClass = GetClassRoot(ClassRoot::OBJECT); + unionClass->SetBase(objectClass); + unionClass->SetConstituentTypes(constituentClasses); + uint32_t accessFlags = ACC_FILE_MASK; + for (auto cl : constituentClasses) { + accessFlags &= cl->GetAccessFlags(); + } + accessFlags &= ~ACC_INTERFACE; + accessFlags |= ACC_FINAL | ACC_ABSTRACT; + unionClass->SetAccessFlags(accessFlags); + unionClass->SetState(Class::State::INITIALIZED); + return true; +} + void CoreClassLinkerExtension::InitializePrimitiveClass(Class *primitiveClass) { ASSERT(IsInitialized()); diff --git a/static_core/runtime/core/core_class_linker_extension.h b/static_core/runtime/core/core_class_linker_extension.h index 24ba66eed8..e6d46fe83b 100644 --- a/static_core/runtime/core/core_class_linker_extension.h +++ b/static_core/runtime/core/core_class_linker_extension.h @@ -28,6 +28,8 @@ public: bool InitializeArrayClass(Class *arrayClass, Class *componentClass) override; + bool InitializeUnionClass(Class *unionClass, Span constituentClasses) override; + void InitializePrimitiveClass(Class *primitiveClass) override; size_t GetClassVTableSize(ClassRoot root) override; diff --git a/static_core/runtime/include/class-inl.h b/static_core/runtime/include/class-inl.h index 7900d8976c..aaf1a2860d 100644 --- a/static_core/runtime/include/class-inl.h +++ b/static_core/runtime/include/class-inl.h @@ -139,22 +139,102 @@ inline bool Class::IsSubClassOf(const Class *klass) const return false; } +inline static bool IsAssignableFromUnion(const Class *sub, const Class *super); +inline static bool IsAssignableFromRef(const Class *sub, const Class *super); + +template // CC-OFFNXT(G.FUD.06) perf critical -inline bool Class::IsAssignableFrom(const Class *klass) const +inline static std::pair IsAssignableFromUnionImpl(const Class *ref, const Class *unionCls, + size_t processedNum = 0) +{ + bool res = false; + size_t process = 0; + auto isAssignable = [](const Class *super, const Class *sub) { + if (sub->IsUnionClass() || super->IsUnionClass()) { + return IsAssignableFromUnion(sub, super); + } + return IsAssignableFromRef(sub, super); + }; + for (auto *consClass : unionCls->GetConstituentTypes()) { + if constexpr (!IS_STRICT) { + if (processedNum != 0) { + res = true; + if (process < processedNum) { + process++; + continue; + } + } + } + bool isAssign; + if constexpr (IS_UNION_SUPER) { + isAssign = isAssignable(consClass, ref); + } else { + isAssign = isAssignable(ref, consClass); + } + + if constexpr (IS_STRICT) { + if (!isAssign) { + return {false, process}; + } + process++; + } + res |= isAssign; + } + return {res, process}; +} + +// CC-OFFNXT(G.FUD.06) perf critical +inline static bool IsAssignableFromUnion(const Class *sub, const Class *super) { - if (klass == this) { + if (!super->IsUnionClass()) { + return std::get(IsAssignableFromUnionImpl(super, sub)); + } + + if (!sub->IsUnionClass()) { + return std::get(IsAssignableFromUnionImpl(sub, super)); + } + + for (auto *consClass : sub->GetConstituentTypes()) { + auto [res, processedNum] = IsAssignableFromUnionImpl(consClass, super); + if (res) { + return true; + } + std::tie(res, processedNum) = IsAssignableFromUnionImpl(consClass, super, processedNum); + if (!res) { + return false; + } + } + return true; +} + +// CC-OFFNXT(G.FUD.06) perf critical +inline bool IsAssignableFromRef(const Class *sub, const Class *super) +{ + if (sub == super) { return true; } - if (IsObjectClass()) { - return !klass->IsPrimitive(); + if (super->IsObjectClass()) { + return !sub->IsPrimitive(); } - if (IsInterface()) { - return klass->Implements(this); + if (super->IsInterface()) { + return sub->Implements(super); + } + if (sub->IsArrayClass()) { + return super->IsArrayClass() && super->GetComponentType()->IsAssignableFrom(sub->GetComponentType()); + } + return !sub->IsInterface() && sub->IsSubClassOf(super); +} + +// CC-OFFNXT(G.FUD.06) perf critical +inline bool Class::IsAssignableFrom(const Class *klass) const +{ + if (klass == this) { + return true; } - if (klass->IsArrayClass()) { - return IsArrayClass() && GetComponentType()->IsAssignableFrom(klass->GetComponentType()); + if (IsUnionClass() || klass->IsUnionClass()) { + return IsAssignableFromUnion(klass, this); } - return !klass->IsInterface() && klass->IsSubClassOf(this); + return IsAssignableFromRef(klass, this); } inline bool Class::Implements(const Class *klass) const diff --git a/static_core/runtime/include/class.h b/static_core/runtime/include/class.h index a6121da597..ea75de4de4 100644 --- a/static_core/runtime/include/class.h +++ b/static_core/runtime/include/class.h @@ -340,11 +340,27 @@ public: componentType_ = type; } + Span GetConstituentTypes() const + { + return {constituentTypes_, numConsTypes_}; + } + + void SetConstituentTypes(Span types) + { + constituentTypes_ = types.data(); + numConsTypes_ = types.size(); + } + bool IsArrayClass() const { return componentType_ != nullptr; } + bool IsUnionClass() const + { + return constituentTypes_ != nullptr; + } + bool IsObjectArrayClass() const { return IsArrayClass() && !componentType_->IsPrimitive(); @@ -914,6 +930,7 @@ private: uint32_t numFields_ {0}; uint32_t numSfields_ {0}; uint32_t numIfaces_ {0}; + uint32_t numConsTypes_ {0}; uint32_t initTid_ {0}; ITable itable_; @@ -921,6 +938,9 @@ private: // For array types this field contains array's element size, for non-array type it should be zero. Class *componentType_ {nullptr}; + // For union types his field contains union's constituent types, for other types it should be nullptr. + Class **constituentTypes_ {nullptr}; + ClassLinkerContext *loadContext_ {nullptr}; panda_file::Type type_ {panda_file::Type::TypeId::REFERENCE}; diff --git a/static_core/runtime/include/class_helper.h b/static_core/runtime/include/class_helper.h index 0e15b97680..f0b021deab 100644 --- a/static_core/runtime/include/class_helper.h +++ b/static_core/runtime/include/class_helper.h @@ -32,6 +32,12 @@ public: using ClassWordSize = typename Config::Size; }; +class Class; +class ClassLinker; +class ClassLinkerContext; +class ClassLinkerExtension; +class ClassLinkerErrorHandler; + class ClassHelper : private ClassConfig { public: using ClassWordSize = typename ClassConfig::ClassWordSize; // To be visible outside @@ -88,8 +94,12 @@ public: static bool IsPrimitive(const uint8_t *descriptor); static bool IsReference(const uint8_t *descriptor); - static bool IsUnion(const uint8_t *descriptor); + static bool IsUnionDescriptor(const uint8_t *descriptor); + static bool IsUnionOrArrayUnionDescriptor(const uint8_t *descriptor); static Span GetUnionComponent(const uint8_t *descriptor); + static Class *GetUnionLUBClass(const uint8_t *descriptor, ClassLinker *classLinker, + ClassLinkerContext *classLinkerCtx, ClassLinkerExtension *ext, + ClassLinkerErrorHandler *handler = nullptr); private: template diff --git a/static_core/runtime/include/class_linker.h b/static_core/runtime/include/class_linker.h index 3e306c4f30..e2b79d8ae0 100644 --- a/static_core/runtime/include/class_linker.h +++ b/static_core/runtime/include/class_linker.h @@ -99,6 +99,9 @@ public: Field *GetField(const Method &caller, panda_file::File::EntityId id, bool isStatic, ClassLinkerErrorHandler *errorHandler = nullptr); + Field *GetField(Class *klass, const panda_file::FieldDataAccessor &fda, bool isStatic, + ClassLinkerErrorHandler *errorHandler = nullptr); + PANDA_PUBLIC_API void AddPandaFile(std::unique_ptr &&pf, ClassLinkerContext *context = nullptr); @@ -264,6 +267,8 @@ public: Class *CreateArrayClass(ClassLinkerExtension *ext, const uint8_t *descriptor, bool needCopyDescriptor, Class *componentClass); + Class *CreateUnionClass(ClassLinkerExtension *ext, const uint8_t *descriptor, bool needCopyDescriptor, + Span constituentClasses, ClassLinkerContext *commonContext); void FreeClassData(Class *classPtr); @@ -350,6 +355,10 @@ private: Class *LoadUnionClass(const uint8_t *descriptor, bool needCopyDescriptor, ClassLinkerContext *context, ClassLinkerErrorHandler *errorHandler); + std::optional> LoadConstituentClasses(const uint8_t *descriptor, bool needCopyDescriptor, + ClassLinkerContext *context, + ClassLinkerErrorHandler *errorHandler); + Class *LoadClass(const panda_file::File *pf, panda_file::File::EntityId classId, const uint8_t *descriptor, ClassLinkerContext *context, ClassLinkerErrorHandler *errorHandler, bool addToRuntime = true); diff --git a/static_core/runtime/include/class_linker_extension.h b/static_core/runtime/include/class_linker_extension.h index 3542258941..4414885d03 100644 --- a/static_core/runtime/include/class_linker_extension.h +++ b/static_core/runtime/include/class_linker_extension.h @@ -42,6 +42,12 @@ public: virtual bool InitializeArrayClass(Class *arrayClass, Class *componentClass) = 0; + virtual bool InitializeUnionClass([[maybe_unused]] Class *unionClass, + [[maybe_unused]] Span constituentClasses) + { + return false; + } + virtual void InitializePrimitiveClass(Class *primitiveClass) = 0; virtual size_t GetClassVTableSize(ClassRoot root) = 0; @@ -90,6 +96,11 @@ public: virtual ClassLinkerContext *CreateApplicationClassLinkerContext(const PandaVector &path); + virtual ClassLinkerContext *GetCommonContext([[maybe_unused]] Span classes) + { + UNREACHABLE(); + } + virtual const uint8_t *ComputeLUB([[maybe_unused]] const ClassLinkerContext *ctx, [[maybe_unused]] const uint8_t *descriptor) { diff --git a/static_core/runtime/include/hclass.h b/static_core/runtime/include/hclass.h index e2108dce48..f102209c66 100644 --- a/static_core/runtime/include/hclass.h +++ b/static_core/runtime/include/hclass.h @@ -99,19 +99,19 @@ public: return (nativeFields_ & FieldOffsetToMask(offset)) != 0; } - uint32_t GetNativeFieldMask() const + uint64_t GetNativeFieldMask() const { return nativeFields_; } - void SetNativeFieldMask(uint32_t mask) + void SetNativeFieldMask(uint64_t mask) { nativeFields_ = mask; } - static constexpr uint32_t FieldOffsetToMask(size_t offset) + static constexpr uint64_t FieldOffsetToMask(size_t offset) { - uint32_t index = (offset - ObjectHeader::ObjectHeaderSize()) / TaggedValue::TaggedTypeSize(); + uint64_t index = (offset - ObjectHeader::ObjectHeaderSize()) / TaggedValue::TaggedTypeSize(); return 1U << index; } @@ -146,7 +146,7 @@ private: friend class coretypes::DynClass; - uint32_t nativeFields_ {0}; + uint64_t nativeFields_ {0}; // Data for language extension flags // NOTE(maksenov): maybe merge this with BaseClass flags diff --git a/static_core/verification/absint/abs_int_inl.h b/static_core/verification/absint/abs_int_inl.h index 6a7a0637d2..6739b42615 100644 --- a/static_core/verification/absint/abs_int_inl.h +++ b/static_core/verification/absint/abs_int_inl.h @@ -1828,7 +1828,13 @@ public: ScopedChangeThreadStatus st {ManagedThread::GetCurrent(), ThreadStatus::RUNNING}; Job::ErrorHandler handler; - auto typeCls = field->ResolveTypeClass(&handler); + Class *typeCls {nullptr}; + if (field->GetType().IsReference()) { + auto classId = panda_file::FieldDataAccessor::GetTypeId(*field->GetPandaFile(), field->GetFileId()); + typeCls = job_->GetClass(classId, field->GetClass()->GetLoadContext(), field->GetPandaFile(), &handler); + } else { + typeCls = field->ResolveTypeClass(&handler); + } if (typeCls == nullptr) { return Type {}; } diff --git a/static_core/verification/gen/templates/job_fill_gen.h.erb b/static_core/verification/gen/templates/job_fill_gen.h.erb index 5d760dcb03..f734cb1020 100644 --- a/static_core/verification/gen/templates/job_fill_gen.h.erb +++ b/static_core/verification/gen/templates/job_fill_gen.h.erb @@ -196,7 +196,7 @@ std::array dispatchTable{ % bool isArrayConstructorCall = false; % if (calledMethod == nullptr) { % panda_file::MethodDataAccessor const mda(*pf, methodId); -% Class *cls = classLinker_->GetClass(*method_, mda.GetClassId(), &errorHandler); +% Class *cls = GetClass(panda_file::MethodDataAccessor(*method_->GetPandaFile(), methodId).GetClassId(), &errorHandler); % % auto opcode = inst.GetOpcode(); % if (UNLIKELY((opcode == BytecodeInstructionSafe::Opcode::INITOBJ_SHORT_V4_V4_ID16 || @@ -222,7 +222,7 @@ std::array dispatchTable{ % "StaticFieldId_" => %({ % ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING); % auto fieldIdx = method_->GetClass()->ResolveFieldIndex(id.AsIndex()); -% auto *cachedField = classLinker_->GetField(*method_, fieldIdx, true, &errorHandler); +% auto *cachedField = GetField(fieldIdx, true, &errorHandler); % if (cachedField != nullptr) { % AddField(inst.GetOffset(), cachedField); % auto classType = Type {cachedField->GetClass()}; @@ -235,7 +235,7 @@ std::array dispatchTable{ % "FieldId_" => %({ % ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING); % auto fieldIdx = method_->GetClass()->ResolveFieldIndex(id.AsIndex()); -% auto *cachedField = classLinker_->GetField(*method_, fieldIdx, false, &errorHandler); +% auto *cachedField = GetField(fieldIdx, false, &errorHandler); % if (cachedField != nullptr) { % AddField(inst.GetOffset(), cachedField); % auto classType = Type {cachedField->GetClass()}; @@ -248,7 +248,7 @@ std::array dispatchTable{ % "TypeId_" => %({ % auto classIdx = method_->GetClass()->ResolveClassIndex(id.AsIndex()); % ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING); -% auto *cachedClass = classLinker_->GetClass(*method_, classIdx, &errorHandler); +% auto *cachedClass = GetClass(classIdx, &errorHandler); % if (cachedClass != nullptr) { % auto classType = Type {cachedClass}; % AddType(inst.GetOffset(), &classType); diff --git a/static_core/verification/jobs/job.cpp b/static_core/verification/jobs/job.cpp index 7eaf270599..39d6504020 100644 --- a/static_core/verification/jobs/job.cpp +++ b/static_core/verification/jobs/job.cpp @@ -41,7 +41,9 @@ bool Job::UpdateTypes(TypeSystem *types) const result = result && hasType(field->GetClass()); if (field->GetType().IsReference()) { ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING); - result = result && hasType(field->ResolveTypeClass(&handler)); + auto typeId = panda_file::FieldDataAccessor::GetTypeId(*field->GetPandaFile(), field->GetFileId()); + auto *klass = GetClass(typeId, field->GetClass()->GetLoadContext(), field->GetPandaFile(), &handler); + result = result && hasType(klass); } }); return result; @@ -102,4 +104,32 @@ void Job::ErrorHandler::OnError([[maybe_unused]] ClassLinker::Error error, Panda LOG(ERROR, VERIFIER) << "Cannot link class: " << message; } +Class *Job::GetClass(panda_file::File::EntityId classId, ClassLinkerContext *ctx, const panda_file::File *pfile, + ErrorHandler *errorHandler) const +{ + const auto *desc = pfile->GetStringData(classId).data; + if (ClassHelper::IsUnionOrArrayUnionDescriptor(desc)) { + return ClassHelper::GetUnionLUBClass(desc, classLinker_, ctx, classLinker_->GetExtension(langContext_), + errorHandler); + } + return classLinker_->GetClass(desc, true, ctx, errorHandler); +} + +Class *Job::GetClass(panda_file::File::EntityId classId, ErrorHandler *errorHandler) const +{ + return GetClass(classId, method_->GetClass()->GetLoadContext(), method_->GetPandaFile(), errorHandler); +} + +Field *Job::GetField(panda_file::File::EntityId fieldIdx, bool isStatic, ErrorHandler *errorHandler) const +{ + Field *field = method_->GetPandaFile()->GetPandaCache()->GetFieldFromCache(fieldIdx); + if (field != nullptr) { + return field; + } + panda_file::FieldDataAccessor const fda(*method_->GetPandaFile(), fieldIdx); + auto *klass = + GetClass(fda.GetClassId(), method_->GetClass()->GetLoadContext(), method_->GetPandaFile(), errorHandler); + return classLinker_->GetField(klass, fda, isStatic, errorHandler); +} + } // namespace ark::verifier diff --git a/static_core/verification/jobs/job.h b/static_core/verification/jobs/job.h index 66af983fe7..a3a7ff6360 100644 --- a/static_core/verification/jobs/job.h +++ b/static_core/verification/jobs/job.h @@ -133,7 +133,13 @@ public: void OnError(ClassLinker::Error error, PandaString const &message) override; }; + Class *GetClass(panda_file::File::EntityId classId, ClassLinkerContext *ctx, const panda_file::File *pfile, + ErrorHandler *errorHandler) const; + private: + Class *GetClass(panda_file::File::EntityId classId, ErrorHandler *errorHandler = nullptr) const; + Field *GetField(panda_file::File::EntityId fieldIdx, bool isStatic, ErrorHandler *errorHandler) const; + Service *service_; ClassLinker *classLinker_; Method const *method_; diff --git a/static_core/verification/type/type_system.cpp b/static_core/verification/type/type_system.cpp index 874a348c54..1f0541c35b 100644 --- a/static_core/verification/type/type_system.cpp +++ b/static_core/verification/type/type_system.cpp @@ -36,15 +36,22 @@ static PandaString ClassNameToDescriptorString(char const *name) return str; } -static Type DescriptorToType(ClassLinker *linker, ClassLinkerContext *ctx, uint8_t const *descr) +static Type DescriptorToType(ClassLinker *linker, ClassLinkerContext *ctx, LanguageContext langCtx, + uint8_t const *descr) { PandaString strDesc = utf::Mutf8AsCString(descr); - auto canonDesc = pandasm::Type::CanonicalizeDescriptor(strDesc); - if (canonDesc != std::string(strDesc)) { - LOG_VERIFIER_NOT_CANONICALIZED_UNION_TYPE(strDesc, canonDesc); - } + ark::Class *klass {nullptr}; Job::ErrorHandler handler; - auto klass = linker->GetClass(descr, true, ctx, &handler); + if (ClassHelper::IsUnionOrArrayUnionDescriptor(descr)) { + auto canonDesc = pandasm::Type::FromDescriptor(strDesc).GetDescriptor(); + if (canonDesc != std::string(strDesc)) { + LOG_VERIFIER_NOT_CANONICALIZED_UNION_TYPE(strDesc, canonDesc); + } + klass = ClassHelper::GetUnionLUBClass(descr, linker, ctx, linker->GetExtension(langCtx), &handler); + } else { + klass = linker->GetClass(descr, true, ctx, &handler); + } + if (klass != nullptr) { return Type {klass}; } @@ -78,7 +85,7 @@ TypeSystem::TypeSystem(VerifierService *service, panda_file::SourceLang lang) Type TypeSystem::BootDescriptorToType(uint8_t const *descr) { - return DescriptorToType(service_->GetClassLinker(), bootLinkerCtx_, descr); + return DescriptorToType(service_->GetClassLinker(), bootLinkerCtx_, langCtx_, descr); } void TypeSystem::ExtendBySupers(PandaUnorderedSet *set, Class const *klass) @@ -127,7 +134,7 @@ MethodSignature const *TypeSystem::GetMethodSignature(Method const *method) methodOfId_[methodId] = method; auto const resolveType = [this, method](const uint8_t *descr) { - return DescriptorToType(service_->GetClassLinker(), method->GetClass()->GetLoadContext(), descr); + return DescriptorToType(service_->GetClassLinker(), method->GetClass()->GetLoadContext(), langCtx_, descr); }; MethodSignature sig; -- Gitee From af070ad9d757f8c420ed3a4cdc247fa1cca724ad Mon Sep 17 00:00:00 2001 From: Lyupa Anastasia Date: Tue, 17 Jun 2025 18:08:50 +0300 Subject: [PATCH 3/3] Test update union type check/narrowing codegen Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICFWFD Change-Id: I92a6947c08a023d9e742a8d64b76e9c5a14316c1 Signed-off-by: Lyupa Anastasia --- static_core/plugins/ets/runtime/ets_vtable_builder.cpp | 2 +- static_core/runtime/compiler.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/static_core/plugins/ets/runtime/ets_vtable_builder.cpp b/static_core/plugins/ets/runtime/ets_vtable_builder.cpp index d22e583f54..b23357f0fa 100644 --- a/static_core/plugins/ets/runtime/ets_vtable_builder.cpp +++ b/static_core/plugins/ets/runtime/ets_vtable_builder.cpp @@ -122,7 +122,7 @@ static bool RefExtendsOrImplements(const ClassLinkerContext *ctx, RefTypeLink su // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) template static std::pair UnionIsAssignableToRef(const ClassLinkerContext *ctx, RefTypeLink sub, - RefTypeLink unionRef, uint32_t depth, uint32_t processIdx = 0) + RefTypeLink unionRef, uint32_t depth, uint32_t processIdx = 0) { auto [res, idx] = [processIdx]() { if (processIdx == 0) { diff --git a/static_core/runtime/compiler.cpp b/static_core/runtime/compiler.cpp index a5ce831bbe..8ff0a60b82 100644 --- a/static_core/runtime/compiler.cpp +++ b/static_core/runtime/compiler.cpp @@ -152,6 +152,8 @@ compiler::RuntimeInterface::ClassPtr PandaRuntimeInterface::GetClass(MethodPtr m Class *loadedClass = classLinker->GetLoadedClass(*caller->GetPandaFile(), panda_file::File::EntityId(id), caller->GetClass()->GetLoadContext()); if (LIKELY(loadedClass != nullptr)) { + ErrorHandler handler; + ScopedMutatorLock lock; loadedClass = HandleUnionClass(loadedClass, classLinker, caller->GetClass()->GetLoadContext()); if (ClassHelper::IsUnionOrArrayUnionDescriptor(loadedClass->GetDescriptor())) { UNREACHABLE(); -- Gitee