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 "include/class.h"
17 #include "include/mem/panda_containers.h"
18 #include "include/method.h"
19 #include "plugins/ets/runtime/interop_js/js_proxy/js_proxy.h"
20 #include "plugins/ets/runtime/interop_js/interop_context.h"
21
22 #include "plugins/ets/runtime/types/ets_object.h"
23
24 namespace ark::ets::interop::js::js_proxy {
25
26 extern "C" void CallJSProxyBridge(Method *method, ...);
27 extern "C" void CallJSFunctionBridge(Method *method, ...);
28
29 using MethodMap = std::unordered_map<uint8_t const *, Method *, utf::Mutf8Hash, utf::Mutf8Equal>;
30
31 // Create JSProxy class descriptor that will respond to IsProxyClass
32 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
MakeProxyDescriptor(const uint8_t * descriptorP)33 static std::unique_ptr<uint8_t[]> MakeProxyDescriptor(const uint8_t *descriptorP)
34 {
35 Span<const uint8_t> descriptor(descriptorP, utf::Mutf8Size(descriptorP));
36
37 ASSERT(descriptor.size() > 2U);
38 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
39 ASSERT(descriptor[0] == 'L');
40 ASSERT(descriptor[descriptor.size() - 1] == ';');
41
42 size_t proxyDescriptorSize = descriptor.size() + 3U; // + $$\0
43 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
44 auto proxyDescriptorData = std::make_unique<uint8_t[]>(proxyDescriptorSize);
45 Span<uint8_t> proxyDescriptor(proxyDescriptorData.get(), proxyDescriptorSize);
46
47 proxyDescriptor[0] = 'L';
48 proxyDescriptor[1] = '$';
49 std::copy_n(&descriptor[1], descriptor.size() - 2U, &proxyDescriptor[2U]);
50 proxyDescriptor[proxyDescriptor.size() - 3U] = '$';
51 proxyDescriptor[proxyDescriptor.size() - 2U] = ';';
52 proxyDescriptor[proxyDescriptor.size() - 1U] = '\0';
53
54 return proxyDescriptorData;
55 }
56
GetAllInterfaceMethod(Class * interfaceCls,PandaVector<Method * > & methodPtrs,PandaSet<Class * > & methodPSet)57 static void GetAllInterfaceMethod(Class *interfaceCls, PandaVector<Method *> &methodPtrs, PandaSet<Class *> &methodPSet)
58 {
59 if (methodPSet.count(interfaceCls) != 0) {
60 return;
61 }
62 methodPSet.insert(interfaceCls);
63 auto methods = interfaceCls->GetMethods();
64 for (auto &method : methods) {
65 methodPtrs.push_back(&method);
66 }
67 for (auto *itf : interfaceCls->GetInterfaces()) {
68 GetAllInterfaceMethod(itf, methodPtrs, methodPSet);
69 }
70 }
71
GetInterfaceMethodDistinct(Class * interfaceCls,MethodMap & methodMap,PandaSet<Class * > & interfaceSet)72 static void GetInterfaceMethodDistinct(Class *interfaceCls, MethodMap &methodMap, PandaSet<Class *> &interfaceSet)
73 {
74 if (interfaceSet.count(interfaceCls) != 0) {
75 return;
76 }
77 interfaceSet.insert(interfaceCls);
78 auto methods = interfaceCls->GetMethods();
79 for (auto &method : methods) {
80 methodMap.insert({method.GetName().data, &method});
81 }
82 for (auto *itf : interfaceCls->GetInterfaces()) {
83 GetInterfaceMethodDistinct(itf, methodMap, interfaceSet);
84 }
85 }
86
InitProxyMethod(Class * cls,Method * src,Method * proxy,void * entryPoint)87 static void InitProxyMethod(Class *cls, Method *src, Method *proxy, void *entryPoint)
88 {
89 new (proxy) Method(src);
90
91 proxy->SetAccessFlags((src->GetAccessFlags() & ~(ACC_ABSTRACT | ACC_DEFAULT_INTERFACE_METHOD)) | ACC_FINAL);
92 proxy->SetClass(cls);
93 proxy->SetCompiledEntryPoint(entryPoint);
94 }
95
96 /*
97 * BuildProxyMethods
98 */
BuildProxyMethods(Class * cls,Span<Method * > targetMethods,void * entryPoint)99 static Span<Method> BuildProxyMethods(Class *cls, Span<Method *> targetMethods, void *entryPoint)
100 {
101 ClassLinker *classLinker = Runtime::GetCurrent()->GetClassLinker();
102
103 PandaVector<size_t> targetMethodsIdx;
104
105 for (size_t i = 0; i < targetMethods.size(); ++i) {
106 auto m = targetMethods[i];
107 if (!m->IsFinal()) { // NOTE(vpukhov): consider internal methods, final methods in builtins
108 targetMethodsIdx.push_back(i);
109 }
110 }
111
112 size_t const numTargets = targetMethodsIdx.size();
113 Span<Method> proxyMethods {classLinker->GetAllocator()->AllocArray<Method>(numTargets), numTargets};
114
115 for (size_t i = 0; i < numTargets; ++i) {
116 InitProxyMethod(cls, targetMethods[targetMethodsIdx[i]], &proxyMethods[i],
117 reinterpret_cast<void *>(entryPoint));
118 }
119
120 return proxyMethods;
121 }
122
123 /*static*/
CreateInterfaceProxy(const PandaSet<Class * > & interfaces,std::string & interfaceName)124 JSProxy *JSProxy::CreateInterfaceProxy(const PandaSet<Class *> &interfaces, std::string &interfaceName)
125 {
126 auto coro = EtsCoroutine::GetCurrent();
127 auto ctx = InteropCtx::Current(coro);
128 auto descriptor = MakeProxyDescriptor(utf::CStringAsMutf8(interfaceName.data()));
129 Class *objectClass = ctx->GetObjectClass();
130 ClassLinker *classLinker = Runtime::GetCurrent()->GetClassLinker();
131 ClassLinkerContext *context = objectClass->GetLoadContext();
132
133 Class *proxyCls = classLinker->GetClass(descriptor.get(), true, context);
134 if (proxyCls == nullptr) {
135 PandaSet<Class *> interfaceSet;
136 MethodMap methodMap;
137 for (auto cls : interfaces) {
138 GetInterfaceMethodDistinct(cls, methodMap, interfaceSet);
139 }
140
141 PandaVector<Method *> methodPtrs;
142 for (auto &[n, p] : methodMap) {
143 methodPtrs.push_back(p);
144 }
145 PandaVector<Class *> interfacesList = {interfaces.begin(), interfaces.end()};
146 return CreateProxy(descriptor.get(), objectClass, {methodPtrs.data(), methodPtrs.size()}, interfacesList,
147 reinterpret_cast<void *>(CallJSProxyBridge));
148 }
149
150 ASSERT(IsProxyClass(proxyCls));
151
152 auto jsProxy = Runtime::GetCurrent()->GetInternalAllocator()->New<JSProxy>(EtsClass::FromRuntimeClass(proxyCls));
153 return jsProxy;
154 }
155
156 /*static*/
CreateBuiltinProxy(EtsClass * etsClass,Span<Method * > targetMethods)157 JSProxy *JSProxy::CreateBuiltinProxy(EtsClass *etsClass, Span<Method *> targetMethods)
158 {
159 Class *cls = etsClass->GetRuntimeClass();
160 ASSERT(!IsProxyClass(cls) && !etsClass->IsFinal());
161 auto descriptor = MakeProxyDescriptor(cls->GetDescriptor());
162 return CreateProxy(descriptor.get(), cls, targetMethods, {}, reinterpret_cast<void *>(CallJSProxyBridge));
163 }
164
165 /*static*/
CreateFunctionProxy(EtsClass * functionInterface)166 JSProxy *JSProxy::CreateFunctionProxy(EtsClass *functionInterface)
167 {
168 auto coro = EtsCoroutine::GetCurrent();
169 auto ctx = InteropCtx::Current(coro);
170
171 Class *interfaceCls = functionInterface->GetRuntimeClass();
172 ASSERT(interfaceCls->IsInterface());
173
174 // create JSFunctoinProxy class descriptor that will respond to IsProxyClass
175 auto descriptor = MakeProxyDescriptor(interfaceCls->GetDescriptor());
176
177 // use `Object` as the base class for the proxy function
178 Class *objectClass = ctx->GetObjectClass();
179
180 // get the proxy function class if it is already created
181 // otherwise, create the class
182 ClassLinker *classLinker = Runtime::GetCurrent()->GetClassLinker();
183 ClassLinkerContext *context = objectClass->GetLoadContext();
184
185 Class *proxyFunctionCls = classLinker->GetClass(descriptor.get(), true, context);
186
187 if (proxyFunctionCls == nullptr) {
188 PandaSet<Class *> methodPSet;
189 PandaVector<Method *> methodPtrs;
190 GetAllInterfaceMethod(interfaceCls, methodPtrs, methodPSet);
191
192 return CreateProxy(descriptor.get(), objectClass, {methodPtrs.data(), methodPtrs.size()}, {interfaceCls},
193 reinterpret_cast<void *>(CallJSFunctionBridge));
194 }
195
196 ASSERT(IsProxyClass(proxyFunctionCls));
197
198 auto functionProxy =
199 Runtime::GetCurrent()->GetInternalAllocator()->New<JSProxy>(EtsClass::FromRuntimeClass(proxyFunctionCls));
200 return functionProxy;
201 }
202
203 /*static*/
CreateProxy(const uint8_t * descriptor,Class * baseClass,Span<Method * > targetMethods,const PandaVector<Class * > & interfaces,void * callBridge)204 JSProxy *JSProxy::CreateProxy(const uint8_t *descriptor, Class *baseClass, Span<Method *> targetMethods,
205 const PandaVector<Class *> &interfaces, void *callBridge)
206 {
207 ClassLinker *classLinker = Runtime::GetCurrent()->GetClassLinker();
208 ClassLinkerContext *context = baseClass->GetLoadContext();
209 auto proxyMethods = BuildProxyMethods(baseClass, targetMethods, callBridge);
210
211 uint32_t accessFlags = baseClass->GetAccessFlags() | ACC_PROXY | ACC_FINAL;
212 Span<Field> fields {};
213 Span<Class *> interfacesSpan {classLinker->GetAllocator()->AllocArray<Class *>(interfaces.size()),
214 interfaces.size()};
215 for (size_t i = 0; i < interfaces.size(); i++) {
216 interfacesSpan[i] = interfaces[i];
217 }
218
219 Class *proxyCls = classLinker->BuildClass(descriptor, true, accessFlags, proxyMethods, fields, baseClass,
220 interfacesSpan, context, false);
221 ASSERT(proxyCls != nullptr);
222 proxyCls->SetState(Class::State::INITIALIZING);
223 proxyCls->SetState(Class::State::INITIALIZED);
224
225 ASSERT(IsProxyClass(proxyCls));
226
227 auto jsProxy = Runtime::GetCurrent()->GetInternalAllocator()->New<JSProxy>(EtsClass::FromRuntimeClass(proxyCls));
228 return jsProxy;
229 }
230
231 } // namespace ark::ets::interop::js::js_proxy
232