<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/. */

use dom::audionode::MAX_CHANNEL_COUNT;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::AudioBufferBinding::{self, AudioBufferMethods, AudioBufferOptions};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::window::Window;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSAutoCompartment, JSContext, JSObject};
use js::jsapi::JS_GetArrayBufferViewBuffer;
use js::rust::CustomAutoRooterGuard;
use js::rust::wrappers::JS_DetachArrayBuffer;
use js::typedarray::{CreateWith, Float32Array};
use servo_media::audio::buffer_source_node::AudioBuffer as ServoMediaAudioBuffer;
use std::cmp::min;
use std::ptr::{self, NonNull};

// This range is defined by the spec.
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer
pub const MIN_SAMPLE_RATE: f32 = 8000.;
pub const MAX_SAMPLE_RATE: f32 = 96000.;

type JSAudioChannel = Heap<*mut JSObject>;

#[dom_struct]
pub struct AudioBuffer {
    reflector_: Reflector,
    js_channels: DomRefCell<Vec<JSAudioChannel>>,
    #[ignore_malloc_size_of = "servo_media"]
    shared_channels: DomRefCell<ServoMediaAudioBuffer>,
    sample_rate: f32,
    length: u32,
    duration: f64,
    number_of_channels: u32,
}

impl AudioBuffer {
    #[allow(unrooted_must_root)]
    #[allow(unsafe_code)]
    pub fn new_inherited(number_of_channels: u32, length: u32, sample_rate: f32) -> AudioBuffer {
        let vec = (0..number_of_channels).map(|_| Heap::default()).collect();
        AudioBuffer {
            reflector_: Reflector::new(),
            js_channels: DomRefCell::new(vec),
            shared_channels: DomRefCell::new(ServoMediaAudioBuffer::new(
                number_of_channels as u8,
                length as usize,
            )),
            sample_rate,
            length,
            duration: length as f64 / sample_rate as f64,
            number_of_channels,
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(
        global: &Window,
        number_of_channels: u32,
        length: u32,
        sample_rate: f32,
        initial_data: Option<&[Vec<f32>]>,
    ) -> DomRoot<AudioBuffer> {
        let buffer = AudioBuffer::new_inherited(number_of_channels, length, sample_rate);
        let buffer = reflect_dom_object(Box::new(buffer), global, AudioBufferBinding::Wrap);
        buffer.set_channels(initial_data);
        buffer
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-audiobuffer
    pub fn Constructor(
        window: &Window,
        options: &AudioBufferOptions,
    ) -> Fallible<DomRoot<AudioBuffer>> {
        if options.length <= 0 ||
            options.numberOfChannels <= 0 ||
            options.numberOfChannels > MAX_CHANNEL_COUNT ||
            *options.sampleRate < MIN_SAMPLE_RATE ||
            *options.sampleRate > MAX_SAMPLE_RATE
        {
            return Err(Error::NotSupported);
        }
        Ok(AudioBuffer::new(
            window,
            options.numberOfChannels,
            options.length,
            *options.sampleRate,
            None,
        ))
    }

    // Initialize the underlying channels data with initial data provided by
    // the user or silence otherwise.
    #[allow(unsafe_code)]
    pub fn set_channels(&self, initial_data: Option<&[Vec<f32>]>) {
        for channel in 0..self.number_of_channels {
            (*self.shared_channels.borrow_mut()).buffers[channel as usize] = match initial_data {
                Some(data) => data[channel as usize].clone(),
                None => vec![0.; self.length as usize],
            };
        }
    }

    pub fn get_channels(&self) -> ServoMediaAudioBuffer {
        self.shared_channels.borrow().clone()
    }

    #[allow(unsafe_code)]
    unsafe fn restore_js_channel_data(&self, cx: *mut JSContext) -> bool {
        let global = self.global();
        let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            if !channel.get().is_null() {
                // Already have data in JS array.
                continue;
            }

            // Copy the channel data from shared_channels to js_channels.
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            let shared_channel = &(*self.shared_channels.borrow_mut()).buffers[i];
            if Float32Array::create(cx, CreateWith::Slice(shared_channel), array.handle_mut())
                .is_err()
            {
                return false;
            }
            channel.set(array.get());
        }

        true
    }

    // https://webaudio.github.io/web-audio-api/#acquire-the-content
    #[allow(unsafe_code)]
    pub fn acquire_contents(&self) -> Option<ServoMediaAudioBuffer> {
        let cx = self.global().get_cx();
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            // Step 1.
            if channel.get().is_null() {
                return None;
            }

            // Step 2.
            let channel_data = unsafe {
                typedarray!(in(cx) let array: Float32Array = channel.get());
                if let Ok(array) = array {
                    let data = array.to_vec();
                    let mut is_shared = false;
                    rooted!(in (cx) let view_buffer =
                        JS_GetArrayBufferViewBuffer(cx, channel.handle(), &mut is_shared));
                    // This buffer is always created unshared
                    debug_assert!(!is_shared);
                    let _ = JS_DetachArrayBuffer(cx, view_buffer.handle());
                    data
                } else {
                    return None;
                }
            };

            channel.set(ptr::null_mut());

            // Step 3.
            (*self.shared_channels.borrow_mut()).buffers[i] = channel_data;

            // Step 4 will complete turning shared_channels
            // data into js_channels ArrayBuffers in restore_js_channel_data.
        }

        Some((*self.shared_channels.borrow()).clone())
    }
}

impl AudioBufferMethods for AudioBuffer {
    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate
    fn SampleRate(&self) -> Finite<f32> {
        Finite::wrap(self.sample_rate)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length
    fn Length(&self) -> u32 {
        self.length
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration
    fn Duration(&self) -> Finite<f64> {
        Finite::wrap(self.duration)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels
    fn NumberOfChannels(&self) -> u32 {
        self.number_of_channels
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata
    #[allow(unsafe_code)]
    unsafe fn GetChannelData(
        &self,
        cx: *mut JSContext,
        channel: u32,
    ) -> Fallible<NonNull<JSObject>> {
        if channel >= self.number_of_channels {
            return Err(Error::IndexSize);
        }

        if !self.restore_js_channel_data(cx) {
            return Err(Error::JSFailed);
        }

        Ok(NonNull::new_unchecked(
            self.js_channels.borrow()[channel as usize].get(),
        ))
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel
    #[allow(unsafe_code)]
    fn CopyFromChannel(
        &self,
        mut destination: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if destination.is_shared() {
            return Err(Error::Type("Cannot copy to shared buffer".to_owned()));
        }

        if channel_number >= self.number_of_channels || start_in_channel > self.length {
            return Err(Error::IndexSize);
        }

        let bytes_to_copy = min(self.length - start_in_channel, destination.len() as u32) as usize;
        let cx = self.global().get_cx();
        let channel_number = channel_number as usize;
        let offset = start_in_channel as usize;
        let mut dest = Vec::with_capacity(destination.len());

        // We either copy form js_channels or shared_channels.

        let js_channel = self.js_channels.borrow()[channel_number].get();
        if !js_channel.is_null() {
            typedarray!(in(cx) let array: Float32Array = js_channel);
            if let Ok(array) = array {
                let data = unsafe { array.as_slice() };
                dest.extend_from_slice(&data[offset..offset + bytes_to_copy]);
                return Ok(());
            }
        }

        if let Some(shared_channel) = self.shared_channels.borrow().buffers.get(channel_number) {
            dest.extend_from_slice(&shared_channel.as_slice()[offset..offset + bytes_to_copy]);
        }

        unsafe {
            destination.update(&dest);
        }

        Ok(())
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel
    #[allow(unsafe_code)]
    fn CopyToChannel(
        &self,
        source: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if source.is_shared() {
            return Err(Error::Type("Cannot copy from shared buffer".to_owned()));
        }

        if channel_number >= self.number_of_channels || start_in_channel > (source.len() as u32) {
            return Err(Error::IndexSize);
        }

        let cx = self.global().get_cx();
        if unsafe { !self.restore_js_channel_data(cx) } {
            return Err(Error::JSFailed);
        }

        let js_channel = self.js_channels.borrow()[channel_number as usize].get();
        if js_channel.is_null() {
            // The array buffer was detached.
            return Err(Error::IndexSize);
        }

        typedarray!(in(cx) let js_channel: Float32Array = js_channel);
        if let Ok(mut js_channel) = js_channel {
            let bytes_to_copy = min(self.length - start_in_channel, source.len() as u32) as usize;
            unsafe {
                let data = &source.as_slice()[0..bytes_to_copy];
                // Update shared channel.
                let mut shared_channels = self.shared_channels.borrow_mut();
                let shared_channel = shared_channels.data_chan_mut(channel_number as u8);
                let (_, mut shared_channel) = shared_channel.split_at_mut(start_in_channel as usize);
                shared_channel[0..bytes_to_copy].copy_from_slice(data);
                // Update js channel.
                js_channel.update(data);
            }
        } else {
            return Err(Error::IndexSize);
        }

        Ok(())
    }
}
<commit_msg>Throw IndexError if start_in_channel is equal to ArrayBuffer length<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/. */

use dom::audionode::MAX_CHANNEL_COUNT;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::AudioBufferBinding::{self, AudioBufferMethods, AudioBufferOptions};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::window::Window;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSAutoCompartment, JSContext, JSObject};
use js::jsapi::JS_GetArrayBufferViewBuffer;
use js::rust::CustomAutoRooterGuard;
use js::rust::wrappers::JS_DetachArrayBuffer;
use js::typedarray::{CreateWith, Float32Array};
use servo_media::audio::buffer_source_node::AudioBuffer as ServoMediaAudioBuffer;
use std::cmp::min;
use std::ptr::{self, NonNull};

// This range is defined by the spec.
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer
pub const MIN_SAMPLE_RATE: f32 = 8000.;
pub const MAX_SAMPLE_RATE: f32 = 96000.;

type JSAudioChannel = Heap<*mut JSObject>;

#[dom_struct]
pub struct AudioBuffer {
    reflector_: Reflector,
    js_channels: DomRefCell<Vec<JSAudioChannel>>,
    #[ignore_malloc_size_of = "servo_media"]
    shared_channels: DomRefCell<ServoMediaAudioBuffer>,
    sample_rate: f32,
    length: u32,
    duration: f64,
    number_of_channels: u32,
}

impl AudioBuffer {
    #[allow(unrooted_must_root)]
    #[allow(unsafe_code)]
    pub fn new_inherited(number_of_channels: u32, length: u32, sample_rate: f32) -> AudioBuffer {
        let vec = (0..number_of_channels).map(|_| Heap::default()).collect();
        AudioBuffer {
            reflector_: Reflector::new(),
            js_channels: DomRefCell::new(vec),
            shared_channels: DomRefCell::new(ServoMediaAudioBuffer::new(
                number_of_channels as u8,
                length as usize,
            )),
            sample_rate,
            length,
            duration: length as f64 / sample_rate as f64,
            number_of_channels,
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(
        global: &Window,
        number_of_channels: u32,
        length: u32,
        sample_rate: f32,
        initial_data: Option<&[Vec<f32>]>,
    ) -> DomRoot<AudioBuffer> {
        let buffer = AudioBuffer::new_inherited(number_of_channels, length, sample_rate);
        let buffer = reflect_dom_object(Box::new(buffer), global, AudioBufferBinding::Wrap);
        buffer.set_channels(initial_data);
        buffer
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-audiobuffer
    pub fn Constructor(
        window: &Window,
        options: &AudioBufferOptions,
    ) -> Fallible<DomRoot<AudioBuffer>> {
        if options.length <= 0 ||
            options.numberOfChannels <= 0 ||
            options.numberOfChannels > MAX_CHANNEL_COUNT ||
            *options.sampleRate < MIN_SAMPLE_RATE ||
            *options.sampleRate > MAX_SAMPLE_RATE
        {
            return Err(Error::NotSupported);
        }
        Ok(AudioBuffer::new(
            window,
            options.numberOfChannels,
            options.length,
            *options.sampleRate,
            None,
        ))
    }

    // Initialize the underlying channels data with initial data provided by
    // the user or silence otherwise.
    #[allow(unsafe_code)]
    pub fn set_channels(&self, initial_data: Option<&[Vec<f32>]>) {
        for channel in 0..self.number_of_channels {
            (*self.shared_channels.borrow_mut()).buffers[channel as usize] = match initial_data {
                Some(data) => data[channel as usize].clone(),
                None => vec![0.; self.length as usize],
            };
        }
    }

    pub fn get_channels(&self) -> ServoMediaAudioBuffer {
        self.shared_channels.borrow().clone()
    }

    #[allow(unsafe_code)]
    unsafe fn restore_js_channel_data(&self, cx: *mut JSContext) -> bool {
        let global = self.global();
        let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            if !channel.get().is_null() {
                // Already have data in JS array.
                continue;
            }

            // Copy the channel data from shared_channels to js_channels.
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            let shared_channel = &(*self.shared_channels.borrow_mut()).buffers[i];
            if Float32Array::create(cx, CreateWith::Slice(shared_channel), array.handle_mut())
                .is_err()
            {
                return false;
            }
            channel.set(array.get());
        }

        true
    }

    // https://webaudio.github.io/web-audio-api/#acquire-the-content
    #[allow(unsafe_code)]
    pub fn acquire_contents(&self) -> Option<ServoMediaAudioBuffer> {
        let cx = self.global().get_cx();
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            // Step 1.
            if channel.get().is_null() {
                return None;
            }

            // Step 2.
            let channel_data = unsafe {
                typedarray!(in(cx) let array: Float32Array = channel.get());
                if let Ok(array) = array {
                    let data = array.to_vec();
                    let mut is_shared = false;
                    rooted!(in (cx) let view_buffer =
                        JS_GetArrayBufferViewBuffer(cx, channel.handle(), &mut is_shared));
                    // This buffer is always created unshared
                    debug_assert!(!is_shared);
                    let _ = JS_DetachArrayBuffer(cx, view_buffer.handle());
                    data
                } else {
                    return None;
                }
            };

            channel.set(ptr::null_mut());

            // Step 3.
            (*self.shared_channels.borrow_mut()).buffers[i] = channel_data;

            // Step 4 will complete turning shared_channels
            // data into js_channels ArrayBuffers in restore_js_channel_data.
        }

        Some((*self.shared_channels.borrow()).clone())
    }
}

impl AudioBufferMethods for AudioBuffer {
    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate
    fn SampleRate(&self) -> Finite<f32> {
        Finite::wrap(self.sample_rate)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length
    fn Length(&self) -> u32 {
        self.length
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration
    fn Duration(&self) -> Finite<f64> {
        Finite::wrap(self.duration)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels
    fn NumberOfChannels(&self) -> u32 {
        self.number_of_channels
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata
    #[allow(unsafe_code)]
    unsafe fn GetChannelData(
        &self,
        cx: *mut JSContext,
        channel: u32,
    ) -> Fallible<NonNull<JSObject>> {
        if channel >= self.number_of_channels {
            return Err(Error::IndexSize);
        }

        if !self.restore_js_channel_data(cx) {
            return Err(Error::JSFailed);
        }

        Ok(NonNull::new_unchecked(
            self.js_channels.borrow()[channel as usize].get(),
        ))
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel
    #[allow(unsafe_code)]
    fn CopyFromChannel(
        &self,
        mut destination: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if destination.is_shared() {
            return Err(Error::Type("Cannot copy to shared buffer".to_owned()));
        }

        if channel_number >= self.number_of_channels || start_in_channel >= self.length {
            return Err(Error::IndexSize);
        }

        let bytes_to_copy = min(self.length - start_in_channel, destination.len() as u32) as usize;
        let cx = self.global().get_cx();
        let channel_number = channel_number as usize;
        let offset = start_in_channel as usize;
        let mut dest = Vec::with_capacity(destination.len());

        // We either copy form js_channels or shared_channels.

        let js_channel = self.js_channels.borrow()[channel_number].get();
        if !js_channel.is_null() {
            typedarray!(in(cx) let array: Float32Array = js_channel);
            if let Ok(array) = array {
                let data = unsafe { array.as_slice() };
                dest.extend_from_slice(&data[offset..offset + bytes_to_copy]);
                return Ok(());
            }
        }

        if let Some(shared_channel) = self.shared_channels.borrow().buffers.get(channel_number) {
            dest.extend_from_slice(&shared_channel.as_slice()[offset..offset + bytes_to_copy]);
        }

        unsafe {
            destination.update(&dest);
        }

        Ok(())
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel
    #[allow(unsafe_code)]
    fn CopyToChannel(
        &self,
        source: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if source.is_shared() {
            return Err(Error::Type("Cannot copy from shared buffer".to_owned()));
        }

        if channel_number >= self.number_of_channels || start_in_channel > (source.len() as u32) {
            return Err(Error::IndexSize);
        }

        let cx = self.global().get_cx();
        if unsafe { !self.restore_js_channel_data(cx) } {
            return Err(Error::JSFailed);
        }

        let js_channel = self.js_channels.borrow()[channel_number as usize].get();
        if js_channel.is_null() {
            // The array buffer was detached.
            return Err(Error::IndexSize);
        }

        typedarray!(in(cx) let js_channel: Float32Array = js_channel);
        if let Ok(mut js_channel) = js_channel {
            let bytes_to_copy = min(self.length - start_in_channel, source.len() as u32) as usize;
            unsafe {
                let data = &source.as_slice()[0..bytes_to_copy];
                // Update shared channel.
                let mut shared_channels = self.shared_channels.borrow_mut();
                let shared_channel = shared_channels.data_chan_mut(channel_number as u8);
                let (_, mut shared_channel) = shared_channel.split_at_mut(start_in_channel as usize);
                shared_channel[0..bytes_to_copy].copy_from_slice(data);
                // Update js channel.
                js_channel.update(data);
            }
        } else {
            return Err(Error::IndexSize);
        }

        Ok(())
    }
}
<|endoftext|>


<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/. */

use dom::audionode::MAX_CHANNEL_COUNT;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::AudioBufferBinding::{self, AudioBufferMethods, AudioBufferOptions};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::window::Window;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSAutoCompartment, JSContext, JSObject};
use js::jsapi::JS_GetArrayBufferViewBuffer;
use js::rust::CustomAutoRooterGuard;
use js::rust::wrappers::JS_DetachArrayBuffer;
use js::typedarray::{CreateWith, Float32Array};
use servo_media::audio::buffer_source_node::AudioBuffer as ServoMediaAudioBuffer;
use std::cmp::min;
use std::ptr::{self, NonNull};

// This range is defined by the spec.
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer
pub const MIN_SAMPLE_RATE: f32 = 8000.;
pub const MAX_SAMPLE_RATE: f32 = 96000.;

type JSAudioChannel = Heap<*mut JSObject>;

#[dom_struct]
pub struct AudioBuffer {
    reflector_: Reflector,
    js_channels: DomRefCell<Vec<JSAudioChannel>>,
    #[ignore_malloc_size_of = "servo_media"]
    shared_channels: DomRefCell<ServoMediaAudioBuffer>,
    sample_rate: f32,
    length: u32,
    duration: f64,
    number_of_channels: u32,
}

impl AudioBuffer {
    #[allow(unrooted_must_root)]
    #[allow(unsafe_code)]
    pub fn new_inherited(number_of_channels: u32, length: u32, sample_rate: f32) -> AudioBuffer {
        let vec = (0..number_of_channels).map(|_| Heap::default()).collect();
        AudioBuffer {
            reflector_: Reflector::new(),
            js_channels: DomRefCell::new(vec),
            shared_channels: DomRefCell::new(ServoMediaAudioBuffer::new(
                number_of_channels as u8,
                length as usize,
            )),
            sample_rate,
            length,
            duration: length as f64 / sample_rate as f64,
            number_of_channels,
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(
        global: &Window,
        number_of_channels: u32,
        length: u32,
        sample_rate: f32,
        initial_data: Option<&[Vec<f32>]>,
    ) -> DomRoot<AudioBuffer> {
        let buffer = AudioBuffer::new_inherited(number_of_channels, length, sample_rate);
        let buffer = reflect_dom_object(Box::new(buffer), global, AudioBufferBinding::Wrap);
        buffer.set_channels(initial_data);
        buffer
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-audiobuffer
    pub fn Constructor(
        window: &Window,
        options: &AudioBufferOptions,
    ) -> Fallible<DomRoot<AudioBuffer>> {
        if options.numberOfChannels > MAX_CHANNEL_COUNT ||
            *options.sampleRate < MIN_SAMPLE_RATE ||
            *options.sampleRate > MAX_SAMPLE_RATE
        {
            return Err(Error::NotSupported);
        }
        Ok(AudioBuffer::new(
            window,
            options.numberOfChannels,
            options.length,
            *options.sampleRate,
            None,
        ))
    }

    #[allow(unsafe_code)]
    pub fn set_channels(&self, initial_data: Option<&[Vec<f32>]>) {
        let global = self.global();
        let cx = global.get_cx();
        let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
        let chans = self.js_channels.borrow_mut();
        for channel in 0..self.number_of_channels {
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            match initial_data {
                Some(data) => {
                    let _ = unsafe {
                        Float32Array::create(
                            cx,
                            CreateWith::Slice(data[channel as usize].as_slice()),
                            array.handle_mut(),
                        )
                    };
                },
                None => {
                    let _ = unsafe {
                        Float32Array::create(
                            cx,
                            CreateWith::Slice(&vec![0.; self.length as usize]),
                            array.handle_mut(),
                        )
                    };
                },
            }
            chans[channel as usize].set(array.get());
        }
    }

    #[allow(unsafe_code)]
    unsafe fn restore_js_channel_data(&self, cx: *mut JSContext) -> bool {
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            if !channel.get().is_null() {
                // Already have data in JS array.
                continue;
            }

            // Move the channel data from shared_channels to js_channels.
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            let shared_channel = &(*self.shared_channels.borrow_mut()).buffers[i];
            if Float32Array::create(cx, CreateWith::Slice(shared_channel), array.handle_mut())
                .is_err()
            {
                return false;
            }
            channel.set(array.get());
        }

        true
    }

    // https://webaudio.github.io/web-audio-api/#acquire-the-content
    #[allow(unsafe_code)]
    pub fn acquire_contents(&self) -> Option<ServoMediaAudioBuffer> {
        let cx = self.global().get_cx();
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            // Step 1.
            if channel.get().is_null() {
                return None;
            }

            // Step 2.
            let channel_data = unsafe {
                typedarray!(in(cx) let array: Float32Array = channel.get());
                if let Ok(array) = array {
                    let data = array.to_vec();
                    let mut is_shared = false;
                    rooted!(in (cx) let view_buffer =
                        JS_GetArrayBufferViewBuffer(cx, channel.handle(), &mut is_shared));
                    // This buffer is always created unshared
                    debug_assert!(!is_shared);
                    let _ = JS_DetachArrayBuffer(cx, view_buffer.handle());
                    data
                } else {
                    return None;
                }
            };

            channel.set(ptr::null_mut());

            // Step 3.
            (*self.shared_channels.borrow_mut()).buffers[i] = channel_data;

            // Step 4 will complete turning shared_channels
            // data into js_channels ArrayBuffers in restore_js_channel_data.
        }

        Some((*self.shared_channels.borrow()).clone())
    }
}

impl AudioBufferMethods for AudioBuffer {
    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate
    fn SampleRate(&self) -> Finite<f32> {
        Finite::wrap(self.sample_rate)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length
    fn Length(&self) -> u32 {
        self.length
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration
    fn Duration(&self) -> Finite<f64> {
        Finite::wrap(self.duration)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels
    fn NumberOfChannels(&self) -> u32 {
        self.number_of_channels
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata
    #[allow(unsafe_code)]
    unsafe fn GetChannelData(
        &self,
        cx: *mut JSContext,
        channel: u32,
    ) -> Fallible<NonNull<JSObject>> {
        if channel >= self.number_of_channels {
            return Err(Error::IndexSize);
        }

        if !self.restore_js_channel_data(cx) {
            return Err(Error::JSFailed);
        }

        Ok(NonNull::new_unchecked(
            self.js_channels.borrow()[channel as usize].get(),
        ))
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel
    #[allow(unsafe_code)]
    fn CopyFromChannel(
        &self,
        mut destination: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if destination.is_shared() {
            return Err(Error::Type("Cannot copy to shared buffer".to_owned()));
        }

        if channel_number >= self.number_of_channels || start_in_channel > self.length {
            return Err(Error::IndexSize);
        }

        let bytes_to_copy = min(self.length - start_in_channel, destination.len() as u32) as usize;
        let cx = self.global().get_cx();
        let channel_number = channel_number as usize;
        let offset = start_in_channel as usize;
        let mut dest = Vec::with_capacity(destination.len());

        // We either copy form js_channels or shared_channels.

        let js_channel = self.js_channels.borrow()[channel_number].get();
        if !js_channel.is_null() {
            typedarray!(in(cx) let array: Float32Array = js_channel);
            if let Ok(array) = array {
                let data = unsafe { array.as_slice() };
                dest.extend_from_slice(&data[offset..offset + bytes_to_copy]);
                return Ok(());
            }
        }

        if let Some(shared_channel) = self.shared_channels.borrow().buffers.get(channel_number) {
            dest.extend_from_slice(&shared_channel.as_slice()[offset..offset + bytes_to_copy]);
        }

        unsafe {
            destination.update(&dest);
        }

        Ok(())
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel
    #[allow(unsafe_code)]
    fn CopyToChannel(
        &self,
        source: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if source.is_shared() {
            return Err(Error::Type("Cannot copy from shared buffer".to_owned()));
        }

        if channel_number >= self.number_of_channels || start_in_channel > (source.len() as u32) {
            return Err(Error::IndexSize);
        }

        let cx = self.global().get_cx();
        if unsafe { !self.restore_js_channel_data(cx) } {
            return Err(Error::JSFailed);
        }

        let js_channel = self.js_channels.borrow()[channel_number as usize].get();
        if js_channel.is_null() {
            // The array buffer was detached.
            return Err(Error::IndexSize);
        }

        typedarray!(in(cx) let array: Float32Array = js_channel);
        if let Ok(mut array) = array {
            let bytes_to_copy = min(self.length - start_in_channel, source.len() as u32) as usize;
            let offset = start_in_channel as usize;
            unsafe {
                array.update(&source.as_slice()[offset..offset + bytes_to_copy]);
            }
        } else {
            return Err(Error::IndexSize);
        }

        Ok(())
    }
}
<commit_msg>Check AudioBuffer options constraints<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/. */

use dom::audionode::MAX_CHANNEL_COUNT;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::AudioBufferBinding::{self, AudioBufferMethods, AudioBufferOptions};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::window::Window;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSAutoCompartment, JSContext, JSObject};
use js::jsapi::JS_GetArrayBufferViewBuffer;
use js::rust::CustomAutoRooterGuard;
use js::rust::wrappers::JS_DetachArrayBuffer;
use js::typedarray::{CreateWith, Float32Array};
use servo_media::audio::buffer_source_node::AudioBuffer as ServoMediaAudioBuffer;
use std::cmp::min;
use std::ptr::{self, NonNull};

// This range is defined by the spec.
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer
pub const MIN_SAMPLE_RATE: f32 = 8000.;
pub const MAX_SAMPLE_RATE: f32 = 96000.;

type JSAudioChannel = Heap<*mut JSObject>;

#[dom_struct]
pub struct AudioBuffer {
    reflector_: Reflector,
    js_channels: DomRefCell<Vec<JSAudioChannel>>,
    #[ignore_malloc_size_of = "servo_media"]
    shared_channels: DomRefCell<ServoMediaAudioBuffer>,
    sample_rate: f32,
    length: u32,
    duration: f64,
    number_of_channels: u32,
}

impl AudioBuffer {
    #[allow(unrooted_must_root)]
    #[allow(unsafe_code)]
    pub fn new_inherited(number_of_channels: u32, length: u32, sample_rate: f32) -> AudioBuffer {
        let vec = (0..number_of_channels).map(|_| Heap::default()).collect();
        AudioBuffer {
            reflector_: Reflector::new(),
            js_channels: DomRefCell::new(vec),
            shared_channels: DomRefCell::new(ServoMediaAudioBuffer::new(
                number_of_channels as u8,
                length as usize,
            )),
            sample_rate,
            length,
            duration: length as f64 / sample_rate as f64,
            number_of_channels,
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(
        global: &Window,
        number_of_channels: u32,
        length: u32,
        sample_rate: f32,
        initial_data: Option<&[Vec<f32>]>,
    ) -> DomRoot<AudioBuffer> {
        let buffer = AudioBuffer::new_inherited(number_of_channels, length, sample_rate);
        let buffer = reflect_dom_object(Box::new(buffer), global, AudioBufferBinding::Wrap);
        buffer.set_channels(initial_data);
        buffer
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-audiobuffer
    pub fn Constructor(
        window: &Window,
        options: &AudioBufferOptions,
    ) -> Fallible<DomRoot<AudioBuffer>> {
        if options.length <= 0 ||
            options.numberOfChannels <= 0 ||
            options.numberOfChannels > MAX_CHANNEL_COUNT ||
            *options.sampleRate < MIN_SAMPLE_RATE ||
            *options.sampleRate > MAX_SAMPLE_RATE
        {
            return Err(Error::NotSupported);
        }
        Ok(AudioBuffer::new(
            window,
            options.numberOfChannels,
            options.length,
            *options.sampleRate,
            None,
        ))
    }

    #[allow(unsafe_code)]
    pub fn set_channels(&self, initial_data: Option<&[Vec<f32>]>) {
        let global = self.global();
        let cx = global.get_cx();
        let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
        let chans = self.js_channels.borrow_mut();
        for channel in 0..self.number_of_channels {
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            match initial_data {
                Some(data) => {
                    let _ = unsafe {
                        Float32Array::create(
                            cx,
                            CreateWith::Slice(data[channel as usize].as_slice()),
                            array.handle_mut(),
                        )
                    };
                },
                None => {
                    let _ = unsafe {
                        Float32Array::create(
                            cx,
                            CreateWith::Slice(&vec![0.; self.length as usize]),
                            array.handle_mut(),
                        )
                    };
                },
            }
            chans[channel as usize].set(array.get());
        }
    }

    #[allow(unsafe_code)]
    unsafe fn restore_js_channel_data(&self, cx: *mut JSContext) -> bool {
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            if !channel.get().is_null() {
                // Already have data in JS array.
                continue;
            }

            // Move the channel data from shared_channels to js_channels.
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            let shared_channel = &(*self.shared_channels.borrow_mut()).buffers[i];
            if Float32Array::create(cx, CreateWith::Slice(shared_channel), array.handle_mut())
                .is_err()
            {
                return false;
            }
            channel.set(array.get());
        }

        true
    }

    // https://webaudio.github.io/web-audio-api/#acquire-the-content
    #[allow(unsafe_code)]
    pub fn acquire_contents(&self) -> Option<ServoMediaAudioBuffer> {
        let cx = self.global().get_cx();
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            // Step 1.
            if channel.get().is_null() {
                return None;
            }

            // Step 2.
            let channel_data = unsafe {
                typedarray!(in(cx) let array: Float32Array = channel.get());
                if let Ok(array) = array {
                    let data = array.to_vec();
                    let mut is_shared = false;
                    rooted!(in (cx) let view_buffer =
                        JS_GetArrayBufferViewBuffer(cx, channel.handle(), &mut is_shared));
                    // This buffer is always created unshared
                    debug_assert!(!is_shared);
                    let _ = JS_DetachArrayBuffer(cx, view_buffer.handle());
                    data
                } else {
                    return None;
                }
            };

            channel.set(ptr::null_mut());

            // Step 3.
            (*self.shared_channels.borrow_mut()).buffers[i] = channel_data;

            // Step 4 will complete turning shared_channels
            // data into js_channels ArrayBuffers in restore_js_channel_data.
        }

        Some((*self.shared_channels.borrow()).clone())
    }
}

impl AudioBufferMethods for AudioBuffer {
    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate
    fn SampleRate(&self) -> Finite<f32> {
        Finite::wrap(self.sample_rate)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length
    fn Length(&self) -> u32 {
        self.length
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration
    fn Duration(&self) -> Finite<f64> {
        Finite::wrap(self.duration)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels
    fn NumberOfChannels(&self) -> u32 {
        self.number_of_channels
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata
    #[allow(unsafe_code)]
    unsafe fn GetChannelData(
        &self,
        cx: *mut JSContext,
        channel: u32,
    ) -> Fallible<NonNull<JSObject>> {
        if channel >= self.number_of_channels {
            return Err(Error::IndexSize);
        }

        if !self.restore_js_channel_data(cx) {
            return Err(Error::JSFailed);
        }

        Ok(NonNull::new_unchecked(
            self.js_channels.borrow()[channel as usize].get(),
        ))
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel
    #[allow(unsafe_code)]
    fn CopyFromChannel(
        &self,
        mut destination: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if destination.is_shared() {
            return Err(Error::Type("Cannot copy to shared buffer".to_owned()));
        }

        if channel_number >= self.number_of_channels || start_in_channel > self.length {
            return Err(Error::IndexSize);
        }

        let bytes_to_copy = min(self.length - start_in_channel, destination.len() as u32) as usize;
        let cx = self.global().get_cx();
        let channel_number = channel_number as usize;
        let offset = start_in_channel as usize;
        let mut dest = Vec::with_capacity(destination.len());

        // We either copy form js_channels or shared_channels.

        let js_channel = self.js_channels.borrow()[channel_number].get();
        if !js_channel.is_null() {
            typedarray!(in(cx) let array: Float32Array = js_channel);
            if let Ok(array) = array {
                let data = unsafe { array.as_slice() };
                dest.extend_from_slice(&data[offset..offset + bytes_to_copy]);
                return Ok(());
            }
        }

        if let Some(shared_channel) = self.shared_channels.borrow().buffers.get(channel_number) {
            dest.extend_from_slice(&shared_channel.as_slice()[offset..offset + bytes_to_copy]);
        }

        unsafe {
            destination.update(&dest);
        }

        Ok(())
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel
    #[allow(unsafe_code)]
    fn CopyToChannel(
        &self,
        source: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if source.is_shared() {
            return Err(Error::Type("Cannot copy from shared buffer".to_owned()));
        }

        if channel_number >= self.number_of_channels || start_in_channel > (source.len() as u32) {
            return Err(Error::IndexSize);
        }

        let cx = self.global().get_cx();
        if unsafe { !self.restore_js_channel_data(cx) } {
            return Err(Error::JSFailed);
        }

        let js_channel = self.js_channels.borrow()[channel_number as usize].get();
        if js_channel.is_null() {
            // The array buffer was detached.
            return Err(Error::IndexSize);
        }

        typedarray!(in(cx) let array: Float32Array = js_channel);
        if let Ok(mut array) = array {
            let bytes_to_copy = min(self.length - start_in_channel, source.len() as u32) as usize;
            let offset = start_in_channel as usize;
            unsafe {
                array.update(&source.as_slice()[offset..offset + bytes_to_copy]);
            }
        } else {
            return Err(Error::IndexSize);
        }

        Ok(())
    }
}
<|endoftext|>


<commit_before>use crate::{LateContext, LateLintPass, LintContext};
use rustc_ast as ast;
use rustc_errors::{pluralize, Applicability};
use rustc_hir as hir;
use rustc_middle::ty;
use rustc_parse_format::{ParseMode, Parser, Piece};
use rustc_span::{sym, symbol::kw, InnerSpan, Span, Symbol};

declare_lint! {
    /// The `non_fmt_panic` lint detects `panic!(..)` invocations where the first
    /// argument is not a formatting string.
    ///
    /// ### Example
    ///
    /// ```rust,no_run
    /// panic!("{}");
    /// panic!(123);
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// In Rust 2018 and earlier, `panic!(x)` directly uses `x` as the message.
    /// That means that `panic!("{}")` panics with the message `"{}"` instead
    /// of using it as a formatting string, and `panic!(123)` will panic with
    /// an `i32` as message.
    ///
    /// Rust 2021 always interprets the first argument as format string.
    NON_FMT_PANIC,
    Warn,
    "detect single-argument panic!() invocations in which the argument is not a format string",
    report_in_external_macro
}

declare_lint_pass!(NonPanicFmt => [NON_FMT_PANIC]);

impl<'tcx> LateLintPass<'tcx> for NonPanicFmt {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
        if let hir::ExprKind::Call(f, [arg]) = &expr.kind {
            if let &ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(f).kind() {
                if Some(def_id) == cx.tcx.lang_items().begin_panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_str()
                {
                    if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
                        if cx.tcx.is_diagnostic_item(sym::std_panic_2015_macro, id)
                            || cx.tcx.is_diagnostic_item(sym::core_panic_2015_macro, id)
                        {
                            check_panic(cx, f, arg);
                        }
                    }
                }
            }
        }
    }
}

fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>) {
    if let hir::ExprKind::Lit(lit) = &arg.kind {
        if let ast::LitKind::Str(sym, _) = lit.node {
            // The argument is a string literal.
            check_panic_str(cx, f, arg, &sym.as_str());
            return;
        }
    }

    // The argument is *not* a string literal.

    let (span, panic) = panic_call(cx, f);

    // Find the span of the argument to `panic!()`, before expansion in the
    // case of `panic!(some_macro!())`.
    let mut arg_span = arg.span;
    let mut arg_macro = None;
    while !span.contains(arg_span) {
        let expn = arg_span.ctxt().outer_expn_data();
        if expn.is_root() {
            break;
        }
        arg_macro = expn.macro_def_id;
        arg_span = expn.call_site;
    }

    cx.struct_span_lint(NON_FMT_PANIC, arg_span, |lint| {
        let mut l = lint.build("panic message is not a string literal");
        l.note("this is no longer accepted in Rust 2021");
        if !span.contains(arg_span) {
            // No clue where this argument is coming from.
            l.emit();
            return;
        }
        if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) {
            // A case of `panic!(format!(..))`.
            l.note("the panic!() macro supports formatting, so there's no need for the format!() macro here");
            if let Some((open, close, _)) = find_delimiters(cx, arg_span) {
                l.multipart_suggestion(
                    "remove the `format!(..)` macro call",
                    vec![
                        (arg_span.until(open.shrink_to_hi()), "".into()),
                        (close.until(arg_span.shrink_to_hi()), "".into()),
                    ],
                    Applicability::MachineApplicable,
                );
            }
        } else {
            l.span_suggestion_verbose(
                arg_span.shrink_to_lo(),
                "add a \"{}\" format string to Display the message",
                "\"{}\", ".into(),
                Applicability::MaybeIncorrect,
            );
            if panic == sym::std_panic_macro {
                if let Some((open, close, del)) = find_delimiters(cx, span) {
                    l.multipart_suggestion(
                        "or use std::panic::panic_any instead",
                        if del == '(' {
                            vec![(span.until(open), "std::panic::panic_any".into())]
                        } else {
                            vec![
                                (span.until(open.shrink_to_hi()), "std::panic::panic_any(".into()),
                                (close, ")".into()),
                            ]
                        },
                        Applicability::MachineApplicable,
                    );
                }
            }
        }
        l.emit();
    });
}

fn check_panic_str<'tcx>(
    cx: &LateContext<'tcx>,
    f: &'tcx hir::Expr<'tcx>,
    arg: &'tcx hir::Expr<'tcx>,
    fmt: &str,
) {
    if !fmt.contains(&['{', '}'][..]) {
        // No brace, no problem.
        return;
    }

    let fmt_span = arg.span.source_callsite();

    let (snippet, style) = match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) {
        Ok(snippet) => {
            // Count the number of `#`s between the `r` and `"`.
            let style = snippet.strip_prefix('r').and_then(|s| s.find('"'));
            (Some(snippet), style)
        }
        Err(_) => (None, None),
    };

    let mut fmt_parser =
        Parser::new(fmt.as_ref(), style, snippet.clone(), false, ParseMode::Format);
    let n_arguments = (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count();

    let (span, _) = panic_call(cx, f);

    if n_arguments > 0 && fmt_parser.errors.is_empty() {
        let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] {
            [] => vec![fmt_span],
            v => v.iter().map(|span| fmt_span.from_inner(*span)).collect(),
        };
        cx.struct_span_lint(NON_FMT_PANIC, arg_spans, |lint| {
            let mut l = lint.build(match n_arguments {
                1 => "panic message contains an unused formatting placeholder",
                _ => "panic message contains unused formatting placeholders",
            });
            l.note("this message is not used as a format string when given without arguments, but will be in Rust 2021");
            if span.contains(arg.span) {
                l.span_suggestion(
                    arg.span.shrink_to_hi(),
                    &format!("add the missing argument{}", pluralize!(n_arguments)),
                    ", ...".into(),
                    Applicability::HasPlaceholders,
                );
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "or add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    } else {
        let brace_spans: Option<Vec<_>> =
            snippet.filter(|s| s.starts_with('"') || s.starts_with("r#")).map(|s| {
                s.char_indices()
                    .filter(|&(_, c)| c == '{' || c == '}')
                    .map(|(i, _)| fmt_span.from_inner(InnerSpan { start: i, end: i + 1 }))
                    .collect()
            });
        let msg = match &brace_spans {
            Some(v) if v.len() == 1 => "panic message contains a brace",
            _ => "panic message contains braces",
        };
        cx.struct_span_lint(NON_FMT_PANIC, brace_spans.unwrap_or(vec![span]), |lint| {
            let mut l = lint.build(msg);
            l.note("this message is not used as a format string, but will be in Rust 2021");
            if span.contains(arg.span) {
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    }
}

/// Given the span of `some_macro!(args);`, gives the span of `(` and `)`,
/// and the type of (opening) delimiter used.
fn find_delimiters<'tcx>(cx: &LateContext<'tcx>, span: Span) -> Option<(Span, Span, char)> {
    let snippet = cx.sess().parse_sess.source_map().span_to_snippet(span).ok()?;
    let (open, open_ch) = snippet.char_indices().find(|&(_, c)| "([{".contains(c))?;
    let close = snippet.rfind(|c| ")]}".contains(c))?;
    Some((
        span.from_inner(InnerSpan {
            start: open,
            end: open + 1,
        }),
        span.from_inner(InnerSpan {
            start: close,
            end: close + 1,
        }),
        open_ch,
    ))
}

fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol) {
    let mut expn = f.span.ctxt().outer_expn_data();

    let mut panic_macro = kw::Empty;

    // Unwrap more levels of macro expansion, as panic_2015!()
    // was likely expanded from panic!() and possibly from
    // [debug_]assert!().
    for &i in
        &[sym::std_panic_macro, sym::core_panic_macro, sym::assert_macro, sym::debug_assert_macro]
    {
        let parent = expn.call_site.ctxt().outer_expn_data();
        if parent.macro_def_id.map_or(false, |id| cx.tcx.is_diagnostic_item(i, id)) {
            expn = parent;
            panic_macro = i;
        }
    }

    (expn.call_site, panic_macro)
}
<commit_msg>Formatting.<commit_after>use crate::{LateContext, LateLintPass, LintContext};
use rustc_ast as ast;
use rustc_errors::{pluralize, Applicability};
use rustc_hir as hir;
use rustc_middle::ty;
use rustc_parse_format::{ParseMode, Parser, Piece};
use rustc_span::{sym, symbol::kw, InnerSpan, Span, Symbol};

declare_lint! {
    /// The `non_fmt_panic` lint detects `panic!(..)` invocations where the first
    /// argument is not a formatting string.
    ///
    /// ### Example
    ///
    /// ```rust,no_run
    /// panic!("{}");
    /// panic!(123);
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// In Rust 2018 and earlier, `panic!(x)` directly uses `x` as the message.
    /// That means that `panic!("{}")` panics with the message `"{}"` instead
    /// of using it as a formatting string, and `panic!(123)` will panic with
    /// an `i32` as message.
    ///
    /// Rust 2021 always interprets the first argument as format string.
    NON_FMT_PANIC,
    Warn,
    "detect single-argument panic!() invocations in which the argument is not a format string",
    report_in_external_macro
}

declare_lint_pass!(NonPanicFmt => [NON_FMT_PANIC]);

impl<'tcx> LateLintPass<'tcx> for NonPanicFmt {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
        if let hir::ExprKind::Call(f, [arg]) = &expr.kind {
            if let &ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(f).kind() {
                if Some(def_id) == cx.tcx.lang_items().begin_panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_str()
                {
                    if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
                        if cx.tcx.is_diagnostic_item(sym::std_panic_2015_macro, id)
                            || cx.tcx.is_diagnostic_item(sym::core_panic_2015_macro, id)
                        {
                            check_panic(cx, f, arg);
                        }
                    }
                }
            }
        }
    }
}

fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>) {
    if let hir::ExprKind::Lit(lit) = &arg.kind {
        if let ast::LitKind::Str(sym, _) = lit.node {
            // The argument is a string literal.
            check_panic_str(cx, f, arg, &sym.as_str());
            return;
        }
    }

    // The argument is *not* a string literal.

    let (span, panic) = panic_call(cx, f);

    // Find the span of the argument to `panic!()`, before expansion in the
    // case of `panic!(some_macro!())`.
    let mut arg_span = arg.span;
    let mut arg_macro = None;
    while !span.contains(arg_span) {
        let expn = arg_span.ctxt().outer_expn_data();
        if expn.is_root() {
            break;
        }
        arg_macro = expn.macro_def_id;
        arg_span = expn.call_site;
    }

    cx.struct_span_lint(NON_FMT_PANIC, arg_span, |lint| {
        let mut l = lint.build("panic message is not a string literal");
        l.note("this is no longer accepted in Rust 2021");
        if !span.contains(arg_span) {
            // No clue where this argument is coming from.
            l.emit();
            return;
        }
        if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) {
            // A case of `panic!(format!(..))`.
            l.note("the panic!() macro supports formatting, so there's no need for the format!() macro here");
            if let Some((open, close, _)) = find_delimiters(cx, arg_span) {
                l.multipart_suggestion(
                    "remove the `format!(..)` macro call",
                    vec![
                        (arg_span.until(open.shrink_to_hi()), "".into()),
                        (close.until(arg_span.shrink_to_hi()), "".into()),
                    ],
                    Applicability::MachineApplicable,
                );
            }
        } else {
            l.span_suggestion_verbose(
                arg_span.shrink_to_lo(),
                "add a \"{}\" format string to Display the message",
                "\"{}\", ".into(),
                Applicability::MaybeIncorrect,
            );
            if panic == sym::std_panic_macro {
                if let Some((open, close, del)) = find_delimiters(cx, span) {
                    l.multipart_suggestion(
                        "or use std::panic::panic_any instead",
                        if del == '(' {
                            vec![(span.until(open), "std::panic::panic_any".into())]
                        } else {
                            vec![
                                (span.until(open.shrink_to_hi()), "std::panic::panic_any(".into()),
                                (close, ")".into()),
                            ]
                        },
                        Applicability::MachineApplicable,
                    );
                }
            }
        }
        l.emit();
    });
}

fn check_panic_str<'tcx>(
    cx: &LateContext<'tcx>,
    f: &'tcx hir::Expr<'tcx>,
    arg: &'tcx hir::Expr<'tcx>,
    fmt: &str,
) {
    if !fmt.contains(&['{', '}'][..]) {
        // No brace, no problem.
        return;
    }

    let fmt_span = arg.span.source_callsite();

    let (snippet, style) = match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) {
        Ok(snippet) => {
            // Count the number of `#`s between the `r` and `"`.
            let style = snippet.strip_prefix('r').and_then(|s| s.find('"'));
            (Some(snippet), style)
        }
        Err(_) => (None, None),
    };

    let mut fmt_parser =
        Parser::new(fmt.as_ref(), style, snippet.clone(), false, ParseMode::Format);
    let n_arguments = (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count();

    let (span, _) = panic_call(cx, f);

    if n_arguments > 0 && fmt_parser.errors.is_empty() {
        let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] {
            [] => vec![fmt_span],
            v => v.iter().map(|span| fmt_span.from_inner(*span)).collect(),
        };
        cx.struct_span_lint(NON_FMT_PANIC, arg_spans, |lint| {
            let mut l = lint.build(match n_arguments {
                1 => "panic message contains an unused formatting placeholder",
                _ => "panic message contains unused formatting placeholders",
            });
            l.note("this message is not used as a format string when given without arguments, but will be in Rust 2021");
            if span.contains(arg.span) {
                l.span_suggestion(
                    arg.span.shrink_to_hi(),
                    &format!("add the missing argument{}", pluralize!(n_arguments)),
                    ", ...".into(),
                    Applicability::HasPlaceholders,
                );
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "or add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    } else {
        let brace_spans: Option<Vec<_>> =
            snippet.filter(|s| s.starts_with('"') || s.starts_with("r#")).map(|s| {
                s.char_indices()
                    .filter(|&(_, c)| c == '{' || c == '}')
                    .map(|(i, _)| fmt_span.from_inner(InnerSpan { start: i, end: i + 1 }))
                    .collect()
            });
        let msg = match &brace_spans {
            Some(v) if v.len() == 1 => "panic message contains a brace",
            _ => "panic message contains braces",
        };
        cx.struct_span_lint(NON_FMT_PANIC, brace_spans.unwrap_or(vec![span]), |lint| {
            let mut l = lint.build(msg);
            l.note("this message is not used as a format string, but will be in Rust 2021");
            if span.contains(arg.span) {
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    }
}

/// Given the span of `some_macro!(args);`, gives the span of `(` and `)`,
/// and the type of (opening) delimiter used.
fn find_delimiters<'tcx>(cx: &LateContext<'tcx>, span: Span) -> Option<(Span, Span, char)> {
    let snippet = cx.sess().parse_sess.source_map().span_to_snippet(span).ok()?;
    let (open, open_ch) = snippet.char_indices().find(|&(_, c)| "([{".contains(c))?;
    let close = snippet.rfind(|c| ")]}".contains(c))?;
    Some((
        span.from_inner(InnerSpan { start: open, end: open + 1 }),
        span.from_inner(InnerSpan { start: close, end: close + 1 }),
        open_ch,
    ))
}

fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol) {
    let mut expn = f.span.ctxt().outer_expn_data();

    let mut panic_macro = kw::Empty;

    // Unwrap more levels of macro expansion, as panic_2015!()
    // was likely expanded from panic!() and possibly from
    // [debug_]assert!().
    for &i in
        &[sym::std_panic_macro, sym::core_panic_macro, sym::assert_macro, sym::debug_assert_macro]
    {
        let parent = expn.call_site.ctxt().outer_expn_data();
        if parent.macro_def_id.map_or(false, |id| cx.tcx.is_diagnostic_item(i, id)) {
            expn = parent;
            panic_macro = i;
        }
    }

    (expn.call_site, panic_macro)
}
<|endoftext|>


<commit_before>//! Store XML element attributes and their values.

use std::slice;
use std::str;

use markup5ever::{namespace_url, LocalName, Namespace, Prefix, QualName};
use string_cache::DefaultAtom;

use crate::util::{opt_utf8_cstr, utf8_cstr};

/// Type used to store attribute values.
///
/// Attribute values are often repeated in an SVG file, so we intern them using the
/// string_cache crate.
pub type AttributeValue = DefaultAtom;

/// Iterable wrapper for libxml2's representation of attribute/value.
///
/// See the [`new_from_xml2_attributes`] function for information.
///
/// [`new_from_xml2_attributes`]: #method.new_from_xml2_attributes
#[derive(Clone)]
pub struct Attributes(Box<[(QualName, AttributeValue)]>);

/// Iterator from `Attributes.iter`.
pub struct AttributesIter<'a>(slice::Iter<'a, (QualName, AttributeValue)>);

impl Attributes {
    #[cfg(test)]
    pub fn new() -> Attributes {
        Attributes([].into())
    }

    /// Creates an iterable `Attributes` from the C array of borrowed C strings.
    ///
    /// With libxml2's SAX parser, the caller's startElementNsSAX2Func
    /// callback gets passed a `xmlChar **` for attributes, which
    /// comes in groups of (localname/prefix/URI/value_start/value_end).
    /// In those, localname/prefix/URI are NUL-terminated strings;
    /// value_start and value_end point to the start-inclusive and
    /// end-exclusive bytes in the attribute's value.
    ///
    /// # Safety
    ///
    /// This function is unsafe because the caller must guarantee the following:
    ///
    /// * `attrs` is a valid pointer, with (n_attributes * 5) elements.
    ///
    /// * All strings are valid UTF-8.
    pub unsafe fn new_from_xml2_attributes(
        n_attributes: usize,
        attrs: *const *const libc::c_char,
    ) -> Attributes {
        let mut array = Vec::new();

        if n_attributes > 0 && !attrs.is_null() {
            for attr in slice::from_raw_parts(attrs, n_attributes * 5).chunks_exact(5) {
                let localname = attr[0];
                let prefix = attr[1];
                let uri = attr[2];
                let value_start = attr[3];
                let value_end = attr[4];

                assert!(!localname.is_null());

                let localname = utf8_cstr(localname);

                let prefix = opt_utf8_cstr(prefix);
                let uri = opt_utf8_cstr(uri);
                let qual_name = QualName::new(
                    prefix.map(Prefix::from),
                    uri.map(Namespace::from)
                        .unwrap_or_else(|| namespace_url!("")),
                    LocalName::from(localname),
                );

                if !value_start.is_null() && !value_end.is_null() {
                    assert!(value_end >= value_start);

                    // FIXME: ptr::offset_from() is nightly-only.
                    // We'll do the computation of the length by hand.
                    let start = value_start as usize;
                    let end = value_end as usize;
                    let len = end - start;

                    let value_slice = slice::from_raw_parts(value_start as *const u8, len);
                    let value_str = str::from_utf8_unchecked(value_slice);
                    let value_atom = DefaultAtom::from(value_str);

                    array.push((qual_name, value_atom));
                }
            }
        }

        Attributes(array.into())
    }

    /// Returns the number of attributes.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Creates an iterator that yields `(QualName, &'a str)` tuples.
    pub fn iter(&self) -> AttributesIter<'_> {
        AttributesIter(self.0.iter())
    }
}

impl<'a> Iterator for AttributesIter<'a> {
    type Item = (QualName, &'a str);

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|(a, v)| (a.clone(), v.as_ref()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use markup5ever::{expanded_name, local_name, namespace_url, ns};
    use std::ffi::CString;
    use std::ptr;

    #[test]
    fn empty_attributes() {
        let map = unsafe { Attributes::new_from_xml2_attributes(0, ptr::null()) };
        assert_eq!(map.len(), 0);
    }

    #[test]
    fn attributes_with_namespaces() {
        let attrs = [
            (
                CString::new("href").unwrap(),
                Some(CString::new("xlink").unwrap()),
                Some(CString::new("http://www.w3.org/1999/xlink").unwrap()),
                CString::new("1").unwrap(),
            ),
            (
                CString::new("ry").unwrap(),
                None,
                None,
                CString::new("2").unwrap(),
            ),
            (
                CString::new("d").unwrap(),
                None,
                None,
                CString::new("").unwrap(),
            ),
        ];

        let mut v: Vec<*const libc::c_char> = Vec::new();

        for (localname, prefix, uri, val) in &attrs {
            v.push(localname.as_ptr());
            v.push(
                prefix
                    .as_ref()
                    .map(|p: &CString| p.as_ptr())
                    .unwrap_or_else(|| ptr::null()),
            );
            v.push(
                uri.as_ref()
                    .map(|p: &CString| p.as_ptr())
                    .unwrap_or_else(|| ptr::null()),
            );

            let val_start = val.as_ptr();
            let val_end = unsafe { val_start.offset(val.as_bytes().len() as isize) };
            v.push(val_start); // value_start
            v.push(val_end); // value_end
        }

        let attrs = unsafe { Attributes::new_from_xml2_attributes(3, v.as_ptr()) };

        let mut had_href: bool = false;
        let mut had_ry: bool = false;
        let mut had_d: bool = false;

        for (a, v) in attrs.iter() {
            match a.expanded() {
                expanded_name!(xlink "href") => {
                    assert!(v == "1");
                    had_href = true;
                }

                expanded_name!("", "ry") => {
                    assert!(v == "2");
                    had_ry = true;
                }

                expanded_name!("", "d") => {
                    assert!(v == "");
                    had_d = true;
                }

                _ => unreachable!(),
            }
        }

        assert!(had_href);
        assert!(had_ry);
        assert!(had_d);
    }
}
<commit_msg>Assume that attributes are normally going to be valid<commit_after>//! Store XML element attributes and their values.

use std::slice;
use std::str;

use markup5ever::{namespace_url, LocalName, Namespace, Prefix, QualName};
use string_cache::DefaultAtom;

use crate::util::{opt_utf8_cstr, utf8_cstr};

/// Type used to store attribute values.
///
/// Attribute values are often repeated in an SVG file, so we intern them using the
/// string_cache crate.
pub type AttributeValue = DefaultAtom;

/// Iterable wrapper for libxml2's representation of attribute/value.
///
/// See the [`new_from_xml2_attributes`] function for information.
///
/// [`new_from_xml2_attributes`]: #method.new_from_xml2_attributes
#[derive(Clone)]
pub struct Attributes(Box<[(QualName, AttributeValue)]>);

/// Iterator from `Attributes.iter`.
pub struct AttributesIter<'a>(slice::Iter<'a, (QualName, AttributeValue)>);

impl Attributes {
    #[cfg(test)]
    pub fn new() -> Attributes {
        Attributes([].into())
    }

    /// Creates an iterable `Attributes` from the C array of borrowed C strings.
    ///
    /// With libxml2's SAX parser, the caller's startElementNsSAX2Func
    /// callback gets passed a `xmlChar **` for attributes, which
    /// comes in groups of (localname/prefix/URI/value_start/value_end).
    /// In those, localname/prefix/URI are NUL-terminated strings;
    /// value_start and value_end point to the start-inclusive and
    /// end-exclusive bytes in the attribute's value.
    ///
    /// # Safety
    ///
    /// This function is unsafe because the caller must guarantee the following:
    ///
    /// * `attrs` is a valid pointer, with (n_attributes * 5) elements.
    ///
    /// * All strings are valid UTF-8.
    pub unsafe fn new_from_xml2_attributes(
        n_attributes: usize,
        attrs: *const *const libc::c_char,
    ) -> Attributes {
        let mut array = Vec::with_capacity(n_attributes);

        if n_attributes > 0 && !attrs.is_null() {
            for attr in slice::from_raw_parts(attrs, n_attributes * 5).chunks_exact(5) {
                let localname = attr[0];
                let prefix = attr[1];
                let uri = attr[2];
                let value_start = attr[3];
                let value_end = attr[4];

                assert!(!localname.is_null());

                let localname = utf8_cstr(localname);

                let prefix = opt_utf8_cstr(prefix);
                let uri = opt_utf8_cstr(uri);
                let qual_name = QualName::new(
                    prefix.map(Prefix::from),
                    uri.map(Namespace::from)
                        .unwrap_or_else(|| namespace_url!("")),
                    LocalName::from(localname),
                );

                if !value_start.is_null() && !value_end.is_null() {
                    assert!(value_end >= value_start);

                    // FIXME: ptr::offset_from() is nightly-only.
                    // We'll do the computation of the length by hand.
                    let start = value_start as usize;
                    let end = value_end as usize;
                    let len = end - start;

                    let value_slice = slice::from_raw_parts(value_start as *const u8, len);
                    let value_str = str::from_utf8_unchecked(value_slice);
                    let value_atom = DefaultAtom::from(value_str);

                    array.push((qual_name, value_atom));
                }
            }
        }

        Attributes(array.into())
    }

    /// Returns the number of attributes.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Creates an iterator that yields `(QualName, &'a str)` tuples.
    pub fn iter(&self) -> AttributesIter<'_> {
        AttributesIter(self.0.iter())
    }
}

impl<'a> Iterator for AttributesIter<'a> {
    type Item = (QualName, &'a str);

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next().map(|(a, v)| (a.clone(), v.as_ref()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use markup5ever::{expanded_name, local_name, namespace_url, ns};
    use std::ffi::CString;
    use std::ptr;

    #[test]
    fn empty_attributes() {
        let map = unsafe { Attributes::new_from_xml2_attributes(0, ptr::null()) };
        assert_eq!(map.len(), 0);
    }

    #[test]
    fn attributes_with_namespaces() {
        let attrs = [
            (
                CString::new("href").unwrap(),
                Some(CString::new("xlink").unwrap()),
                Some(CString::new("http://www.w3.org/1999/xlink").unwrap()),
                CString::new("1").unwrap(),
            ),
            (
                CString::new("ry").unwrap(),
                None,
                None,
                CString::new("2").unwrap(),
            ),
            (
                CString::new("d").unwrap(),
                None,
                None,
                CString::new("").unwrap(),
            ),
        ];

        let mut v: Vec<*const libc::c_char> = Vec::new();

        for (localname, prefix, uri, val) in &attrs {
            v.push(localname.as_ptr());
            v.push(
                prefix
                    .as_ref()
                    .map(|p: &CString| p.as_ptr())
                    .unwrap_or_else(|| ptr::null()),
            );
            v.push(
                uri.as_ref()
                    .map(|p: &CString| p.as_ptr())
                    .unwrap_or_else(|| ptr::null()),
            );

            let val_start = val.as_ptr();
            let val_end = unsafe { val_start.offset(val.as_bytes().len() as isize) };
            v.push(val_start); // value_start
            v.push(val_end); // value_end
        }

        let attrs = unsafe { Attributes::new_from_xml2_attributes(3, v.as_ptr()) };

        let mut had_href: bool = false;
        let mut had_ry: bool = false;
        let mut had_d: bool = false;

        for (a, v) in attrs.iter() {
            match a.expanded() {
                expanded_name!(xlink "href") => {
                    assert!(v == "1");
                    had_href = true;
                }

                expanded_name!("", "ry") => {
                    assert!(v == "2");
                    had_ry = true;
                }

                expanded_name!("", "d") => {
                    assert!(v == "");
                    had_d = true;
                }

                _ => unreachable!(),
            }
        }

        assert!(had_href);
        assert!(had_ry);
        assert!(had_d);
    }
}
<|endoftext|>


<commit_before>//! Byte container optionally encoded as UTF-8.

use std::{str, fmt};
use std::str::MaybeOwned;
use std::default::Default;
use std::path::BytesContainer;

#[deriving(Clone)]
pub enum MaybeUTF8 {
    UTF8(String),
    Bytes(Vec<u8>),
}

impl MaybeUTF8 {
    pub fn new() -> MaybeUTF8 {
        UTF8(String::new())
    }

    pub fn from_str(s: String) -> MaybeUTF8 {
        UTF8(s)
    }

    pub fn from_bytes(v: Vec<u8>) -> MaybeUTF8 {
        Bytes(v)
    }

    pub fn as_bytes<'a>(&'a self) -> &'a [u8] {
        match *self {
            UTF8(ref s) => s.as_bytes(),
            Bytes(ref v) => v.as_slice(),
        }
    }

    pub fn as_str<'a>(&'a self) -> Option<&'a str> {
        match *self {
            UTF8(ref s) => Some(s.as_slice()),
            Bytes(ref v) => str::from_utf8(v.as_slice()),
        }
    }

    pub fn map_as_maybe_owned<'a>(&'a self,
                                  as_maybe_owned: |&'a [u8]| -> MaybeOwned<'a>) -> MaybeOwned<'a> {
        match *self {
            UTF8(ref s) => s.as_slice().into_maybe_owned(),
            Bytes(ref v) => as_maybe_owned(v.as_slice()),
        }
    }

    pub fn as_maybe_owned<'a>(&'a self) -> MaybeOwned<'a> {
        self.map_as_maybe_owned(str::from_utf8_lossy)
    }

    pub fn into_str(self) -> Result<String, MaybeUTF8> {
        match self {
            UTF8(s) => Ok(s),
            Bytes(v) => match String::from_utf8(v) {
                Ok(s) => Ok(s),
                Err(v) => Err(Bytes(v)),
            },
        }
    }

    pub fn map_into_str(self, into_str: |Vec<u8>| -> String) -> String {
        match self {
            UTF8(s) => s,
            Bytes(v) => into_str(v),
        }
    }

    pub fn into_str_lossy(self) -> String {
        self.map_into_str(|v| match str::from_utf8_lossy(v.as_slice()) {
            // `v` is definitely UTF-8, so do not make a copy!
            str::Slice(_) => unsafe {str::raw::from_utf8_owned(v)},
            str::Owned(s) => s,
        })
    }

    pub fn into_bytes(self) -> Vec<u8> {
        match self {
            UTF8(s) => s.into_bytes(),
            Bytes(v) => v,
        }
    }
}

impl PartialEq for MaybeUTF8 {
    fn eq(&self, other: &MaybeUTF8) -> bool {
        self.as_bytes().eq(&other.as_bytes())
    }
}

impl TotalEq for MaybeUTF8 {
}

impl PartialOrd for MaybeUTF8 {
    fn lt(&self, other: &MaybeUTF8) -> bool {
        self.as_bytes().lt(&other.as_bytes())
    }
}

impl TotalOrd for MaybeUTF8 {
    fn cmp(&self, other: &MaybeUTF8) -> Ordering {
        self.as_bytes().cmp(&other.as_bytes())
    }
}

impl BytesContainer for MaybeUTF8 {
    fn container_as_bytes<'a>(&'a self) -> &'a [u8] {
        self.as_bytes()
    }

    fn container_into_owned_bytes(self) -> Vec<u8> {
        self.into_bytes()
    }

    fn container_as_str<'a>(&'a self) -> Option<&'a str> {
        self.as_str()
    }
}

impl<T:BytesContainer> Equiv<T> for MaybeUTF8 {
    fn equiv(&self, other: &T) -> bool {
        self.as_bytes() == other.container_as_bytes()
    }
}

impl Container for MaybeUTF8 {
    fn len(&self) -> uint {
        self.as_bytes().len()
    }
}

impl Mutable for MaybeUTF8 {
    fn clear(&mut self) {
        match *self {
            UTF8(ref mut s) => s.clear(),
            Bytes(ref mut v) => v.clear(),
        }
    }
}

// a workaround for multiple `FromIterator` implementations with differing type params
trait MaybeUTF8FromIterator {
    fn maybe_utf8_from_iter<I:Iterator<Self>>(iterator: I) -> MaybeUTF8;
}

impl MaybeUTF8FromIterator for char {
    fn maybe_utf8_from_iter<I:Iterator<char>>(iterator: I) -> MaybeUTF8 {
        MaybeUTF8::from_str(FromIterator::from_iter(iterator))
    }
}

impl MaybeUTF8FromIterator for u8 {
    fn maybe_utf8_from_iter<I:Iterator<u8>>(iterator: I) -> MaybeUTF8 {
        MaybeUTF8::from_bytes(FromIterator::from_iter(iterator))
    }
}

impl<T:MaybeUTF8FromIterator> FromIterator<T> for MaybeUTF8 {
    fn from_iter<I:Iterator<T>>(iterator: I) -> MaybeUTF8 {
        MaybeUTF8FromIterator::maybe_utf8_from_iter(iterator)
    }
}

impl Default for MaybeUTF8 {
    fn default() -> MaybeUTF8 {
        MaybeUTF8::new()
    }
}

impl fmt::Show for MaybeUTF8 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write(self.as_bytes())
    }
}

<commit_msg>language changes: Total{Eq,Ord} -> {Eq,Ord}.<commit_after>//! Byte container optionally encoded as UTF-8.

use std::{str, fmt};
use std::str::MaybeOwned;
use std::default::Default;
use std::path::BytesContainer;

#[deriving(Clone)]
pub enum MaybeUTF8 {
    UTF8(String),
    Bytes(Vec<u8>),
}

impl MaybeUTF8 {
    pub fn new() -> MaybeUTF8 {
        UTF8(String::new())
    }

    pub fn from_str(s: String) -> MaybeUTF8 {
        UTF8(s)
    }

    pub fn from_bytes(v: Vec<u8>) -> MaybeUTF8 {
        Bytes(v)
    }

    pub fn as_bytes<'a>(&'a self) -> &'a [u8] {
        match *self {
            UTF8(ref s) => s.as_bytes(),
            Bytes(ref v) => v.as_slice(),
        }
    }

    pub fn as_str<'a>(&'a self) -> Option<&'a str> {
        match *self {
            UTF8(ref s) => Some(s.as_slice()),
            Bytes(ref v) => str::from_utf8(v.as_slice()),
        }
    }

    pub fn map_as_maybe_owned<'a>(&'a self,
                                  as_maybe_owned: |&'a [u8]| -> MaybeOwned<'a>) -> MaybeOwned<'a> {
        match *self {
            UTF8(ref s) => s.as_slice().into_maybe_owned(),
            Bytes(ref v) => as_maybe_owned(v.as_slice()),
        }
    }

    pub fn as_maybe_owned<'a>(&'a self) -> MaybeOwned<'a> {
        self.map_as_maybe_owned(str::from_utf8_lossy)
    }

