From a6285e77e21f65e623ce76e6db92e06ff6af4d28 Mon Sep 17 00:00:00 2001 From: aleksisch Date: Wed, 7 Feb 2024 16:02:06 +0300 Subject: [PATCH] implement dynamic call compilation Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/I91ZFK Testing: All required pre-merge tests Signed-off-by: aleksisch --- ets2panda/BUILD.gn | 1 + ets2panda/CMakeLists.txt | 1 + ets2panda/checker/ETSchecker.h | 3 +- ets2panda/checker/ets/dynamic.cpp | 69 +------ ets2panda/checker/ets/dynamic/dynamicCall.cpp | 78 ++++++++ ets2panda/checker/ets/dynamic/dynamicCall.h | 60 ++++++ ets2panda/compiler/core/ETSCompiler.cpp | 120 ++++------- ets2panda/compiler/core/ETSCompiler.h | 2 + ets2panda/compiler/core/ETSemitter.h | 2 +- ets2panda/test/CMakeLists.txt | 29 +++ .../test/unit/dynamic/dynamic_call_test.cpp | 187 ++++++++++++++++++ ets2panda/test/unit/es2panda_unit_gtest.h | 47 +++++ 12 files changed, 452 insertions(+), 147 deletions(-) create mode 100644 ets2panda/checker/ets/dynamic/dynamicCall.cpp create mode 100644 ets2panda/checker/ets/dynamic/dynamicCall.h create mode 100644 ets2panda/test/unit/dynamic/dynamic_call_test.cpp create mode 100644 ets2panda/test/unit/es2panda_unit_gtest.h diff --git a/ets2panda/BUILD.gn b/ets2panda/BUILD.gn index 1ff9bd5d16..7db2d1773d 100644 --- a/ets2panda/BUILD.gn +++ b/ets2panda/BUILD.gn @@ -41,6 +41,7 @@ libes2panda_sources = [ "checker/ets/castingContext.cpp", "checker/ets/conversion.cpp", "checker/ets/dynamic.cpp", + "checker/ets/dynamic/dynamicCall.cpp", "checker/ets/enum.cpp", "checker/ets/function.cpp", "checker/ets/helpers.cpp", diff --git a/ets2panda/CMakeLists.txt b/ets2panda/CMakeLists.txt index 764781d68c..669fb995d0 100644 --- a/ets2panda/CMakeLists.txt +++ b/ets2panda/CMakeLists.txt @@ -372,6 +372,7 @@ set(ES2PANDA_LIB_SRC checker/ets/castingContext.cpp checker/ets/conversion.cpp checker/ets/dynamic.cpp + checker/ets/dynamic/dynamicCall.cpp checker/ets/function.cpp checker/ets/enum.cpp checker/ets/helpers.cpp diff --git a/ets2panda/checker/ETSchecker.h b/ets2panda/checker/ETSchecker.h index fd21462dd1..d351638da5 100644 --- a/ets2panda/checker/ETSchecker.h +++ b/ets2panda/checker/ETSchecker.h @@ -54,7 +54,7 @@ using DynamicCallIntrinsicsMap = ArenaUnorderedMap; using FunctionalInterfaceMap = ArenaUnorderedMap; using TypeMapping = ArenaUnorderedMap; -using DynamicCallNamesMap = ArenaMap, uint32_t>; +using DynamicCallNamesMap = ArenaMap, uint32_t>; class ETSChecker final : public Checker { public: @@ -684,7 +684,6 @@ private: template ir::ScriptFunction *CreateDynamicCallIntrinsic(ir::Expression *callee, const ArenaVector &arguments, Language lang); - void CreateDynamicCallQualifiedName(ir::Expression *callee, bool isConstruct); ir::ClassStaticBlock *CreateDynamicCallClassInitializer(varbinder::ClassScope *classScope, Language lang, bool isConstruct); ir::ClassStaticBlock *CreateDynamicModuleClassInitializer(varbinder::ClassScope *classScope, diff --git a/ets2panda/checker/ets/dynamic.cpp b/ets2panda/checker/ets/dynamic.cpp index 80ac4bd698..d7cfd1d502 100644 --- a/ets2panda/checker/ets/dynamic.cpp +++ b/ets2panda/checker/ets/dynamic.cpp @@ -21,6 +21,7 @@ #include "varbinder/varbinder.h" #include "varbinder/ETSBinder.h" #include "checker/types/ets/etsDynamicFunctionType.h" +#include "checker/ets/dynamic/dynamicCall.h" #include "ir/base/classProperty.h" #include "ir/base/classStaticBlock.h" #include "ir/base/methodDefinition.h" @@ -62,28 +63,6 @@ ir::ETSParameterExpression *ETSChecker::AddParam(varbinder::FunctionParamScope * return param; } -static bool IsByValueCall(varbinder::ETSBinder *varbinder, ir::Expression *callee) -{ - if (callee->IsMemberExpression()) { - return !callee->AsMemberExpression()->ObjType()->IsETSDynamicType(); - } - - if (callee->IsETSTypeReference()) { - return false; - } - - auto *var = callee->AsIdentifier()->Variable(); - auto *data = varbinder->DynamicImportDataForVar(var); - if (data != nullptr) { - auto *specifier = data->specifier; - if (specifier->IsImportSpecifier()) { - return false; - } - } - - return true; -} - template ir::ScriptFunction *ETSChecker::CreateDynamicCallIntrinsic(ir::Expression *callee, const ArenaVector &arguments, Language lang) @@ -105,7 +84,7 @@ ir::ScriptFunction *ETSChecker::CreateDynamicCallIntrinsic(ir::Expression *calle info->params.push_back(objParam->Ident()->Variable()->AsLocalVariable()); ir::ETSParameterExpression *param2; - if (!IsByValueCall(VarBinder()->AsETSBinder(), callee)) { + if (!DynamicCall::IsByValue(VarBinder()->AsETSBinder(), callee)) { // SUPPRESS_CSA_NEXTLINE(alpha.core.AllocatorETSCheckerHint) param2 = AddParam(paramScope, "qname_start", GlobalIntType()); params.push_back(param2); @@ -171,45 +150,6 @@ static void ToString([[maybe_unused]] ETSChecker *checker, const ArenaVector parts(Allocator()->Adapter()); - - if (isConstruct) { - auto *name = obj->AsETSTypeReference()->Part()->Name(); - while (name->IsTSQualifiedName()) { - auto *qname = name->AsTSQualifiedName(); - name = qname->Left(); - parts.push_back(qname->Right()->AsIdentifier()->Name()); - } - obj = name; - } else { - while (obj->IsMemberExpression() && obj->AsMemberExpression()->ObjType()->IsETSDynamicType()) { - auto *memExpr = obj->AsMemberExpression(); - obj = memExpr->Object(); - parts.push_back(memExpr->Property()->AsIdentifier()->Name()); - } - } - if (obj->IsIdentifier()) { - auto *var = obj->AsIdentifier()->Variable(); - auto *data = VarBinder()->AsETSBinder()->DynamicImportDataForVar(var); - if (data != nullptr) { - ASSERT(data->import->Language().IsDynamic()); - auto *specifier = data->specifier; - if (specifier->IsImportSpecifier()) { - parts.push_back(specifier->AsImportSpecifier()->Imported()->Name()); - } - } - } - - if (parts.empty()) { - return; - } - std::reverse(parts.begin(), parts.end()); - DynamicCallNames(isConstruct)->try_emplace(parts, 0); -} - template Signature *ETSChecker::ResolveDynamicCallExpression(ir::Expression *callee, const ArenaVector &arguments, Language lang, bool isConstruct) @@ -225,10 +165,11 @@ Signature *ETSChecker::ResolveDynamicCallExpression(ir::Expression *callee, cons std::stringstream ss; ss << "dyncall"; - if (IsByValueCall(VarBinder()->AsETSBinder(), callee)) { + if (DynamicCall::IsByValue(VarBinder()->AsETSBinder(), callee)) { ss << "-byvalue"; } else { - CreateDynamicCallQualifiedName(callee, isConstruct); + const auto callNames = DynamicCall::ResolveCall(VarBinder()->AsETSBinder(), callee); + DynamicCallNames(isConstruct)->try_emplace(callNames.name, 0); } ToString(this, arguments, ss); diff --git a/ets2panda/checker/ets/dynamic/dynamicCall.cpp b/ets2panda/checker/ets/dynamic/dynamicCall.cpp new file mode 100644 index 0000000000..35cc54b76e --- /dev/null +++ b/ets2panda/checker/ets/dynamic/dynamicCall.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 - 2024 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 "checker/ets/dynamic/dynamicCall.h" + +#include "ir/ets/etsTypeReference.h" +#include "ir/ets/etsTypeReferencePart.h" +#include "ir/ts/tsQualifiedName.h" +#include "ir/expressions/memberExpression.h" + +namespace ark::es2panda::checker { + +DynamicCall::Result DynamicCall::ResolveCall(const varbinder::ETSBinder *varbinder, const ir::Expression *callee) +{ + auto calleeName = NameHolder(varbinder->Allocator()->Adapter()); + + if (callee->IsETSTypeReference()) { + // new A.B.C() => call js.new(A, ".B.C") + callee = callee->AsETSTypeReference()->Part()->Name(); + while (callee->IsTSQualifiedName()) { + auto *qname = callee->AsTSQualifiedName(); + callee = qname->Left(); + calleeName.emplace_back(qname->Right()->AsIdentifier()->Name()); + } + ASSERT(callee->IsIdentifier()); + } else if (callee->IsMemberExpression()) { + const auto memberExpr = callee->AsMemberExpression(); + callee = SqueezeExpr(memberExpr, calleeName); + } + if (callee->IsIdentifier()) { + // kinda optimization in case: + // `import X from Y` to use (load Y, call "X"), instead of (load Y, load X, call) + const auto var = callee->AsIdentifier()->Variable(); + const auto *data = varbinder->DynamicImportDataForVar(var); + if (data != nullptr && data->specifier != nullptr && data->specifier->IsImportSpecifier()) { + calleeName.emplace_back(data->specifier->AsImportSpecifier()->Imported()->Name()); + std::reverse(calleeName.begin(), calleeName.end()); + return {data->import, calleeName}; + } + } + std::reverse(calleeName.begin(), calleeName.end()); + return {callee, calleeName}; +} + +DynamicCall::Result DynamicCall::SqueezeExpr(ArenaAllocator *allocator, const ir::MemberExpression *expr) +{ + NameHolder name(allocator->Adapter()); + auto obj = SqueezeExpr(expr, name); + std::reverse(name.begin(), name.end()); + return {obj, name}; +} + +const ir::Expression *DynamicCall::SqueezeExpr(const ir::MemberExpression *memberExpr, NameHolder &name) +{ + if (!memberExpr->Object()->TsType()->IsETSDynamicType() || memberExpr->IsComputed()) { + return memberExpr; + } + ASSERT(memberExpr->Property()->IsIdentifier()); + name.emplace_back(memberExpr->Property()->AsIdentifier()->Name()); + if (memberExpr->Object()->IsMemberExpression()) { + return SqueezeExpr(memberExpr->Object()->AsMemberExpression(), name); + } + return memberExpr->Object(); +} + +} // namespace ark::es2panda::checker diff --git a/ets2panda/checker/ets/dynamic/dynamicCall.h b/ets2panda/checker/ets/dynamic/dynamicCall.h new file mode 100644 index 0000000000..b4de6cfd0a --- /dev/null +++ b/ets2panda/checker/ets/dynamic/dynamicCall.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 - 2024 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. + */ + +#ifndef ARK_DYNAMICCALLINFO_H +#define ARK_DYNAMICCALLINFO_H + +#include + +#include "varbinder/ETSBinder.h" +#include "ir/expression.h" + +namespace ark::es2panda::checker { + +class DynamicCall { + using NameHolder = ArenaVector; + +public: + struct Result { + const ir::AstNode *obj; + const NameHolder name; // NOLINT(readability-identifier-naming) + }; + + /** + * Resolve callee + * @param varbinder + * @param callee expression used to call method + * @return callee and name from which should be used to produce call + */ + static Result ResolveCall(const varbinder::ETSBinder *varbinder, const ir::Expression *callee); + static bool IsByValue(const varbinder::ETSBinder *varbinder, const ir::Expression *callee) + { + return ResolveCall(varbinder, callee).name.empty(); + } + + /** + * Example: A[0].C.D => return: A[0], name: ".C.D" + * @param expr member expression + * @param name to store result + * @return object with remaining member expression + */ + static Result SqueezeExpr(ArenaAllocator *allocator, const ir::MemberExpression *expr); + +private: + static const ir::Expression *SqueezeExpr(const ir::MemberExpression *expr, NameHolder &name); +}; + +} // namespace ark::es2panda::checker +#endif // ARK_DYNAMICCALLINFO_H diff --git a/ets2panda/compiler/core/ETSCompiler.cpp b/ets2panda/compiler/core/ETSCompiler.cpp index f7d9c68b16..e4243e2bb0 100644 --- a/ets2panda/compiler/core/ETSCompiler.cpp +++ b/ets2panda/compiler/core/ETSCompiler.cpp @@ -16,6 +16,7 @@ #include "ETSCompiler.h" #include "compiler/base/catchTable.h" +#include "checker/ets/dynamic/dynamicCall.h" #include "compiler/base/condition.h" #include "compiler/base/lreference.h" #include "compiler/core/ETSGen.h" @@ -254,46 +255,38 @@ void ETSCompiler::Compile(const ir::ETSNewArrayInstanceExpression *expr) const etsg->LoadAccumulator(expr, arr); } -static void CreateDynamicObject(const ir::AstNode *node, compiler::ETSGen *etsg, compiler::VReg &objReg, - ir::Expression *name, checker::Signature *signature, - const ArenaVector &arguments) +static std::pair LoadDynamicName(compiler::ETSGen *etsg, const ir::AstNode *node, + const ArenaVector &dynName, bool isConstructor) { auto *checker = const_cast(etsg->Checker()->AsETSChecker()); - ArenaVector parts(checker->Allocator()->Adapter()); - - while (name->IsTSQualifiedName()) { - auto *qname = name->AsTSQualifiedName(); - name = qname->Left(); - parts.push_back(qname->Right()->AsIdentifier()->Name()); - } - - auto *var = name->AsIdentifier()->Variable(); - auto *data = etsg->VarBinder()->DynamicImportDataForVar(var); - if (data != nullptr) { - auto *import = data->import; - auto *specifier = data->specifier; - ASSERT(import->Language().IsDynamic()); - etsg->LoadAccumulatorDynamicModule(node, import); - if (specifier->IsImportSpecifier()) { - parts.push_back(specifier->AsImportSpecifier()->Imported()->Name()); - } + auto *callNames = checker->DynamicCallNames(isConstructor); + + auto qnameStart = etsg->AllocReg(); + auto qnameLen = etsg->AllocReg(); + + TargetTypeContext ttctx(etsg, nullptr); // without this ints will be cast to JSValue + etsg->LoadAccumulatorInt(node, callNames->at(dynName)); + etsg->StoreAccumulator(node, qnameStart); + etsg->LoadAccumulatorInt(node, dynName.size()); + etsg->StoreAccumulator(node, qnameLen); + return {qnameStart, qnameLen}; +} + +static void CreateDynamicObject(const ir::AstNode *node, compiler::ETSGen *etsg, const ir::Expression *typeRef, + checker::Signature *signature, const ArenaVector &arguments) +{ + auto objReg = etsg->AllocReg(); + + auto callInfo = checker::DynamicCall::ResolveCall(etsg->VarBinder(), typeRef); + if (callInfo.obj->IsETSImportDeclaration()) { + etsg->LoadAccumulatorDynamicModule(node, callInfo.obj->AsETSImportDeclaration()); } else { - name->Compile(etsg); + callInfo.obj->Compile(etsg); } etsg->StoreAccumulator(node, objReg); - std::reverse(parts.begin(), parts.end()); - auto *callNames = checker->DynamicCallNames(true); - auto qnameStart = etsg->AllocReg(); - auto qnameLen = etsg->AllocReg(); - { - TargetTypeContext ttctx(etsg, nullptr); // without this ints will be cast to JSValue - etsg->LoadAccumulatorInt(node, callNames->at(parts)); - etsg->StoreAccumulator(node, qnameStart); - etsg->LoadAccumulatorInt(node, parts.size()); - etsg->StoreAccumulator(node, qnameLen); - } + auto [qnameStart, qnameLen] = LoadDynamicName(etsg, node, callInfo.name, true); etsg->CallDynamic(node, objReg, qnameStart, qnameLen, signature, arguments); } @@ -328,9 +321,8 @@ void ETSCompiler::Compile(const ir::ETSNewClassInstanceExpression *expr) const ETSGen *etsg = GetETSGen(); if (expr->TsType()->IsETSDynamicType()) { compiler::RegScope rs(etsg); - auto objReg = etsg->AllocReg(); - auto *name = expr->GetTypeRef()->AsETSTypeReference()->Part()->Name(); - CreateDynamicObject(expr, etsg, objReg, name, expr->signature_, expr->GetArguments()); + auto *name = expr->GetTypeRef(); + CreateDynamicObject(expr, etsg, name, expr->signature_, expr->GetArguments()); } else { ConvertRestArguments(const_cast(etsg->Checker()->AsETSChecker()), expr); etsg->InitObject(expr, expr->signature_, expr->GetArguments()); @@ -818,55 +810,23 @@ bool ETSCompiler::IsSucceedCompilationProxyMemberExpr(const ir::CallExpression * return enumInterface != nullptr; } -void ETSCompiler::GetDynamicNameParts(const ir::CallExpression *expr, ArenaVector &parts) const -{ - ETSGen *etsg = GetETSGen(); - ir::Expression *obj = expr->callee_; - while (obj->IsMemberExpression() && obj->AsMemberExpression()->ObjType()->IsETSDynamicType()) { - auto *memExpr = obj->AsMemberExpression(); - obj = memExpr->Object(); - parts.push_back(memExpr->Property()->AsIdentifier()->Name()); - } - - if (!obj->IsMemberExpression() && obj->IsIdentifier()) { - auto *var = obj->AsIdentifier()->Variable(); - auto *data = etsg->VarBinder()->DynamicImportDataForVar(var); - if (data != nullptr) { - auto *import = data->import; - auto *specifier = data->specifier; - ASSERT(import->Language().IsDynamic()); - etsg->LoadAccumulatorDynamicModule(expr, import); - if (specifier->IsImportSpecifier()) { - parts.push_back(specifier->AsImportSpecifier()->Imported()->Name()); - } - return; - } - } - obj->Compile(etsg); -} - void ETSCompiler::CompileDynamic(const ir::CallExpression *expr, compiler::VReg &calleeReg) const { ETSGen *etsg = GetETSGen(); - compiler::VReg dynParam2 = etsg->AllocReg(); - auto *checker = const_cast(etsg->Checker()->AsETSChecker()); - ArenaVector parts(checker->Allocator()->Adapter()); - GetDynamicNameParts(expr, parts); + auto callInfo = checker::DynamicCall::ResolveCall(etsg->VarBinder(), expr->Callee()); + if (callInfo.obj->IsETSImportDeclaration()) { + etsg->LoadAccumulatorDynamicModule(expr, callInfo.obj->AsETSImportDeclaration()); + } else { + callInfo.obj->Compile(etsg); + } etsg->StoreAccumulator(expr, calleeReg); - if (!parts.empty()) { - std::reverse(parts.begin(), parts.end()); - auto *callNames = checker->DynamicCallNames(false); - compiler::VReg dynParam3 = etsg->AllocReg(); - { - TargetTypeContext ttctx(etsg, nullptr); // without this ints will be cast to JSValue - etsg->LoadAccumulatorInt(expr, callNames->at(parts)); - etsg->StoreAccumulator(expr, dynParam2); - etsg->LoadAccumulatorInt(expr, parts.size()); - etsg->StoreAccumulator(expr, dynParam3); - } - etsg->CallDynamic(expr, calleeReg, dynParam2, dynParam3, expr->Signature(), expr->Arguments()); + if (!callInfo.name.empty()) { + auto [qnameStart, qnameLen] = LoadDynamicName(etsg, expr, callInfo.name, false); + etsg->CallDynamic(expr, calleeReg, qnameStart, qnameLen, expr->Signature(), expr->Arguments()); } else { + compiler::VReg dynParam2 = etsg->AllocReg(); + auto lang = expr->Callee()->TsType()->IsETSDynamicFunctionType() ? expr->Callee()->TsType()->AsETSDynamicFunctionType()->Language() : expr->Callee()->TsType()->AsETSDynamicType()->Language(); @@ -1051,7 +1011,7 @@ void ETSCompiler::Compile([[maybe_unused]] const ir::ImportExpression *expr) con UNREACHABLE(); } -static bool CompileComputed(compiler::ETSGen *etsg, const ir::MemberExpression *expr) +bool ETSCompiler::CompileComputed(compiler::ETSGen *etsg, const ir::MemberExpression *expr) { if (!expr->IsComputed()) { return false; diff --git a/ets2panda/compiler/core/ETSCompiler.h b/ets2panda/compiler/core/ETSCompiler.h index d7efc11593..b9c6b681c1 100644 --- a/ets2panda/compiler/core/ETSCompiler.h +++ b/ets2panda/compiler/core/ETSCompiler.h @@ -42,6 +42,8 @@ private: void EmitCall(const ir::CallExpression *expr, compiler::VReg &calleeReg, bool isStatic, checker::Signature *signature, bool isReference) const; + static bool CompileComputed(compiler::ETSGen *etsg, const ir::MemberExpression *expr); + ETSGen *GetETSGen() const; }; diff --git a/ets2panda/compiler/core/ETSemitter.h b/ets2panda/compiler/core/ETSemitter.h index e19f0bc2de..ac95a5116f 100644 --- a/ets2panda/compiler/core/ETSemitter.h +++ b/ets2panda/compiler/core/ETSemitter.h @@ -71,7 +71,7 @@ public: void GenAnnotation() override; private: - using DynamicCallNamesMap = ArenaMap, uint32_t>; + using DynamicCallNamesMap = ArenaMap, uint32_t>; void GenExternalRecord(varbinder::RecordTable *recordTable); void GenGlobalArrayRecord(checker::ETSArrayType *arrayType, checker::Signature *signature); diff --git a/ets2panda/test/CMakeLists.txt b/ets2panda/test/CMakeLists.txt index 9d5f7996e8..5e81e2016a 100644 --- a/ets2panda/test/CMakeLists.txt +++ b/ets2panda/test/CMakeLists.txt @@ -31,6 +31,31 @@ endif() add_custom_target(es2panda_tests COMMENT "Running es2panda test suites") +function(ets2panda_add_gtest TARGET) + # Parse arguments + cmake_parse_arguments( + ARG + "" + "" + "CPP_SOURCES" + ${ARGN} + ) + + MESSAGE(${TARGET}) + panda_add_gtest( + NAME ${TARGET} + SOURCES ${ARG_CPP_SOURCES} + LIBRARIES + es2panda-public es2panda-lib + INCLUDE_DIRS + ${ES2PANDA_PATH} + ${ES2PANDA_BINARY_ROOT} + SANITIZERS + ${PANDA_SANITIZERS_LIST} + ) +endfunction(ets2panda_add_gtest) + + if(PANDA_WITH_ETS) if (NOT (PANDA_ENABLE_ADDRESS_SANITIZER OR PANDA_ENABLE_THREAD_SANITIZER) OR NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR @@ -67,6 +92,10 @@ if(PANDA_REGRESSION_TESTS) add_dependencies(es2panda-plugin-test es2panda e2p_test_plugin) add_dependencies(es2panda_tests es2panda-plugin-test) + ets2panda_add_gtest(es2panda_dynamic_call_test + CPP_SOURCES unit/dynamic/dynamic_call_test.cpp + ) + panda_add_gtest( NAME es2panda_astverifier_tests SOURCES diff --git a/ets2panda/test/unit/dynamic/dynamic_call_test.cpp b/ets2panda/test/unit/dynamic/dynamic_call_test.cpp new file mode 100644 index 0000000000..e0cf5067c3 --- /dev/null +++ b/ets2panda/test/unit/dynamic/dynamic_call_test.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2021 - 2024 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 "checker/ets/dynamic/dynamicCall.h" +#include "checker/types/ets/etsDynamicType.h" +#include "test/unit/es2panda_unit_gtest.h" +#include "ir/expressions/callExpression.h" +#include "ir/expressions/memberExpression.h" +#include "ir/expressions/identifier.h" +#include "ir/ets/etsNewClassInstanceExpression.h" +#include "ir/ets/etsTypeReferencePart.h" +#include "ir/ets/etsTypeReference.h" +#include "ir/ts/tsQualifiedName.h" +#include "util/language.h" +#include "parser/ETSparser.h" + +namespace ark::es2panda::testing { + +class DynamicCall : public Es2pandaUnitGtest { +public: + std::pair ParseExpr(const std::string &strExpr) + { + auto program = + Allocator()->New(Allocator(), Allocator()->New(Allocator())); + program->VarBinder()->SetProgram(program); + program->VarBinder()->InitTopScope(); + auto etsParser = parser::ETSParser(program, CompilerOptions {}); + auto expr = + etsParser.CreateExpression(strExpr, parser::ExpressionParseFlags::NO_OPTS, parser::DEFAULT_SOURCE_FILE); + return {program, expr}; + } + + ir::Expression *MarkChainDynamic(ir::Expression *obj) + { + auto dynamicType = Allocator()->New(Allocator(), "test", "test", obj, + checker::ETSObjectFlags::NO_OPTS, nullptr, + Language::FromString("ets").value(), false); + if (obj->IsETSTypeReference()) { + obj = obj->AsETSTypeReference()->Part()->Name(); + } + while (obj != nullptr && (obj->IsMemberExpression() || obj->IsTSQualifiedName())) { + obj->SetTsType(dynamicType); + if (obj->IsMemberExpression()) { + obj = obj->AsMemberExpression()->Object(); + } else if (obj->IsTSQualifiedName()) { + obj = obj->AsTSQualifiedName()->Left(); + } + } + obj->SetTsType(dynamicType); + return obj; + } + + std::tuple ParseDynExpr(const std::string &strExpr) + { + auto [prog, expr] = ParseExpr(strExpr); + ir::Expression *obj = nullptr; + if (expr->IsCallExpression()) { + obj = expr->AsCallExpression()->Callee(); + } else { + obj = expr->AsETSNewClassInstanceExpression()->GetTypeRef()->AsETSTypeReference(); + } + auto first = MarkChainDynamic(obj); + return {prog, obj, first}; + } + + void AddDynImport(const char *specifierName, varbinder::ETSBinder *varbinder, ir::Identifier *node) + { + auto aIdent = Allocator()->New(specifierName, Allocator()); + auto specifier = Allocator()->New(aIdent, aIdent); + auto importSrc = Allocator()->New(Allocator()->New(), + Allocator()->New(), + Language::FromString("ets").value(), false); + auto importDecl = + Allocator()->New(importSrc, ArenaVector {Allocator()->Adapter()}); + varbinder->AddDynamicSpecifiersToTopBindings(specifier, importDecl); + auto var = varbinder->TopScope()->Find(specifierName); + node->SetVariable(var.variable); + } + + void AssertNameEq(const ArenaVector &name, std::initializer_list expected) + { + ASSERT_EQ(name.size(), expected.size()); + auto it1 = expected.begin(); + auto it2 = name.begin(); + while (it2 != name.end()) { + ASSERT_EQ(util::StringView(*it1), *it2); + it1++, it2++; + } + } +}; + +TEST_F(DynamicCall, JoinDynMemberChain) +{ + auto strExpr = "A.b.c.d()"; + auto [prog, obj, first] = ParseDynExpr(strExpr); + auto [squeezedObj, name] = checker::DynamicCall::SqueezeExpr(Allocator(), obj->AsMemberExpression()); + AssertNameEq(name, {"b", "c", "d"}); + ASSERT(squeezedObj->IsIdentifier()); + auto varbinder = prog->VarBinder()->AsETSBinder(); + { + // With empty varbinder A is local variable + auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj); + AssertNameEq(callName, {"b", "c", "d"}); + } + // Now A is import => we can optimize + AddDynImport("A", varbinder, first->AsIdentifier()); + auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj); + AssertNameEq(callName, {"A", "b", "c", "d"}); +} + +TEST_F(DynamicCall, JoinCompitedMemberChain) +{ + auto strExpr = "A.b.c[0].d.e.f()"; + auto [prog, obj, first] = ParseDynExpr(strExpr); + auto [squeezedObj, name] = checker::DynamicCall::SqueezeExpr(Allocator(), obj->AsMemberExpression()); + // Can't optimize [] + AssertNameEq(name, {"d", "e", "f"}); + ASSERT_EQ(squeezedObj, + obj->AsMemberExpression()->Object()->AsMemberExpression()->Object()->AsMemberExpression()->Object()); + auto varbinder = prog->VarBinder()->AsETSBinder(); + { + // Can't optimize [] + auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj); + AssertNameEq(callName, {"d", "e", "f"}); + } + // Can't optimize [] + AddDynImport("A", varbinder, first->AsIdentifier()); + auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj); + AssertNameEq(callName, {"d", "e", "f"}); +} + +TEST_F(DynamicCall, JoinDynCallMember) +{ + auto strExpr = "A.b().c.d()"; + auto [program, obj, first] = ParseDynExpr(strExpr); + auto [squeezedObj, name] = checker::DynamicCall::SqueezeExpr(Allocator(), obj->AsMemberExpression()); + AssertNameEq(name, {"c", "d"}); + ASSERT_EQ(squeezedObj, obj->AsMemberExpression()->Object()->AsMemberExpression()->Object()); + + auto varbinder = program->VarBinder()->AsETSBinder(); + auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj); + AssertNameEq(callName, {"c", "d"}); +} + +TEST_F(DynamicCall, JoinDynStaticCallMember) +{ + auto strExpr = "A.b.c.d.e()"; + auto [program, obj, first] = ParseDynExpr(strExpr); + + auto bObj = obj->AsMemberExpression()->Object()->AsMemberExpression()->Object(); + ASSERT_EQ(bObj->AsMemberExpression()->Property()->AsIdentifier()->Name(), "c"); + auto staticType = Allocator()->New(Allocator(), checker::ETSObjectFlags::NO_OPTS); + bObj->AsMemberExpression()->Object()->SetTsType(staticType); + + auto [squeezedObj, name] = checker::DynamicCall::SqueezeExpr(Allocator(), obj->AsMemberExpression()); + AssertNameEq(name, {"d", "e"}); + ASSERT_EQ(squeezedObj, bObj); + + auto varbinder = program->VarBinder()->AsETSBinder(); + AddDynImport("A", varbinder, first->AsIdentifier()); + auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj); + AssertNameEq(callName, {"d", "e"}); +} + +TEST_F(DynamicCall, TsQualifiedName) +{ + auto strExpr = "new A.b.c.d()"; + auto [program, obj, first] = ParseDynExpr(strExpr); + auto varbinder = program->VarBinder()->AsETSBinder(); + AddDynImport("A", varbinder, first->AsIdentifier()); + auto [finalObj, callName] = checker::DynamicCall::ResolveCall(varbinder, obj); + AssertNameEq(callName, {"A", "b", "c", "d"}); +} + +} // namespace ark::es2panda::testing \ No newline at end of file diff --git a/ets2panda/test/unit/es2panda_unit_gtest.h b/ets2panda/test/unit/es2panda_unit_gtest.h new file mode 100644 index 0000000000..9e0b858458 --- /dev/null +++ b/ets2panda/test/unit/es2panda_unit_gtest.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) Huawei Device Co., Ltd. 2023 - 2024. All rights reserved. + * 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. + */ + +#ifndef PANDA_ES2PANDA_UNIT_GTEST_H +#define PANDA_ES2PANDA_UNIT_GTEST_H + +#include +#include +#include +#include + +namespace ark::es2panda::testing { + +class Es2pandaUnitGtest : public ::testing::Test { +public: + Es2pandaUnitGtest() : allocator_(std::make_unique(SpaceType::SPACE_TYPE_COMPILER)) {} + + static void SetUpTestCase() + { + constexpr auto COMPILER_SIZE = operator""_MB(256ULL); + mem::MemConfig::Initialize(0, 0, COMPILER_SIZE, 0, 0, 0); + PoolManager::Initialize(); + } + + ArenaAllocator *Allocator() + { + return allocator_.get(); + } + +private: + std::unique_ptr allocator_; +}; + +} // namespace ark::es2panda::testing +#endif // PANDA_ES2PANDA_UNIT_GTEST_H -- Gitee