• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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