    pub fn into_str(self) -> Result<String, MaybeUTF8> {
        match self {
            UTF8(s) => Ok(s),
            Bytes(v) => match String::from_utf8(v) {
                Ok(s) => Ok(s),
                Err(v) => Err(Bytes(v)),
            },
        }
    }

    pub fn map_into_str(self, into_str: |Vec<u8>| -> String) -> String {
        match self {
            UTF8(s) => s,
            Bytes(v) => into_str(v),
        }
    }

    pub fn into_str_lossy(self) -> String {
        self.map_into_str(|v| match str::from_utf8_lossy(v.as_slice()) {
            // `v` is definitely UTF-8, so do not make a copy!
            str::Slice(_) => unsafe {str::raw::from_utf8_owned(v)},
            str::Owned(s) => s,
        })
    }

    pub fn into_bytes(self) -> Vec<u8> {
        match self {
            UTF8(s) => s.into_bytes(),
            Bytes(v) => v,
        }
    }
}

impl PartialEq for MaybeUTF8 {
    fn eq(&self, other: &MaybeUTF8) -> bool {
        self.as_bytes().eq(&other.as_bytes())
    }
}

impl Eq for MaybeUTF8 {
}

impl PartialOrd for MaybeUTF8 {
    fn lt(&self, other: &MaybeUTF8) -> bool {
        self.as_bytes().lt(&other.as_bytes())
    }
}

