Source code
Revision control
Copy as Markdown
Other Tools
// |reftest| skip-if(!xulRuntime.shell)
/* -*- Mode: js2; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* Any copyright is dedicated to the Public Domain.
* Contributor:
* Vladimir Vukicevic <vladimir@pobox.com>
*/
//-----------------------------------------------------------------------------
var BUGNUMBER = 532774;
var summary = 'js typed arrays (webgl arrays)';
var actual = '';
var expect = '';
//-----------------------------------------------------------------------------
if (typeof gczeal !== 'undefined')
gczeal(0)
if (typeof numberToDouble !== 'function') {
var numberToDouble = SpecialPowers.Cu.getJSTestingFunctions().numberToDouble;
}
test();
//-----------------------------------------------------------------------------
function test()
{
printBugNumber(BUGNUMBER);
printStatus(summary);
var TestPassCount = 0;
var TestFailCount = 0;
var TestTodoCount = 0;
var TODO = 1;
function check(fun, msg, todo) {
var thrown = null;
var success = false;
try {
success = fun();
} catch (x) {
thrown = x;
}
if (thrown)
success = false;
if (todo) {
TestTodoCount++;
if (success) {
var ex = new Error;
print ("=== TODO but PASSED? ===");
print (ex.stack);
print ("========================");
}
return;
}
if (success) {
TestPassCount++;
} else {
TestFailCount++;
var ex = new Error;
print ("=== FAILED ===");
if (msg)
print (msg);
print (ex.stack);
if (thrown) {
print (" threw exception:");
print (thrown);
}
print ("==============");
}
}
function checkThrows(fun, type, todo) {
var thrown = false;
try {
fun();
} catch (x) {
thrown = x;
}
if (typeof(type) !== 'undefined')
if (thrown) {
check(() => thrown instanceof type,
"expected " + type.name + " but saw " + thrown,
todo);
} else {
check(() => thrown, "expected " + type.name + " but no exception thrown", todo);
}
else
check(() => thrown, undefined, todo);
}
function checkThrowsTODO(fun, type) {
checkThrows(fun, type, true);
}
function testBufferManagement() {
// Single buffer
var buffer = new ArrayBuffer(128);
buffer = null;
gc();
// Buffer with single view, kill the view first
buffer = new ArrayBuffer(128);
var v1 = new Uint8Array(buffer);
gc();
v1 = null;
gc();
buffer = null;
gc();
// Buffer with single view, kill the buffer first
buffer = new ArrayBuffer(128);
v1 = new Uint8Array(buffer);
gc();
buffer = null;
gc();
v1 = null;
gc();
// Buffer with multiple views, kill first view first
buffer = new ArrayBuffer(128);
v1 = new Uint8Array(buffer);
v2 = new Uint8Array(buffer);
gc();
v1 = null;
gc();
v2 = null;
gc();
// Buffer with multiple views, kill second view first
buffer = new ArrayBuffer(128);
v1 = new Uint8Array(buffer);
v2 = new Uint8Array(buffer);
gc();
v2 = null;
gc();
v1 = null;
gc();
// Buffer with multiple views, kill all possible subsets of views
buffer = new ArrayBuffer(128);
for (let order = 0; order < 16; order++) {
var views = [ new Uint8Array(buffer),
new Uint8Array(buffer),
new Uint8Array(buffer),
new Uint8Array(buffer) ];
gc();
// Kill views according to the bits set in 'order'
for (let i = 0; i < 4; i++) {
if (order & (1 << i))
views[i] = null;
}
gc();
views = null;
gc();
}
// Similar: multiple views, kill them one at a time in every possible order
buffer = new ArrayBuffer(128);
for (let order = 0; order < 4*3*2*1; order++) {
var views = [ new Uint8Array(buffer),
new Uint8Array(buffer),
new Uint8Array(buffer),
new Uint8Array(buffer) ];
gc();
var sequence = [ 0, 1, 2, 3 ];
let groupsize = 4*3*2*1;
let o = order;
for (let i = 4; i > 0; i--) {
groupsize = groupsize / i;
let which = Math.floor(o/groupsize);
[ sequence[i-1], sequence[which] ] = [ sequence[which], sequence[i-1] ];
o = o % groupsize;
}
for (let i = 0; i < 4; i++) {
views[i] = null;
gc();
}
}
// Multiple buffers with multiple views
var views = [];
for (let numViews of [ 1, 2, 0, 3, 2, 1 ]) {
buffer = new ArrayBuffer(128);
for (let viewNum = 0; viewNum < numViews; viewNum++) {
views.push(new Int8Array(buffer));
}
}
if (typeof setMarkStackLimit === 'function') {
setMarkStackLimit(200);
}
var forceOverflow = [ buffer ];
for (let i = 0; i < 1000; i++) {
forceOverflow = [ forceOverflow ];
}
gc();
buffer = null;
views = null;
gcslice(3); gcslice(3); gcslice(3); gcslice(3); gcslice(3); gcslice(3); gc();
}
var buf, buf2;
buf = new ArrayBuffer(100);
check(() => buf);
check(() => buf.byteLength == 100);
buf.byteLength = 50;
check(() => buf.byteLength == 100);
var zerobuf = new ArrayBuffer(0);
check(() => zerobuf);
check(() => zerobuf.byteLength == 0);
check(() => (new Int32Array(zerobuf)).length == 0);
checkThrows(() => new Int32Array(zerobuf, 1));
var zerobuf2 = new ArrayBuffer();
check(() => zerobuf2.byteLength == 0);
checkThrows(() => new ArrayBuffer(-100), RangeError);
// this is using js_ValueToECMAUInt32, which is giving 0 for "abc"
checkThrowsTODO(() => new ArrayBuffer("abc"), TypeError);
var zeroarray = new Int32Array(0);
check(() => zeroarray.length == 0);
check(() => zeroarray.byteLength == 0);
check(() => zeroarray.buffer);
check(() => zeroarray.buffer.byteLength == 0);
var zeroarray2 = new Int32Array();
check(() => zeroarray2.length == 0);
check(() => zeroarray2.byteLength == 0);
check(() => zeroarray2.buffer);
check(() => zeroarray2.buffer.byteLength == 0);
var a = new Int32Array(20);
check(() => a);
check(() => a.length == 20);
check(() => a.byteLength == 80);
check(() => a.byteOffset == 0);
check(() => a.buffer);
check(() => a.buffer.byteLength == 80);
var b = new Uint8Array(a.buffer, 4, 4);
check(() => b);
check(() => b.length == 4);
check(() => b.byteLength == 4);
check(() => a.buffer == b.buffer);
b[0] = 0xaa;
b[1] = 0xbb;
b[2] = 0xcc;
b[3] = 0xdd;
check(() => a[0] == 0);
check(() => a[1] != 0);
check(() => a[2] == 0);
buf = new ArrayBuffer(4);
check(() => (new Int8Array(buf)).length == 4);
check(() => (new Uint8Array(buf)).length == 4);
check(() => (new Int16Array(buf)).length == 2);
check(() => (new Uint16Array(buf)).length == 2);
check(() => (new Int32Array(buf)).length == 1);
check(() => (new Uint32Array(buf)).length == 1);
check(() => (new Float32Array(buf)).length == 1);
checkThrows(() => (new Float64Array(buf)));
buf2 = new ArrayBuffer(8);
check(() => (new Float64Array(buf2)).length == 1);
buf = new ArrayBuffer(5);
check(() => buf);
check(() => buf.byteLength == 5);
check(() => new Int32Array(buf, 0, 1));
checkThrows(() => new Int32Array(buf, 0));
check(() => new Int8Array(buf, 0));
check(() => (new Int8Array(buf, 3)).byteLength == 2);
checkThrows(() => new Int8Array(buf, 500));
checkThrows(() => new Int8Array(buf, 0, 50));
checkThrows(() => new Float32Array(buf, 500));
checkThrows(() => new Float32Array(buf, 0, 50));
var sl = a.subarray(5,10);
check(() => sl.length == 5);
check(() => sl.buffer == a.buffer);
check(() => sl.byteLength == 20);
check(() => sl.byteOffset == 20);
check(() => a.subarray(5,5).length == 0);
check(() => a.subarray(-5).length == 5);
check(() => a.subarray(-100).length == 20);
check(() => a.subarray(0, 2).length == 2);
check(() => a.subarray().length == a.length);
check(() => a.subarray(-7,-5).length == 2);
check(() => a.subarray(-5,-7).length == 0);
check(() => a.subarray(15).length == 5);
a = new Uint8Array([0xaa, 0xbb, 0xcc]);
check(() => a.length == 3);
check(() => a.byteLength == 3);
check(() => a[1] == 0xbb);
// not sure if this is supposed to throw or to treat "foo"" as 0.
checkThrowsTODO(() => new Int32Array([0xaa, "foo", 0xbb]), Error);
checkThrows(() => new Int32Array(-100));
a = new Uint8Array(3);
// XXX these are ignored now and return undefined
//checkThrows(() => a[5000] = 0, RangeError);
//checkThrows(() => a["hello"] = 0, TypeError);
//checkThrows(() => a[-10] = 0, RangeError);
check(() => (a[0] = "10") && (a[0] == 10));
// check Uint8ClampedArray, which is an extension to this extension
a = new Uint8ClampedArray(4);
a[0] = 128;
a[1] = 512;
a[2] = -123.723;
a[3] = "foopy";
check(() => a[0] == 128);
check(() => a[1] == 255);
check(() => a[2] == 0);
check(() => a[3] == 0);
// check handling of holes and non-numeric values
var x = Array(5);
x[0] = "hello";
x[1] = { };
//x[2] is a hole
x[3] = undefined;
x[4] = true;
a = new Uint8Array(x);
check(() => a[0] == 0);
check(() => a[1] == 0);
check(() => a[2] == 0);
check(() => a[3] == 0);
check(() => a[4] == 1);
a = new Float32Array(x);
check(() => !(a[0] == a[0]));
check(() => !(a[1] == a[1]));
check(() => !(a[2] == a[2]));
check(() => !(a[3] == a[3]));
check(() => a[4] == 1);
// test set()
var empty = new Int32Array(0);
a = new Int32Array(9);
empty.set([]);
empty.set([], 0);
empty.set(empty);
checkThrows(() => empty.set([1]));
checkThrows(() => empty.set([1], 0));
checkThrows(() => empty.set([1], 1));
a.set([]);
a.set([], 3);
a.set([], 9);
a.set(a);
a.set(empty);
a.set(empty, 3);
a.set(empty, 9);
a.set(Array.prototype);
checkThrows(() => a.set(empty, 100));
checkThrows(() => a.set([1,2,3,4,5,6,7,8,9,10]));
checkThrows(() => a.set([1,2,3,4,5,6,7,8,9,10], 0));
checkThrows(() => a.set([1,2,3,4,5,6,7,8,9,10], 0x7fffffff));
checkThrows(() => a.set([1,2,3,4,5,6,7,8,9,10], 0xffffffff));
checkThrows(() => a.set([1,2,3,4,5,6], 6));
checkThrows(() => a.set(new Array(0x7fffffff)));
checkThrows(() => a.set([1,2,3], 2147483647));
a.set(ArrayBuffer.prototype);
checkThrows(() => a.set(Int16Array.prototype), TypeError);
checkThrows(() => a.set(Int32Array.prototype), TypeError);
a.set([1,2,3]);
a.set([4,5,6], 3);
check(() =>
a[0] == 1 && a[1] == 2 && a[2] == 3 &&
a[3] == 4 && a[4] == 5 && a[5] == 6 &&
a[6] == 0 && a[7] == 0 && a[8] == 0);
b = new Float32Array([7,8,9]);
a.set(b, 0);
a.set(b, 3);
check(() =>
a[0] == 7 && a[1] == 8 && a[2] == 9 &&
a[3] == 7 && a[4] == 8 && a[5] == 9 &&
a[6] == 0 && a[7] == 0 && a[8] == 0);
a.set(a.subarray(0,3), 6);
check(() =>
a[0] == 7 && a[1] == 8 && a[2] == 9 &&
a[3] == 7 && a[4] == 8 && a[5] == 9 &&
a[6] == 7 && a[7] == 8 && a[8] == 9);
a.set([1,2,3,4,5,6,7,8,9]);
a.set(a.subarray(0,6), 3);
check(() =>
a[0] == 1 && a[1] == 2 && a[2] == 3 &&
a[3] == 1 && a[4] == 2 && a[5] == 3 &&
a[6] == 4 && a[7] == 5 && a[8] == 6);
a.set(a.subarray(3,9), 0);
check(() =>
a[0] == 1 && a[1] == 2 && a[2] == 3 &&
a[3] == 4 && a[4] == 5 && a[5] == 6 &&
a[6] == 4 && a[7] == 5 && a[8] == 6);
// verify that subarray() returns a new view that
// references the same buffer
a.subarray(0,3).set(a.subarray(3,6), 0);
check(() =>
a[0] == 4 && a[1] == 5 && a[2] == 6 &&
a[3] == 4 && a[4] == 5 && a[5] == 6 &&
a[6] == 4 && a[7] == 5 && a[8] == 6);
a = new ArrayBuffer(0x10);
checkThrows(() => new Uint32Array(buffer, 4, 0x3FFFFFFF));
check(() => new Float32Array(null).length === 0);
a = new Uint8Array(0x100);
b = Uint32Array.prototype.subarray.apply(a, [0, 0x100]);
check(() => Object.prototype.toString.call(b) === "[object Uint8Array]");
check(() => b.buffer === a.buffer);
check(() => b.length === a.length);
check(() => b.byteLength === a.byteLength);
check(() => b.byteOffset === a.byteOffset);
check(() => b.BYTES_PER_ELEMENT === a.BYTES_PER_ELEMENT);
// webidl section 4.4.6, getter bullet point 2.2: prototypes are not
// platform objects, and calling the getter of any attribute defined on the
// interface should throw a TypeError according to
checkThrows(() => ArrayBuffer.prototype.byteLength, TypeError);
checkThrows(() => Int32Array.prototype.length, TypeError);
checkThrows(() => Int32Array.prototype.byteLength, TypeError);
checkThrows(() => Int32Array.prototype.byteOffset, TypeError);
checkThrows(() => Float64Array.prototype.length, TypeError);
checkThrows(() => Float64Array.prototype.byteLength, TypeError);
checkThrows(() => Float64Array.prototype.byteOffset, TypeError);
// webidl 4.4.6: a readonly attribute's setter is undefined. From
// observation, that seems to mean it silently does nothing, and returns
// the value that you tried to set it to.
check(() => Int32Array.prototype.length = true);
check(() => Float64Array.prototype.length = true);
check(() => Int32Array.prototype.byteLength = true);
check(() => Float64Array.prototype.byteLength = true);
check(() => Int32Array.prototype.byteOffset = true);
check(() => Float64Array.prototype.byteOffset = true);
// ArrayBuffer, Int32Array and Float64Array are native functions and have a
// .length, so none of these should throw:
check(() => (new Int32Array(ArrayBuffer)).length >= 0);
check(() => (new Int32Array(Int32Array)).length >= 0);
check(() => (new Int32Array(Float64Array)).length >= 0);
// webidl 4.4.6, under getters: "The value of the Function object’s
// 'length' property is the Number value 0"
//
// Except this fails in getOwnPropertyDescriptor, I think because
// Int32Array.prototype does not provide a lookup hook, and the fallback
// case ends up calling the getter. Which seems odd to me, but much of this
// stuff baffles me. It does seem strange that there's no way to do
// getOwnPropertyDescriptor on any of these attributes.
//
//check(Object.getOwnPropertyDescriptor(Int32Array.prototype, 'byteOffset')['get'].length == 0);
check(() => Int32Array.BYTES_PER_ELEMENT == 4);
check(() => (new Int32Array(4)).BYTES_PER_ELEMENT == 4);
check(() => (new Int32Array()).BYTES_PER_ELEMENT == 4);
check(() => (new Int32Array(0)).BYTES_PER_ELEMENT == 4);
check(() => Int16Array.BYTES_PER_ELEMENT == Uint16Array.BYTES_PER_ELEMENT);
// test various types of args; numberToDouble(2) is used to ensure that the
// function gets a double, and not a demoted int
check(() => (new Float32Array(numberToDouble(2))).length == 2);
check(() => (new Float32Array({ length: 10 })).length == 10);
check(() => (new Float32Array({})).length == 0);
check(() => new Float32Array("3").length === 3);
check(() => new Float32Array(null).length === 0);
check(() => new Float32Array(undefined).length === 0);
// check that NaN conversions happen correctly with array conversions
check(() => (new Int32Array([NaN])[0]) == 0);
check(() => { var q = new Float32Array([NaN])[0]; return q != q; });
// check that setting and reading arbitrary properties works
// this is not something that will be done in real world
// situations, but it should work when done just like in
// regular objects
buf = new ArrayBuffer(128);
a = new Uint32Array(buf, 0, 4);
check(() => a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0);
buf.a = 42;
buf.b = "abcdefgh";
buf.c = {a:'literal'};
check(() => a[0] == 0 && a[1] == 0 && a[2] == 0 && a[3] == 0);
check(() => buf.a == 42);
delete buf.a;
check(() => !buf.a);
// check edge cases for small arrays
// 16 reserved slots
a = new Uint8Array(120);
check(() => a.byteLength == 120);
check(() => a.length == 120);
for (var i = 0; i < a.length; i++)
check(() => a[i] == 0)
a = new Uint8Array(121);
check(() => a.byteLength == 121);
check(() => a.length == 121);
for (var i = 0; i < a.length; i++)
check(() => a[i] == 0)
// check that TM generated byte offset is right (requires run with -j)
a = new Uint8Array(100);
a[99] = 5;
b = new Uint8Array(a.buffer, 9); // force a offset
// use a loop to invoke the TM
for (var i = 0; i < b.length; i++)
check(() => b[90] == 5)
// Protos and proxies, oh my!
var alien = newGlobal({newCompartment: true});
var alien_view = alien.eval('view = new Uint8Array(7)');
var alien_buffer = alien.eval('buffer = view.buffer');
// when creating a view of a buffer in a different compartment, the view
// itself should be created in the other compartment and wrapped for use in
// this compartment. (There should never be a compartment boundary between
// an ArrayBufferView and its ArrayBuffer.)
var view = new Int8Array(alien_buffer);
// First make sure they're looking at the same data
alien_view[3] = 77;
check(() => view[3] == 77);
// Now check that the proxy setup is as expected in the cross-compartment
// case.
if (isProxy(alien)) {
check(() => isProxy(alien_view));
check(() => isProxy(alien_buffer));
check(() => isProxy(view)); // the real test
}
// cross-realm property access
check(() => alien_buffer.byteLength == 7);
check(() => alien_view.byteLength == 7);
check(() => view.byteLength == 7);
// typed array protos should be equal
simple = new Int8Array(12);
check(() => Object.getPrototypeOf(view) == Object.getPrototypeOf(simple));
check(() => Object.getPrototypeOf(view) == Int8Array.prototype);
// Most named properties are defined on %TypedArray%.prototype.
check(() => !simple.hasOwnProperty('byteLength'));
check(() => !Int8Array.prototype.hasOwnProperty('byteLength'));
check(() => Object.getPrototypeOf(Int8Array.prototype).hasOwnProperty('byteLength'));
check(() => !simple.hasOwnProperty("BYTES_PER_ELEMENT"));
check(() => Int8Array.prototype.hasOwnProperty("BYTES_PER_ELEMENT"));
check(() => !Object.getPrototypeOf(Int8Array.prototype).hasOwnProperty("BYTES_PER_ELEMENT"));
// crazy as it sounds, the named properties are configurable per WebIDL.
// But we are currently discussing the situation, and typed arrays may be
// pulled into the ES spec, so for now this is disallowed.
if (false) {
check(() => simple.byteLength == 12);
getter = Object.getOwnPropertyDescriptor(Int8Array.prototype, 'byteLength').get;
Object.defineProperty(Int8Array.prototype, 'byteLength', { get: function () { return 1 + getter.apply(this) } });
check(() => simple.byteLength == 13);
}
// test copyWithin()
var numbers = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ];
function tastring(tarray) {
return [...tarray].toString();
}
function checkCopyWithin(offset, start, end, dest, want) {
var numbers_buffer = new Uint8Array(numbers).buffer;
var view = new Int8Array(numbers_buffer, offset);
view.copyWithin(dest, start, end);
check(() => tastring(view) == want.toString());
if (tastring(view) != want.toString()) {
print("Wanted: " + want.toString());
print("Got : " + tastring(view));
}
}
// basic copyWithin [2,5) -> 4
checkCopyWithin(0, 2, 5, 4, [ 0, 1, 2, 3, 2, 3, 4, 7, 8 ]);
// negative values should count from end
checkCopyWithin(0, -7, 5, 4, [ 0, 1, 2, 3, 2, 3, 4, 7, 8 ]);
checkCopyWithin(0, 2, -4, 4, [ 0, 1, 2, 3, 2, 3, 4, 7, 8 ]);
checkCopyWithin(0, 2, 5, -5, [ 0, 1, 2, 3, 2, 3, 4, 7, 8 ]);
checkCopyWithin(0, -7, -4, -5, [ 0, 1, 2, 3, 2, 3, 4, 7, 8 ]);
// offset
checkCopyWithin(2, 0, 3, 4, [ 2, 3, 4, 5, 2, 3, 4 ]);
// clipping
checkCopyWithin(0, 5000, 6000, 0, [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]);
checkCopyWithin(0, -5000, -6000, 0, [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]);
checkCopyWithin(0, -5000, 6000, 0, [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]);
checkCopyWithin(0, 5000, 6000, 1, [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]);
checkCopyWithin(0, -5000, -6000, 1, [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]);
checkCopyWithin(0, 5000, 6000, 0, [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ]);
checkCopyWithin(2, -5000, -6000, 0, [ 2, 3, 4, 5, 6, 7, 8 ]);
checkCopyWithin(2, -5000, 6000, 0, [ 2, 3, 4, 5, 6, 7, 8 ]);
checkCopyWithin(2, 5000, 6000, 1, [ 2, 3, 4, 5, 6, 7, 8 ]);
checkCopyWithin(2, -5000, -6000, 1, [ 2, 3, 4, 5, 6, 7, 8 ]);
checkCopyWithin(2, -5000, 3, 1, [ 2, 2, 3, 4, 6, 7, 8 ]);
checkCopyWithin(2, 1, 6000, 0, [ 3, 4, 5, 6, 7, 8, 8 ]);
checkCopyWithin(2, 1, 6000, -4000, [ 3, 4, 5, 6, 7, 8, 8 ]);
testBufferManagement();
print ("done");
reportCompare(0, TestFailCount, "typed array tests");
}