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
21 #include <gtest/gtest.h>
22
23 #include <optional>
24 #include <string_view>
25
26 namespace {
27
28 class ErrorHandler final : public abckit::IErrorHandler {
HandleError(abckit::Exception && e)29 void HandleError(abckit::Exception &&e) override
30 {
31 EXPECT_TRUE(false) << "Abckit exception raised: " << e.what();
32 }
33 };
34
EnumerateModuleFunctions(const abckit::core::Module & mod,const std::function<bool (abckit::core::Function)> & cb)35 inline void EnumerateModuleFunctions(const abckit::core::Module &mod,
36 const std::function<bool(abckit::core::Function)> &cb)
37 {
38 // NOTE: currently we can only enumerate class methods and top level functions. need to update.
39 mod.EnumerateTopLevelFunctions(cb);
40 mod.EnumerateClasses([&](const abckit::core::Class &klass) -> bool {
41 klass.EnumerateMethods(cb);
42 return true;
43 });
44 }
45
EnumerateFunctionInsts(const abckit::core::Function & func,const std::function<bool (abckit::Instruction)> & cb)46 inline void EnumerateFunctionInsts(const abckit::core::Function &func,
47 const std::function<bool(abckit::Instruction)> &cb)
48 {
49 abckit::Graph graph = func.CreateGraph();
50 graph.EnumerateBasicBlocksRpo([&](const abckit::BasicBlock &bb) {
51 for (auto inst = bb.GetFirstInst(); inst; inst = inst.GetNext()) {
52 cb(inst);
53 }
54 return true;
55 });
56 }
57
58 struct ApiInfo {
59 std::string source;
60 std::string objName;
61 std::string propName;
62 };
63
64 struct UsageInfo {
65 std::string source;
66 std::string funcName;
67 size_t idx;
68 };
69
70 using ApiUsageMap = std::unordered_map<size_t, std::vector<UsageInfo>>;
71 using SuspectsMap = std::unordered_map<abckit::core::ImportDescriptor, size_t>;
72
GetMethodName(const abckit::core::Function & method)73 std::string GetMethodName(const abckit::core::Function &method)
74 {
75 auto methodName = method.GetName();
76 auto cls = method.GetParentClass();
77 if (cls) {
78 auto className = cls.GetName();
79 return className + '.' + methodName;
80 };
81 return methodName;
82 }
83
IsLoadApi(const abckit::core::ImportDescriptor & id,size_t apiIndex,const abckit::Instruction & inst,std::vector<ApiInfo> & apiList)84 bool IsLoadApi(const abckit::core::ImportDescriptor &id, size_t apiIndex, const abckit::Instruction &inst,
85 std::vector<ApiInfo> &apiList)
86 {
87 // find the following pattern:
88 // 1. ldExternalModuleVar (import {ApiNamespace} from "./modules/myApi")
89 // 2. ldObjByName v1, "foo" (api.propName)
90 // or
91 // 1. ldExternalModuleVar (import {bar} from "./modules/myApi")
92
93 if (inst.GetGraph()->DynIsa().GetOpcode(inst) != ABCKIT_ISA_API_DYNAMIC_OPCODE_LDEXTERNALMODULEVAR) {
94 return false;
95 }
96
97 abckit::core::ImportDescriptor instId = inst.GetGraph()->DynIsa().GetImportDescriptor(inst);
98 if (instId != id) {
99 return false;
100 }
101
102 const auto &prop = apiList[apiIndex].propName;
103 if (prop.empty()) {
104 return true;
105 }
106
107 return !inst.VisitUsers([&](const abckit::Instruction &user) {
108 if (user.GetGraph()->DynIsa().GetOpcode(user) == ABCKIT_ISA_API_DYNAMIC_OPCODE_LDOBJBYNAME) {
109 if (user.GetString() == prop) {
110 return false;
111 }
112 }
113 return true;
114 });
115 }
116
AddApiUsage(size_t apiIndex,const UsageInfo & usage,ApiUsageMap * apiUsages)117 void AddApiUsage(size_t apiIndex, const UsageInfo &usage, ApiUsageMap *apiUsages)
118 {
119 auto iter = apiUsages->find(apiIndex);
120 if (iter == apiUsages->end()) {
121 std::vector<UsageInfo> usages;
122 usages.emplace_back(usage);
123 apiUsages->emplace(apiIndex, usages);
124 } else {
125 iter->second.emplace_back(usage);
126 }
127 }
128
CollectUsageInMethod(const abckit::core::Function & method,SuspectsMap & suspects,ApiUsageMap * apiUsages,std::vector<ApiInfo> & apiList)129 void CollectUsageInMethod(const abckit::core::Function &method, SuspectsMap &suspects, ApiUsageMap *apiUsages,
130 std::vector<ApiInfo> &apiList)
131 {
132 auto methodName = GetMethodName(method);
133 auto mod = method.GetModule();
134 auto source = mod.GetName();
135 UsageInfo usage = {source, methodName, 0};
136 EnumerateFunctionInsts(method, [&](const abckit::Instruction &inst) {
137 for (const auto &[id, apiIndex] : suspects) {
138 if (IsLoadApi(id, apiIndex, inst, apiList)) {
139 usage.idx = apiIndex;
140 AddApiUsage(apiIndex, usage, apiUsages);
141 }
142 }
143 return true;
144 });
145 }
146
GetSuspects(const abckit::core::Module & mod,SuspectsMap * suspects,std::vector<ApiInfo> & apiList)147 bool GetSuspects(const abckit::core::Module &mod, SuspectsMap *suspects, std::vector<ApiInfo> &apiList)
148 {
149 mod.EnumerateImports([&](const abckit::core::ImportDescriptor &id) -> bool {
150 auto importName = id.GetName();
151 auto importedModule = id.GetImportedModule();
152 auto source = importedModule.GetName();
153 for (size_t i = 0; i < apiList.size(); ++i) {
154 const auto api = apiList[i];
155 if (source != api.source) {
156 continue;
157 }
158 if (importName == api.objName) {
159 suspects->emplace(id, i);
160 }
161 }
162 return true;
163 });
164 return !suspects->empty();
165 }
166
CollectApiUsages(ApiUsageMap * apiUsages,std::vector<ApiInfo> & apiList,abckit::File * file)167 void CollectApiUsages(ApiUsageMap *apiUsages, std::vector<ApiInfo> &apiList, abckit::File *file)
168 {
169 file->EnumerateModules([&](const abckit::core::Module &mod) -> bool {
170 SuspectsMap suspects = {};
171 GetSuspects(mod, &suspects, apiList);
172 if (suspects.empty()) {
173 return true;
174 }
175 EnumerateModuleFunctions(mod, [&](const abckit::core::Function &method) -> bool {
176 CollectUsageInMethod(method, suspects, apiUsages, apiList);
177 return true;
178 });
179 return true;
180 });
181 }
182
GetApiUsages(std::vector<ApiInfo> & apiList,abckit::File * file)183 ApiUsageMap GetApiUsages(std::vector<ApiInfo> &apiList, abckit::File *file)
184 {
185 auto apiUsages = ApiUsageMap({});
186 CollectApiUsages(&apiUsages, apiList, file);
187 return apiUsages;
188 }
189
GetQuatatedStr(const std::string & str)190 std::string GetQuatatedStr(const std::string &str)
191 {
192 const static std::string QUOTATION = "\"";
193 return QUOTATION + str + QUOTATION;
194 }
195
ApiUsageMapGetString(const ApiUsageMap & apiUsages,std::vector<ApiInfo> & apiList)196 std::string ApiUsageMapGetString(const ApiUsageMap &apiUsages, std::vector<ApiInfo> &apiList)
197 {
198 std::stringstream ss;
199 const static std::string INDENT_1 = " ";
200 const static std::string INDENT_2 = INDENT_1 + INDENT_1;
201 const static std::string INDENT_3 = INDENT_1 + INDENT_2;
202 const static std::string INDENT_4 = INDENT_1 + INDENT_3;
203 const static std::string LEFT_BRACKET = "{";
204 const static std::string RIGHT_BRACKET = "}";
205 const static std::string COLON = ": ";
206 const static std::string COMMA = ",";
207 const static std::string APIINFO = "APIInfo";
208 const static std::string APIINFO_SOURCE = "source";
209 const static std::string APIINFO_OBJNAME = "objName";
210 const static std::string APIINFO_PROPNAME = "propName";
211 const static std::string USAGES = "Usages";
212 const static std::string USAGES_SOURCE = "source";
213 const static std::string USAGES_FUNCNAME = "funcName";
214 const static std::string API_IDX = "indexOfAPI";
215
216 ss << LEFT_BRACKET << std::endl;
217 for (const auto &[index, usageInfos] : apiUsages) {
218 ss << INDENT_1 << LEFT_BRACKET << std::endl;
219
220 if (usageInfos.empty()) {
221 continue;
222 }
223 const auto &apiInfo = apiList[index];
224 ss << INDENT_2 << APIINFO << COLON << LEFT_BRACKET << std::endl;
225 ss << INDENT_3 << APIINFO_SOURCE << COLON << GetQuatatedStr(apiInfo.source) << COMMA << std::endl;
226 ss << INDENT_3 << APIINFO_OBJNAME << COLON << GetQuatatedStr(apiInfo.objName) << COMMA << std::endl;
227 ss << INDENT_3 << APIINFO_PROPNAME << COLON << GetQuatatedStr(apiInfo.propName) << COMMA << std::endl;
228 ss << INDENT_2 << RIGHT_BRACKET << COMMA << std::endl;
229
230 ss << INDENT_2 << USAGES << COLON << LEFT_BRACKET << std::endl;
231 for (const auto &usage : usageInfos) {
232 ss << INDENT_3 << LEFT_BRACKET << std::endl;
233 ss << INDENT_4 << USAGES_SOURCE << COLON << GetQuatatedStr(usage.source) << COMMA << std::endl;
234 ss << INDENT_4 << USAGES_FUNCNAME << COLON << GetQuatatedStr(usage.funcName) << COMMA << std::endl;
235 ss << INDENT_4 << API_IDX << COLON << usage.idx << COMMA << std::endl;
236 ss << INDENT_3 << RIGHT_BRACKET << COMMA << std::endl;
237 }
238 ss << INDENT_2 << RIGHT_BRACKET << std::endl;
239
240 ss << INDENT_1 << RIGHT_BRACKET << COMMA << std::endl;
241 }
242 ss << RIGHT_BRACKET << std::endl;
243
244 return ss.str();
245 }
246
IsUsagesEqual(const std::vector<UsageInfo> & otherUsages,const ApiUsageMap & apiUsages)247 bool IsUsagesEqual(const std::vector<UsageInfo> &otherUsages, const ApiUsageMap &apiUsages)
248 {
249 for (auto &otherUsage : otherUsages) {
250 auto usages = apiUsages.at(otherUsage.idx);
251 auto iter = std::find_if(usages.begin(), usages.end(), [&otherUsage](const UsageInfo &usage) {
252 return (otherUsage.funcName == usage.funcName) && (otherUsage.source == usage.source) &&
253 (otherUsage.idx == usage.idx);
254 });
255 if (iter == usages.end()) {
256 return false;
257 }
258 }
259 return true;
260 }
261
262 } // namespace
263
264 namespace libabckit::test {
265
266 class AbckitScenarioCppTestClean : public ::testing::Test {};
267
268 // Test: test-kind=scenario, abc-kind=ArkTS1, category=positive, extension=cpp
TEST_F(AbckitScenarioCppTestClean,LibAbcKitTestDynamicApiScannerClean)269 TEST_F(AbckitScenarioCppTestClean, LibAbcKitTestDynamicApiScannerClean)
270 {
271 // CC-OFFNXT(G.NAM.03) project code style
272
273 const std::string testSandboxPath = ABCKIT_ABC_DIR "clean_scenarios/cpp_api/dynamic/api_scanner/";
274 const std::string inputAbcPath = testSandboxPath + "api_scanner.abc";
275
276 abckit::File file(inputAbcPath, std::make_unique<ErrorHandler>());
277
278 std::vector<ApiInfo> apiList = {{"modules/apiNamespace", "ApiNamespace", "foo"},
279 {"modules/toplevelApi", "bar", ""},
280 {"@ohos.geolocation", "geolocation", "getCurrentLocation"}};
281
282 const ApiUsageMap usages = GetApiUsages(apiList, &file);
283 EXPECT_FALSE(usages.empty());
284
285 auto usagesStr = ApiUsageMapGetString(usages, apiList);
286 std::vector<UsageInfo> expectedUsages = {
287 {"api_scanner", "useAll", 0},
288 {"api_scanner", "useFoo", 0},
289 {"api_scanner", "Person.personUseAll", 0},
290 {"api_scanner", "Person.personUseFoo", 0},
291 {"modules/src2", "Animal.animalUseFoo", 0},
292 {"modules/src2", "Animal.animalUseAll", 0},
293 {"modules/src2", "src2UseAll", 0},
294 {"modules/src2", "src2UseFoo", 0},
295 {"api_scanner", "useAll", 1},
296 {"api_scanner", "useBar", 1},
297 {"api_scanner", "Person.personUseAll", 1},
298 {"api_scanner", "Person.personUseBar", 1},
299 {"api_scanner", "useAll", 2},
300 {"api_scanner", "useLocation", 2},
301 {"api_scanner", "Person.personUseLocation", 2},
302 {"modules/src3", "src3UseLocation", 2},
303 {"modules/src3", "Cat.catUseLocation", 2},
304 };
305 EXPECT_TRUE(IsUsagesEqual(expectedUsages, usages));
306 LIBABCKIT_LOG(DEBUG) << "====================================\n";
307 LIBABCKIT_LOG(DEBUG) << usagesStr;
308 }
309
310 } // namespace libabckit::test
311