• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2023-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 "plugins/ets/runtime/interop_js/ets_proxy/ets_proxy.h"
17 
18 #include "plugins/ets/runtime/ets_panda_file_items.h"
19 #include "plugins/ets/runtime/ets_utils.h"
20 #include "plugins/ets/runtime/interop_js/code_scopes.h"
21 #include "plugins/ets/runtime/interop_js/ets_proxy/ets_method_set.h"
22 #include "plugins/ets/runtime/interop_js/ets_proxy/ets_class_wrapper.h"
23 #include "plugins/ets/runtime/interop_js/ets_proxy/ets_method_wrapper.h"
24 #include "plugins/ets/runtime/interop_js/interop_context.h"
25 #include "plugins/ets/runtime/interop_js/js_refconvert_record.h"
26 
27 namespace ark::ets::interop::js::ets_proxy {
28 
GetETSFunction(napi_env env,std::string_view packageName,std::string_view methodName)29 napi_value GetETSFunction(napi_env env, std::string_view packageName, std::string_view methodName)
30 {
31     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
32     INTEROP_CODE_SCOPE_JS(coro);
33 
34     std::ostringstream classDescriptorBuilder;
35     classDescriptorBuilder << "L" << packageName << (packageName.empty() ? "ETSGLOBAL;" : "/ETSGLOBAL;");
36     std::string classDescriptor = classDescriptorBuilder.str();
37 
38     if (IsEtsGlobalClassName(packageName.data())) {
39         // Old-style value for legacy code
40         // NOTE remove this check after all tests fixed
41         classDescriptor = packageName;
42     }
43 
44     napi_value jsClass = GetETSClass(env, classDescriptor);
45     ASSERT(GetValueType(env, jsClass) == napi_function);
46 
47     napi_value jsMethod;
48     const napi_status resolveStatus = napi_get_named_property(env, jsClass, methodName.data(), &jsMethod);
49     if (UNLIKELY(napi_ok != resolveStatus || GetValueType(env, jsMethod) != napi_function)) {
50         InteropCtx::ThrowJSError(env, "GetETSFunction: class " + std::string(classDescriptor) + " doesn't contain " +
51                                           std::string(methodName) + " method");
52         return nullptr;
53     }
54     NAPI_CHECK_FATAL(napi_object_seal(env, jsMethod));
55 
56     return jsMethod;
57 }
58 
GetETSClassImpl(napi_env env,std::string_view classDescriptor)59 napi_value GetETSClassImpl(napi_env env, std::string_view classDescriptor)
60 {
61     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
62     InteropCtx *ctx = InteropCtx::Current(coro);
63 
64     EtsClass *etsKlass = coro->GetPandaVM()->GetClassLinker()->GetClass(classDescriptor.data(), true, ctx->LinkerCtx());
65     if (UNLIKELY(etsKlass == nullptr)) {
66         ctx->ForwardEtsException(coro);
67         return nullptr;
68     }
69 
70     EtsClassWrapper *etsClassWrapper = EtsClassWrapper::Get(ctx, etsKlass);
71     if (UNLIKELY(etsClassWrapper == nullptr)) {
72         return nullptr;
73     }
74 
75     return etsClassWrapper->GetJsCtor(env);
76 }
77 
CreateEtsRecordInstance(napi_env env)78 napi_value CreateEtsRecordInstance(napi_env env)
79 {
80     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
81     InteropCtx *ctx = InteropCtx::Current(coro);
82 
83     INTEROP_CODE_SCOPE_JS(coro);
84     ScopedManagedCodeThread managedScope(coro);
85 
86     EtsClass *etsClass = PlatformTypes()->escompatRecord;
87     EtsObject *etsInstance = etsClass->CreateInstance();
88     if (UNLIKELY(etsInstance == nullptr)) {
89         ASSERT(coro->HasPendingException());
90         InteropCtx::ThrowJSError(env, "Failed to create ETS record instance");
91         return nullptr;
92     }
93 
94     JSRefConvertRecord converter(ctx);
95     napi_value jsWrapper = converter.WrapImpl(ctx, etsInstance);
96 
97     return jsWrapper;
98 }
99 
GetETSInstance(napi_env env,std::string_view classDescriptor)100 napi_value GetETSInstance(napi_env env, std::string_view classDescriptor)
101 {
102     if (classDescriptor == ark::ets::panda_file_items::class_descriptors::RECORD) {
103         return CreateEtsRecordInstance(env);
104     }
105 
106     InteropCtx::ThrowJSError(env, "Unsupported ETS instance type: " + std::string(classDescriptor));
107     return nullptr;
108 }
109 
GetETSClass(napi_env env,std::string_view classDescriptor)110 napi_value GetETSClass(napi_env env, std::string_view classDescriptor)
111 {
112     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
113     INTEROP_CODE_SCOPE_JS(coro);
114     ScopedManagedCodeThread managedScope(coro);
115 
116     return GetETSClassImpl(env, classDescriptor);
117 }
118 
FillExportedClasses(napi_env env,EtsClass * globalClass,napi_value moduleObject)119 static void FillExportedClasses(napi_env env, EtsClass *globalClass, napi_value moduleObject)
120 {
121     std::vector<std::string> exportedClasses;
122     if (!GetExportedClassDescriptorsFromModule(globalClass, exportedClasses)) {
123         return;
124     }
125 
126     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
127     InteropCtx *ctx = InteropCtx::Current(coro);
128     ClassLinkerContext *ctxForLoad = globalClass->GetLoadContext();
129     auto *classLinker = coro->GetPandaVM()->GetClassLinker();
130 
131     for (const std::string &clsDesc : exportedClasses) {
132         EtsClass *exportedKlass = classLinker->GetClass(clsDesc.c_str(), true, ctxForLoad);
133         if (exportedKlass == nullptr) {
134             LOG(WARNING, INTEROP) << "Failed to resolve exported class: " << clsDesc;
135             continue;
136         }
137 
138         EtsClassWrapper *wrapper = EtsClassWrapper::Get(ctx, exportedKlass);
139         if (wrapper == nullptr) {
140             LOG(WARNING, INTEROP) << "Failed to get wrapper for exported class: " << clsDesc;
141             continue;
142         }
143 
144         napi_value clsProxy = wrapper->GetJsCtor(env);
145 
146         std::string simpleName = clsDesc.substr(clsDesc.find_last_of('/') + 1);
147         if (!simpleName.empty() && simpleName.back() == ';') {
148             simpleName.pop_back();
149         }
150 
151         NAPI_CHECK_FATAL(napi_set_named_property(env, moduleObject, simpleName.c_str(), clsProxy));
152     }
153 }
154 
CopyNamedProperties(napi_env env,napi_value from,napi_value to)155 static void CopyNamedProperties(napi_env env, napi_value from, napi_value to)
156 {
157     auto *coro = EtsCoroutine::GetCurrent();
158     ScopedNativeCodeThread etsNativeScope(coro);
159     napi_value keys;
160     NAPI_CHECK_FATAL(napi_get_property_names(env, from, &keys));
161     uint32_t len;
162     NAPI_CHECK_FATAL(napi_get_array_length(env, keys, &len));
163     for (uint32_t i = 0; i < len; i++) {
164         napi_value key;
165         NAPI_CHECK_FATAL(napi_get_element(env, keys, i, &key));
166         napi_value val;
167         NAPI_CHECK_FATAL(napi_get_property(env, from, key, &val));
168         NAPI_CHECK_FATAL(napi_set_property(env, to, key, val));
169     }
170 }
171 
ProcessModuleRecursive(napi_env env,EtsClass * globalClass,napi_value moduleObject,std::unordered_set<EtsClass * > & visitedModules)172 static void ProcessModuleRecursive(napi_env env, EtsClass *globalClass, napi_value moduleObject,
173                                    std::unordered_set<EtsClass *> &visitedModules)
174 {
175     if (visitedModules.count(globalClass) > 0) {
176         return;
177     }
178     visitedModules.insert(globalClass);
179 
180     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
181     InteropCtx *ctx = InteropCtx::Current(coro);
182 
183     napi_value globalProxy = EtsClassWrapper::Get(ctx, globalClass)->GetJsCtor(env);
184     FillExportedClasses(env, globalClass, moduleObject);
185     CopyNamedProperties(env, globalProxy, moduleObject);
186 
187     std::vector<std::string> exportedClasses;
188     if (!GetExportedClassDescriptorsFromModule(globalClass, exportedClasses)) {
189         return;
190     }
191 
192     auto *classLinker = coro->GetPandaVM()->GetClassLinker();
193     auto *ctxForLoad = globalClass->GetLoadContext();
194 
195     for (const auto &clsDesc : exportedClasses) {
196         EtsClass *exportedKlass = classLinker->GetClass(clsDesc.c_str(), true, ctxForLoad);
197         if (exportedKlass == nullptr) {
198             continue;
199         }
200 
201         if (exportedKlass->IsModule()) {
202             ProcessModuleRecursive(env, exportedKlass, moduleObject, visitedModules);
203         }
204     }
205 }
206 
GetETSModule(napi_env env,const std::string & moduleName)207 napi_value GetETSModule(napi_env env, const std::string &moduleName)
208 {
209     EtsCoroutine *coro = EtsCoroutine::GetCurrent();
210     if (coro == nullptr) {
211         return InteropCtx::CreateJSTypeError(env, "Static context not loaded", "");
212     }
213     ScopedManagedCodeThread managedScope(coro);
214     InteropCtx *ctx = InteropCtx::Current(coro);
215 
216     std::string descriptor = "L" + moduleName + "/ETSGLOBAL;";
217     EtsClass *globalClass = coro->GetPandaVM()->GetClassLinker()->GetClass(descriptor.c_str(), true, ctx->LinkerCtx());
218     if (globalClass == nullptr) {
219         ctx->ForwardEtsException(coro);
220         return nullptr;
221     }
222 
223     napi_value moduleObject;
224     NAPI_CHECK_FATAL(napi_create_object(env, &moduleObject));
225 
226     std::unordered_set<EtsClass *> visitedModules;
227     ProcessModuleRecursive(env, globalClass, moduleObject, visitedModules);
228     return moduleObject;
229 }
230 
231 }  // namespace ark::ets::interop::js::ets_proxy
232