// Copyright 2021 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Flags: --harmony-rab-gsab --allow-natives-syntax "use strict"; d8.file.execute('test/mjsunit/typedarray-helpers.js'); (function DataViewPrototype() { const rab = CreateResizableArrayBuffer(40, 80); const ab = new ArrayBuffer(80); const dvRab = new DataView(rab, 0, 3); const dvAb = new DataView(ab, 0, 3); assertEquals(dvRab.__proto__, dvAb.__proto__); })(); (function DataViewByteLength() { const rab = CreateResizableArrayBuffer(40, 80); const dv = new DataView(rab, 0, 3); assertEquals(rab, dv.buffer); assertEquals(3, dv.byteLength); const emptyDv = new DataView(rab, 0, 0); assertEquals(rab, emptyDv.buffer); assertEquals(0, emptyDv.byteLength); const dvWithOffset = new DataView(rab, 2, 3); assertEquals(rab, dvWithOffset.buffer); assertEquals(3, dvWithOffset.byteLength); const emptyDvWithOffset = new DataView(rab, 2, 0); assertEquals(rab, emptyDvWithOffset.buffer); assertEquals(0, emptyDvWithOffset.byteLength); const lengthTracking = new DataView(rab); assertEquals(rab, lengthTracking.buffer); assertEquals(40, lengthTracking.byteLength); const offset = 8; const lengthTrackingWithOffset = new DataView(rab, offset); assertEquals(rab, lengthTrackingWithOffset.buffer); assertEquals(40 - offset, lengthTrackingWithOffset.byteLength); const emptyLengthTrackingWithOffset = new DataView(rab, 40); assertEquals(rab, emptyLengthTrackingWithOffset.buffer); assertEquals(0, emptyLengthTrackingWithOffset.byteLength); })(); (function ConstructInvalid() { const rab = CreateResizableArrayBuffer(40, 80); // Length too big. assertThrows(() => { new DataView(rab, 0, 41); }, RangeError); // Offset too close to the end. assertThrows(() => { new DataView(rab, 39, 2); }, RangeError); // Offset beyond end. assertThrows(() => { new DataView(rab, 40, 1); }, RangeError); })(); (function ConstructorParameterConversionShrinks() { const rab = CreateResizableArrayBuffer(40, 80); const evil = { valueOf: () => { rab.resize(10); return 0; }}; assertThrows(() => { new DataView(rab, evil, 20);}, RangeError); })(); (function ConstructorParameterConversionGrows() { const gsab = CreateResizableArrayBuffer(40, 80); const evil = { valueOf: () => { gsab.resize(50); return 0; }}; // Constructing will fail unless we take the new size into account. const dv = new DataView(gsab, evil, 50); assertEquals(50, dv.byteLength); })(); (function OrdinaryCreateFromConstructorShrinks() { { const rab = CreateResizableArrayBuffer(16, 40); const newTarget = function() {}.bind(null); Object.defineProperty(newTarget, "prototype", { get() { rab.resize(8); return DataView.prototype; } }); assertThrows(() => {Reflect.construct(DataView, [rab, 0, 16], newTarget); }, RangeError); } { const rab = CreateResizableArrayBuffer(16, 40); const newTarget = function() {}.bind(null); Object.defineProperty(newTarget, "prototype", { get() { rab.resize(6); return DataView.prototype; } }); assertThrows(() => {Reflect.construct(DataView, [rab, 8, 2], newTarget); }, RangeError); } })(); (function DataViewByteLengthWhenResizedOutOfBounds1() { const rab = CreateResizableArrayBuffer(16, 40); const fixedLength = new DataView(rab, 0, 8); const lengthTracking = new DataView(rab); assertEquals(8, fixedLength.byteLength); assertEquals(16, lengthTracking.byteLength); assertEquals(0, fixedLength.byteOffset); assertEquals(0, lengthTracking.byteOffset); rab.resize(2); assertThrows(() => { fixedLength.byteLength; }, TypeError); assertEquals(2, lengthTracking.byteLength); assertThrows(() => { fixedLength.byteOffset; }, TypeError); assertEquals(0, lengthTracking.byteOffset); rab.resize(8); assertEquals(8, fixedLength.byteLength); assertEquals(8, lengthTracking.byteLength); assertEquals(0, fixedLength.byteOffset); assertEquals(0, lengthTracking.byteOffset); rab.resize(40); assertEquals(8, fixedLength.byteLength); assertEquals(40, lengthTracking.byteLength); assertEquals(0, fixedLength.byteOffset); assertEquals(0, lengthTracking.byteOffset); rab.resize(0); assertThrows(() => { fixedLength.byteLength; }, TypeError); assertEquals(0, lengthTracking.byteLength); assertThrows(() => { fixedLength.byteOffset; }, TypeError); assertEquals(0, lengthTracking.byteOffset); })(); // The previous test with offsets. (function DataViewByteLengthWhenResizedOutOfBounds2() { const rab = CreateResizableArrayBuffer(20, 40); const fixedLengthWithOffset = new DataView(rab, 8, 8); const lengthTrackingWithOffset = new DataView(rab, 8); assertEquals(8, fixedLengthWithOffset.byteLength); assertEquals(12, lengthTrackingWithOffset.byteLength); assertEquals(8, fixedLengthWithOffset.byteOffset); assertEquals(8, lengthTrackingWithOffset.byteOffset); rab.resize(10); assertThrows(() => { fixedLengthWithOffset.byteLength }, TypeError); assertEquals(2, lengthTrackingWithOffset.byteLength); assertThrows(() => { fixedLengthWithOffset.byteOffset }, TypeError); assertEquals(8, lengthTrackingWithOffset.byteOffset); rab.resize(16); assertEquals(8, fixedLengthWithOffset.byteLength); assertEquals(8, lengthTrackingWithOffset.byteLength); assertEquals(8, fixedLengthWithOffset.byteOffset); assertEquals(8, lengthTrackingWithOffset.byteOffset); rab.resize(40); assertEquals(8, fixedLengthWithOffset.byteLength); assertEquals(32, lengthTrackingWithOffset.byteLength); assertEquals(8, fixedLengthWithOffset.byteOffset); assertEquals(8, lengthTrackingWithOffset.byteOffset); rab.resize(6); assertThrows(() => { fixedLengthWithOffset.byteLength }, TypeError); assertThrows(() => { lengthTrackingWithOffset.byteLength }, TypeError); assertThrows(() => { fixedLengthWithOffset.byteOffset }, TypeError); assertThrows(() => { lengthTrackingWithOffset.byteOffset }, TypeError); })(); (function GetAndSet() { const rab = CreateResizableArrayBuffer(64, 128); const fixedLength = new DataView(rab, 0, 32); const fixedLengthWithOffset = new DataView(rab, 2, 32); const lengthTracking = new DataView(rab, 0); const lengthTrackingWithOffset = new DataView(rab, 2); testDataViewMethodsUpToSize(fixedLength, 32); assertAllDataViewMethodsThrow(fixedLength, 33, RangeError); testDataViewMethodsUpToSize(fixedLengthWithOffset, 32); assertAllDataViewMethodsThrow(fixedLengthWithOffset, 33, RangeError); testDataViewMethodsUpToSize(lengthTracking, 64); assertAllDataViewMethodsThrow(lengthTracking, 65, RangeError); testDataViewMethodsUpToSize(lengthTrackingWithOffset, 64 - 2); assertAllDataViewMethodsThrow(lengthTrackingWithOffset, 64 - 2 + 1, RangeError); // Shrink so that fixed length TAs go out of bounds. rab.resize(30); assertAllDataViewMethodsThrow(fixedLength, 0, TypeError); assertAllDataViewMethodsThrow(fixedLengthWithOffset, 0, TypeError); testDataViewMethodsUpToSize(lengthTracking, 30); testDataViewMethodsUpToSize(lengthTrackingWithOffset, 30 - 2); // Shrink so that the TAs with offset go out of bounds. rab.resize(1); assertAllDataViewMethodsThrow(fixedLength, 0, TypeError); assertAllDataViewMethodsThrow(fixedLengthWithOffset, 0, TypeError); assertAllDataViewMethodsThrow(lengthTrackingWithOffset, 0, TypeError); testDataViewMethodsUpToSize(lengthTracking, 1); assertAllDataViewMethodsThrow(lengthTracking, 2, RangeError); // Shrink to zero. rab.resize(0); assertAllDataViewMethodsThrow(fixedLength, 0, TypeError); assertAllDataViewMethodsThrow(fixedLengthWithOffset, 0, TypeError); assertAllDataViewMethodsThrow(lengthTrackingWithOffset, 0, TypeError); testDataViewMethodsUpToSize(lengthTracking, 0); assertAllDataViewMethodsThrow(lengthTracking, 1, RangeError); // Grow so that all views are back in-bounds. rab.resize(34); testDataViewMethodsUpToSize(fixedLength, 32); assertAllDataViewMethodsThrow(fixedLength, 33, RangeError); testDataViewMethodsUpToSize(fixedLengthWithOffset, 32); assertAllDataViewMethodsThrow(fixedLengthWithOffset, 33, RangeError); testDataViewMethodsUpToSize(lengthTracking, 34); assertAllDataViewMethodsThrow(lengthTracking, 35, RangeError); testDataViewMethodsUpToSize(lengthTrackingWithOffset, 34 - 2); assertAllDataViewMethodsThrow(lengthTrackingWithOffset, 34 - 2 + 1, RangeError); })(); (function GetParameterConversionShrinks() { { const rab = CreateResizableArrayBuffer(640, 1280); const fixedLength = new DataView(rab, 0, 64); const evil = { valueOf: () => { rab.resize(10); return 0; }}; assertThrows(() => { fixedLength.getUint8(evil); }, TypeError); } { const rab = CreateResizableArrayBuffer(640, 1280); const fixedLengthWithOffset = new DataView(rab, 2, 64); const evil = { valueOf: () => { rab.resize(10); return 0; }}; assertThrows(() => { fixedLengthWithOffset.getUint8(evil); }, TypeError); } { const rab = CreateResizableArrayBuffer(640, 1280); const lengthTracking = new DataView(rab); const evil = { valueOf: () => { rab.resize(10); return 12; }}; // The DataView is not out of bounds but the index is. assertThrows(() => { lengthTracking.getUint8(evil); }, RangeError); assertEquals(0, lengthTracking.getUint8(2)); } { const rab = CreateResizableArrayBuffer(640, 1280); const lengthTrackingWithOffset = new DataView(rab, 2); let evil = { valueOf: () => { rab.resize(10); return 12; }}; // The DataView is not out of bounds but the index is. assertThrows(() => { lengthTrackingWithOffset.getUint8(evil); }, RangeError); evil = { valueOf: () => { rab.resize(0); return 0; }}; // Now the DataView is out of bounds. assertThrows(() => { lengthTrackingWithOffset.getUint8(evil); }, TypeError); } })(); (function SetParameterConversionShrinks() { { const rab = CreateResizableArrayBuffer(640, 1280); const fixedLength = new DataView(rab, 0, 64); const evil = { valueOf: () => { rab.resize(10); return 0; }}; assertThrows(() => { fixedLength.setUint8(evil, 0); }, TypeError); } { const rab = CreateResizableArrayBuffer(640, 1280); const fixedLengthWithOffset = new DataView(rab, 2, 64); const evil = { valueOf: () => { rab.resize(10); return 0; }}; assertThrows(() => { fixedLengthWithOffset.setUint8(evil, 0); }, TypeError); } { const rab = CreateResizableArrayBuffer(640, 1280); const lengthTracking = new DataView(rab); const evil = { valueOf: () => { rab.resize(10); return 12; }}; lengthTracking.setUint8(12, 0); // Does not throw. // The DataView is not out of bounds but the index is. assertThrows(() => { lengthTracking.setUint8(evil, 0); }, RangeError); lengthTracking.setUint8(2, 0); // Does not throw. } { const rab = CreateResizableArrayBuffer(640, 1280); const lengthTrackingWithOffset = new DataView(rab, 2); let evil = { valueOf: () => { rab.resize(10); return 12; }}; lengthTrackingWithOffset.setUint8(12, 0); // Does not throw. // The DataView is not out of bounds but the index is. assertThrows(() => { lengthTrackingWithOffset.setUint8(evil, 0); }, RangeError); evil = { valueOf: () => { rab.resize(0); return 0; }}; // Now the DataView is out of bounds. assertThrows(() => { lengthTrackingWithOffset.setUint8(evil, 0); }, TypeError); } // The same tests as before, except now the "resizing" parameter is the second // one, not the first one. { const rab = CreateResizableArrayBuffer(640, 1280); const fixedLength = new DataView(rab, 0, 64); const evil = { valueOf: () => { rab.resize(10); return 0; }}; assertThrows(() => { fixedLength.setUint8(0, evil); }, TypeError); } { const rab = CreateResizableArrayBuffer(640, 1280); const fixedLengthWithOffset = new DataView(rab, 2, 64); const evil = { valueOf: () => { rab.resize(10); return 0; }}; assertThrows(() => { fixedLengthWithOffset.setUint8(0, evil); }, TypeError); } { const rab = CreateResizableArrayBuffer(640, 1280); const lengthTracking = new DataView(rab); const evil = { valueOf: () => { rab.resize(10); return 0; }}; lengthTracking.setUint8(12, 0); // Does not throw. // The DataView is not out of bounds but the index is. assertThrows(() => { lengthTracking.setUint8(12, evil); }, RangeError); lengthTracking.setUint8(2, 0); // Does not throw. } { const rab = CreateResizableArrayBuffer(640, 1280); const lengthTrackingWithOffset = new DataView(rab, 2); let evil = { valueOf: () => { rab.resize(10); return 0; }}; // The DataView is not out of bounds but the index is. assertThrows(() => { lengthTrackingWithOffset.setUint8(12, evil); }, RangeError); evil = { valueOf: () => { rab.resize(0); return 0; }}; // Now the DataView is out of bounds. assertThrows(() => { lengthTrackingWithOffset.setUint8(0, evil); }, TypeError); } })(); (function DataViewsAndRabGsabDataViews() { // Internally we differentiate between JSDataView and JSRabGsabDataView. Test // that they're indistinguishable externally. const ab = new ArrayBuffer(10); const rab = new ArrayBuffer(10, {maxByteLength: 20}); const dv1 = new DataView(ab); const dv2 = new DataView(rab); assertEquals(DataView.prototype, dv1.__proto__); assertEquals(DataView.prototype, dv2.__proto__); assertEquals(DataView, dv1.constructor); assertEquals(DataView, dv2.constructor); class MyDataView extends DataView { constructor(buffer) { super(buffer); } } const dv3 = new MyDataView(ab); const dv4 = new MyDataView(rab); assertEquals(MyDataView.prototype, dv3.__proto__); assertEquals(MyDataView.prototype, dv4.__proto__); assertEquals(MyDataView, dv3.constructor); assertEquals(MyDataView, dv4.constructor); })();