1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #ifndef vm_TypedArrayObject_h
8 #define vm_TypedArrayObject_h
9
10 #include "mozilla/Maybe.h"
11 #include "mozilla/TextUtils.h"
12
13 #include "gc/Barrier.h"
14 #include "gc/MaybeRooted.h"
15 #include "js/Class.h"
16 #include "js/experimental/TypedData.h" // js::detail::TypedArrayLengthSlot
17 #include "js/Result.h"
18 #include "js/ScalarType.h" // js::Scalar::Type
19 #include "vm/ArrayBufferObject.h"
20 #include "vm/ArrayBufferViewObject.h"
21 #include "vm/JSObject.h"
22 #include "vm/SharedArrayObject.h"
23
24 #define JS_FOR_EACH_TYPED_ARRAY(MACRO) \
25 MACRO(int8_t, Int8) \
26 MACRO(uint8_t, Uint8) \
27 MACRO(int16_t, Int16) \
28 MACRO(uint16_t, Uint16) \
29 MACRO(int32_t, Int32) \
30 MACRO(uint32_t, Uint32) \
31 MACRO(float, Float32) \
32 MACRO(double, Float64) \
33 MACRO(uint8_clamped, Uint8Clamped) \
34 MACRO(int64_t, BigInt64) \
35 MACRO(uint64_t, BigUint64)
36
37 namespace js {
38
39 /*
40 * TypedArrayObject
41 *
42 * The non-templated base class for the specific typed implementations.
43 * This class holds all the member variables that are used by
44 * the subclasses.
45 */
46
47 class TypedArrayObject : public ArrayBufferViewObject {
48 public:
49 static_assert(js::detail::TypedArrayLengthSlot == LENGTH_SLOT,
50 "bad inlined constant in TypedData.h");
51
sameBuffer(Handle<TypedArrayObject * > a,Handle<TypedArrayObject * > b)52 static bool sameBuffer(Handle<TypedArrayObject*> a,
53 Handle<TypedArrayObject*> b) {
54 // Inline buffers.
55 if (!a->hasBuffer() || !b->hasBuffer()) {
56 return a.get() == b.get();
57 }
58
59 // Shared buffers.
60 if (a->isSharedMemory() && b->isSharedMemory()) {
61 return a->bufferShared()->globalID() == b->bufferShared()->globalID();
62 }
63
64 return a->bufferEither() == b->bufferEither();
65 }
66
67 static const JSClass classes[Scalar::MaxTypedArrayViewType];
68 static const JSClass protoClasses[Scalar::MaxTypedArrayViewType];
69 static const JSClass sharedTypedArrayPrototypeClass;
70
classForType(Scalar::Type type)71 static const JSClass* classForType(Scalar::Type type) {
72 MOZ_ASSERT(type < Scalar::MaxTypedArrayViewType);
73 return &classes[type];
74 }
75
protoClassForType(Scalar::Type type)76 static const JSClass* protoClassForType(Scalar::Type type) {
77 MOZ_ASSERT(type < Scalar::MaxTypedArrayViewType);
78 return &protoClasses[type];
79 }
80
81 static constexpr size_t FIXED_DATA_START = DATA_SLOT + 1;
82
83 // For typed arrays which can store their data inline, the array buffer
84 // object is created lazily.
85 static constexpr uint32_t INLINE_BUFFER_LIMIT =
86 (NativeObject::MAX_FIXED_SLOTS - FIXED_DATA_START) * sizeof(Value);
87
88 static inline gc::AllocKind AllocKindForLazyBuffer(size_t nbytes);
89
90 inline Scalar::Type type() const;
91 inline size_t bytesPerElement() const;
92
93 static bool ensureHasBuffer(JSContext* cx, Handle<TypedArrayObject*> tarray);
94
byteLength()95 size_t byteLength() const { return length() * bytesPerElement(); }
96
length()97 size_t length() const {
98 return size_t(getFixedSlot(LENGTH_SLOT).toPrivate());
99 }
100
byteLengthValue()101 Value byteLengthValue() const {
102 size_t len = byteLength();
103 return NumberValue(len);
104 }
105
lengthValue()106 Value lengthValue() const {
107 size_t len = length();
108 return NumberValue(len);
109 }
110
111 bool hasInlineElements() const;
112 void setInlineElements();
elementsRaw()113 uint8_t* elementsRaw() const {
114 return *(uint8_t**)((((char*)this) + ArrayBufferViewObject::dataOffset()));
115 }
elements()116 uint8_t* elements() const {
117 assertZeroLengthArrayData();
118 return elementsRaw();
119 }
120
121 #ifdef DEBUG
122 void assertZeroLengthArrayData() const;
123 #else
assertZeroLengthArrayData()124 void assertZeroLengthArrayData() const {};
125 #endif
126
127 template <AllowGC allowGC>
128 bool getElement(JSContext* cx, size_t index,
129 typename MaybeRooted<Value, allowGC>::MutableHandleType val);
130 bool getElementPure(size_t index, Value* vp);
131
132 /*
133 * Copy all elements from this typed array to vp. vp must point to rooted
134 * memory.
135 */
136 static bool getElements(JSContext* cx, Handle<TypedArrayObject*> tarray,
137 Value* vp);
138
139 static bool GetTemplateObjectForNative(JSContext* cx, Native native,
140 const JS::HandleValueArray args,
141 MutableHandleObject res);
142
143 /*
144 * Maximum allowed byte length for any typed array.
145 */
maxByteLength()146 static size_t maxByteLength() {
147 return ArrayBufferObject::maxBufferByteLength();
148 }
149
150 static bool isOriginalLengthGetter(Native native);
151
152 static bool isOriginalByteOffsetGetter(Native native);
153
154 static bool isOriginalByteLengthGetter(Native native);
155
156 static void finalize(JSFreeOp* fop, JSObject* obj);
157 static size_t objectMoved(JSObject* obj, JSObject* old);
158
159 /* Initialization bits */
160
161 static const JSFunctionSpec protoFunctions[];
162 static const JSPropertySpec protoAccessors[];
163 static const JSFunctionSpec staticFunctions[];
164 static const JSPropertySpec staticProperties[];
165
166 /* Accessors and functions */
167
168 static bool is(HandleValue v);
169
170 static bool set(JSContext* cx, unsigned argc, Value* vp);
171 static bool copyWithin(JSContext* cx, unsigned argc, Value* vp);
172
173 bool convertForSideEffect(JSContext* cx, HandleValue v) const;
174
175 private:
176 static bool set_impl(JSContext* cx, const CallArgs& args);
177 static bool copyWithin_impl(JSContext* cx, const CallArgs& args);
178 };
179
180 [[nodiscard]] bool TypedArray_bufferGetter(JSContext* cx, unsigned argc,
181 Value* vp);
182
183 extern TypedArrayObject* NewTypedArrayWithTemplateAndLength(
184 JSContext* cx, HandleObject templateObj, int32_t len);
185
186 extern TypedArrayObject* NewTypedArrayWithTemplateAndArray(
187 JSContext* cx, HandleObject templateObj, HandleObject array);
188
189 extern TypedArrayObject* NewTypedArrayWithTemplateAndBuffer(
190 JSContext* cx, HandleObject templateObj, HandleObject arrayBuffer,
191 HandleValue byteOffset, HandleValue length);
192
IsTypedArrayClass(const JSClass * clasp)193 inline bool IsTypedArrayClass(const JSClass* clasp) {
194 return &TypedArrayObject::classes[0] <= clasp &&
195 clasp < &TypedArrayObject::classes[Scalar::MaxTypedArrayViewType];
196 }
197
GetTypedArrayClassType(const JSClass * clasp)198 inline Scalar::Type GetTypedArrayClassType(const JSClass* clasp) {
199 MOZ_ASSERT(IsTypedArrayClass(clasp));
200 return static_cast<Scalar::Type>(clasp - &TypedArrayObject::classes[0]);
201 }
202
203 bool IsTypedArrayConstructor(const JSObject* obj);
204
205 bool IsTypedArrayConstructor(HandleValue v, Scalar::Type type);
206
207 JSNative TypedArrayConstructorNative(Scalar::Type type);
208
209 // In WebIDL terminology, a BufferSource is either an ArrayBuffer or a typed
210 // array view. In either case, extract the dataPointer/byteLength.
211 bool IsBufferSource(JSObject* object, SharedMem<uint8_t*>* dataPointer,
212 size_t* byteLength);
213
type()214 inline Scalar::Type TypedArrayObject::type() const {
215 return GetTypedArrayClassType(getClass());
216 }
217
bytesPerElement()218 inline size_t TypedArrayObject::bytesPerElement() const {
219 return Scalar::byteSize(type());
220 }
221
222 // ES2020 draft rev a5375bdad264c8aa264d9c44f57408087761069e
223 // 7.1.16 CanonicalNumericIndexString
224 //
225 // Checks whether or not the string is a canonical numeric index string. If the
226 // string is a canonical numeric index which is not representable as a uint64_t,
227 // the returned index is UINT64_MAX.
228 template <typename CharT>
229 [[nodiscard]] bool StringToTypedArrayIndex(JSContext* cx,
230 mozilla::Range<const CharT> s,
231 mozilla::Maybe<uint64_t>* indexp);
232
233 // A string |s| is a TypedArray index (or: canonical numeric index string) iff
234 // |s| is "-0" or |SameValue(ToString(ToNumber(s)), s)| is true. So check for
235 // any characters which can start the string representation of a number,
236 // including "NaN" and "Infinity".
237 template <typename CharT>
CanStartTypedArrayIndex(CharT ch)238 inline bool CanStartTypedArrayIndex(CharT ch) {
239 return mozilla::IsAsciiDigit(ch) || ch == '-' || ch == 'N' || ch == 'I';
240 }
241
ToTypedArrayIndex(JSContext * cx,jsid id,mozilla::Maybe<uint64_t> * indexp)242 [[nodiscard]] inline bool ToTypedArrayIndex(JSContext* cx, jsid id,
243 mozilla::Maybe<uint64_t>* indexp) {
244 if (JSID_IS_INT(id)) {
245 int32_t i = JSID_TO_INT(id);
246 MOZ_ASSERT(i >= 0);
247 indexp->emplace(i);
248 return true;
249 }
250
251 if (MOZ_UNLIKELY(!JSID_IS_STRING(id))) {
252 MOZ_ASSERT(indexp->isNothing());
253 return true;
254 }
255
256 JS::AutoCheckCannotGC nogc;
257 JSAtom* atom = id.toAtom();
258
259 if (atom->empty() || !CanStartTypedArrayIndex(atom->latin1OrTwoByteChar(0))) {
260 MOZ_ASSERT(indexp->isNothing());
261 return true;
262 }
263
264 if (atom->hasLatin1Chars()) {
265 mozilla::Range<const Latin1Char> chars = atom->latin1Range(nogc);
266 return StringToTypedArrayIndex(cx, chars, indexp);
267 }
268
269 mozilla::Range<const char16_t> chars = atom->twoByteRange(nogc);
270 return StringToTypedArrayIndex(cx, chars, indexp);
271 }
272
273 bool SetTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj,
274 uint64_t index, HandleValue v,
275 ObjectOpResult& result);
276
277 /*
278 * Implements [[DefineOwnProperty]] for TypedArrays when the property
279 * key is a TypedArray index.
280 */
281 bool DefineTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj,
282 uint64_t index, Handle<PropertyDescriptor> desc,
283 ObjectOpResult& result);
284
TypedArrayShift(Scalar::Type viewType)285 static inline constexpr unsigned TypedArrayShift(Scalar::Type viewType) {
286 switch (viewType) {
287 case Scalar::Int8:
288 case Scalar::Uint8:
289 case Scalar::Uint8Clamped:
290 return 0;
291 case Scalar::Int16:
292 case Scalar::Uint16:
293 return 1;
294 case Scalar::Int32:
295 case Scalar::Uint32:
296 case Scalar::Float32:
297 return 2;
298 case Scalar::BigInt64:
299 case Scalar::BigUint64:
300 case Scalar::Int64:
301 case Scalar::Float64:
302 return 3;
303 default:
304 MOZ_CRASH("Unexpected array type");
305 }
306 }
307
TypedArrayElemSize(Scalar::Type viewType)308 static inline constexpr unsigned TypedArrayElemSize(Scalar::Type viewType) {
309 return 1u << TypedArrayShift(viewType);
310 }
311
312 } // namespace js
313
314 template <>
315 inline bool JSObject::is<js::TypedArrayObject>() const {
316 return js::IsTypedArrayClass(getClass());
317 }
318
319 #endif /* vm_TypedArrayObject_h */
320