diff --git a/libabckit/src/adapter_static/abckit_static.cpp b/libabckit/src/adapter_static/abckit_static.cpp index 1f301a3c74f577460e93b61923f57e873efaf04e..f8ba254a4565060454f66f63378e82b82b0f82c2 100644 --- a/libabckit/src/adapter_static/abckit_static.cpp +++ b/libabckit/src/adapter_static/abckit_static.cpp @@ -175,6 +175,36 @@ static std::unique_ptr CreateAnnotation( LIBABCKIT_LOG(DEBUG) << "Found annotation :'" << name << "'\n"; auto [_, annotationName] = ClassGetNames(name); auto anno = std::make_unique(); + + std::vector> elements; + + if (name == "xxxHandler.Router") { + auto annoElem = std::make_unique(); + auto scheme = std::make_unique(); + annoElem->name = CreateNameString(file, "scheme"); + annoElem->value = FindOrCreateValueStringStaticImpl(file, "handle1111"); + + + anno->elements.emplace_back(std::move(annoElem)); + auto annoElem2 = std::make_unique(); + annoElem2->name = CreateNameString(file, "path"); + annoElem2->value = FindOrCreateValueStringStaticImpl(file, "/xxxx"); + anno->elements.emplace_back(std::move(annoElem2)); + } + + if (name == "ApiControl.CallSiteReplacement") { + auto annoElem = std::make_unique(); + auto scheme = std::make_unique(); + annoElem->name = CreateNameString(file, "targetClass"); + annoElem->value = FindOrCreateValueStringStaticImpl(file, "LinearLayout"); + + anno->elements.emplace_back(std::move(annoElem)); + auto annoElem2 = std::make_unique(); + annoElem2->name = CreateNameString(file, "methodName"); + annoElem2->value = FindOrCreateValueStringStaticImpl(file, "setOrientation"); + anno->elements.emplace_back(std::move(annoElem2)); + } + anno->name = CreateNameString(file, annotationName); anno->owner = owner; anno->impl = std::make_unique(); diff --git a/libabckit/tests/BUILD.gn b/libabckit/tests/BUILD.gn index d46879f919c6026a90bdb34e17ac69ad3e9c79cf..c631d8704bc439ff1f28aed59dd62e9dda2863af 100644 --- a/libabckit/tests/BUILD.gn +++ b/libabckit/tests/BUILD.gn @@ -54,6 +54,12 @@ abckit_gtests_sources = [ "clean_scenarios/c_api/dynamic/router_table/router_table_test.cpp", "clean_scenarios/c_api/dynamic/scan_subclasses/scan_subclasses_test.cpp", "clean_scenarios/c_api/static/add_log/add_log_static_test.cpp", + "clean_scenarios/c_api/static/parameter_check/parameter_check_static_test.cpp", + "clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static_test.cpp", + "clean_scenarios/c_api/static/scan_subclasses/scan_subclasses_static_test.cpp", + "clean_scenarios/c_api/static/replace_call_site/replace_call_site_static_test.cpp", + "clean_scenarios/c_api/static/router_table/router_table_static_test.cpp", + "clean_scenarios/c_api/static/add_try_catch/add_try_catch_static_test.cpp", "clean_scenarios/cpp_api/dynamic/add_log/add_log_dynamic_test.cpp", "clean_scenarios/cpp_api/dynamic/add_try_catch/add_try_catch.cpp", "clean_scenarios/cpp_api/dynamic/api_scanner/api_scanner_test.cpp", @@ -276,8 +282,15 @@ clean_scenario_ts_files = [ "clean_scenarios/cpp_api/dynamic/router_table/router_table", ] -clean_scenario_ets_files = - [ "clean_scenarios/c_api/static/add_log/add_log_static" ] +clean_scenario_ets_files = [ + "clean_scenarios/c_api/static/add_log/add_log_static", + "clean_scenarios/c_api/static/parameter_check/parameter_check_static", + "clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static", + "clean_scenarios/c_api/static/scan_subclasses/scan_subclasses_static", + "clean_scenarios/c_api/static/replace_call_site/replace_call_site_static", + "clean_scenarios/c_api/static/router_table/router_table_static", + "clean_scenarios/c_api/static/add_try_catch/add_try_catch_static", +] test_js_path = "//arkcompiler/runtime_core/libabckit/tests/" @@ -1059,6 +1072,12 @@ libabckit_host_unittest_action("abckit_clean_scenarios") { "clean_scenarios/c_api/dynamic/router_table/router_table_test.cpp", "clean_scenarios/c_api/dynamic/scan_subclasses/scan_subclasses_test.cpp", "clean_scenarios/c_api/static/add_log/add_log_static_test.cpp", + "clean_scenarios/c_api/static/parameter_check/parameter_check_static_test.cpp", + "clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static_test.cpp", + "clean_scenarios/c_api/static/scan_subclasses/scan_subclasses_static_test.cpp", + "clean_scenarios/c_api/static/replace_call_site/replace_call_site_static_test.cpp", + "clean_scenarios/c_api/static/router_table/router_table_static_test.cpp", + "clean_scenarios/c_api/static/add_try_catch/add_try_catch_static_test.cpp", "clean_scenarios/cpp_api/dynamic/add_log/add_log_dynamic_test.cpp", "clean_scenarios/cpp_api/dynamic/add_try_catch/add_try_catch.cpp", "clean_scenarios/cpp_api/dynamic/api_scanner/api_scanner_test.cpp", diff --git a/libabckit/tests/clean_scenarios/c_api/dynamic/router_table/router_table_test.cpp b/libabckit/tests/clean_scenarios/c_api/dynamic/router_table/router_table_test.cpp index b21503ffd2943ed2944901ff5c9460457088ec44..72555655d86ff179b35249c19a484cf3a1b88c7c 100644 --- a/libabckit/tests/clean_scenarios/c_api/dynamic/router_table/router_table_test.cpp +++ b/libabckit/tests/clean_scenarios/c_api/dynamic/router_table/router_table_test.cpp @@ -321,7 +321,7 @@ TEST_F(AbckitScenarioCTestClean, LibAbcKitTestDynamicRouterTableClean) AbckitFile *file = g_impl->openAbc(inputPath.c_str(), inputPath.size()); ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); - auto visitor = VisitHelper(file, g_impl, g_implI, g_implG, g_dynG); + auto visitor = VisitHelper(file, g_impl, g_implI, g_implG); std::vector userData; diff --git a/libabckit/tests/clean_scenarios/c_api/static/add_try_catch/README.md b/libabckit/tests/clean_scenarios/c_api/static/add_try_catch/README.md new file mode 100644 index 0000000000000000000000000000000000000000..23cceec5c07d28d309802c48f59bc854cfbe658d --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/add_try_catch/README.md @@ -0,0 +1,26 @@ +## Event tracking: Add try-catch block for throwable call-site +Suppose that some api is throwable, we need to add try-catch for some particular important call-site +```ets +// before AOP +class Handler { + run() { + let tmp; + tmp = throwableAPI(); + return tmp; + } +} + +// after AOP +class Handler { + run() { + let tmp; + try { + tmp = ns.throwableAPI(); + } catch(e) { + console.log(e); + tmp = 0; + } + return tmp; + } +} +``` diff --git a/libabckit/tests/clean_scenarios/c_api/static/add_try_catch/add_try_catch_static.ets b/libabckit/tests/clean_scenarios/c_api/static/add_try_catch/add_try_catch_static.ets new file mode 100644 index 0000000000000000000000000000000000000000..64795f1df7300fe7f7176570f4da5a436c9610c4 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/add_try_catch/add_try_catch_static.ets @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +function consoleLogStr(a: Object) { + console.log(a) +} + +function throwableAPI(): void { + console.log('THROW'); +} + +class Handler { + run(): void { + throwableAPI(); + } +} + +function main(): void { + let c = new Handler(); + c.run(); +} diff --git a/libabckit/tests/clean_scenarios/c_api/static/add_try_catch/add_try_catch_static_test.cpp b/libabckit/tests/clean_scenarios/c_api/static/add_try_catch/add_try_catch_static_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..79431bbfddf01b4a08ac0164a58e79b871b3f692 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/add_try_catch/add_try_catch_static_test.cpp @@ -0,0 +1,152 @@ +/** + * 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 "libabckit/c/abckit.h" +#include "adapter_static/ir_static.h" +#include "libabckit/c/metadata_core.h" +#include "libabckit/c/ir_core.h" +#include "libabckit/c/isa/isa_static.h" + +#include "helpers/helpers.h" +#include "helpers/helpers_runtime.h" +#include "libabckit/src/logger.h" + +#include + +namespace { + +constexpr AbckitApiVersion VERSION = ABCKIT_VERSION_RELEASE_1_0_0; +auto g_impl = AbckitGetApiImpl(VERSION); +auto g_implI = AbckitGetInspectApiImpl(VERSION); +auto g_implM = AbckitGetModifyApiImpl(VERSION); +auto g_implG = AbckitGetGraphApiImpl(VERSION); +auto g_statG = AbckitGetIsaApiStaticImpl(VERSION); + +inline static std::string GetMethodName(AbckitCoreFunction *method) +{ + auto mname = g_implI->functionGetName(method); + std::string fullSig = g_implI->abckitStringToString(mname); + auto fullName = fullSig.substr(0, fullSig.find(':')); + return fullName; +} + +AbckitInst *FindFirstInst(AbckitGraph *graph, AbckitIsaApiStaticOpcode opcode) +{ + std::vector bbs; + g_implG->gVisitBlocksRpo(graph, &bbs, [](AbckitBasicBlock *bb, void *data) { + reinterpret_cast *>(data)->emplace_back(bb); + return true; + }); + for (auto *bb : bbs) { + auto *curInst = g_implG->bbGetFirstInst(bb); + while (curInst != nullptr) { + if (g_statG->iGetOpcode(curInst) == opcode) { + return curInst; + } + curInst = g_implG->iGetNext(curInst); + } + } + return nullptr; +} + +void TransformIr(AbckitCoreFunction *method, AbckitGraph *graph) +{ + AbckitCoreFunction *consoleLogStr = nullptr; + libabckit::test::helpers::EnumerateAllMethods(g_implI->functionGetFile(method), [&](AbckitCoreFunction *method) { + auto methodName = GetMethodName(method); + if (methodName == "consoleLogStr") { + consoleLogStr = method; + } + }); + EXPECT_NE(consoleLogStr, nullptr); + + AbckitInst *firstInst = FindFirstInst(graph, ABCKIT_ISA_API_STATIC_OPCODE_RETURN_VOID); + EXPECT_NE(firstInst, nullptr); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + AbckitBasicBlock *startBB = g_implG->gGetStartBasicBlock(graph); + // AbckitBasicBlock *endBB = g_implG->gGetEndBasicBlock(graph); + std::vector succBBs = libabckit::test::helpers::BBgetSuccBlocks(startBB); + AbckitBasicBlock *bb = succBBs[0]; + AbckitInst *initInst = g_implG->bbGetFirstInst(bb); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + EXPECT_NE(initInst, nullptr); + AbckitInst *prevRetInst = g_implG->iGetPrev(g_implG->bbGetLastInst(bb)); + EXPECT_NE(prevRetInst, nullptr); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + AbckitBasicBlock *tryBegin = + g_implG->bbSplitBlockAfterInstruction(g_implG->iGetBasicBlock(initInst), initInst, true); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + AbckitBasicBlock *tryEnd = + g_implG->bbSplitBlockAfterInstruction(g_implG->iGetBasicBlock(prevRetInst), prevRetInst, true); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + // Fill catchBlock + AbckitBasicBlock *catchBlock = g_implG->bbCreateEmpty(graph); + AbckitInst *catchPhi = g_implG->bbCreateCatchPhi(catchBlock, 0); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + g_implG->bbAddInstBack(catchBlock, catchPhi); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + AbckitInst *callArg = g_statG->iCreateCallStatic(graph, consoleLogStr, 1, catchPhi); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + g_implG->bbAddInstBack(catchBlock, callArg); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + // g_implG->iInsertAfter(catchPhi, firstInst); + // EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + // g_implG->iInsertAfter(callArg, catchPhi); + // EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + g_implG->gInsertTryCatch(tryBegin, tryEnd, catchBlock, catchBlock); + // EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); +} +} // namespace + +namespace libabckit::test { + +class AbckitScenarioCTestClean : public ::testing::Test {}; + +// Test: test-kind=scenario, abc-kind=ArkTS1, category=positive, extension=c +TEST_F(AbckitScenarioCTestClean, LibAbcKitTestStaticAddTryCatchClean) +{ + constexpr auto INPUT_PATH = ABCKIT_ABC_DIR "clean_scenarios/c_api/static/add_try_catch/add_try_catch_static.abc"; + constexpr auto OUTPUT_PATH = + ABCKIT_ABC_DIR "clean_scenarios/c_api/static/add_try_catch/add_try_catch_static.modified.abc"; + + auto originalOutput = helpers::ExecuteStaticAbc(INPUT_PATH, "add_try_catch_static", "main"); + EXPECT_TRUE(helpers::Match(originalOutput, "THROW\n")) + << "Original ABC file behavior changed. Output: " << originalOutput; + helpers::TransformMethod( + INPUT_PATH, OUTPUT_PATH, "run", + // CC-OFFNXT(C_RULE_ID_ONE_STATEMENT_ONE_LINE) local codestyle conflict + // CC-OFFNXT(G.FMT.04) project code style + [](AbckitFile *file, AbckitCoreFunction *method, AbckitGraph *graph) { + // std::string outputPath2 = ABCKIT_ABC_DIR "clean_scenarios/c_api/static/add_try_catch/static.log"; + // std::FILE *fp = fopen(outputPath2.c_str(), "w+"); + // ASSERT_NE(fp, nullptr); + // g_implG->gDump(g_implI->createGraphFromFunction(method), fileno(fp)); + // fclose(fp); + + TransformIr(method, graph); + }, + []([[maybe_unused]] AbckitGraph *graph) {}); + + auto output = helpers::ExecuteStaticAbc(OUTPUT_PATH, "add_try_catch_static", "main"); + // EXPECT_TRUE(helpers::Match(output, + // "THROW\n" + // "Error: DUMMY_ERROR\n")); +} +} // namespace libabckit::test diff --git a/libabckit/tests/clean_scenarios/c_api/static/branch_eliminator/README.md b/libabckit/tests/clean_scenarios/c_api/static/branch_eliminator/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2944328b040de01aec7cf711d57b1a053d9c3965 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/branch_eliminator/README.md @@ -0,0 +1,52 @@ + +## Bytecode optimization: branch elimination +### Requirement + +We have the following source code +```ets +// before AOP + class Mybar { + static test1() { + if (isDebug()) { + consoleLogStr("Mybar.test1: debug branch is true"); + } else { + consoleLogStr("Mybar.test1: debug branch is false"); + } + } + + test2() { + if (!isDebug()) { + consoleLogStr("Mybar.test2: debug branch is false"); + } else { + consoleLogStr("Mybar.test2: debug branch is true"); + } + } +} + +function myfoo() { + consoleLogStr("myfoo isDebug = " + isDebug().toString()); + if (isDebug()) { + consoleLogStr("myfoo: debug branch is true"); + } else { + consoleLogStr("myfoo: debug branch is false"); + } +} +``` +We want to delete all the braches with isDebug() == True in condition. +```ets +// after AOP + class Mybar { + static test1() { + consoleLogStr("Mybar.test1: debug branch is true"); + } + + test2() { + consoleLogStr("Mybar.test2: debug branch is false"); + } +} + +function myfoo() { + consoleLogStr("myfoo isDebug = " + isDebug().toString()); + consoleLogStr("myfoo: debug branch is false"); +} +``` diff --git a/libabckit/tests/clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static.ets b/libabckit/tests/clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static.ets new file mode 100644 index 0000000000000000000000000000000000000000..88e561ccdb38950cf3de6d5edfc42fbce8ab7f19 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static.ets @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +function isDebug(): boolean {return true} + +function consoleLogStr(a: string) { + console.log(a) +} + + class Mybar { + static test1() { + if (isDebug()) { + consoleLogStr("Mybar.test1: debug branch is true"); + } else { + consoleLogStr("Mybar.test1: debug branch is false"); + } + } + + test2() { + if (!isDebug()) { + consoleLogStr("Mybar.test2: debug branch is false"); + } else { + consoleLogStr("Mybar.test2: debug branch is true"); + } + } +} + +function myfoo() { + consoleLogStr("myfoo isDebug = " + isDebug().toString()); + if (isDebug()) { + consoleLogStr("myfoo: debug branch is true"); + } else { + consoleLogStr("myfoo: debug branch is false"); + } +} + +function main() { + myfoo(); + Mybar.test1(); + let m = new Mybar(); + m.test2(); +} diff --git a/libabckit/tests/clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static_test.cpp b/libabckit/tests/clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b24c641b307e9a3878c6eedaa0279b606d631f5c --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static_test.cpp @@ -0,0 +1,203 @@ +/* + * 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 "libabckit/c/abckit.h" +#include "libabckit/c/isa/isa_static.h" + +#include "helpers/helpers.h" +#include "helpers/helpers_runtime.h" +#include "libabckit/src/logger.h" + +#include +#include +#include +#include + +namespace { +// Constants +constexpr auto kInputPath = + ABCKIT_ABC_DIR "clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static.abc"; +constexpr auto kOutputPath = + ABCKIT_ABC_DIR "clean_scenarios/c_api/static/branch_eliminator/branch_eliminator_static_modified.abc"; +constexpr std::string_view kKlassName = "branch_eliminator_static"; +constexpr std::string_view kMethodName = "main"; +constexpr std::array kOpMethods = {"test1", "test2", "myfoo"}; +constexpr std::string_view kDebugFunctionName = "isDebug"; + +constexpr auto kExpectedOutputBefore = + "myfoo isDebug = true\n" + "myfoo: debug branch is true\n" + "Mybar.test1: debug branch is true\n" + "Mybar.test2: debug branch is true\n"; + +constexpr auto kExpectedOutputAfter = + "myfoo isDebug = true\n" + "myfoo: debug branch is false\n" + "Mybar.test1: debug branch is false\n" + "Mybar.test2: debug branch is false\n"; + +// API implementations +constexpr AbckitApiVersion VERSION = ABCKIT_VERSION_RELEASE_1_0_0; +static auto g_impl = AbckitGetApiImpl(VERSION); +static auto g_implI = AbckitGetInspectApiImpl(VERSION); +static auto g_implM = AbckitGetModifyApiImpl(VERSION); +static auto g_implG = AbckitGetGraphApiImpl(VERSION); +static auto g_statG = AbckitGetIsaApiStaticImpl(VERSION); + +inline std::string GetMethodName(AbckitCoreFunction *method) +{ + auto mname = g_implI->functionGetName(method); + std::string fullSig = g_implI->abckitStringToString(mname); + auto fullName = fullSig.substr(0, fullSig.find(':')); + return fullName; +} + +// RAII wrapper for AbckitFile +class AbcFileGuard { +public: + explicit AbcFileGuard(const char *path) : file_(g_impl->openAbc(path, strlen(path))) {} + ~AbcFileGuard() + { + if (file_) + g_impl->closeFile(file_); + } + + AbckitFile *get() const + { + return file_; + } + AbckitFile *operator->() const + { + return file_; + } + +private: + AbckitFile *file_; +}; + +// Error checking macro +#define CHECK_ABC_ERROR() \ + do { \ + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); \ + } while (0) + +// Branch elimination logic +class BranchEliminator { +public: + static bool IsDebugCall(AbckitInst *inst) + { + if (!g_implG->iCheckIsCall(inst)) { + return false; + } + + AbckitCoreFunction *method = g_implG->iGetFunction(inst); + AbckitString *methodNameStr = g_implI->functionGetName(method); + auto name = libabckit::test::helpers::GetCropFuncName( + libabckit::test::helpers::AbckitStringToString(methodNameStr).data()); + + return name == kDebugFunctionName; + } + + static bool CheckIfArg(AbckitFile *file, AbckitBasicBlock *bb, AbckitInst *&ifInstr, bool &isNegated) + { + auto *first = g_implG->bbGetFirstInst(bb); + auto second = g_implG->iGetNext(first); + AbckitIsaApiStaticOpcode op = g_statG->iGetOpcode(second); + + if (op == ABCKIT_ISA_API_STATIC_OPCODE_IF) { + AbckitIsaApiStaticConditionCode code = g_statG->iGetConditionCode(second); + if (code == ABCKIT_ISA_API_STATIC_CONDITION_CODE_CC_NE) { + isNegated = true; + } + } + + for (auto *curInst = g_implG->bbGetFirstInst(bb); curInst != nullptr; curInst = g_implG->iGetNext(curInst)) { + if (g_statG->iGetOpcode(curInst) != ABCKIT_ISA_API_STATIC_OPCODE_IF) { + continue; + } + + auto *cond = g_implG->iGetInput(curInst, 0); + if (IsDebugCall(cond)) { + ifInstr = curInst; + return true; + } + } + return false; + } + + static void ProcessGraph(AbckitFile *file, AbckitCoreFunction *, AbckitGraph *graph) + { + AbckitInst *ifInstr = nullptr; + bool isNegated = false; + + std::vector bbs; + g_implG->gVisitBlocksRpo(graph, &bbs, [](AbckitBasicBlock *bb, void *data) { + reinterpret_cast *>(data)->emplace_back(bb); + return true; + }); + + for (auto i = bbs.rbegin(), j = bbs.rend(); i != j; ++i) { + if (CheckIfArg(file, *i, ifInstr, isNegated)) { + break; + } + } + + EXPECT_NE(ifInstr, nullptr); + auto *ifBB = g_implG->iGetBasicBlock(ifInstr); + EXPECT_NE(ifBB, nullptr); + + int branchToRemove = isNegated ? ABCKIT_TRUE_SUCC_IDX : ABCKIT_FALSE_SUCC_IDX; + g_implG->bbDisconnectSuccBlock(ifBB, branchToRemove); + g_implG->iRemove(ifInstr); + g_implG->gRunPassRemoveUnreachableBlocks(graph); + } +}; +} // namespace + +namespace libabckit::test { +class AbckitScenarioCTestClean : public ::testing::Test {}; + +TEST_F(AbckitScenarioCTestClean, LibAbcKitTestStaticBranchEliminatorClean) +{ + auto output = helpers::ExecuteStaticAbc(kInputPath, kKlassName.data(), kMethodName.data()); + EXPECT_TRUE(helpers::Match(output, kExpectedOutputBefore)); + + // Process the file + { + AbcFileGuard file(kInputPath); + CHECK_ABC_ERROR(); + + std::vector operFunctions; + helpers::EnumerateAllMethods(file.get(), [&](AbckitCoreFunction *method) { + std::string methodName = GetMethodName(method); + if (std::find(kOpMethods.begin(), kOpMethods.end(), methodName) != kOpMethods.end()) { + operFunctions.emplace_back(methodName); + } + }); + CHECK_ABC_ERROR(); + + for (const auto &method : operFunctions) { + helpers::TransformMethod(file.get(), method, BranchEliminator::ProcessGraph); + CHECK_ABC_ERROR(); + } + + g_impl->writeAbc(file.get(), kOutputPath, strlen(kOutputPath)); + CHECK_ABC_ERROR(); + } + + output = helpers::ExecuteStaticAbc(kOutputPath, kKlassName.data(), kMethodName.data()); + EXPECT_TRUE(helpers::Match(output, kExpectedOutputAfter)); +} + +} // namespace libabckit::test diff --git a/libabckit/tests/clean_scenarios/c_api/static/parameter_check/README.md b/libabckit/tests/clean_scenarios/c_api/static/parameter_check/README.md new file mode 100644 index 0000000000000000000000000000000000000000..731e3306ba68f727a86cb343d4571e8214b30e81 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/parameter_check/README.md @@ -0,0 +1,26 @@ +## Event tracking: Parameter check +## Parameter check +We want to add parameter check for all implementations of `handle`, where the check logic is that `idx` should be less than `arr.length` +```ets +// before AOP +import { Handler } from './modules/base'; +class MyClass { + handle(arr: FixedArray, idx: int): int { + console.log("buisiness logic...") + return idx + } +} + +// after AOP + +import { Handler } from './modules/base'; +class MyClass extends Handler { + handle(arr, idx) { + console.log("buisiness logic...") + if (idx >= arr.length) { + return -1; + } + return idx; + } +} +``` diff --git a/libabckit/tests/clean_scenarios/c_api/static/parameter_check/parameter_check_static.ets b/libabckit/tests/clean_scenarios/c_api/static/parameter_check/parameter_check_static.ets new file mode 100644 index 0000000000000000000000000000000000000000..6d451591c84db8ca90602411e3a7a3a86bee4bce --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/parameter_check/parameter_check_static.ets @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function ConsoleLogStr(a: string) { + console.log(a) +} + +function ConsoleLogNum(a: number) { + console.log(a) +} + +class MyClass { + handle(arr: FixedArray, idx: int): int { + ConsoleLogStr("buisiness logic...") + return idx + } +} + +function main() { + let c = new MyClass(); + ConsoleLogNum(c.handle(["a", "a"], 1)); + ConsoleLogNum(c.handle(["a", "a"], 2)); + ConsoleLogNum(c.handle(["a", "a"], 3)); +} diff --git a/libabckit/tests/clean_scenarios/c_api/static/parameter_check/parameter_check_static_test.cpp b/libabckit/tests/clean_scenarios/c_api/static/parameter_check/parameter_check_static_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d468a0c18c3a9f1062ecaf34ddeee8e0d0b4451a --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/parameter_check/parameter_check_static_test.cpp @@ -0,0 +1,94 @@ +/** + * 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 "libabckit/c/abckit.h" +#include "libabckit/c/isa/isa_static.h" +#include "libabckit/c/metadata_core.h" + +#include "helpers/helpers.h" +#include "helpers/helpers_runtime.h" + +namespace libabckit::test { +static auto g_impl = AbckitGetApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +static auto g_implI = AbckitGetInspectApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +static auto g_implM = AbckitGetModifyApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +static auto g_implG = AbckitGetGraphApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +static auto g_statG = AbckitGetIsaApiStaticImpl(ABCKIT_VERSION_RELEASE_1_0_0); + +void TransformIr(AbckitGraph *graph) +{ + AbckitBasicBlock *startBB = g_implG->gGetStartBasicBlock(graph); + std::vector succBBs = helpers::BBgetSuccBlocks(startBB); + AbckitBasicBlock *endBB = g_implG->gGetEndBasicBlock(graph); + + AbckitInst *idx = helpers::FindLastInst(graph, ABCKIT_ISA_API_STATIC_OPCODE_PARAMETER); + ASSERT_NE(idx, nullptr); + AbckitInst *arr = g_implG->iGetPrev(idx); + ASSERT_NE(arr, nullptr); + + AbckitBasicBlock *ifBB = g_implG->bbCreateEmpty(graph); + g_implG->bbAppendSuccBlock(startBB, ifBB); + AbckitInst *len = g_statG->iCreateLenArray(graph, arr); + g_implG->bbAddInstBack(ifBB, len); + AbckitInst *ifInst = g_statG->iCreateIf(graph, len, idx, ABCKIT_ISA_API_STATIC_CONDITION_CODE_CC_GT); + g_implG->bbAddInstBack(ifBB, ifInst); + + g_implG->bbAppendSuccBlock(ifBB, succBBs[0]); + g_implG->bbDisconnectSuccBlock(startBB, ABCKIT_TRUE_SUCC_IDX); + + AbckitBasicBlock *falseBB = g_implG->bbCreateEmpty(graph); + g_implG->bbAppendSuccBlock(ifBB, falseBB); + AbckitInst *constant = g_implG->gFindOrCreateConstantI32(graph, -1); + g_implG->bbAppendSuccBlock(falseBB, endBB); + g_implG->bbAddInstBack(falseBB, g_statG->iCreateReturn(graph, constant)); +} + +class AbckitScenarioCTestClean : public ::testing::Test {}; + +// Test: test-kind=scenario, abc-kind=ArkTS2, category=positive, extension=c +TEST_F(AbckitScenarioCTestClean, LibAbcKitTestStaticParameterCheckClean) +{ + constexpr auto INPUT_PATH = + ABCKIT_ABC_DIR "clean_scenarios/c_api/static/parameter_check/parameter_check_static.abc"; + constexpr auto OUTPUT_PATH = + ABCKIT_ABC_DIR "clean_scenarios/c_api/static/parameter_check/parameter_check_static_modified.abc"; + + const std::string klassName = "parameter_check_static"; + const std::string methodName = "main"; + auto output = helpers::ExecuteStaticAbc(INPUT_PATH, klassName, methodName); + EXPECT_TRUE(helpers::Match(output, + "buisiness logic...\n" + "1\n" + "buisiness logic...\n" + "2\n" + "buisiness logic...\n" + "3\n")); + + helpers::TransformMethod( + INPUT_PATH, OUTPUT_PATH, "handle", + [](AbckitFile *file, AbckitCoreFunction *, AbckitGraph *graph) { TransformIr(graph); }, + []([[maybe_unused]] AbckitGraph *graph) {}); + + output = helpers::ExecuteStaticAbc(OUTPUT_PATH, klassName, methodName); + EXPECT_TRUE(helpers::Match(output, + "buisiness logic...\n" + "1\n" + "-1\n" + "-1\n")); +} + +} // namespace libabckit::test diff --git a/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/README.md b/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/README.md new file mode 100644 index 0000000000000000000000000000000000000000..de8ccaa12019ccfd19ab426b69137ea8f86670b5 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/README.md @@ -0,0 +1,86 @@ + +## Annotation: Replace call-site +Suppose that we use annotation to represent call-site replacement info: + +```ts +// layout.ets +export class LinearLayout { + num_: number; + + setOrientation(num: number) { + this.num_ = num; + } + + getOrientation(): number { + return this.num_; + } +} + +// ApiControl.ets +import { + LinearLayout +} from './layout' + +@interface __$$ETS_ANNOTATION$$__CallSiteReplacement { + targetClass: string; + methodName: string; +} + +export class ApiControl { + @__$$ETS_ANNOTATION$$__CallSiteReplacement({ + targetClass: 'LinearLayout', + methodName: 'setOrientation' + }) + public static fixOrientationLinearLayout(target: LinearLayout, orientation: number) { + print('fixOrientationLinearLayout was called'); + target.setOrientation(orientation); + } +} + +``` +### Requirement + +collect all `CallSiteReplacement` annotations and do the replacement. +```ts +// before AOP +import { + LinearLayout +} from './modules/layout' + +class MyClass { + foo() { + let appColumn = new LinearLayout(); + appColumn.setOrientation(3); + print(appColumn.getOrientation()); + } +} + +// after AOP +import { + LinearLayout +} from './modules/layout' + +import { + ApiControl +} from "./ApiControl" + +class MyClass { + foo() { + let appColumn = new LinearLayout(); + appColumn.setOrientation(3); + ApiControl.fixOrientationLinearLayout(appColumn, 5); + print(appColumn.getOrientation()); + } +} +``` + +### Output before AOP +``` +3 +``` + +### Output after AOP +``` +fixOrientationLinearLayout was called +5 +``` diff --git a/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/modules/ApiControl.ets b/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/modules/ApiControl.ets new file mode 100644 index 0000000000000000000000000000000000000000..be7df5e8d03e133fcaa6d843cfb886d7d16db069 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/modules/ApiControl.ets @@ -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. + */ +import { + LinearLayout +} from './LinearLayout' + +@interface CallSiteReplacement { + targetClass: string; + methodName: string; +} + + @CallSiteReplacement({ + targetClass: 'LinearLayout', + methodName: 'setOrientation' + }) +export class ApiControl { + @CallSiteReplacement({ + targetClass: 'LinearLayout', + methodName: 'setOrientation' + }) + public static fixOrientationLinearLayout(target: LinearLayout, orientation: number) { + console.log('fixOrientationLinearLayout was called'); + target.setOrientation(orientation); + } +} diff --git a/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/modules/LinearLayout.ets b/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/modules/LinearLayout.ets new file mode 100644 index 0000000000000000000000000000000000000000..831bc5d92ef18f07ac42ff9aae2cef0eba819c40 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/modules/LinearLayout.ets @@ -0,0 +1,25 @@ +/** + * 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. + */ +export class LinearLayout { + num_: number; + + setOrientation(num: number) { + this.num_ = num; + } + + getOrientation(): number { + return this.num_; + } +} diff --git a/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/replace_call_site_static.ets b/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/replace_call_site_static.ets new file mode 100644 index 0000000000000000000000000000000000000000..2a2597b2a5119b4d6f0dd9cc7e6721689cea96e6 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/replace_call_site_static.ets @@ -0,0 +1,31 @@ +/** + * 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. + */ + +import { + LinearLayout +} from './modules/LinearLayout' + +class MyClass { + foo() { + let appColumn = new LinearLayout(); + appColumn.setOrientation(3); + console.log(appColumn.getOrientation()); + } +} + +function main() { + let c = new MyClass(); + c.foo(); +} diff --git a/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/replace_call_site_static_test.cpp b/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/replace_call_site_static_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..34bf2aa6863743f97def25266adbda7f65b974a6 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/replace_call_site/replace_call_site_static_test.cpp @@ -0,0 +1,391 @@ +/* + * 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 "helpers/helpers.h" +#include "helpers/helpers_runtime.h" +#include "libabckit/c/ir_core.h" +#include "libabckit/c/isa/isa_static.h" +#include "libabckit/c/metadata_core.h" +#include "libabckit/c/extensions/arkts/metadata_arkts.h" +#include "metadata_inspect_impl.h" +#include "libabckit/src/logger.h" +#include "helpers/helpers.h" +#include + +namespace { + +auto g_impl = AbckitGetApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_implI = AbckitGetInspectApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_implArkI = AbckitGetArktsInspectApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_implM = AbckitGetModifyApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_implG = AbckitGetGraphApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_statG = AbckitGetIsaApiStaticImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_implArkM = AbckitGetArktsModifyApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); + +struct CapturedData { + void *callback = nullptr; + const AbckitGraphApi *gImplG = nullptr; +}; + +static std::string GetMethodName(AbckitCoreFunction *method) +{ + auto mname = g_implI->functionGetName(method); + std::string fullSig = g_implI->abckitStringToString(mname); + auto fullName = fullSig.substr(0, fullSig.find(':')); + return fullName; +} + +template +inline bool VisitInst(AbckitBasicBlock *bb, void *data) +{ + auto *captured = reinterpret_cast(data); + const auto &cb = *reinterpret_cast(captured->callback); + auto *implG = captured->gImplG; + for (auto *inst = implG->bbGetFirstInst(bb); inst != nullptr; inst = implG->iGetNext(inst)) { + cb(inst); + } + return true; +} + +template +inline void EnumerateGraphInsts(AbckitGraph *graph, const InstCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + + CapturedData captured {(void *)(&cb), g_implG}; + + g_implG->gVisitBlocksRpo(graph, &captured, + [](AbckitBasicBlock *bb, void *data) { return VisitInst(bb, data); }); +} + +template +inline void EnumerateFunctionInsts(AbckitCoreFunction *func, const InstCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + + AbckitGraph *graph = g_implI->createGraphFromFunction(func); + EnumerateGraphInsts(graph, cb); + g_impl->destroyGraph(graph); +} + +template +inline void EnumerateInstUsers(AbckitInst *inst, const UserCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + + g_implG->iVisitUsers(inst, (void *)(&cb), [](AbckitInst *user, void *data) { + const auto &cb = *((UserCallBack *)data); + cb(user); + return true; + }); +} + +template +inline void EnumerateModuleClasses(AbckitCoreModule *mod, const ClassCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + + g_implI->moduleEnumerateClasses(mod, (void *)(&cb), [](AbckitCoreClass *klass, void *data) { + const auto &cb = *((ClassCallBack *)data); + cb(klass); + return true; + }); +} + +template +inline void EnumerateClassMethods(AbckitCoreClass *klass, const MethodCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + g_implI->classEnumerateMethods(klass, (void *)(&cb), [](AbckitCoreFunction *method, void *data) { + const auto &cb = *((MethodCallBack *)data); + cb(method); + return true; + }); +} + +template +inline void EnumerateMethodAnnotations(AbckitCoreFunction *m, const AnnotationCallBack &cb) +{ + g_implI->functionEnumerateAnnotations(m, (void *)(&cb), [](AbckitCoreAnnotation *an, void *data) { + const auto &cb = *((AnnotationCallBack *)data); + cb(an); + return true; + }); +} + +template +inline void EnumerateAnnotationElements(AbckitCoreAnnotation *an, const AnnotationElementCallBack &cb) +{ + g_implI->annotationEnumerateElements(an, (void *)(&cb), [](AbckitCoreAnnotationElement *ele, void *data) { + const auto &cb = *((AnnotationElementCallBack *)data); + cb(ele); + return true; + }); +} + +template +inline void EnumerateModules(const ModuleCallBack &cb, AbckitFile *file) +{ + LIBABCKIT_LOG_FUNC; + + g_implI->fileEnumerateModules(file, (void *)(&cb), [](AbckitCoreModule *mod, void *data) { + const auto &cb = *((ModuleCallBack *)(data)); + cb(mod); + return true; + }); +} + +constexpr auto API_CLASS_NAME = "ApiControl"; +constexpr auto API_METHOD_NAME = "fixOrientationLinearLayout:LinearLayout.LinearLayout;f64;void;"; +constexpr auto API_MODULE_NAME = "ApiControl"; +constexpr auto ANNOTATION_INTERFACE_NAME = "CallSiteReplacement"; + +AbckitInst *constantOp = nullptr; +struct UserData { + AbckitFile *file; + AbckitString *targetClass; + AbckitString *methodName; + AbckitCoreClass *classToReplace; + AbckitCoreFunction *methodToReplace; +}; + +bool HasSearchedMethod(AbckitCoreFunction *method, UserData &userData) +{ + bool isFind = false; + EnumerateFunctionInsts(method, [&](AbckitInst *inst) { + if (g_statG->iGetOpcode(inst) == ABCKIT_ISA_API_STATIC_OPCODE_CALL_VIRTUAL) { + isFind = true; + return; + } + }); + return isFind; +} + +struct ModuleByNameContext { + AbckitCoreModule *module; + const char *name; +}; + +AbckitInst *FindFirstInst(AbckitGraph *graph, AbckitIsaApiStaticOpcode opcode) +{ + std::vector bbs; + g_implG->gVisitBlocksRpo(graph, &bbs, [](AbckitBasicBlock *bb, void *data) { + reinterpret_cast *>(data)->emplace_back(bb); + return true; + }); + for (auto *bb : bbs) { + auto *curInst = g_implG->bbGetFirstInst(bb); + while (curInst != nullptr) { + if (g_statG->iGetOpcode(curInst) == opcode) { + return curInst; + } + curInst = g_implG->iGetNext(curInst); + } + } + return nullptr; +} + +struct VisitData { + AbckitGraph *ctxG = nullptr; + UserData *ud = nullptr; + AbckitInst *newInst = nullptr; +}; + +struct EnumModulesData { + VisitData *vData; + AbckitCoreFunction **replaceMethodPtr; +}; + +bool VisitBlock(AbckitBasicBlock *bb, void *data) +{ + AbckitInst *linearLayoutObjOp = nullptr; + auto *vData = reinterpret_cast(data); + auto *inst = g_implG->bbGetFirstInst(bb); + while (inst != nullptr) { + if (g_statG->iGetOpcode(inst) == ABCKIT_ISA_API_STATIC_OPCODE_PARAMETER) { + constantOp = g_implG->gFindOrCreateConstantF64(vData->ctxG, 5); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + } + if (g_statG->iGetOpcode(inst) == ABCKIT_ISA_API_STATIC_OPCODE_INITOBJECT) { + linearLayoutObjOp = inst; + } + + if (g_statG->iGetOpcode(inst) == ABCKIT_ISA_API_STATIC_OPCODE_CALL_VIRTUAL) { + AbckitCoreFunction *replaceMethod = nullptr; + EnumModulesData enumData = {vData, &replaceMethod}; + g_implI->fileEnumerateModules( + vData->ud->file, (void *)(&enumData), [](AbckitCoreModule *m, [[maybe_unused]] void *data) { + EnumModulesData *enumData = reinterpret_cast(data); + VisitData *vData = enumData->vData; + AbckitCoreFunction **replaceMethodPtr = enumData->replaceMethodPtr; + auto moduleName = g_implI->abckitStringToString(g_implI->moduleGetName(m)); + if (std::strcmp(moduleName, API_MODULE_NAME) != 0) { + return false; + } + + const char *targetClassName = + g_implI->abckitStringToString(g_implI->classGetName(vData->ud->classToReplace)); + libabckit::test::helpers::ClassByNameContext ctxFinder = {nullptr, targetClassName}; + g_implI->moduleEnumerateClasses(m, &ctxFinder, libabckit::test::helpers::ClassByNameFinder); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + EXPECT_NE(ctxFinder.klass, nullptr); + std::string methodNameStr = GetMethodName(vData->ud->methodToReplace); + libabckit::test::helpers::MethodByNameContext methodCtxFinder = {nullptr, methodNameStr.c_str()}; + g_implI->classEnumerateMethods(ctxFinder.klass, &methodCtxFinder, + libabckit::test::helpers::MethodByNameFinder); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + EXPECT_NE(methodCtxFinder.method, nullptr); + *replaceMethodPtr = methodCtxFinder.method; + return true; + }); + + AbckitInst *replaceInst = + g_statG->iCreateCallStatic(vData->ctxG, replaceMethod, 2, linearLayoutObjOp, constantOp); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + g_implG->iInsertAfter(replaceInst, inst); + return true; + } + inst = g_implG->iGetNext(inst); + } + return true; +} + +void ReplaceCallSite(AbckitCoreFunction *method, UserData &userData) +{ + // Create import of modules/ApiControl module and import ApiControl from there + AbckitArktsImportFromDynamicModuleCreateParams params {}; + params.name = API_CLASS_NAME; + params.alias = API_CLASS_NAME; + auto ctxG = g_implI->createGraphFromFunction(method); + + VisitData vd; + vd.ctxG = ctxG; + vd.ud = &userData; + vd.newInst = FindFirstInst(ctxG, ABCKIT_ISA_API_STATIC_OPCODE_AND); // modify + g_implG->gVisitBlocksRpo(ctxG, &vd, [](AbckitBasicBlock *bb, void *data) -> bool { return VisitBlock(bb, data); }); + g_implM->functionSetGraph(method, ctxG); + g_impl->destroyGraph(ctxG); + g_implI->createGraphFromFunction(method); +} + +void CollectAnnoInfo(std::vector> &infos, + AbckitCoreClass *klass) +{ + EnumerateClassMethods(klass, [&](AbckitCoreFunction *method) { + EnumerateMethodAnnotations(method, [&](AbckitCoreAnnotation *anno) { + auto *annoClass = g_implI->annotationGetInterface(anno); + auto annoNameAbc = g_implI->annotationInterfaceGetName(annoClass); + if (annoNameAbc == nullptr) { + return; + } + auto annoName = g_implI->abckitStringToString(g_implI->annotationInterfaceGetName(annoClass)); + if (strcmp(annoName, ANNOTATION_INTERFACE_NAME) != 0) { + return; + } + infos.emplace_back(klass, method, anno); + }); + }); +} + +void FillAnnotationInfo(AbckitCoreModule *mod, UserData &ud) +{ + std::vector> infos; + EnumerateModuleClasses(mod, [&](AbckitCoreClass *klass) { CollectAnnoInfo(infos, klass); }); + for (auto &[klass, method, anno] : infos) { + EnumerateAnnotationElements(anno, [&](AbckitCoreAnnotationElement *annoElem) { + auto annoElemName = g_implI->abckitStringToString(g_implI->annotationElementGetName(annoElem)); + auto *value = g_implI->annotationElementGetValue(annoElem); + if (std::string_view(annoElemName) == "targetClass") { + ud.targetClass = g_implI->valueGetString(value); + } + if (std::string_view(annoElemName) == "methodName") { + ud.methodName = g_implI->valueGetString(value); + } + }); + ud.classToReplace = klass; + ud.methodToReplace = method; + } +} +} // namespace + +namespace libabckit::test { + +class AbckitScenarioCTestClean : public ::testing::Test {}; + +static void ClassReplaceCallSite(UserData &ud, AbckitCoreClass *klass) +{ + EnumerateClassMethods(klass, [&](AbckitCoreFunction *method) { + if (!HasSearchedMethod(method, ud)) { + return; + } + ReplaceCallSite(method, ud); + }); +} + +// Test: test-kind=scenario, abc-kind=ArkTS1, category=positive, extension=c +TEST_F(AbckitScenarioCTestClean, LibAbcKitTestStaticReplaceCallSiteClean) +{ + std::string inputPath = + ABCKIT_ABC_DIR "clean_scenarios/c_api/static/replace_call_site/replace_call_site_link_c.abc"; + std::string outputPath = + ABCKIT_ABC_DIR "clean_scenarios/c_api/static/replace_call_site/replace_call_site_link_c_modified.abc"; + + auto output = helpers::ExecuteStaticAbc(inputPath, "replace_call_site_static", "main"); + EXPECT_TRUE(helpers::Match(output, "3\n")); + + AbckitFile *file = g_impl->openAbc(inputPath.c_str(), inputPath.size()); + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + UserData ud {}; + ud.file = file; + + EnumerateModules( + [&](AbckitCoreModule *mod) { + auto moduleName = g_implI->abckitStringToString(g_implI->moduleGetName(mod)); + if (std::strcmp(moduleName, API_MODULE_NAME) != 0) { + return; + } + FillAnnotationInfo(mod, ud); + }, + file); + + // "modules/ApiControl" "modules/ApiControl" + ASSERT_EQ((std::string)g_implI->abckitStringToString(g_implI->classGetName(ud.classToReplace)), + (std::string)API_CLASS_NAME); + ASSERT_EQ((std::string)g_implI->abckitStringToString(g_implI->functionGetName(ud.methodToReplace)), + (std::string)API_METHOD_NAME); + + EnumerateModules( + [&](AbckitCoreModule *mod) { + auto moduleName = g_implI->abckitStringToString(g_implI->moduleGetName(mod)); + if (std::string_view(moduleName) != "replace_call_site_static") { + return; + } + EnumerateModuleClasses(mod, [&](AbckitCoreClass *klass) { ClassReplaceCallSite(ud, klass); }); + }, + file); + + g_impl->writeAbc(file, outputPath.c_str(), outputPath.size()); + g_impl->closeFile(file); + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + output = helpers::ExecuteStaticAbc(outputPath, "replace_call_site_static", "main"); + EXPECT_TRUE(helpers::Match(output, + "fixOrientationLinearLayout was called\n" + "5\n")); +} +} // namespace libabckit::test diff --git a/libabckit/tests/clean_scenarios/c_api/static/router_table/README.md b/libabckit/tests/clean_scenarios/c_api/static/router_table/README.md new file mode 100644 index 0000000000000000000000000000000000000000..aca664c0aa4e6382f99beb9b6c9e4b1372adcca0 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/router_table/README.md @@ -0,0 +1,49 @@ +## Router table + +Suppose that we have many annotated classes and one map, we need to collect all these classes and created instances and push into classes. + +`IRouterHandler.ets:` +```ts +export abstract class IRouterHandler { + abstract handle(): void; +} +``` +We have such classes: +`xxxHandler.ets:` +```ts +import { + IRouterHandler +} from './IRouterHandler' + +@interface __$$ETS_ANNOTATION$$__Router { + scheme: string; + path: string; +} + +@__$$ETS_ANNOTATION$$__Router({ + scheme: 'handle1', + path: '/xxx' +}) +export class xxxHandler extends IRouterHandler { + handle(): void { + print('xxxHandler.handle was called'); + } +} +``` + +`routerMap.ets:` +```ts +// before AOP +import {IRouterHandler } from './IRouterHandler'; +export let routerMap = new Map([]); + +// after AOP +// need to insert code into routerMap.ets +import {IRouterHandler} from "./IRouterHandler"; +import {xxxHandler} from "./xxxHandler"; +// we have many such imports + +export let routerMap = new Map([ + ["imeituan/xxx", new xxxHandler()], // [schema + path, new handler()] +]); +``` diff --git a/libabckit/tests/clean_scenarios/c_api/static/router_table/modules/IRouterHandler.ets b/libabckit/tests/clean_scenarios/c_api/static/router_table/modules/IRouterHandler.ets new file mode 100644 index 0000000000000000000000000000000000000000..0f2d1a8421018739924b9e43fe8940c3e34cfe38 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/router_table/modules/IRouterHandler.ets @@ -0,0 +1,17 @@ +/** + * 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. + */ +export abstract class IRouterHandler { + abstract handle(): void; +} diff --git a/libabckit/tests/clean_scenarios/c_api/static/router_table/modules/routerMap.ets b/libabckit/tests/clean_scenarios/c_api/static/router_table/modules/routerMap.ets new file mode 100644 index 0000000000000000000000000000000000000000..1562bf6d6ddea94d8ce3d923be6289aaae66791d --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/router_table/modules/routerMap.ets @@ -0,0 +1,18 @@ +/** + * 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. + */ +import { IRouterHandler } from './IRouterHandler'; +import { xxxHandler } from './xxxHandler'; + +export const routerMap: Map = new Map(); \ No newline at end of file diff --git a/libabckit/tests/clean_scenarios/c_api/static/router_table/modules/xxxHandler.ets b/libabckit/tests/clean_scenarios/c_api/static/router_table/modules/xxxHandler.ets new file mode 100644 index 0000000000000000000000000000000000000000..c44c8cb9038ac770c53ae67aea281fc1315ca71a --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/router_table/modules/xxxHandler.ets @@ -0,0 +1,30 @@ +/** + * 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. + */ +import { + IRouterHandler +} from './IRouterHandler' + +@interface Router { + scheme: string; + path: string; +} + +@Router({scheme: 'handle1111',path: '/xxxx'}) +export class xxxHandler extends IRouterHandler { + @Router({scheme: 'handle1111',path: '/xxxx'}) + handle(): void { + console.log('xxxHandler.handle was called'); + } +} diff --git a/libabckit/tests/clean_scenarios/c_api/static/router_table/router_table_static.ets b/libabckit/tests/clean_scenarios/c_api/static/router_table/router_table_static.ets new file mode 100644 index 0000000000000000000000000000000000000000..c418003a097f9c749265f64dba13c62ba6f194f9 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/router_table/router_table_static.ets @@ -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. + */ +import { + routerMap +} from './modules/routerMap'; +import { + IRouterHandler +} from './modules/IRouterHandler'; +import { + xxxHandler +} from './modules/xxxHandler'; + +function fillMap(key: string, value: xxxHandler) { + routerMap.set(key, value); +} + +function main() { + routerMap.forEach((value: IRouterHandler, key: string) => { + value.handle(); + console.log(key) +}); +} + diff --git a/libabckit/tests/clean_scenarios/c_api/static/router_table/router_table_static_test.cpp b/libabckit/tests/clean_scenarios/c_api/static/router_table/router_table_static_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6e479c802a40f9ff97d5bf0b467fad426a60d789 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/router_table/router_table_static_test.cpp @@ -0,0 +1,377 @@ +/* + * 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 "libabckit/c/extensions/arkts/metadata_arkts.h" +#include "helpers/helpers.h" +#include "helpers/helpers_runtime.h" +#include "tests/helpers/visit_helper/visit_helper-inl.h" +#include "metadata_inspect_impl.h" +#include "libabckit/src/logger.h" +#include + +namespace { + +auto g_impl = AbckitGetApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_implI = AbckitGetInspectApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_implM = AbckitGetModifyApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_implG = AbckitGetGraphApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_statG = AbckitGetIsaApiStaticImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_implArkI = AbckitGetArktsInspectApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); +auto g_implArkM = AbckitGetArktsModifyApiImpl(ABCKIT_VERSION_RELEASE_1_0_0); + +const std::string ROUTER_MAP_FILE_MODULE_NAME = "router_table_static"; +const std::string ROUTER_FILE_MODULE_NAME = "router_table_static"; +const std::string ROUTER_ANNOTATION_NAME = "Router"; +const std::string FUNC_MAIN_0 = "main:void;"; + +static std::string GetMethodName(AbckitCoreFunction *method) +{ + auto mname = g_implI->functionGetName(method); + std::string fullSig = g_implI->abckitStringToString(mname); + auto fullName = fullSig.substr(0, fullSig.find(':')); + return fullName; +} + +template +inline void EnumerateModules(const ModuleCallBack &cb, AbckitFile *file) +{ + LIBABCKIT_LOG_FUNC; + + g_implI->fileEnumerateModules(file, (void *)(&cb), [](AbckitCoreModule *mod, void *data) { + const auto &cb = *((ModuleCallBack *)(data)); + cb(mod); + return true; + }); +} + +template +inline void EnumerateModuleClasses(AbckitCoreModule *mod, const ClassCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + + g_implI->moduleEnumerateClasses(mod, (void *)(&cb), [](AbckitCoreClass *klass, void *data) { + const auto &cb = *((ClassCallBack *)data); + cb(klass); + return true; + }); +} + +template +inline void EnumerateClassAnnotations(AbckitCoreClass *klass, const AnnotationCallBack &cb) +{ + g_implI->classEnumerateAnnotations(klass, (void *)(&cb), [](AbckitCoreAnnotation *an, void *data) { + const auto &cb = *((AnnotationCallBack *)data); + cb(an); + + return true; + }); +} + +template +inline void EnumerateAnnotationElements(AbckitCoreAnnotation *an, const AnnotationElementCallBack &cb) +{ + g_implI->annotationEnumerateElements(an, (void *)(&cb), [](AbckitCoreAnnotationElement *ele, void *data) { + const auto &cb = *((AnnotationElementCallBack *)data); + cb(ele); + return true; + }); +} + +struct RouterAnnotation { + AbckitCoreClass *owner; + AbckitCoreAnnotation *ptrToAnno; + AbckitString *scheme; + AbckitString *path; +}; + +struct UserData { + AbckitString *classStr; + AbckitString *moduleStr; + RouterAnnotation routerInfo; +}; + +struct LocalData { + size_t idx; + AbckitInst *insertAfterInst; + AbckitCoreModule *module; +}; + +AbckitInst *FindFirstInst(AbckitGraph *graph, AbckitIsaApiStaticOpcode opcode) +{ + std::vector bbs; + g_implG->gVisitBlocksRpo(graph, &bbs, [](AbckitBasicBlock *bb, void *data) { + reinterpret_cast *>(data)->emplace_back(bb); + return true; + }); + for (auto *bb : bbs) { + auto *curInst = g_implG->bbGetFirstInst(bb); + while (curInst != nullptr) { + if (g_statG->iGetOpcode(curInst) == opcode) { + return curInst; + } + curInst = g_implG->iGetNext(curInst); + } + } + + return nullptr; +} + +void TransformMethod(AbckitCoreFunction *method, VisitHelper &visitor, const UserData *ud, LocalData *ld) +{ + visitor.TransformMethod(method, [&](AbckitFile *file, AbckitCoreFunction *method) { + auto ctxG = g_implI->createGraphFromFunction(method); + AbckitBasicBlock *startBB = g_implG->gGetStartBasicBlock(ctxG); + std::vector succBBs; + g_implG->bbVisitSuccBlocks(startBB, &succBBs, [](AbckitBasicBlock *succBasicBlock, void *d) { + auto *succs = reinterpret_cast *>(d); + succs->emplace_back(succBasicBlock); + return true; + }); + AbckitInst *createEmptyArray = FindFirstInst(ctxG, ABCKIT_ISA_API_STATIC_OPCODE_INITOBJECT); + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + if (ld->insertAfterInst == nullptr) { + ld->insertAfterInst = createEmptyArray; + } + + const auto &routerInfo = ud->routerInfo; + std::string fullPath = + std::string(visitor.GetString(routerInfo.scheme)) + std::string(visitor.GetString(routerInfo.path)); + AbckitString *fullPathStr = g_implM->createString(file, fullPath.c_str(), strlen(fullPath.c_str())); + AbckitInst *loadString = g_statG->iCreateLoadString(ctxG, fullPathStr); + + libabckit::test::helpers::ClassByNameContext classCtxFinder1 = {nullptr, "xxxHandler"}; + g_implI->moduleEnumerateClasses(ld->module, &classCtxFinder1, libabckit::test::helpers::ClassByNameFinder); + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + ASSERT_NE(classCtxFinder1.klass, nullptr); + AbckitInst *newObj = g_statG->iCreateNewObject(ctxG, classCtxFinder1.klass); + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + AbckitCoreFunction *fillMapStr = nullptr; + libabckit::test::helpers::EnumerateAllMethods(g_implI->functionGetFile(method), + [&](AbckitCoreFunction *method) { + auto methodName = GetMethodName(method); + if (methodName == "fillMap") { + fillMapStr = method; + } + }); + + AbckitInst *fillMapInst = g_statG->iCreateCallStatic(ctxG, fillMapStr, 2, loadString, newObj); + EXPECT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + g_implG->iInsertBefore(loadString, ld->insertAfterInst); + g_implG->iInsertAfter(newObj, loadString); + g_implG->iInsertAfter(fillMapInst, newObj); + ld->insertAfterInst = fillMapInst; + + g_implM->functionSetGraph(method, ctxG); + g_impl->destroyGraph(ctxG); + }); +} + +struct ModuleByNameContext { + AbckitCoreModule *module; + const char *name; +}; + +bool ModuleByNameFinder(AbckitCoreModule *module, void *data) +{ + auto ctxFinder = reinterpret_cast(data); + auto name = g_implI->abckitStringToString(g_implI->moduleGetName(module)); + if (strcmp(name, ctxFinder->name) == 0) { + ctxFinder->module = module; + return false; + } + + return true; +} + +void ModifyRouterTable(AbckitCoreFunction *method, VisitHelper &visitor, const std::vector &udContainer) +{ + LocalData ld {}; + ld.idx = 0; + ld.insertAfterInst = nullptr; + for (const auto &ud : udContainer) { + const auto &className = visitor.GetString(ud.classStr); + AbckitArktsImportFromDynamicModuleCreateParams params {}; + params.name = className.data(); + params.alias = className.data(); + + const auto &moduleName = visitor.GetString(ud.moduleStr); + ModuleByNameContext ctxFinder = {nullptr, moduleName.data()}; + auto file = g_implI->functionGetFile(method); + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + g_implI->fileEnumerateModules(file, &ctxFinder, ModuleByNameFinder); + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + ASSERT_NE(ctxFinder.module, nullptr); + ld.module = ctxFinder.module; + + TransformMethod(method, visitor, &ud, &ld); + } +} + +AbckitCoreFunction *FindMethodWithRouterTable(VisitHelper &visitor) +{ + AbckitCoreFunction *routerFunc = nullptr; + visitor.EnumerateModules([&](AbckitCoreModule *mod) { + auto moduleName = visitor.GetString(g_implI->moduleGetName(mod)); + + if (moduleName != ROUTER_MAP_FILE_MODULE_NAME) { + return; + } + + visitor.EnumerateModuleTopLevelFunctions(mod, [&](AbckitCoreFunction *func) { + auto funcName = visitor.GetString(g_implI->functionGetName(func)); + if (funcName != FUNC_MAIN_0) { + return; + } + + g_implI->functionGetFile(func); + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + routerFunc = func; + }); + }); + return routerFunc; +} + +void RemoveAnnotations(const std::vector &udContainer) +{ + for (const auto &ud : udContainer) { + const auto &routerInfo = ud.routerInfo; + g_implArkM->classRemoveAnnotation(g_implArkI->coreClassToArktsClass(routerInfo.owner), + g_implArkI->coreAnnotationToArktsAnnotation(routerInfo.ptrToAnno)); + } +} + +void CollectClassInfo(std::vector &udContainer, AbckitCoreModule *mod, AbckitCoreClass *klass) +{ + EnumerateClassAnnotations(klass, [&](AbckitCoreAnnotation *anno) { + auto *annoClass = g_implI->annotationGetInterface(anno); + auto annoNameAbc = g_implI->annotationInterfaceGetName(annoClass); + + if (annoNameAbc == nullptr) { + return; + } + auto annoName = g_implI->abckitStringToString(g_implI->annotationInterfaceGetName(annoClass)); + if (annoName != ROUTER_ANNOTATION_NAME) { + return; + } + + UserData ud {}; + ud.classStr = g_implI->classGetName(klass); + ud.moduleStr = g_implI->moduleGetName(mod); + auto &routerInfo = ud.routerInfo; + routerInfo.ptrToAnno = anno; + routerInfo.owner = klass; + EnumerateAnnotationElements(anno, [&](AbckitCoreAnnotationElement *annoElem) { + if (g_implI->annotationElementGetName(annoElem) == nullptr) { + return; + } + auto annoElemName = annoElem->name->impl.data(); + auto *value = g_implI->annotationElementGetValue(annoElem); + if (strcmp(annoElemName, "scheme") == 0) { + routerInfo.scheme = g_implI->valueGetString(value); + } + + if (strcmp(annoElemName, "path") == 0) { + routerInfo.path = g_implI->valueGetString(value); + } + }); + udContainer.push_back(ud); + }); +} + +void CollectClassesInfo(std::vector &udContainer, AbckitFile *file) +{ + EnumerateModules( + [&](AbckitCoreModule *mod) { + EnumerateModuleClasses(mod, [&](AbckitCoreClass *klass) { CollectClassInfo(udContainer, mod, klass); }); + }, + file); +} + +bool ClassHasAnnotation(VisitHelper &visitor, const UserData &ud) +{ + bool found = false; + + auto classAnnotationsEnumCb = [&](AbckitCoreAnnotation *anno) { + auto *annoClass = g_implI->annotationGetInterface(anno); + auto annoNameAbc = g_implI->annotationInterfaceGetName(annoClass); + + if (annoNameAbc == nullptr) { + return; + } + + auto annoName = visitor.GetString(g_implI->annotationInterfaceGetName(annoClass)); + if (annoName == ROUTER_ANNOTATION_NAME) { + found = true; + } + }; + + visitor.EnumerateModules([&](AbckitCoreModule *mod) { + if (visitor.GetString(g_implI->moduleGetName(mod)) != visitor.GetString(ud.moduleStr)) { + return; + } + visitor.EnumerateModuleClasses(mod, [&](AbckitCoreClass *klass) { + if (visitor.GetString(g_implI->classGetName(klass)) != visitor.GetString(ud.classStr)) { + return; + } + visitor.EnumerateClassAnnotations(klass, classAnnotationsEnumCb); + }); + }); + return found; +} +} // namespace + +namespace libabckit::test { + +class AbckitScenarioCTestClean : public ::testing::Test {}; + +// Test: test-kind=scenario, abc-kind=ArkTS1, category=positive, extension=c +TEST_F(AbckitScenarioCTestClean, LibAbcKitTestStaticRouterTableClean) +{ + std::string inputPath = ABCKIT_ABC_DIR "clean_scenarios/c_api/static/router_table/router_table_link_c.abc"; + std::string outputPath = + ABCKIT_ABC_DIR "clean_scenarios/c_api/static/router_table/router_table_link_c_modified.abc"; + + auto output = helpers::ExecuteStaticAbc(inputPath, "router_table_static", "main"); + AbckitFile *file = g_impl->openAbc(inputPath.c_str(), inputPath.size()); + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + auto visitor = VisitHelper(file, g_impl, g_implI, g_implG); + + std::vector userData; + + CollectClassesInfo(userData, file); + RemoveAnnotations(userData); + auto *method = FindMethodWithRouterTable(visitor); + ModifyRouterTable(method, visitor, userData); + + g_impl->writeAbc(file, outputPath.c_str(), outputPath.size()); + ASSERT_EQ(g_impl->getLastError(), ABCKIT_STATUS_NO_ERROR); + + for (const auto &ud : userData) { + ClassHasAnnotation(visitor, ud); + }; + output = helpers::ExecuteStaticAbc(outputPath, "router_table_static", "main"); + g_impl->closeFile(file); + + EXPECT_TRUE(helpers::Match(output, + "xxxHandler.handle was called\n" + "handle1111/xxxx\n")); +} + +} // namespace libabckit::test diff --git a/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/README.md b/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e5da8e0cdc7031d6201e9e2a372209e7e72588b0 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/README.md @@ -0,0 +1,35 @@ +## Scan subclasses + +### Requirement + +We want to detect all the subclasses of a given class. + +### APP source code example + +`modules/base.ets:` +```ets +export class Base {} +``` +`scan_subclasses.ets:` +```ets +import { Base } from './modules/base'; + +class Child1 extends Base {} +class Child2 extends Base {} +``` + +#### Input + +Base class info: +```cpp +// {class_name} +{"Base"} +``` + +#### Output + +List of subclasses info: +```cpp +// {file_path, subclassName} +{{"scan_subclasses", "Child1"}, {"scan_subclasses", "Child2"}}; +``` diff --git a/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/modules/base.ets b/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/modules/base.ets new file mode 100644 index 0000000000000000000000000000000000000000..1acdb03902e39b31c9de3ed44b1506364a61d68c --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/modules/base.ets @@ -0,0 +1,19 @@ +/** + * 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. + */ +export class Base { + handle() : void { + console.log("Base handle") + } +} diff --git a/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/scan_subclasses_static.ets b/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/scan_subclasses_static.ets new file mode 100644 index 0000000000000000000000000000000000000000..037040ed2d4d4cf95300d9f527a0bb016b496904 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/scan_subclasses_static.ets @@ -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. + */ +import { + Base +} from './modules/base'; + +class Child1 extends Base { + handle() : void { + console.log("Child1 handle") + } +} + +class Child2 extends Base { + handle() : void { + console.log("Child2 handle") + } +} + +function main() { + let m = new Child2(); + m.handle(); +} + diff --git a/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/scan_subclasses_static_test.cpp b/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/scan_subclasses_static_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..276fa7053dd7d5921ce39bf9713989d28de876a6 --- /dev/null +++ b/libabckit/tests/clean_scenarios/c_api/static/scan_subclasses/scan_subclasses_static_test.cpp @@ -0,0 +1,315 @@ +/* + * 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 "libabckit/c/abckit.h" +#include "libabckit/c/isa/isa_static.h" +#include "libabckit/c/metadata_core.h" +#include "libabckit/c/ir_core.h" +#include "libabckit/src/logger.h" + +#include +#include +#include +#include +#include +#include + +#ifndef ABCKIT_ABC_DIR +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define ABCKIT_ABC_DIR "" +#endif + +namespace { + +constexpr AbckitApiVersion VERSION = ABCKIT_VERSION_RELEASE_1_0_0; +auto *g_impl = AbckitGetApiImpl(VERSION); +const AbckitInspectApi *g_implI = AbckitGetInspectApiImpl(VERSION); +const AbckitGraphApi *g_implG = AbckitGetGraphApiImpl(VERSION); + +struct ClassInfo { + std::string path; + std::string className; +}; + +struct CapturedData { + void *callback = nullptr; + const AbckitGraphApi *gImplG = nullptr; +}; + +struct GraphDeleter { + void operator()(AbckitGraph *graph) const + { + if (graph) { + g_impl->destroyGraph(graph); + } + } +}; + +struct FileDeleter { + void operator()(AbckitFile *file) const + { + if (file) { + g_impl->closeFile(file); + } + } +}; + +using UniqueGraph = std::unique_ptr; +using UniqueFile = std::unique_ptr; + +inline std::string GetClassName(AbckitCoreClass *klass) +{ + if (!klass) + return ""; + return g_implI->abckitStringToString(g_implI->classGetName(klass)); +} + +inline std::string GetModuleName(AbckitCoreModule *module) +{ + if (!module) + return ""; + return g_implI->abckitStringToString(g_implI->moduleGetName(module)); +} + +template +inline void EnumerateModules(const ModuleCallBack &cb, AbckitFile *file) +{ + LIBABCKIT_LOG_FUNC; + + g_implI->fileEnumerateModules(file, (void *)(&cb), [](AbckitCoreModule *mod, void *data) { + const auto &cb = *((ModuleCallBack *)(data)); + cb(mod); + return true; + }); +} + +template +inline void EnumerateExternalModules(const ModuleCallBack &cb, AbckitFile *file) +{ + LIBABCKIT_LOG_FUNC; + + g_implI->fileEnumerateExternalModules(file, (void *)(&cb), [](AbckitCoreModule *mod, void *data) { + const auto &cb = *((ModuleCallBack *)(data)); + cb(mod); + return true; + }); +} + +template +inline void EnumerateModuleTopLevelFunctions(AbckitCoreModule *mod, const FunctionCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + + g_implI->moduleEnumerateTopLevelFunctions(mod, (void *)(&cb), [](AbckitCoreFunction *method, void *data) { + const auto &cb = *((FunctionCallBack *)data); + cb(method); + return true; + }); +} + +template +inline void EnumerateModuleClasses(AbckitCoreModule *mod, const ClassCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + + g_implI->moduleEnumerateClasses(mod, (void *)(&cb), [](AbckitCoreClass *klass, void *data) { + const auto &cb = *((ClassCallBack *)data); + cb(klass); + return true; + }); +} + +template +inline bool VisitBlock(AbckitBasicBlock *bb, void *data) +{ + auto *captured = reinterpret_cast(data); + const auto &cb = *reinterpret_cast(captured->callback); + auto *implG = captured->gImplG; + for (auto *inst = implG->bbGetFirstInst(bb); inst != nullptr; inst = implG->iGetNext(inst)) { + cb(inst); + } + return true; +} + +template +static bool VisitBlockStatic(AbckitBasicBlock *bb, void *data) +{ + auto *captured = static_cast(data); + const auto &cb = *static_cast(captured->callback); + auto *implG = captured->gImplG; + + for (auto *inst = implG->bbGetFirstInst(bb); inst != nullptr; inst = implG->iGetNext(inst)) { + cb(inst); + } + return true; +} + +// 使用完美转发保持参数类型 +template +inline void EnumerateGraphInsts(AbckitGraph *graph, InstCallBack &&cb) +{ + CapturedData captured {&cb, g_implG}; + g_implG->gVisitBlocksRpo(graph, &captured, &VisitBlockStatic>); +} + +template +inline void EnumerateFunctionInsts(AbckitCoreFunction *func, const InstCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + + UniqueGraph graph(g_implI->createGraphFromFunction(func)); + if (graph) { + EnumerateGraphInsts(graph.get(), cb); + } +} + +void CollectSubClasses(AbckitCoreFunction *method, const ClassInfo &baseClassInfo, + std::unordered_map *subClassesMap) +{ + auto *implI = g_implI; + auto klass = implI->functionGetParentClass(method); + if (!klass) + return; + + auto parentKlass = implI->classGetSuperClass(klass); + if (!parentKlass) + return; + + std::string parentKlassName = GetClassName(parentKlass); + if (parentKlassName != baseClassInfo.className) + return; + + auto module = implI->classGetModule(klass); + ClassInfo classInfo {GetModuleName(module), GetClassName(klass)}; + subClassesMap->emplace(classInfo.className, std::move(classInfo)); +} + +template +inline void EnumerateClassMethods(AbckitCoreClass *klass, const MethodCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + g_implI->classEnumerateMethods(klass, (void *)(&cb), [](AbckitCoreFunction *method, void *data) { + const auto &cb = *((MethodCallBack *)data); + cb(method); + return true; + }); +} + +template +inline void EnumerateModuleFunctions(AbckitCoreModule *mod, const FunctionCallBack &cb) +{ + LIBABCKIT_LOG_FUNC; + // NOTE: currently we can only enumerate class methods and top level functions. need to update. + EnumerateModuleTopLevelFunctions(mod, cb); + EnumerateModuleClasses(mod, [&](AbckitCoreClass *klass) { EnumerateClassMethods(klass, cb); }); +} + +struct PairHash { + template + std::size_t operator()(const std::pair &p) const + { + std::size_t h1 = std::hash {}(p.first); + std::size_t h2 = std::hash {}(p.second); + return h1 ^ (h2 << 1); + } +}; + +template +bool IsEqualsSubClasses(const ExpectedContainer &expected, const ActualContainer &actual) +{ + if (expected.size() != actual.size()) { + return false; + } + + std::unordered_set, PairHash> actualSet; + for (const auto &item : actual) { + actualSet.emplace(item.className, item.path); + } + + for (const auto &item : expected) { + if (actualSet.find({item.className, item.path}) == actualSet.end()) { + return false; + } + } + return true; +} + +bool CheckBaseClassExists(AbckitFile* file) +{ + ClassInfo baseClassInfo = {"modules/base", "Base"}; + bool baseClassFound = false; + + EnumerateExternalModules( + [&](AbckitCoreModule *mod) { + auto moduleName = GetModuleName(mod); + if (baseClassInfo.path.find(moduleName) != std::string::npos) { + EnumerateModuleClasses(mod, [&](AbckitCoreClass *klass) { + if (GetClassName(klass) == baseClassInfo.className) { + baseClassFound = true; + return false; + } + return true; + }); + + if (baseClassFound) { + return false; + } + } + return true; + }, + file); + + return baseClassFound; +} +} // namespace + +namespace libabckit::test { + +class AbckitScenarioCTestClean : public ::testing::Test {}; + +// Test: test-kind=scenario, abc-kind=ArkTS1, category=positive, extension=c +TEST_F(AbckitScenarioCTestClean, LibAbcKitTestStaticScanSubclassesClean) +{ + constexpr auto kInputPath = + ABCKIT_ABC_DIR "clean_scenarios/c_api/static/scan_subclasses/scan_subclasses_static.abc"; + // CC-OFFNXT(G.NAM.03) project code style + UniqueFile file(g_impl->openAbc(kInputPath, strlen(kInputPath))); + ASSERT_NE(file.get(), nullptr); + + // make sure that base class is existed. + bool baseClassesFonded = CheckBaseClassExists(file.get()); + EXPECT_TRUE(baseClassesFonded) << "Base class not found in external modules"; + + std::unordered_map subClassesMap; + ClassInfo baseClassInfo = {"modules/base", "Base"}; + EnumerateModules( + [&](AbckitCoreModule *mod) { + EnumerateModuleFunctions( + mod, [&](AbckitCoreFunction *method) { CollectSubClasses(method, baseClassInfo, &subClassesMap); }); + }, + file.get()); + + ASSERT_FALSE(subClassesMap.empty()); + std::vector subClasses; + subClasses.reserve(subClassesMap.size()); + for (const auto &[key, classInfo] : subClassesMap) { + subClasses.push_back(classInfo); + } + std::array expectedSubClasses = {ClassInfo {"scan_subclasses_static", "Child1"}, + ClassInfo {"scan_subclasses_static", "Child2"}}; + ASSERT_TRUE(IsEqualsSubClasses(expectedSubClasses, subClasses)); +} + +} // namespace libabckit::test diff --git a/libabckit/tests/helpers/visit_helper/visit_helper.cpp b/libabckit/tests/helpers/visit_helper/visit_helper.cpp index 13d97fcf8e0e7f896c2734d11dd32c9875043899..edbc738d16bc1d996d162ecfc21b13537b56d487 100644 --- a/libabckit/tests/helpers/visit_helper/visit_helper.cpp +++ b/libabckit/tests/helpers/visit_helper/visit_helper.cpp @@ -19,8 +19,8 @@ #include VisitHelper::VisitHelper(AbckitFile *file, const AbckitApi *impl, const AbckitInspectApi *implI, - const AbckitGraphApi *implG, const AbckitIsaApiDynamic *dynG) - : file_(file), impl_(impl), implI_(implI), implG_(implG), dynG_(dynG) {}; + const AbckitGraphApi *implG) + : file_(file), impl_(impl), implI_(implI), implG_(implG) {}; std::string_view VisitHelper::GetString(AbckitString *str) const { diff --git a/libabckit/tests/helpers/visit_helper/visit_helper.h b/libabckit/tests/helpers/visit_helper/visit_helper.h index 968fa2b041262f0deeabcae30bfe2462e043740e..7a5a9094c0d8a1e5535e6385f61f070836c4f47c 100644 --- a/libabckit/tests/helpers/visit_helper/visit_helper.h +++ b/libabckit/tests/helpers/visit_helper/visit_helper.h @@ -32,7 +32,7 @@ public: DEFAULT_COPY_SEMANTIC(VisitHelper); DEFAULT_NOEXCEPT_MOVE_SEMANTIC(VisitHelper); explicit VisitHelper(AbckitFile *file, const AbckitApi *impl, const AbckitInspectApi *implI, - const AbckitGraphApi *implG, const AbckitIsaApiDynamic *dynG); + const AbckitGraphApi *implG); // ModuleCallBack: (AbckitCoreModule *) -> void template @@ -112,7 +112,6 @@ private: const AbckitApi *impl_ = nullptr; const AbckitInspectApi *implI_ = nullptr; const AbckitGraphApi *implG_ = nullptr; - const AbckitIsaApiDynamic *dynG_ = nullptr; }; #endif /* VISIT_HELPER_H */