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 "runtime/tooling/evaluation/expression_loader.h"
17
18 #include "libpandabase/utils/span.h"
19 #include "libpandafile/class_data_accessor.h"
20 #include "runtime/include/runtime.h"
21
22 namespace ark::tooling {
23
24 namespace {
25
ValidateEvaluationMethod(Method * method)26 bool ValidateEvaluationMethod(Method *method)
27 {
28 if (method == nullptr) {
29 LOG(WARNING, DEBUGGER) << "Evaluate failed to find method";
30 return false;
31 }
32 // No arguments can be provided by interface.
33 if (method->GetNumArgs() != 0) {
34 LOG(WARNING, DEBUGGER) << "Evaluation cannot have any arguments, but got " << method->GetNumArgs();
35 return false;
36 }
37 // Currently support only primitive types
38 auto type = method->GetReturnType();
39 if (!type.IsPrimitive()) {
40 LOG(WARNING, DEBUGGER) << "Evaluation method can return only primitive values";
41 return false;
42 }
43
44 return true;
45 }
46
47 /**
48 * @brief Checks if class fully-qualified name meets evaluation class requirements.
49 * @param demangledClassName tested class name, can be obtained via ClassDataAccessor::DemangledName.
50 * @returns name of evaluation package, class and method on success, nullopt otherwise.
51 */
FindEvaluationMethodName(std::string_view demangledClassName)52 std::optional<std::string> FindEvaluationMethodName(std::string_view demangledClassName)
53 {
54 static constexpr std::string_view SUFFIX = "_eval";
55 // CC-OFFNXT(G.NAM.03) project code style
56 static constexpr char DELIMITER = '.';
57
58 // Evaluation class fully-qualified name must consist of package and class
59 // equal names: "<NAME>.<NAME>".
60 auto iter = std::find(demangledClassName.begin(), demangledClassName.end(), DELIMITER);
61 if (iter == demangledClassName.end()) {
62 return {};
63 }
64 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
65 if (std::find(iter + 1, demangledClassName.end(), DELIMITER) != demangledClassName.end()) {
66 return {};
67 }
68 // Check package and class names equality.
69 size_t delimiterPos = iter - demangledClassName.begin();
70 auto expectedMethodName = demangledClassName.substr(0, delimiterPos);
71 if (expectedMethodName != demangledClassName.substr(delimiterPos + 1)) {
72 return {};
73 }
74 // Check expected suffix.
75 if (delimiterPos < SUFFIX.size() || expectedMethodName.substr(delimiterPos - SUFFIX.size()) != SUFFIX) {
76 return {};
77 }
78 return std::string(expectedMethodName);
79 }
80
81 /**
82 * @brief Finds evaluation class according to naming convention.
83 * @param pf received panda file.
84 * @returns pair of evaluation method name and class data accessor on success, nullopt otherwise.
85 */
FindEvaluationMethodClass(const panda_file::File * pf)86 std::optional<std::pair<std::string, panda_file::ClassDataAccessor>> FindEvaluationMethodClass(
87 const panda_file::File *pf)
88 {
89 for (uint32_t id : pf->GetClasses()) {
90 panda_file::File::EntityId entityId(id);
91 if (pf->IsExternal(entityId)) {
92 continue;
93 }
94
95 panda_file::ClassDataAccessor cda(*pf, entityId);
96 std::string className = cda.DemangledName();
97 auto expectedMethodName = FindEvaluationMethodName(className);
98 if (expectedMethodName) {
99 return std::make_pair(*expectedMethodName, cda);
100 }
101 }
102 return {};
103 }
104
LoadFileAndGetEntryMethod(ClassLinkerContext * ctx,std::unique_ptr<const panda_file::File> && pf)105 Method *LoadFileAndGetEntryMethod(ClassLinkerContext *ctx, std::unique_ptr<const panda_file::File> &&pf)
106 {
107 // Extract information about evaluation entry method. By convention, the passed
108 // bytecode must contain a class with a static method, which both have the same names as panda file has.
109 auto optEvalClassInfo = FindEvaluationMethodClass(pf.get());
110 if (!optEvalClassInfo) {
111 LOG(WARNING, DEBUGGER) << "Evaluate failed to find entry class";
112 return nullptr;
113 }
114 auto [methodName, cda] = *optEvalClassInfo;
115 auto evalClassId = cda.GetClassId();
116 const auto *descriptor = pf->GetStringData(evalClassId).data;
117
118 // Check that the class is not loaded yet.
119 auto *linker = Runtime::GetCurrent()->GetClassLinker();
120 if (linker->GetClass(*pf, evalClassId, ctx, nullptr) != nullptr) {
121 LOG(WARNING, DEBUGGER) << "Evaluation class is already loaded, which is not supported";
122 return nullptr;
123 }
124
125 // Add panda file into context of the target thread.
126 linker->AddPandaFile(std::move(pf), ctx);
127
128 auto sourceLang = cda.GetSourceLang().value_or(panda_file::SourceLang::PANDA_ASSEMBLY);
129 ClassLinkerExtension *extension = linker->GetExtension(sourceLang);
130 ASSERT(extension != nullptr);
131
132 // Linker must accept nullptr as error handler, otherwise managed exception will occur on failure.
133 auto *klass = linker->GetClass(descriptor, true, extension->ResolveContext(ctx), nullptr);
134 if (klass == nullptr) {
135 LOG(WARNING, DEBUGGER) << "Evaluate failed to load class";
136 return nullptr;
137 }
138 return klass->GetDirectMethod(utf::CStringAsMutf8(methodName.c_str()));
139 }
140
141 } // namespace
142
LoadExpressionBytecode(ClassLinkerContext * ctx,const std::string & bytecode)143 Expected<Method *, Error> LoadExpressionBytecode(ClassLinkerContext *ctx, const std::string &bytecode)
144 {
145 auto pf = ark::panda_file::OpenPandaFileFromMemory(bytecode.data(), bytecode.size());
146 if (pf == nullptr) {
147 return Unexpected(Error(Error::Type::INVALID_EXPRESSION, "failed to load panda file"));
148 }
149
150 auto *method = LoadFileAndGetEntryMethod(ctx, std::move(pf));
151 if (!ValidateEvaluationMethod(method)) {
152 return Unexpected(Error(Error::Type::INVALID_ENTRY_POINT, "invalid expression bytecode"));
153 }
154 return method;
155 }
156
157 } // namespace ark::tooling
158