diff --git a/static_core/assembler/assembly-emitter.cpp b/static_core/assembler/assembly-emitter.cpp index ad37aaf4caf022a2a3673d37b9976bc0b81922cb..72bf2585787f5961638e1d788cbf6338552b6797 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 50e6b5b9040abf212ee0291c4f7dbe26c88a9dd3..2eb0f483e6ebad291f0728ca2d778179d743a92a 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 7120ff519492e40ea85891865fd188ed9237e8de..3fd2d651e2f39eee0a461a15c2c7ba056ac50849 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 a91277e844c2cd5d334ee98aa44a0e034751799e..28077482189d3e399b1ed2a0b594d94d81fa26d4 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 3693b542062959eb4bfc3975b1fe1ae052771465..56b5d7cd03bb870da345b51746c389921e0f4b61 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 2479b67f109a4fbe3dd389a4828bad5e0bb45759..2ccfd4dbcbeea44127838b796b306cbe7d788289 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 fb71b9434a0b401aae16d855d4030372fb172dea..755010566555e1f07925fd0dc55ea4533cb357cd 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 bc385e8745657be21f3107fdd98df36a586a5bda..5992274e42f7c1f5d2e27016fa589685ce5c53b9 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 14319b0acb98b5f5f7990862f1233bb01c3842b4..91af93e43728cf3e4021e1c27c5350c542a0f6e2 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 8ffe840c9ca3b3a0f888b465928a0354a518685c..bad3c3b00d2ef3efeb180d1a0d490902379b4262 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 4060d178f9322bad417d26ec2d9cc4720a482477..0a36e3a780c08ecc3977babdab46a85fd64bd285 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 0ec1543265513b919503e1f138b3af26ddf793b7..d54809cce6a6c9530498bf7291a0a9d60ab9d9b1 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 821c55293064a1b4a6f9377d49163afdb70a398c..cece7d0759c67508c75b83ee4fd05c2543472e69 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 0941a2a0ee7d5ace664304fac61e6a3e9e211a70..8bbe1ba25f097f789e0c0eb382f331f014d22f13 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) << "): "; @@ -203,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()); @@ -707,4 +741,104 @@ ClassLinkerContext *EtsClassLinkerExtension::CreateApplicationClassLinkerContext return ctx; } +/* static */ +ClassLinkerContext *EtsClassLinkerExtension::GetParentContext(ClassLinkerContext *ctx) +{ + auto *linker = GetOrCreateEtsRuntimeLinker(ctx); + auto *abcRuntimeLinker = EtsAbcRuntimeLinker::FromEtsObject(linker); + auto *parentLinker = abcRuntimeLinker->GetParentLinker(); + return parentLinker->GetClassLinkerContext(); +} + +ClassLinkerContext *EtsClassLinkerExtension::GetCommonContext(ClassLinkerContext *ctx1, ClassLinkerContext *ctx2) +{ + 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( + 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 a63e0281b7adbc92c594b9521aada86164938644..7d3d02f744fc81feca420dc10f98207235794b95 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,9 +116,18 @@ 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); + /// @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 81ffd11fd466b1491585aea8f836aa8cd59dcacd..b23357f0fa9414307e2aa388841210bf1bff9ebb 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) { @@ -163,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)) { @@ -186,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; @@ -196,11 +234,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 +270,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 +279,61 @@ 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, const_cast(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 d39eeb7fa677055d8a8824b40db4a0fa668c3722..0fe51b2655dd3678a335e5b8e0bb670f098f2ad8 100644 --- a/static_core/plugins/ets/runtime/ets_vtable_builder.h +++ b/static_core/plugins/ets/runtime/ets_vtable_builder.h @@ -25,15 +25,25 @@ 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); +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: 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 +69,64 @@ 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) + { + 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/intrinsics/std_core_Type.cpp b/static_core/plugins/ets/runtime/intrinsics/std_core_Type.cpp index 71b2f7c3dc1788c9b6b08a128ee1b2fc39cf3ced..063b70570869264e1622a05dd3e7941bb12c9c15 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 0c4b44f89c1aa1e38b05e754388126eaf73b7214..749039275ad9040db6d1f3b69e4e82005840e4b8 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_method_signature.h b/static_core/plugins/ets/runtime/types/ets_method_signature.h index d183d4f32365de748b69681e1b46eb048242d430..8c5a9b112d1481a841bfa1ac40acd6afc449ee7c 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/runtime/types/ets_type.h b/static_core/plugins/ets/runtime/types/ets_type.h index 70e497723c3f14118b84931b06a7adc056a549bf..a03c52946d764a6e9d7b45864a51920f8b230d6f 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/ani/tests/mangling/mangle_signature_test.cpp b/static_core/plugins/ets/tests/ani/tests/mangling/mangle_signature_test.cpp index c1bed415bd34392e513d72c427fecfd358c73750..21a1537b123fe728c21ac4fd6356f52e945f3d3a 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 7d0b966967a771b00da7dad1c1f435e2f1cc44c6..1d2d4590c45ba92aaf516f36a13ecef4e877faf7 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 2e4c5d1175098a2637901ae8b98ae22a3d09959c..6fe69acbfc0b550105c8fb017fa02df7fb8919dd 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/ets_test_suite/CMakeLists.txt b/static_core/plugins/ets/tests/ets_test_suite/CMakeLists.txt index 6c5aef969ca385dfa683eaace3a53aa8047dbea6..977a6d5b493faa7555cdb81beb7097dc65873c0a 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 c3f394ee986c2d2b8a4c08dbaf9df97d929b2a1c..d2d092871e5e35229b88ee7137d874b8291fe9c6 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/ets_test_suite/unions/CMakeLists.txt b/static_core/plugins/ets/tests/ets_test_suite/unions/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..2dd56d0c9a929b6188ce66f0e298c10751fb48f7 --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/unions/CMakeLists.txt @@ -0,0 +1,102 @@ +# 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. + + +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) + 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/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 + DEPENDS etsstdlib + LANGUAGE_CONTEXT ets + STDLIBS $ + ) + 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(unions_${ARG_FILE}-check-logmsg + COMMENT "Check unions_${ARG_FILE} log message" + COMMAND grep -zo \"${ARG_SEARCH_STDERR}\" ${error_file} >/dev/null + DEPENDS unions_${ARG_FILE}-verify) + + 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_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_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/ets_test_suite/unions/correct_union_arg.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_arg.pa new file mode 100644 index 0000000000000000000000000000000000000000..a1f60c1d65e7c480399c24853946c3926f754c22 --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/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/ets_test_suite/unions/correct_union_arg_2.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_arg_2.pa new file mode 100644 index 0000000000000000000000000000000000000000..21a441571ba0b67007ab9eee44ca8fefd104e40b --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/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/ets_test_suite/unions/correct_union_arg_redecl.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_arg_redecl.pa new file mode 100644 index 0000000000000000000000000000000000000000..9dd6a3685537a9aac7e8af116d304bb9a1bf5150 --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_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/ets_test_suite/unions/correct_union_override.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_override.pa new file mode 100644 index 0000000000000000000000000000000000000000..3519a67b402c8314a86b081cb459ff70744aa173 --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/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/ets_test_suite/unions/correct_union_override_2.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/correct_union_override_2.pa new file mode 100644 index 0000000000000000000000000000000000000000..4596d4d5fa7ad41ed699cba6a3a6b1471f7bf3b0 --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/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/ets_test_suite/unions/neg_union_arg.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_arg.pa new file mode 100644 index 0000000000000000000000000000000000000000..b903da492058edee0088c4d773cadaa8e0019a5a --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/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/ets_test_suite/unions/neg_union_override_multi1.pa b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi1.pa new file mode 100644 index 0000000000000000000000000000000000000000..0569d4d0eddd86924c53f380e68a817ff2d9f709 --- /dev/null +++ b/static_core/plugins/ets/tests/ets_test_suite/unions/neg_union_override_multi1.pa @@ -0,0 +1,56 @@ +# 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 main() { + initobj.short E._ctor_:(E) + sta.obj v0 + initobj.short BB._ctor_:(BB) + sta.obj 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 0000000000000000000000000000000000000000..c8a84f9b4a96e903805c92c72580158a0f0951d9 --- /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 0000000000000000000000000000000000000000..0434b53acb7996ca3ddd61d641a7a1d7b6c3bed0 --- /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 0000000000000000000000000000000000000000..0e596e4b53430bb12f3a56a5787721f5ed2b6f77 --- /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 0000000000000000000000000000000000000000..66a7ffe9ea96a2177f7f1ba07b6e73bea11965af --- /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 0000000000000000000000000000000000000000..a5ed1a6c7a9ebecdffd926c443bb4a5fd1ee1c73 --- /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/CMakeLists.txt b/static_core/plugins/ets/tests/runtime/types/CMakeLists.txt index 946f0c6d008c2852088451cf5616ae76700d93c6..1c9d6947682e1f5ce455886427534ee976020c2b 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 45fe7e17c60b2f0d3c3ff53d7312a59a4682741b..a151389258dfdd24a3f16d677f79def480f372f8 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,289 @@ TEST_F(EtsRuntimeLinkerTest, GetClassReturnsNullWhenErrorSuppressed) ASSERT_EQ(klass, nullptr); } +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassNotRedecl) +{ + 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_NE(klass, nullptr); + ASSERT_EQ(handler.GetMessage(), ""); +} + +TEST_F(EtsRuntimeLinkerTest, CreateUnionClassMultiOverriding1) +{ + 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"); +} + +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 new file mode 100644 index 0000000000000000000000000000000000000000..05773f82401f10b10532165c383af57708d21cc1 --- /dev/null +++ b/static_core/plugins/ets/tests/runtime/types/ets_union_test.cpp @@ -0,0 +1,154 @@ +/** + * 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(); + } + + 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) + +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 { + {"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); + 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)); + } +} + +} // namespace ark::ets::test diff --git a/static_core/runtime/BUILD.gn b/static_core/runtime/BUILD.gn index 9a85818826b745d64f3e3229c1bf8bbba21d0928..a411e7642ccb8a4301809b1113d0a5f4bd2ad7ad 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 8a5005a67cd276a5cfdee4a6ca82a86b6c1f3209..3d9fbe712aeee1c90218daf299ba461d4c13bdc9 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 eda09c7c258ce6aac90d148891188544372a0069..46da6fec2806431279eef3d6dde89526583f77cf 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 @@ -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 { @@ -119,4 +121,102 @@ 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::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; + // 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 + 1; +} + +/* 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 (IsUnionDescriptor(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 cad9d9876efe3eaf696d89af7e792e378bb0f5ee..2d7c8c405fbd488ba6435e03c6e3f84372dd088e 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) { @@ -1279,6 +1395,10 @@ Class *ClassLinker::GetClass(const uint8_t *descriptor, bool needCopyDescriptor, return cls; } + if (ClassHelper::IsUnionDescriptor(descriptor)) { + return LoadUnionClass(descriptor, needCopyDescriptor, context, errorHandler); + } + if (ClassHelper::IsArrayDescriptor(descriptor)) { return LoadArrayClass(descriptor, needCopyDescriptor, context, errorHandler); } @@ -1322,14 +1442,22 @@ 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; + const uint8_t *descriptor = pf.GetStringData(id).data; 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)) { @@ -1625,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 6d585c402b3cdcc39fc79ddcebb6128c91dacb88..8ff0a60b82710842d016978012cd80cbb89f5557 100644 --- a/static_core/runtime/compiler.cpp +++ b/static_core/runtime/compiler.cpp @@ -131,18 +131,44 @@ 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)) { + ErrorHandler handler; + ScopedMutatorLock lock; + 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 +189,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 +447,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 +1095,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 0a2588a17fb168747b27d40a144ecfee5704fd32..94c69ff6db9514b49920757cd31117a5f6bd72e1 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 24ba66eed8ee3fadccc96739a6e0ecacba5e7e6b..e6d46fe83bade80eca4f6a110b6f307d278b8d96 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 7900d8976c31c57e026214f449fc612c34a51019..aaf1a2860d83622981d4e3abacc532cb30058c04 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 a6121da5975ae474ead8522102d18625d9b97725..ea75de4de4bd82b2cb1780bb8e798fc02b5091f2 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 6791ebc54df91a7e7e5a9b8d323be492a4bff228..f0b021deab58eb48e41a6a774c64f0c8b4c03328 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 @@ -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 @@ -86,6 +92,15 @@ public: return dim; } + static bool IsPrimitive(const uint8_t *descriptor); + static bool IsReference(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 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 ac5e23b8bbb9c74c44df11a2e9f929a000610d4b..e2b79d8ae0d788b7c92b04f58c1c36ce03e4e405 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); @@ -98,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); @@ -263,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); @@ -346,6 +352,13 @@ 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); + + 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 f922c478241c0293fb62bad801e210ef964ae0ff..4414885d03997da890e23054ffe5efcd90ace644 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,23 @@ 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) + { + 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/hclass.h b/static_core/runtime/include/hclass.h index e2108dce4858b9c8d9fd6a4504064db6f9bb1cc9..f102209c66296542083f805fe9970c87510e0ba8 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/runtime/include/vtable_builder_variance-inl.h b/static_core/runtime/include/vtable_builder_variance-inl.h index 92512fce63c66760509373f849480c1eb9d8a842..883eb13ecbd3d53cd5cf7aeb160d86347685928b 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/absint/abs_int_inl.h b/static_core/verification/absint/abs_int_inl.h index 6a7a0637d253be488cca8f9f921613dcec10c8d8..6739b42615df598a89f8a8698154205ee5a1e99b 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 5d760dcb0327e3414cdf8b92e5cd2cc9475b5630..f734cb102074c2fe6b4f75e735dfd6cc620d6ee7 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 7eaf270599767dc7e9380a4b363704351a4ddadb..39d6504020323a7e8fa73cdd9b4730e694be1833 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 66af983fe7ef9876593d42fe3f4003b277ee6e69..a3a7ff636033132f199c34c09c9fc80667d88e75 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/messages.yaml b/static_core/verification/messages.yaml index 1143c2b6436f8bbdf0f41aca961dc9d4959b3cfb..218ae395adc9f73bc3718659ae1a50232e1512ae 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 fef57e95b76a5f1e959bdc2efa99bd06e1c1a778..1f0541c35be3faca417427dfd26b33baedb42a72 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" @@ -35,10 +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); + 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}; } @@ -72,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) @@ -121,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;