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