1/*
2 * Copyright (c) 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 "ecmascript/builtins/builtins_sendable_arraybuffer.h"
17
18#include <typeinfo>
19
20#include "ecmascript/containers/containers_errors.h"
21#include "ecmascript/interpreter/interpreter.h"
22#include "ecmascript/js_function.h"
23#include "ecmascript/shared_objects/js_sendable_arraybuffer.h"
24#include "ecmascript/base/typed_array_helper-inl.h"
25#include "cstdio"
26#include "cstring"
27
28namespace panda::ecmascript::builtins {
29using TypedArrayHelper = base::TypedArrayHelper;
30using ContainerError = containers::ContainerError;
31// 24.1.2.1 ArrayBuffer(length)
32JSTaggedValue BuiltinsSendableArrayBuffer::ArrayBufferConstructor(EcmaRuntimeCallInfo *argv)
33{
34    ASSERT(argv);
35    JSThread *thread = argv->GetThread();
36    BUILTINS_API_TRACE(thread, SendableArrayBuffer, Constructor);
37    [[maybe_unused]] EcmaHandleScope handleScope(thread);
38    JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
39    // 1. If NewTarget is undefined, throw a TypeError exception.
40    if (newTarget->IsUndefined()) {
41        JSTaggedValue error = ContainerError::BusinessError(thread, containers::ErrorFlag::IS_NULL_ERROR,
42            "The ArkTS ArrayBuffer's constructor cannot be directly invoked.");
43        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
44    }
45    JSHandle<JSTaggedValue> lengthHandle = GetCallArg(argv, 0);
46    JSTaggedNumber lenNum = JSTaggedValue::ToIndex(thread, lengthHandle);
47    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
48    uint64_t length = lenNum.GetNumber();
49    return AllocateSendableArrayBuffer(thread, newTarget, length);
50}
51
52// 24.1.3.1 ArrayBuffer.isView(arg)
53JSTaggedValue BuiltinsSendableArrayBuffer::IsView(EcmaRuntimeCallInfo *argv)
54{
55    ASSERT(argv);
56    JSThread *thread = argv->GetThread();
57    BUILTINS_API_TRACE(thread, SendableArrayBuffer, IsView);
58    [[maybe_unused]] EcmaHandleScope handleScope(thread);
59    JSHandle<JSTaggedValue> arg = GetCallArg(argv, 0);
60    // 1. If Type(arg) is not Object, return false.
61    if (!arg->IsECMAObject()) {
62        return BuiltinsSendableArrayBuffer::GetTaggedBoolean(false);
63    }
64    // 2. If arg has a [[ViewedArrayBuffer]] internal slot, return true.
65    if (arg->IsDataView() || arg->IsSharedTypedArray()) {
66        return BuiltinsSendableArrayBuffer::GetTaggedBoolean(true);
67    }
68    // 3. Return false.
69    return BuiltinsSendableArrayBuffer::GetTaggedBoolean(false);
70}
71
72// 24.1.3.3 get ArrayBuffer [ @@species ]
73JSTaggedValue BuiltinsSendableArrayBuffer::Species(EcmaRuntimeCallInfo *argv)
74{
75    ASSERT(argv);
76    BUILTINS_API_TRACE(argv->GetThread(), SendableArrayBuffer, Species);
77    return GetThis(argv).GetTaggedValue();
78}
79
80// 24.1.4.1 get ArrayBuffer.prototype.byteLength
81JSTaggedValue BuiltinsSendableArrayBuffer::GetByteLength(EcmaRuntimeCallInfo *argv)
82{
83    ASSERT(argv);
84    JSThread *thread = argv->GetThread();
85    BUILTINS_API_TRACE(thread, SendableArrayBuffer, GetByteLength);
86    [[maybe_unused]] EcmaHandleScope handleScope(thread);
87
88    // 1. Let O be the this value.
89    JSHandle<JSTaggedValue> thisHandle = GetThis(argv);
90    // 2. If Type(O) is not Object, throw a TypeError exception.
91    if (!thisHandle->IsECMAObject()) {
92        THROW_TYPE_ERROR_AND_RETURN(thread, "this value is not an object", JSTaggedValue::Exception());
93    }
94    // 3. If O does not have an [[ArrayBufferData]] internal slot, throw a TypeError exception.
95    if (!thisHandle->IsSendableArrayBuffer()) {
96        THROW_TYPE_ERROR_AND_RETURN(thread, "don't have internal slot", JSTaggedValue::Exception());
97    }
98    // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception.
99    if (IsDetachedBuffer(thisHandle.GetTaggedValue())) {
100        THROW_TYPE_ERROR_AND_RETURN(thread, "IsDetachedBuffer", JSTaggedValue::Exception());
101    }
102    JSHandle<JSSendableArrayBuffer> arrBuf(thisHandle);
103    // 5. Let length be the value of O’s [[ArrayBufferByteLength]] internal slot.
104    uint32_t length = arrBuf->GetArrayBufferByteLength();
105    // 6. Return length.
106    return JSTaggedValue(length);
107}
108
109// 24.1.4.3 ArrayBuffer.prototype.slice(start, end)
110JSTaggedValue BuiltinsSendableArrayBuffer::Slice(EcmaRuntimeCallInfo *argv)
111{
112    ASSERT(argv);
113    JSThread *thread = argv->GetThread();
114    BUILTINS_API_TRACE(thread, SendableArrayBuffer, Slice);
115    [[maybe_unused]] EcmaHandleScope handleScope(thread);
116    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
117    // 1. Let O be the this value.
118    JSHandle<JSTaggedValue> thisHandle = GetThis(argv);
119    // 2. If Type(O) is not Object, throw a TypeError exception.
120    if (!thisHandle->IsHeapObject()) {
121        THROW_TYPE_ERROR_AND_RETURN(thread, "this value is not an object", JSTaggedValue::Exception());
122    }
123    // 3. If O does not have an [[ArrayBufferData]] internal slot, throw a TypeError exception.
124    if (!thisHandle->IsSendableArrayBuffer()) {
125        auto error = ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
126                                                   "The slice method cannot be bound.");
127        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
128    }
129    // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception.
130    if (IsDetachedBuffer(thisHandle.GetTaggedValue())) {
131        THROW_TYPE_ERROR_AND_RETURN(thread, "this value IsDetachedBuffer", JSTaggedValue::Exception());
132    }
133    JSHandle<JSSendableArrayBuffer> arrBuf(thisHandle);
134    // 5. Let len be the value of O’s [[ArrayBufferByteLength]] internal slot.
135    int32_t len = static_cast<int32_t>(arrBuf->GetArrayBufferByteLength());
136    JSHandle<JSTaggedValue> startHandle = GetCallArg(argv, 0);
137    // 6. Let relativeStart be ToInteger(start).
138    JSTaggedNumber relativeStart = JSTaggedValue::ToInteger(thread, startHandle);
139    // 7. ReturnIfAbrupt(relativeStart).
140    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
141    int32_t start = base::NumberHelper::DoubleInRangeInt32(relativeStart.GetNumber());
142    int32_t end = 0;
143    int32_t first = 0;
144    int32_t last = 0;
145    // 8. If relativeStart < 0, let first be max((len + relativeStart),0); else let first be min(relativeStart, len).
146    if (start < 0) {
147        first = std::max((len + start), 0);
148    } else {
149        first = std::min(start, len);
150    }
151    // 9. If end is undefined, let relativeEnd be len; else let relativeEnd be ToInteger(end).
152    JSHandle<JSTaggedValue> endHandle = GetCallArg(argv, 1);
153    if (endHandle->IsUndefined()) {
154        end = len;
155    } else {
156        JSTaggedNumber relativeEnd = JSTaggedValue::ToInteger(thread, endHandle);
157        // 10. ReturnIfAbrupt(relativeEnd).
158        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
159        end = base::NumberHelper::DoubleInRangeInt32(relativeEnd.GetNumber());
160    }
161    // 11. If relativeEnd < 0, let final be max((len + relativeEnd),0); else let final be min(relativeEnd, len).
162    if (end < 0) {
163        last = std::max((len + end), 0);
164    } else {
165        last = std::min(end, len);
166    }
167    // 12. Let newLen be max(final-first,0).
168    uint32_t newLen = std::max((last - first), 0);
169    // 13. Let ctor be SpeciesConstructor(O, %ArrayBuffer%).
170    JSHandle<JSTaggedValue> defaultConstructor = env->GetSBuiltininArrayBufferFunction();
171    JSHandle<JSObject> objHandle(thisHandle);
172    JSHandle<JSTaggedValue> constructor = JSObject::SpeciesConstructor(thread, objHandle, defaultConstructor);
173    // 14. ReturnIfAbrupt(ctor).
174    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
175    // 15. Let new be Construct(ctor, «newLen»).
176    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
177    EcmaRuntimeCallInfo *info =
178        EcmaInterpreter::NewRuntimeCallInfo(thread, constructor, undefined, undefined, 1);
179    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
180    info->SetCallArg(JSTaggedValue(newLen));
181    JSTaggedValue taggedNewArrBuf = JSFunction::Construct(info);
182    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
183    JSHandle<JSTaggedValue> newArrBuf(thread, taggedNewArrBuf);
184    // 16. ReturnIfAbrupt(new).
185    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
186    // 17. If new does not have an [[ArrayBufferData]] internal slot, throw a TypeError exception.
187    if (!newArrBuf->IsSendableArrayBuffer()) {
188        THROW_TYPE_ERROR_AND_RETURN(thread, "don't have bufferdata internal slot", JSTaggedValue::Exception());
189    }
190    // 18. If IsDetachedBuffer(new) is true, throw a TypeError exception.
191    if (IsDetachedBuffer(newArrBuf.GetTaggedValue())) {
192        THROW_TYPE_ERROR_AND_RETURN(thread, "new arrayBuffer IsDetachedBuffer", JSTaggedValue::Exception());
193    }
194    // 19. If SameValue(new, O) is true, throw a TypeError exception.
195    if (JSTaggedValue::SameValue(newArrBuf.GetTaggedValue(), thisHandle.GetTaggedValue())) {
196        THROW_TYPE_ERROR_AND_RETURN(thread, "value of new arraybuffer and this is same", JSTaggedValue::Exception());
197    }
198    JSHandle<JSSendableArrayBuffer> newJsArrBuf(newArrBuf);
199    // 20. If the value of new’s [[ArrayBufferByteLength]] internal slot < newLen, throw a TypeError exception.
200    uint32_t newArrBufLen = newJsArrBuf->GetArrayBufferByteLength();
201    if (newArrBufLen < newLen) {
202        THROW_TYPE_ERROR_AND_RETURN(thread, "new array buffer length smaller than newlen", JSTaggedValue::Exception());
203    }
204    // 21. NOTE: Side-effects of the above steps may have detached O.
205    // 22. If IsDetachedBuffer(O) is true, throw a TypeError exception.
206    if (IsDetachedBuffer(thisHandle.GetTaggedValue())) {
207        THROW_TYPE_ERROR_AND_RETURN(thread, "this value IsDetachedBuffer", JSTaggedValue::Exception());
208    }
209    if (newLen > 0) {
210        // 23. Let fromBuf be the value of O’s [[ArrayBufferData]] internal slot.
211        void *fromBuf = GetDataPointFromBuffer(arrBuf.GetTaggedValue());
212        // 24. Let toBuf be the value of new’s [[ArrayBufferData]] internal slot.
213        void *toBuf = GetDataPointFromBuffer(newJsArrBuf.GetTaggedValue());
214        // 25. Perform CopyDataBlockBytes(toBuf, fromBuf, first, newLen).
215        JSSendableArrayBuffer::CopyDataPointBytes(toBuf, fromBuf, first, newLen);
216    }
217    // Return new.
218    return newArrBuf.GetTaggedValue();
219}
220
221// 24.1.1.1 AllocateArrayBuffer(constructor, byteLength)
222JSTaggedValue BuiltinsSendableArrayBuffer::AllocateSendableArrayBuffer(
223    JSThread *thread, const JSHandle<JSTaggedValue> &newTarget, uint64_t byteLength)
224{
225    BUILTINS_API_TRACE(thread, SendableArrayBuffer, AllocateSendableArrayBuffer);
226    /**
227     * 1. Let obj be OrdinaryCreateFromConstructor(constructor, "%ArrayBufferPrototype%",
228     * «[[ArrayBufferData]], [[ArrayBufferByteLength]]» ).
229     * */
230    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
231    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
232    JSHandle<JSTaggedValue> arrBufFunc = env->GetSBuiltininArrayBufferFunction();
233    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(arrBufFunc), newTarget);
234    // 2. ReturnIfAbrupt
235    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
236    ASSERT(obj.GetTaggedValue().IsInSharedHeap());
237    // 4. Let block be CreateByteDataBlock(byteLength).
238    if (byteLength > INT_MAX) {
239        THROW_RANGE_ERROR_AND_RETURN(thread, "Out of range", JSTaggedValue::Exception());
240    }
241    uint32_t arrayByteLength = static_cast<uint32_t>(byteLength);
242    JSHandle<JSSendableArrayBuffer> arrayBuffer(obj);
243    // 6. Set obj’s [[ArrayBufferData]] internal slot to block.
244    factory->NewJSSendableArrayBufferData(arrayBuffer, arrayByteLength);
245    // 7. Set obj’s [[ArrayBufferByteLength]] internal slot to byteLength.
246    arrayBuffer->SetArrayBufferByteLength(arrayByteLength);
247    // 8. Return obj.
248    return arrayBuffer.GetTaggedValue();
249}
250
251// 24.1.1.2 IsDetachedBuffer()
252bool BuiltinsSendableArrayBuffer::IsDetachedBuffer(JSTaggedValue arrayBuffer)
253{
254    if (arrayBuffer.IsByteArray()) {
255        return false;
256    }
257    // 1. Assert: Type(arrayBuffer) is Object and it has an [[ArrayBufferData]] internal slot.
258    ASSERT(arrayBuffer.IsSendableArrayBuffer());
259    JSSendableArrayBuffer *buffer = JSSendableArrayBuffer::Cast(arrayBuffer.GetTaggedObject());
260    JSTaggedValue dataSlot = buffer->GetArrayBufferData();
261    // 2. If arrayBuffer’s [[ArrayBufferData]] internal slot is null, return true.
262    // 3. Return false.
263    return dataSlot.IsNull();
264}
265
266// 24.1.1.4
267JSTaggedValue BuiltinsSendableArrayBuffer::CloneArrayBuffer(JSThread *thread,
268                                                            const JSHandle<JSTaggedValue> &srcBuffer,
269                                                            uint32_t srcByteOffset,
270                                                            JSHandle<JSTaggedValue> constructor)
271{
272    BUILTINS_API_TRACE(thread, SendableArrayBuffer, CloneArrayBuffer);
273    // 1. Assert: Type(srcBuffer) is Object and it has an [[ArrayBufferData]] internal slot.
274    ASSERT(srcBuffer->IsSendableArrayBuffer()|| srcBuffer->IsByteArray());
275    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
276    // 2. If cloneConstructor is not present
277    if (constructor->IsUndefined()) {
278        // a. Let cloneConstructor be SpeciesConstructor(srcBuffer, %ArrayBuffer%).
279        JSHandle<JSTaggedValue> defaultConstructor = env->GetSBuiltininArrayBufferFunction();
280        JSHandle<JSObject> objHandle(srcBuffer);
281        constructor = JSObject::SpeciesConstructor(thread, objHandle, defaultConstructor);
282        // b. ReturnIfAbrupt(cloneConstructor).
283        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
284        // c. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception.
285        if (IsDetachedBuffer(srcBuffer.GetTaggedValue())) {
286            THROW_TYPE_ERROR_AND_RETURN(thread, "Is Detached Buffer", JSTaggedValue::Exception());
287        } else {
288            ASSERT(constructor->IsConstructor());
289        }
290    }
291    // 4. Let srcLength be the value of srcBuffer’s [[ArrayBufferByteLength]] internal slot.
292    uint32_t srcLen = 0;
293    int32_t cloneLen = 0;
294    if (srcBuffer->IsByteArray()) {
295        JSHandle<ByteArray> byteArrayBuf(srcBuffer);
296        srcLen = byteArrayBuf->GetArrayLength();
297        int32_t byteLen = static_cast<int32_t>(byteArrayBuf->GetByteLength());
298        // 5. Assert: srcByteOffset ≤ srcLength.
299        ASSERT(srcByteOffset <= srcLen);
300        // 6. Let cloneLength be (srcLength – srcByteOffset) * byteLen.
301        cloneLen = static_cast<int32_t>(srcLen - srcByteOffset) * byteLen;
302        srcByteOffset *= static_cast<uint32_t>(byteLen);
303    } else {
304        JSHandle<JSSendableArrayBuffer> arrBuf(srcBuffer);
305        srcLen = arrBuf->GetArrayBufferByteLength();
306        // 5. Assert: srcByteOffset ≤ srcLength.
307        ASSERT(srcByteOffset <= srcLen);
308        // 6. Let cloneLength be srcLength – srcByteOffset.
309        cloneLen = static_cast<int32_t>(srcLen - srcByteOffset);
310    }
311
312    // 8. Let targetBuffer be AllocateArrayBuffer(cloneConstructor, cloneLength).
313    JSTaggedValue taggedBuf = AllocateSendableArrayBuffer(thread, constructor, cloneLen);
314    // 9. ReturnIfAbrupt(targetBuffer).
315    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
316    // 10. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception.
317    if (IsDetachedBuffer(srcBuffer.GetTaggedValue())) {
318        THROW_TYPE_ERROR_AND_RETURN(thread, "Is Detached Buffer", JSTaggedValue::Exception());
319    }
320    // 11. Let targetBlock be the value of targetBuffer’s [[ArrayBufferData]] internal slot.
321    JSHandle<JSSendableArrayBuffer> newArrBuf(thread, taggedBuf);
322    // Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, cloneLength).
323    // 7. Let srcBlock be the value of srcBuffer’s [[ArrayBufferData]] internal slot.
324    void *fromBuf = GetDataPointFromBuffer(srcBuffer.GetTaggedValue());
325    void *toBuf = GetDataPointFromBuffer(taggedBuf);
326    if (cloneLen > 0) {
327        JSSendableArrayBuffer::CopyDataPointBytes(toBuf, fromBuf, srcByteOffset, cloneLen);
328    }
329    return taggedBuf;
330}
331
332void *BuiltinsSendableArrayBuffer::GetDataPointFromBuffer(JSTaggedValue arrBuf, uint32_t byteOffset)
333{
334    if (arrBuf.IsByteArray()) {
335        return reinterpret_cast<void *>(ToUintPtr(ByteArray::Cast(arrBuf.GetTaggedObject())->GetData()) + byteOffset);
336    }
337
338    JSSendableArrayBuffer *arrayBuffer = JSSendableArrayBuffer::Cast(arrBuf.GetTaggedObject());
339    if (arrayBuffer->GetArrayBufferByteLength() == 0) {
340        return nullptr;
341    }
342
343    JSTaggedValue data = arrayBuffer->GetArrayBufferData();
344    return reinterpret_cast<void *>(ToUintPtr(JSNativePointer::Cast(data.GetTaggedObject())
345                                    ->GetExternalPointer()) + byteOffset);
346}
347}  // namespace panda::ecmascript::builtins
348