1 /**
2 * Copyright (c) 2023-2024 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/shared_reference.h"
17 #include "plugins/ets/runtime/interop_js/interop_context.h"
18 #include "plugins/ets/runtime/interop_js/js_convert.h"
19 #include "plugins/ets/runtime/types/ets_box_primitive.h"
20 #include "runtime/mem/local_object_handle.h"
21
22 #include <node_api.h>
23
24 namespace ark::ets::interop::js::ets_proxy {
25
CBDoNothing(napi_env env,void * data,void * hint)26 static void CBDoNothing([[maybe_unused]] napi_env env, [[maybe_unused]] void *data, [[maybe_unused]] void *hint) {}
27
InitETSObject(InteropCtx * ctx,EtsObject * etsObject,napi_value jsObject,uint32_t refIdx)28 bool SharedReference::InitETSObject(InteropCtx *ctx, EtsObject *etsObject, napi_value jsObject, uint32_t refIdx)
29 {
30 SetFlags(HasETSObject::Encode(true) | HasJSObject::Encode(false));
31
32 auto env = ctx->GetJSEnv();
33 if (UNLIKELY(napi_ok != NapiWrap(env, jsObject, this, FinalizeJSWeak, nullptr, &jsRef_))) {
34 return false;
35 }
36
37 etsObject->SetInteropHash(refIdx);
38 etsRef_ = ctx->Refstor()->Add(etsObject->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
39 if (UNLIKELY(etsRef_ == nullptr)) {
40 INTEROP_LOG(ERROR) << "REFERENCE STORAGE OVERFLOW";
41 ctx->ThrowJSError(env, "ets refstor overflow");
42 return false;
43 }
44 return true;
45 }
46
InitJSObject(InteropCtx * ctx,EtsObject * etsObject,napi_value jsObject,uint32_t refIdx)47 bool SharedReference::InitJSObject(InteropCtx *ctx, EtsObject *etsObject, napi_value jsObject, uint32_t refIdx)
48 {
49 SetFlags(HasETSObject::Encode(false) | HasJSObject::Encode(true));
50
51 auto coro = EtsCoroutine::GetCurrent();
52 auto env = ctx->GetJSEnv();
53 if (UNLIKELY(napi_ok != NapiWrap(env, jsObject, this, CBDoNothing, nullptr, &jsRef_))) {
54 return false;
55 }
56 NAPI_CHECK_FATAL(napi_reference_ref(env, jsRef_, nullptr));
57
58 LocalObjectHandle<EtsObject> handle(coro, etsObject); // object may have no strong refs, so create one
59 handle->SetInteropHash(refIdx);
60 // NOTE(vpukhov): reuse weakref from finalizationRegistry
61 etsRef_ = ctx->Refstor()->Add(etsObject->GetCoreType(), mem::Reference::ObjectType::WEAK);
62
63 auto boxLong = EtsBoxPrimitive<EtsLong>::Create(EtsCoroutine::GetCurrent(), ToUintPtr(this));
64 if (UNLIKELY(boxLong == nullptr ||
65 !ctx->PushOntoFinalizationRegistry(EtsCoroutine::GetCurrent(), handle.GetPtr(), boxLong))) {
66 NAPI_CHECK_FATAL(napi_delete_reference(env, jsRef_));
67 return false;
68 }
69 return true;
70 }
71
72 // NOTE(vpukhov): Circular interop references
73 // Present solution is dummy and consists of two strong refs
InitHybridObject(InteropCtx * ctx,EtsObject * etsObject,napi_value jsObject,uint32_t refIdx)74 bool SharedReference::InitHybridObject(InteropCtx *ctx, EtsObject *etsObject, napi_value jsObject, uint32_t refIdx)
75 {
76 SetFlags(HasETSObject::Encode(true) | HasJSObject::Encode(true));
77
78 auto env = ctx->GetJSEnv();
79 if (UNLIKELY(napi_ok != NapiWrap(env, jsObject, this, CBDoNothing, nullptr, &jsRef_))) {
80 return false;
81 }
82 NAPI_CHECK_FATAL(napi_reference_ref(env, jsRef_, nullptr));
83
84 etsObject->SetInteropHash(refIdx);
85 etsRef_ = ctx->Refstor()->Add(etsObject->GetCoreType(), mem::Reference::ObjectType::GLOBAL);
86 if (UNLIKELY(etsRef_ == nullptr)) {
87 INTEROP_LOG(ERROR) << "REFERENCE STORAGE OVERFLOW";
88 ctx->ThrowJSError(env, "ets refstor overflow");
89 NAPI_CHECK_FATAL(napi_delete_reference(env, jsRef_));
90 return false;
91 }
92 return true;
93 }
94
95 /*static*/
FinalizeJSWeak(napi_env env,void * data,void * hint)96 void SharedReference::FinalizeJSWeak([[maybe_unused]] napi_env env, void *data, [[maybe_unused]] void *hint)
97 {
98 if (UNLIKELY(Runtime::GetCurrent() == nullptr)) {
99 // Runtime was destroyed, no need to cleanup
100 return;
101 }
102 EtsCoroutine *coro = EtsCoroutine::GetCurrent();
103 ASSERT(coro != nullptr);
104 InteropCtx *ctx = InteropCtx::Current(coro);
105 ScopedManagedCodeThread scope(coro);
106
107 auto ref = reinterpret_cast<SharedReference *>(data);
108 ASSERT(ref->etsRef_ != nullptr);
109
110 ref->GetEtsObject(ctx)->DropInteropHash();
111 ctx->Refstor()->Remove(ref->etsRef_);
112 ctx->GetSharedRefStorage()->RemoveReference(ref);
113 }
114
115 /*static*/
FinalizeETSWeak(InteropCtx * ctx,EtsObject * cbarg)116 void SharedReference::FinalizeETSWeak(InteropCtx *ctx, EtsObject *cbarg)
117 {
118 ASSERT(cbarg->GetClass()->GetRuntimeClass() == ctx->GetBoxLongClass());
119 auto boxLong = FromEtsObject<EtsBoxPrimitive<EtsLong>>(cbarg);
120
121 auto sharedRef = ToNativePtr<SharedReference>(static_cast<uintptr_t>(boxLong->GetValue()));
122
123 NAPI_CHECK_FATAL(napi_delete_reference(ctx->GetJSEnv(), sharedRef->jsRef_));
124 ctx->GetSharedRefStorage()->RemoveReference(sharedRef);
125 }
126
127 } // namespace ark::ets::interop::js::ets_proxy
128