impl Ord for MaybeUTF8 {
    fn cmp(&self, other: &MaybeUTF8) -> Ordering {
        self.as_bytes().cmp(&other.as_bytes())
    }
}

impl BytesContainer for MaybeUTF8 {
    fn container_as_bytes<'a>(&'a self) -> &'a [u8] {
        self.as_bytes()
    }

    fn container_into_owned_bytes(self) -> Vec<u8> {
        self.into_bytes()
    }

    fn container_as_str<'a>(&'a self) -> Option<&'a str> {
        self.as_str()
    }
}

impl<T:BytesContainer> Equiv<T> for MaybeUTF8 {
    fn equiv(&self, other: &T) -> bool {
        self.as_bytes() == other.container_as_bytes()
    }
}

impl Container for MaybeUTF8 {
    fn len(&self) -> uint {
        self.as_bytes().len()
    }
}

impl Mutable for MaybeUTF8 {
    fn clear(&mut self) {
        match *self {
            UTF8(ref mut s) => s.clear(),
            Bytes(ref mut v) => v.clear(),
        }
    }
}

// a workaround for multiple `FromIterator` implementations with differing type params
trait MaybeUTF8FromIterator {
    fn maybe_utf8_from_iter<I:Iterator<Self>>(iterator: I) -> MaybeUTF8;
}

