1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "libabckit/include/cpp/abckit_cpp.h"
17
18 #include "helpers/helpers.h"
19 #include "helpers/helpers_runtime.h"
20 #include "libabckit/src/logger.h"
21
22 #include <gtest/gtest.h>
23
24 #include <optional>
25
26 namespace {
27
28 class GTestAssertErrorHandler final : public abckit::IErrorHandler {
29 public:
HandleError(abckit::Exception && err)30 void HandleError(abckit::Exception &&err) override
31 {
32 EXPECT_TRUE(false) << "Abckit expection raised: " << err.what();
33 }
34 };
35
36 constexpr auto API_CLASS_NAME = "ApiControl";
37 constexpr auto API_METHOD_NAME = "fixOrientationLinearLayout";
38 constexpr auto API_MODULE_NAME = "modules/ApiControl";
39 constexpr auto ANNOTATION_INTERFACE_NAME = "CallSiteReplacement";
40 constexpr auto TARGET_ORIENTATION = 5;
41
42 struct ReplaceCommand {
43 struct Subject {
44 abckit::core::Class klass;
45 abckit::core::Function method;
46 };
47 Subject subject;
48
49 struct Target {
50 std::string className;
51 std::string methodName;
52 };
53 Target target;
54 };
55
HasTargetMethod(const abckit::Instruction & inst,const ReplaceCommand::Target & target)56 bool HasTargetMethod(const abckit::Instruction &inst, const ReplaceCommand::Target &target)
57 {
58 if (inst.GetGraph()->DynIsa().GetOpcode(inst) != ABCKIT_ISA_API_DYNAMIC_OPCODE_LDEXTERNALMODULEVAR) {
59 return false;
60 }
61
62 bool hasNewObjUser = false;
63 inst.VisitUsers([&](const abckit::Instruction &user) -> bool {
64 hasNewObjUser |= user.GetGraph()->DynIsa().GetOpcode(user) == ABCKIT_ISA_API_DYNAMIC_OPCODE_NEWOBJRANGE;
65 return !hasNewObjUser;
66 });
67 if (!hasNewObjUser) {
68 return false;
69 }
70
71 bool found = false;
72 inst.VisitUsers([&](const abckit::Instruction &user) -> bool {
73 if (user.GetGraph()->DynIsa().GetOpcode(user) == ABCKIT_ISA_API_DYNAMIC_OPCODE_THROW_UNDEFINEDIFHOLEWITHNAME) {
74 found |= target.className == user.GetString();
75 }
76 return !found;
77 });
78
79 return found;
80 }
81
HasTargetMethod(const abckit::core::Function & method,const ReplaceCommand::Target & target)82 bool HasTargetMethod(const abckit::core::Function &method, const ReplaceCommand::Target &target)
83 {
84 bool found = false;
85
86 auto graph = method.CreateGraph();
87 graph.EnumerateBasicBlocksRpo([&](const abckit::BasicBlock &bb) -> bool {
88 for (abckit::Instruction inst = bb.GetFirstInst(); !!inst && !found; inst = inst.GetNext()) {
89 found |= HasTargetMethod(inst, target);
90 }
91 return !found;
92 });
93
94 return found;
95 }
96
FindFirstInst(const abckit::Graph & graph,AbckitIsaApiDynamicOpcode opcode)97 std::optional<abckit::Instruction> FindFirstInst(const abckit::Graph &graph, AbckitIsaApiDynamicOpcode opcode)
98 {
99 std::optional<abckit::Instruction> found;
100 graph.EnumerateBasicBlocksRpo([&](const abckit::BasicBlock &bb) -> bool {
101 for (auto inst = bb.GetFirstInst(); !!inst && !found; inst = inst.GetNext()) {
102 if (inst.GetGraph()->DynIsa().GetOpcode(inst) == opcode) {
103 found = inst;
104 }
105 }
106 return !found;
107 });
108 return found;
109 }
110
ReplaceCallSite(const abckit::core::Function & method,const ReplaceCommand & command)111 void ReplaceCallSite(const abckit::core::Function &method, const ReplaceCommand &command)
112 {
113 std::optional<abckit::arkts::Module> targetModule;
114 method.GetFile()->EnumerateModules([&](const abckit::core::Module &mod) -> bool {
115 if (mod.GetName() == API_MODULE_NAME) {
116 targetModule = abckit::arkts::Module(mod);
117 return false;
118 }
119 return true;
120 });
121 EXPECT_TRUE(targetModule.has_value());
122
123 abckit::arkts::Module arktsMethMod(method.GetModule());
124 abckit::core::ImportDescriptor idesc =
125 arktsMethMod.AddImportFromArktsV1ToArktsV1(*targetModule, API_CLASS_NAME, API_CLASS_NAME);
126
127 abckit::Graph graph = method.CreateGraph();
128
129 std::optional<abckit::Instruction> maybeConstInst;
130 auto maybeNewobjInst = FindFirstInst(graph, ABCKIT_ISA_API_DYNAMIC_OPCODE_NEWOBJRANGE);
131 EXPECT_TRUE(maybeNewobjInst.has_value());
132
133 graph.EnumerateBasicBlocksRpo([&](const abckit::BasicBlock &bb) -> bool {
134 for (abckit::Instruction inst = bb.GetFirstInst(); !!inst; inst = inst.GetNext()) {
135 if (inst.GetGraph()->DynIsa().GetOpcode(inst) != ABCKIT_ISA_API_DYNAMIC_OPCODE_CALLTHIS1) {
136 continue;
137 }
138 maybeConstInst = maybeConstInst ? maybeConstInst : graph.FindOrCreateConstantI32(TARGET_ORIENTATION);
139
140 auto ldExternal = graph.DynIsa().CreateLdexternalmodulevar(idesc).InsertAfter(inst);
141 auto classThrow = graph.DynIsa()
142 .CreateThrowUndefinedifholewithname(ldExternal, command.subject.klass.GetName())
143 .InsertAfter(ldExternal);
144 auto ldObj =
145 graph.DynIsa().CreateLdobjbyname(ldExternal, command.subject.method.GetName()).InsertAfter(classThrow);
146 auto staticCall =
147 graph.DynIsa().CreateCallthis2(ldObj, ldExternal, *maybeNewobjInst, *maybeConstInst).InsertAfter(ldObj);
148 }
149 return true;
150 });
151
152 method.SetGraph(graph);
153 }
154
ReplaceCallSite(const abckit::core::Class & klass,const ReplaceCommand & command)155 void ReplaceCallSite(const abckit::core::Class &klass, const ReplaceCommand &command)
156 {
157 klass.EnumerateMethods([&](const abckit::core::Function &method) -> bool {
158 if (HasTargetMethod(method, command.target)) {
159 ReplaceCallSite(method, command);
160 }
161 return true;
162 });
163 }
164
165 struct AnnotationTrack {
166 abckit::core::Class klass;
167 abckit::core::Function method;
168 abckit::core::Annotation anno;
169 };
170
GetAnnotationTracks(const abckit::core::Class & klass,const abckit::core::Function & method,std::vector<AnnotationTrack> & tracks)171 void GetAnnotationTracks(const abckit::core::Class &klass, const abckit::core::Function &method,
172 std::vector<AnnotationTrack> &tracks)
173 {
174 method.EnumerateAnnotations([&](auto anno) {
175 if (anno.GetInterface().GetName() == ANNOTATION_INTERFACE_NAME) {
176 tracks.push_back({klass, method, anno});
177 }
178 return true;
179 });
180 }
181
GetAnnotationTracks(const abckit::core::Module & mod)182 std::vector<AnnotationTrack> GetAnnotationTracks(const abckit::core::Module &mod)
183 {
184 std::vector<AnnotationTrack> tracks;
185 mod.EnumerateClasses([&](const auto &klass) {
186 klass.EnumerateMethods([&](const auto &method) {
187 GetAnnotationTracks(klass, method, tracks);
188 return true;
189 });
190 return true;
191 });
192 return tracks;
193 }
194
GetReplaceCommand(const AnnotationTrack & track)195 ReplaceCommand GetReplaceCommand(const AnnotationTrack &track)
196 {
197 ReplaceCommand::Target target;
198 track.anno.EnumerateElements([&](const abckit::core::AnnotationElement &el) -> bool {
199 auto name = el.GetName();
200 if (name == "targetClass") {
201 target.className = el.GetValue().GetString();
202 } else if (name == "methodName") {
203 target.methodName = el.GetValue().GetString();
204 }
205 return true;
206 });
207 EXPECT_TRUE(!target.className.empty());
208 EXPECT_TRUE(!target.methodName.empty());
209
210 return ReplaceCommand {ReplaceCommand::Subject {track.klass, track.method}, target};
211 }
212
GetAnnotationInfo(const abckit::core::Module & mod,std::vector<ReplaceCommand> & replaceCommands)213 void GetAnnotationInfo(const abckit::core::Module &mod, std::vector<ReplaceCommand> &replaceCommands)
214 {
215 const std::vector<AnnotationTrack> tracks = GetAnnotationTracks(mod);
216 for (const auto &track : tracks) {
217 replaceCommands.push_back(GetReplaceCommand(track));
218 }
219 }
220
221 } // namespace
222
223 namespace libabckit::test {
224
225 class AbckitScenarioCppTestClean : public ::testing::Test {};
226
227 // Test: test-kind=scenario, abc-kind=ArkTS1, category=positive, extension=cpp
TEST_F(AbckitScenarioCppTestClean,LibAbcKitTestCppDynamicReplaceCallSiteClean)228 TEST_F(AbckitScenarioCppTestClean, LibAbcKitTestCppDynamicReplaceCallSiteClean)
229 {
230 const std::string sandboxPath = ABCKIT_ABC_DIR "clean_scenarios/cpp_api/dynamic/replace_call_site/";
231 const std::string inputPath = sandboxPath + "replace_call_site.abc";
232 const std::string outputPath = sandboxPath + "replace_call_site_modified.abc";
233
234 auto output = helpers::ExecuteDynamicAbc(inputPath, "replace_call_site");
235 EXPECT_EQ(output, "3\n");
236
237 abckit::File file(inputPath, std::make_unique<GTestAssertErrorHandler>());
238
239 std::vector<ReplaceCommand> replaceCommands;
240 file.EnumerateModules([&](const abckit::core::Module &mod) {
241 if (mod.GetName() == API_MODULE_NAME) {
242 GetAnnotationInfo(mod, replaceCommands);
243 }
244 return true;
245 });
246 EXPECT_EQ(replaceCommands.size(), 1);
247 const auto &command = replaceCommands.front();
248
249 // "modules/ApiControl" "modules/ApiControl"
250 EXPECT_EQ(command.subject.klass.GetName(), API_CLASS_NAME);
251 EXPECT_EQ(command.subject.method.GetName(), API_METHOD_NAME);
252
253 file.EnumerateModules([&](const abckit::core::Module &mod) {
254 if (mod.GetName() != "replace_call_site") {
255 return true;
256 }
257 mod.EnumerateClasses([&](const abckit::core::Class &klass) {
258 ReplaceCallSite(klass, command);
259 return true;
260 });
261 return true;
262 });
263
264 file.WriteAbc(outputPath);
265
266 output = helpers::ExecuteDynamicAbc(outputPath, "replace_call_site");
267 EXPECT_TRUE(helpers::Match(output,
268 "fixOrientationLinearLayout was called\n"
269 "5\n"));
270 }
271
272 } // namespace libabckit::test
273