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 size = static_cast<size_t>(numElements) * static_cast<size_t>(elementByteSize);
110 if (!size)
111 size = 1; // Make sure malloc actually allocates something, but not too much. We use null to mean that the buffer is neutered.
112
113 void* data = Gigacage::tryMalloc(Gigacage::Primitive, numElements * elementByteSize);
114 m_data = DataType(data, size);
115 if (!data) {
116 reset();
117 return;
118 }
119
120 if (policy == ZeroInitialize)
121 memset(data, 0, size);
122
123 m_sizeInBytes = numElements * elementByteSize;
124 RELEASE_ASSERT(m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE);
125 m_destructor = [] (void* p) { Gigacage::free(Gigacage::Primitive, p); };
126}
127
128void ArrayBufferContents::makeShared()
129{
130 m_shared = adoptRef(new SharedArrayBufferContents(data(), sizeInBytes(), WTFMove(m_destructor)));
131 m_destructor = [] (void*) { };
132}
133
134void ArrayBufferContents::transferTo(ArrayBufferContents& other)
135{
136 other.clear();
137 other.m_data = m_data;
138 other.m_sizeInBytes = m_sizeInBytes;
139 RELEASE_ASSERT(other.m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE);
140 other.m_destructor = WTFMove(m_destructor);
141 other.m_shared = m_shared;
142 reset();
143}
144
145void ArrayBufferContents::copyTo(ArrayBufferContents& other)
146{
147 ASSERT(!other.m_data);
148 other.tryAllocate(m_sizeInBytes, sizeof(char), ArrayBufferContents::DontInitialize);
149 if (!other.m_data)
150 return;
151 memcpy(other.data(), data(), m_sizeInBytes);
152 other.m_sizeInBytes = m_sizeInBytes;
153 RELEASE_ASSERT(other.m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE);
154}
155
156void ArrayBufferContents::shareWith(ArrayBufferContents& other)
157{
158 ASSERT(!other.m_data);
159 ASSERT(m_shared);
160 other.m_destructor = [] (void*) { };
161 other.m_shared = m_shared;
162 other.m_data = m_data;
163 other.m_sizeInBytes = m_sizeInBytes;
164 RELEASE_ASSERT(other.m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE);
165}
166
167Ref<ArrayBuffer> ArrayBuffer::create(unsigned numElements, unsigned elementByteSize)
168{
169 auto buffer = tryCreate(numElements, elementByteSize);
170 if (!buffer)
171 CRASH();
172 return buffer.releaseNonNull();
173}
174
175Ref<ArrayBuffer> ArrayBuffer::create(ArrayBuffer& other)
176{
177 return ArrayBuffer::create(other.data(), other.byteLength());
178}
179
180Ref<ArrayBuffer> ArrayBuffer::create(const void* source, unsigned byteLength)
181{
182 auto buffer = tryCreate(source, byteLength);
183 if (!buffer)
184 CRASH();
185 return buffer.releaseNonNull();
186}
187
188Ref<ArrayBuffer> ArrayBuffer::create(ArrayBufferContents&& contents)
189{
190 return adoptRef(*new ArrayBuffer(WTFMove(contents)));
191}
192
193// FIXME: We cannot use this except if the memory comes from the cage.
194// Current this is only used from:
195// - JSGenericTypedArrayView<>::slowDownAndWasteMemory. But in that case, the memory should have already come
196// from the cage.
197Ref<ArrayBuffer> ArrayBuffer::createAdopted(const void* data, unsigned byteLength)
198{
199 return createFromBytes(data, byteLength, [] (void* p) { Gigacage::free(Gigacage::Primitive, p); });
200}
201
202// FIXME: We cannot use this except if the memory comes from the cage.
203// Currently this is only used from:
204// - The C API. We could support that by either having the system switch to a mode where typed arrays are no
205// longer caged, or we could introduce a new set of typed array types that are uncaged and get accessed
206// differently.
207// - WebAssembly. Wasm should allocate from the cage.
208Ref<ArrayBuffer> ArrayBuffer::createFromBytes(const void* data, unsigned byteLength, ArrayBufferDestructorFunction&& destructor)
209{
210 if (data && !Gigacage::isCaged(Gigacage::Primitive, data))
211 Gigacage::disablePrimitiveGigacage();
212
213 ArrayBufferContents contents(const_cast<void*>(data), byteLength, WTFMove(destructor));
214 return create(WTFMove(contents));
215}
216
217RefPtr<ArrayBuffer> ArrayBuffer::tryCreate(unsigned numElements, unsigned elementByteSize)
218{
219 return tryCreate(numElements, elementByteSize, ArrayBufferContents::ZeroInitialize);
220}
221
222RefPtr<ArrayBuffer> ArrayBuffer::tryCreate(ArrayBuffer& other)
223{
224 return tryCreate(other.data(), other.byteLength());
225}
226
227RefPtr<ArrayBuffer> ArrayBuffer::tryCreate(const void* source, unsigned byteLength)
228{
229 ArrayBufferContents contents;
230 contents.tryAllocate(byteLength, 1, ArrayBufferContents::DontInitialize);
231 if (!contents.m_data)
232 return nullptr;
233 return createInternal(WTFMove(contents), source, byteLength);
234}
235
236Ref<ArrayBuffer> ArrayBuffer::createUninitialized(unsigned numElements, unsigned elementByteSize)
237{
238 return create(numElements, elementByteSize, ArrayBufferContents::DontInitialize);
239}
240
241RefPtr<ArrayBuffer> ArrayBuffer::tryCreateUninitialized(unsigned numElements, unsigned elementByteSize)
242{
243 return tryCreate(numElements, elementByteSize, ArrayBufferContents::DontInitialize);
244}
245
246Ref<ArrayBuffer> ArrayBuffer::create(unsigned numElements, unsigned elementByteSize, ArrayBufferContents::InitializationPolicy policy)
247{
248 auto buffer = tryCreate(numElements, elementByteSize, policy);
249 if (!buffer)
250 CRASH();
251 return buffer.releaseNonNull();
252}
253
254Ref<ArrayBuffer> ArrayBuffer::createInternal(ArrayBufferContents&& contents, const void* source, unsigned byteLength)
255{
256 ASSERT(!byteLength || source);
257 auto buffer = adoptRef(*new ArrayBuffer(WTFMove(contents)));
258 memcpy(buffer->data(), source, byteLength);
259 return buffer;
260}
261
262RefPtr<ArrayBuffer> ArrayBuffer::tryCreate(unsigned numElements, unsigned elementByteSize, ArrayBufferContents::InitializationPolicy policy)
263{
264 ArrayBufferContents contents;
265 contents.tryAllocate(numElements, elementByteSize, policy);
266 if (!contents.m_data)
267 return nullptr;
268 return adoptRef(*new ArrayBuffer(WTFMove(contents)));
269}
270
271ArrayBuffer::ArrayBuffer(ArrayBufferContents&& contents)
272 : m_contents(WTFMove(contents))
273 , m_pinCount(0)
274 , m_isWasmMemory(false)
275 , m_locked(false)
276{
277}
278
279unsigned ArrayBuffer::clampValue(double x, unsigned left, unsigned right)
280{
281 ASSERT(left <= right);
282 if (x < left)
283 x = left;
284 if (right < x)
285 x = right;
286 return x;
287}
288
289unsigned ArrayBuffer::clampIndex(double index) const
290{
291 unsigned currentLength = byteLength();
292 if (index < 0)
293 index = currentLength + index;
294 return clampValue(index, 0, currentLength);
295}
296
297Ref<ArrayBuffer> ArrayBuffer::slice(double begin, double end) const
298{
299 return sliceImpl(clampIndex(begin), clampIndex(end));
300}
301
302Ref<ArrayBuffer> ArrayBuffer::slice(double begin) const
303{
304 return sliceImpl(clampIndex(begin), byteLength());
305}
306
307Ref<ArrayBuffer> ArrayBuffer::sliceImpl(unsigned begin, unsigned end) const
308{
309 unsigned size = begin <= end ? end - begin : 0;
310 auto result = ArrayBuffer::create(static_cast<const char*>(data()) + begin, size);
311 result->setSharingMode(sharingMode());
312 return result;
313}
314
315void ArrayBuffer::makeShared()
316{
317 m_contents.makeShared();
318 m_locked = true;
319}
320
321void ArrayBuffer::makeWasmMemory()
322{
323 m_locked = true;
324 m_isWasmMemory = true;
325}
326
327void ArrayBuffer::setSharingMode(ArrayBufferSharingMode newSharingMode)
328{
329 if (newSharingMode == sharingMode())
330 return;
331 RELEASE_ASSERT(!isShared()); // Cannot revert sharing.
332 RELEASE_ASSERT(newSharingMode == ArrayBufferSharingMode::Shared);
333 makeShared();
334}
335
336bool ArrayBuffer::shareWith(ArrayBufferContents& result)
337{
338 if (!m_contents.m_data || !isShared()) {
339 result.m_data = nullptr;
340 return false;
341 }
342
343 m_contents.shareWith(result);
344 return true;
345}
346
347bool ArrayBuffer::transferTo(VM& vm, ArrayBufferContents& result)
348{
349 Ref<ArrayBuffer> protect(*this);
350
351 if (!m_contents.m_data) {
352 result.m_data = nullptr;
353 return false;
354 }
355
356 if (isShared()) {
357 m_contents.shareWith(result);
358 return true;
359 }
360
361 bool isNeuterable = !m_pinCount && !m_locked;
362
363 if (!isNeuterable) {
364 m_contents.copyTo(result);
365 if (!result.m_data)
366 return false;
367 return true;
368 }
369
370 m_contents.transferTo(result);
371 notifyIncommingReferencesOfTransfer(vm);
372 return true;
373}
374
375// We allow neutering wasm memory ArrayBuffers even though they are locked.
376void ArrayBuffer::neuter(VM& vm)
377{
378 ASSERT(isWasmMemory());
379 ArrayBufferContents unused;
380 m_contents.transferTo(unused);
381 notifyIncommingReferencesOfTransfer(vm);
382}
383
384void ArrayBuffer::notifyIncommingReferencesOfTransfer(VM& vm)
385{
386 for (size_t i = numberOfIncomingReferences(); i--;) {
387 JSCell* cell = incomingReferenceAt(i);
388 if (JSArrayBufferView* view = jsDynamicCast<JSArrayBufferView*>(vm, cell))
389 view->neuter();
390 else if (ArrayBufferNeuteringWatchpointSet* watchpoint = jsDynamicCast<ArrayBufferNeuteringWatchpointSet*>(vm, cell))
391 watchpoint->fireAll();
392 }
393}
394
395ASCIILiteral errorMesasgeForTransfer(ArrayBuffer* buffer)
396{
397 ASSERT(buffer->isLocked());
398 if (buffer->isShared())
399 return "Cannot transfer a SharedArrayBuffer"_s;
400 if (buffer->isWasmMemory())
401 return "Cannot transfer a WebAssembly.Memory"_s;
402 return "Cannot transfer an ArrayBuffer whose backing store has been accessed by the JavaScriptCore C API"_s;
403}
404
405} // namespace JSC
406
407