impl MaybeUTF8FromIterator for char {
    fn maybe_utf8_from_iter<I:Iterator<char>>(iterator: I) -> MaybeUTF8 {
        MaybeUTF8::from_str(FromIterator::from_iter(iterator))
    }
}

impl MaybeUTF8FromIterator for u8 {
    fn maybe_utf8_from_iter<I:Iterator<u8>>(iterator: I) -> MaybeUTF8 {
        MaybeUTF8::from_bytes(FromIterator::from_iter(iterator))
    }
}

impl<T:MaybeUTF8FromIterator> FromIterator<T> for MaybeUTF8 {
    fn from_iter<I:Iterator<T>>(iterator: I) -> MaybeUTF8 {
        MaybeUTF8FromIterator::maybe_utf8_from_iter(iterator)
    }
}

impl Default for MaybeUTF8 {
    fn default() -> MaybeUTF8 {
        MaybeUTF8::new()
    }
}

impl fmt::Show for MaybeUTF8 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write(self.as_bytes())
    }
}

<|endoftext|>


<commit_before>use std::num::Float;
use std::f64::consts::PI;

use sdl2::event::Event;
use sdl2::mouse;
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::{Renderer, RenderDrawer};
use sdl2_ttf::Font;

const SOULLESS_GREY: Color = Color::RGB(175, 175, 175);

