1 /**
2 * Copyright (c) 2025 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 "ani.h"
17 #include "ani_options_parser.h"
18 #include "ani_options.h"
19 #include "generated/base_options.h"
20 #include "plugins/ets/runtime/ani/ani_checkers.h"
21 #include "plugins/ets/runtime/ani/ani_interaction_api.h"
22 #include "plugins/ets/runtime/ets_coroutine.h"
23 #include "plugins/ets/runtime/ets_vm.h"
24 #include "plugins/ets/runtime/interop_js/interop_context_api.h"
25 #include "utils/logger.h"
26 #include "utils/pandargs.h"
27
28 // CC-OFFNXT(huge_method[C++]) solid logic
ANI_CreateVM(const ani_options * options,uint32_t version,ani_vm ** result)29 extern "C" ani_status ANI_CreateVM(const ani_options *options, uint32_t version, ani_vm **result)
30 {
31 ANI_DEBUG_TRACE(env);
32 ANI_CHECK_RETURN_IF_EQ(result, nullptr, ANI_INVALID_ARGS);
33 ANI_CHECK_RETURN_IF_EQ(options, nullptr, ANI_INVALID_ARGS);
34 if (!ark::ets::ani::IsVersionSupported(version)) {
35 return ANI_INVALID_VERSION;
36 }
37
38 size_t optionsSize = options->nr_options;
39 const ani_option *optionsArr = options->options;
40
41 ark::ets::ani::ANIOptionsParser aniParser(optionsSize, optionsArr);
42
43 // NOTE(konstanting, #23205): please note that compiler options are not supported by ANI_CreateVM.
44 // Compiler logging is not supported too. Please look at e.g. ets_vm_api.cpp:CreateRuntime
45 // and ets_vm_plugin.cpp:AddOptions for reference if you would like to support the mentioned
46 // features.
47 ark::ets::ani::ANIOptions aniOptions;
48 ark::base_options::Options baseOptions("");
49 ark::PandArgParser paParser;
50
51 // NOTE(konstanting, #23205): consider adding options validation (Validate() method)
52 // Add runtime options
53 baseOptions.AddOptions(&paParser);
54 aniOptions.AddOptions(&paParser);
55
56 if (!paParser.Parse(aniParser.GetRuntimeOptions())) {
57 std::string errorMessage = paParser.GetErrorString();
58 if (!errorMessage.empty() && errorMessage.back() == '\n') {
59 // Trim new line
60 errorMessage = std::string(errorMessage.c_str(), errorMessage.length() - 1);
61 }
62 ark::Logger::Initialize(baseOptions, aniParser.GetLoggerCallback());
63 LOG(ERROR, ANI) << errorMessage;
64 return ANI_ERROR;
65 }
66 ark::Logger::Initialize(baseOptions, aniParser.GetLoggerCallback());
67
68 #ifndef PANDA_ETS_INTEROP_JS
69 if (aniParser.IsInteropMode()) {
70 // no interop options allowed in the interop-free build!
71 return ANI_INVALID_ARGS;
72 }
73 #endif /* PANDA_ETS_INTEROP_JS */
74
75 if (!ark::Runtime::Create(aniOptions)) {
76 LOG(ERROR, ANI) << "Cannot create runtime";
77 return ANI_ERROR;
78 }
79
80 auto coroutine = ark::ets::EtsCoroutine::GetCurrent();
81 ASSERT(coroutine != nullptr);
82 #ifdef PANDA_ETS_INTEROP_JS
83 if (aniParser.IsInteropMode()) {
84 bool created = ark::ets::interop::js::CreateMainInteropContext(coroutine, aniParser.GetInteropEnv());
85 if (!created) {
86 LOG(ERROR, ANI) << "Cannot create interop context";
87 ark::Runtime::Destroy();
88 return ANI_ERROR;
89 }
90 }
91 #endif /* PANDA_ETS_INTEROP_JS */
92
93 *result = coroutine->GetPandaVM();
94 ASSERT(*result != nullptr);
95
96 // NOTE:
97 // Don't change the following log message, because it used for testing logger callback.
98 // Or change log message at the same time as the ani_option_logger_parser_success_test.cpp test.
99 LOG(INFO, ANI) << "ani_vm has been created";
100 return ANI_OK;
101 }
102
103 // CC-OFFNXT(G.FUN.02-CPP) project code stytle
ANI_GetCreatedVMs(ani_vm ** vmsBuffer,ani_size vmsBufferLength,ani_size * result)104 extern "C" ani_status ANI_GetCreatedVMs(ani_vm **vmsBuffer, ani_size vmsBufferLength, ani_size *result)
105 {
106 ANI_DEBUG_TRACE(env);
107 ANI_CHECK_RETURN_IF_EQ(vmsBuffer, nullptr, ANI_INVALID_ARGS);
108 ANI_CHECK_RETURN_IF_EQ(result, nullptr, ANI_INVALID_ARGS);
109
110 auto *thread = ark::Thread::GetCurrent();
111 if (thread == nullptr) {
112 *result = 0;
113 return ANI_OK;
114 }
115 auto *coroutine = ark::ets::EtsCoroutine::CastFromThread(thread);
116 if (coroutine != nullptr) {
117 if (vmsBufferLength < 1) {
118 return ANI_INVALID_ARGS;
119 }
120
121 vmsBuffer[0] = coroutine->GetPandaVM(); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
122 *result = 1;
123 } else {
124 *result = 0;
125 }
126 return ANI_OK;
127 }
128
129 namespace ark::ets::ani {
130
DestroyVM(ani_vm * vm)131 static ani_status DestroyVM(ani_vm *vm)
132 {
133 ANI_DEBUG_TRACE(env);
134 ANI_CHECK_RETURN_IF_EQ(vm, nullptr, ANI_INVALID_ARGS);
135
136 auto runtime = Runtime::GetCurrent();
137 if (runtime == nullptr) {
138 LOG(ERROR, ANI) << "Cannot destroy ANI VM, there is no current runtime";
139 return ANI_ERROR;
140 }
141
142 auto pandaVm = PandaEtsVM::FromAniVM(vm);
143 auto mainVm = runtime->GetPandaVM();
144 if (pandaVm == mainVm) {
145 Runtime::Destroy();
146 } else {
147 PandaEtsVM::Destroy(pandaVm);
148 }
149
150 return ANI_OK;
151 }
152
GetEnv(ani_vm * vm,uint32_t version,ani_env ** result)153 NO_UB_SANITIZE static ani_status GetEnv(ani_vm *vm, uint32_t version, ani_env **result)
154 {
155 ANI_DEBUG_TRACE(env);
156 ANI_CHECK_RETURN_IF_EQ(vm, nullptr, ANI_INVALID_ARGS);
157 ANI_CHECK_RETURN_IF_EQ(result, nullptr, ANI_INVALID_ARGS);
158
159 if (!IsVersionSupported(version)) {
160 return ANI_ERROR; // NOTE: Unsupported version?
161 }
162
163 PandaEtsVM *pandaVM = PandaEtsVM::FromAniVM(vm);
164 EtsCoroutine *coroutine = EtsCoroutine::CastFromThread(pandaVM->GetAssociatedThread());
165 if (coroutine == nullptr) {
166 LOG(ERROR, ANI) << "Cannot get environment";
167 return ANI_ERROR;
168 }
169 *result = coroutine->GetEtsNapiEnv();
170 return ANI_OK;
171 }
172
AttachCurrentThread(ani_vm * vm,const ani_options * options,uint32_t version,ani_env ** result)173 static ani_status AttachCurrentThread(ani_vm *vm, const ani_options *options, uint32_t version, ani_env **result)
174 {
175 ANI_DEBUG_TRACE(vm);
176 ANI_CHECK_RETURN_IF_EQ(vm, nullptr, ANI_INVALID_ARGS);
177 ANI_CHECK_RETURN_IF_EQ(result, nullptr, ANI_INVALID_ARGS);
178 ANI_CHECK_RETURN_IF_EQ(IsVersionSupported(version), false, ANI_INVALID_VERSION);
179
180 if (Thread::GetCurrent() != nullptr) {
181 LOG(ERROR, ANI) << "Cannot attach current thread, thread has already been attached";
182 return ANI_ERROR;
183 }
184
185 bool interopEnabled = false;
186 if (options != nullptr) {
187 for (size_t i = 0; i < options->nr_options; ++i) {
188 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
189 PandaString opt(options->options[i].option);
190 if (opt == "--interop=enable") {
191 interopEnabled = true;
192 } else if (opt == "--interop=disable") {
193 interopEnabled = false;
194 }
195 }
196 }
197
198 auto *runtime = Runtime::GetCurrent();
199 auto *etsVM = PandaEtsVM::FromAniVM(vm);
200 auto *coroMan = etsVM->GetCoroutineManager();
201 auto *exclusiveCoro = coroMan->CreateExclusiveWorkerForThread(runtime, etsVM);
202 if (exclusiveCoro == nullptr) {
203 LOG(ERROR, ANI) << "Cannot attach current thread, reached the limit of EAWorkers";
204 return ANI_ERROR;
205 }
206
207 ASSERT(exclusiveCoro == Coroutine::GetCurrent());
208
209 if (interopEnabled) {
210 auto *ifaceTable = EtsCoroutine::CastFromThread(coroMan->GetMainThread())->GetExternalIfaceTable();
211 auto *jsEnv = ifaceTable->CreateJSRuntime();
212 ASSERT(jsEnv != nullptr);
213 ifaceTable->CreateInteropCtx(exclusiveCoro, jsEnv);
214 }
215 *result = PandaEtsNapiEnv::GetCurrent();
216 return ANI_OK;
217 }
218
DetachCurrentThread(ani_vm * vm)219 static ani_status DetachCurrentThread(ani_vm *vm)
220 {
221 ANI_DEBUG_TRACE(vm);
222 ANI_CHECK_RETURN_IF_EQ(vm, nullptr, ANI_INVALID_ARGS);
223 if (Thread::GetCurrent() == nullptr) {
224 LOG(ERROR, ANI) << "Cannot detach current thread, thread is not attached";
225 return ANI_ERROR;
226 }
227
228 auto *etsVM = PandaEtsVM::FromAniVM(vm);
229 auto *coroMan = etsVM->GetCoroutineManager();
230 auto result = coroMan->DestroyExclusiveWorker();
231 if (!result) {
232 LOG(ERROR, ANI) << "Cannot DetachThread, thread was not attached";
233 return ANI_ERROR;
234 }
235 ASSERT(Thread::GetCurrent() == nullptr);
236 return ANI_OK;
237 }
238
239 // clang-format off
240 const __ani_vm_api VM_API = {
241 nullptr,
242 nullptr,
243 nullptr,
244 nullptr,
245 DestroyVM,
246 GetEnv,
247 AttachCurrentThread,
248 DetachCurrentThread,
249 };
250 // clang-format on
251
GetVMAPI()252 const __ani_vm_api *GetVMAPI()
253 {
254 return &VM_API;
255 }
256 } // namespace ark::ets::ani
257