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