const PADDLE_COLOR: Color = SOULLESS_GREY;
const PADDLE_WIDTH: i32 = 12;
const PADDLE_HEIGHT: i32 = 64;
const PADDLE_WALL_PADDING: i32 = 10;

const BALL_COLOR: Color = SOULLESS_GREY;
const BALL_BREADTH: i32 = 8;
const BALL_VELOCITY: f64 = 10.0;

const SCORE_TOP_PADDING: i32 = 10;
const SCORE_COLOR: Color = SOULLESS_GREY;
const SCORE_FONT_SIZE: isize = 64;

pub struct GameController {
    window_width: i32,
    window_height: i32,

    score_font: Font,

    ball_position: (i32, i32),
    ball_angle: f64,

    player_paddle_y: i32,
    opponent_paddle_y: i32,

    player_score: i32,
    opponent_score: i32
}

impl GameController {
    pub fn new(window_width: i32, window_height: i32) -> GameController {
        let score_font_path = &Path::new("art/alterebro-pixel-font.ttf");
        let score_font = Font::from_file(score_font_path, SCORE_FONT_SIZE).unwrap();

        return GameController{
            window_width: window_width,
            window_height: window_height,
            score_font: score_font,
            ball_position: (0, 0),
            ball_angle: 0.0,
            player_paddle_y: 280,
            opponent_paddle_y: 10,
            player_score: 0,
            opponent_score: 0
        }
    }

