From 7beea80f003aa8b08a643cc19cb69ba7b9145598 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Sat, 25 Jul 2015 19:16:17 -0400 Subject: [PATCH 1/8] Port Gecko's TypedArray.h API to Rust. --- components/script/dom/bindings/mod.rs | 1 + components/script/dom/bindings/typedarray.rs | 401 +++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 components/script/dom/bindings/typedarray.rs diff --git a/components/script/dom/bindings/mod.rs b/components/script/dom/bindings/mod.rs index 283dc9b08c98..0076bac12760 100644 --- a/components/script/dom/bindings/mod.rs +++ b/components/script/dom/bindings/mod.rs @@ -148,6 +148,7 @@ pub mod num; pub mod str; pub mod structuredclone; pub mod trace; +pub mod typedarray; /// Generated JS-Rust bindings. #[allow(missing_docs, non_snake_case)] diff --git a/components/script/dom/bindings/typedarray.rs b/components/script/dom/bindings/typedarray.rs new file mode 100644 index 000000000000..36ac907dcbf4 --- /dev/null +++ b/components/script/dom/bindings/typedarray.rs @@ -0,0 +1,401 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! High-level, safe bindings for JS typed array APIs. Allows creating new +//! typed arrays or wrapping existing JS reflectors, and prevents reinterpreting +//! existing buffers as different types except in well-defined cases. + +use js::jsapi::{JS_NewUint8Array, JS_NewUint16Array, JS_NewUint32Array, JS_NewInt8Array}; +use js::jsapi::{JS_NewInt16Array, JS_NewInt32Array, JS_NewFloat32Array, JS_NewFloat64Array}; +use js::jsapi::{JS_NewUint8ClampedArray, JS_GetUint8ArrayData, JS_GetUint16ArrayData}; +use js::jsapi::{JS_GetUint32ArrayData, JS_GetInt8ArrayData, JS_GetInt16ArrayData, JSObject}; +use js::jsapi::{JS_GetInt32ArrayData, JS_GetUint8ClampedArrayData, JS_GetFloat32ArrayData}; +use js::jsapi::{JS_GetFloat64ArrayData, JSContext, Type}; +use js::jsapi::{UnwrapInt8Array, UnwrapInt16Array, UnwrapInt32Array, UnwrapUint8ClampedArray}; +use js::jsapi::{UnwrapUint8Array, UnwrapUint16Array, UnwrapUint32Array, UnwrapArrayBufferView}; +use js::jsapi::{UnwrapFloat32Array, UnwrapFloat64Array, GetUint8ArrayLengthAndData}; +use js::jsapi::{JS_NewArrayBuffer, JS_GetArrayBufferData, JS_GetArrayBufferViewType}; +use js::jsapi::{UnwrapArrayBuffer, GetArrayBufferLengthAndData, GetArrayBufferViewLengthAndData}; +use js::glue::{GetUint8ClampedArrayLengthAndData, GetFloat32ArrayLengthAndData}; +use js::glue::{GetUint16ArrayLengthAndData, GetUint32ArrayLengthAndData}; +use js::glue::{GetInt8ArrayLengthAndData, GetFloat64ArrayLengthAndData}; +use js::glue::{GetInt16ArrayLengthAndData, GetInt32ArrayLengthAndData}; + +use core::nonzero::NonZero; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::slice; +use std::ptr; + +struct TypedArrayObjectStorage { + typed_obj: *mut JSObject, + wrapped_obj: *mut JSObject, +} + +impl TypedArrayObjectStorage { + fn new() -> TypedArrayObjectStorage { + TypedArrayObjectStorage { + typed_obj: ptr::null_mut(), + wrapped_obj: ptr::null_mut(), + } + } +} + +/// Internal trait used to associate an element type with an underlying representation +/// and various functions required to manipulate typed arrays of that element type. +pub trait TypedArrayElement { + /// Underlying primitive representation of this element type. + type FundamentalType; + /// Unwrap a typed array JS reflector for this element type. + fn unwrap_array(obj: *mut JSObject) -> *mut JSObject; + /// Retrieve the length and data of a typed array's buffer for this element type. + fn length_and_data(obj: *mut JSObject) -> (u32, *mut Self::FundamentalType); +} + +macro_rules! typed_array_element { + ($t: ty, $fundamental: ty, $unwrap: ident, $length_and_data: ident) => ( + impl TypedArrayElement for $t { + type FundamentalType = $fundamental; + fn unwrap_array(obj: *mut JSObject) -> *mut JSObject { + unsafe { + $unwrap(obj) + } + } + + fn length_and_data(obj: *mut JSObject) -> (u32, *mut Self::FundamentalType) { + unsafe { + let mut len = 0; + let mut data = ptr::null_mut(); + $length_and_data(obj, &mut len, &mut data); + (len, data) + } + } + } + ); + ($t: ty, $unwrap: ident, $length_and_data: ident) => { + typed_array_element!($t, $t, $unwrap, $length_and_data); + }; +} + +#[derive(Clone, Copy)] +/// Wrapped for u8 to allow different utility methods for Uint8ClampedArray objects. +pub struct ClampedU8(pub u8); + +#[derive(Clone, Copy)] +/// Wrapped for u8 to allow different utility methods for ArrayBuffer objects. +pub struct ArrayBufferU8(pub u8); + +#[derive(Clone, Copy)] +/// Wrapped for u8 to allow different utility methods for ArrayBufferView objects. +pub struct ArrayBufferViewU8(pub u8); + +typed_array_element!(u8, UnwrapUint8Array, GetUint8ArrayLengthAndData); +typed_array_element!(u16, UnwrapUint16Array, GetUint16ArrayLengthAndData); +typed_array_element!(u32, UnwrapUint32Array, GetUint32ArrayLengthAndData); +typed_array_element!(i8, UnwrapInt8Array, GetInt8ArrayLengthAndData); +typed_array_element!(i16, UnwrapInt16Array, GetInt16ArrayLengthAndData); +typed_array_element!(i32, UnwrapInt32Array, GetInt32ArrayLengthAndData); +typed_array_element!(f32, UnwrapFloat32Array, GetFloat32ArrayLengthAndData); +typed_array_element!(f64, UnwrapFloat64Array, GetFloat64ArrayLengthAndData); +typed_array_element!(ClampedU8, u8, UnwrapUint8ClampedArray, GetUint8ClampedArrayLengthAndData); +typed_array_element!(ArrayBufferU8, u8, UnwrapArrayBuffer, GetArrayBufferLengthAndData); +typed_array_element!(ArrayBufferViewU8, u8, UnwrapArrayBufferView, GetArrayBufferViewLengthAndData); + +/// The base representation for all typed arrays. +pub struct TypedArrayBase { + /// The JS wrapper storage. + storage: TypedArrayObjectStorage, + /// The underlying memory buffer. + data: *mut T::FundamentalType, + /// The number of elements that make up the buffer. + length: u32, + /// True if the length and data of this typed array have been computed. + /// Any attempt to interact with the underlying buffer must compute it first, + /// to minimize the window of potential GC hazards. + computed: bool, +} + +impl TypedArrayBase { + /// Create an uninitialized typed array representation. + pub fn new() -> TypedArrayBase { + TypedArrayBase { + storage: TypedArrayObjectStorage::new(), + data: ptr::null_mut(), + length: 0, + computed: false, + } + } + + fn inited(&self) -> bool { + !self.storage.typed_obj.is_null() + } + + /// Initialize this typed array representaiton from an existing JS reflector + /// for a typed array. This operation will fail if attempted on a JS object + /// that does not match the expected typed array details. + pub fn init(&mut self, obj: *mut JSObject) -> Result<(), ()> { + assert!(!self.inited()); + self.storage.typed_obj = T::unwrap_array(obj); + self.storage.wrapped_obj = self.storage.typed_obj; + if self.inited() { + Ok(()) + } else { + Err(()) + } + } + + /// Return the underlying buffer as a slice of the element type. + /// Length and data must be computed first. + pub fn as_slice(&self) -> &[T::FundamentalType] { + assert!(self.computed); + unsafe { + slice::from_raw_parts(self.data, self.length as usize) + } + } + + /// Return the underlying buffer as a mutable slice of the element type. + /// Length and data must be computed first. + pub fn as_mut_slice(&mut self) -> &mut [T::FundamentalType] { + assert!(self.computed); + unsafe { + slice::from_raw_parts_mut(self.data, self.length as usize) + } + } + + /// Compute the length and data of this typed array. + /// This operation that must only be performed once, as soon as + /// possible before making use of the resulting buffer. + pub fn compute_length_and_data(&mut self) { + assert!(self.inited()); + assert!(!self.computed); + let (length, data) = T::length_and_data(self.storage.typed_obj); + self.length = length; + self.data = data; + self.computed = true; + } +} + +impl TypedArrayBase { + unsafe fn as_slice_transmute(&self) -> &[T] { + assert!(self.computed); + slice::from_raw_parts(self.data as *mut T, self.length as usize / mem::size_of::()) + } +} + +trait TypedArrayElementCreator: TypedArrayElement { + fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject; + fn get_data(obj: *mut JSObject) -> *mut Self::FundamentalType; +} + +macro_rules! typed_array_element_creator { + ($t: ty, $create_new: ident, $get_data: ident) => ( + impl TypedArrayElementCreator for $t { + fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject { + unsafe { + $create_new(cx, length) + } + } + + fn get_data(obj: *mut JSObject) -> *mut Self::FundamentalType { + unsafe { + $get_data(obj, ptr::null_mut()) + } + } + } + ) +} + +typed_array_element_creator!(u8, JS_NewUint8Array, JS_GetUint8ArrayData); +typed_array_element_creator!(u16, JS_NewUint16Array, JS_GetUint16ArrayData); +typed_array_element_creator!(u32, JS_NewUint32Array, JS_GetUint32ArrayData); +typed_array_element_creator!(i8, JS_NewInt8Array, JS_GetInt8ArrayData); +typed_array_element_creator!(i16, JS_NewInt16Array, JS_GetInt16ArrayData); +typed_array_element_creator!(i32, JS_NewInt32Array, JS_GetInt32ArrayData); +typed_array_element_creator!(f32, JS_NewFloat32Array, JS_GetFloat32ArrayData); +typed_array_element_creator!(f64, JS_NewFloat64Array, JS_GetFloat64ArrayData); +typed_array_element_creator!(ClampedU8, JS_NewUint8ClampedArray, JS_GetUint8ClampedArrayData); +typed_array_element_creator!(ArrayBufferU8, JS_NewArrayBuffer, JS_GetArrayBufferData); + +/// A wrapper that can be used to create a new typed array from scratch, or +/// initialized from an existing JS reflector as necessary. +pub struct TypedArray { + base: TypedArrayBase, +} + +impl Deref for TypedArray { + type Target = TypedArrayBase; + fn deref<'a>(&'a self) -> &'a TypedArrayBase { + &self.base + } +} + +impl DerefMut for TypedArray { + fn deref_mut<'a>(&'a mut self) -> &'a mut TypedArrayBase { + &mut self.base + } +} + +impl TypedArray { + /// Create an uninitialized wrapper around a future typed array. + pub fn new() -> TypedArray { + TypedArray { + base: TypedArrayBase::new(), + } + } + + /// Create a new JS typed array, optionally providing initial data that will + /// be copied into the newly-allocated buffer. Returns the new JS reflector. + pub fn create(cx: *mut JSContext, length: u32, data: Option<&[T::FundamentalType]>) + -> Option> { + let obj = T::create_new(cx, length); + if obj.is_null() { + return None; + } + + if let Some(data) = data { + assert!(data.len() <= length as usize); + let buf = T::get_data(obj); + unsafe { + ptr::copy_nonoverlapping(data.as_ptr(), buf, data.len()); + } + } + + unsafe { + Some(NonZero::new(obj)) + } + } +} + +/// A wrapper around a JS ArrayBufferView object. +pub struct ArrayBufferViewBase { + typed_array: TypedArrayBase, + type_: Option, +} + +trait ArrayBufferViewType { + fn get_view_type(obj: *mut JSObject) -> Type; +} + +macro_rules! array_buffer_view_type { + ($t: ty, $get_view_type: ident) => ( + impl ArrayBufferViewType for $t { + fn get_view_type(obj: *mut JSObject) -> Type { + unsafe { + $get_view_type(obj) + } + } + } + ) +} + +array_buffer_view_type!(ArrayBufferViewU8, JS_GetArrayBufferViewType); + +/// Internal trait used to perform compile-time reflection of specific +/// typed array element types. +pub trait ArrayBufferViewOutput { + /// Fetch the compile-type type representation for this type. + fn get_view_type() -> Type; +} + +macro_rules! array_buffer_view_output { + ($t: ty, $scalar: ident) => ( + impl ArrayBufferViewOutput for $t { + fn get_view_type() -> Type { Type::$scalar } + } + ) +} + +array_buffer_view_output!(u8, Uint8); +array_buffer_view_output!(i8, Int8); +array_buffer_view_output!(u16, Uint16); +array_buffer_view_output!(i16, Int16); +array_buffer_view_output!(u32, Uint32); +array_buffer_view_output!(i32, Int32); +array_buffer_view_output!(f32, Float32); +array_buffer_view_output!(f64, Float64); +array_buffer_view_output!(ClampedU8, Uint8Clamped); + +impl ArrayBufferViewBase { + /// Create an uninitilized ArrayBufferView wrapper. + pub fn new() -> ArrayBufferViewBase { + ArrayBufferViewBase { + typed_array: TypedArrayBase::new(), + type_: None, + } + } + + /// Initialize this wrapper with a JS ArrayBufferView reflector. Fails + /// if the JS reflector is not an ArrayBufferView, or if it is not + /// one of the primitive numeric types (ie. SharedArrayBufferView, SIMD, etc.). + pub fn init(&mut self, obj: *mut JSObject) -> Result<(), ()> { + try!(self.typed_array.init(obj)); + let scalar_type = T::get_view_type(obj); + if (scalar_type as u32) < Type::MaxTypedArrayViewType as u32 { + self.type_ = Some(scalar_type); + Ok(()) + } else { + Err(()) + } + } + + /// Compute the length and data of this typed array. + /// This operation that must only be performed once, as soon as + /// possible before making use of the resulting buffer. + pub fn compute_length_and_data(&mut self) { + self.typed_array.compute_length_and_data(); + } + + /// Return the element type of the wrapped ArrayBufferView. + pub fn element_type(&self) -> Type { + assert!(self.type_.is_some()); + self.type_.unwrap() + } + + /// Return a slice of the bytes of the underlying buffer. + /// Length and data must be computed first. + pub fn as_untyped_slice(&self) -> &[u8] { + unsafe { &*(self.typed_array.as_slice() as *const [T::FundamentalType] as *const [u8]) } + } + + /// Return a mutable slice of the bytes of the underlying buffer. + /// Length and data must be computed first. + pub fn as_mut_untyped_slice(&mut self) -> &mut [u8] { + unsafe { &mut *(self.typed_array.as_mut_slice() as *mut [T::FundamentalType] as *mut [u8]) } + } +} + +impl ArrayBufferViewBase { + /// Return a slice of the underlying buffer reinterpreted as a different element type. + /// Length and data must be computed first. + pub fn as_slice(&mut self) -> Result<&[U], ()> { + assert!(self.type_.is_some()); + if U::get_view_type() as u32 == self.type_.unwrap() as u32 { + unsafe { + Ok(self.typed_array.as_slice_transmute()) + } + } else { + Err(()) + } + } +} + +#[allow(missing_docs)] +mod typedefs { + use super::{TypedArray, ArrayBufferViewBase, ClampedU8, ArrayBufferViewU8, ArrayBufferU8}; + pub type Uint8ClampedArray = TypedArray; + pub type Uint8Array = TypedArray; + pub type Int8Array = TypedArray; + pub type Uint16Array = TypedArray; + pub type Int16Array = TypedArray; + pub type Uint32Array = TypedArray; + pub type Int32Array = TypedArray; + pub type Float32Array = TypedArray; + pub type Float64Array = TypedArray; + pub type ArrayBufferView = ArrayBufferViewBase; + pub type ArrayBuffer = TypedArray; +} + +pub use self::typedefs::{Uint8ClampedArray, Uint8Array, Uint16Array, Uint32Array}; +pub use self::typedefs::{Int8Array, Int16Array, Int32Array, Float64Array, Float32Array}; +pub use self::typedefs::{ArrayBuffer, ArrayBufferView}; From 1cff7a56ec1c48804a114006c31ee949aa85482d Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Sat, 25 Jul 2015 20:49:22 -0400 Subject: [PATCH 2/8] Convert ImageData to use safe typed array API. --- .../script/dom/canvasrenderingcontext2d.rs | 6 +-- components/script/dom/imagedata.rs | 38 +++++++------------ 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs index 2e100a929603..bda308233382 100644 --- a/components/script/dom/canvasrenderingcontext2d.rs +++ b/components/script/dom/canvasrenderingcontext2d.rs @@ -963,12 +963,12 @@ impl<'a> CanvasRenderingContext2DMethods for &'a CanvasRenderingContext2D { return Err(IndexSize) } - Ok(ImageData::new(self.global.root().r(), sw.abs().to_u32().unwrap(), sh.abs().to_u32().unwrap(), None)) + ImageData::new(self.global.root().r(), sw.abs().to_u32().unwrap(), sh.abs().to_u32().unwrap(), None) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-createimagedata fn CreateImageData_(self, imagedata: &ImageData) -> Fallible> { - Ok(ImageData::new(self.global.root().r(), imagedata.Width(), imagedata.Height(), None)) + ImageData::new(self.global.root().r(), imagedata.Width(), imagedata.Height(), None) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-getimagedata @@ -995,7 +995,7 @@ impl<'a> CanvasRenderingContext2DMethods for &'a CanvasRenderingContext2D { .send(CanvasMsg::Canvas2d(Canvas2dMsg::GetImageData(dest_rect, canvas_size, sender))) .unwrap(); let data = receiver.recv().unwrap(); - Ok(ImageData::new(self.global.root().r(), sw.abs().to_u32().unwrap(), sh.abs().to_u32().unwrap(), Some(data))) + ImageData::new(self.global.root().r(), sw.abs().to_u32().unwrap(), sh.abs().to_u32().unwrap(), Some(&data)) } // https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata diff --git a/components/script/dom/imagedata.rs b/components/script/dom/imagedata.rs index 513ef7916891..1d592b4841cb 100644 --- a/components/script/dom/imagedata.rs +++ b/components/script/dom/imagedata.rs @@ -4,16 +4,14 @@ use dom::bindings::codegen::Bindings::ImageDataBinding; use dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods; +use dom::bindings::error::{Error, Fallible}; use dom::bindings::global::GlobalRef; use dom::bindings::js::Root; +use dom::bindings::typedarray::Uint8ClampedArray; use dom::bindings::utils::{Reflector, reflect_dom_object}; use euclid::size::Size2D; use js::jsapi::{JSContext, JSObject, Heap}; -use js::jsapi::{JS_NewUint8ClampedArray, JS_GetUint8ClampedArrayData}; -use libc::uint8_t; use std::vec::Vec; -use std::slice; -use std::ptr; use std::default::Default; #[dom_struct] @@ -26,8 +24,8 @@ pub struct ImageData { } impl ImageData { - #[allow(unsafe_code)] - pub fn new(global: GlobalRef, width: u32, height: u32, data: Option>) -> Root { + pub fn new(global: GlobalRef, width: u32, height: u32, data: Option<&[u8]>) + -> Fallible> { let mut imagedata = box ImageData { reflector_: Reflector::new(), width: width, @@ -35,19 +33,12 @@ impl ImageData { data: Heap::default(), }; - unsafe { - let cx = global.get_cx(); - let js_object: *mut JSObject = JS_NewUint8ClampedArray(cx, width * height * 4); - - if let Some(vec) = data { - let js_object_data: *mut uint8_t = JS_GetUint8ClampedArrayData(js_object, ptr::null()); - ptr::copy_nonoverlapping(vec.as_ptr(), js_object_data, vec.len()) - } - (*imagedata).data.set(js_object); + match Uint8ClampedArray::create(global.get_cx(), width * height * 4, data) { + Some(js_object) => imagedata.data.set(*js_object), + None => return Err(Error::JSFailed) } - reflect_dom_object(imagedata, - global, ImageDataBinding::Wrap) + Ok(reflect_dom_object(imagedata, global, ImageDataBinding::Wrap)) } } @@ -57,14 +48,13 @@ pub trait ImageDataHelpers { } impl<'a> ImageDataHelpers for &'a ImageData { - #[allow(unsafe_code)] - fn get_data_array(self, global: &GlobalRef) -> Vec { - unsafe { - let cx = global.get_cx(); - let data: *const uint8_t = JS_GetUint8ClampedArrayData(self.Data(cx), ptr::null()) as *const uint8_t; - let len = self.Width() * self.Height() * 4; - slice::from_raw_parts(data, len as usize).to_vec() + fn get_data_array(self, _global: &GlobalRef) -> Vec { + let mut typed_array = Uint8ClampedArray::new(); + if typed_array.init(self.data.get()).is_err() { + return vec!(); } + typed_array.compute_length_and_data(); + typed_array.as_slice().to_vec() } fn get_size(self) -> Size2D { From b4633ed64be7e4599a606108ffb725824cc98537 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Sun, 26 Jul 2015 01:27:41 -0400 Subject: [PATCH 3/8] Replace unsafe code in WebGLRenderingContext with new typed array API. Fixes #6602. Fixes #6601. --- components/canvas_traits/lib.rs | 2 +- .../script/dom/webglrenderingcontext.rs | 37 +++++++++---------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/components/canvas_traits/lib.rs b/components/canvas_traits/lib.rs index 48cabed045ca..6c9e3f786029 100644 --- a/components/canvas_traits/lib.rs +++ b/components/canvas_traits/lib.rs @@ -123,7 +123,7 @@ pub enum CanvasWebGLMsg { BlendFunc(u32, u32), BlendFuncSeparate(u32, u32, u32, u32), AttachShader(u32, u32), - BufferData(u32, Vec, u32), + BufferData(u32, Vec, u32), //TODO: add variants for all possible buffer types Clear(u32), ClearColor(f32, f32, f32, f32), CompileShader(u32), diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 750edfb8329b..8f974e656bdc 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -13,6 +13,7 @@ use dom::bindings::global::{GlobalRef, GlobalField}; use dom::bindings::js::{JS, LayoutJS, Root}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::bindings::conversions::ToJSValConvertible; +use dom::bindings::typedarray::{ArrayBufferView, Float32Array}; use dom::htmlcanvaselement::{HTMLCanvasElement}; use dom::webglbuffer::{WebGLBuffer, WebGLBufferHelpers}; use dom::webglframebuffer::{WebGLFramebuffer, WebGLFramebufferHelpers}; @@ -24,13 +25,9 @@ use dom::webgluniformlocation::{WebGLUniformLocation, WebGLUniformLocationHelper use euclid::size::Size2D; use ipc_channel::ipc::{self, IpcSender}; use js::jsapi::{JSContext, JSObject, RootedValue}; -use js::jsapi::{JS_GetFloat32ArrayData, JS_GetObjectAsArrayBufferView}; use js::jsval::{JSVal, UndefinedValue, NullValue, Int32Value, BooleanValue}; use msg::constellation_msg::Msg as ConstellationMsg; use std::cell::Cell; -use std::mem; -use std::ptr; -use std::slice; use std::sync::mpsc::channel; use util::str::DOMString; use offscreen_gl_context::GLContextAttributes; @@ -279,23 +276,21 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { } } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5 fn BufferData(self, _cx: *mut JSContext, target: u32, data: Option<*mut JSObject>, usage: u32) { let data = match data { Some(data) => data, None => return, }; - let data_vec = unsafe { - let mut length = 0; - let mut ptr = ptr::null_mut(); - let buffer_data = JS_GetObjectAsArrayBufferView(data, &mut length, &mut ptr); - if buffer_data.is_null() { - panic!("Argument data to WebGLRenderingContext.bufferdata is not a Float32Array") - } - let data_f32 = JS_GetFloat32ArrayData(buffer_data, ptr::null()); - let data_vec_length = length / mem::size_of::() as u32; - slice::from_raw_parts(data_f32, data_vec_length as usize).to_vec() + + let mut array_buffer_view = ArrayBufferView::new(); + if array_buffer_view.init(data).is_err() { + return; + } + array_buffer_view.compute_length_and_data(); + let data_vec = match array_buffer_view.as_slice() { + Ok(data) => data.to_vec(), + Err(_) => return, }; self.ipc_renderer .send(CanvasMsg::WebGL(CanvasWebGLMsg::BufferData(target, data_vec, usage))) @@ -477,7 +472,6 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { } } - #[allow(unsafe_code)] // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10 fn Uniform4fv(self, _cx: *mut JSContext, @@ -493,10 +487,13 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { None => return, }; - let data_vec = unsafe { - let data_f32 = JS_GetFloat32ArrayData(data, ptr::null()); - slice::from_raw_parts(data_f32, 4).to_vec() - }; + let mut typed_array = Float32Array::new(); + if typed_array.init(data).is_err() { + return; + } + typed_array.compute_length_and_data(); + let data_vec = typed_array.as_slice().iter().cloned().take(4).collect(); + self.ipc_renderer .send(CanvasMsg::WebGL(CanvasWebGLMsg::Uniform4fv(uniform_id, data_vec))) .unwrap() From bdb09d17360a38ba12366190ef15c710374a259f Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Sun, 26 Jul 2015 09:03:15 -0400 Subject: [PATCH 4/8] Replace unsafe JSAPI in Crypto with the new typed array APIs. --- components/script/dom/crypto.rs | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/components/script/dom/crypto.rs b/components/script/dom/crypto.rs index ef7bc62c9d93..8fcb68a75a63 100644 --- a/components/script/dom/crypto.rs +++ b/components/script/dom/crypto.rs @@ -7,14 +7,11 @@ use dom::bindings::codegen::Bindings::CryptoBinding::CryptoMethods; use dom::bindings::error::{Error, Fallible}; use dom::bindings::global::GlobalRef; use dom::bindings::js::Root; +use dom::bindings::typedarray::{ArrayBufferView}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::bindings::cell::DOMRefCell; -use js::jsapi::{JSContext, JSObject}; -use js::jsapi::{JS_GetObjectAsArrayBufferView, JS_GetArrayBufferViewType, Type}; - -use std::ptr; -use std::slice; +use js::jsapi::{JSContext, JSObject, Type}; use rand::{Rng, OsRng}; @@ -41,35 +38,33 @@ impl Crypto { } impl<'a> CryptoMethods for &'a Crypto { - #[allow(unsafe_code)] // https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#Crypto-method-getRandomValues fn GetRandomValues(self, _cx: *mut JSContext, input: *mut JSObject) -> Fallible<*mut JSObject> { - let mut length = 0; - let mut data = ptr::null_mut(); - if unsafe { JS_GetObjectAsArrayBufferView(input, &mut length, &mut data).is_null() } { + let mut array_buffer_view = ArrayBufferView::new(); + if array_buffer_view.init(input).is_err() { return Err(Error::Type("Argument to Crypto.getRandomValues is not an ArrayBufferView".to_owned())); } - if !is_integer_buffer(input) { + + if !is_integer_buffer(&array_buffer_view) { return Err(Error::TypeMismatch); } - if length > 65536 { + + array_buffer_view.compute_length_and_data(); + let mut buffer = array_buffer_view.as_mut_untyped_slice(); + + if buffer.len() > 65536 { return Err(Error::QuotaExceeded); } - let mut buffer = unsafe { - slice::from_raw_parts_mut(data, length as usize) - }; - - self.rng.borrow_mut().fill_bytes(&mut buffer); + self.rng.borrow_mut().fill_bytes(buffer); Ok(input) } } -#[allow(unsafe_code)] -fn is_integer_buffer(input: *mut JSObject) -> bool { - match unsafe { JS_GetArrayBufferViewType(input) } { +fn is_integer_buffer(input: &ArrayBufferView) -> bool { + match input.element_type() { Type::Uint8 | Type::Uint8Clamped | Type::Int8 | From 504de811d9d442105b034498067e67976c24a418 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Sun, 26 Jul 2015 09:04:09 -0400 Subject: [PATCH 5/8] Replace unsafe JSAPI in TextDecoder with the new typed array APIs. --- components/script/dom/textdecoder.rs | 45 ++++++++++++++++------------ 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/components/script/dom/textdecoder.rs b/components/script/dom/textdecoder.rs index 4b8756afba7c..3f429143efc4 100644 --- a/components/script/dom/textdecoder.rs +++ b/components/script/dom/textdecoder.rs @@ -9,6 +9,7 @@ use dom::bindings::global::GlobalRef; use dom::bindings::js::Root; use dom::bindings::str::USVString; use dom::bindings::trace::JSTraceable; +use dom::bindings::typedarray::{ArrayBufferView, ArrayBuffer}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use util::str::DOMString; @@ -17,11 +18,8 @@ use encoding::Encoding; use encoding::types::{EncodingRef, DecoderTrap}; use encoding::label::encoding_from_whatwg_label; use js::jsapi::{JSContext, JSObject}; -use js::jsapi::JS_GetObjectAsArrayBufferView; use std::borrow::ToOwned; -use std::ptr; -use std::slice; #[dom_struct] pub struct TextDecoder { @@ -92,23 +90,32 @@ impl<'a> TextDecoderMethods for &'a TextDecoder { None => return Ok(USVString("".to_owned())), }; - let mut length = 0; - let mut data = ptr::null_mut(); - if unsafe { JS_GetObjectAsArrayBufferView(input, &mut length, &mut data).is_null() } { - return Err(Error::Type("Argument to TextDecoder.decode is not an ArrayBufferView".to_owned())); + let mut array_buffer_view = ArrayBufferView::new(); + if array_buffer_view.init(input).is_err() { + let mut array_buffer = ArrayBuffer::new(); + if array_buffer.init(input).is_err() { + return Err(Error::Type("Argument to TextDecoder.decode is not an ArrayBufferView \ + or ArrayBuffer".to_owned())); + } + array_buffer.compute_length_and_data(); + let buffer = array_buffer.as_slice(); + return decode_from_slice(buffer, self.fatal, &self.encoding); } + array_buffer_view.compute_length_and_data(); + let buffer = array_buffer_view.as_untyped_slice(); + return decode_from_slice(buffer, self.fatal, &self.encoding); + } +} - let buffer = unsafe { - slice::from_raw_parts(data as *const _, length as usize) - }; - let trap = if self.fatal { - DecoderTrap::Strict - } else { - DecoderTrap::Replace - }; - match self.encoding.decode(buffer, trap) { - Ok(s) => Ok(USVString(s)), - Err(_) => Err(Error::Type("Decoding failed".to_owned())), - } +fn decode_from_slice(buffer: &[u8], fatal: bool, encoding: &EncodingRef) -> Result { + let trap = if fatal { + DecoderTrap::Strict + } else { + DecoderTrap::Replace + }; + match encoding.decode(buffer, trap) { + Ok(s) => Ok(USVString(s)), + Err(_) => Err(Error::Type("Decoding failed".to_owned())), } } + From 53ecd529890cfb8bc398dcc7a3744b427f58e2f6 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Sun, 26 Jul 2015 11:40:54 -0400 Subject: [PATCH 6/8] Make the typed array wrappers GC-safe by introducing stack rooting. --- components/script/dom/bindings/trace.rs | 6 +- components/script/dom/bindings/typedarray.rs | 124 +++++++++++++----- components/script/dom/crypto.rs | 5 +- components/script/dom/imagedata.rs | 5 +- components/script/dom/textdecoder.rs | 8 +- .../script/dom/webglrenderingcontext.rs | 8 +- 6 files changed, 112 insertions(+), 44 deletions(-) diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 741921af652f..57d0283acf1d 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -391,7 +391,8 @@ impl RootedTraceableSet { } } - fn remove(traceable: &T) { + /// Remove a previously-registered JSTraceable pointer from the set. + pub fn remove(traceable: &T) { ROOTED_TRACEABLES.with(|ref traceables| { let mut traceables = traceables.borrow_mut(); let idx = @@ -404,7 +405,8 @@ impl RootedTraceableSet { }); } - fn add(traceable: &T) { + /// Remove a JSTraceable pointer to the set of known traceable objects. + pub fn add(traceable: &T) { ROOTED_TRACEABLES.with(|ref traceables| { fn trace(obj: *const libc::c_void, tracer: *mut JSTracer) { let obj: &T = unsafe { &*(obj as *const T) }; diff --git a/components/script/dom/bindings/typedarray.rs b/components/script/dom/bindings/typedarray.rs index 36ac907dcbf4..84b2cbcb575b 100644 --- a/components/script/dom/bindings/typedarray.rs +++ b/components/script/dom/bindings/typedarray.rs @@ -6,12 +6,14 @@ //! typed arrays or wrapping existing JS reflectors, and prevents reinterpreting //! existing buffers as different types except in well-defined cases. +use dom::bindings::trace::{JSTraceable, RootedTraceableSet}; + use js::jsapi::{JS_NewUint8Array, JS_NewUint16Array, JS_NewUint32Array, JS_NewInt8Array}; use js::jsapi::{JS_NewInt16Array, JS_NewInt32Array, JS_NewFloat32Array, JS_NewFloat64Array}; use js::jsapi::{JS_NewUint8ClampedArray, JS_GetUint8ArrayData, JS_GetUint16ArrayData}; use js::jsapi::{JS_GetUint32ArrayData, JS_GetInt8ArrayData, JS_GetInt16ArrayData, JSObject}; use js::jsapi::{JS_GetInt32ArrayData, JS_GetUint8ClampedArrayData, JS_GetFloat32ArrayData}; -use js::jsapi::{JS_GetFloat64ArrayData, JSContext, Type}; +use js::jsapi::{JS_GetFloat64ArrayData, JSContext, Type, JSTracer, JS_CallUnbarrieredObjectTracer}; use js::jsapi::{UnwrapInt8Array, UnwrapInt16Array, UnwrapInt32Array, UnwrapUint8ClampedArray}; use js::jsapi::{UnwrapUint8Array, UnwrapUint16Array, UnwrapUint32Array, UnwrapArrayBufferView}; use js::jsapi::{UnwrapFloat32Array, UnwrapFloat64Array, GetUint8ArrayLengthAndData}; @@ -28,6 +30,8 @@ use std::ops::{Deref, DerefMut}; use std::slice; use std::ptr; +#[derive(Debug)] +#[allow(raw_pointer_derive)] struct TypedArrayObjectStorage { typed_obj: *mut JSObject, wrapped_obj: *mut JSObject, @@ -103,7 +107,7 @@ typed_array_element!(ArrayBufferU8, u8, UnwrapArrayBuffer, GetArrayBufferLengthA typed_array_element!(ArrayBufferViewU8, u8, UnwrapArrayBufferView, GetArrayBufferViewLengthAndData); /// The base representation for all typed arrays. -pub struct TypedArrayBase { +pub struct TypedArrayBase<'a, T: 'a + TypedArrayElement> { /// The JS wrapper storage. storage: TypedArrayObjectStorage, /// The underlying memory buffer. @@ -114,16 +118,18 @@ pub struct TypedArrayBase { /// Any attempt to interact with the underlying buffer must compute it first, /// to minimize the window of potential GC hazards. computed: bool, + rooter: &'a mut TypedArrayRooter, } -impl TypedArrayBase { +impl<'a, T: TypedArrayElement> TypedArrayBase<'a, T> { /// Create an uninitialized typed array representation. - pub fn new() -> TypedArrayBase { + pub fn new(rooter: &'a mut TypedArrayRooter) -> TypedArrayBase<'a, T> { TypedArrayBase { storage: TypedArrayObjectStorage::new(), data: ptr::null_mut(), length: 0, computed: false, + rooter: rooter, } } @@ -136,6 +142,7 @@ impl TypedArrayBase { /// that does not match the expected typed array details. pub fn init(&mut self, obj: *mut JSObject) -> Result<(), ()> { assert!(!self.inited()); + self.rooter.init(&mut self.storage); self.storage.typed_obj = T::unwrap_array(obj); self.storage.wrapped_obj = self.storage.typed_obj; if self.inited() { @@ -176,7 +183,16 @@ impl TypedArrayBase { } } -impl TypedArrayBase { +impl<'a, T: TypedArrayElement> Drop for TypedArrayBase<'a, T> { + fn drop(&mut self) { + // Ensure this typed array wrapper wasn't moved during its lifetime. + let storage = &mut self.storage as *mut _; + let original = self.rooter.typed_array; + assert_eq!(storage, original); + } +} + +impl<'a> TypedArrayBase<'a, ArrayBufferViewU8> { unsafe fn as_slice_transmute(&self) -> &[T] { assert!(self.computed); slice::from_raw_parts(self.data as *mut T, self.length as usize / mem::size_of::()) @@ -219,28 +235,28 @@ typed_array_element_creator!(ArrayBufferU8, JS_NewArrayBuffer, JS_GetArrayBuffer /// A wrapper that can be used to create a new typed array from scratch, or /// initialized from an existing JS reflector as necessary. -pub struct TypedArray { - base: TypedArrayBase, +pub struct TypedArray<'a, T: 'a + TypedArrayElement> { + base: TypedArrayBase<'a, T>, } -impl Deref for TypedArray { - type Target = TypedArrayBase; - fn deref<'a>(&'a self) -> &'a TypedArrayBase { +impl<'a, T: TypedArrayElement> Deref for TypedArray<'a, T> { + type Target = TypedArrayBase<'a, T>; + fn deref<'b>(&'b self) -> &'b TypedArrayBase<'a, T> { &self.base } } -impl DerefMut for TypedArray { - fn deref_mut<'a>(&'a mut self) -> &'a mut TypedArrayBase { +impl<'a, T: TypedArrayElement> DerefMut for TypedArray<'a, T> { + fn deref_mut<'b>(&'b mut self) -> &'b mut TypedArrayBase<'a, T> { &mut self.base } } -impl TypedArray { +impl<'a, T: TypedArrayElementCreator + TypedArrayElement> TypedArray<'a, T> { /// Create an uninitialized wrapper around a future typed array. - pub fn new() -> TypedArray { + pub fn new(rooter: &'a mut TypedArrayRooter) -> TypedArray<'a, T> { TypedArray { - base: TypedArrayBase::new(), + base: TypedArrayBase::new(rooter), } } @@ -268,8 +284,8 @@ impl TypedArray { } /// A wrapper around a JS ArrayBufferView object. -pub struct ArrayBufferViewBase { - typed_array: TypedArrayBase, +pub struct ArrayBufferViewBase<'a, T: 'a + TypedArrayElement> { + typed_array: TypedArrayBase<'a, T>, type_: Option, } @@ -316,11 +332,11 @@ array_buffer_view_output!(f32, Float32); array_buffer_view_output!(f64, Float64); array_buffer_view_output!(ClampedU8, Uint8Clamped); -impl ArrayBufferViewBase { +impl<'a, T: TypedArrayElement + ArrayBufferViewType> ArrayBufferViewBase<'a, T> { /// Create an uninitilized ArrayBufferView wrapper. - pub fn new() -> ArrayBufferViewBase { + pub fn new(rooter: &'a mut TypedArrayRooter) -> ArrayBufferViewBase<'a, T> { ArrayBufferViewBase { - typed_array: TypedArrayBase::new(), + typed_array: TypedArrayBase::new(rooter), type_: None, } } @@ -365,7 +381,7 @@ impl ArrayBufferViewBase { } } -impl ArrayBufferViewBase { +impl<'a> ArrayBufferViewBase<'a, ArrayBufferViewU8> { /// Return a slice of the underlying buffer reinterpreted as a different element type. /// Length and data must be computed first. pub fn as_slice(&mut self) -> Result<&[U], ()> { @@ -383,19 +399,63 @@ impl ArrayBufferViewBase { #[allow(missing_docs)] mod typedefs { use super::{TypedArray, ArrayBufferViewBase, ClampedU8, ArrayBufferViewU8, ArrayBufferU8}; - pub type Uint8ClampedArray = TypedArray; - pub type Uint8Array = TypedArray; - pub type Int8Array = TypedArray; - pub type Uint16Array = TypedArray; - pub type Int16Array = TypedArray; - pub type Uint32Array = TypedArray; - pub type Int32Array = TypedArray; - pub type Float32Array = TypedArray; - pub type Float64Array = TypedArray; - pub type ArrayBufferView = ArrayBufferViewBase; - pub type ArrayBuffer = TypedArray; + pub type Uint8ClampedArray<'a> = TypedArray<'a, ClampedU8>; + pub type Uint8Array<'a> = TypedArray<'a, u8>; + pub type Int8Array<'a> = TypedArray<'a, i8>; + pub type Uint16Array<'a> = TypedArray<'a, u16>; + pub type Int16Array<'a> = TypedArray<'a, i16>; + pub type Uint32Array<'a> = TypedArray<'a, u32>; + pub type Int32Array<'a> = TypedArray<'a, i32>; + pub type Float32Array<'a> = TypedArray<'a, f32>; + pub type Float64Array<'a> = TypedArray<'a, f64>; + pub type ArrayBufferView<'a> = ArrayBufferViewBase<'a, ArrayBufferViewU8>; + pub type ArrayBuffer<'a> = TypedArray<'a, ArrayBufferU8>; } pub use self::typedefs::{Uint8ClampedArray, Uint8Array, Uint16Array, Uint32Array}; pub use self::typedefs::{Int8Array, Int16Array, Int32Array, Float64Array, Float32Array}; pub use self::typedefs::{ArrayBuffer, ArrayBufferView}; + +/// A GC root for a typed array wrapper. +pub struct TypedArrayRooter { + typed_array: *mut TypedArrayObjectStorage, +} + +impl TypedArrayRooter { + /// Create a new GC root for a forthcoming typed array wrapper. + pub fn new() -> TypedArrayRooter { + TypedArrayRooter { + typed_array: ptr::null_mut(), + } + } + + fn init(&mut self, array: &mut TypedArrayObjectStorage) { + assert!(self.typed_array.is_null()); + self.typed_array = array; + RootedTraceableSet::add(self); + } +} + +impl JSTraceable for TypedArrayRooter { + fn trace(&self, trc: *mut JSTracer) { + unsafe { + let storage = &mut (*self.typed_array); + if !storage.wrapped_obj.is_null() { + JS_CallUnbarrieredObjectTracer(trc, + &mut storage.wrapped_obj, + b"TypedArray.wrapped_obj\0".as_ptr() as *const _); + } + if !storage.typed_obj.is_null() { + JS_CallUnbarrieredObjectTracer(trc, + &mut storage.typed_obj, + b"TypedArray.typed_obj\0".as_ptr() as *const _); + } + } + } +} + +impl Drop for TypedArrayRooter { + fn drop(&mut self) { + RootedTraceableSet::remove(self); + } +} diff --git a/components/script/dom/crypto.rs b/components/script/dom/crypto.rs index 8fcb68a75a63..8f075b91cc39 100644 --- a/components/script/dom/crypto.rs +++ b/components/script/dom/crypto.rs @@ -7,7 +7,7 @@ use dom::bindings::codegen::Bindings::CryptoBinding::CryptoMethods; use dom::bindings::error::{Error, Fallible}; use dom::bindings::global::GlobalRef; use dom::bindings::js::Root; -use dom::bindings::typedarray::{ArrayBufferView}; +use dom::bindings::typedarray::{ArrayBufferView, TypedArrayRooter}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::bindings::cell::DOMRefCell; @@ -41,7 +41,8 @@ impl<'a> CryptoMethods for &'a Crypto { // https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#Crypto-method-getRandomValues fn GetRandomValues(self, _cx: *mut JSContext, input: *mut JSObject) -> Fallible<*mut JSObject> { - let mut array_buffer_view = ArrayBufferView::new(); + let mut rooter = TypedArrayRooter::new(); + let mut array_buffer_view = ArrayBufferView::new(&mut rooter); if array_buffer_view.init(input).is_err() { return Err(Error::Type("Argument to Crypto.getRandomValues is not an ArrayBufferView".to_owned())); } diff --git a/components/script/dom/imagedata.rs b/components/script/dom/imagedata.rs index 1d592b4841cb..6489492ee2e5 100644 --- a/components/script/dom/imagedata.rs +++ b/components/script/dom/imagedata.rs @@ -7,7 +7,7 @@ use dom::bindings::codegen::Bindings::ImageDataBinding::ImageDataMethods; use dom::bindings::error::{Error, Fallible}; use dom::bindings::global::GlobalRef; use dom::bindings::js::Root; -use dom::bindings::typedarray::Uint8ClampedArray; +use dom::bindings::typedarray::{Uint8ClampedArray, TypedArrayRooter}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use euclid::size::Size2D; use js::jsapi::{JSContext, JSObject, Heap}; @@ -49,7 +49,8 @@ pub trait ImageDataHelpers { impl<'a> ImageDataHelpers for &'a ImageData { fn get_data_array(self, _global: &GlobalRef) -> Vec { - let mut typed_array = Uint8ClampedArray::new(); + let mut rooter = TypedArrayRooter::new(); + let mut typed_array = Uint8ClampedArray::new(&mut rooter); if typed_array.init(self.data.get()).is_err() { return vec!(); } diff --git a/components/script/dom/textdecoder.rs b/components/script/dom/textdecoder.rs index 3f429143efc4..691efff16f70 100644 --- a/components/script/dom/textdecoder.rs +++ b/components/script/dom/textdecoder.rs @@ -9,7 +9,7 @@ use dom::bindings::global::GlobalRef; use dom::bindings::js::Root; use dom::bindings::str::USVString; use dom::bindings::trace::JSTraceable; -use dom::bindings::typedarray::{ArrayBufferView, ArrayBuffer}; +use dom::bindings::typedarray::{ArrayBufferView, ArrayBuffer, TypedArrayRooter}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use util::str::DOMString; @@ -90,9 +90,11 @@ impl<'a> TextDecoderMethods for &'a TextDecoder { None => return Ok(USVString("".to_owned())), }; - let mut array_buffer_view = ArrayBufferView::new(); + let mut rooter = TypedArrayRooter::new(); + let mut array_buffer_view = ArrayBufferView::new(&mut rooter); if array_buffer_view.init(input).is_err() { - let mut array_buffer = ArrayBuffer::new(); + let mut rooter = TypedArrayRooter::new(); + let mut array_buffer = ArrayBuffer::new(&mut rooter); if array_buffer.init(input).is_err() { return Err(Error::Type("Argument to TextDecoder.decode is not an ArrayBufferView \ or ArrayBuffer".to_owned())); diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 8f974e656bdc..db22a80bcf40 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -13,7 +13,7 @@ use dom::bindings::global::{GlobalRef, GlobalField}; use dom::bindings::js::{JS, LayoutJS, Root}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use dom::bindings::conversions::ToJSValConvertible; -use dom::bindings::typedarray::{ArrayBufferView, Float32Array}; +use dom::bindings::typedarray::{ArrayBufferView, Float32Array, TypedArrayRooter}; use dom::htmlcanvaselement::{HTMLCanvasElement}; use dom::webglbuffer::{WebGLBuffer, WebGLBufferHelpers}; use dom::webglframebuffer::{WebGLFramebuffer, WebGLFramebufferHelpers}; @@ -283,7 +283,8 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { None => return, }; - let mut array_buffer_view = ArrayBufferView::new(); + let mut rooter = TypedArrayRooter::new(); + let mut array_buffer_view = ArrayBufferView::new(&mut rooter); if array_buffer_view.init(data).is_err() { return; } @@ -487,7 +488,8 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { None => return, }; - let mut typed_array = Float32Array::new(); + let mut rooter = TypedArrayRooter::new(); + let mut typed_array = Float32Array::new(&mut rooter); if typed_array.init(data).is_err() { return; } From 82ee22cc980e5591f3119de09abe2fb056e4f00e Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Mon, 27 Jul 2015 16:42:21 -0400 Subject: [PATCH 7/8] Address review comments. --- components/canvas_traits/lib.rs | 2 +- components/script/dom/bindings/typedarray.rs | 162 +++++++++--------- components/script/dom/crypto.rs | 17 +- components/script/dom/imagedata.rs | 25 ++- components/script/dom/textdecoder.rs | 29 ++-- .../script/dom/webglrenderingcontext.rs | 18 +- 6 files changed, 140 insertions(+), 113 deletions(-) diff --git a/components/canvas_traits/lib.rs b/components/canvas_traits/lib.rs index 6c9e3f786029..050fe4794f1b 100644 --- a/components/canvas_traits/lib.rs +++ b/components/canvas_traits/lib.rs @@ -123,7 +123,7 @@ pub enum CanvasWebGLMsg { BlendFunc(u32, u32), BlendFuncSeparate(u32, u32, u32, u32), AttachShader(u32, u32), - BufferData(u32, Vec, u32), //TODO: add variants for all possible buffer types + BufferData(u32, Vec, u32), //TODO: allow non-f32 values (#6791) Clear(u32), ClearColor(f32, f32, f32, f32), CompileShader(u32), diff --git a/components/script/dom/bindings/typedarray.rs b/components/script/dom/bindings/typedarray.rs index 84b2cbcb575b..463815292d1b 100644 --- a/components/script/dom/bindings/typedarray.rs +++ b/components/script/dom/bindings/typedarray.rs @@ -19,12 +19,12 @@ use js::jsapi::{UnwrapUint8Array, UnwrapUint16Array, UnwrapUint32Array, UnwrapAr use js::jsapi::{UnwrapFloat32Array, UnwrapFloat64Array, GetUint8ArrayLengthAndData}; use js::jsapi::{JS_NewArrayBuffer, JS_GetArrayBufferData, JS_GetArrayBufferViewType}; use js::jsapi::{UnwrapArrayBuffer, GetArrayBufferLengthAndData, GetArrayBufferViewLengthAndData}; +use js::jsapi::MutableHandleObject; use js::glue::{GetUint8ClampedArrayLengthAndData, GetFloat32ArrayLengthAndData}; use js::glue::{GetUint16ArrayLengthAndData, GetUint32ArrayLengthAndData}; use js::glue::{GetInt8ArrayLengthAndData, GetFloat64ArrayLengthAndData}; use js::glue::{GetInt16ArrayLengthAndData, GetInt32ArrayLengthAndData}; -use core::nonzero::NonZero; use std::mem; use std::ops::{Deref, DerefMut}; use std::slice; @@ -34,16 +34,6 @@ use std::ptr; #[allow(raw_pointer_derive)] struct TypedArrayObjectStorage { typed_obj: *mut JSObject, - wrapped_obj: *mut JSObject, -} - -impl TypedArrayObjectStorage { - fn new() -> TypedArrayObjectStorage { - TypedArrayObjectStorage { - typed_obj: ptr::null_mut(), - wrapped_obj: ptr::null_mut(), - } - } } /// Internal trait used to associate an element type with an underlying representation @@ -122,34 +112,35 @@ pub struct TypedArrayBase<'a, T: 'a + TypedArrayElement> { } impl<'a, T: TypedArrayElement> TypedArrayBase<'a, T> { - /// Create an uninitialized typed array representation. - pub fn new(rooter: &'a mut TypedArrayRooter) -> TypedArrayBase<'a, T> { - TypedArrayBase { - storage: TypedArrayObjectStorage::new(), + /// Create a typed array representation that wraps an existing JS reflector. + /// This operation will fail if attempted on a JS object that does not match + /// the expected typed array details. + pub fn from(obj: *mut JSObject, rooter: &'a mut TypedArrayRooter) + -> Result, ()> { + let typed_obj = T::unwrap_array(obj); + if typed_obj.is_null() { + return Err(()); + } + + Ok(TypedArrayBase { + storage: TypedArrayObjectStorage { + typed_obj: obj, + }, data: ptr::null_mut(), length: 0, computed: false, rooter: rooter, - } + }) } fn inited(&self) -> bool { - !self.storage.typed_obj.is_null() + self.rooter.inited() } - /// Initialize this typed array representaiton from an existing JS reflector - /// for a typed array. This operation will fail if attempted on a JS object - /// that does not match the expected typed array details. - pub fn init(&mut self, obj: *mut JSObject) -> Result<(), ()> { + /// Initialize the rooting for this wrapper. + pub fn init(&mut self) { assert!(!self.inited()); self.rooter.init(&mut self.storage); - self.storage.typed_obj = T::unwrap_array(obj); - self.storage.wrapped_obj = self.storage.typed_obj; - if self.inited() { - Ok(()) - } else { - Err(()) - } } /// Return the underlying buffer as a slice of the element type. @@ -163,11 +154,11 @@ impl<'a, T: TypedArrayElement> TypedArrayBase<'a, T> { /// Return the underlying buffer as a mutable slice of the element type. /// Length and data must be computed first. - pub fn as_mut_slice(&mut self) -> &mut [T::FundamentalType] { + /// Creating multiple mutable slices of the same buffer simultaneously + /// will result in undefined behaviour. + pub unsafe fn as_mut_slice(&mut self) -> &mut [T::FundamentalType] { assert!(self.computed); - unsafe { - slice::from_raw_parts_mut(self.data, self.length as usize) - } + slice::from_raw_parts_mut(self.data, self.length as usize) } /// Compute the length and data of this typed array. @@ -181,6 +172,16 @@ impl<'a, T: TypedArrayElement> TypedArrayBase<'a, T> { self.data = data; self.computed = true; } + + fn data(&self) -> *mut T::FundamentalType { + assert!(self.computed); + self.data + } + + fn length(&self) -> u32 { + assert!(self.computed); + self.length + } } impl<'a, T: TypedArrayElement> Drop for TypedArrayBase<'a, T> { @@ -193,7 +194,7 @@ impl<'a, T: TypedArrayElement> Drop for TypedArrayBase<'a, T> { } impl<'a> TypedArrayBase<'a, ArrayBufferViewU8> { - unsafe fn as_slice_transmute(&self) -> &[T] { + unsafe fn as_slice_transmute(&self) -> &[T] { assert!(self.computed); slice::from_raw_parts(self.data as *mut T, self.length as usize / mem::size_of::()) } @@ -254,39 +255,41 @@ impl<'a, T: TypedArrayElement> DerefMut for TypedArray<'a, T> { impl<'a, T: TypedArrayElementCreator + TypedArrayElement> TypedArray<'a, T> { /// Create an uninitialized wrapper around a future typed array. - pub fn new(rooter: &'a mut TypedArrayRooter) -> TypedArray<'a, T> { - TypedArray { - base: TypedArrayBase::new(rooter), - } + pub fn from(obj: *mut JSObject, rooter: &'a mut TypedArrayRooter) + -> Result, ()> { + Ok(TypedArray { + base: try!(TypedArrayBase::from(obj, rooter)), + }) } /// Create a new JS typed array, optionally providing initial data that will /// be copied into the newly-allocated buffer. Returns the new JS reflector. - pub fn create(cx: *mut JSContext, length: u32, data: Option<&[T::FundamentalType]>) - -> Option> { - let obj = T::create_new(cx, length); - if obj.is_null() { - return None; + pub fn create(cx: *mut JSContext, + length: u32, + data: Option<&[T::FundamentalType]>, + result: MutableHandleObject) + -> Result<(), ()> { + result.set(T::create_new(cx, length)); + if result.get().is_null() { + return Err(()); } if let Some(data) = data { assert!(data.len() <= length as usize); - let buf = T::get_data(obj); + let buf = T::get_data(result.get()); unsafe { ptr::copy_nonoverlapping(data.as_ptr(), buf, data.len()); } } - unsafe { - Some(NonZero::new(obj)) - } + Ok(()) } } /// A wrapper around a JS ArrayBufferView object. pub struct ArrayBufferViewBase<'a, T: 'a + TypedArrayElement> { typed_array: TypedArrayBase<'a, T>, - type_: Option, + type_: Type, } trait ArrayBufferViewType { @@ -333,26 +336,27 @@ array_buffer_view_output!(f64, Float64); array_buffer_view_output!(ClampedU8, Uint8Clamped); impl<'a, T: TypedArrayElement + ArrayBufferViewType> ArrayBufferViewBase<'a, T> { - /// Create an uninitilized ArrayBufferView wrapper. - pub fn new(rooter: &'a mut TypedArrayRooter) -> ArrayBufferViewBase<'a, T> { - ArrayBufferViewBase { - typed_array: TypedArrayBase::new(rooter), - type_: None, - } - } - - /// Initialize this wrapper with a JS ArrayBufferView reflector. Fails - /// if the JS reflector is not an ArrayBufferView, or if it is not + /// Create an ArrayBufferView wrapper for a JS reflector. + /// Fails if the JS reflector is not an ArrayBufferView, or if it is not /// one of the primitive numeric types (ie. SharedArrayBufferView, SIMD, etc.). - pub fn init(&mut self, obj: *mut JSObject) -> Result<(), ()> { - try!(self.typed_array.init(obj)); + pub fn from(obj: *mut JSObject, rooter: &'a mut TypedArrayRooter) + -> Result, ()> { + let typed_array_base = try!(TypedArrayBase::from(obj, rooter)); + let scalar_type = T::get_view_type(obj); - if (scalar_type as u32) < Type::MaxTypedArrayViewType as u32 { - self.type_ = Some(scalar_type); - Ok(()) - } else { - Err(()) + if (scalar_type as u32) >= Type::MaxTypedArrayViewType as u32 { + return Err(()); } + + Ok(ArrayBufferViewBase { + typed_array: typed_array_base, + type_: scalar_type, + }) + } + + /// Initialize this wrapper. + pub fn init(&mut self) { + self.typed_array.init(); } /// Compute the length and data of this typed array. @@ -364,29 +368,34 @@ impl<'a, T: TypedArrayElement + ArrayBufferViewType> ArrayBufferViewBase<'a, T> /// Return the element type of the wrapped ArrayBufferView. pub fn element_type(&self) -> Type { - assert!(self.type_.is_some()); - self.type_.unwrap() + self.type_ } /// Return a slice of the bytes of the underlying buffer. /// Length and data must be computed first. pub fn as_untyped_slice(&self) -> &[u8] { - unsafe { &*(self.typed_array.as_slice() as *const [T::FundamentalType] as *const [u8]) } + unsafe { + let num_bytes = self.typed_array.length() as usize * mem::size_of::(); + slice::from_raw_parts(self.typed_array.data() as *const _ as *const u8, num_bytes) + } } /// Return a mutable slice of the bytes of the underlying buffer. /// Length and data must be computed first. - pub fn as_mut_untyped_slice(&mut self) -> &mut [u8] { - unsafe { &mut *(self.typed_array.as_mut_slice() as *mut [T::FundamentalType] as *mut [u8]) } + /// Creating multiple mutable slices of the same buffer simultaneously + /// will result in undefined behaviour. + pub unsafe fn as_mut_untyped_slice(&mut self) -> &mut [u8] { + let num_bytes = self.typed_array.length() as usize * mem::size_of::(); + slice::from_raw_parts_mut(self.typed_array.data() as *mut u8, num_bytes) } } impl<'a> ArrayBufferViewBase<'a, ArrayBufferViewU8> { /// Return a slice of the underlying buffer reinterpreted as a different element type. /// Length and data must be computed first. - pub fn as_slice(&mut self) -> Result<&[U], ()> { - assert!(self.type_.is_some()); - if U::get_view_type() as u32 == self.type_.unwrap() as u32 { + pub fn as_slice(&mut self) -> Result<&[U], ()> + where U: ArrayBufferViewOutput + TypedArrayElement { + if U::get_view_type() as u32 == self.type_ as u32 { unsafe { Ok(self.typed_array.as_slice_transmute()) } @@ -429,6 +438,10 @@ impl TypedArrayRooter { } } + fn inited(&self) -> bool { + !self.typed_array.is_null() + } + fn init(&mut self, array: &mut TypedArrayObjectStorage) { assert!(self.typed_array.is_null()); self.typed_array = array; @@ -440,11 +453,6 @@ impl JSTraceable for TypedArrayRooter { fn trace(&self, trc: *mut JSTracer) { unsafe { let storage = &mut (*self.typed_array); - if !storage.wrapped_obj.is_null() { - JS_CallUnbarrieredObjectTracer(trc, - &mut storage.wrapped_obj, - b"TypedArray.wrapped_obj\0".as_ptr() as *const _); - } if !storage.typed_obj.is_null() { JS_CallUnbarrieredObjectTracer(trc, &mut storage.typed_obj, diff --git a/components/script/dom/crypto.rs b/components/script/dom/crypto.rs index 8f075b91cc39..25f26bdbdb42 100644 --- a/components/script/dom/crypto.rs +++ b/components/script/dom/crypto.rs @@ -38,21 +38,26 @@ impl Crypto { } impl<'a> CryptoMethods for &'a Crypto { + #[allow(unsafe_code)] // https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#Crypto-method-getRandomValues fn GetRandomValues(self, _cx: *mut JSContext, input: *mut JSObject) -> Fallible<*mut JSObject> { let mut rooter = TypedArrayRooter::new(); - let mut array_buffer_view = ArrayBufferView::new(&mut rooter); - if array_buffer_view.init(input).is_err() { - return Err(Error::Type("Argument to Crypto.getRandomValues is not an ArrayBufferView".to_owned())); - } - + let mut array_buffer_view = match ArrayBufferView::from(input, &mut rooter) { + Ok(view) => view, + Err(_) => + return Err(Error::Type("Argument to Crypto.getRandomValues is not an \ + ArrayBufferView".to_owned())), + }; + array_buffer_view.init(); if !is_integer_buffer(&array_buffer_view) { return Err(Error::TypeMismatch); } array_buffer_view.compute_length_and_data(); - let mut buffer = array_buffer_view.as_mut_untyped_slice(); + let mut buffer = unsafe { + array_buffer_view.as_mut_untyped_slice() + }; if buffer.len() > 65536 { return Err(Error::QuotaExceeded); diff --git a/components/script/dom/imagedata.rs b/components/script/dom/imagedata.rs index 6489492ee2e5..7a85207b043b 100644 --- a/components/script/dom/imagedata.rs +++ b/components/script/dom/imagedata.rs @@ -10,7 +10,8 @@ use dom::bindings::js::Root; use dom::bindings::typedarray::{Uint8ClampedArray, TypedArrayRooter}; use dom::bindings::utils::{Reflector, reflect_dom_object}; use euclid::size::Size2D; -use js::jsapi::{JSContext, JSObject, Heap}; +use js::jsapi::{JSContext, JSObject, Heap, RootedObject}; +use std::ptr; use std::vec::Vec; use std::default::Default; @@ -33,9 +34,14 @@ impl ImageData { data: Heap::default(), }; - match Uint8ClampedArray::create(global.get_cx(), width * height * 4, data) { - Some(js_object) => imagedata.data.set(*js_object), - None => return Err(Error::JSFailed) + let mut array_ptr = RootedObject::new(global.get_cx(), ptr::null_mut()); + let array = Uint8ClampedArray::create(global.get_cx(), + width * height * 4, + data, + array_ptr.handle_mut()); + match array { + Ok(()) => imagedata.data.set(*array_ptr.handle()), + Err(_) => return Err(Error::JSFailed) } Ok(reflect_dom_object(imagedata, global, ImageDataBinding::Wrap)) @@ -48,12 +54,13 @@ pub trait ImageDataHelpers { } impl<'a> ImageDataHelpers for &'a ImageData { - fn get_data_array(self, _global: &GlobalRef) -> Vec { + fn get_data_array(self, global: &GlobalRef) -> Vec { let mut rooter = TypedArrayRooter::new(); - let mut typed_array = Uint8ClampedArray::new(&mut rooter); - if typed_array.init(self.data.get()).is_err() { - return vec!(); - } + let mut typed_array = match Uint8ClampedArray::from(self.Data(global.get_cx()), &mut rooter) { + Ok(typed_array) => typed_array, + Err(_) => return vec![], + }; + typed_array.init(); typed_array.compute_length_and_data(); typed_array.as_slice().to_vec() } diff --git a/components/script/dom/textdecoder.rs b/components/script/dom/textdecoder.rs index 691efff16f70..fa8803e09618 100644 --- a/components/script/dom/textdecoder.rs +++ b/components/script/dom/textdecoder.rs @@ -91,21 +91,26 @@ impl<'a> TextDecoderMethods for &'a TextDecoder { }; let mut rooter = TypedArrayRooter::new(); - let mut array_buffer_view = ArrayBufferView::new(&mut rooter); - if array_buffer_view.init(input).is_err() { - let mut rooter = TypedArrayRooter::new(); - let mut array_buffer = ArrayBuffer::new(&mut rooter); - if array_buffer.init(input).is_err() { - return Err(Error::Type("Argument to TextDecoder.decode is not an ArrayBufferView \ - or ArrayBuffer".to_owned())); + let mut array_buffer_view = match ArrayBufferView::from(input, &mut rooter) { + Ok(array_buffer_view) => array_buffer_view, + Err(_) => { + let mut rooter = TypedArrayRooter::new(); + let mut array_buffer = match ArrayBuffer::from(input, &mut rooter) { + Ok(buffer) => buffer, + Err(_) => + return Err(Error::Type("Argument to TextDecoder.decode is not an \ + ArrayBufferView or ArrayBuffer".to_owned())), + }; + array_buffer.init(); + array_buffer.compute_length_and_data(); + let buffer = array_buffer.as_slice(); + return decode_from_slice(buffer, self.fatal, &self.encoding); } - array_buffer.compute_length_and_data(); - let buffer = array_buffer.as_slice(); - return decode_from_slice(buffer, self.fatal, &self.encoding); - } + }; + array_buffer_view.init(); array_buffer_view.compute_length_and_data(); let buffer = array_buffer_view.as_untyped_slice(); - return decode_from_slice(buffer, self.fatal, &self.encoding); + decode_from_slice(buffer, self.fatal, &self.encoding) } } diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index db22a80bcf40..dc97ff16eb81 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -284,10 +284,11 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { }; let mut rooter = TypedArrayRooter::new(); - let mut array_buffer_view = ArrayBufferView::new(&mut rooter); - if array_buffer_view.init(data).is_err() { - return; - } + let mut array_buffer_view = match ArrayBufferView::from(data, &mut rooter) { + Ok(view) => view, + Err(_) => return, + }; + array_buffer_view.init(); array_buffer_view.compute_length_and_data(); let data_vec = match array_buffer_view.as_slice() { Ok(data) => data.to_vec(), @@ -489,10 +490,11 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { }; let mut rooter = TypedArrayRooter::new(); - let mut typed_array = Float32Array::new(&mut rooter); - if typed_array.init(data).is_err() { - return; - } + let mut typed_array = match Float32Array::from(data, &mut rooter) { + Ok(array) => array, + Err(_) => return, + }; + typed_array.init(); typed_array.compute_length_and_data(); let data_vec = typed_array.as_slice().iter().cloned().take(4).collect(); From 0fd208553b3311240973b65aef592aa5abc322b2 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Thu, 30 Jul 2015 09:22:48 -0400 Subject: [PATCH 8/8] Make it impossible to get a usable buffer without computing the length, and make that operation infallible. --- components/script/dom/bindings/typedarray.rs | 173 +++++++++--------- components/script/dom/crypto.rs | 4 +- components/script/dom/imagedata.rs | 4 +- components/script/dom/textdecoder.rs | 10 +- .../script/dom/webglrenderingcontext.rs | 8 +- 5 files changed, 102 insertions(+), 97 deletions(-) diff --git a/components/script/dom/bindings/typedarray.rs b/components/script/dom/bindings/typedarray.rs index 463815292d1b..bf9cd1bf3f74 100644 --- a/components/script/dom/bindings/typedarray.rs +++ b/components/script/dom/bindings/typedarray.rs @@ -96,18 +96,49 @@ typed_array_element!(ClampedU8, u8, UnwrapUint8ClampedArray, GetUint8ClampedArra typed_array_element!(ArrayBufferU8, u8, UnwrapArrayBuffer, GetArrayBufferLengthAndData); typed_array_element!(ArrayBufferViewU8, u8, UnwrapArrayBufferView, GetArrayBufferViewLengthAndData); +/// The underlying buffer representation for a typed array. +pub struct TypedArrayBuffer<'a, T: TypedArrayElement> { + buffer: *mut T::FundamentalType, + elements: u32, + _typed_array: &'a TypedArrayObjectStorage, +} + +impl<'owner, T: TypedArrayElement> TypedArrayBuffer<'owner, T> { + /// Return the underlying buffer as a slice of the element type. + pub fn as_slice(&self) -> &[T::FundamentalType] { + unsafe { + slice::from_raw_parts(self.buffer, self.elements as usize) + } + } + + /// Return the underlying buffer as a mutable slice of the element type. + /// Creating multiple mutable slices of the same buffer simultaneously + /// will result in undefined behaviour. + pub unsafe fn as_mut_slice(&mut self) -> &mut [T::FundamentalType] { + slice::from_raw_parts_mut(self.buffer, self.elements as usize) + } + + fn data(&self) -> *mut T::FundamentalType { + self.buffer + } + + fn length(&self) -> u32 { + self.elements + } +} + +impl<'owner> TypedArrayBuffer<'owner, ArrayBufferViewU8> { + unsafe fn as_slice_transmute(&self) -> &[T] { + slice::from_raw_parts(self.buffer as *mut T, self.elements as usize / mem::size_of::()) + } +} + /// The base representation for all typed arrays. pub struct TypedArrayBase<'a, T: 'a + TypedArrayElement> { /// The JS wrapper storage. storage: TypedArrayObjectStorage, - /// The underlying memory buffer. - data: *mut T::FundamentalType, - /// The number of elements that make up the buffer. - length: u32, - /// True if the length and data of this typed array have been computed. - /// Any attempt to interact with the underlying buffer must compute it first, - /// to minimize the window of potential GC hazards. - computed: bool, + /// The underlying memory buffer and number of contained elements, if computed + computed: Option<(*mut T::FundamentalType, u32)>, rooter: &'a mut TypedArrayRooter, } @@ -126,9 +157,7 @@ impl<'a, T: TypedArrayElement> TypedArrayBase<'a, T> { storage: TypedArrayObjectStorage { typed_obj: obj, }, - data: ptr::null_mut(), - length: 0, - computed: false, + computed: None, rooter: rooter, }) } @@ -143,45 +172,24 @@ impl<'a, T: TypedArrayElement> TypedArrayBase<'a, T> { self.rooter.init(&mut self.storage); } - /// Return the underlying buffer as a slice of the element type. - /// Length and data must be computed first. - pub fn as_slice(&self) -> &[T::FundamentalType] { - assert!(self.computed); - unsafe { - slice::from_raw_parts(self.data, self.length as usize) - } - } - - /// Return the underlying buffer as a mutable slice of the element type. - /// Length and data must be computed first. - /// Creating multiple mutable slices of the same buffer simultaneously - /// will result in undefined behaviour. - pub unsafe fn as_mut_slice(&mut self) -> &mut [T::FundamentalType] { - assert!(self.computed); - slice::from_raw_parts_mut(self.data, self.length as usize) - } - - /// Compute the length and data of this typed array. - /// This operation that must only be performed once, as soon as - /// possible before making use of the resulting buffer. - pub fn compute_length_and_data(&mut self) { + /// Retrieve a usable buffer from this typed array. + pub fn extract(&mut self) -> TypedArrayBuffer { assert!(self.inited()); - assert!(!self.computed); - let (length, data) = T::length_and_data(self.storage.typed_obj); - self.length = length; - self.data = data; - self.computed = true; - } - - fn data(&self) -> *mut T::FundamentalType { - assert!(self.computed); - self.data + let (data, length) = match self.computed.as_ref() { + None => { + let (length, data) = T::length_and_data(self.storage.typed_obj); + self.computed = Some((data, length)); + (data, length) + } + Some(&(data, length)) => (data, length) + }; + TypedArrayBuffer { + buffer: data, + elements: length, + _typed_array: &self.storage + } } - fn length(&self) -> u32 { - assert!(self.computed); - self.length - } } impl<'a, T: TypedArrayElement> Drop for TypedArrayBase<'a, T> { @@ -193,13 +201,6 @@ impl<'a, T: TypedArrayElement> Drop for TypedArrayBase<'a, T> { } } -impl<'a> TypedArrayBase<'a, ArrayBufferViewU8> { - unsafe fn as_slice_transmute(&self) -> &[T] { - assert!(self.computed); - slice::from_raw_parts(self.data as *mut T, self.length as usize / mem::size_of::()) - } -} - trait TypedArrayElementCreator: TypedArrayElement { fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject; fn get_data(obj: *mut JSObject) -> *mut Self::FundamentalType; @@ -335,6 +336,30 @@ array_buffer_view_output!(f32, Float32); array_buffer_view_output!(f64, Float64); array_buffer_view_output!(ClampedU8, Uint8Clamped); +/// The underlying buffer representation for a typed array. +pub struct ArrayBufferBuffer<'owner, T: TypedArrayElement> { + base: TypedArrayBuffer<'owner, T>, + type_: Type, +} + +impl<'owner, T: TypedArrayElement> ArrayBufferBuffer<'owner, T> { + /// Return a slice of the bytes of the underlying buffer. + pub fn as_untyped_slice(&self) -> &[u8] { + unsafe { + let num_bytes = self.base.length() as usize * mem::size_of::(); + slice::from_raw_parts(self.base.data() as *const _ as *const u8, num_bytes) + } + } + + /// Return a mutable slice of the bytes of the underlying buffer. + /// Creating multiple mutable slices of the same buffer simultaneously + /// will result in undefined behaviour. + pub unsafe fn as_mut_untyped_slice(&mut self) -> &mut [u8] { + let num_bytes = self.base.length() as usize * mem::size_of::(); + slice::from_raw_parts_mut(self.base.data() as *mut u8, num_bytes) + } +} + impl<'a, T: TypedArrayElement + ArrayBufferViewType> ArrayBufferViewBase<'a, T> { /// Create an ArrayBufferView wrapper for a JS reflector. /// Fails if the JS reflector is not an ArrayBufferView, or if it is not @@ -359,45 +384,27 @@ impl<'a, T: TypedArrayElement + ArrayBufferViewType> ArrayBufferViewBase<'a, T> self.typed_array.init(); } - /// Compute the length and data of this typed array. - /// This operation that must only be performed once, as soon as - /// possible before making use of the resulting buffer. - pub fn compute_length_and_data(&mut self) { - self.typed_array.compute_length_and_data(); + /// Retrieve a usable buffer from this typed array. + pub fn extract(&mut self) -> ArrayBufferBuffer { + ArrayBufferBuffer { + base: self.typed_array.extract(), + type_: self.type_, + } } /// Return the element type of the wrapped ArrayBufferView. pub fn element_type(&self) -> Type { self.type_ } - - /// Return a slice of the bytes of the underlying buffer. - /// Length and data must be computed first. - pub fn as_untyped_slice(&self) -> &[u8] { - unsafe { - let num_bytes = self.typed_array.length() as usize * mem::size_of::(); - slice::from_raw_parts(self.typed_array.data() as *const _ as *const u8, num_bytes) - } - } - - /// Return a mutable slice of the bytes of the underlying buffer. - /// Length and data must be computed first. - /// Creating multiple mutable slices of the same buffer simultaneously - /// will result in undefined behaviour. - pub unsafe fn as_mut_untyped_slice(&mut self) -> &mut [u8] { - let num_bytes = self.typed_array.length() as usize * mem::size_of::(); - slice::from_raw_parts_mut(self.typed_array.data() as *mut u8, num_bytes) - } } -impl<'a> ArrayBufferViewBase<'a, ArrayBufferViewU8> { +impl<'owner> ArrayBufferBuffer<'owner, ArrayBufferViewU8> { /// Return a slice of the underlying buffer reinterpreted as a different element type. - /// Length and data must be computed first. - pub fn as_slice(&mut self) -> Result<&[U], ()> - where U: ArrayBufferViewOutput + TypedArrayElement { + pub fn as_slice(&self) -> Result<&[U], ()> + where U: ArrayBufferViewOutput + TypedArrayElement { if U::get_view_type() as u32 == self.type_ as u32 { unsafe { - Ok(self.typed_array.as_slice_transmute()) + Ok(self.base.as_slice_transmute()) } } else { Err(()) @@ -452,7 +459,7 @@ impl TypedArrayRooter { impl JSTraceable for TypedArrayRooter { fn trace(&self, trc: *mut JSTracer) { unsafe { - let storage = &mut (*self.typed_array); + let storage = &mut *self.typed_array; if !storage.typed_obj.is_null() { JS_CallUnbarrieredObjectTracer(trc, &mut storage.typed_obj, diff --git a/components/script/dom/crypto.rs b/components/script/dom/crypto.rs index 25f26bdbdb42..ac7cd7478a72 100644 --- a/components/script/dom/crypto.rs +++ b/components/script/dom/crypto.rs @@ -54,9 +54,9 @@ impl<'a> CryptoMethods for &'a Crypto { return Err(Error::TypeMismatch); } - array_buffer_view.compute_length_and_data(); + let mut data = array_buffer_view.extract(); let mut buffer = unsafe { - array_buffer_view.as_mut_untyped_slice() + data.as_mut_untyped_slice() }; if buffer.len() > 65536 { diff --git a/components/script/dom/imagedata.rs b/components/script/dom/imagedata.rs index 7a85207b043b..4c7171dba469 100644 --- a/components/script/dom/imagedata.rs +++ b/components/script/dom/imagedata.rs @@ -61,8 +61,8 @@ impl<'a> ImageDataHelpers for &'a ImageData { Err(_) => return vec![], }; typed_array.init(); - typed_array.compute_length_and_data(); - typed_array.as_slice().to_vec() + let data = typed_array.extract(); + data.as_slice().to_vec() } fn get_size(self) -> Size2D { diff --git a/components/script/dom/textdecoder.rs b/components/script/dom/textdecoder.rs index fa8803e09618..1cf8181e95ac 100644 --- a/components/script/dom/textdecoder.rs +++ b/components/script/dom/textdecoder.rs @@ -102,15 +102,13 @@ impl<'a> TextDecoderMethods for &'a TextDecoder { ArrayBufferView or ArrayBuffer".to_owned())), }; array_buffer.init(); - array_buffer.compute_length_and_data(); - let buffer = array_buffer.as_slice(); - return decode_from_slice(buffer, self.fatal, &self.encoding); + let data = array_buffer.extract(); + return decode_from_slice(data.as_slice(), self.fatal, &self.encoding); } }; array_buffer_view.init(); - array_buffer_view.compute_length_and_data(); - let buffer = array_buffer_view.as_untyped_slice(); - decode_from_slice(buffer, self.fatal, &self.encoding) + let data = array_buffer_view.extract(); + decode_from_slice(data.as_untyped_slice(), self.fatal, &self.encoding) } } diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index dc97ff16eb81..e338242093bd 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -289,8 +289,8 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { Err(_) => return, }; array_buffer_view.init(); - array_buffer_view.compute_length_and_data(); - let data_vec = match array_buffer_view.as_slice() { + let data_buffer = array_buffer_view.extract(); + let data_vec = match data_buffer.as_slice() { Ok(data) => data.to_vec(), Err(_) => return, }; @@ -495,8 +495,8 @@ impl<'a> WebGLRenderingContextMethods for &'a WebGLRenderingContext { Err(_) => return, }; typed_array.init(); - typed_array.compute_length_and_data(); - let data_vec = typed_array.as_slice().iter().cloned().take(4).collect(); + let data_buffer = typed_array.extract(); + let data_vec = data_buffer.as_slice().iter().cloned().take(4).collect(); self.ipc_renderer .send(CanvasMsg::WebGL(CanvasWebGLMsg::Uniform4fv(uniform_id, data_vec)))