<commit_before>/* 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 conversions::ToJSValConvertible;
use conversions::ConversionResult;
use conversions::FromJSValConvertible;
use glue::GetFloat32ArrayLengthAndData;
use glue::GetFloat64ArrayLengthAndData;
use glue::GetInt16ArrayLengthAndData;
use glue::GetInt32ArrayLengthAndData;
use glue::GetInt8ArrayLengthAndData;
use glue::GetUint16ArrayLengthAndData;
use glue::GetUint32ArrayLengthAndData;
use glue::GetUint8ArrayLengthAndData;
use glue::GetUint8ClampedArrayLengthAndData;
use jsapi::GetArrayBufferLengthAndData;
use jsapi::GetArrayBufferViewLengthAndData;
use jsapi::HandleValue;
use jsapi::Heap;
use jsapi::JSContext;
use jsapi::JSObject;
use jsapi::JSTracer;
use jsapi::JS_GetArrayBufferData;
use jsapi::JS_GetArrayBufferViewType;
use jsapi::JS_GetFloat32ArrayData;
use jsapi::JS_GetFloat64ArrayData;
use jsapi::JS_GetInt16ArrayData;
use jsapi::JS_GetInt32ArrayData;
use jsapi::JS_GetInt8ArrayData;
use jsapi::JS_GetUint16ArrayData;
use jsapi::JS_GetUint32ArrayData;
use jsapi::JS_GetUint8ArrayData;
use jsapi::JS_GetUint8ClampedArrayData;
use jsapi::JS_NewArrayBuffer;
use jsapi::JS_NewFloat32Array;
use jsapi::JS_NewFloat64Array;
use jsapi::JS_NewInt16Array;
use jsapi::JS_NewInt32Array;
use jsapi::JS_NewInt8Array;
use jsapi::JS_NewUint16Array;
use jsapi::JS_NewUint32Array;
use jsapi::JS_NewUint8Array;
use jsapi::JS_NewUint8ClampedArray;
use jsapi::MutableHandleObject;
use jsapi::MutableHandleValue;
use jsapi::Type;
use jsapi::UnwrapArrayBuffer;
use jsapi::UnwrapArrayBufferView;
use jsapi::UnwrapFloat32Array;
use jsapi::UnwrapFloat64Array;
use jsapi::UnwrapInt16Array;
use jsapi::UnwrapInt32Array;
use jsapi::UnwrapInt8Array;
use jsapi::UnwrapUint16Array;
use jsapi::UnwrapUint32Array;
use jsapi::UnwrapUint8Array;
use jsapi::UnwrapUint8ClampedArray;
use rust::CustomTrace;

use std::ptr;
use std::slice;

/// Trait that specifies how pointers to wrapped objects are stored. It supports
/// two variants, one with bare pointer (to be rooted on stack using
/// CustomAutoRooter) and wrapped in a Box<Heap<T>>, which can be stored in a
/// heap-allocated structure, to be rooted with JSTraceable-implementing tracers
/// (currently implemented in Servo).
pub trait JSObjectStorage {
    fn as_raw(&self) -> *mut JSObject;
    fn from_raw(raw: *mut JSObject) -> Self;
}

impl JSObjectStorage for *mut JSObject {
    fn as_raw(&self) -> *mut JSObject { *self }
    fn from_raw(raw: *mut JSObject) -> Self { raw }
}

impl JSObjectStorage for Box<Heap<*mut JSObject>> {
    fn as_raw(&self) -> *mut JSObject { self.get() }
    fn from_raw(raw: *mut JSObject) -> Self {
        let boxed = Box::new(Heap::default());
        boxed.set(raw);
        boxed
    }
}

impl<T: TypedArrayElement, S: JSObjectStorage> FromJSValConvertible for TypedArray<T, S> {
    type Config = ();
    unsafe fn from_jsval(_cx: *mut JSContext,
                         value: HandleValue,
                         _option: ())
                         -> Result<ConversionResult<Self>, ()> {
        if value.get().is_object() {
            Self::from(value.get().to_object()).map(ConversionResult::Success)
        } else {
            Err(())
        }
    }
}

impl<T: TypedArrayElement, S: JSObjectStorage> ToJSValConvertible for TypedArray<T, S> {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
        ToJSValConvertible::to_jsval(&self.object.as_raw(), cx, rval);
    }
}

pub enum CreateWith<'a, T: 'a> {
    Length(u32),
    Slice(&'a [T]),
}

/// A typed array wrapper.
pub struct TypedArray<T: TypedArrayElement, S: JSObjectStorage> {
    object: S,
    computed: Option<(*mut T::Element, u32)>,
}

unsafe impl<T> CustomTrace for TypedArray<T, *mut JSObject> where T: TypedArrayElement {
    fn trace(&self, trc: *mut JSTracer) {
        self.object.trace(trc);
    }
}

impl<T: TypedArrayElement, S: JSObjectStorage> TypedArray<T, S> {
    /// 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(object: *mut JSObject) -> Result<Self, ()> {
        if object.is_null() {
            return Err(());
        }
        unsafe {
            let unwrapped = T::unwrap_array(object);
            if unwrapped.is_null() {
                return Err(());
            }

            Ok(TypedArray {
                object: S::from_raw(unwrapped),
                computed: None,
            })
        }
    }

    fn data(&mut self) -> (*mut T::Element, u32) {
        if let Some(data) = self.computed {
            return data;
        }

        let data = unsafe { T::length_and_data(self.object.as_raw()) };
        self.computed = Some(data);
        data
    }

    /// # Unsafety
    ///
    /// Returned wrapped pointer to the underlying `JSObject` is meant to be
    /// read-only, modifying it can lead to Undefined Behaviour and violation
    /// of TypedArray API guarantees.
    ///
    /// Practically, this exists only to implement `JSTraceable` trait in Servo
    /// for Box<Heap<*mut JSObject>> variant.
    pub unsafe fn underlying_object(&self) -> &S {
        &self.object
    }

    /// Retrieves an owned data that's represented by the typed array.
    pub fn to_vec(&mut self) -> Vec<T::Element>
        where T::Element: Clone
    {
        // This is safe, because we immediately copy from the underlying buffer
        // to an owned collection. Otherwise, one needs to be careful, since
        // the underlying buffer can easily invalidated when transferred with
        // postMessage to another thread (To remedy that, we shouldn't
        // execute any JS code between getting the data pointer and using it).
        unsafe { self.as_slice().to_vec() }
    }

    /// # Unsafety
    ///
    /// The returned slice can be invalidated if the underlying typed array
    /// is neutered.
    pub unsafe fn as_slice(&mut self) -> &[T::Element] {
        let (pointer, length) = self.data();
        slice::from_raw_parts(pointer as *const T::Element, length as usize)
    }

    /// # Unsafety
    ///
    /// The returned slice can be invalidated if the underlying typed array
    /// is neutered.
    ///
    /// The underlying `JSObject` can be aliased, which can lead to
    /// Undefined Behavior due to mutable aliasing.
    pub unsafe fn as_mut_slice(&mut self) -> &mut [T::Element] {
        let (pointer, length) = self.data();
        slice::from_raw_parts_mut(pointer, length as usize)
    }
}

impl<T: TypedArrayElementCreator + TypedArrayElement, S: JSObjectStorage> TypedArray<T, S> {
    /// 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 unsafe fn create(cx: *mut JSContext,
                         with: CreateWith<T::Element>,
                         result: MutableHandleObject)
                         -> Result<(), ()> {
        let length = match with {
            CreateWith::Length(len) => len,
            CreateWith::Slice(slice) => slice.len() as u32,
        };

        result.set(T::create_new(cx, length));
        if result.get().is_null() {
            return Err(());
        }

        if let CreateWith::Slice(data) = with {
            Self::update_raw(data, result.get());
        }

        Ok(())
    }

    ///  Update an existed JS typed array
    pub unsafe fn update(&mut self, data: &[T::Element]) {
        Self::update_raw(data, self.object.as_raw());
    }

    unsafe fn update_raw(data: &[T::Element], result: *mut JSObject) {
        let (buf, length) = T::length_and_data(result);
        assert!(data.len() <= length as usize);
        ptr::copy_nonoverlapping(data.as_ptr(), buf, data.len());
    }
}

/// 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 Element;
    /// Unwrap a typed array JS reflector for this element type.
    unsafe fn unwrap_array(obj: *mut JSObject) -> *mut JSObject;
    /// Retrieve the length and data of a typed array's buffer for this element type.
    unsafe fn length_and_data(obj: *mut JSObject) -> (*mut Self::Element, u32);
}

/// Internal trait for creating new typed arrays.
pub trait TypedArrayElementCreator: TypedArrayElement {
    /// Create a new typed array.
    unsafe fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject;
    /// Get the data.
    unsafe fn get_data(obj: *mut JSObject) -> *mut Self::Element;
}

macro_rules! typed_array_element {
    ($t: ident,
     $element: ty,
     $unwrap: ident,
     $length_and_data: ident) => (
        /// A kind of typed array.
        pub struct $t;

        impl TypedArrayElement for $t {
            type Element = $element;
            unsafe fn unwrap_array(obj: *mut JSObject) -> *mut JSObject {
                $unwrap(obj)
            }

            unsafe fn length_and_data(obj: *mut JSObject) -> (*mut Self::Element, u32) {
                let mut len = 0;
                let mut shared = false;
                let mut data = ptr::null_mut();
                $length_and_data(obj, &mut len, &mut shared, &mut data);
                assert!(!shared);
                (data, len)
            }
        }
    );

    ($t: ident,
     $element: ty,
     $unwrap: ident,
     $length_and_data: ident,
     $create_new: ident,
     $get_data: ident) => (
        typed_array_element!($t, $element, $unwrap, $length_and_data);

        impl TypedArrayElementCreator for $t {
            unsafe fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject {
                $create_new(cx, length)
            }

            unsafe fn get_data(obj: *mut JSObject) -> *mut Self::Element {
                let mut shared = false;
                let data = $get_data(obj, &mut shared, ptr::null_mut());
                assert!(!shared);
                data
            }
        }
    );
}

typed_array_element!(Uint8,
                     u8,
                     UnwrapUint8Array,
                     GetUint8ArrayLengthAndData,
                     JS_NewUint8Array,
                     JS_GetUint8ArrayData);
typed_array_element!(Uint16,
                     u16,
                     UnwrapUint16Array,
                     GetUint16ArrayLengthAndData,
                     JS_NewUint16Array,
                     JS_GetUint16ArrayData);
typed_array_element!(Uint32,
                     u32,
                     UnwrapUint32Array,
                     GetUint32ArrayLengthAndData,
                     JS_NewUint32Array,
                     JS_GetUint32ArrayData);
typed_array_element!(Int8,
                     i8,
                     UnwrapInt8Array,
                     GetInt8ArrayLengthAndData,
                     JS_NewInt8Array,
                     JS_GetInt8ArrayData);
typed_array_element!(Int16,
                     i16,
                     UnwrapInt16Array,
                     GetInt16ArrayLengthAndData,
                     JS_NewInt16Array,
                     JS_GetInt16ArrayData);
typed_array_element!(Int32,
                     i32,
                     UnwrapInt32Array,
                     GetInt32ArrayLengthAndData,
                     JS_NewInt32Array,
                     JS_GetInt32ArrayData);
typed_array_element!(Float32,
                     f32,
                     UnwrapFloat32Array,
                     GetFloat32ArrayLengthAndData,
                     JS_NewFloat32Array,
                     JS_GetFloat32ArrayData);
typed_array_element!(Float64,
                     f64,
                     UnwrapFloat64Array,
                     GetFloat64ArrayLengthAndData,
                     JS_NewFloat64Array,
                     JS_GetFloat64ArrayData);
typed_array_element!(ClampedU8,
                     u8,
                     UnwrapUint8ClampedArray,
                     GetUint8ClampedArrayLengthAndData,
                     JS_NewUint8ClampedArray,
                     JS_GetUint8ClampedArrayData);
typed_array_element!(ArrayBufferU8,
                     u8,
                     UnwrapArrayBuffer,
                     GetArrayBufferLengthAndData,
                     JS_NewArrayBuffer,
                     JS_GetArrayBufferData);
typed_array_element!(ArrayBufferViewU8,
                     u8,
                     UnwrapArrayBufferView,
                     GetArrayBufferViewLengthAndData);

// Default type aliases, uses bare pointer by default, since stack lifetime
// should be the most common scenario
macro_rules! array_alias {
    ($arr: ident, $heap_arr: ident, $elem: ty) => {
        pub type $arr = TypedArray<$elem, *mut JSObject>;
        pub type $heap_arr = TypedArray<$elem, Box<Heap<*mut JSObject>>>;
    }
}

array_alias!(Uint8ClampedArray, HeapUint8ClampedArray, ClampedU8);
array_alias!(Uint8Array, HeapUint8Array, Uint8);
array_alias!(Int8Array, HeapInt8Array, Int8);
array_alias!(Uint16Array, HeapUint16Array, Uint16);
array_alias!(Int16Array, HeapInt16Array, Int16);
array_alias!(Uint32Array, HeapUint32Array, Uint32);
array_alias!(Int32Array, HeapInt32Array, Int32);
array_alias!(Float32Array, HeapFloat32Array, Float32);
array_alias!(Float64Array, HeapFloat64Array, Float64);
array_alias!(ArrayBuffer, HeapArrayBuffer, ArrayBufferU8);
array_alias!(ArrayBufferView, HeapArrayBufferView, ArrayBufferViewU8);

impl<S: JSObjectStorage> TypedArray<ArrayBufferViewU8, S> {
    pub fn get_array_type(&self) -> Type {
        unsafe { JS_GetArrayBufferViewType(self.object.as_raw()) }
    }
}

#[macro_export]
macro_rules! typedarray {
    (in($cx:expr) let $name:ident : $ty:ident = $init:expr) => {
        let mut __array = $crate::typedarray::$ty::from($init)
            .map($crate::rust::CustomAutoRooter::new);

        let $name = __array.as_mut().map(|ok| ok.root($cx));
    };
    (in($cx:expr) let mut $name:ident : $ty:ident = $init:expr) => {
        let mut __array = $crate::typedarray::$ty::from($init)
            .map($crate::rust::CustomAutoRooter::new);

        let mut $name = __array.as_mut().map(|ok| ok.root($cx));
    }
}
<commit_msg>typedarray: Use a Cell for handling interior mutability.<commit_after>/* 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 conversions::ToJSValConvertible;
use conversions::ConversionResult;
use conversions::FromJSValConvertible;
use glue::GetFloat32ArrayLengthAndData;
use glue::GetFloat64ArrayLengthAndData;
use glue::GetInt16ArrayLengthAndData;
use glue::GetInt32ArrayLengthAndData;
use glue::GetInt8ArrayLengthAndData;
use glue::GetUint16ArrayLengthAndData;
use glue::GetUint32ArrayLengthAndData;
use glue::GetUint8ArrayLengthAndData;
use glue::GetUint8ClampedArrayLengthAndData;
use jsapi::GetArrayBufferLengthAndData;
use jsapi::GetArrayBufferViewLengthAndData;
use jsapi::HandleValue;
use jsapi::Heap;
use jsapi::JSContext;
use jsapi::JSObject;
use jsapi::JSTracer;
use jsapi::JS_GetArrayBufferData;
use jsapi::JS_GetArrayBufferViewType;
use jsapi::JS_GetFloat32ArrayData;
use jsapi::JS_GetFloat64ArrayData;
use jsapi::JS_GetInt16ArrayData;
use jsapi::JS_GetInt32ArrayData;
use jsapi::JS_GetInt8ArrayData;
use jsapi::JS_GetUint16ArrayData;
use jsapi::JS_GetUint32ArrayData;
use jsapi::JS_GetUint8ArrayData;
use jsapi::JS_GetUint8ClampedArrayData;
use jsapi::JS_NewArrayBuffer;
use jsapi::JS_NewFloat32Array;
use jsapi::JS_NewFloat64Array;
use jsapi::JS_NewInt16Array;
use jsapi::JS_NewInt32Array;
use jsapi::JS_NewInt8Array;
use jsapi::JS_NewUint16Array;
use jsapi::JS_NewUint32Array;
use jsapi::JS_NewUint8Array;
use jsapi::JS_NewUint8ClampedArray;
use jsapi::MutableHandleObject;
use jsapi::MutableHandleValue;
use jsapi::Type;
use jsapi::UnwrapArrayBuffer;
use jsapi::UnwrapArrayBufferView;
use jsapi::UnwrapFloat32Array;
use jsapi::UnwrapFloat64Array;
use jsapi::UnwrapInt16Array;
use jsapi::UnwrapInt32Array;
use jsapi::UnwrapInt8Array;
use jsapi::UnwrapUint16Array;
use jsapi::UnwrapUint32Array;
use jsapi::UnwrapUint8Array;
use jsapi::UnwrapUint8ClampedArray;
use rust::CustomTrace;

use std::ptr;
use std::slice;
use std::cell::Cell;

/// Trait that specifies how pointers to wrapped objects are stored. It supports
/// two variants, one with bare pointer (to be rooted on stack using
/// CustomAutoRooter) and wrapped in a Box<Heap<T>>, which can be stored in a
/// heap-allocated structure, to be rooted with JSTraceable-implementing tracers
/// (currently implemented in Servo).
pub trait JSObjectStorage {
    fn as_raw(&self) -> *mut JSObject;
    fn from_raw(raw: *mut JSObject) -> Self;
}

impl JSObjectStorage for *mut JSObject {
    fn as_raw(&self) -> *mut JSObject { *self }
    fn from_raw(raw: *mut JSObject) -> Self { raw }
}

impl JSObjectStorage for Box<Heap<*mut JSObject>> {
    fn as_raw(&self) -> *mut JSObject { self.get() }
    fn from_raw(raw: *mut JSObject) -> Self {
        let boxed = Box::new(Heap::default());
        boxed.set(raw);
        boxed
    }
}

impl<T: TypedArrayElement, S: JSObjectStorage> FromJSValConvertible for TypedArray<T, S> {
    type Config = ();
    unsafe fn from_jsval(_cx: *mut JSContext,
                         value: HandleValue,
                         _option: ())
                         -> Result<ConversionResult<Self>, ()> {
        if value.get().is_object() {
            Self::from(value.get().to_object()).map(ConversionResult::Success)
        } else {
            Err(())
        }
    }
}

impl<T: TypedArrayElement, S: JSObjectStorage> ToJSValConvertible for TypedArray<T, S> {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
        ToJSValConvertible::to_jsval(&self.object.as_raw(), cx, rval);
    }
}

pub enum CreateWith<'a, T: 'a> {
    Length(u32),
    Slice(&'a [T]),
}

/// A typed array wrapper.
pub struct TypedArray<T: TypedArrayElement, S: JSObjectStorage> {
    object: S,
    computed: Cell<Option<(*mut T::Element, u32)>>,
}

unsafe impl<T> CustomTrace for TypedArray<T, *mut JSObject> where T: TypedArrayElement {
    fn trace(&self, trc: *mut JSTracer) {
        self.object.trace(trc);
    }
}

impl<T: TypedArrayElement, S: JSObjectStorage> TypedArray<T, S> {
    /// 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(object: *mut JSObject) -> Result<Self, ()> {
        if object.is_null() {
            return Err(());
        }
        unsafe {
            let unwrapped = T::unwrap_array(object);
            if unwrapped.is_null() {
                return Err(());
            }

            Ok(TypedArray {
                object: S::from_raw(unwrapped),
                computed: Cell::new(None),
            })
        }
    }

    fn data(&self) -> (*mut T::Element, u32) {
        if let Some(data) = self.computed.get() {
            return data;
        }

        let data = unsafe { T::length_and_data(self.object.as_raw()) };
        self.computed.set(Some(data));
        data
    }

    /// # Unsafety
    ///
    /// Returned wrapped pointer to the underlying `JSObject` is meant to be
    /// read-only, modifying it can lead to Undefined Behaviour and violation
    /// of TypedArray API guarantees.
    ///
    /// Practically, this exists only to implement `JSTraceable` trait in Servo
    /// for Box<Heap<*mut JSObject>> variant.
    pub unsafe fn underlying_object(&self) -> &S {
        &self.object
    }

    /// Retrieves an owned data that's represented by the typed array.
    pub fn to_vec(&self) -> Vec<T::Element>
        where T::Element: Clone
    {
        // This is safe, because we immediately copy from the underlying buffer
        // to an owned collection. Otherwise, one needs to be careful, since
        // the underlying buffer can easily invalidated when transferred with
        // postMessage to another thread (To remedy that, we shouldn't
        // execute any JS code between getting the data pointer and using it).
        unsafe { self.as_slice().to_vec() }
    }

    /// # Unsafety
    ///
    /// The returned slice can be invalidated if the underlying typed array
    /// is neutered.
    pub unsafe fn as_slice(&self) -> &[T::Element] {
        let (pointer, length) = self.data();
        slice::from_raw_parts(pointer as *const T::Element, length as usize)
    }

    /// # Unsafety
    ///
    /// The returned slice can be invalidated if the underlying typed array
    /// is neutered.
    ///
    /// The underlying `JSObject` can be aliased, which can lead to
    /// Undefined Behavior due to mutable aliasing.
    pub unsafe fn as_mut_slice(&mut self) -> &mut [T::Element] {
        let (pointer, length) = self.data();
        slice::from_raw_parts_mut(pointer, length as usize)
    }
}

impl<T: TypedArrayElementCreator + TypedArrayElement, S: JSObjectStorage> TypedArray<T, S> {
    /// 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 unsafe fn create(cx: *mut JSContext,
                         with: CreateWith<T::Element>,
                         result: MutableHandleObject)
                         -> Result<(), ()> {
        let length = match with {
            CreateWith::Length(len) => len,
            CreateWith::Slice(slice) => slice.len() as u32,
        };

        result.set(T::create_new(cx, length));
        if result.get().is_null() {
            return Err(());
        }

        if let CreateWith::Slice(data) = with {
            Self::update_raw(data, result.get());
        }

        Ok(())
    }

    ///  Update an existed JS typed array
    pub unsafe fn update(&mut self, data: &[T::Element]) {
        Self::update_raw(data, self.object.as_raw());
    }

    unsafe fn update_raw(data: &[T::Element], result: *mut JSObject) {
        let (buf, length) = T::length_and_data(result);
        assert!(data.len() <= length as usize);
        ptr::copy_nonoverlapping(data.as_ptr(), buf, data.len());
    }
}

/// 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 Element;
    /// Unwrap a typed array JS reflector for this element type.
    unsafe fn unwrap_array(obj: *mut JSObject) -> *mut JSObject;
    /// Retrieve the length and data of a typed array's buffer for this element type.
    unsafe fn length_and_data(obj: *mut JSObject) -> (*mut Self::Element, u32);
}

/// Internal trait for creating new typed arrays.
pub trait TypedArrayElementCreator: TypedArrayElement {
    /// Create a new typed array.
    unsafe fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject;
    /// Get the data.
    unsafe fn get_data(obj: *mut JSObject) -> *mut Self::Element;
}

macro_rules! typed_array_element {
    ($t: ident,
     $element: ty,
     $unwrap: ident,
     $length_and_data: ident) => (
        /// A kind of typed array.
        pub struct $t;

        impl TypedArrayElement for $t {
            type Element = $element;
            unsafe fn unwrap_array(obj: *mut JSObject) -> *mut JSObject {
                $unwrap(obj)
            }

            unsafe fn length_and_data(obj: *mut JSObject) -> (*mut Self::Element, u32) {
                let mut len = 0;
                let mut shared = false;
                let mut data = ptr::null_mut();
                $length_and_data(obj, &mut len, &mut shared, &mut data);
                assert!(!shared);
                (data, len)
            }
        }
    );

    ($t: ident,
     $element: ty,
     $unwrap: ident,
     $length_and_data: ident,
     $create_new: ident,
     $get_data: ident) => (
        typed_array_element!($t, $element, $unwrap, $length_and_data);

        impl TypedArrayElementCreator for $t {
            unsafe fn create_new(cx: *mut JSContext, length: u32) -> *mut JSObject {
                $create_new(cx, length)
            }

            unsafe fn get_data(obj: *mut JSObject) -> *mut Self::Element {
                let mut shared = false;
                let data = $get_data(obj, &mut shared, ptr::null_mut());
                assert!(!shared);
                data
            }
        }
    );
}

typed_array_element!(Uint8,
                     u8,
                     UnwrapUint8Array,
                     GetUint8ArrayLengthAndData,
                     JS_NewUint8Array,
                     JS_GetUint8ArrayData);
typed_array_element!(Uint16,
                     u16,
                     UnwrapUint16Array,
                     GetUint16ArrayLengthAndData,
                     JS_NewUint16Array,
                     JS_GetUint16ArrayData);
typed_array_element!(Uint32,
                     u32,
                     UnwrapUint32Array,
                     GetUint32ArrayLengthAndData,
                     JS_NewUint32Array,
                     JS_GetUint32ArrayData);
typed_array_element!(Int8,
                     i8,
                     UnwrapInt8Array,
                     GetInt8ArrayLengthAndData,
                     JS_NewInt8Array,
                     JS_GetInt8ArrayData);
typed_array_element!(Int16,
                     i16,
                     UnwrapInt16Array,
                     GetInt16ArrayLengthAndData,
                     JS_NewInt16Array,
                     JS_GetInt16ArrayData);
typed_array_element!(Int32,
                     i32,
                     UnwrapInt32Array,
                     GetInt32ArrayLengthAndData,
                     JS_NewInt32Array,
                     JS_GetInt32ArrayData);
typed_array_element!(Float32,
                     f32,
                     UnwrapFloat32Array,
                     GetFloat32ArrayLengthAndData,
                     JS_NewFloat32Array,
                     JS_GetFloat32ArrayData);
typed_array_element!(Float64,
                     f64,
                     UnwrapFloat64Array,
                     GetFloat64ArrayLengthAndData,
                     JS_NewFloat64Array,
                     JS_GetFloat64ArrayData);
typed_array_element!(ClampedU8,
                     u8,
                     UnwrapUint8ClampedArray,
                     GetUint8ClampedArrayLengthAndData,
                     JS_NewUint8ClampedArray,
                     JS_GetUint8ClampedArrayData);
typed_array_element!(ArrayBufferU8,
                     u8,
                     UnwrapArrayBuffer,
                     GetArrayBufferLengthAndData,
                     JS_NewArrayBuffer,
                     JS_GetArrayBufferData);
typed_array_element!(ArrayBufferViewU8,
                     u8,
                     UnwrapArrayBufferView,
                     GetArrayBufferViewLengthAndData);

// Default type aliases, uses bare pointer by default, since stack lifetime
// should be the most common scenario
macro_rules! array_alias {
    ($arr: ident, $heap_arr: ident, $elem: ty) => {
        pub type $arr = TypedArray<$elem, *mut JSObject>;
        pub type $heap_arr = TypedArray<$elem, Box<Heap<*mut JSObject>>>;
    }
}

array_alias!(Uint8ClampedArray, HeapUint8ClampedArray, ClampedU8);
array_alias!(Uint8Array, HeapUint8Array, Uint8);
array_alias!(Int8Array, HeapInt8Array, Int8);
array_alias!(Uint16Array, HeapUint16Array, Uint16);
array_alias!(Int16Array, HeapInt16Array, Int16);
array_alias!(Uint32Array, HeapUint32Array, Uint32);
array_alias!(Int32Array, HeapInt32Array, Int32);
array_alias!(Float32Array, HeapFloat32Array, Float32);
array_alias!(Float64Array, HeapFloat64Array, Float64);
array_alias!(ArrayBuffer, HeapArrayBuffer, ArrayBufferU8);
array_alias!(ArrayBufferView, HeapArrayBufferView, ArrayBufferViewU8);

impl<S: JSObjectStorage> TypedArray<ArrayBufferViewU8, S> {
    pub fn get_array_type(&self) -> Type {
        unsafe { JS_GetArrayBufferViewType(self.object.as_raw()) }
    }
}

#[macro_export]
macro_rules! typedarray {
    (in($cx:expr) let $name:ident : $ty:ident = $init:expr) => {
        let mut __array = $crate::typedarray::$ty::from($init)
            .map($crate::rust::CustomAutoRooter::new);

        let $name = __array.as_mut().map(|ok| ok.root($cx));
    };
    (in($cx:expr) let mut $name:ident : $ty:ident = $init:expr) => {
        let mut __array = $crate::typedarray::$ty::from($init)
            .map($crate::rust::CustomAutoRooter::new);

        let mut $name = __array.as_mut().map(|ok| ok.root($cx));
    }
}
<|endoftext|>


<commit_before>//! A Rust interface for the functionality of the Objective-C runtime.
//!
//! For more information on foreign functions, see Apple's documentation:
//! https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html

use std::ffi::{CStr, CString};
use std::fmt;
use std::ptr;
use std::str;
use libc::{c_char, c_int, c_schar, c_uint, c_void, ptrdiff_t, size_t};
use malloc_buf::{MallocBuffer, MallocString};

use {encode, Encode};

/// The Objective-C `BOOL` type.
///
/// To convert an Objective-C `BOOL` into a Rust `bool`, compare it with `NO`.
pub type BOOL = c_schar;
/// The equivalent of true for Objective-C's `BOOL` type.
pub const YES: BOOL = 1;
/// The equivalent of false for Objective-C's `BOOL` type.
pub const NO: BOOL = 0;

/// A type that represents a method selector.
#[repr(C)]
pub struct Sel {
    ptr: *const c_void,
}

/// A type that represents an instance variable.
pub enum Ivar { }

/// A type that represents a method in a class definition.
pub enum Method { }

/// A type that represents an Objective-C class.
pub enum Class { }

/// A type that represents an instance of a class.
pub enum Object { }

/// A pointer to the start of a method implementation.
pub type Imp = extern fn(*mut Object, Sel, ...) -> *mut Object;

#[link(name = "objc", kind = "dylib")]
extern {
    pub fn sel_registerName(name: *const c_char) -> Sel;
    pub fn sel_getName(sel: Sel) -> *const c_char;

    pub fn class_getName(cls: *const Class) -> *const c_char;
    pub fn class_getInstanceSize(cls: *const Class) -> size_t;
    pub fn class_getInstanceMethod(cls: *const Class, sel: Sel) -> *const Method;
    pub fn class_getInstanceVariable(cls: *const Class, name: *const c_char) -> *const Ivar;
    pub fn class_copyMethodList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Method;
    pub fn class_copyIvarList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Ivar;
    pub fn class_addMethod(cls: *mut Class, name: Sel, imp: Imp, types: *const c_char) -> BOOL;
    pub fn class_addIvar(cls: *mut Class, name: *const c_char, size: size_t, alignment: u8, types: *const c_char) -> BOOL;

    pub fn objc_allocateClassPair(superclass: *const Class, name: *const c_char, extraBytes: size_t) -> *mut Class;
    pub fn objc_disposeClassPair(cls: *mut Class);
    pub fn objc_registerClassPair(cls: *mut Class);

    pub fn object_getClass(obj: *const Object) -> *const Class;

    pub fn objc_getClassList(buffer: *mut *const Class, bufferLen: c_int) -> c_int;
    pub fn objc_copyClassList(outCount: *mut c_uint) -> *mut *const Class;
    pub fn objc_getClass(name: *const c_char) -> *const Class;

    pub fn ivar_getName(ivar: *const Ivar) -> *const c_char;
    pub fn ivar_getOffset(ivar: *const Ivar) -> ptrdiff_t;
    pub fn ivar_getTypeEncoding(ivar: *const Ivar) -> *const c_char;

    pub fn objc_msgSend(obj: *mut Object, op: Sel, ...) -> *mut Object;
    pub fn objc_msgSend_stret(obj: *mut Object, op: Sel, ...);

    pub fn method_getName(method: *const Method) -> Sel;
    pub fn method_getImplementation(method: *const Method) -> Imp;
    pub fn method_getTypeEncoding(method: *const Method) -> *const c_char;
    pub fn method_copyReturnType(method: *const Method) -> *mut c_char;
    pub fn method_copyArgumentType(method: *const Method, index: c_uint) -> *mut c_char;
    pub fn method_getNumberOfArguments(method: *const Method) -> c_uint;
    pub fn method_setImplementation(method: *mut Method, imp: Imp) -> Imp;
    pub fn method_exchangeImplementations(m1: *mut Method, m2: *mut Method);
}

impl Sel {
    /// Registers a method with the Objective-C runtime system,
    /// maps the method name to a selector, and returns the selector value.
    pub fn register(name: &str) -> Sel {
        let name = CString::new(name).unwrap();
        unsafe {
            sel_registerName(name.as_ptr())
        }
    }

    /// Returns the name of the method specified by self.
    pub fn name(&self) -> &str {
        let name = unsafe {
            CStr::from_ptr(sel_getName(*self))
        };
        str::from_utf8(name.to_bytes()).unwrap()
    }
}

impl PartialEq for Sel {
    fn eq(&self, other: &Sel) -> bool {
        self.ptr == other.ptr
    }
}

impl Eq for Sel { }

impl Copy for Sel { }

impl Clone for Sel {
    fn clone(&self) -> Sel { *self }
}

impl fmt::Debug for Sel {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.name())
    }
}

impl Ivar {
    /// Returns the name of self.
    pub fn name(&self) -> &str {
        let name = unsafe {
            CStr::from_ptr(ivar_getName(self))
        };
        str::from_utf8(name.to_bytes()).unwrap()
    }

    /// Returns the offset of self.
    pub fn offset(&self) -> isize {
        let offset = unsafe {
            ivar_getOffset(self)
        };
        offset as isize
    }

    /// Returns the type string of self.
    pub fn type_encoding(&self) -> &str {
        let encoding = unsafe {
            CStr::from_ptr(ivar_getTypeEncoding(self))
        };
        str::from_utf8(encoding.to_bytes()).unwrap()
    }
}

impl Method {
    /// Returns the name of self.
    pub fn name(&self) -> Sel {
        unsafe {
            method_getName(self)
        }
    }

    /// Returns a string describing self's parameter and return types.
    pub fn type_encoding(&self) -> &str {
        let encoding = unsafe {
            CStr::from_ptr(method_getTypeEncoding(self))
        };
        str::from_utf8(encoding.to_bytes()).unwrap()
    }

    /// Returns a string describing self's return type.
    pub fn return_type(&self) -> MallocString {
        unsafe {
            let encoding = method_copyReturnType(self);
            MallocString::new(encoding).unwrap()
        }
    }

    /// Returns a string describing a single parameter type of self, or
    /// `None` if self has no parameter at the given index.
    pub fn argument_type(&self, index: usize) -> Option<MallocString> {
        unsafe {
            let encoding = method_copyArgumentType(self, index as c_uint);
            if encoding.is_null() {
                None
            } else {
                Some(MallocString::new(encoding).unwrap())
            }
        }
    }

    /// Returns the number of arguments accepted by self.
    pub fn arguments_count(&self) -> usize {
        unsafe {
            method_getNumberOfArguments(self) as usize
        }
    }

    /// Returns the implementation of self.
    pub fn implementation(&self) -> Imp {
        unsafe {
            method_getImplementation(self)
        }
    }

    /// Sets the implementation of self.
    /// Unsafe because the caller must ensure the implementation has the
    /// correct self, return, and argument types for the method.
    pub unsafe fn set_implementation(&mut self, imp: Imp) -> Imp {
        method_setImplementation(self, imp)
    }

    /// Exchanges the implementations of self with another `Method`.
    /// Unsafe because the caller must ensure the two methods have the same
    /// self, return, and argument types.
    pub unsafe fn exchange_implementation(&mut self, other: &mut Method) {
        method_exchangeImplementations(self, other);
    }
}

impl Class {
    /// Returns the class definition of a specified class, or `None` if the
    /// class is not registered with the Objective-C runtime.
    pub fn get(name: &str) -> Option<&'static Class> {
        let name = CString::new(name).unwrap();
        unsafe {
            objc_getClass(name.as_ptr()).as_ref()
        }
    }

    /// Obtains the list of registered class definitions.
    pub fn classes() -> MallocBuffer<&'static Class> {
        unsafe {
            let mut count: c_uint = 0;
            let classes = objc_copyClassList(&mut count);
            MallocBuffer::new(classes as *mut _, count as usize).unwrap()
        }
    }

    /// Returns the total number of registered classes.
    pub fn classes_count() -> usize {
        unsafe {
            objc_getClassList(ptr::null_mut(), 0) as usize
        }
    }

    /// Returns the name of self.
    pub fn name(&self) -> &str {
        let name = unsafe {
            CStr::from_ptr(class_getName(self))
        };
        str::from_utf8(name.to_bytes()).unwrap()
    }

    /// Returns the size of instances of self.
    pub fn instance_size(&self) -> usize {
        unsafe {
            class_getInstanceSize(self) as usize
        }
    }

    /// Returns a specified instance method for self, or `None` if self and
    /// its superclasses do not contain an instance method with the
    /// specified selector.
    pub fn instance_method(&self, sel: Sel) -> Option<&Method> {
        unsafe {
            class_getInstanceMethod(self, sel).as_ref()
        }
    }

    /// Returns the ivar for a specified instance variable of self, or `None`
    /// if self has no ivar with the given name.
    pub fn instance_variable(&self, name: &str) -> Option<&Ivar> {
        let name = CString::new(name).unwrap();
        unsafe {
            class_getInstanceVariable(self, name.as_ptr()).as_ref()
        }
    }

    /// Describes the instance methods implemented by self.
    pub fn instance_methods(&self) -> MallocBuffer<&Method> {
        unsafe {
            let mut count: c_uint = 0;
            let methods = class_copyMethodList(self, &mut count);
            MallocBuffer::new(methods as *mut _, count as usize).unwrap()
        }

    }

    /// Describes the instance variables declared by self.
    pub fn instance_variables(&self) -> MallocBuffer<&Ivar> {
        unsafe {
            let mut count: c_uint = 0;
            let ivars = class_copyIvarList(self, &mut count);
            MallocBuffer::new(ivars as *mut _, count as usize).unwrap()
        }
    }
}

impl PartialEq for Class {
    fn eq(&self, other: &Class) -> bool {
        self as *const Class == other as *const Class
    }
}

impl Eq for Class { }

impl fmt::Debug for Class {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.name())
    }
}

impl Object {
    /// Returns the class of self.
    pub fn class(&self) -> &'static Class {
        unsafe {
            &*object_getClass(self)
        }
    }

    /// Returns a reference to the ivar of self with the given name.
    /// Panics if self has no ivar with the given name.
    /// Unsafe because the caller must ensure that the ivar is actually
    /// of type `T`.
    pub unsafe fn get_ivar<T>(&self, name: &str) -> &T where T: Encode {
        let cls = self.class();
        let ptr = match cls.instance_variable(name) {
            Some(ivar) => {
                assert!(ivar.type_encoding() == encode::<T>());
                let offset = ivar.offset();
                let self_ptr = self as *const Object;
                (self_ptr as *const u8).offset(offset) as *const T
            }
            None => panic!("Ivar {} not found on class {}", name, cls.name()),
        };
        &*ptr
    }

    /// Returns a mutable reference to the ivar of self with the given name.
    /// Panics if self has no ivar with the given name.
    /// Unsafe because the caller must ensure that the ivar is actually
    /// of type `T`.
    pub unsafe fn get_mut_ivar<T>(&mut self, name: &str) -> &mut T
            where T: Encode {
        let cls = self.class();
        let ptr = match cls.instance_variable(name) {
            Some(ivar) => {
                assert!(ivar.type_encoding() == encode::<T>());
                let offset = ivar.offset();
                let self_ptr = self as *mut Object;
                (self_ptr as *mut u8).offset(offset) as *mut T
            }
            None => panic!("Ivar {} not found on class {}", name, cls.name()),
        };
        &mut *ptr
    }

    /// Sets the value of the ivar of self with the given name.
    /// Panics if self has no ivar with the given name.
    /// Unsafe because the caller must ensure that the ivar is actually
    /// of type `T`.
    pub unsafe fn set_ivar<T>(&mut self, name: &str, value: T)
            where T: Encode {
        *self.get_mut_ivar::<T>(name) = value;
    }
}

impl fmt::Debug for Object {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "<{:?}: {:?}>", self.class(), self as *const Object)
    }
}

#[cfg(test)]
mod tests {
    use std::mem;
    use test_utils;
    use super::{Class, Sel};

    #[test]
    fn test_ivar() {
        let cls = Class::get("NSObject").unwrap();
        let ivar = cls.instance_variable("isa").unwrap();
        assert!(ivar.name() == "isa");
        assert!(ivar.type_encoding() == "#");
        assert!(ivar.offset() == 0);

        let ivars = cls.instance_variables();
        assert!(ivars.len() > 0);
    }

    #[test]
    fn test_method() {
        let cls = Class::get("NSObject").unwrap();
        let sel = Sel::register("description");
        let method = cls.instance_method(sel).unwrap();
        assert!(method.name().name() == "description");
        assert!(method.type_encoding() != "");
        assert!(method.arguments_count() == 2);
        assert!(&*method.return_type() == "@");
        assert!(&*method.argument_type(1).unwrap() == ":");

        let methods = cls.instance_methods();
        assert!(methods.len() > 0);
    }

    #[test]
    fn test_class() {
        let cls = Class::get("NSObject").unwrap();
        assert!(cls.name() == "NSObject");
        assert!(cls.instance_size() == mem::size_of::<*const Class>());
    }

    #[test]
    fn test_classes() {
        let classes_count = Class::classes_count();
        assert!(classes_count > 0);

        let classes = Class::classes();
        assert!(classes.len() == classes_count);
    }

    #[test]
    fn test_object() {
        let cls = Class::get("NSObject").unwrap();
        let obj = test_utils::sample_object();
        assert!(obj.class() == cls);
        let isa = unsafe { *obj.get_ivar("isa") };
        assert!(isa == cls as *const Class);
    }
}
<commit_msg>Runtime structs are no longer enums.<commit_after>//! A Rust interface for the functionality of the Objective-C runtime.
//!
//! For more information on foreign functions, see Apple's documentation:
//! https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html

use std::ffi::{CStr, CString};
use std::fmt;
use std::ptr;
use std::str;
use libc::{c_char, c_int, c_schar, c_uint, c_void, ptrdiff_t, size_t};
use malloc_buf::{MallocBuffer, MallocString};

use {encode, Encode};

/// The Objective-C `BOOL` type.
///
/// To convert an Objective-C `BOOL` into a Rust `bool`, compare it with `NO`.
pub type BOOL = c_schar;
/// The equivalent of true for Objective-C's `BOOL` type.
pub const YES: BOOL = 1;
/// The equivalent of false for Objective-C's `BOOL` type.
pub const NO: BOOL = 0;

/// A type that represents a method selector.
#[repr(C)]
pub struct Sel {
    ptr: *const c_void,
}

/// A marker type to be embedded into other types just so that they cannot be
/// constructed externally.
#[repr(C)]
struct PrivateMarker;

/// A type that represents an instance variable.
#[repr(C)]
pub struct Ivar {
    _priv: PrivateMarker,
}

/// A type that represents a method in a class definition.
#[repr(C)]
pub struct Method {
    _priv: PrivateMarker,
}

/// A type that represents an Objective-C class.
#[repr(C)]
pub struct Class {
    _priv: PrivateMarker,
}

/// A type that represents an instance of a class.
#[repr(C)]
pub struct Object {
    _priv: PrivateMarker,
}

/// A pointer to the start of a method implementation.
pub type Imp = extern fn(*mut Object, Sel, ...) -> *mut Object;

#[link(name = "objc", kind = "dylib")]
extern {
    pub fn sel_registerName(name: *const c_char) -> Sel;
    pub fn sel_getName(sel: Sel) -> *const c_char;

    pub fn class_getName(cls: *const Class) -> *const c_char;
    pub fn class_getInstanceSize(cls: *const Class) -> size_t;
    pub fn class_getInstanceMethod(cls: *const Class, sel: Sel) -> *const Method;
    pub fn class_getInstanceVariable(cls: *const Class, name: *const c_char) -> *const Ivar;
    pub fn class_copyMethodList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Method;
    pub fn class_copyIvarList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Ivar;
    pub fn class_addMethod(cls: *mut Class, name: Sel, imp: Imp, types: *const c_char) -> BOOL;
    pub fn class_addIvar(cls: *mut Class, name: *const c_char, size: size_t, alignment: u8, types: *const c_char) -> BOOL;

    pub fn objc_allocateClassPair(superclass: *const Class, name: *const c_char, extraBytes: size_t) -> *mut Class;
    pub fn objc_disposeClassPair(cls: *mut Class);
    pub fn objc_registerClassPair(cls: *mut Class);

    pub fn object_getClass(obj: *const Object) -> *const Class;

    pub fn objc_getClassList(buffer: *mut *const Class, bufferLen: c_int) -> c_int;
    pub fn objc_copyClassList(outCount: *mut c_uint) -> *mut *const Class;
    pub fn objc_getClass(name: *const c_char) -> *const Class;

    pub fn ivar_getName(ivar: *const Ivar) -> *const c_char;
    pub fn ivar_getOffset(ivar: *const Ivar) -> ptrdiff_t;
    pub fn ivar_getTypeEncoding(ivar: *const Ivar) -> *const c_char;

    pub fn objc_msgSend(obj: *mut Object, op: Sel, ...) -> *mut Object;
    pub fn objc_msgSend_stret(obj: *mut Object, op: Sel, ...);

    pub fn method_getName(method: *const Method) -> Sel;
    pub fn method_getImplementation(method: *const Method) -> Imp;
    pub fn method_getTypeEncoding(method: *const Method) -> *const c_char;
    pub fn method_copyReturnType(method: *const Method) -> *mut c_char;
    pub fn method_copyArgumentType(method: *const Method, index: c_uint) -> *mut c_char;
    pub fn method_getNumberOfArguments(method: *const Method) -> c_uint;
    pub fn method_setImplementation(method: *mut Method, imp: Imp) -> Imp;
    pub fn method_exchangeImplementations(m1: *mut Method, m2: *mut Method);
}

impl Sel {
    /// Registers a method with the Objective-C runtime system,
    /// maps the method name to a selector, and returns the selector value.
    pub fn register(name: &str) -> Sel {
        let name = CString::new(name).unwrap();
        unsafe {
            sel_registerName(name.as_ptr())
        }
    }

    /// Returns the name of the method specified by self.
    pub fn name(&self) -> &str {
        let name = unsafe {
            CStr::from_ptr(sel_getName(*self))
        };
        str::from_utf8(name.to_bytes()).unwrap()
    }
}

impl PartialEq for Sel {
    fn eq(&self, other: &Sel) -> bool {
        self.ptr == other.ptr
    }
}

impl Eq for Sel { }

impl Copy for Sel { }

impl Clone for Sel {
    fn clone(&self) -> Sel { *self }
}

impl fmt::Debug for Sel {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.name())
    }
}

impl Ivar {
    /// Returns the name of self.
    pub fn name(&self) -> &str {
        let name = unsafe {
            CStr::from_ptr(ivar_getName(self))
        };
        str::from_utf8(name.to_bytes()).unwrap()
    }

    /// Returns the offset of self.
    pub fn offset(&self) -> isize {
        let offset = unsafe {
            ivar_getOffset(self)
        };
        offset as isize
    }

    /// Returns the type string of self.
    pub fn type_encoding(&self) -> &str {
        let encoding = unsafe {
            CStr::from_ptr(ivar_getTypeEncoding(self))
        };
        str::from_utf8(encoding.to_bytes()).unwrap()
    }
}

impl Method {
    /// Returns the name of self.
    pub fn name(&self) -> Sel {
        unsafe {
            method_getName(self)
        }
    }

    /// Returns a string describing self's parameter and return types.
    pub fn type_encoding(&self) -> &str {
        let encoding = unsafe {
            CStr::from_ptr(method_getTypeEncoding(self))
        };
        str::from_utf8(encoding.to_bytes()).unwrap()
    }

    /// Returns a string describing self's return type.
    pub fn return_type(&self) -> MallocString {
        unsafe {
            let encoding = method_copyReturnType(self);
            MallocString::new(encoding).unwrap()
        }
    }

    /// Returns a string describing a single parameter type of self, or
    /// `None` if self has no parameter at the given index.
    pub fn argument_type(&self, index: usize) -> Option<MallocString> {
        unsafe {
            let encoding = method_copyArgumentType(self, index as c_uint);
            if encoding.is_null() {
                None
            } else {
                Some(MallocString::new(encoding).unwrap())
            }
        }
    }

    /// Returns the number of arguments accepted by self.
    pub fn arguments_count(&self) -> usize {
        unsafe {
            method_getNumberOfArguments(self) as usize
        }
    }

    /// Returns the implementation of self.
    pub fn implementation(&self) -> Imp {
        unsafe {
            method_getImplementation(self)
        }
    }

    /// Sets the implementation of self.
    /// Unsafe because the caller must ensure the implementation has the
    /// correct self, return, and argument types for the method.
    pub unsafe fn set_implementation(&mut self, imp: Imp) -> Imp {
        method_setImplementation(self, imp)
    }

    /// Exchanges the implementations of self with another `Method`.
    /// Unsafe because the caller must ensure the two methods have the same
    /// self, return, and argument types.
    pub unsafe fn exchange_implementation(&mut self, other: &mut Method) {
        method_exchangeImplementations(self, other);
    }
}

impl Class {
    /// Returns the class definition of a specified class, or `None` if the
    /// class is not registered with the Objective-C runtime.
    pub fn get(name: &str) -> Option<&'static Class> {
        let name = CString::new(name).unwrap();
        unsafe {
            objc_getClass(name.as_ptr()).as_ref()
        }
    }

    /// Obtains the list of registered class definitions.
    pub fn classes() -> MallocBuffer<&'static Class> {
        unsafe {
            let mut count: c_uint = 0;
            let classes = objc_copyClassList(&mut count);
            MallocBuffer::new(classes as *mut _, count as usize).unwrap()
        }
    }

    /// Returns the total number of registered classes.
    pub fn classes_count() -> usize {
        unsafe {
            objc_getClassList(ptr::null_mut(), 0) as usize
        }
    }

    /// Returns the name of self.
    pub fn name(&self) -> &str {
        let name = unsafe {
            CStr::from_ptr(class_getName(self))
        };
        str::from_utf8(name.to_bytes()).unwrap()
    }

    /// Returns the size of instances of self.
    pub fn instance_size(&self) -> usize {
        unsafe {
            class_getInstanceSize(self) as usize
        }
    }

    /// Returns a specified instance method for self, or `None` if self and
    /// its superclasses do not contain an instance method with the
    /// specified selector.
    pub fn instance_method(&self, sel: Sel) -> Option<&Method> {
        unsafe {
            class_getInstanceMethod(self, sel).as_ref()
        }
    }

    /// Returns the ivar for a specified instance variable of self, or `None`
    /// if self has no ivar with the given name.
    pub fn instance_variable(&self, name: &str) -> Option<&Ivar> {
        let name = CString::new(name).unwrap();
        unsafe {
            class_getInstanceVariable(self, name.as_ptr()).as_ref()
        }
    }

    /// Describes the instance methods implemented by self.
    pub fn instance_methods(&self) -> MallocBuffer<&Method> {
        unsafe {
            let mut count: c_uint = 0;
            let methods = class_copyMethodList(self, &mut count);
            MallocBuffer::new(methods as *mut _, count as usize).unwrap()
        }

    }

    /// Describes the instance variables declared by self.
    pub fn instance_variables(&self) -> MallocBuffer<&Ivar> {
        unsafe {
            let mut count: c_uint = 0;
            let ivars = class_copyIvarList(self, &mut count);
            MallocBuffer::new(ivars as *mut _, count as usize).unwrap()
        }
    }
}

impl PartialEq for Class {
    fn eq(&self, other: &Class) -> bool {
        self as *const Class == other as *const Class
    }
}

impl Eq for Class { }

impl fmt::Debug for Class {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.name())
    }
}

impl Object {
    /// Returns the class of self.
    pub fn class(&self) -> &'static Class {
        unsafe {
            &*object_getClass(self)
        }
    }

    /// Returns a reference to the ivar of self with the given name.
    /// Panics if self has no ivar with the given name.
    /// Unsafe because the caller must ensure that the ivar is actually
    /// of type `T`.
    pub unsafe fn get_ivar<T>(&self, name: &str) -> &T where T: Encode {
        let cls = self.class();
        let ptr = match cls.instance_variable(name) {
            Some(ivar) => {
                assert!(ivar.type_encoding() == encode::<T>());
                let offset = ivar.offset();
                let self_ptr = self as *const Object;
                (self_ptr as *const u8).offset(offset) as *const T
            }
            None => panic!("Ivar {} not found on class {}", name, cls.name()),
        };
        &*ptr
    }

    /// Returns a mutable reference to the ivar of self with the given name.
    /// Panics if self has no ivar with the given name.
    /// Unsafe because the caller must ensure that the ivar is actually
    /// of type `T`.
    pub unsafe fn get_mut_ivar<T>(&mut self, name: &str) -> &mut T
            where T: Encode {
        let cls = self.class();
        let ptr = match cls.instance_variable(name) {
            Some(ivar) => {
                assert!(ivar.type_encoding() == encode::<T>());
                let offset = ivar.offset();
                let self_ptr = self as *mut Object;
                (self_ptr as *mut u8).offset(offset) as *mut T
            }
            None => panic!("Ivar {} not found on class {}", name, cls.name()),
        };
        &mut *ptr
    }

    /// Sets the value of the ivar of self with the given name.
    /// Panics if self has no ivar with the given name.
    /// Unsafe because the caller must ensure that the ivar is actually
    /// of type `T`.
    pub unsafe fn set_ivar<T>(&mut self, name: &str, value: T)
            where T: Encode {
        *self.get_mut_ivar::<T>(name) = value;
    }
}

impl fmt::Debug for Object {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "<{:?}: {:?}>", self.class(), self as *const Object)
    }
}

#[cfg(test)]
mod tests {
    use std::mem;
    use test_utils;
    use super::{Class, Sel};

    #[test]
    fn test_ivar() {
        let cls = Class::get("NSObject").unwrap();
        let ivar = cls.instance_variable("isa").unwrap();
        assert!(ivar.name() == "isa");
        assert!(ivar.type_encoding() == "#");
        assert!(ivar.offset() == 0);

        let ivars = cls.instance_variables();
        assert!(ivars.len() > 0);
    }

    #[test]
    fn test_method() {
        let cls = Class::get("NSObject").unwrap();
        let sel = Sel::register("description");
        let method = cls.instance_method(sel).unwrap();
        assert!(method.name().name() == "description");
        assert!(method.type_encoding() != "");
        assert!(method.arguments_count() == 2);
        assert!(&*method.return_type() == "@");
        assert!(&*method.argument_type(1).unwrap() == ":");

        let methods = cls.instance_methods();
        assert!(methods.len() > 0);
    }

    #[test]
    fn test_class() {
        let cls = Class::get("NSObject").unwrap();
        assert!(cls.name() == "NSObject");
        assert!(cls.instance_size() == mem::size_of::<*const Class>());
    }

    #[test]
    fn test_classes() {
        let classes_count = Class::classes_count();
        assert!(classes_count > 0);

        let classes = Class::classes();
        assert!(classes.len() == classes_count);
    }

    #[test]
    fn test_object() {
        let cls = Class::get("NSObject").unwrap();
        let obj = test_utils::sample_object();
        assert!(obj.class() == cls);
        let isa = unsafe { *obj.get_ivar("isa") };
        assert!(isa == cls as *const Class);
    }
}
<|endoftext|>


<commit_before>// This file is part of rss.
//
// Copyright © 2015-2017 The rust-syndication Developers
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the MIT License and/or Apache 2.0 License.

use std::collections::HashSet;
use std::error::Error as StdError;
use std::fmt;
use std::num::ParseIntError;

use chrono::DateTime;
use chrono::ParseError as DateParseError;
use mime::Mime;
use mime::FromStrError as MimeParseError;
use url::ParseError as UrlParseError;
use url::Url;

use {Category, Channel, Cloud, Enclosure, Image, Item, Source, TextInput};

#[derive(Debug)]
/// Errors that occur during validation.
pub enum ValidationError {
    /// An error while parsing a string to a date.
    DateParsing(DateParseError),
    /// An error while parsing a string to an integer.
    IntParsing(ParseIntError),
    /// An error while parsing a string to a URL.
    UrlParsing(UrlParseError),
    /// An error while parsing a string to a MIME type.
    MimeParsing(MimeParseError),
    /// A different validation error.
    Validation(String),
}

impl StdError for ValidationError {
    fn description(&self) -> &str {
        match *self {
            ValidationError::DateParsing(ref err) => err.description(),
            ValidationError::IntParsing(ref err) => err.description(),
            ValidationError::UrlParsing(ref err) => err.description(),
            ValidationError::MimeParsing(_) => "Unable to parse MIME type",
            ValidationError::Validation(ref s) => s.as_str(),
        }
    }

    fn cause(&self) -> Option<&StdError> {
        match *self {
            ValidationError::DateParsing(ref err) => Some(err),
            ValidationError::IntParsing(ref err) => Some(err),
            ValidationError::UrlParsing(ref err) => Some(err),
            _ => None,
        }
    }
}

impl fmt::Display for ValidationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ValidationError::DateParsing(ref err) => err.fmt(f),
            ValidationError::IntParsing(ref err) => err.fmt(f),
            ValidationError::UrlParsing(ref err) => err.fmt(f),
            ValidationError::MimeParsing(_) => write!(f, "Unable to parse MIME type"),
            ValidationError::Validation(ref s) => write!(f, "{}", s),
        }
    }
}

impl From<DateParseError> for ValidationError {
    fn from(err: DateParseError) -> Self {
        ValidationError::DateParsing(err)
    }
}

impl From<ParseIntError> for ValidationError {
    fn from(err: ParseIntError) -> Self {
        ValidationError::IntParsing(err)
    }
}

impl From<UrlParseError> for ValidationError {
    fn from(err: UrlParseError) -> Self {
        ValidationError::UrlParsing(err)
    }
}

impl From<MimeParseError> for ValidationError {
    fn from(err: MimeParseError) -> Self {
        ValidationError::MimeParsing(err)
    }
}

/// A trait to support data validation.
pub trait Validate {
    /// Validate the data against the RSS specification.
    fn validate(&self) -> Result<(), ValidationError>;
}

macro_rules! validate {
    ($e:expr, $msg:expr) => ({
        if !($e) {
            return Err(ValidationError::Validation(String::from($msg)));
        }
    })
}

impl Validate for Channel {
    fn validate(&self) -> Result<(), ValidationError> {
        Url::parse(self.link())?;

        for category in self.categories() {
            category.validate()?;
        }

        if let Some(cloud) = self.cloud() {
            cloud.validate()?;
        }

        if let Some(docs) = self.docs() {
            Url::parse(docs)?;
        }

        if let Some(image) = self.image() {
            image.validate()?;
        }

        for item in self.items() {
            item.validate()?;
        }

        if let Some(last_build_date) = self.last_build_date() {
            DateTime::parse_from_rfc2822(last_build_date)?;
        }

        if let Some(pub_date) = self.pub_date() {
            DateTime::parse_from_rfc2822(pub_date)?;
        }

        for hour in self.skip_hours() {
            let hour = hour.parse::<i64>()?;
            validate!(
                hour >= 0 && hour <= 23,
                "Channel skip hour is not between 0 and 23"
            );
        }

        let valid_days = {
            let mut set = HashSet::with_capacity(7);
            set.insert("Monday");
            set.insert("Tuesday");
            set.insert("Wednesday");
            set.insert("Thursday");
            set.insert("Friday");
            set.insert("Saturday");
            set.insert("Sunday");
            set
        };

        for day in self.skip_days() {
            validate!(
                valid_days.contains(day.as_str()),
                format!("Unknown skip day: {}", day)
            );
        }

        if let Some(text_input) = self.text_input() {
            text_input.validate()?;
        }

        if let Some(ttl) = self.ttl() {
            let ttl = ttl.parse::<i64>()?;
            validate!(ttl > 0, "Channel TTL is not greather than 0");
        }

        Ok(())
    }
}

impl Validate for Category {
    fn validate(&self) -> Result<(), ValidationError> {
        if let Some(domain) = self.domain() {
            Url::parse(domain)?;
        }
        Ok(())
    }
}

impl Validate for Cloud {
    fn validate(&self) -> Result<(), ValidationError> {
        let port = self.port().parse::<i64>()?;
        validate!(port > 0, "Cloud port must be greather than 0");
        Url::parse(self.domain())?;
        validate!(
            self.protocol() == "xml-rpc" || self.protocol() == "soap" || self.protocol() == "http-post",
            format!("Unknown cloud protocol: {}", self.protocol())
        );
        Ok(())
    }
}

impl Validate for Enclosure {
    fn validate(&self) -> Result<(), ValidationError> {
        Url::parse(self.url())?;
        self.mime_type().parse::<Mime>()?;
        let length = self.length().parse::<i64>()?;
        validate!(length > 0, "Enclosure length is not greather than 0");
        Ok(())
    }
}

impl Validate for TextInput {
    fn validate(&self) -> Result<(), ValidationError> {
        Url::parse(self.link())?;
        Ok(())
    }
}

impl Validate for Image {
    fn validate(&self) -> Result<(), ValidationError> {
        Url::parse(self.link())?;
        Url::parse(self.url())?;

        if let Some(width) = self.width() {
            let width = width.parse::<i64>()?;
            validate!(
                width >= 0 && width <= 144,
                "Image width is not between 0 and 144"
            );
        }

        if let Some(height) = self.height() {
            let height = height.parse::<i64>()?;
            validate!(
                height >= 0 && height <= 144,
                "Image height is not between 0 and 144"
            );
        }

        Ok(())
    }
}

impl Validate for Item {
    fn validate(&self) -> Result<(), ValidationError> {
        if let Some(link) = self.link() {
            Url::parse(link)?;
        }

        if let Some(comments) = self.comments() {
            Url::parse(comments)?;
        }

        if let Some(enclosure) = self.enclosure() {
            enclosure.validate()?;
        }

        if let Some(pub_date) = self.pub_date() {
            DateTime::parse_from_rfc2822(pub_date)?;
        }

        if let Some(source) = self.source() {
            source.validate()?;
        }

        Ok(())
    }
}

impl Validate for Source {
    fn validate(&self) -> Result<(), ValidationError> {
        Url::parse(self.url())?;
        Ok(())
    }
}
<commit_msg>Switch up some code to make RustFmt happier.<commit_after>// This file is part of rss.
//
// Copyright © 2015-2017 The rust-syndication Developers
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the MIT License and/or Apache 2.0 License.

use std::collections::HashSet;
use std::error::Error as StdError;
use std::fmt;
use std::num::ParseIntError;

use chrono::DateTime;
use chrono::ParseError as DateParseError;
use mime::Mime;
use mime::FromStrError as MimeParseError;
use url::ParseError as UrlParseError;
use url::Url;

use {Category, Channel, Cloud, Enclosure, Image, Item, Source, TextInput};

#[derive(Debug)]
/// Errors that occur during validation.
pub enum ValidationError {
    /// An error while parsing a string to a date.
    DateParsing(DateParseError),
    /// An error while parsing a string to an integer.
    IntParsing(ParseIntError),
    /// An error while parsing a string to a URL.
    UrlParsing(UrlParseError),
    /// An error while parsing a string to a MIME type.
    MimeParsing(MimeParseError),
    /// A different validation error.
    Validation(String),
}

impl StdError for ValidationError {
    fn description(&self) -> &str {
        match *self {
            ValidationError::DateParsing(ref err) => err.description(),
            ValidationError::IntParsing(ref err) => err.description(),
            ValidationError::UrlParsing(ref err) => err.description(),
            ValidationError::MimeParsing(_) => "Unable to parse MIME type",
            ValidationError::Validation(ref s) => s.as_str(),
        }
    }

    fn cause(&self) -> Option<&StdError> {
        match *self {
            ValidationError::DateParsing(ref err) => Some(err),
            ValidationError::IntParsing(ref err) => Some(err),
            ValidationError::UrlParsing(ref err) => Some(err),
            _ => None,
        }
    }
}

impl fmt::Display for ValidationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ValidationError::DateParsing(ref err) => err.fmt(f),
            ValidationError::IntParsing(ref err) => err.fmt(f),
            ValidationError::UrlParsing(ref err) => err.fmt(f),
            ValidationError::MimeParsing(_) => write!(f, "Unable to parse MIME type"),
            ValidationError::Validation(ref s) => write!(f, "{}", s),
        }
    }
}

impl From<DateParseError> for ValidationError {
    fn from(err: DateParseError) -> Self {
        ValidationError::DateParsing(err)
    }
}

impl From<ParseIntError> for ValidationError {
    fn from(err: ParseIntError) -> Self {
        ValidationError::IntParsing(err)
    }
}

impl From<UrlParseError> for ValidationError {
    fn from(err: UrlParseError) -> Self {
        ValidationError::UrlParsing(err)
    }
}

impl From<MimeParseError> for ValidationError {
    fn from(err: MimeParseError) -> Self {
        ValidationError::MimeParsing(err)
    }
}

/// A trait to support data validation.
pub trait Validate {
    /// Validate the data against the RSS specification.
    fn validate(&self) -> Result<(), ValidationError>;
}

macro_rules! validate {
    ($e:expr, $msg:expr) => ({
        if !($e) {
            return Err(ValidationError::Validation(String::from($msg)));
        }
    })
}

impl Validate for Channel {
    fn validate(&self) -> Result<(), ValidationError> {
        Url::parse(self.link())?;

        for category in self.categories() {
            category.validate()?;
        }

        if let Some(cloud) = self.cloud() {
            cloud.validate()?;
        }

        if let Some(docs) = self.docs() {
            Url::parse(docs)?;
        }

        if let Some(image) = self.image() {
            image.validate()?;
        }

        for item in self.items() {
            item.validate()?;
        }

        if let Some(last_build_date) = self.last_build_date() {
            DateTime::parse_from_rfc2822(last_build_date)?;
        }

        if let Some(pub_date) = self.pub_date() {
            DateTime::parse_from_rfc2822(pub_date)?;
        }

        for hour in self.skip_hours() {
            let hour = hour.parse::<i64>()?;
            validate!(
                hour >= 0 && hour <= 23,
                "Channel skip hour is not between 0 and 23"
            );
        }

        let valid_days = {
            let mut set = HashSet::with_capacity(7);
            set.insert("Monday");
            set.insert("Tuesday");
            set.insert("Wednesday");
            set.insert("Thursday");
            set.insert("Friday");
            set.insert("Saturday");
            set.insert("Sunday");
            set
        };

        for day in self.skip_days() {
            validate!(
                valid_days.contains(day.as_str()),
                format!("Unknown skip day: {}", day)
            );
        }

        if let Some(text_input) = self.text_input() {
            text_input.validate()?;
        }

        if let Some(ttl) = self.ttl() {
            let ttl = ttl.parse::<i64>()?;
            validate!(ttl > 0, "Channel TTL is not greather than 0");
        }

        Ok(())
    }
}

impl Validate for Category {
    fn validate(&self) -> Result<(), ValidationError> {
        if let Some(domain) = self.domain() {
            Url::parse(domain)?;
        }
        Ok(())
    }
}

impl Validate for Cloud {
    fn validate(&self) -> Result<(), ValidationError> {
        let port = self.port().parse::<i64>()?;
        validate!(port > 0, "Cloud port must be greather than 0");
        Url::parse(self.domain())?;
        validate!(
            vec!["xml-rpc", "soap", "http-post"].contains(self.protocol()),
            format!("Unknown cloud protocol: {}", self.protocol())
        );
        Ok(())
    }
}

impl Validate for Enclosure {
    fn validate(&self) -> Result<(), ValidationError> {
        Url::parse(self.url())?;
        self.mime_type().parse::<Mime>()?;
        let length = self.length().parse::<i64>()?;
        validate!(length > 0, "Enclosure length is not greather than 0");
        Ok(())
    }
}

impl Validate for TextInput {
    fn validate(&self) -> Result<(), ValidationError> {
        Url::parse(self.link())?;
        Ok(())
    }
}

impl Validate for Image {
    fn validate(&self) -> Result<(), ValidationError> {
        Url::parse(self.link())?;
        Url::parse(self.url())?;

        if let Some(width) = self.width() {
            let width = width.parse::<i64>()?;
            validate!(
                width >= 0 && width <= 144,
                "Image width is not between 0 and 144"
            );
        }

        if let Some(height) = self.height() {
            let height = height.parse::<i64>()?;
            validate!(
                height >= 0 && height <= 144,
                "Image height is not between 0 and 144"
            );
        }

        Ok(())
    }
}

impl Validate for Item {
    fn validate(&self) -> Result<(), ValidationError> {
        if let Some(link) = self.link() {
            Url::parse(link)?;
        }

        if let Some(comments) = self.comments() {
            Url::parse(comments)?;
        }

        if let Some(enclosure) = self.enclosure() {
            enclosure.validate()?;
        }

        if let Some(pub_date) = self.pub_date() {
            DateTime::parse_from_rfc2822(pub_date)?;
        }

        if let Some(source) = self.source() {
            source.validate()?;
        }

        Ok(())
    }
}

impl Validate for Source {
    fn validate(&self) -> Result<(), ValidationError> {
        Url::parse(self.url())?;
        Ok(())
    }
}
<|endoftext|>


<commit_before>use super::frame::Closure;
use super::value::Value;

pub fn build_prelude() -> Closure {
    let closure = Closure::new_builtins();

    let functions = vec![("println".to_owned(), prelude_println)];

    for (name, function) in functions.into_iter() {
        closure.set_directly(name, Value::make_native_function(function));
    }

    closure
}

fn prelude_println(arguments: Vec<Value>) -> Value {
    if let Some(argument) = arguments.first() {
        match argument {
            Value::Boolean(value) => println!("{:?}", value),
            Value::Function(_) => println!("Function"),
            Value::Integer(value) => println!("{}", value),
            Value::Null => println!("null"),
            _ => unreachable!(),
        }
    };
    Value::Null
}
<commit_msg>Improve `println` builtin<commit_after>use super::frame::Closure;
use super::value::Value;

pub fn build_prelude() -> Closure {
    let closure = Closure::new_builtins();

    let functions = vec![("println".to_owned(), prelude_println)];

    for (name, function) in functions.into_iter() {
        closure.set_directly(name, Value::make_native_function(function));
    }

    closure
}

fn prelude_println(arguments: Vec<Value>) -> Value {
    if let Some(argument) = arguments.first() {
        match argument {
            Value::Boolean(value) => println!("{:?}", value),
            Value::Function(function) => println!(
                "Function({})",
                function.call_target.function.qualified_name()
            ),
            Value::Integer(value) => println!("{}", value),
            Value::Module(module) => println!("Module({})", module.name()),
            Value::Null => println!("null"),
            _ => unreachable!(),
        }
    };
    Value::Null
}
<|endoftext|>


<commit_before>//! Source code browser

use std::collections::BTreeMap;
use std::cmp::Ordering;
use super::MetaData;
use super::page::Page;
use super::pool::Pool;
use super::file::File as DbFile;
use iron::prelude::*;
use router::Router;
use rustc_serialize::json::{Json, ToJson};
use postgres::Connection;

#[derive(PartialEq, PartialOrd)]
enum FileType {
    Dir,
    Text,
    Binary,
    RustSource,
}

#[derive(PartialEq, PartialOrd)]
struct File {
    name: String,
    file_type: FileType,
}

struct FileList {
    metadata: MetaData,
    files: Vec<File>,
}

impl ToJson for FileList {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();

        m.insert("metadata".to_string(), self.metadata.to_json());

        let mut file_vec: Vec<Json> = Vec::new();

        for file in &self.files {
            let mut file_m: BTreeMap<String, Json> = BTreeMap::new();
            file_m.insert("name".to_string(), file.name.to_json());

            file_m.insert(match file.file_type {
                              FileType::Dir => "file_type_dir".to_string(),
                              FileType::Text => "file_type_text".to_string(),
                              FileType::Binary => "file_type_binary".to_string(),
                              FileType::RustSource => "file_type_rust_source".to_string(),
                          },
                          true.to_json());

            file_vec.push(file_m.to_json());
        }

        m.insert("files".to_string(), file_vec.to_json());
        m.to_json()
    }
}

impl FileList {
    /// Gets FileList from a request path
    ///
    /// All paths stored in database have this format:
    ///
    /// ```text
    /// [
    ///   ["text/plain",".gitignore"],
    ///   ["text/x-c","src/reseeding.rs"],
    ///   ["text/x-c","src/lib.rs"],
    ///   ["text/x-c","README.md"],
    ///   ...
    /// ]
    /// ```
    ///
    /// This function is only returning FileList for requested directory. If is empty,
    /// it will return list of files (and dirs) for root directory. req_path must be a
    /// directory or empty for root directory.
    pub fn from_path(conn: &Connection,
                     name: &str,
                     version: &str,
                     req_path: &str)
                     -> Option<FileList> {

        let rows = conn.query("SELECT crates.name,
                                      releases.version,
                                      releases.description,
                                      releases.target_name,
                                      releases.rustdoc_status,
                                      releases.files
                               FROM releases
                               LEFT OUTER JOIN crates ON crates.id = releases.crate_id
                               WHERE crates.name = $1 AND releases.version = $2",
                   &[&name, &version])
            .unwrap();

        if rows.len() == 0 {
            return None;
        }

        let files: Json = rows.get(0).get_opt(5).unwrap().ok()?;

        let mut file_list: Vec<File> = Vec::new();

        files.as_array().map(|files| {
            for file in files {
                file.as_array().map(|file| {
                    let mime = file[0].as_string().unwrap();
                    let path = file[1].as_string().unwrap();

                    // skip .cargo-ok generated by cargo
                    if path == ".cargo-ok" {
                        return;
                    }

                    // look only files for req_path
                    if path.starts_with(&req_path) {
                        // remove req_path from path to reach files in this directory
                        let path = path.replace(&req_path, "");
                        let path_splited: Vec<&str> = path.split("/").collect();

                        // if path have '/' it is a directory
                        let ftype = if path_splited.len() > 1 {
                            FileType::Dir
                        } else if mime.starts_with("text") &&
                                              path_splited[0].ends_with(".rs") {
                            FileType::RustSource
                        } else if mime.starts_with("text") {
                            FileType::Text
                        } else {
                            FileType::Binary
                        };

                        let file = File {
                            name: path_splited[0].to_owned(),
                            file_type: ftype,
                        };

                        // avoid adding duplicates, a directory may occur more than once
                        if !file_list.contains(&file) {
                            file_list.push(file);
                        }
                    }
                });
            }
        });

        if file_list.is_empty() {
            return None;
        }

        file_list.sort_by(|a, b| {
            // directories must be listed first
            if a.file_type == FileType::Dir && b.file_type != FileType::Dir {
                Ordering::Less
            } else if a.file_type != FileType::Dir && b.file_type == FileType::Dir {
                Ordering::Greater
            } else {
                a.name.to_lowercase().cmp(&b.name.to_lowercase())
            }
        });

        Some(FileList {
            metadata: MetaData {
                name: rows.get(0).get(0),
                version: rows.get(0).get(1),
                description: rows.get(0).get(2),
                target_name: rows.get(0).get(3),
                rustdoc_status: rows.get(0).get(4),
            },
            files: file_list,
        })
    }
}

pub fn source_browser_handler(req: &mut Request) -> IronResult<Response> {
    use iron::status;
    use super::error::Nope;
    let router = extension!(req, Router);
    let name = cexpect!(router.find("name"));
    let version = cexpect!(router.find("version"));

    // get path (req_path) for FileList::from_path and actual path for super::file::File::from_path
    let (req_path, file_path) = {
        let mut req_path = req.url.path();
        // remove first elements from path which is /crate/:name/:version/source
        for _ in 0..4 {
            req_path.remove(0);
        }
        let file_path = format!("sources/{}/{}/{}", name, version, req_path.join("/"));

        // FileList::from_path is only working for directories
        // remove file name if it's not a directory
        req_path.last_mut().map(|last| {
            if !last.is_empty() {
                *last = "";
            }
        });

        // remove crate name and version from req_path
        let path = req_path.join("/").replace(&format!("{}/{}/", name, version), "");

        (path, file_path)
    };

    let conn = extension!(req, Pool);

    // try to get actual file first
    // skip if request is a directory
    let file = if !file_path.ends_with("/") {
        DbFile::from_path(&conn, &file_path)
    } else {
        None
    };

    let (content, is_rust_source) = if let Some(file) = file {
        // serve the file with DatabaseFileHandler if file isn't text and not empty
        if !file.0.mime.starts_with("text") && !file.is_empty() {
            return Ok(file.serve());
        } else if file.0.mime.starts_with("text") && !file.is_empty() {
            (String::from_utf8(file.0.content).ok(), file.0.path.ends_with(".rs"))
        } else {
            (None, false)
        }
    } else {
        (None, false)
    };

    let list = FileList::from_path(&conn, &name, &version, &req_path);

    let page = Page::new(list)
        .set_bool("show_parent_link", !req_path.is_empty())
        .set_true("javascript_highlightjs")
        .set_true("show_package_navigation")
        .set_true("package_source_tab");

    if let Some(content) = content {
        page.set("file_content", &content)
            .set_bool("file_content_rust_source", is_rust_source)
            .to_resp("source")
    } else {
        Err(IronError::new(Nope::CrateNotFound, status::NotFound))
    }
}
<commit_msg>Change Nope variant to NoResults<commit_after>//! Source code browser

use std::collections::BTreeMap;
use std::cmp::Ordering;
use super::MetaData;
use super::page::Page;
use super::pool::Pool;
use super::file::File as DbFile;
use iron::prelude::*;
use router::Router;
use rustc_serialize::json::{Json, ToJson};
use postgres::Connection;

#[derive(PartialEq, PartialOrd)]
enum FileType {
    Dir,
    Text,
    Binary,
    RustSource,
}

#[derive(PartialEq, PartialOrd)]
struct File {
    name: String,
    file_type: FileType,
}

struct FileList {
    metadata: MetaData,
    files: Vec<File>,
}

impl ToJson for FileList {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();

        m.insert("metadata".to_string(), self.metadata.to_json());

        let mut file_vec: Vec<Json> = Vec::new();

        for file in &self.files {
            let mut file_m: BTreeMap<String, Json> = BTreeMap::new();
            file_m.insert("name".to_string(), file.name.to_json());

            file_m.insert(match file.file_type {
                              FileType::Dir => "file_type_dir".to_string(),
                              FileType::Text => "file_type_text".to_string(),
                              FileType::Binary => "file_type_binary".to_string(),
                              FileType::RustSource => "file_type_rust_source".to_string(),
                          },
                          true.to_json());

            file_vec.push(file_m.to_json());
        }

        m.insert("files".to_string(), file_vec.to_json());
        m.to_json()
    }
}

impl FileList {
    /// Gets FileList from a request path
    ///
    /// All paths stored in database have this format:
    ///
    /// ```text
    /// [
    ///   ["text/plain",".gitignore"],
    ///   ["text/x-c","src/reseeding.rs"],
    ///   ["text/x-c","src/lib.rs"],
    ///   ["text/x-c","README.md"],
    ///   ...
    /// ]
    /// ```
    ///
    /// This function is only returning FileList for requested directory. If is empty,
    /// it will return list of files (and dirs) for root directory. req_path must be a
    /// directory or empty for root directory.
    pub fn from_path(conn: &Connection,
                     name: &str,
                     version: &str,
                     req_path: &str)
                     -> Option<FileList> {

        let rows = conn.query("SELECT crates.name,
                                      releases.version,
                                      releases.description,
                                      releases.target_name,
                                      releases.rustdoc_status,
                                      releases.files
                               FROM releases
                               LEFT OUTER JOIN crates ON crates.id = releases.crate_id
                               WHERE crates.name = $1 AND releases.version = $2",
                   &[&name, &version])
            .unwrap();

        if rows.len() == 0 {
            return None;
        }

        let files: Json = rows.get(0).get_opt(5).unwrap().ok()?;

        let mut file_list: Vec<File> = Vec::new();

        files.as_array().map(|files| {
            for file in files {
                file.as_array().map(|file| {
                    let mime = file[0].as_string().unwrap();
                    let path = file[1].as_string().unwrap();

                    // skip .cargo-ok generated by cargo
                    if path == ".cargo-ok" {
                        return;
                    }

                    // look only files for req_path
                    if path.starts_with(&req_path) {
                        // remove req_path from path to reach files in this directory
                        let path = path.replace(&req_path, "");
                        let path_splited: Vec<&str> = path.split("/").collect();

                        // if path have '/' it is a directory
                        let ftype = if path_splited.len() > 1 {
                            FileType::Dir
                        } else if mime.starts_with("text") &&
                                              path_splited[0].ends_with(".rs") {
                            FileType::RustSource
                        } else if mime.starts_with("text") {
                            FileType::Text
                        } else {
                            FileType::Binary
                        };

                        let file = File {
                            name: path_splited[0].to_owned(),
                            file_type: ftype,
                        };

                        // avoid adding duplicates, a directory may occur more than once
                        if !file_list.contains(&file) {
                            file_list.push(file);
                        }
                    }
                });
            }
        });

        if file_list.is_empty() {
            return None;
        }

        file_list.sort_by(|a, b| {
            // directories must be listed first
            if a.file_type == FileType::Dir && b.file_type != FileType::Dir {
                Ordering::Less
            } else if a.file_type != FileType::Dir && b.file_type == FileType::Dir {
                Ordering::Greater
            } else {
                a.name.to_lowercase().cmp(&b.name.to_lowercase())
            }
        });

        Some(FileList {
            metadata: MetaData {
                name: rows.get(0).get(0),
                version: rows.get(0).get(1),
                description: rows.get(0).get(2),
                target_name: rows.get(0).get(3),
                rustdoc_status: rows.get(0).get(4),
            },
            files: file_list,
        })
    }
}

pub fn source_browser_handler(req: &mut Request) -> IronResult<Response> {
    use iron::status;
    use super::error::Nope;
    let router = extension!(req, Router);
    let name = cexpect!(router.find("name"));
    let version = cexpect!(router.find("version"));

    // get path (req_path) for FileList::from_path and actual path for super::file::File::from_path
    let (req_path, file_path) = {
        let mut req_path = req.url.path();
        // remove first elements from path which is /crate/:name/:version/source
        for _ in 0..4 {
            req_path.remove(0);
        }
        let file_path = format!("sources/{}/{}/{}", name, version, req_path.join("/"));

        // FileList::from_path is only working for directories
        // remove file name if it's not a directory
        req_path.last_mut().map(|last| {
            if !last.is_empty() {
                *last = "";
            }
        });

        // remove crate name and version from req_path
        let path = req_path.join("/").replace(&format!("{}/{}/", name, version), "");

        (path, file_path)
    };

    let conn = extension!(req, Pool);

    // try to get actual file first
    // skip if request is a directory
    let file = if !file_path.ends_with("/") {
        DbFile::from_path(&conn, &file_path)
    } else {
        None
    };

    let (content, is_rust_source) = if let Some(file) = file {
        // serve the file with DatabaseFileHandler if file isn't text and not empty
        if !file.0.mime.starts_with("text") && !file.is_empty() {
            return Ok(file.serve());
        } else if file.0.mime.starts_with("text") && !file.is_empty() {
            (String::from_utf8(file.0.content).ok(), file.0.path.ends_with(".rs"))
        } else {
            (None, false)
        }
    } else {
        (None, false)
    };

    let list = FileList::from_path(&conn, &name, &version, &req_path);

    let page = Page::new(list)
        .set_bool("show_parent_link", !req_path.is_empty())
        .set_true("javascript_highlightjs")
        .set_true("show_package_navigation")
        .set_true("package_source_tab");

    if let Some(content) = content {
        page.set("file_content", &content)
            .set_bool("file_content_rust_source", is_rust_source)
            .to_resp("source")
    } else {
        Err(IronError::new(Nope::NoResults, status::NotFound))
    }
}
<|endoftext|>