    pub fn restart_game(&mut self) {
        self.ball_position = (
            self.window_width / 2 - BALL_BREADTH / 2,
            self.window_height / 2 - BALL_BREADTH / 2
        );
        self.ball_angle = 45.0.to_radians();
    }

    pub fn tick(&mut self, _: &Event, renderer: &Renderer, drawer: &mut RenderDrawer) {
        self.move_player_paddle();
        self.move_opponent_paddle();
        self.move_ball();
        self.draw_scores(&renderer, drawer);
        self.draw_player(drawer);
        self.draw_opponent(drawer);
        self.draw_ball(drawer);
    }

    fn move_player_paddle(&mut self) {
        let (_, _, mut y) = mouse::get_mouse_state();
        if y < 0 { y = 0; }
        else if y + PADDLE_HEIGHT > self.window_height { y = self.window_height - PADDLE_HEIGHT; }

        self.player_paddle_y = y;
    }

    fn move_opponent_paddle(&mut self) {
        let (_, y) = self.ball_position;
        self.opponent_paddle_y = y - PADDLE_HEIGHT / 2;
    }

    // TODO this shouldn't be framerate/vsync dependent.
    // Thanks StackOverflow!
    // http://stackoverflow.com/questions/5830791/find-radians-reflection-angle
    fn move_ball(&mut self) {
        let x_change = (self.ball_angle.cos() * BALL_VELOCITY) as i32;
        let y_change = (self.ball_angle.sin() * BALL_VELOCITY) as i32;

        let (mut x, mut y) = self.ball_position;
        x += x_change;
        y += y_change;

        // Wall collision
        let upper_y_limit = self.window_height - BALL_BREADTH;
        if y > upper_y_limit || y < 0 { self.ball_angle *= -1.0 }
        if y > upper_y_limit { y = upper_y_limit; }
        else if y < 0 { y = 0; }

        // Paddle collision
        // TODO ball dimensions aren't respected. Left side of ball hits on the left, right side of
        // ball hits on the right. Also need to reset ball x on impact.
        // TODO There's also a bug there the ball can be BALL_BREADTH height above the paddle but
        // still count as a miss. Any part of the ball on the paddle should probably be a save.
        let player_paddle_x = PADDLE_WALL_PADDING + PADDLE_WIDTH;
        let player_paddle_impacted = x <= player_paddle_x && y >= self.player_paddle_y && y <= self.player_paddle_y + PADDLE_HEIGHT;
        let opponent_paddle_x = self.window_width - PADDLE_WALL_PADDING - PADDLE_WIDTH - BALL_BREADTH;
        let opponent_paddle_impacted = x >= opponent_paddle_x && y >= self.opponent_paddle_y && y <= self.opponent_paddle_y + PADDLE_HEIGHT;

        // Reflect and tweak ball position.
        if player_paddle_impacted || opponent_paddle_impacted {
            self.ball_angle = 0.0 - PI - self.ball_angle;
        }
        if player_paddle_impacted { x = player_paddle_x; }
        else if opponent_paddle_impacted { x = opponent_paddle_x; }

        if x < player_paddle_x || x > opponent_paddle_x {
            if x < player_paddle_x { self.opponent_score += 1; }
            else if x > opponent_paddle_x { self.player_score += 1; }

            self.restart_game();
        } else {
            self.ball_position = (x, y);
        }
    }

