1/*
2 * Copyright (C) 2009-2018 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ArrayBuffer.h"
28
29#include "ArrayBufferNeuteringWatchpointSet.h"
30#include "JSArrayBufferView.h"
31#include "JSCInlines.h"
32#include <wtf/Gigacage.h>
33
34namespace JSC {
35
36SharedArrayBufferContents::SharedArrayBufferContents(void* data, unsigned size, ArrayBufferDestructorFunction&& destructor)
37 : m_data(data, size)
38 , m_destructor(WTFMove(destructor))
39 , m_sizeInBytes(size)
40{
41}
42
43SharedArrayBufferContents::~SharedArrayBufferContents()
44{
45 // FIXME: we shouldn't use getUnsafe here https://bugs.webkit.org/show_bug.cgi?id=197698
46 m_destructor(m_data.getUnsafe());
47}
48
49ArrayBufferContents::ArrayBufferContents()
50{
51 reset();
52}
53
54ArrayBufferContents::ArrayBufferContents(ArrayBufferContents&& other)
55{
56 reset();
57 other.transferTo(*this);
58}
59
60ArrayBufferContents::ArrayBufferContents(void* data, unsigned sizeInBytes, ArrayBufferDestructorFunction&& destructor)
61 : m_data(data, sizeInBytes)
62 , m_sizeInBytes(sizeInBytes)
63{
64 RELEASE_ASSERT(m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE);
65 m_destructor = WTFMove(destructor);
66}
67
68ArrayBufferContents& ArrayBufferContents::operator=(ArrayBufferContents&& other)
69{
70 other.transferTo(*this);
71 return *this;
72}
73
74ArrayBufferContents::~ArrayBufferContents()
75{
76 destroy();
77}
78
79void ArrayBufferContents::clear()
80{
81 destroy();
82 reset();
83}
84
85void ArrayBufferContents::destroy()
86{
87 // FIXME: We shouldn't use getUnsafe here: https://bugs.webkit.org/show_bug.cgi?id=197698
88 m_destructor(m_data.getUnsafe());
89}
90
91void ArrayBufferContents::reset()
92{
93 m_destructor = [] (void*) { };
94 m_shared = nullptr;
95 m_data = nullptr;
96 m_sizeInBytes = 0;
97}
98
99void ArrayBufferContents::tryAllocate(unsigned numElements, unsigned elementByteSize, InitializationPolicy policy)
100{
101 // Do not allow 31-bit overflow of the total size.
102 if (numElements) {
103 unsigned totalSize = numElements * elementByteSize;
104 if (totalSize / numElements != elementByteSize || totalSize > MAX_ARRAY_BUFFER_SIZE) {
105 reset();
106 return;
107 }
108 }
109 size_t sizeInBytes = static_cast<size_t>(numElements) * static_cast<size_t>(elementByteSize);
110 size_t allocationSize = sizeInBytes;
111 if (!allocationSize)
112 allocationSize = 1; // Make sure malloc actually allocates something, but not too much. We use null to mean that the buffer is neutered.
113
114 void* data = Gigacage::tryMalloc(Gigacage::Primitive, allocationSize);
115 m_data = DataType(data, sizeInBytes);
116 if (!data) {
117 reset();
118 return;
119 }
120
121 if (policy == ZeroInitialize)
122 memset(data, 0, allocationSize);
123
124 m_sizeInBytes = sizeInBytes;
125 RELEASE_ASSERT(m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE);
126 m_destructor = [] (void* p) { Gigacage::free(Gigacage::Primitive, p); };
127}
128
129void ArrayBufferContents::makeShared()
130{
131 m_shared = adoptRef(new SharedArrayBufferContents(data(), sizeInBytes(), WTFMove(m_destructor)));
132 m_destructor = [] (void*) { };
133}
134
135void ArrayBufferContents::transferTo(ArrayBufferContents& other)
136{
137 other.clear();
138 other.m_data = m_data;
139 other.m_sizeInBytes = m_sizeInBytes;
140 RELEASE_ASSERT(other.m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE);
141 other.m_destructor = WTFMove(m_destructor);
142 other.m_shared = m_shared;
143 reset();
144}
145
146void ArrayBufferContents::copyTo(ArrayBufferContents& other)
147{
148 ASSERT(!other.m_data);
149 other.tryAllocate(m_sizeInBytes, sizeof(char), ArrayBufferContents::DontInitialize);
150 if (!other.m_data)
151 return;
152 memcpy(other.data(), data(), m_sizeInBytes);
153 other.m_sizeInBytes = m_sizeInBytes;
154 RELEASE_ASSERT(other.m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE);
155}
156
157void ArrayBufferContents::shareWith(ArrayBufferContents& other)
158{
159 ASSERT(!other.m_data);
160 ASSERT(m_shared);
161 other.m_destructor = [] (void*) { };
162 other.m_shared = m_shared;
163 other.m_data = m_data;
164 other.m_sizeInBytes = m_sizeInBytes;
165 RELEASE_ASSERT(other.m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE);
166}
167
168Ref<ArrayBuffer> ArrayBuffer::create(unsigned numElements, unsigned elementByteSize)
169{
170 auto buffer = tryCreate(numElements, elementByteSize);
171 if (!buffer)
172 CRASH();
173 return buffer.releaseNonNull();
174}
175
176Ref<ArrayBuffer> ArrayBuffer::create(ArrayBuffer& other)
177{
178 return ArrayBuffer::create(other.data(), other.byteLength());
179}
180
181Ref<ArrayBuffer> ArrayBuffer::create(const void* source, unsigned byteLength)
182{
183 auto buffer = tryCreate(source, byteLength);
184 if (!buffer)
185 CRASH();
186 return buffer.releaseNonNull();
187}
188
189Ref<ArrayBuffer> ArrayBuffer::create(ArrayBufferContents&& contents)
190{
191 return adoptRef(*new ArrayBuffer(WTFMove(contents)));
192}
193
194// FIXME: We cannot use this except if the memory comes from the cage.
195// Current this is only used from:
196// - JSGenericTypedArrayView<>::slowDownAndWasteMemory. But in that case, the memory should have already come
197// from the cage.
198Ref<ArrayBuffer> ArrayBuffer::createAdopted(const void* data, unsigned byteLength)
199{
200 return createFromBytes(data, byteLength, [] (void* p) { Gigacage::free(Gigacage::Primitive, p); });
201}
202
203// FIXME: We cannot use this except if the memory comes from the cage.
204// Currently this is only used from:
205// - The C API. We could support that by either having the system switch to a mode where typed arrays are no
206// longer caged, or we could introduce a new set of typed array types that are uncaged and get accessed
207// differently.
208// - WebAssembly. Wasm should allocate from the cage.
209Ref<ArrayBuffer> ArrayBuffer::createFromBytes(const void* data, unsigned byteLength, ArrayBufferDestructorFunction&& destructor)
210{
211 if (data && !Gigacage::isCaged(Gigacage::Primitive, data))
212 Gigacage::disablePrimitiveGigacage();
213
214 ArrayBufferContents contents(const_cast<void*>(data), byteLength, WTFMove(destructor));
215 return create(WTFMove(contents));
216}
217
218RefPtr<ArrayBuffer> ArrayBuffer::tryCreate(unsigned numElements, unsigned elementByteSize)
219{
220 return tryCreate(numElements, elementByteSize, ArrayBufferContents::ZeroInitialize);
221}
222
223RefPtr<ArrayBuffer> ArrayBuffer::tryCreate(ArrayBuffer& other)
224{
225 return tryCreate(other.data(), other.byteLength());
226}
227
228RefPtr<ArrayBuffer> ArrayBuffer::tryCreate(const void* source, unsigned byteLength)
229{
230 ArrayBufferContents contents;
231 contents.tryAllocate(byteLength, 1, ArrayBufferContents::DontInitialize);
232 if (!contents.m_data)
233 return nullptr;
234 return createInternal(WTFMove(contents), source, byteLength);
235}
236
237Ref<ArrayBuffer> ArrayBuffer::createUninitialized(unsigned numElements, unsigned elementByteSize)
238{
239 return create(numElements, elementByteSize, ArrayBufferContents::DontInitialize);
240}
241
242RefPtr<ArrayBuffer> ArrayBuffer::tryCreateUninitialized(unsigned numElements, unsigned elementByteSize)
243{
244 return tryCreate(numElements, elementByteSize, ArrayBufferContents::DontInitialize);
245}
246
247Ref<ArrayBuffer> ArrayBuffer::create(unsigned numElements, unsigned elementByteSize, ArrayBufferContents::InitializationPolicy policy)
248{
249 auto buffer = tryCreate(numElements, elementByteSize, policy);
250 if (!buffer)
251 CRASH();
252 return buffer.releaseNonNull();
253}
254
255Ref<ArrayBuffer> ArrayBuffer::createInternal(ArrayBufferContents&& contents, const void* source, unsigned byteLength)
256{
257 ASSERT(!byteLength || source);
258 auto buffer = adoptRef(*new ArrayBuffer(WTFMove(contents)));
259 memcpy(buffer->data(), source, byteLength);
260 return buffer;
261}
262
263RefPtr<ArrayBuffer> ArrayBuffer::tryCreate(unsigned numElements, unsigned elementByteSize, ArrayBufferContents::InitializationPolicy policy)
264{
265 ArrayBufferContents contents;
266 contents.tryAllocate(numElements, elementByteSize, policy);
267 if (!contents.m_data)
268 return nullptr;
269 return adoptRef(*new ArrayBuffer(WTFMove(contents)));
270}
271
272ArrayBuffer::ArrayBuffer(ArrayBufferContents&& contents)
273 : m_contents(WTFMove(contents))
274 , m_pinCount(0)
275 , m_isWasmMemory(false)
276 , m_locked(false)
277{
278}
279
280unsigned ArrayBuffer::clampValue(double x, unsigned left, unsigned right)
281{
282 ASSERT(left <= right);
283 if (x < left)
284 x = left;
285 if (right < x)
286 x = right;
287 return x;
288}
289
290unsigned ArrayBuffer::clampIndex(double index) const
291{
292 unsigned currentLength = byteLength();
293 if (index < 0)
294 index = currentLength + index;
295 return clampValue(index, 0, currentLength);
296}
297
298Ref<ArrayBuffer> ArrayBuffer::slice(double begin, double end) const
299{
300 return sliceImpl(clampIndex(begin), clampIndex(end));
301}
302
303Ref<ArrayBuffer> ArrayBuffer::slice(double begin) const
304{
305 return sliceImpl(clampIndex(begin), byteLength());
306}
307
308Ref<ArrayBuffer> ArrayBuffer::sliceImpl(unsigned begin, unsigned end) const
309{
310 unsigned size = begin <= end ? end - begin : 0;
311 auto result = ArrayBuffer::create(static_cast<const char*>(data()) + begin, size);
312 result->setSharingMode(sharingMode());
313 return result;
314}
315
316void ArrayBuffer::makeShared()
317{
318 m_contents.makeShared();
319 m_locked = true;
320}
321
322void ArrayBuffer::makeWasmMemory()
323{
324 m_locked = true;
325 m_isWasmMemory = true;
326}
327
328void ArrayBuffer::setSharingMode(ArrayBufferSharingMode newSharingMode)
329{
330 if (newSharingMode == sharingMode())
331 return;
332 RELEASE_ASSERT(!isShared()); // Cannot revert sharing.
333 RELEASE_ASSERT(newSharingMode == ArrayBufferSharingMode::Shared);
334 makeShared();
335}
336
337bool ArrayBuffer::shareWith(ArrayBufferContents& result)
338{
339 if (!m_contents.m_data || !isShared()) {
340 result.m_data = nullptr;
341 return false;
342 }
343
344 m_contents.shareWith(result);
345 return true;
346}
347
348bool ArrayBuffer::transferTo(VM& vm, ArrayBufferContents& result)
349{
350 Ref<ArrayBuffer> protect(*this);
351
352 if (!m_contents.m_data) {
353 result.m_data = nullptr;
354 return false;
355 }
356
357 if (isShared()) {
358 m_contents.shareWith(result);
359 return true;
360 }
361
362 bool isNeuterable = !m_pinCount && !m_locked;
363
364 if (!isNeuterable) {
365 m_contents.copyTo(result);
366 if (!result.m_data)
367 return false;
368 return true;
369 }
370
371 m_contents.transferTo(result);
372 notifyIncommingReferencesOfTransfer(vm);
373 return true;
374}
375
376// We allow neutering wasm memory ArrayBuffers even though they are locked.
377void ArrayBuffer::neuter(VM& vm)
378{
379 ASSERT(isWasmMemory());
380 ArrayBufferContents unused;
381 m_contents.transferTo(unused);
382 notifyIncommingReferencesOfTransfer(vm);
383}
384
385void ArrayBuffer::notifyIncommingReferencesOfTransfer(VM& vm)
386{
387 for (size_t i = numberOfIncomingReferences(); i--;) {
388 JSCell* cell = incomingReferenceAt(i);
389 if (JSArrayBufferView* view = jsDynamicCast<JSArrayBufferView*>(vm, cell))
390 view->neuter();
391 else if (ArrayBufferNeuteringWatchpointSet* watchpoint = jsDynamicCast<ArrayBufferNeuteringWatchpointSet*>(vm, cell))
392 watchpoint->fireAll();
393 }
394}
395
396ASCIILiteral errorMesasgeForTransfer(ArrayBuffer* buffer)
397{
398 ASSERT(buffer->isLocked());
399 if (buffer->isShared())
400 return "Cannot transfer a SharedArrayBuffer"_s;
401 if (buffer->isWasmMemory())
402 return "Cannot transfer a WebAssembly.Memory"_s;
403 return "Cannot transfer an ArrayBuffer whose backing store has been accessed by the JavaScriptCore C API"_s;
404}
405
406} // namespace JSC
407
408