1 /**
2 * Copyright (c) 2021-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 "libpandabase/os/native_stack.h"
17 #include "monitor.h"
18 #include "os/mutex.h"
19 #include "runtime/include/runtime.h"
20 #include "runtime/include/thread_scopes.h"
21 #include "utils/logger.h"
22 #include "utils/span.h"
23 #include "verification/plugins.h"
24 #include "verification/util/is_system.h"
25 #include "verification/util/optional_ref.h"
26 #include "generated/base_options.h"
27
28 // generated
29 #include "ark_version.h"
30 #include "generated/verifier_options_gen.h"
31
32 #include <csignal>
33 #include <chrono>
34 #include <thread>
35
36 namespace ark::verifier {
37
38 size_t const MAX_THREADS = 64;
39
Worker(PandaDeque<Method *> *queue, os::memory::Mutex *lock, size_t threadNum, std::atomic<bool> *result)40 void Worker(PandaDeque<Method *> *queue, os::memory::Mutex *lock, size_t threadNum, std::atomic<bool> *result)
41 {
42 LOG(DEBUG, VERIFIER) << "Verifier thread " << threadNum << " starting";
43 {
44 std::stringstream ss;
45 ss << "Verifier #" << threadNum;
46 if (os::thread::SetThreadName(os::thread::GetNativeHandle(), ss.str().c_str()) != 0) {
47 LOG(ERROR, VERIFIER) << "Failed to set worker thread name " << ss.str();
48 }
49 }
50
51 auto *service = Runtime::GetCurrent()->GetVerifierService();
52 auto options = Runtime::GetCurrent()->GetOptions();
53 auto mode = options.GetVerificationMode();
54
55 ManagedThread *thread = nullptr;
56 panda_file::SourceLang currentLang = panda_file::SourceLang::INVALID;
57 for (;;) {
58 Method *method;
59 {
60 os::memory::LockHolder lh {*lock};
61 if (queue->empty()) {
62 break;
63 }
64 method = queue->front();
65 queue->pop_front();
66 }
67 auto methodLang = method->GetClass()->GetSourceLang();
68 if (methodLang != currentLang) {
69 if (thread != nullptr) {
70 plugin::GetLanguagePlugin(currentLang)->DestroyManagedThread(thread);
71 }
72 thread = plugin::GetLanguagePlugin(methodLang)->CreateManagedThread();
73 currentLang = methodLang;
74 }
75 if (ark::verifier::Verify(service, method, mode) != Status::OK) {
76 *result = false;
77 LOG(ERROR, VERIFIER) << "Error: method " << method->GetFullName(true) << " failed to verify";
78 }
79 }
80 if (thread != nullptr) {
81 plugin::GetLanguagePlugin(currentLang)->DestroyManagedThread(thread);
82 }
83 LOG(DEBUG, VERIFIER) << "Verifier thread " << threadNum << " finishing";
84 }
85
EnqueueMethod(Method *method, PandaDeque<Method *> &queue, PandaUnorderedSet<Method *> &methodsSet)86 static void EnqueueMethod(Method *method, PandaDeque<Method *> &queue, PandaUnorderedSet<Method *> &methodsSet)
87 {
88 if (methodsSet.count(method) == 0) {
89 queue.push_back(method);
90 methodsSet.insert(method);
91 }
92 }
93
EnqueueClass(const Class &klass, bool verifyLibraries, PandaDeque<Method *> &queue, PandaUnorderedSet<Method *> &methodsSet)94 static void EnqueueClass(const Class &klass, bool verifyLibraries, PandaDeque<Method *> &queue,
95 PandaUnorderedSet<Method *> &methodsSet)
96 {
97 if (!verifyLibraries && IsSystemClass(&klass)) {
98 LOG(INFO, VERIFIER) << klass.GetName() << " is a system class, skipping";
99 return;
100 }
101 LOG(INFO, VERIFIER) << "Begin verification of class " << klass.GetName();
102 for (auto &method : klass.GetMethods()) {
103 EnqueueMethod(&method, queue, methodsSet);
104 }
105 }
106
GetFileHandler(std::atomic<bool> &result, ClassLinker &classLinker, bool verifyLibraries, PandaDeque<Method *> &queue, PandaUnorderedSet<Method *> &methodsSet)107 static auto GetFileHandler(std::atomic<bool> &result, ClassLinker &classLinker, bool verifyLibraries,
108 PandaDeque<Method *> &queue, PandaUnorderedSet<Method *> &methodsSet)
109 {
110 auto handleClass = [&result, &classLinker, verifyLibraries, &queue,
111 &methodsSet](const panda_file::File &file, const panda_file::File::EntityId &entityId) {
112 if (!file.IsExternal(entityId)) {
113 auto optLang = panda_file::ClassDataAccessor {file, entityId}.GetSourceLang();
114 if (optLang.has_value() && !IsValidSourceLang(optLang.value())) {
115 LOG(ERROR, VERIFIER) << "Unknown SourceLang";
116 result = false;
117 return false;
118 }
119 ClassLinkerExtension *ext =
120 classLinker.GetExtension(optLang.value_or(panda_file::SourceLang::PANDA_ASSEMBLY));
121 if (ext == nullptr) {
122 LOG(ERROR, VERIFIER) << "Error: Class Linker Extension failed to initialize";
123 result = false;
124 return false;
125 }
126 const Class *klass = ext->GetClass(file, entityId);
127
128 if (klass != nullptr) {
129 EnqueueClass(*klass, verifyLibraries, queue, methodsSet);
130 }
131 }
132
133 return true;
134 };
135
136 return [handleClass](const panda_file::File &file) {
137 LOG(INFO, VERIFIER) << "Processing file" << file.GetFilename();
138 for (auto id : file.GetClasses()) {
139 panda_file::File::EntityId entityId {id};
140 if (!handleClass(file, entityId)) {
141 return false;
142 }
143 }
144
145 return true;
146 };
147 }
148
VerifyAllNames(std::atomic<bool> &result, bool verifyLibraries, Runtime &runtime, PandaDeque<Method *> &queue, PandaUnorderedSet<Method *> &methodsSet)149 static void VerifyAllNames(std::atomic<bool> &result, bool verifyLibraries, Runtime &runtime,
150 PandaDeque<Method *> &queue, PandaUnorderedSet<Method *> &methodsSet)
151 {
152 auto &classLinker = *runtime.GetClassLinker();
153 PandaVector<const panda_file::File *> pfiles;
154 classLinker.EnumeratePandaFiles([&pfiles](const panda_file::File &file) {
155 pfiles.push_back(&file);
156 return true;
157 });
158
159 auto handleFile = GetFileHandler(result, classLinker, verifyLibraries, queue, methodsSet);
160
161 for (auto pf : pfiles) {
162 if (!handleFile(*pf)) {
163 break;
164 }
165 }
166
167 if (verifyLibraries) {
168 classLinker.EnumerateBootPandaFiles(handleFile);
169 } else if (runtime.GetPandaFiles().empty()) {
170 // in this case the last boot-panda-file and only it is actually not a system file and should be
171 // verified
172 OptionalConstRef<panda_file::File> file;
173 classLinker.EnumerateBootPandaFiles([&file](const panda_file::File &pf) {
174 file = std::cref(pf);
175 return true;
176 });
177 if (file.HasRef()) {
178 handleFile(*file);
179 } else {
180 LOG(ERROR, VERIFIER) << "No files given to verify";
181 }
182 }
183 }
184
GetClassByName(std::atomic<bool> &result, PandaUnorderedMap<std::string, Class *> &classesByName, Runtime &runtime, const std::string &className)185 static Class *GetClassByName(std::atomic<bool> &result, PandaUnorderedMap<std::string, Class *> &classesByName,
186 Runtime &runtime, const std::string &className)
187 {
188 auto it = classesByName.find(className);
189 if (it != classesByName.end()) {
190 return it->second;
191 }
192
193 PandaString descriptor;
194 auto &classLinker = *runtime.GetClassLinker();
195 auto *ctx = classLinker.GetExtension(runtime.GetLanguageContext(runtime.GetRuntimeType()))->GetBootContext();
196 const uint8_t *classNameBytes = ClassHelper::GetDescriptor(utf::CStringAsMutf8(className.c_str()), &descriptor);
197
198 Class *klass = classLinker.GetClass(classNameBytes, true, ctx);
199 if (klass == nullptr) {
200 LOG(ERROR, VERIFIER) << "Error: Cannot resolve class with name " << className;
201 result = false;
202 }
203
204 classesByName.emplace(className, klass);
205 return klass;
206 }
207
VeifyMethod(Class *klass, const std::string &fqMethodName, const std::string_view &unqualifiedMethodName, PandaDeque<Method *> &queue, PandaUnorderedSet<Method *> &methodsSet)208 static bool VeifyMethod(Class *klass, const std::string &fqMethodName, const std::string_view &unqualifiedMethodName,
209 PandaDeque<Method *> &queue, PandaUnorderedSet<Method *> &methodsSet)
210 {
211 bool methodFound = false;
212 for (auto &method : klass->GetMethods()) {
213 const char *nameData = utf::Mutf8AsCString(method.GetName().data);
214 if (std::string_view(nameData) == unqualifiedMethodName) {
215 methodFound = true;
216 LOG(INFO, VERIFIER) << "Verification of method '" << fqMethodName << "'";
217 EnqueueMethod(&method, queue, methodsSet);
218 }
219 }
220 return methodFound;
221 }
222
RunVerifierImpl(std::atomic<bool> &result, Runtime &runtime, const std::vector<std::string> &classNames, const std::vector<std::string> &methodNames, PandaDeque<Method *> &queue)223 static void RunVerifierImpl(std::atomic<bool> &result, Runtime &runtime, const std::vector<std::string> &classNames,
224 const std::vector<std::string> &methodNames, PandaDeque<Method *> &queue)
225 {
226 bool verifyLibraries = runtime.GetOptions().IsVerifyRuntimeLibraries();
227 PandaUnorderedSet<Method *> methodsSet;
228
229 // we need ScopedManagedCodeThread for the verifier since it can allocate objects
230 ScopedManagedCodeThread managedObjThread(ManagedThread::GetCurrent());
231 if (classNames.empty() && methodNames.empty()) {
232 VerifyAllNames(result, verifyLibraries, runtime, queue, methodsSet);
233 } else {
234 PandaUnorderedMap<std::string, Class *> classesByName;
235
236 for (const auto &className : classNames) {
237 Class *klass = GetClassByName(result, classesByName, runtime, className);
238 // the bad case is already handled in get_class_by_name
239 if (klass != nullptr) {
240 EnqueueClass(*klass, verifyLibraries, queue, methodsSet);
241 }
242 }
243
244 for (const std::string &fqMethodName : methodNames) {
245 size_t pos = fqMethodName.find_last_of("::");
246 if (pos == std::string::npos) {
247 LOG(ERROR, VERIFIER) << "Error: Fully qualified method name must contain '::', was " << fqMethodName;
248 result = false;
249 break;
250 }
251 std::string className = fqMethodName.substr(0, pos - 1);
252 std::string_view unqualifiedMethodName = std::string_view(fqMethodName).substr(pos + 1);
253 if (std::find(classNames.begin(), classNames.end(), className) != classNames.end()) {
254 // this method was already verified while enumerating class_names
255 continue;
256 }
257 Class *klass = GetClassByName(result, classesByName, runtime, className);
258 if (klass == nullptr) {
259 continue;
260 }
261 bool methodFound = VeifyMethod(klass, fqMethodName, unqualifiedMethodName, queue, methodsSet);
262 if (!methodFound) {
263 LOG(ERROR, VERIFIER) << "Error: Cannot resolve method with name " << unqualifiedMethodName
264 << " in class " << className;
265 result = false;
266 }
267 }
268 }
269 }
270
RunVerifier(const Options &cliOptions)271 bool RunVerifier(const Options &cliOptions)
272 {
273 auto isPerfMeasure = cliOptions.IsPerfMeasure();
274 std::chrono::steady_clock::time_point begin;
275 std::chrono::steady_clock::time_point end;
276
277 os::memory::Mutex lock;
278
279 auto &runtime = *Runtime::GetCurrent();
280
281 PandaDeque<Method *> queue;
282
283 const std::vector<std::string> &classNames = cliOptions.GetClasses();
284 const std::vector<std::string> &methodNames = cliOptions.GetMethods();
285
286 std::atomic<bool> result = true;
287 RunVerifierImpl(result, runtime, classNames, methodNames, queue);
288
289 if (isPerfMeasure) {
290 begin = std::chrono::steady_clock::now();
291 }
292
293 size_t nthreads = runtime.GetOptions().GetVerificationThreads();
294 std::vector<std::thread *> threads;
295 for (size_t i = 0; i < nthreads; i++) {
296 auto *worker = runtime.GetInternalAllocator()->New<std::thread>(Worker, &queue, &lock, i, &result);
297 threads.push_back(worker);
298 }
299
300 for (auto *thr : threads) {
301 thr->join();
302 runtime.GetInternalAllocator()->Delete(thr);
303 }
304
305 if (isPerfMeasure) {
306 end = std::chrono::steady_clock::now();
307 std::cout << "Verification time = "
308 << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << " us" << std::endl;
309 }
310
311 return result;
312 }
313
PrintHelp(const PandArgParser &paParser)314 void PrintHelp(const PandArgParser &paParser)
315 {
316 std::string error = paParser.GetErrorString();
317 if (!error.empty()) {
318 error += "\n\n";
319 }
320 std::cerr << error << "Usage: verifier [option...] [file]\n"
321 << "Verify specified Panda files (given by file and --panda-files) "
322 << "or certain classes/methods in them.\n\n"
323 << paParser.GetHelpString() << std::endl;
324 }
325
RunThreads(Options &cliOptions, RuntimeOptions &runtimeOptions)326 static int RunThreads(Options &cliOptions, RuntimeOptions &runtimeOptions)
327 {
328 uint32_t threads = cliOptions.GetThreads();
329 if (threads == 0) {
330 threads = std::thread::hardware_concurrency();
331 // hardware_concurrency can return 0 if the value is not computable or well defined
332 if (threads == 0) {
333 threads = 1;
334 } else if (threads > MAX_THREADS) {
335 threads = MAX_THREADS;
336 }
337 }
338 runtimeOptions.SetVerificationThreads(threads);
339
340 if (!Runtime::Create(runtimeOptions)) {
341 std::cerr << "Error: cannot create runtime" << std::endl;
342 return -1;
343 }
344
345 int ret = RunVerifier(cliOptions) ? 0 : -1;
346
347 if (cliOptions.IsPrintMemoryStatistics()) {
348 std::cout << Runtime::GetCurrent()->GetMemoryStatistics();
349 }
350 if (!Runtime::Destroy()) {
351 std::cerr << "Error: cannot destroy runtime" << std::endl;
352 return -1;
353 }
354 return ret;
355 }
356
Run(Options &cliOptions, RuntimeOptions &runtimeOptions, base_options::Options &baseOptions, PandArg<std::string> &file)357 static int Run(Options &cliOptions, RuntimeOptions &runtimeOptions, base_options::Options &baseOptions,
358 PandArg<std::string> &file)
359 {
360 auto bootPandaFiles = cliOptions.GetBootPandaFiles();
361 auto pandaFiles = cliOptions.GetPandaFiles();
362 std::string mainFileName = file.GetValue();
363 if (!mainFileName.empty()) {
364 if (pandaFiles.empty()) {
365 bootPandaFiles.push_back(mainFileName);
366 } else {
367 auto foundIter = std::find_if(pandaFiles.begin(), pandaFiles.end(),
368 [&mainFileName](auto &fileName) { return mainFileName == fileName; });
369 if (foundIter == pandaFiles.end()) {
370 pandaFiles.push_back(mainFileName);
371 }
372 }
373 }
374
375 runtimeOptions.SetBootPandaFiles(bootPandaFiles);
376 runtimeOptions.SetPandaFiles(pandaFiles);
377 runtimeOptions.SetLoadRuntimes(cliOptions.GetLoadRuntimes());
378 runtimeOptions.SetGcType(cliOptions.GetGcType());
379
380 baseOptions.SetLogComponents(cliOptions.GetLogComponents());
381 baseOptions.SetLogLevel(cliOptions.GetLogLevel());
382 baseOptions.SetLogStream(cliOptions.GetLogStream());
383 baseOptions.SetLogFile(cliOptions.GetLogFile());
384 Logger::Initialize(baseOptions);
385
386 runtimeOptions.SetLimitStandardAlloc(cliOptions.IsLimitStandardAlloc());
387 runtimeOptions.SetInternalAllocatorType(cliOptions.GetInternalAllocatorType());
388 runtimeOptions.SetInternalMemorySizeLimit(cliOptions.GetInternalMemorySizeLimit());
389
390 runtimeOptions.SetVerificationMode(cliOptions.IsDebugMode() ? VerificationMode::DEBUG
391 : VerificationMode::AHEAD_OF_TIME);
392 runtimeOptions.SetVerificationConfigFile(cliOptions.GetConfigFile());
393 runtimeOptions.SetVerificationCacheFile(cliOptions.GetCacheFile());
394 runtimeOptions.SetVerificationUpdateCache(cliOptions.IsUpdateCache());
395 runtimeOptions.SetVerifyRuntimeLibraries(cliOptions.IsVerifyRuntimeLibraries());
396 return RunThreads(cliOptions, runtimeOptions);
397 }
398
Main(int argc, const char **argv)399 int Main(int argc, const char **argv)
400 {
401 Span<const char *> sp(argv, argc);
402 RuntimeOptions runtimeOptions(sp[0]);
403 Options cliOptions(sp[0]);
404 PandArgParser paParser;
405 base_options::Options baseOptions("");
406
407 PandArg<bool> help("help", false, "Print this message and exit");
408 PandArg<bool> options("options", false, "Print verifier options");
409 // tail argument
410 PandArg<std::string> file("file", "", "path to pandafile");
411
412 cliOptions.AddOptions(&paParser);
413
414 paParser.Add(&help);
415 paParser.Add(&options);
416 paParser.PushBackTail(&file);
417 paParser.EnableTail();
418
419 if (!paParser.Parse(argc, argv)) {
420 PrintHelp(paParser);
421 return 1;
422 }
423
424 if (runtimeOptions.IsVersion()) {
425 PrintPandaVersion();
426 return 0;
427 }
428
429 if (help.GetValue()) {
430 PrintHelp(paParser);
431 return 0;
432 }
433
434 if (options.GetValue()) {
435 std::cout << paParser.GetRegularArgs() << std::endl;
436 return 0;
437 }
438
439 auto cliOptionsErr = cliOptions.Validate();
440 if (cliOptionsErr) {
441 std::cerr << "Error: " << cliOptionsErr.value().GetMessage() << std::endl;
442 return 1;
443 }
444
445 return Run(cliOptions, runtimeOptions, baseOptions, file);
446 }
447
448 } // namespace ark::verifier
449
450 int main(int argc, const char **argv)
451 {
452 return ark::verifier::Main(argc, argv);
453 }
454