    fn draw_scores(&self, r: &Renderer, d: &mut RenderDrawer) {
        let third = self.window_width / 3;
        self.draw_score(r, d, self.opponent_score, third);
        self.draw_score(r, d, self.player_score, third * 2);
    }

    fn draw_score(&self, r: &Renderer, d: &mut RenderDrawer, score: i32, x: i32) {
        let surface = self.score_font.render_str_blended(&score.to_string(), SCORE_COLOR).unwrap();
        let t = r.create_texture_from_surface(&surface).unwrap();
        let q = t.query();
        let b = Rect::new(x - q.width / 2, SCORE_TOP_PADDING, q.width, q.height);
        d.copy(&t, None, Some(b));
    }

    fn draw_player(&self, d: &mut RenderDrawer) {
        self.draw_box(d, PADDLE_WALL_PADDING, self.player_paddle_y);
    }

    fn draw_opponent(&self, d: &mut RenderDrawer) {
        self.draw_box(d, self.window_width - PADDLE_WIDTH - PADDLE_WALL_PADDING, self.opponent_paddle_y);
    }

    fn draw_box(&self, d: &mut RenderDrawer, x: i32, y: i32) {
        d.set_draw_color(PADDLE_COLOR);
        let r = Rect::new(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
        d.fill_rect(r);
     }

    fn draw_ball(&self, d: &mut RenderDrawer) {
        d.set_draw_color(BALL_COLOR);
        let (x, y) = self.ball_position;
        let r = Rect::new(x, y, BALL_BREADTH, BALL_BREADTH);
        d.fill_rect(r);
    }
}
<commit_msg>Fix ball passing through the top of the paddle.<commit_after>use std::num::Float;
use std::f64::consts::PI;

use sdl2::event::Event;
use sdl2::mouse;
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::{Renderer, RenderDrawer};
use sdl2_ttf::Font;

const SOULLESS_GREY: Color = Color::RGB(175, 175, 175);

const PADDLE_COLOR: Color = SOULLESS_GREY;
const PADDLE_WIDTH: i32 = 12;
const PADDLE_HEIGHT: i32 = 64;
const PADDLE_WALL_PADDING: i32 = 10;

const BALL_COLOR: Color = SOULLESS_GREY;
const BALL_BREADTH: i32 = 8;
const BALL_VELOCITY: f64 = 10.0;

const SCORE_TOP_PADDING: i32 = 10;
const SCORE_COLOR: Color = SOULLESS_GREY;
const SCORE_FONT_SIZE: isize = 64;

pub struct GameController {
    window_width: i32,
    window_height: i32,

