mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 03:25:13 +00:00
LibWeb: Prevent AudioBuffer data being copied to or from a shared buffer
This commit is contained in:
parent
958938655d
commit
c4bc0842c1
Notes:
github-actions[bot]
2025-02-09 14:14:38 +00:00
Author: https://github.com/tcl3 Commit: https://github.com/LadybirdBrowser/ladybird/commit/c4bc0842c1b Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3298
3 changed files with 401 additions and 0 deletions
|
@ -94,6 +94,8 @@ WebIDL::ExceptionOr<void> AudioBuffer::copy_from_channel(GC::Root<WebIDL::Buffer
|
|||
if (!is<JS::Float32Array>(*destination->raw_object()))
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Float32Array");
|
||||
auto& float32_array = static_cast<JS::Float32Array&>(*destination->raw_object());
|
||||
if (float32_array.viewed_array_buffer()->is_shared_array_buffer())
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::SharedArrayBuffer, "Float32Array");
|
||||
|
||||
auto const channel = TRY(get_channel_data(channel_number));
|
||||
|
||||
|
@ -122,6 +124,8 @@ WebIDL::ExceptionOr<void> AudioBuffer::copy_to_channel(GC::Root<WebIDL::BufferSo
|
|||
if (!is<JS::Float32Array>(*source->raw_object()))
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Float32Array");
|
||||
auto const& float32_array = static_cast<JS::Float32Array const&>(*source->raw_object());
|
||||
if (float32_array.viewed_array_buffer()->is_shared_array_buffer())
|
||||
return vm.throw_completion<JS::TypeError>(JS::ErrorType::SharedArrayBuffer, "Float32Array");
|
||||
|
||||
auto channel = TRY(get_channel_data(channel_number));
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 62 tests
|
||||
|
||||
62 Pass
|
||||
Pass # AUDIT TASK RUNNER STARTED.
|
||||
Pass Executing "initialize"
|
||||
Pass Executing "copyFrom-exceptions"
|
||||
Pass Executing "copyTo-exceptions"
|
||||
Pass Executing "copyFrom-validate"
|
||||
Pass Executing "copyTo-validate"
|
||||
Pass Audit report
|
||||
Pass > [initialize]
|
||||
Pass Initialized values contains only the constant -1.
|
||||
Pass < [initialize] All assertions passed. (total 1 assertions)
|
||||
Pass > [copyFrom-exceptions]
|
||||
Pass AudioBuffer.prototype.copyFromChannel does exist.
|
||||
Pass 0: buffer = context.createBuffer(3, 16, context.sampleRate) did not throw an exception.
|
||||
Pass 1: buffer.copyFromChannel(null, 0) threw TypeError: "Not an object of type Float32Array".
|
||||
Pass 2: buffer.copyFromChannel(context, 0) threw TypeError: "Not an object of type Float32Array".
|
||||
Pass 3: buffer.copyFromChannel(x, -1) threw IndexSizeError: "Channel index is out of range".
|
||||
Pass 4: buffer.copyFromChannel(x, 3) threw IndexSizeError: "Channel index is out of range".
|
||||
Pass 5: buffer.copyFromChannel(x, 0, -1) did not throw an exception.
|
||||
Pass 6: buffer.copyFromChannel(x, 0, 16) did not throw an exception.
|
||||
Pass 7: buffer.copyFromChannel(x, 3) threw IndexSizeError: "Channel index is out of range".
|
||||
Pass 8: buffer.copyFromChannel(SharedArrayBuffer view, 0) threw TypeError: "The array buffer object cannot be a SharedArrayBuffer".
|
||||
Pass 9: buffer.copyFromChannel(SharedArrayBuffer view, 0, 0) threw TypeError: "The array buffer object cannot be a SharedArrayBuffer".
|
||||
Pass < [copyFrom-exceptions] All assertions passed. (total 11 assertions)
|
||||
Pass > [copyTo-exceptions]
|
||||
Pass AudioBuffer.prototype.copyToChannel does exist.
|
||||
Pass 0: buffer.copyToChannel(null, 0) threw TypeError: "Not an object of type Float32Array".
|
||||
Pass 1: buffer.copyToChannel(context, 0) threw TypeError: "Not an object of type Float32Array".
|
||||
Pass 2: buffer.copyToChannel(x, -1) threw IndexSizeError: "Channel index is out of range".
|
||||
Pass 3: buffer.copyToChannel(x, 3) threw IndexSizeError: "Channel index is out of range".
|
||||
Pass 4: buffer.copyToChannel(x, 0, -1) did not throw an exception.
|
||||
Pass 5: buffer.copyToChannel(x, 0, 16) did not throw an exception.
|
||||
Pass 6: buffer.copyToChannel(x, 3) threw IndexSizeError: "Channel index is out of range".
|
||||
Pass 7: buffer.copyToChannel(SharedArrayBuffer view, 0) threw TypeError: "The array buffer object cannot be a SharedArrayBuffer".
|
||||
Pass 8: buffer.copyToChannel(SharedArrayBuffer view, 0, 0) threw TypeError: "The array buffer object cannot be a SharedArrayBuffer".
|
||||
Pass < [copyTo-exceptions] All assertions passed. (total 10 assertions)
|
||||
Pass > [copyFrom-validate]
|
||||
Pass buffer.copyFromChannel(dst8, 0) is identical to the array [1,2,3,4,5,6,7,8].
|
||||
Pass buffer.copyFromChannel(dst8, 1) is identical to the array [2,3,4,5,6,7,8,9].
|
||||
Pass buffer.copyFromChannel(dst8, 2) is identical to the array [3,4,5,6,7,8,9,10].
|
||||
Pass buffer.copyFromChannel(dst8, 0, 1) is identical to the array [2,3,4,5,6,7,8,9].
|
||||
Pass buffer.copyFromChannel(dst8, 1, 1) is identical to the array [3,4,5,6,7,8,9,10].
|
||||
Pass buffer.copyFromChannel(dst8, 2, 1) is identical to the array [4,5,6,7,8,9,10,11].
|
||||
Pass buffer.copyFromChannel(dst8, 0, 11) is identical to the array [12,13,14,15,16,-1,-1,-1].
|
||||
Pass buffer.copyFromChannel(dst8, 1, 11) is identical to the array [13,14,15,16,17,-1,-1,-1].
|
||||
Pass buffer.copyFromChannel(dst8, 2, 11) is identical to the array [14,15,16,17,18,-1,-1,-1].
|
||||
Pass buffer.copyFromChannel(dst26, 0) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16...].
|
||||
Pass buffer.copyFromChannel(dst26, 1) is identical to the array [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17...].
|
||||
Pass buffer.copyFromChannel(dst26, 2) is identical to the array [3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18...].
|
||||
Pass < [copyFrom-validate] All assertions passed. (total 12 assertions)
|
||||
Pass > [copyTo-validate]
|
||||
Pass buffer = createConstantBuffer(context, 16, [-1,-1,-1]) did not throw an exception.
|
||||
Pass buffer.copyToChannel(src, 0) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16...].
|
||||
Pass buffer.copyToChannel(src, 1) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16...].
|
||||
Pass buffer.copyToChannel(src, 2) is identical to the array [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16...].
|
||||
Pass buffer.copyToChannel(src10, 0) is identical to the array [1,2,3,4,5,6,7,8,9,10,-1,-1,-1,-1,-1,-1...].
|
||||
Pass buffer.copyToChannel(src10, 1) is identical to the array [1,2,3,4,5,6,7,8,9,10,-1,-1,-1,-1,-1,-1...].
|
||||
Pass buffer.copyToChannel(src10, 2) is identical to the array [1,2,3,4,5,6,7,8,9,10,-1,-1,-1,-1,-1,-1...].
|
||||
Pass buffer.copyToChannel(src10, 0, 5) is identical to the array [-1,-1,-1,-1,-1,1,2,3,4,5,6,7,8,9,10,-1...].
|
||||
Pass buffer.copyToChannel(src10, 1, 5) is identical to the array [-1,-1,-1,-1,-1,1,2,3,4,5,6,7,8,9,10,-1...].
|
||||
Pass buffer.copyToChannel(src10, 2, 5) is identical to the array [-1,-1,-1,-1,-1,1,2,3,4,5,6,7,8,9,10,-1...].
|
||||
Pass < [copyTo-validate] All assertions passed. (total 10 assertions)
|
||||
Pass # AUDIT TASK RUNNER FINISHED: 5 tasks ran successfully.
|
|
@ -0,0 +1,330 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Test Basic Functionality of AudioBuffer.copyFromChannel and
|
||||
AudioBuffer.copyToChannel
|
||||
</title>
|
||||
<script src="../../../resources/testharness.js"></script>
|
||||
<script src="../../../resources/testharnessreport.js"></script>
|
||||
<script src="../../../webaudio/resources/audit-util.js"></script>
|
||||
<script src="../../../webaudio/resources/audit.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script id="layout-test-code">
|
||||
// Define utility routines.
|
||||
|
||||
// Initialize the AudioBuffer |buffer| with a ramp signal on each channel.
|
||||
// The ramp starts at channel number + 1.
|
||||
function initializeAudioBufferRamp(buffer) {
|
||||
for (let c = 0; c < buffer.numberOfChannels; ++c) {
|
||||
let d = buffer.getChannelData(c);
|
||||
for (let k = 0; k < d.length; ++k) {
|
||||
d[k] = k + c + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a Float32Array of length |length| and initialize the array to
|
||||
// -1.
|
||||
function createInitializedF32Array(length) {
|
||||
let x = new Float32Array(length);
|
||||
for (let k = 0; k < length; ++k) {
|
||||
x[k] = -1;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// Create a Float32Array of length |length| that is initialized to be a
|
||||
// ramp starting at 1.
|
||||
function createFloat32RampArray(length) {
|
||||
let x = new Float32Array(length);
|
||||
for (let k = 0; k < x.length; ++k) {
|
||||
x[k] = k + 1;
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
// Test that the array |x| is a ramp starting at value |start| of length
|
||||
// |length|, starting at |startIndex| in the array. |startIndex| is
|
||||
// optional and defaults to 0. Any other values must be -1.
|
||||
function shouldBeRamp(
|
||||
should, testName, x, startValue, length, startIndex) {
|
||||
let k;
|
||||
let startingIndex = startIndex || 0;
|
||||
let expected = Array(x.length);
|
||||
|
||||
// Fill the expected array with the correct results.
|
||||
|
||||
// The initial part (if any) must be -1.
|
||||
for (k = 0; k < startingIndex; ++k) {
|
||||
expected[k] = -1;
|
||||
}
|
||||
|
||||
// The second part should be a ramp starting with |startValue|
|
||||
for (; k < startingIndex + length; ++k) {
|
||||
expected[k] = startValue + k - startingIndex;
|
||||
}
|
||||
|
||||
// The last part (if any) should be -1.
|
||||
for (; k < x.length; ++k) {
|
||||
expected[k] = -1;
|
||||
}
|
||||
|
||||
should(x, testName, {numberOfArrayLog: 32}).beEqualToArray(expected);
|
||||
}
|
||||
|
||||
let audit = Audit.createTaskRunner();
|
||||
|
||||
let context = new AudioContext();
|
||||
// Temp array for testing exceptions for copyToChannel/copyFromChannel.
|
||||
// The length is arbitrary.
|
||||
let x = new Float32Array(8);
|
||||
|
||||
// Number of frames in the AudioBuffer for testing. This is pretty
|
||||
// arbitrary so choose a fairly small value.
|
||||
let bufferLength = 16;
|
||||
|
||||
// Number of channels in the AudioBuffer. Also arbitrary, but it should
|
||||
// be greater than 1 for test coverage.
|
||||
let numberOfChannels = 3;
|
||||
|
||||
// AudioBuffer that will be used for testing copyFrom and copyTo.
|
||||
let buffer = context.createBuffer(
|
||||
numberOfChannels, bufferLength, context.sampleRate);
|
||||
|
||||
let initialValues = Array(numberOfChannels);
|
||||
|
||||
// Initialize things
|
||||
audit.define('initialize', (task, should) => {
|
||||
// Initialize to -1.
|
||||
initialValues.fill(-1);
|
||||
should(initialValues, 'Initialized values').beConstantValueOf(-1)
|
||||
task.done();
|
||||
});
|
||||
|
||||
// Test that expected exceptions are signaled for copyFrom.
|
||||
audit.define('copyFrom-exceptions', (task, should) => {
|
||||
should(
|
||||
AudioBuffer.prototype.copyFromChannel,
|
||||
'AudioBuffer.prototype.copyFromChannel')
|
||||
.exist();
|
||||
|
||||
should(
|
||||
() => {
|
||||
buffer = context.createBuffer(
|
||||
numberOfChannels, bufferLength, context.sampleRate);
|
||||
},
|
||||
'0: buffer = context.createBuffer(' + numberOfChannels + ', ' +
|
||||
bufferLength + ', context.sampleRate)')
|
||||
.notThrow();
|
||||
should(() => {
|
||||
buffer.copyFromChannel(null, 0);
|
||||
}, '1: buffer.copyFromChannel(null, 0)').throw(TypeError);
|
||||
should(() => {
|
||||
buffer.copyFromChannel(context, 0);
|
||||
}, '2: buffer.copyFromChannel(context, 0)').throw(TypeError);
|
||||
should(() => {
|
||||
buffer.copyFromChannel(x, -1);
|
||||
}, '3: buffer.copyFromChannel(x, -1)').throw(DOMException, 'IndexSizeError');
|
||||
should(
|
||||
() => {
|
||||
buffer.copyFromChannel(x, numberOfChannels);
|
||||
},
|
||||
'4: buffer.copyFromChannel(x, ' + numberOfChannels + ')')
|
||||
.throw(DOMException, 'IndexSizeError');
|
||||
;
|
||||
should(() => {
|
||||
buffer.copyFromChannel(x, 0, -1);
|
||||
}, '5: buffer.copyFromChannel(x, 0, -1)').notThrow();
|
||||
should(
|
||||
() => {
|
||||
buffer.copyFromChannel(x, 0, bufferLength);
|
||||
},
|
||||
'6: buffer.copyFromChannel(x, 0, ' + bufferLength + ')')
|
||||
.notThrow();
|
||||
|
||||
should(() => {
|
||||
buffer.copyFromChannel(x, 3);
|
||||
}, '7: buffer.copyFromChannel(x, 3)').throw(DOMException, 'IndexSizeError');
|
||||
|
||||
// See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
|
||||
// WebAssembly.Memory's size is in multiples of 64 KiB
|
||||
const shared_buffer = new Float32Array(new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer);
|
||||
should(
|
||||
() => {
|
||||
buffer.copyFromChannel(shared_buffer, 0);
|
||||
},
|
||||
'8: buffer.copyFromChannel(SharedArrayBuffer view, 0)')
|
||||
.throw(TypeError);
|
||||
|
||||
should(
|
||||
() => {
|
||||
buffer.copyFromChannel(shared_buffer, 0, 0);
|
||||
},
|
||||
'9: buffer.copyFromChannel(SharedArrayBuffer view, 0, 0)')
|
||||
.throw(TypeError);
|
||||
|
||||
task.done();
|
||||
});
|
||||
|
||||
// Test that expected exceptions are signaled for copyTo.
|
||||
audit.define('copyTo-exceptions', (task, should) => {
|
||||
should(
|
||||
AudioBuffer.prototype.copyToChannel,
|
||||
'AudioBuffer.prototype.copyToChannel')
|
||||
.exist();
|
||||
should(() => {
|
||||
buffer.copyToChannel(null, 0);
|
||||
}, '0: buffer.copyToChannel(null, 0)').throw(TypeError);
|
||||
should(() => {
|
||||
buffer.copyToChannel(context, 0);
|
||||
}, '1: buffer.copyToChannel(context, 0)').throw(TypeError);
|
||||
should(() => {
|
||||
buffer.copyToChannel(x, -1);
|
||||
}, '2: buffer.copyToChannel(x, -1)').throw(DOMException, 'IndexSizeError');
|
||||
should(
|
||||
() => {
|
||||
buffer.copyToChannel(x, numberOfChannels);
|
||||
},
|
||||
'3: buffer.copyToChannel(x, ' + numberOfChannels + ')')
|
||||
.throw(DOMException, 'IndexSizeError');
|
||||
should(() => {
|
||||
buffer.copyToChannel(x, 0, -1);
|
||||
}, '4: buffer.copyToChannel(x, 0, -1)').notThrow();
|
||||
should(
|
||||
() => {
|
||||
buffer.copyToChannel(x, 0, bufferLength);
|
||||
},
|
||||
'5: buffer.copyToChannel(x, 0, ' + bufferLength + ')')
|
||||
.notThrow();
|
||||
|
||||
should(() => {
|
||||
buffer.copyToChannel(x, 3);
|
||||
}, '6: buffer.copyToChannel(x, 3)').throw(DOMException, 'IndexSizeError');
|
||||
|
||||
// See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
|
||||
// WebAssembly.Memory's size is in multiples of 64 KiB
|
||||
const shared_buffer = new Float32Array(new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer);
|
||||
should(
|
||||
() => {
|
||||
buffer.copyToChannel(shared_buffer, 0);
|
||||
},
|
||||
'7: buffer.copyToChannel(SharedArrayBuffer view, 0)')
|
||||
.throw(TypeError);
|
||||
|
||||
should(
|
||||
() => {
|
||||
buffer.copyToChannel(shared_buffer, 0, 0);
|
||||
},
|
||||
'8: buffer.copyToChannel(SharedArrayBuffer view, 0, 0)')
|
||||
.throw(TypeError);
|
||||
|
||||
task.done();
|
||||
});
|
||||
|
||||
// Test copyFromChannel
|
||||
audit.define('copyFrom-validate', (task, should) => {
|
||||
// Initialize the AudioBuffer to a ramp for testing copyFrom.
|
||||
initializeAudioBufferRamp(buffer);
|
||||
|
||||
// Test copyFrom operation with a short destination array, filling the
|
||||
// destination completely.
|
||||
for (let c = 0; c < numberOfChannels; ++c) {
|
||||
let dst8 = createInitializedF32Array(8);
|
||||
buffer.copyFromChannel(dst8, c);
|
||||
shouldBeRamp(
|
||||
should, 'buffer.copyFromChannel(dst8, ' + c + ')', dst8, c + 1, 8)
|
||||
}
|
||||
|
||||
// Test copyFrom operation with a short destination array using a
|
||||
// non-zero start index that still fills the destination completely.
|
||||
for (let c = 0; c < numberOfChannels; ++c) {
|
||||
let dst8 = createInitializedF32Array(8);
|
||||
buffer.copyFromChannel(dst8, c, 1);
|
||||
shouldBeRamp(
|
||||
should, 'buffer.copyFromChannel(dst8, ' + c + ', 1)', dst8, c + 2,
|
||||
8)
|
||||
}
|
||||
|
||||
// Test copyFrom operation with a short destination array using a
|
||||
// non-zero start index that does not fill the destinatiom completely.
|
||||
// The extra elements should be unchanged.
|
||||
for (let c = 0; c < numberOfChannels; ++c) {
|
||||
let dst8 = createInitializedF32Array(8);
|
||||
let startInChannel = bufferLength - 5;
|
||||
buffer.copyFromChannel(dst8, c, startInChannel);
|
||||
shouldBeRamp(
|
||||
should,
|
||||
'buffer.copyFromChannel(dst8, ' + c + ', ' + startInChannel + ')',
|
||||
dst8, c + 1 + startInChannel, bufferLength - startInChannel);
|
||||
}
|
||||
|
||||
// Copy operation with the destination longer than the buffer, leaving
|
||||
// the trailing elements of the destination untouched.
|
||||
for (let c = 0; c < numberOfChannels; ++c) {
|
||||
let dst26 = createInitializedF32Array(bufferLength + 10);
|
||||
buffer.copyFromChannel(dst26, c);
|
||||
shouldBeRamp(
|
||||
should, 'buffer.copyFromChannel(dst26, ' + c + ')', dst26, c + 1,
|
||||
bufferLength);
|
||||
}
|
||||
|
||||
task.done();
|
||||
});
|
||||
|
||||
// Test copyTo
|
||||
audit.define('copyTo-validate', (task, should) => {
|
||||
// Create a source consisting of a ramp starting at 1, longer than the
|
||||
// AudioBuffer
|
||||
let src = createFloat32RampArray(bufferLength + 10);
|
||||
|
||||
// Test copyTo with AudioBuffer shorter than Float32Array. The
|
||||
// AudioBuffer should be completely filled with the Float32Array.
|
||||
should(
|
||||
() => {
|
||||
buffer =
|
||||
createConstantBuffer(context, bufferLength, initialValues);
|
||||
},
|
||||
'buffer = createConstantBuffer(context, ' + bufferLength + ', [' +
|
||||
initialValues + '])')
|
||||
.notThrow();
|
||||
|
||||
for (let c = 0; c < numberOfChannels; ++c) {
|
||||
buffer.copyToChannel(src, c);
|
||||
shouldBeRamp(
|
||||
should, 'buffer.copyToChannel(src, ' + c + ')',
|
||||
buffer.getChannelData(c), 1, bufferLength);
|
||||
}
|
||||
|
||||
// Test copyTo with AudioBuffer longer than the Float32Array. The tail
|
||||
// of the AudioBuffer should be unchanged.
|
||||
buffer = createConstantBuffer(context, bufferLength, initialValues);
|
||||
let src10 = createFloat32RampArray(10);
|
||||
for (let c = 0; c < numberOfChannels; ++c) {
|
||||
buffer.copyToChannel(src10, c);
|
||||
shouldBeRamp(
|
||||
should, 'buffer.copyToChannel(src10, ' + c + ')',
|
||||
buffer.getChannelData(c), 1, 10);
|
||||
}
|
||||
|
||||
// Test copyTo with non-default startInChannel. Part of the AudioBuffer
|
||||
// should filled with the beginning and end sections untouched.
|
||||
buffer = createConstantBuffer(context, bufferLength, initialValues);
|
||||
for (let c = 0; c < numberOfChannels; ++c) {
|
||||
let startInChannel = 5;
|
||||
buffer.copyToChannel(src10, c, startInChannel);
|
||||
|
||||
shouldBeRamp(
|
||||
should,
|
||||
'buffer.copyToChannel(src10, ' + c + ', ' + startInChannel + ')',
|
||||
buffer.getChannelData(c), 1, src10.length, startInChannel);
|
||||
}
|
||||
task.done();
|
||||
});
|
||||
|
||||
audit.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue