1/*
2 * Copyright (c) 2023 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 "ecmascript/serializer/value_serializer.h"
17
18#include "ecmascript/checkpoint/thread_state_transition.h"
19#include "ecmascript/base/array_helper.h"
20
21namespace panda::ecmascript {
22
23bool ValueSerializer::CheckObjectCanSerialize(TaggedObject *object, bool &findSharedObject)
24{
25    JSType type = object->GetClass()->GetObjectType();
26    if (IsInternalJSType(type)) {
27        return true;
28    }
29    switch (type) {
30        case JSType::JS_ERROR:
31        case JSType::JS_EVAL_ERROR:
32        case JSType::JS_RANGE_ERROR:
33        case JSType::JS_REFERENCE_ERROR:
34        case JSType::JS_TYPE_ERROR:
35        case JSType::JS_AGGREGATE_ERROR:
36        case JSType::JS_URI_ERROR:
37        case JSType::JS_SYNTAX_ERROR:
38        case JSType::JS_OOM_ERROR:
39        case JSType::JS_TERMINATION_ERROR:
40        case JSType::JS_DATE:
41        case JSType::JS_ARRAY:
42        case JSType::JS_MAP:
43        case JSType::JS_SET:
44        case JSType::JS_REG_EXP:
45        case JSType::JS_INT8_ARRAY:
46        case JSType::JS_UINT8_ARRAY:
47        case JSType::JS_UINT8_CLAMPED_ARRAY:
48        case JSType::JS_INT16_ARRAY:
49        case JSType::JS_UINT16_ARRAY:
50        case JSType::JS_INT32_ARRAY:
51        case JSType::JS_UINT32_ARRAY:
52        case JSType::JS_FLOAT32_ARRAY:
53        case JSType::JS_FLOAT64_ARRAY:
54        case JSType::JS_BIGINT64_ARRAY:
55        case JSType::JS_BIGUINT64_ARRAY:
56        case JSType::JS_ARRAY_BUFFER:
57        case JSType::JS_SHARED_ARRAY_BUFFER:
58        case JSType::LINE_STRING:
59        case JSType::CONSTANT_STRING:
60        case JSType::TREE_STRING:
61        case JSType::SLICED_STRING:
62        case JSType::JS_OBJECT:
63        case JSType::JS_ASYNC_FUNCTION:  // means CONCURRENT_FUNCTION
64            return true;
65        case JSType::JS_SHARED_SET:
66        case JSType::JS_SHARED_MAP:
67        case JSType::JS_SENDABLE_ARRAY_BUFFER:
68        case JSType::JS_SHARED_ARRAY:
69        case JSType::JS_SHARED_INT8_ARRAY:
70        case JSType::JS_SHARED_UINT8_ARRAY:
71        case JSType::JS_SHARED_UINT8_CLAMPED_ARRAY:
72        case JSType::JS_SHARED_INT16_ARRAY:
73        case JSType::JS_SHARED_UINT16_ARRAY:
74        case JSType::JS_SHARED_INT32_ARRAY:
75        case JSType::JS_SHARED_UINT32_ARRAY:
76        case JSType::JS_SHARED_FLOAT32_ARRAY:
77        case JSType::JS_SHARED_FLOAT64_ARRAY:
78        case JSType::JS_SHARED_BIGINT64_ARRAY:
79        case JSType::JS_SHARED_BIGUINT64_ARRAY:
80        case JSType::JS_SHARED_OBJECT:
81        case JSType::JS_SHARED_FUNCTION:
82        case JSType::JS_SHARED_ASYNC_FUNCTION: {
83            if (serializeSharedEvent_ > 0) {
84                return true;
85            }
86            if (defaultCloneShared_ || cloneSharedSet_.find(ToUintPtr(object)) != cloneSharedSet_.end()) {
87                findSharedObject = true;
88                serializeSharedEvent_++;
89            }
90            return true;
91        }
92        default:
93            break;
94    }
95    LOG_ECMA(ERROR) << "Unsupport serialize object type: " << JSHClass::DumpJSType(type);
96    return false;
97}
98
99bool ValueSerializer::WriteValue(JSThread *thread,
100                                 const JSHandle<JSTaggedValue> &value,
101                                 const JSHandle<JSTaggedValue> &transfer,
102                                 const JSHandle<JSTaggedValue> &cloneList)
103{
104    ECMA_BYTRACE_NAME(HITRACE_TAG_ARK, "ValueSerializer::WriteValue");
105    ASSERT(!value->IsWeak());
106    if (!defaultTransfer_ && !PrepareTransfer(thread, transfer)) {
107        LOG_ECMA(ERROR) << "ValueSerialize: PrepareTransfer fail";
108        data_->SetIncompleteData(true);
109        return false;
110    }
111    if (!defaultCloneShared_ && !PrepareClone(thread, cloneList)) {
112        LOG_ECMA(ERROR) << "ValueSerialize: PrepareClone fail";
113        data_->SetIncompleteData(true);
114        return false;
115    }
116    SerializeJSTaggedValue(value.GetTaggedValue());
117    // ThreadNativeScope may trigger moving gc, so PushSerializationRoot must do before native state.
118    // Push share root object to runtime map
119    uint32_t index = data_->GetDataIndex();
120    bool chunkEmpty = sharedObjChunk_->Empty();
121    if (!chunkEmpty) {
122        index = Runtime::GetInstance()->PushSerializationRoot(thread_, std::move(sharedObjChunk_));
123    }
124    {
125        ThreadNativeScope nativeScope(thread);
126        for (auto &entry : detachCallbackInfo_) {
127            auto info = entry.second;
128            DetachFunc detachNative = reinterpret_cast<DetachFunc>(info->detachFunc);
129            if (detachNative == nullptr || entry.first < 0) {
130                LOG_ECMA(ERROR) << "ValueSerialize: SerializeNativeBindingObject detachNative == nullptr";
131                notSupport_ = true;
132                break;
133            }
134            void *buffer = detachNative(info->env, info->nativeValue, info->hint, info->detachData);
135            data_->EmitU64(reinterpret_cast<uint64_t>(buffer), static_cast<size_t>(entry.first));
136        }
137    }
138    if (notSupport_) {
139        LOG_ECMA(ERROR) << "ValueSerialize: serialize data is incomplete";
140        data_->SetIncompleteData(true);
141        if (!chunkEmpty) {
142            // If notSupport, serializeRoot should be removed.
143            Runtime::GetInstance()->RemoveSerializationRoot(thread_, index);
144        }
145        return false;
146    }
147    if (!chunkEmpty) {
148        data_->SetDataIndex(index);
149    }
150    size_t maxSerializerSize = vm_->GetEcmaParamConfiguration().GetMaxJSSerializerSize();
151    if (data_->Size() > maxSerializerSize) {
152        LOG_ECMA(ERROR) << "The serialization data size has exceed limit Size, current size is: " << data_->Size()
153                        << " max size is: " << maxSerializerSize;
154        return false;
155    }
156    return true;
157}
158
159void ValueSerializer::SerializeObjectImpl(TaggedObject *object, bool isWeak)
160{
161    if (notSupport_) {
162        return;
163    }
164    bool cloneSharedObject = false;
165    if (!CheckObjectCanSerialize(object, cloneSharedObject)) {
166        notSupport_ = true;
167        return;
168    }
169    if (isWeak) {
170        data_->WriteEncodeFlag(EncodeFlag::WEAK);
171    }
172    if (SerializeReference(object) || SerializeRootObject(object)) {
173        return;
174    }
175    Region *region = Region::ObjectAddressToRange(object);
176    if (object->GetClass()->IsString() || object->GetClass()->IsMethod() || region->InSharedReadOnlySpace() ||
177        (serializeSharedEvent_ == 0 && region->InSharedHeap())) {
178        SerializeSharedObject(object);
179        return;
180    }
181    if (object->GetClass()->IsNativeBindingObject()) {
182        SerializeNativeBindingObject(object);
183        return;
184    }
185    if (object->GetClass()->IsJSError()) {
186        SerializeJSError(object);
187        return;
188    }
189    bool arrayBufferDeferDetach = false;
190    JSTaggedValue trackInfo;
191    JSTaggedType hashfield = JSTaggedValue::VALUE_ZERO;
192    JSType type = object->GetClass()->GetObjectType();
193    // serialize prologue
194    switch (type) {
195        case JSType::JS_ARRAY_BUFFER: {
196            supportJSNativePointer_ = true;
197            arrayBufferDeferDetach = SerializeJSArrayBufferPrologue(object);
198            break;
199        }
200        case JSType::JS_SHARED_ARRAY_BUFFER: {
201            supportJSNativePointer_ = true;
202            SerializeJSSharedArrayBufferPrologue(object);
203            break;
204        }
205        case JSType::JS_SENDABLE_ARRAY_BUFFER: {
206            supportJSNativePointer_ = true;
207            SerializeJSSendableArrayBufferPrologue(object);
208            break;
209        }
210        case JSType::JS_ARRAY: {
211            JSArray *array = reinterpret_cast<JSArray *>(object);
212            trackInfo = array->GetTrackInfo();
213            array->SetTrackInfo(thread_, JSTaggedValue::Undefined());
214            break;
215        }
216        case JSType::JS_REG_EXP: {
217            supportJSNativePointer_ = true;
218            SerializeJSRegExpPrologue(reinterpret_cast<JSRegExp *>(object));
219            break;
220        }
221        case JSType::JS_OBJECT: {
222            hashfield = Barriers::GetValue<JSTaggedType>(object, JSObject::HASH_OFFSET);
223            Barriers::SetPrimitive<JSTaggedType>(object, JSObject::HASH_OFFSET, JSTaggedValue::VALUE_ZERO);
224            break;
225        }
226        default:
227            break;
228    }
229
230    // serialize object here
231    SerializeTaggedObject<SerializeType::VALUE_SERIALIZE>(object);
232
233    // serialize epilogue
234    switch (type) {
235        case JSType::JS_ARRAY_BUFFER:
236        case JSType::JS_SHARED_ARRAY_BUFFER:
237        case JSType::JS_SENDABLE_ARRAY_BUFFER:
238        case JSType::JS_REG_EXP:
239            // JSNativePointer supports serialization only during serialize JSArrayBuffer,
240            // JSSharedArrayBuffer and JSRegExp
241            supportJSNativePointer_ = false;
242            break;
243        case JSType::JS_ARRAY: {
244            JSArray *array = reinterpret_cast<JSArray *>(object);
245            array->SetTrackInfo(thread_, trackInfo);
246            break;
247        }
248        case JSType::JS_OBJECT: {
249            if (JSTaggedValue(hashfield).IsHeapObject()) {
250                Barriers::SetObject<true>(thread_, object, JSObject::HASH_OFFSET, hashfield);
251            } else {
252                Barriers::SetPrimitive<JSTaggedType>(object, JSObject::HASH_OFFSET, hashfield);
253            }
254            break;
255        }
256        default:
257            break;
258    }
259    if (cloneSharedObject) {
260        serializeSharedEvent_--;
261    }
262    if (arrayBufferDeferDetach) {
263        ASSERT(object->GetClass()->IsArrayBuffer());
264        JSArrayBuffer *arrayBuffer = reinterpret_cast<JSArrayBuffer *>(object);
265        arrayBuffer->Detach(thread_, arrayBuffer->GetWithNativeAreaAllocator(), true);
266    }
267}
268
269void ValueSerializer::SerializeJSError(TaggedObject *object)
270{
271    [[maybe_unused]] EcmaHandleScope scope(thread_);
272    data_->WriteEncodeFlag(EncodeFlag::JS_ERROR);
273    JSType type = object->GetClass()->GetObjectType();
274    ASSERT(type >= JSType::JS_ERROR_FIRST && type <= JSType::JS_ERROR_LAST);
275    data_->WriteUint8(static_cast<uint8_t>(type));
276    auto globalConst = thread_->GlobalConstants();
277    JSHandle<JSTaggedValue> handleMsg = globalConst->GetHandledMessageString();
278    JSHandle<JSTaggedValue> msg =
279        JSObject::GetProperty(thread_, JSHandle<JSTaggedValue>(thread_, object), handleMsg).GetValue();
280    if (msg->IsString()) {
281        data_->WriteUint8(1); // 1: msg is string
282        // string must be shared
283        SerializeSharedObject(msg->GetTaggedObject());
284    } else {
285        data_->WriteUint8(0); // 0: msg is undefined
286    }
287}
288
289void ValueSerializer::SerializeNativeBindingObject(TaggedObject *object)
290{
291    [[maybe_unused]] EcmaHandleScope scope(thread_);
292    JSHandle<GlobalEnv> env = vm_->GetGlobalEnv();
293    JSHandle<JSTaggedValue> nativeBindingSymbol = env->GetNativeBindingSymbol();
294    JSHandle<JSTaggedValue> nativeBindingValue =
295        JSObject::GetProperty(thread_, JSHandle<JSObject>(thread_, object), nativeBindingSymbol).GetRawValue();
296    if (!nativeBindingValue->IsJSNativePointer()) {
297        LOG_ECMA(ERROR) << "ValueSerialize: SerializeNativeBindingObject nativeBindingValue is not JSNativePointer";
298        notSupport_ = true;
299        return;
300    }
301    auto info = reinterpret_cast<panda::JSNApi::NativeBindingInfo *>(
302        JSNativePointer::Cast(nativeBindingValue->GetTaggedObject())->GetExternalPointer());
303    if (info == nullptr) {
304        LOG_ECMA(ERROR) << "ValueSerialize: SerializeNativeBindingObject NativeBindingInfo is nullptr";
305        notSupport_ = true;
306        return;
307    }
308    void *hint = info->hint;
309    void *attachData = info->attachData;
310    AttachFunc attachNative = reinterpret_cast<AttachFunc>(info->attachFunc);
311    data_->WriteEncodeFlag(EncodeFlag::NATIVE_BINDING_OBJECT);
312    data_->WriteJSTaggedType(reinterpret_cast<JSTaggedType>(attachNative));
313    ssize_t offset = data_->EmitU64(0); // 0 is a placeholder which will be filled later
314    detachCallbackInfo_.push_back({offset, info});
315    data_->WriteJSTaggedType(reinterpret_cast<JSTaggedType>(hint));
316    data_->WriteJSTaggedType(reinterpret_cast<JSTaggedType>(attachData));
317}
318
319bool ValueSerializer::SerializeJSArrayBufferPrologue(TaggedObject *object)
320{
321    ASSERT(object->GetClass()->IsArrayBuffer());
322    JSArrayBuffer *arrayBuffer = reinterpret_cast<JSArrayBuffer *>(object);
323    if (arrayBuffer->IsDetach()) {
324        LOG_ECMA(ERROR) << "ValueSerialize: don't support serialize detached array buffer";
325        notSupport_ = true;
326        return false;
327    }
328    bool transfer = transferDataSet_.find(ToUintPtr(object)) != transferDataSet_.end();
329    bool clone = cloneArrayBufferSet_.find(ToUintPtr(object)) != cloneArrayBufferSet_.end();
330    size_t arrayLength = arrayBuffer->GetArrayBufferByteLength();
331    if (arrayLength > 0) {
332        if (transfer) {
333            if (clone) {
334                notSupport_ = true;
335                LOG_ECMA(ERROR) << "ValueSerialize: can't put arraybuffer in both transfer list and clone list";
336                return false;
337            }
338            data_->WriteEncodeFlag(EncodeFlag::TRANSFER_ARRAY_BUFFER);
339            return true;
340        } else if (clone || !defaultTransfer_) {
341            bool nativeAreaAllocated = arrayBuffer->GetWithNativeAreaAllocator();
342            if (!nativeAreaAllocated) {
343                LOG_ECMA(ERROR) << "ValueSerialize: don't support clone arraybuffer has external allocated buffer, \
344                    considering transfer it";
345                notSupport_ = true;
346                return false;
347            }
348            data_->WriteEncodeFlag(EncodeFlag::ARRAY_BUFFER);
349            data_->WriteUint32(arrayLength);
350            JSNativePointer *np =
351                reinterpret_cast<JSNativePointer *>(arrayBuffer->GetArrayBufferData().GetTaggedObject());
352            data_->WriteRawData(static_cast<uint8_t *>(np->GetExternalPointer()), arrayLength);
353            return false;
354        } else {
355            data_->WriteEncodeFlag(EncodeFlag::TRANSFER_ARRAY_BUFFER);
356            return true;
357        }
358    }
359    return false;
360}
361
362void ValueSerializer::SerializeJSSharedArrayBufferPrologue(TaggedObject *object)
363{
364    ASSERT(object->GetClass()->IsSharedArrayBuffer());
365    JSArrayBuffer *arrayBuffer = reinterpret_cast<JSArrayBuffer *>(object);
366    bool transfer = transferDataSet_.find(ToUintPtr(object)) != transferDataSet_.end();
367    if (arrayBuffer->IsDetach() || transfer) {
368        LOG_ECMA(ERROR) << "ValueSerialize: don't support serialize detached or transfer shared array buffer";
369        notSupport_ = true;
370        return;
371    }
372    size_t arrayLength = arrayBuffer->GetArrayBufferByteLength();
373    if (arrayLength > 0) {
374        JSNativePointer *np = reinterpret_cast<JSNativePointer *>(arrayBuffer->GetArrayBufferData().GetTaggedObject());
375        void *buffer = np->GetExternalPointer();
376        if (JSSharedMemoryManager::GetInstance()->CreateOrLoad(&buffer, arrayLength)) {
377            LOG_ECMA(ERROR) << "ValueSerialize: can't find buffer form shared memory pool";
378            notSupport_ = true;
379            return;
380        }
381        data_->WriteEncodeFlag(EncodeFlag::SHARED_ARRAY_BUFFER);
382        data_->insertSharedArrayBuffer(reinterpret_cast<uintptr_t>(buffer));
383    }
384}
385
386void ValueSerializer::SerializeJSSendableArrayBufferPrologue(TaggedObject *object)
387{
388    ASSERT(object->GetClass()->IsSendableArrayBuffer());
389    JSSendableArrayBuffer *arrayBuffer = reinterpret_cast<JSSendableArrayBuffer *>(object);
390    if (arrayBuffer->IsDetach()) {
391        LOG_ECMA(ERROR) << "ValueSerialize: don't support serialize detached sendable array buffer";
392        notSupport_ = true;
393        return;
394    }
395    size_t arrayLength = arrayBuffer->GetArrayBufferByteLength();
396    if (arrayLength > 0) {
397        bool nativeAreaAllocated = arrayBuffer->GetWithNativeAreaAllocator();
398        if (!nativeAreaAllocated) {
399            LOG_ECMA(ERROR) << "ValueSerialize: don't support clone sendablearraybuffer has external allocated buffer";
400            notSupport_ = true;
401            return;
402        }
403        data_->WriteEncodeFlag(EncodeFlag::SENDABLE_ARRAY_BUFFER);
404        data_->WriteUint32(arrayLength);
405        JSNativePointer *np =
406            reinterpret_cast<JSNativePointer *>(arrayBuffer->GetArrayBufferData().GetTaggedObject());
407        data_->WriteRawData(static_cast<uint8_t *>(np->GetExternalPointer()), arrayLength);
408    }
409}
410
411void ValueSerializer::SerializeJSRegExpPrologue(JSRegExp *jsRegExp)
412{
413    uint32_t bufferSize = jsRegExp->GetLength();
414    if (bufferSize == 0) {
415        LOG_ECMA(ERROR) << "ValueSerialize: JSRegExp buffer size is 0";
416        notSupport_ = true;
417        return;
418    }
419
420    data_->WriteEncodeFlag(EncodeFlag::JS_REG_EXP);
421    data_->WriteUint32(bufferSize);
422    JSNativePointer *np =
423        reinterpret_cast<JSNativePointer *>(jsRegExp->GetByteCodeBuffer().GetTaggedObject());
424    data_->WriteRawData(static_cast<uint8_t *>(np->GetExternalPointer()), bufferSize);
425}
426
427bool ValueSerializer::PrepareTransfer(JSThread *thread, const JSHandle<JSTaggedValue> &transfer)
428{
429    if (transfer->IsUndefined()) {
430        return true;
431    }
432    if (!transfer->IsJSArray()) {
433        return false;
434    }
435    int len = base::ArrayHelper::GetArrayLength(thread, transfer);
436    int k = 0;
437    while (k < len) {
438        bool exists = JSTaggedValue::HasProperty(thread, transfer, k);
439        if (exists) {
440            JSHandle<JSTaggedValue> element = JSArray::FastGetPropertyByValue(thread, transfer, k);
441            if (!element->IsArrayBuffer()) {
442                return false;
443            }
444            transferDataSet_.insert(static_cast<uintptr_t>(element.GetTaggedType()));
445        }
446        k++;
447    }
448    return true;
449}
450
451bool ValueSerializer::PrepareClone(JSThread *thread, const JSHandle<JSTaggedValue> &cloneList)
452{
453    if (cloneList->IsUndefined()) {
454        return true;
455    }
456    if (!cloneList->IsJSArray()) {
457        return false;
458    }
459    int len = base::ArrayHelper::GetArrayLength(thread, cloneList);
460    int index = 0;
461    while (index < len) {
462        bool exists = JSTaggedValue::HasProperty(thread, cloneList, index);
463        if (exists) {
464            JSHandle<JSTaggedValue> element = JSArray::FastGetPropertyByValue(thread, cloneList, index);
465            if (element->IsArrayBuffer()) {
466                cloneArrayBufferSet_.insert(static_cast<uintptr_t>(element.GetTaggedType()));
467            } else if (element->IsJSShared()) {
468                cloneSharedSet_.insert(static_cast<uintptr_t>(element.GetTaggedType()));
469            } else {
470                return false;
471            }
472        }
473        index++;
474    }
475    return true;
476}
477}  // namespace panda::ecmascript
478
479