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