    score_font: Font,

    ball_position: (i32, i32),
    ball_angle: f64,

    player_paddle_y: i32,
    opponent_paddle_y: i32,

    player_score: i32,
    opponent_score: i32
}

impl GameController {
    pub fn new(window_width: i32, window_height: i32) -> GameController {
        let score_font_path = &Path::new("art/alterebro-pixel-font.ttf");
        let score_font = Font::from_file(score_font_path, SCORE_FONT_SIZE).unwrap();

        return GameController{
            window_width: window_width,
            window_height: window_height,
            score_font: score_font,
            ball_position: (0, 0),
            ball_angle: 0.0,
            player_paddle_y: 280,
            opponent_paddle_y: 10,
            player_score: 0,
            opponent_score: 0
        }
    }

    pub fn restart_game(&mut self) {
        self.ball_position = (
            self.window_width / 2 - BALL_BREADTH / 2,
            self.window_height / 2 - BALL_BREADTH / 2
        );
        self.ball_angle = 45.0.to_radians();
    }

    pub fn tick(&mut self, _: &Event, renderer: &Renderer, drawer: &mut RenderDrawer) {
        self.move_player_paddle();
        self.move_opponent_paddle();
        self.move_ball();
        self.draw_scores(&renderer, drawer);
        self.draw_player(drawer);
        self.draw_opponent(drawer);
        self.draw_ball(drawer);
    }

    fn move_player_paddle(&mut self) {
        let (_, _, mut y) = mouse::get_mouse_state();
        if y < 0 { y = 0; }
        else if y + PADDLE_HEIGHT > self.window_height { y = self.window_height - PADDLE_HEIGHT; }

        self.player_paddle_y = y;
    }

    fn move_opponent_paddle(&mut self) {
        let (_, y) = self.ball_position;
        self.opponent_paddle_y = y - PADDLE_HEIGHT / 2;
    }

    // TODO this shouldn't be framerate/vsync dependent.
    // Thanks StackOverflow!
    // http://stackoverflow.com/questions/5830791/find-radians-reflection-angle
    fn move_ball(&mut self) {
        let x_change = (self.ball_angle.cos() * BALL_VELOCITY) as i32;
        let y_change = (self.ball_angle.sin() * BALL_VELOCITY) as i32;

        let (mut x, mut y) = self.ball_position;
        x += x_change;
        y += y_change;

        // Wall collision
        let upper_y_limit = self.window_height - BALL_BREADTH;
        if y > upper_y_limit || y < 0 { self.ball_angle *= -1.0 }
        if y > upper_y_limit { y = upper_y_limit; }
        else if y < 0 { y = 0; }

        // Paddle collision
        // TODO ball dimensions aren't respected. Left side of ball hits on the left, right side of
        // ball hits on the right. Also need to reset ball x on impact.
        let player_paddle_x = PADDLE_WALL_PADDING + PADDLE_WIDTH;
        let player_paddle_impacted = x <= player_paddle_x && y + BALL_BREADTH >= self.player_paddle_y && y <= self.player_paddle_y + PADDLE_HEIGHT;
        let opponent_paddle_x = self.window_width - PADDLE_WALL_PADDING - PADDLE_WIDTH - BALL_BREADTH;
        let opponent_paddle_impacted = x >= opponent_paddle_x && y >= self.opponent_paddle_y && y <= self.opponent_paddle_y + PADDLE_HEIGHT;

        // Reflect and tweak ball position.
        if player_paddle_impacted || opponent_paddle_impacted {
            self.ball_angle = 0.0 - PI - self.ball_angle;
        }
        if player_paddle_impacted { x = player_paddle_x; }
        else if opponent_paddle_impacted { x = opponent_paddle_x; }

        if x < player_paddle_x || x > opponent_paddle_x {
            if x < player_paddle_x { self.opponent_score += 1; }
            else if x > opponent_paddle_x { self.player_score += 1; }

            self.restart_game();
        } else {
            self.ball_position = (x, y);
        }
    }

    fn draw_scores(&self, r: &Renderer, d: &mut RenderDrawer) {
        let third = self.window_width / 3;
        self.draw_score(r, d, self.opponent_score, third);
        self.draw_score(r, d, self.player_score, third * 2);
    }

    fn draw_score(&self, r: &Renderer, d: &mut RenderDrawer, score: i32, x: i32) {
        let surface = self.score_font.render_str_blended(&score.to_string(), SCORE_COLOR).unwrap();
        let t = r.create_texture_from_surface(&surface).unwrap();
        let q = t.query();
        let b = Rect::new(x - q.width / 2, SCORE_TOP_PADDING, q.width, q.height);
        d.copy(&t, None, Some(b));
    }

    fn draw_player(&self, d: &mut RenderDrawer) {
        self.draw_box(d, PADDLE_WALL_PADDING, self.player_paddle_y);
    }

    fn draw_opponent(&self, d: &mut RenderDrawer) {
        self.draw_box(d, self.window_width - PADDLE_WIDTH - PADDLE_WALL_PADDING, self.opponent_paddle_y);
    }

    fn draw_box(&self, d: &mut RenderDrawer, x: i32, y: i32) {
        d.set_draw_color(PADDLE_COLOR);
        let r = Rect::new(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
        d.fill_rect(r);
     }

    fn draw_ball(&self, d: &mut RenderDrawer) {
        d.set_draw_color(BALL_COLOR);
        let (x, y) = self.ball_position;
        let r = Rect::new(x, y, BALL_BREADTH, BALL_BREADTH);
        d.fill_rect(r);
    }
}
<|endoftext|>