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