<commit_before>// Copyright (C) 2020 Mathieu Duponchelle <mathieu@centricular.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
// Boston, MA 02110-1335, USA.

use glib::prelude::*;
use glib::subclass;
use glib::subclass::prelude::*;
use gst::prelude::*;
use gst::subclass::prelude::*;
use gst::{gst_error, gst_info};

use std::default::Default;
use std::fs::File;
use std::io;
use std::sync::Mutex;

use once_cell::sync::Lazy;

use hyphenation::{Load, Standard};

static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
    gst::DebugCategory::new(
        "textwrap",
        gst::DebugColorFlags::empty(),
        Some("Text wrapper element"),
    )
});

const DEFAULT_DICTIONARY: Option<String> = None;
const DEFAULT_COLUMNS: u32 = 32; /* CEA 608 max columns */
const DEFAULT_LINES: u32 = 0;

static PROPERTIES: [subclass::Property; 3] = [
    subclass::Property("dictionary", |name| {
        glib::ParamSpec::string(
            name,
            "Dictionary",
            "Path to a dictionary to load at runtime to perform hyphenation, see \
                <https://docs.rs/crate/hyphenation/0.7.1> for more information",
            None,
            glib::ParamFlags::READWRITE,
        )
    }),
    subclass::Property("columns", |name| {
        glib::ParamSpec::uint(
            name,
            "Columns",
            "Maximum number of columns for any given line",
            1,
            std::u32::MAX,
            DEFAULT_COLUMNS,
            glib::ParamFlags::READWRITE,
        )
    }),
    subclass::Property("lines", |name| {
        glib::ParamSpec::uint(
            name,
            "Lines",
            "Split input buffer into output buffers with max lines (0=do not split)",
            0,
            std::u32::MAX,
            DEFAULT_LINES,
            glib::ParamFlags::READWRITE,
        )
    }),
];

#[derive(Debug, Clone)]
struct Settings {
    dictionary: Option<String>,
    columns: u32,
    lines: u32,
}

impl Default for Settings {
    fn default() -> Self {
        Self {
            dictionary: DEFAULT_DICTIONARY,
            columns: DEFAULT_COLUMNS, /* CEA 608 max columns */
            lines: DEFAULT_LINES,
        }
    }
}

struct State {
    options: Option<textwrap::Options<'static, Box<dyn textwrap::WordSplitter + Send>>>,
}

impl Default for State {
    fn default() -> Self {
        Self { options: None }
    }
}

pub struct TextWrap {
    srcpad: gst::Pad,
    sinkpad: gst::Pad,
    settings: Mutex<Settings>,
    state: Mutex<State>,
}

impl TextWrap {
    fn update_wrapper(&self, element: &super::TextWrap) {
        let settings = self.settings.lock().unwrap();
        let mut state = self.state.lock().unwrap();

        if state.options.is_some() {
            return;
        }

        state.options = if let Some(dictionary) = &settings.dictionary {
            let dict_file = match File::open(dictionary) {
                Err(err) => {
                    gst_error!(CAT, obj: element, "Failed to open dictionary file: {}", err);
                    return;
                }
                Ok(dict_file) => dict_file,
            };

            let mut reader = io::BufReader::new(dict_file);
            let standard = match Standard::any_from_reader(&mut reader) {
                Err(err) => {
                    gst_error!(
                        CAT,
                        obj: element,
                        "Failed to load standard from file: {}",
                        err
                    );
                    return;
                }
                Ok(standard) => standard,
            };

            Some(textwrap::Options::with_splitter(
                settings.columns as usize,
                Box::new(standard),
            ))
        } else {
            Some(textwrap::Options::with_splitter(
                settings.columns as usize,
                Box::new(textwrap::NoHyphenation),
            ))
        };
    }

    fn sink_chain(
        &self,
        _pad: &gst::Pad,
        element: &super::TextWrap,
        buffer: gst::Buffer,
    ) -> Result<gst::FlowSuccess, gst::FlowError> {
        self.update_wrapper(element);

        let mut pts: gst::ClockTime = buffer
            .get_pts()
            .ok_or_else(|| {
                gst_error!(CAT, obj: element, "Need timestamped buffers");
                gst::FlowError::Error
            })?
            .into();

        let duration: gst::ClockTime = buffer
            .get_duration()
            .ok_or_else(|| {
                gst_error!(CAT, obj: element, "Need buffers with duration");
                gst::FlowError::Error
            })?
            .into();

        let data = buffer.map_readable().map_err(|_| {
            gst_error!(CAT, obj: element, "Can't map buffer readable");

            gst::FlowError::Error
        })?;

        let data = std::str::from_utf8(&data).map_err(|err| {
            gst_error!(CAT, obj: element, "Can't decode utf8: {}", err);

            gst::FlowError::Error
        })?;

        let lines = self.settings.lock().unwrap().lines;

        let data = {
            let state = self.state.lock().unwrap();
            let options = state
                .options
                .as_ref()
                .expect("We should have a wrapper by now");
            textwrap::fill(data, options)
        };

        // If the lines property was set, we want to split the result into buffers
        // of at most N lines. We compute the duration for each of those based on
        // the total number of words, and the number of words in each of the split-up
        // buffers.
        if lines > 0 {
            let mut bufferlist = gst::BufferList::new();
            let duration_per_word: gst::ClockTime =
                duration / data.split_whitespace().count() as u64;

            for chunk in data.lines().collect::<Vec<&str>>().chunks(lines as usize) {
                let data = chunk.join("\n");
                let duration: gst::ClockTime =
                    duration_per_word * data.split_whitespace().count() as u64;
                let mut buf = gst::Buffer::from_mut_slice(data.into_bytes());

                {
                    let buf = buf.get_mut().unwrap();

                    buf.set_pts(pts);
                    buf.set_duration(duration);
                    pts += duration;
                }

                bufferlist.get_mut().unwrap().add(buf);
            }

            self.srcpad.push_list(bufferlist)
        } else {
            let mut buf = gst::Buffer::from_mut_slice(data.into_bytes());

            {
                let buf = buf.get_mut().unwrap();

                buf.set_pts(pts);
                buf.set_duration(duration);
            }

            self.srcpad.push(buf)
        }
    }
}

impl ObjectSubclass for TextWrap {
    const NAME: &'static str = "RsTextWrap";
    type Type = super::TextWrap;
    type ParentType = gst::Element;
    type Instance = gst::subclass::ElementInstanceStruct<Self>;
    type Class = subclass::simple::ClassStruct<Self>;

    glib::object_subclass!();

    fn with_class(klass: &Self::Class) -> Self {
        let templ = klass.get_pad_template("sink").unwrap();
        let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink"))
            .chain_function(|pad, parent, buffer| {
                TextWrap::catch_panic_pad_function(
                    parent,
                    || Err(gst::FlowError::Error),
                    |textwrap, element| textwrap.sink_chain(pad, element, buffer),
                )
            })
            .flags(gst::PadFlags::PROXY_CAPS | gst::PadFlags::FIXED_CAPS)
            .build();

        let templ = klass.get_pad_template("src").unwrap();
        let srcpad = gst::Pad::builder_with_template(&templ, Some("src"))
            .flags(gst::PadFlags::PROXY_CAPS | gst::PadFlags::FIXED_CAPS)
            .build();

        let settings = Mutex::new(Settings::default());
        let state = Mutex::new(State::default());

        Self {
            srcpad,
            sinkpad,
            settings,
            state,
        }
    }

    fn class_init(klass: &mut Self::Class) {
        klass.set_metadata(
            "Text Wrapper",
            "Text/Filter",
            "Breaks text into fixed-size lines, with optional hyphenationz",
            "Mathieu Duponchelle <mathieu@centricular.com>",
        );

        let caps = gst::Caps::builder("text/x-raw")
            .field("format", &"utf8")
            .build();
        let src_pad_template = gst::PadTemplate::new(
            "src",
            gst::PadDirection::Src,
            gst::PadPresence::Always,
            &caps,
        )
        .unwrap();
        klass.add_pad_template(src_pad_template);

        let sink_pad_template = gst::PadTemplate::new(
            "sink",
            gst::PadDirection::Sink,
            gst::PadPresence::Always,
            &caps,
        )
        .unwrap();
        klass.add_pad_template(sink_pad_template);

        klass.install_properties(&PROPERTIES);
    }
}

impl ObjectImpl for TextWrap {
    fn constructed(&self, obj: &Self::Type) {
        self.parent_constructed(obj);

        obj.add_pad(&self.sinkpad).unwrap();
        obj.add_pad(&self.srcpad).unwrap();
    }

    fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) {
        let prop = &PROPERTIES[id];

        match *prop {
            subclass::Property("dictionary", ..) => {
                let mut settings = self.settings.lock().unwrap();
                let mut state = self.state.lock().unwrap();
                settings.dictionary = value.get().expect("type checked upstream");
                state.options = None;
            }
            subclass::Property("columns", ..) => {
                let mut settings = self.settings.lock().unwrap();
                let mut state = self.state.lock().unwrap();
                settings.columns = value.get_some().expect("type checked upstream");
                state.options = None;
            }
            subclass::Property("lines", ..) => {
                let mut settings = self.settings.lock().unwrap();
                settings.lines = value.get_some().expect("type checked upstream");
            }
            _ => unimplemented!(),
        }
    }

    fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value {
        let prop = &PROPERTIES[id];

        match *prop {
            subclass::Property("dictionary", ..) => {
                let settings = self.settings.lock().unwrap();
                settings.dictionary.to_value()
            }
            subclass::Property("columns", ..) => {
                let settings = self.settings.lock().unwrap();
                settings.columns.to_value()
            }
            subclass::Property("lines", ..) => {
                let settings = self.settings.lock().unwrap();
                settings.lines.to_value()
            }
            _ => unimplemented!(),
        }
    }
}

impl ElementImpl for TextWrap {
    fn change_state(
        &self,
        element: &Self::Type,
        transition: gst::StateChange,
    ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
        gst_info!(CAT, obj: element, "Changing state {:?}", transition);

        match transition {
            gst::StateChange::PausedToReady => {
                let mut state = self.state.lock().unwrap();
                *state = State::default();
            }
            _ => (),
        }

        let success = self.parent_change_state(element, transition)?;

        Ok(success)
    }
}
<commit_msg>textwrap: expose accumulate-time property<commit_after>// Copyright (C) 2020 Mathieu Duponchelle <mathieu@centricular.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
// Boston, MA 02110-1335, USA.

use glib::prelude::*;
use glib::subclass;
use glib::subclass::prelude::*;
use gst::prelude::*;
use gst::subclass::prelude::*;
use gst::{gst_error, gst_info, gst_log};

use std::default::Default;
use std::fs::File;
use std::io;
use std::mem;
use std::sync::Mutex;

use once_cell::sync::Lazy;

use hyphenation::{Load, Standard};

static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
    gst::DebugCategory::new(
        "textwrap",
        gst::DebugColorFlags::empty(),
        Some("Text wrapper element"),
    )
});

const DEFAULT_DICTIONARY: Option<String> = None;
const DEFAULT_COLUMNS: u32 = 32; /* CEA 608 max columns */
const DEFAULT_LINES: u32 = 0;
const DEFAULT_ACCUMULATE: i64 = -1;

static PROPERTIES: [subclass::Property; 4] = [
    subclass::Property("dictionary", |name| {
        glib::ParamSpec::string(
            name,
            "Dictionary",
            "Path to a dictionary to load at runtime to perform hyphenation, see \
                <https://docs.rs/crate/hyphenation/0.7.1> for more information",
            None,
            glib::ParamFlags::READWRITE,
        )
    }),
    subclass::Property("columns", |name| {
        glib::ParamSpec::uint(
            name,
            "Columns",
            "Maximum number of columns for any given line",
            1,
            std::u32::MAX,
            DEFAULT_COLUMNS,
            glib::ParamFlags::READWRITE,
        )
    }),
    subclass::Property("lines", |name| {
        glib::ParamSpec::uint(
            name,
            "Lines",
            "Split input buffer into output buffers with max lines (0=do not split)",
            0,
            std::u32::MAX,
            DEFAULT_LINES,
            glib::ParamFlags::READWRITE,
        )
    }),
    subclass::Property("accumulate-time", |name| {
        glib::ParamSpec::int64(
            name,
            "accumulate-time",
            "Cut-off time for input text accumulation (-1=do not accumulate)",
            -1,
            std::i64::MAX,
            DEFAULT_ACCUMULATE,
            glib::ParamFlags::READWRITE,
        )
    }),
];

#[derive(Debug, Clone)]
struct Settings {
    dictionary: Option<String>,
    columns: u32,
    lines: u32,
    accumulate_time: gst::ClockTime,
}

impl Default for Settings {
    fn default() -> Self {
        Self {
            dictionary: DEFAULT_DICTIONARY,
            columns: DEFAULT_COLUMNS, /* CEA 608 max columns */
            lines: DEFAULT_LINES,
            accumulate_time: gst::CLOCK_TIME_NONE,
        }
    }
}

struct State {
    options: Option<textwrap::Options<'static, Box<dyn textwrap::WordSplitter + Send>>>,

    current_text: String,
    start_ts: gst::ClockTime,
    end_ts: gst::ClockTime,
}

impl Default for State {
    fn default() -> Self {
        Self {
            options: None,

            current_text: "".to_string(),
            start_ts: gst::CLOCK_TIME_NONE,
            end_ts: gst::CLOCK_TIME_NONE,
        }
    }
}

pub struct TextWrap {
    srcpad: gst::Pad,
    sinkpad: gst::Pad,
    settings: Mutex<Settings>,
    state: Mutex<State>,
}

impl TextWrap {
    fn update_wrapper(&self, element: &super::TextWrap) {
        let settings = self.settings.lock().unwrap();
        let mut state = self.state.lock().unwrap();

        if state.options.is_some() {
            return;
        }

        state.options = if let Some(dictionary) = &settings.dictionary {
            let dict_file = match File::open(dictionary) {
                Err(err) => {
                    gst_error!(CAT, obj: element, "Failed to open dictionary file: {}", err);
                    return;
                }
                Ok(dict_file) => dict_file,
            };

            let mut reader = io::BufReader::new(dict_file);
            let standard = match Standard::any_from_reader(&mut reader) {
                Err(err) => {
                    gst_error!(
                        CAT,
                        obj: element,
                        "Failed to load standard from file: {}",
                        err
                    );
                    return;
                }
                Ok(standard) => standard,
            };

            Some(textwrap::Options::with_splitter(
                settings.columns as usize,
                Box::new(standard),
            ))
        } else {
            Some(textwrap::Options::with_splitter(
                settings.columns as usize,
                Box::new(textwrap::NoHyphenation),
            ))
        };
    }

    fn sink_chain(
        &self,
        _pad: &gst::Pad,
        element: &super::TextWrap,
        buffer: gst::Buffer,
    ) -> Result<gst::FlowSuccess, gst::FlowError> {
        self.update_wrapper(element);

        let mut pts: gst::ClockTime = buffer
            .get_pts()
            .ok_or_else(|| {
                gst_error!(CAT, obj: element, "Need timestamped buffers");
                gst::FlowError::Error
            })?
            .into();

        let duration: gst::ClockTime = buffer
            .get_duration()
            .ok_or_else(|| {
                gst_error!(CAT, obj: element, "Need buffers with duration");
                gst::FlowError::Error
            })?
            .into();

        let data = buffer.map_readable().map_err(|_| {
            gst_error!(CAT, obj: element, "Can't map buffer readable");

            gst::FlowError::Error
        })?;

        let data = std::str::from_utf8(&data).map_err(|err| {
            gst_error!(CAT, obj: element, "Can't decode utf8: {}", err);

            gst::FlowError::Error
        })?;

        let accumulate_time = self.settings.lock().unwrap().accumulate_time;
        let mut state = self.state.lock().unwrap();

        if accumulate_time.is_some() {
            let mut bufferlist = gst::BufferList::new();

            if state.end_ts.is_some() && state.end_ts + accumulate_time < buffer.get_pts() {
                let mut buf = gst::Buffer::from_mut_slice(
                    mem::replace(&mut state.current_text, String::new()).into_bytes(),
                );
                {
                    let buf_mut = buf.get_mut().unwrap();
                    buf_mut.set_pts(state.start_ts);
                    buf_mut.set_duration(state.end_ts - state.start_ts);
                }
                bufferlist.get_mut().unwrap().add(buf);

                state.start_ts = gst::CLOCK_TIME_NONE;
                state.end_ts = gst::CLOCK_TIME_NONE;
            }

            let duration_per_word: gst::ClockTime =
                duration / data.split_whitespace().count() as u64;

            if state.start_ts.is_none() {
                state.start_ts = buffer.get_pts();
            }

            state.end_ts = buffer.get_pts();

            let words = data.split_whitespace();
            let mut current_text = state.current_text.to_string();

            for word in words {
                if !current_text.is_empty() {
                    current_text.push(' ');
                }
                current_text.push_str(word);

                let options = state
                    .options
                    .as_ref()
                    .expect("We should have a wrapper by now");

                let lines = textwrap::wrap(&current_text, options);
                let len = lines.len();
                let mut trailing = "".to_string();

                for (i, line) in lines.iter().enumerate() {
                    if i + 1 == len {
                        trailing = line.to_string();
                    } else {
                        let mut buf = gst::Buffer::from_mut_slice(line.to_string().into_bytes());
                        {
                            let buf_mut = buf.get_mut().unwrap();
                            buf_mut.set_pts(state.start_ts);
                            buf_mut.set_duration(state.end_ts - state.start_ts);
                        }
                        bufferlist.get_mut().unwrap().add(buf);
                        state.start_ts = state.end_ts;
                    }
                }

                current_text = trailing;
                state.end_ts += duration_per_word;
            }

            state.current_text = current_text;

            if state.current_text.is_empty() {
                state.start_ts = gst::CLOCK_TIME_NONE;
                state.end_ts = gst::CLOCK_TIME_NONE;
            }

            drop(state);

            if bufferlist.is_empty() {
                Ok(gst::FlowSuccess::Ok)
            } else {
                self.srcpad.push_list(bufferlist)
            }
        } else {
            let lines = self.settings.lock().unwrap().lines;

            let data = {
                let options = state
                    .options
                    .as_ref()
                    .expect("We should have a wrapper by now");
                textwrap::fill(data, options)
            };

            // If the lines property was set, we want to split the result into buffers
            // of at most N lines. We compute the duration for each of those based on
            // the total number of words, and the number of words in each of the split-up
            // buffers.
            if lines > 0 {
                let mut bufferlist = gst::BufferList::new();
                let duration_per_word: gst::ClockTime =
                    duration / data.split_whitespace().count() as u64;

                for chunk in data.lines().collect::<Vec<&str>>().chunks(lines as usize) {
                    let data = chunk.join("\n");
                    let duration: gst::ClockTime =
                        duration_per_word * data.split_whitespace().count() as u64;
                    let mut buf = gst::Buffer::from_mut_slice(data.into_bytes());

                    {
                        let buf = buf.get_mut().unwrap();

                        buf.set_pts(pts);
                        buf.set_duration(duration);
                        pts += duration;
                    }

                    bufferlist.get_mut().unwrap().add(buf);
                }

                drop(state);

                self.srcpad.push_list(bufferlist)
            } else {
                let mut buf = gst::Buffer::from_mut_slice(data.into_bytes());

                {
                    let buf = buf.get_mut().unwrap();

                    buf.set_pts(pts);
                    buf.set_duration(duration);
                }

                drop(state);

                self.srcpad.push(buf)
            }
        }
    }

    fn sink_event(&self, pad: &gst::Pad, element: &super::TextWrap, event: gst::Event) -> bool {
        gst_log!(CAT, obj: pad, "Handling event {:?}", event);

        use gst::EventView;

        match event.view() {
            EventView::Gap(_) => {
                let state = self.state.lock().unwrap();
                /* We are currently accumulating text, no need to forward the gap */
                if state.start_ts.is_some() {
                    true
                } else {
                    pad.event_default(Some(element), event)
                }
            }
            EventView::FlushStart(_) => {
                let mut state = self.state.lock().unwrap();
                let options = state.options.take();
                *state = State::default();
                state.options = options;
                drop(state);
                pad.event_default(Some(element), event)
            }
            EventView::Eos(_) => {
                let mut state = self.state.lock().unwrap();
                if !state.current_text.is_empty() {
                    let mut buf = gst::Buffer::from_mut_slice(
                        mem::replace(&mut state.current_text, String::new()).into_bytes(),
                    );
                    {
                        let buf_mut = buf.get_mut().unwrap();
                        buf_mut.set_pts(state.start_ts);
                        buf_mut.set_duration(state.end_ts - state.start_ts);
                    }

                    state.start_ts = gst::CLOCK_TIME_NONE;
                    state.end_ts = gst::CLOCK_TIME_NONE;

                    drop(state);
                    let _ = self.srcpad.push(buf);
                } else {
                    drop(state);
                }
                pad.event_default(Some(element), event)
            }
            _ => pad.event_default(Some(element), event),
        }
    }
}

impl ObjectSubclass for TextWrap {
    const NAME: &'static str = "RsTextWrap";
    type Type = super::TextWrap;
    type ParentType = gst::Element;
    type Instance = gst::subclass::ElementInstanceStruct<Self>;
    type Class = subclass::simple::ClassStruct<Self>;

    glib::object_subclass!();

    fn with_class(klass: &Self::Class) -> Self {
        let templ = klass.get_pad_template("sink").unwrap();
        let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink"))
            .chain_function(|pad, parent, buffer| {
                TextWrap::catch_panic_pad_function(
                    parent,
                    || Err(gst::FlowError::Error),
                    |textwrap, element| textwrap.sink_chain(pad, element, buffer),
                )
            })
            .event_function(|pad, parent, event| {
                TextWrap::catch_panic_pad_function(
                    parent,
                    || false,
                    |textwrap, element| textwrap.sink_event(pad, element, event),
                )
            })
            .flags(gst::PadFlags::PROXY_CAPS | gst::PadFlags::FIXED_CAPS)
            .build();

        let templ = klass.get_pad_template("src").unwrap();
        let srcpad = gst::Pad::builder_with_template(&templ, Some("src"))
            .flags(gst::PadFlags::PROXY_CAPS | gst::PadFlags::FIXED_CAPS)
            .build();

        let settings = Mutex::new(Settings::default());
        let state = Mutex::new(State::default());

        Self {
            srcpad,
            sinkpad,
            settings,
            state,
        }
    }

    fn class_init(klass: &mut Self::Class) {
        klass.set_metadata(
            "Text Wrapper",
            "Text/Filter",
            "Breaks text into fixed-size lines, with optional hyphenationz",
            "Mathieu Duponchelle <mathieu@centricular.com>",
        );

        let caps = gst::Caps::builder("text/x-raw")
            .field("format", &"utf8")
            .build();
        let src_pad_template = gst::PadTemplate::new(
            "src",
            gst::PadDirection::Src,
            gst::PadPresence::Always,
            &caps,
        )
        .unwrap();
        klass.add_pad_template(src_pad_template);

        let sink_pad_template = gst::PadTemplate::new(
            "sink",
            gst::PadDirection::Sink,
            gst::PadPresence::Always,
            &caps,
        )
        .unwrap();
        klass.add_pad_template(sink_pad_template);

        klass.install_properties(&PROPERTIES);
    }
}

impl ObjectImpl for TextWrap {
    fn constructed(&self, obj: &Self::Type) {
        self.parent_constructed(obj);

        obj.add_pad(&self.sinkpad).unwrap();
        obj.add_pad(&self.srcpad).unwrap();
    }

    fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) {
        let prop = &PROPERTIES[id];

        match *prop {
            subclass::Property("dictionary", ..) => {
                let mut settings = self.settings.lock().unwrap();
                let mut state = self.state.lock().unwrap();
                settings.dictionary = value.get().expect("type checked upstream");
                state.options = None;
            }
            subclass::Property("columns", ..) => {
                let mut settings = self.settings.lock().unwrap();
                let mut state = self.state.lock().unwrap();
                settings.columns = value.get_some().expect("type checked upstream");
                state.options = None;
            }
            subclass::Property("lines", ..) => {
                let mut settings = self.settings.lock().unwrap();
                settings.lines = value.get_some().expect("type checked upstream");
            }
            subclass::Property("accumulate-time", ..) => {
                let mut settings = self.settings.lock().unwrap();
                settings.accumulate_time = match value.get_some().expect("type checked upstream") {
                    -1i64 => gst::CLOCK_TIME_NONE,
                    time => (time as u64).into(),
                };
            }
            _ => unimplemented!(),
        }
    }

    fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value {
        let prop = &PROPERTIES[id];

        match *prop {
            subclass::Property("dictionary", ..) => {
                let settings = self.settings.lock().unwrap();
                settings.dictionary.to_value()
            }
            subclass::Property("columns", ..) => {
                let settings = self.settings.lock().unwrap();
                settings.columns.to_value()
            }
            subclass::Property("lines", ..) => {
                let settings = self.settings.lock().unwrap();
                settings.lines.to_value()
            }
            subclass::Property("accumulate-time", ..) => {
                let settings = self.settings.lock().unwrap();
                match settings.accumulate_time.0 {
                    Some(time) => (time as i64).to_value(),
                    None => (-1i64).to_value(),
                }
            }
            _ => unimplemented!(),
        }
    }
}

impl ElementImpl for TextWrap {
    fn change_state(
        &self,
        element: &Self::Type,
        transition: gst::StateChange,
    ) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
        gst_info!(CAT, obj: element, "Changing state {:?}", transition);

        if let gst::StateChange::PausedToReady = transition {
            let mut state = self.state.lock().unwrap();
            *state = State::default();
        }

        let success = self.parent_change_state(element, transition)?;

        Ok(success)
    }
}
<|endoftext|>


<commit_before>use failure::Error;
use flate2::read::GzDecoder;
use git2::Repository;
use tar::Archive;

use std::path::PathBuf;

pub const LATEST_URL: &'static str = "https://github.com/exercism/configlet/releases/latest";

#[cfg(target_os = "macos")]
pub fn target_os() -> &'static str {
    "mac"
}

#[cfg(target_os = "windows")]
pub fn target_os() -> &'static str {
    "windows"
}

#[cfg(not(any(target_os = "macos", target_os = "windows")))]
pub fn target_os() -> &'static str {
    "linux"
}

#[cfg(target_pointer_width = "32")]
pub fn target_arch() -> &'static str {
    "32bit"
}

#[cfg(not(target_pointer_width = "32"))]
pub fn target_arch() -> &'static str {
    "64bit"
}

pub fn get_latest() -> Result<String, Error> {
    use reqwest::header;

    let response = reqwest::ClientBuilder::new()
        .redirect(reqwest::RedirectPolicy::none())
        .build()?
        .head(LATEST_URL)
        .send()?;
    let location = response
        .headers()
        .get::<header::Location>()
        .ok_or_else(|| format_err!("location not found in headers\n{}", response.headers()))?;
    Ok(location
        .trim()
        .rsplit('/')
        .nth(0)
        .ok_or(format_err!("path separator not in location"))?
        .to_string())
}

/// get the current URL for the appropriate configlet tarball
pub fn pkg_url() -> Result<String, Error> {
    Ok(format!(
        "https://github.com/exercism/configlet/releases/download/{version}/configlet-{os}-{arch}.tgz",
        version=get_latest()?,
        os=target_os(),
        arch=target_arch(),
    ))
}

/// return the path to the bin directory of the repo
pub fn binpath() -> Result<PathBuf, Error> {
    let repo = Repository::discover(".")?;
    repo.workdir()
        .ok_or(format_err!("repo has no working directory"))
        .map(|p| {
            let mut b = p.to_path_buf();
            b.push("bin");
            b
        })
}

// download and extract configlet into the repo's /bin folder
//
// returns the path into which the bin was extracted on success
pub fn download() -> Result<PathBuf, Error> {
    let path = binpath()?;
    let response = reqwest::get(&pkg_url()?)?;
    let unzipper = GzDecoder::new(response);
    let mut archive = Archive::new(unzipper);
    archive.unpack(&path).map(|_| path).map_err(|e| e.into())
}
<commit_msg>exercise: Removed the redundant 'statit' lifetime<commit_after>use failure::Error;
use flate2::read::GzDecoder;
use git2::Repository;
use tar::Archive;

use std::path::PathBuf;

pub const LATEST_URL: &str = "https://github.com/exercism/configlet/releases/latest";

#[cfg(target_os = "macos")]
pub fn target_os() -> &'static str {
    "mac"
}

#[cfg(target_os = "windows")]
pub fn target_os() -> &'static str {
    "windows"
}

#[cfg(not(any(target_os = "macos", target_os = "windows")))]
pub fn target_os() -> &'static str {
    "linux"
}

#[cfg(target_pointer_width = "32")]
pub fn target_arch() -> &'static str {
    "32bit"
}

#[cfg(not(target_pointer_width = "32"))]
pub fn target_arch() -> &'static str {
    "64bit"
}

pub fn get_latest() -> Result<String, Error> {
    use reqwest::header;

    let response = reqwest::ClientBuilder::new()
        .redirect(reqwest::RedirectPolicy::none())
        .build()?
        .head(LATEST_URL)
        .send()?;
    let location = response
        .headers()
        .get::<header::Location>()
        .ok_or_else(|| format_err!("location not found in headers\n{}", response.headers()))?;
    Ok(location
        .trim()
        .rsplit('/')
        .nth(0)
        .ok_or(format_err!("path separator not in location"))?
        .to_string())
}

/// get the current URL for the appropriate configlet tarball
pub fn pkg_url() -> Result<String, Error> {
    Ok(format!(
        "https://github.com/exercism/configlet/releases/download/{version}/configlet-{os}-{arch}.tgz",
        version=get_latest()?,
        os=target_os(),
        arch=target_arch(),
    ))
}

/// return the path to the bin directory of the repo
pub fn binpath() -> Result<PathBuf, Error> {
    let repo = Repository::discover(".")?;
    repo.workdir()
        .ok_or(format_err!("repo has no working directory"))
        .map(|p| {
            let mut b = p.to_path_buf();
            b.push("bin");
            b
        })
}

// download and extract configlet into the repo's /bin folder
//
// returns the path into which the bin was extracted on success
pub fn download() -> Result<PathBuf, Error> {
    let path = binpath()?;
    let response = reqwest::get(&pkg_url()?)?;
    let unzipper = GzDecoder::new(response);
    let mut archive = Archive::new(unzipper);
    archive.unpack(&path).map(|_| path).map_err(|e| e.into())
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use std::error::Error;
use std::ptr;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

use buffer::Buffer;
use command_buffer::CommandBuffer;
use command_buffer::cb::CommandBufferBuild;
use command_buffer::pool::AllocatedCommandBuffer;
use command_buffer::pool::CommandPool;
use device::Device;
use device::DeviceOwned;
use device::Queue;
use framebuffer::EmptySinglePassRenderPassDesc;
use framebuffer::Framebuffer;
use framebuffer::FramebufferAbstract;
use framebuffer::RenderPass;
use framebuffer::RenderPassAbstract;
use framebuffer::Subpass;
use image::Image;
use sync::AccessFlagBits;
use sync::PipelineStages;
use sync::GpuFuture;
use OomError;
use VulkanObject;
use VulkanPointers;
use check_errors;
use vk;

/// Determines the kind of command buffer that we want to create.
#[derive(Debug, Clone)]
pub enum Kind<R, F> {
    /// A primary command buffer can execute all commands and can call secondary command buffers.
    Primary,

    /// A secondary command buffer can execute all dispatch and transfer operations, but not
    /// drawing operations.
    Secondary,

    /// A secondary command buffer within a render pass can only call draw operations that can
    /// be executed from within a specific subpass.
    SecondaryRenderPass {
        /// Which subpass this secondary command buffer can be called from.
        subpass: Subpass<R>,

        /// The framebuffer object that will be used when calling the command buffer.
        /// This parameter is optional and is an optimization hint for the implementation.
        framebuffer: Option<F>,
    },
}

impl Kind<RenderPass<EmptySinglePassRenderPassDesc>, Framebuffer<RenderPass<EmptySinglePassRenderPassDesc>, ()>> {
    /// Equivalent to `Kind::Primary`.
    ///
    /// > **Note**: If you use `let kind = Kind::Primary;` in your code, you will probably get a
    /// > compilation error because the Rust compiler couldn't determine the template parameters
    /// > of `Kind`. To solve that problem in an easy way you can use this function instead.
    #[inline]
    pub fn primary() -> Kind<RenderPass<EmptySinglePassRenderPassDesc>, Framebuffer<RenderPass<EmptySinglePassRenderPassDesc>, ()>> {
        Kind::Primary
    }
}

/// Flags to pass when creating a command buffer.
///
/// The safest option is `SimultaneousUse`, but it may be slower than the other two.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Flags {
    /// The command buffer can be used multiple times, but must not execute more than once
    /// simultaneously.
    None,

    /// The command buffer can be executed multiple times in parallel.
    SimultaneousUse,

    /// The command buffer can only be submitted once. Any further submit is forbidden.
    OneTimeSubmit,
}

pub struct UnsafeCommandBufferBuilder<P> where P: CommandPool {
    // The Vulkan command buffer. Will be 0 if `build()` has been called.
    cmd: vk::CommandBuffer,

    // Device that owns the command buffer.
    device: Arc<Device>,

    // Pool that owns the command buffer.
    pool: Option<P>,

    // Flags that were used at creation.
    flags: Flags,

    // True if we are a secondary command buffer.
    secondary_cb: bool,
}

impl<P> UnsafeCommandBufferBuilder<P> where P: CommandPool {
    /// Creates a new builder.
    ///
    /// # Safety
    ///
    /// Creating and destroying an unsafe command buffer is not unsafe per se, but the commands
    /// that you add to it are unchecked and do not have any synchronization.
    ///
    /// In other words, it is your job to make sure that the commands you add are valid and that
    /// they do not introduce any race condition.
    ///
    /// > **Note**: Some checks are still made with `debug_assert!`. Do not expect to be able to
    /// > be able to submit invalid commands.
    pub unsafe fn new<R, F>(pool: P, kind: Kind<R, F>, flags: Flags)
                            -> Result<UnsafeCommandBufferBuilder<P>, OomError>
        where R: RenderPassAbstract, F: FramebufferAbstract
    {
        let secondary = match kind {
            Kind::Primary => false,
            Kind::Secondary | Kind::SecondaryRenderPass { .. } => true,
        };

        let cmd = try!(pool.alloc(secondary, 1)).next().unwrap();

        match UnsafeCommandBufferBuilder::already_allocated(pool, cmd, kind, flags) {
            Ok(cmd) => Ok(cmd),
            Err(err) => {
                // FIXME: uncomment this and solve the fact that `pool` has been moved
                //unsafe { pool.free(secondary, Some(cmd.into()).into_iter()) };
                Err(err)
            },
        }
    }

    /// Creates a new command buffer builder from an already-allocated command buffer.
    ///
    /// # Safety
    ///
    /// See also `new`.
    ///
    /// The allocated command buffer must belong to the pool and must not be used anywhere else
    /// in the code for the duration of this command buffer.
    ///
    pub unsafe fn already_allocated<R, F>(pool: P, cmd: AllocatedCommandBuffer,
                                          kind: Kind<R, F>, flags: Flags)
                                          -> Result<UnsafeCommandBufferBuilder<P>, OomError>
        where R: RenderPassAbstract, F: FramebufferAbstract
    {
        let device = pool.device().clone();
        let vk = device.pointers();
        let cmd = cmd.internal_object();

        let vk_flags = {
            let a = match flags {
                Flags::None => 0,
                Flags::SimultaneousUse => vk::COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
                Flags::OneTimeSubmit => vk::COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
            };

            let b = match kind {
                Kind::Primary | Kind::Secondary => 0,
                Kind::SecondaryRenderPass { .. } => {
                    vk::COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT
                },
            };

            a | b
        };

        let (rp, sp) = if let Kind::SecondaryRenderPass { ref subpass, .. } = kind {
            (subpass.render_pass().inner().internal_object(), subpass.index())
        } else {
            (0, 0)
        };

        let framebuffer = if let Kind::SecondaryRenderPass { ref subpass, framebuffer: Some(ref framebuffer) } = kind {
            // TODO: restore check
            //assert!(framebuffer.is_compatible_with(subpass.render_pass()));     // TODO: proper error
            FramebufferAbstract::inner(&framebuffer).internal_object()
        } else {
            0
        };

        let inheritance = vk::CommandBufferInheritanceInfo {
            sType: vk::STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO,
            pNext: ptr::null(),
            renderPass: rp,
            subpass: sp,
            framebuffer: framebuffer,
            occlusionQueryEnable: 0,            // TODO:
            queryFlags: 0,          // TODO:
            pipelineStatistics: 0,          // TODO:
        };

        let infos = vk::CommandBufferBeginInfo {
            sType: vk::STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
            pNext: ptr::null(),
            flags: vk_flags,
            pInheritanceInfo: &inheritance,
        };

        try!(check_errors(vk.BeginCommandBuffer(cmd, &infos)));

        Ok(UnsafeCommandBufferBuilder {
            device: device.clone(),
            pool: Some(pool),
            cmd: cmd,
            flags: flags,
            secondary_cb: match kind {
                Kind::Primary => false,
                Kind::Secondary | Kind::SecondaryRenderPass { .. } => true,
            },
        })
    }
}

unsafe impl<P> DeviceOwned for UnsafeCommandBufferBuilder<P> where P: CommandPool {
    #[inline]
    fn device(&self) -> &Arc<Device> {
        &self.device
    }
}

unsafe impl<P> VulkanObject for UnsafeCommandBufferBuilder<P> where P: CommandPool {
    type Object = vk::CommandBuffer;

    #[inline]
    fn internal_object(&self) -> vk::CommandBuffer {
        self.cmd
    }
}

impl<P> Drop for UnsafeCommandBufferBuilder<P> where P: CommandPool {
    #[inline]
    fn drop(&mut self) {
        //unsafe {
            if self.cmd == 0 {
                return;
            }

            // FIXME: vk.FreeCommandBuffers()
        //}
    }
}

unsafe impl<P> CommandBufferBuild for UnsafeCommandBufferBuilder<P>
    where P: CommandPool
{
    type Out = UnsafeCommandBuffer<P>;

    #[inline]
    fn build(mut self) -> Self::Out {
        unsafe {
            debug_assert_ne!(self.cmd, 0);
            let cmd = self.cmd;
            let vk = self.device.pointers();
            check_errors(vk.EndCommandBuffer(cmd)).unwrap();       // TODO: handle error
            self.cmd = 0;       // Prevents the `Drop` impl of the builder from destroying the cb.

            UnsafeCommandBuffer {
                cmd: cmd,
                device: self.device.clone(),
                pool: self.pool.take().unwrap().finish(),
                flags: self.flags,
                already_submitted: AtomicBool::new(false),
            }
        }
    }
}

pub struct UnsafeCommandBuffer<P> where P: CommandPool {
    // The Vulkan command buffer.
    cmd: vk::CommandBuffer,

    // Device that owns the command buffer.
    device: Arc<Device>,

    // Pool that owns the command buffer.
    pool: P::Finished,

    // Flags that were used at creation.
    flags: Flags,

    // True if the command buffer has always been submitted once. Only relevant if `flags` is
    // `OneTimeSubmit`.
    already_submitted: AtomicBool,
}

unsafe impl<P> CommandBuffer for UnsafeCommandBuffer<P> where P: CommandPool {
    type Pool = P;

    #[inline]
    fn inner(&self) -> &UnsafeCommandBuffer<P> {
        self
    }

    #[inline]
    fn submit_check(&self, _: &GpuFuture, _: &Queue) -> Result<(), Box<Error>> {
        // Not our job to check.
        Ok(())
    }

    #[inline]
    fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue)
                           -> Result<Option<(PipelineStages, AccessFlagBits)>, ()>
    {
        Err(())
    }

    #[inline]
    fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue)
                          -> Result<Option<(PipelineStages, AccessFlagBits)>, ()>
    {
        Err(())
    }
}

unsafe impl<P> DeviceOwned for UnsafeCommandBuffer<P> where P: CommandPool {
    #[inline]
    fn device(&self) -> &Arc<Device> {
        &self.device
    }
}

unsafe impl<P> VulkanObject for UnsafeCommandBuffer<P> where P: CommandPool {
    type Object = vk::CommandBuffer;

    #[inline]
    fn internal_object(&self) -> vk::CommandBuffer {
        self.cmd
    }
}
<commit_msg>Some documentation for UnsafeCommandBufferBuilder<commit_after>// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use std::error::Error;
use std::ptr;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

use buffer::Buffer;
use command_buffer::CommandBuffer;
use command_buffer::cb::CommandBufferBuild;
use command_buffer::pool::AllocatedCommandBuffer;
use command_buffer::pool::CommandPool;
use device::Device;
use device::DeviceOwned;
use device::Queue;
use framebuffer::EmptySinglePassRenderPassDesc;
use framebuffer::Framebuffer;
use framebuffer::FramebufferAbstract;
use framebuffer::RenderPass;
use framebuffer::RenderPassAbstract;
use framebuffer::Subpass;
use image::Image;
use sync::AccessFlagBits;
use sync::PipelineStages;
use sync::GpuFuture;
use OomError;
use VulkanObject;
use VulkanPointers;
use check_errors;
use vk;

/// Determines the kind of command buffer that we want to create.
#[derive(Debug, Clone)]
pub enum Kind<R, F> {
    /// A primary command buffer can execute all commands and can call secondary command buffers.
    Primary,

    /// A secondary command buffer can execute all dispatch and transfer operations, but not
    /// drawing operations.
    Secondary,

    /// A secondary command buffer within a render pass can only call draw operations that can
    /// be executed from within a specific subpass.
    SecondaryRenderPass {
        /// Which subpass this secondary command buffer can be called from.
        subpass: Subpass<R>,

        /// The framebuffer object that will be used when calling the command buffer.
        /// This parameter is optional and is an optimization hint for the implementation.
        framebuffer: Option<F>,
    },
}

impl Kind<RenderPass<EmptySinglePassRenderPassDesc>, Framebuffer<RenderPass<EmptySinglePassRenderPassDesc>, ()>> {
    /// Equivalent to `Kind::Primary`.
    ///
    /// > **Note**: If you use `let kind = Kind::Primary;` in your code, you will probably get a
    /// > compilation error because the Rust compiler couldn't determine the template parameters
    /// > of `Kind`. To solve that problem in an easy way you can use this function instead.
    #[inline]
    pub fn primary() -> Kind<RenderPass<EmptySinglePassRenderPassDesc>, Framebuffer<RenderPass<EmptySinglePassRenderPassDesc>, ()>> {
        Kind::Primary
    }
}

/// Flags to pass when creating a command buffer.
///
/// The safest option is `SimultaneousUse`, but it may be slower than the other two.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Flags {
    /// The command buffer can be used multiple times, but must not execute more than once
    /// simultaneously.
    None,

    /// The command buffer can be executed multiple times in parallel.
    SimultaneousUse,

    /// The command buffer can only be submitted once. Any further submit is forbidden.
    OneTimeSubmit,
}

/// Command buffer being built.
///
/// You can add commands to an `UnsafeCommandBufferBuilder` by using the `AddCommand` trait.
/// The `AddCommand<&Cmd>` trait is implemented on the `UnsafeCommandBufferBuilder` for any `Cmd`
/// that is a raw Vulkan command.
///
/// When you are finished adding commands, you can use the `CommandBufferBuild` trait to turn this
/// builder into an `UnsafeCommandBuffer`.
pub struct UnsafeCommandBufferBuilder<P> where P: CommandPool {
    // The Vulkan command buffer. Will be 0 if `build()` has been called.
    cmd: vk::CommandBuffer,

    // Device that owns the command buffer.
    device: Arc<Device>,

    // Pool that owns the command buffer.
    pool: Option<P>,

    // Flags that were used at creation.
    flags: Flags,

    // True if we are a secondary command buffer.
    secondary_cb: bool,
}

impl<P> UnsafeCommandBufferBuilder<P> where P: CommandPool {
    /// Creates a new builder.
    ///
    /// # Safety
    ///
    /// Creating and destroying an unsafe command buffer is not unsafe per se, but the commands
    /// that you add to it are unchecked, do not have any synchronization, and are not kept alive.
    ///
    /// In other words, it is your job to make sure that the commands you add are valid, that they
    /// don't use resources that have been destroyed, and that they do not introduce any race
    /// condition.
    ///
    /// > **Note**: Some checks are still made with `debug_assert!`. Do not expect to be able to
    /// > submit invalid commands.
    pub unsafe fn new<R, F>(pool: P, kind: Kind<R, F>, flags: Flags)
                            -> Result<UnsafeCommandBufferBuilder<P>, OomError>
        where R: RenderPassAbstract, F: FramebufferAbstract
    {
        let secondary = match kind {
            Kind::Primary => false,
            Kind::Secondary | Kind::SecondaryRenderPass { .. } => true,
        };

        let cmd = try!(pool.alloc(secondary, 1)).next().unwrap();

        match UnsafeCommandBufferBuilder::already_allocated(pool, cmd, kind, flags) {
            Ok(cmd) => Ok(cmd),
            Err(err) => {
                // FIXME: uncomment this and solve the fact that `pool` has been moved
                //unsafe { pool.free(secondary, Some(cmd.into()).into_iter()) };
                Err(err)
            },
        }
    }

    /// Creates a new command buffer builder from an already-allocated command buffer.
    ///
    /// # Safety
    ///
    /// See the `new` method.
    ///
    /// The allocated command buffer must belong to the pool and must not be used anywhere else
    /// in the code for the duration of this command buffer.
    ///
    pub unsafe fn already_allocated<R, F>(pool: P, cmd: AllocatedCommandBuffer,
                                          kind: Kind<R, F>, flags: Flags)
                                          -> Result<UnsafeCommandBufferBuilder<P>, OomError>
        where R: RenderPassAbstract, F: FramebufferAbstract
    {
        let device = pool.device().clone();
        let vk = device.pointers();
        let cmd = cmd.internal_object();

        let vk_flags = {
            let a = match flags {
                Flags::None => 0,
                Flags::SimultaneousUse => vk::COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
                Flags::OneTimeSubmit => vk::COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
            };

            let b = match kind {
                Kind::Primary | Kind::Secondary => 0,
                Kind::SecondaryRenderPass { .. } => {
                    vk::COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT
                },
            };

            a | b
        };

        let (rp, sp) = if let Kind::SecondaryRenderPass { ref subpass, .. } = kind {
            (subpass.render_pass().inner().internal_object(), subpass.index())
        } else {
            (0, 0)
        };

        let framebuffer = if let Kind::SecondaryRenderPass { ref subpass, framebuffer: Some(ref framebuffer) } = kind {
            // TODO: restore check
            //assert!(framebuffer.is_compatible_with(subpass.render_pass()));     // TODO: proper error
            FramebufferAbstract::inner(&framebuffer).internal_object()
        } else {
            0
        };

        let inheritance = vk::CommandBufferInheritanceInfo {
            sType: vk::STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO,
            pNext: ptr::null(),
            renderPass: rp,
            subpass: sp,
            framebuffer: framebuffer,
            occlusionQueryEnable: 0,            // TODO:
            queryFlags: 0,          // TODO:
            pipelineStatistics: 0,          // TODO:
        };

        let infos = vk::CommandBufferBeginInfo {
            sType: vk::STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
            pNext: ptr::null(),
            flags: vk_flags,
            pInheritanceInfo: &inheritance,
        };

        try!(check_errors(vk.BeginCommandBuffer(cmd, &infos)));

        Ok(UnsafeCommandBufferBuilder {
            device: device.clone(),
            pool: Some(pool),
            cmd: cmd,
            flags: flags,
            secondary_cb: match kind {
                Kind::Primary => false,
                Kind::Secondary | Kind::SecondaryRenderPass { .. } => true,
            },
        })
    }
}

unsafe impl<P> DeviceOwned for UnsafeCommandBufferBuilder<P> where P: CommandPool {
    #[inline]
    fn device(&self) -> &Arc<Device> {
        &self.device
    }
}

unsafe impl<P> VulkanObject for UnsafeCommandBufferBuilder<P> where P: CommandPool {
    type Object = vk::CommandBuffer;

    #[inline]
    fn internal_object(&self) -> vk::CommandBuffer {
        self.cmd
    }
}

impl<P> Drop for UnsafeCommandBufferBuilder<P> where P: CommandPool {
    #[inline]
    fn drop(&mut self) {
        //unsafe {
            if self.cmd == 0 {
                return;
            }

            // FIXME: vk.FreeCommandBuffers()
        //}
    }
}

unsafe impl<P> CommandBufferBuild for UnsafeCommandBufferBuilder<P>
    where P: CommandPool
{
    type Out = UnsafeCommandBuffer<P>;

    #[inline]
    fn build(mut self) -> Self::Out {
        unsafe {
            debug_assert_ne!(self.cmd, 0);
            let cmd = self.cmd;
            let vk = self.device.pointers();
            check_errors(vk.EndCommandBuffer(cmd)).unwrap();       // TODO: handle error
            self.cmd = 0;       // Prevents the `Drop` impl of the builder from destroying the cb.

            UnsafeCommandBuffer {
                cmd: cmd,
                device: self.device.clone(),
                pool: self.pool.take().unwrap().finish(),
                flags: self.flags,
                already_submitted: AtomicBool::new(false),
            }
        }
    }
}

/// Command buffer that has been built.
///
/// Doesn't perform any synchronization and doesn't keep the object it uses alive.
pub struct UnsafeCommandBuffer<P> where P: CommandPool {
    // The Vulkan command buffer.
    cmd: vk::CommandBuffer,

    // Device that owns the command buffer.
    device: Arc<Device>,

    // Pool that owns the command buffer.
    pool: P::Finished,

    // Flags that were used at creation.
    flags: Flags,

    // True if the command buffer has always been submitted once. Only relevant if `flags` is
    // `OneTimeSubmit`.
    already_submitted: AtomicBool,
}

unsafe impl<P> CommandBuffer for UnsafeCommandBuffer<P> where P: CommandPool {
    type Pool = P;

    #[inline]
    fn inner(&self) -> &UnsafeCommandBuffer<P> {
        self
    }

    #[inline]
    fn submit_check(&self, _: &GpuFuture, _: &Queue) -> Result<(), Box<Error>> {
        // Not our job to check.
        Ok(())
    }

    #[inline]
    fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue)
                           -> Result<Option<(PipelineStages, AccessFlagBits)>, ()>
    {
        Err(())
    }

    #[inline]
    fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue)
                          -> Result<Option<(PipelineStages, AccessFlagBits)>, ()>
    {
        Err(())
    }
}

unsafe impl<P> DeviceOwned for UnsafeCommandBuffer<P> where P: CommandPool {
    #[inline]
    fn device(&self) -> &Arc<Device> {
        &self.device
    }
}

unsafe impl<P> VulkanObject for UnsafeCommandBuffer<P> where P: CommandPool {
    type Object = vk::CommandBuffer;

    #[inline]
    fn internal_object(&self) -> vk::CommandBuffer {
        self.cmd
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 Ted Mielczarek. See the COPYRIGHT
// file at the top-level directory of this distribution.

// NOTE: arm64_old.rs and arm64.rs should be identical except for the names of
// their context types.

use crate::process_state::{FrameTrust, StackFrame};
use crate::stackwalker::unwind::Unwind;
use crate::stackwalker::CfiStackWalker;
use crate::SymbolProvider;
use log::trace;
use minidump::{
    CpuContext, MinidumpContext, MinidumpContextValidity, MinidumpMemory, MinidumpModuleList,
    MinidumpRawContext,
};
use std::collections::HashSet;

type ArmContext = minidump::format::CONTEXT_ARM64_OLD;
type Pointer = <ArmContext as CpuContext>::Register;
type Registers = minidump::format::Arm64RegisterNumbers;

const POINTER_WIDTH: Pointer = std::mem::size_of::<Pointer>() as Pointer;
const FRAME_POINTER: &str = Registers::FramePointer.name();
const STACK_POINTER: &str = Registers::StackPointer.name();
const LINK_REGISTER: &str = Registers::LinkRegister.name();
const PROGRAM_COUNTER: &str = Registers::ProgramCounter.name();
const CALLEE_SAVED_REGS: &[&str] = &[
    "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29",
];

fn get_caller_by_frame_pointer<P>(
    ctx: &ArmContext,
    valid: &MinidumpContextValidity,
    _trust: FrameTrust,
    stack_memory: &MinidumpMemory,
    grand_callee_frame: Option<&StackFrame>,
    modules: &MinidumpModuleList,
    symbol_provider: &P,
) -> Option<StackFrame>
where
    P: SymbolProvider,
{
    // Assume that the standard %fp-using ARM64 calling convention is in use.
    // The main quirk of this ABI is that the return address doesn't need to
    // be restored from the stack -- it's already in the link register (lr).
    // But that means we need to save/restore lr itself so that the *caller's*
    // return address can be recovered.
    //
    // In the standard calling convention, the following happens:
    //
    // PUSH fp, lr    (save fp and lr to the stack -- ARM64 pushes in pairs)
    // fp := sp       (update the frame pointer to the current stack pointer)
    // lr := pc       (save the return address in the link register)
    //
    // So to restore the caller's registers, we have:
    //
    // pc := lr
    // sp := fp + ptr*2
    // lr := *(fp + ptr)
    // fp := *fp

    let last_fp = ctx.get_register(FRAME_POINTER, valid)?;
    let last_sp = ctx.get_register(STACK_POINTER, valid)?;
    let last_lr = match ctx.get_register(LINK_REGISTER, valid) {
        Some(lr) => lr,
        None => {
            // TODO: it would be good to write this back to the callee's ctx/validity
            get_link_register_by_frame_pointer(ctx, valid, stack_memory, grand_callee_frame)?
        }
    };

    let caller_fp = stack_memory.get_memory_at_address(last_fp as u64)?;
    let caller_lr = stack_memory.get_memory_at_address(last_fp + POINTER_WIDTH as u64)?;
    let caller_lr = ptr_auth_strip(caller_lr);
    let caller_pc = last_lr;

    // TODO: why does breakpad do this? How could we get this far with a null fp?
    let caller_sp = if last_fp == 0 {
        last_sp
    } else {
        last_fp + POINTER_WIDTH * 2
    };

    // TODO: restore all the other callee-save registers that weren't touched.
    // unclear: does this mean we need to be aware of ".undef" entries at this point?

    // Breakpad's tests don't like it we validate the frame pointer's value,
    // so we don't check that.

    // Don't accept obviously wrong instruction pointers.
    if !instruction_seems_valid(caller_pc, modules, symbol_provider) {
        return None;
    }

    // Don't accept obviously wrong stack pointers.
    if !stack_seems_valid(caller_sp, last_sp, stack_memory) {
        return None;
    }

    let mut caller_ctx = ArmContext::default();
    caller_ctx.set_register(PROGRAM_COUNTER, caller_pc);
    caller_ctx.set_register(LINK_REGISTER, caller_lr);
    caller_ctx.set_register(FRAME_POINTER, caller_fp);
    caller_ctx.set_register(STACK_POINTER, caller_sp);

    let mut valid = HashSet::new();
    valid.insert(PROGRAM_COUNTER);
    valid.insert(LINK_REGISTER);
    valid.insert(FRAME_POINTER);
    valid.insert(STACK_POINTER);

    let context = MinidumpContext {
        raw: MinidumpRawContext::OldArm64(caller_ctx),
        valid: MinidumpContextValidity::Some(valid),
    };
    let mut frame = StackFrame::from_context(context, FrameTrust::FramePointer);
    adjust_instruction(&mut frame, caller_pc);
    Some(frame)
}

/// Restores the callee's link register from the stack.
fn get_link_register_by_frame_pointer(
    ctx: &ArmContext,
    valid: &MinidumpContextValidity,
    stack_memory: &MinidumpMemory,
    grand_callee_frame: Option<&StackFrame>,
) -> Option<Pointer> {
    // It may happen that whatever unwinding strategy we're using managed to
    // restore %fp but didn't restore %lr. Frame-pointer-based unwinding requires
    // %lr because it contains the return address (the caller's %pc).
    //
    // In the standard ARM64 calling convention %fp and %lr are pushed together,
    // so if the grand-callee appears to have been called with that convention
    // then we can recover %lr using its %fp.

    // We need the grand_callee_frame's frame pointer
    let grand_callee_frame = grand_callee_frame?;
    let last_last_fp = if let MinidumpRawContext::OldArm64(ref ctx) = grand_callee_frame.context.raw
    {
        ctx.get_register(FRAME_POINTER, &grand_callee_frame.context.valid)?
    } else {
        return None;
    };
    let presumed_last_fp: Pointer = stack_memory.get_memory_at_address(last_last_fp as u64)?;

    // Make sure fp and sp aren't obviously garbage (are well-ordered)
    let last_fp = ctx.get_register(FRAME_POINTER, valid)?;
    let last_sp = ctx.get_register(STACK_POINTER, valid)?;
    if last_fp <= last_sp {
        return None;
    }

    // Make sure the grand-callee and callee agree on the value of fp
    if presumed_last_fp != last_fp {
        return None;
    }

    // Now that we're pretty confident that frame pointers are valid, restore
    // the callee's %lr, which should be right next to where its %fp is saved.
    let last_lr = stack_memory.get_memory_at_address(last_last_fp + POINTER_WIDTH)?;

    Some(ptr_auth_strip(last_lr))
}

fn ptr_auth_strip(ptr: Pointer) -> Pointer {
    // TODO: attempt to remove ARM pointer authentication obfuscation
    ptr
}

fn get_caller_by_cfi<P>(
    ctx: &ArmContext,
    valid: &MinidumpContextValidity,
    _trust: FrameTrust,
    stack_memory: &MinidumpMemory,
    grand_callee_frame: Option<&StackFrame>,
    modules: &MinidumpModuleList,
    symbol_provider: &P,
) -> Option<StackFrame>
where
    P: SymbolProvider,
{
    trace!("trying to get frame by cfi");

    let last_sp = ctx.get_register(STACK_POINTER, valid)?;
    let last_pc = ctx.get_register(PROGRAM_COUNTER, valid)?;

    trace!("  ...context was good");

    let module = modules.module_at_address(last_pc as u64)?;
    trace!("  ...found module");

    let grand_callee_parameter_size = grand_callee_frame
        .and_then(|f| f.parameter_size)
        .unwrap_or(0);

    let mut stack_walker = CfiStackWalker {
        instruction: last_pc as u64,
        grand_callee_parameter_size,

        callee_ctx: ctx,
        callee_validity: valid,

        // Default to forwarding all callee-saved regs verbatim.
        // The CFI evaluator may clear or overwrite these values.
        // The stack pointer and instruction pointer are not included.
        caller_ctx: ctx.clone(),
        caller_validity: callee_forwarded_regs(valid),

        stack_memory,
    };

    symbol_provider.walk_frame(module, &mut stack_walker)?;

    let caller_pc = stack_walker.caller_ctx.get_register_always(PROGRAM_COUNTER);
    let caller_sp = stack_walker.caller_ctx.get_register_always(STACK_POINTER);

    // Don't accept obviously wrong instruction pointers.
    if !instruction_seems_valid(caller_pc, modules, symbol_provider) {
        return None;
    }
    // Don't accept obviously wrong stack pointers.
    if !stack_seems_valid(caller_sp, last_sp, stack_memory) {
        return None;
    }

    let context = MinidumpContext {
        raw: MinidumpRawContext::OldArm64(stack_walker.caller_ctx),
        valid: MinidumpContextValidity::Some(stack_walker.caller_validity),
    };
    let mut frame = StackFrame::from_context(context, FrameTrust::CallFrameInfo);
    adjust_instruction(&mut frame, caller_pc);
    Some(frame)
}

fn callee_forwarded_regs(valid: &MinidumpContextValidity) -> HashSet<&'static str> {
    match valid {
        MinidumpContextValidity::All => CALLEE_SAVED_REGS.iter().copied().collect(),
        MinidumpContextValidity::Some(ref which) => CALLEE_SAVED_REGS
            .iter()
            .filter(|&reg| which.contains(reg))
            .copied()
            .collect(),
    }
}

fn get_caller_by_scan<P>(
    ctx: &ArmContext,
    valid: &MinidumpContextValidity,
    trust: FrameTrust,
    stack_memory: &MinidumpMemory,
    modules: &MinidumpModuleList,
    symbol_provider: &P,
) -> Option<StackFrame>
where
    P: SymbolProvider,
{
    // Stack scanning is just walking from the end of the frame until we encounter
    // a value on the stack that looks like a pointer into some code (it's an address
    // in a range covered by one of our modules). If we find such an instruction,
    // we assume it's an pc value that was pushed by the CALL instruction that created
    // the current frame. The next frame is then assumed to end just before that
    // pc value.
    let last_sp = ctx.get_register(STACK_POINTER, valid)?;

    // Number of pointer-sized values to scan through in our search.
    let default_scan_range = 40;
    let extended_scan_range = default_scan_range * 4;

    // Breakpad devs found that the first frame of an unwind can be really messed up,
    // and therefore benefits from a longer scan. Let's do it too.
    let scan_range = if let FrameTrust::Context = trust {
        extended_scan_range
    } else {
        default_scan_range
    };

    for i in 0..scan_range {
        let address_of_pc = last_sp + i * POINTER_WIDTH;
        let caller_pc = stack_memory.get_memory_at_address(address_of_pc as u64)?;
        if instruction_seems_valid(caller_pc, modules, symbol_provider) {
            // pc is pushed by CALL, so sp is just address_of_pc + ptr
            let caller_sp = address_of_pc + POINTER_WIDTH;

            // Don't do any more validation, and don't try to restore fp
            // (that's what breakpad does!)

            let mut caller_ctx = ArmContext::default();
            caller_ctx.set_register(PROGRAM_COUNTER, caller_pc);
            caller_ctx.set_register(STACK_POINTER, caller_sp);

            let mut valid = HashSet::new();
            valid.insert(PROGRAM_COUNTER);
            valid.insert(STACK_POINTER);

            let context = MinidumpContext {
                raw: MinidumpRawContext::OldArm64(caller_ctx),
                valid: MinidumpContextValidity::Some(valid),
            };
            let mut frame = StackFrame::from_context(context, FrameTrust::Scan);
            adjust_instruction(&mut frame, caller_pc);
            return Some(frame);
        }
    }

    None
}

#[allow(clippy::match_like_matches_macro)]
fn instruction_seems_valid<P>(
    instruction: Pointer,
    modules: &MinidumpModuleList,
    _symbol_provider: &P,
) -> bool
where
    P: SymbolProvider,
{
    // Reject instructions in the first page or above the user-space threshold.
    if !(0x1000..=0x000fffffffffffff).contains(&instruction) {
        return false;
    }

    if let Some(_module) = modules.module_at_address(instruction as u64) {
        // TODO: if mapped, check if this instruction actually maps to a function line
        true
    } else {
        false
    }
}

fn stack_seems_valid(
    caller_sp: Pointer,
    callee_sp: Pointer,
    stack_memory: &MinidumpMemory,
) -> bool {
    // The stack shouldn't *grow* when we unwind
    if caller_sp < callee_sp {
        return false;
    }

    // The stack pointer should be in the stack
    stack_memory
        .get_memory_at_address::<Pointer>(caller_sp as u64)
        .is_some()
}

fn adjust_instruction(frame: &mut StackFrame, caller_pc: Pointer) {
    // A caller's pc is the return address, which is the instruction
    // after the CALL that caused us to arrive at the callee. Set
    // the value to one less than that, so it points within the
    // CALL instruction.
    if caller_pc > 0 {
        frame.instruction = caller_pc as u64 - 1;
    }
}

impl Unwind for ArmContext {
    fn get_caller_frame<P>(
        &self,
        valid: &MinidumpContextValidity,
        trust: FrameTrust,
        stack_memory: Option<&MinidumpMemory>,
        grand_callee_frame: Option<&StackFrame>,
        modules: &MinidumpModuleList,
        syms: &P,
    ) -> Option<StackFrame>
    where
        P: SymbolProvider,
    {
        stack_memory
            .as_ref()
            .and_then(|stack| {
                get_caller_by_cfi(self, valid, trust, stack, grand_callee_frame, modules, syms)
                    .or_else(|| {
                        get_caller_by_frame_pointer(
                            self,
                            valid,
                            trust,
                            stack,
                            grand_callee_frame,
                            modules,
                            syms,
                        )
                    })
                    .or_else(|| get_caller_by_scan(self, valid, trust, stack, modules, syms))
            })
            .and_then(|frame| {
                // Treat an instruction address of 0 as end-of-stack.
                if frame.context.get_instruction_pointer() == 0 {
                    return None;
                }
                // If the new stack pointer is at a lower address than the old,
                // then that's clearly incorrect. Treat this as end-of-stack to
                // enforce progress and avoid infinite loops.
                if frame.context.get_stack_pointer() < self.get_register_always("sp") {
                    return None;
                }
                Some(frame)
            })
    }
}
<commit_msg>fixup clippy<commit_after>// Copyright 2015 Ted Mielczarek. See the COPYRIGHT
// file at the top-level directory of this distribution.

// NOTE: arm64_old.rs and arm64.rs should be identical except for the names of
// their context types.

use crate::process_state::{FrameTrust, StackFrame};
use crate::stackwalker::unwind::Unwind;
use crate::stackwalker::CfiStackWalker;
use crate::SymbolProvider;
use log::trace;
use minidump::{
    CpuContext, MinidumpContext, MinidumpContextValidity, MinidumpMemory, MinidumpModuleList,
    MinidumpRawContext,
};
use std::collections::HashSet;

type ArmContext = minidump::format::CONTEXT_ARM64_OLD;
type Pointer = <ArmContext as CpuContext>::Register;
type Registers = minidump::format::Arm64RegisterNumbers;

const POINTER_WIDTH: Pointer = std::mem::size_of::<Pointer>() as Pointer;
const FRAME_POINTER: &str = Registers::FramePointer.name();
const STACK_POINTER: &str = Registers::StackPointer.name();
const LINK_REGISTER: &str = Registers::LinkRegister.name();
const PROGRAM_COUNTER: &str = Registers::ProgramCounter.name();
const CALLEE_SAVED_REGS: &[&str] = &[
    "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29",
];

fn get_caller_by_frame_pointer<P>(
    ctx: &ArmContext,
    valid: &MinidumpContextValidity,
    _trust: FrameTrust,
    stack_memory: &MinidumpMemory,
    grand_callee_frame: Option<&StackFrame>,
    modules: &MinidumpModuleList,
    symbol_provider: &P,
) -> Option<StackFrame>
where
    P: SymbolProvider,
{
    // Assume that the standard %fp-using ARM64 calling convention is in use.
    // The main quirk of this ABI is that the return address doesn't need to
    // be restored from the stack -- it's already in the link register (lr).
    // But that means we need to save/restore lr itself so that the *caller's*
    // return address can be recovered.
    //
    // In the standard calling convention, the following happens:
    //
    // PUSH fp, lr    (save fp and lr to the stack -- ARM64 pushes in pairs)
    // fp := sp       (update the frame pointer to the current stack pointer)
    // lr := pc       (save the return address in the link register)
    //
    // So to restore the caller's registers, we have:
    //
    // pc := lr
    // sp := fp + ptr*2
    // lr := *(fp + ptr)
    // fp := *fp

    let last_fp = ctx.get_register(FRAME_POINTER, valid)?;
    let last_sp = ctx.get_register(STACK_POINTER, valid)?;
    let last_lr = match ctx.get_register(LINK_REGISTER, valid) {
        Some(lr) => lr,
        None => {
            // TODO: it would be good to write this back to the callee's ctx/validity
            get_link_register_by_frame_pointer(ctx, valid, stack_memory, grand_callee_frame)?
        }
    };

    let caller_fp = stack_memory.get_memory_at_address(last_fp as u64)?;
    let caller_lr = stack_memory.get_memory_at_address(last_fp + POINTER_WIDTH as u64)?;
    let caller_lr = ptr_auth_strip(caller_lr);
    let caller_pc = last_lr;

    // TODO: why does breakpad do this? How could we get this far with a null fp?
    let caller_sp = if last_fp == 0 {
        last_sp
    } else {
        last_fp + POINTER_WIDTH * 2
    };

    // TODO: restore all the other callee-save registers that weren't touched.
    // unclear: does this mean we need to be aware of ".undef" entries at this point?

    // Breakpad's tests don't like it we validate the frame pointer's value,
    // so we don't check that.

    // Don't accept obviously wrong instruction pointers.
    if !instruction_seems_valid(caller_pc, modules, symbol_provider) {
        return None;
    }

    // Don't accept obviously wrong stack pointers.
    if !stack_seems_valid(caller_sp, last_sp, stack_memory) {
        return None;
    }

    let mut caller_ctx = ArmContext::default();
    caller_ctx.set_register(PROGRAM_COUNTER, caller_pc);
    caller_ctx.set_register(LINK_REGISTER, caller_lr);
    caller_ctx.set_register(FRAME_POINTER, caller_fp);
    caller_ctx.set_register(STACK_POINTER, caller_sp);

    let mut valid = HashSet::new();
    valid.insert(PROGRAM_COUNTER);
    valid.insert(LINK_REGISTER);
    valid.insert(FRAME_POINTER);
    valid.insert(STACK_POINTER);

    let context = MinidumpContext {
        raw: MinidumpRawContext::OldArm64(caller_ctx),
        valid: MinidumpContextValidity::Some(valid),
    };
    let mut frame = StackFrame::from_context(context, FrameTrust::FramePointer);
    adjust_instruction(&mut frame, caller_pc);
    Some(frame)
}

/// Restores the callee's link register from the stack.
fn get_link_register_by_frame_pointer(
    ctx: &ArmContext,
    valid: &MinidumpContextValidity,
    stack_memory: &MinidumpMemory,
    grand_callee_frame: Option<&StackFrame>,
) -> Option<Pointer> {
    // It may happen that whatever unwinding strategy we're using managed to
    // restore %fp but didn't restore %lr. Frame-pointer-based unwinding requires
    // %lr because it contains the return address (the caller's %pc).
    //
    // In the standard ARM64 calling convention %fp and %lr are pushed together,
    // so if the grand-callee appears to have been called with that convention
    // then we can recover %lr using its %fp.

    // We need the grand_callee_frame's frame pointer
    let grand_callee_frame = grand_callee_frame?;
    let last_last_fp = if let MinidumpRawContext::OldArm64(ref ctx) = grand_callee_frame.context.raw
    {
        ctx.get_register(FRAME_POINTER, &grand_callee_frame.context.valid)?
    } else {
        return None;
    };
    let presumed_last_fp: Pointer = stack_memory.get_memory_at_address(last_last_fp as u64)?;

    // Make sure fp and sp aren't obviously garbage (are well-ordered)
    let last_fp = ctx.get_register(FRAME_POINTER, valid)?;
    let last_sp = ctx.get_register(STACK_POINTER, valid)?;
    if last_fp <= last_sp {
        return None;
    }

    // Make sure the grand-callee and callee agree on the value of fp
    if presumed_last_fp != last_fp {
        return None;
    }

    // Now that we're pretty confident that frame pointers are valid, restore
    // the callee's %lr, which should be right next to where its %fp is saved.
    let last_lr = stack_memory.get_memory_at_address(last_last_fp + POINTER_WIDTH)?;

    Some(ptr_auth_strip(last_lr))
}

fn ptr_auth_strip(ptr: Pointer) -> Pointer {
    // TODO: attempt to remove ARM pointer authentication obfuscation
    ptr
}

fn get_caller_by_cfi<P>(
    ctx: &ArmContext,
    valid: &MinidumpContextValidity,
    _trust: FrameTrust,
    stack_memory: &MinidumpMemory,
    grand_callee_frame: Option<&StackFrame>,
    modules: &MinidumpModuleList,
    symbol_provider: &P,
) -> Option<StackFrame>
where
    P: SymbolProvider,
{
    trace!("trying to get frame by cfi");

    let last_sp = ctx.get_register(STACK_POINTER, valid)?;
    let last_pc = ctx.get_register(PROGRAM_COUNTER, valid)?;

    trace!("  ...context was good");

    let module = modules.module_at_address(last_pc as u64)?;
    trace!("  ...found module");

    let grand_callee_parameter_size = grand_callee_frame
        .and_then(|f| f.parameter_size)
        .unwrap_or(0);

    let mut stack_walker = CfiStackWalker {
        instruction: last_pc as u64,
        grand_callee_parameter_size,

        callee_ctx: ctx,
        callee_validity: valid,

        // Default to forwarding all callee-saved regs verbatim.
        // The CFI evaluator may clear or overwrite these values.
        // The stack pointer and instruction pointer are not included.
        caller_ctx: *ctx,
        caller_validity: callee_forwarded_regs(valid),

        stack_memory,
    };

    symbol_provider.walk_frame(module, &mut stack_walker)?;

    let caller_pc = stack_walker.caller_ctx.get_register_always(PROGRAM_COUNTER);
    let caller_sp = stack_walker.caller_ctx.get_register_always(STACK_POINTER);

    // Don't accept obviously wrong instruction pointers.
    if !instruction_seems_valid(caller_pc, modules, symbol_provider) {
        return None;
    }
    // Don't accept obviously wrong stack pointers.
    if !stack_seems_valid(caller_sp, last_sp, stack_memory) {
        return None;
    }

    let context = MinidumpContext {
        raw: MinidumpRawContext::OldArm64(stack_walker.caller_ctx),
        valid: MinidumpContextValidity::Some(stack_walker.caller_validity),
    };
    let mut frame = StackFrame::from_context(context, FrameTrust::CallFrameInfo);
    adjust_instruction(&mut frame, caller_pc);
    Some(frame)
}

fn callee_forwarded_regs(valid: &MinidumpContextValidity) -> HashSet<&'static str> {
    match valid {
        MinidumpContextValidity::All => CALLEE_SAVED_REGS.iter().copied().collect(),
        MinidumpContextValidity::Some(ref which) => CALLEE_SAVED_REGS
            .iter()
            .filter(|&reg| which.contains(reg))
            .copied()
            .collect(),
    }
}

fn get_caller_by_scan<P>(
    ctx: &ArmContext,
    valid: &MinidumpContextValidity,
    trust: FrameTrust,
    stack_memory: &MinidumpMemory,
    modules: &MinidumpModuleList,
    symbol_provider: &P,
) -> Option<StackFrame>
where
    P: SymbolProvider,
{
    // Stack scanning is just walking from the end of the frame until we encounter
    // a value on the stack that looks like a pointer into some code (it's an address
    // in a range covered by one of our modules). If we find such an instruction,
    // we assume it's an pc value that was pushed by the CALL instruction that created
    // the current frame. The next frame is then assumed to end just before that
    // pc value.
    let last_sp = ctx.get_register(STACK_POINTER, valid)?;

    // Number of pointer-sized values to scan through in our search.
    let default_scan_range = 40;
    let extended_scan_range = default_scan_range * 4;

    // Breakpad devs found that the first frame of an unwind can be really messed up,
    // and therefore benefits from a longer scan. Let's do it too.
    let scan_range = if let FrameTrust::Context = trust {
        extended_scan_range
    } else {
        default_scan_range
    };

    for i in 0..scan_range {
        let address_of_pc = last_sp + i * POINTER_WIDTH;
        let caller_pc = stack_memory.get_memory_at_address(address_of_pc as u64)?;
        if instruction_seems_valid(caller_pc, modules, symbol_provider) {
            // pc is pushed by CALL, so sp is just address_of_pc + ptr
            let caller_sp = address_of_pc + POINTER_WIDTH;

            // Don't do any more validation, and don't try to restore fp
            // (that's what breakpad does!)

            let mut caller_ctx = ArmContext::default();
            caller_ctx.set_register(PROGRAM_COUNTER, caller_pc);
            caller_ctx.set_register(STACK_POINTER, caller_sp);

            let mut valid = HashSet::new();
            valid.insert(PROGRAM_COUNTER);
            valid.insert(STACK_POINTER);

            let context = MinidumpContext {
                raw: MinidumpRawContext::OldArm64(caller_ctx),
                valid: MinidumpContextValidity::Some(valid),
            };
            let mut frame = StackFrame::from_context(context, FrameTrust::Scan);
            adjust_instruction(&mut frame, caller_pc);
            return Some(frame);
        }
    }

    None
}

#[allow(clippy::match_like_matches_macro)]
fn instruction_seems_valid<P>(
    instruction: Pointer,
    modules: &MinidumpModuleList,
    _symbol_provider: &P,
) -> bool
where
    P: SymbolProvider,
{
    // Reject instructions in the first page or above the user-space threshold.
    if !(0x1000..=0x000fffffffffffff).contains(&instruction) {
        return false;
    }

    if let Some(_module) = modules.module_at_address(instruction as u64) {
        // TODO: if mapped, check if this instruction actually maps to a function line
        true
    } else {
        false
    }
}

fn stack_seems_valid(
    caller_sp: Pointer,
    callee_sp: Pointer,
    stack_memory: &MinidumpMemory,
) -> bool {
    // The stack shouldn't *grow* when we unwind
    if caller_sp < callee_sp {
        return false;
    }

    // The stack pointer should be in the stack
    stack_memory
        .get_memory_at_address::<Pointer>(caller_sp as u64)
        .is_some()
}

fn adjust_instruction(frame: &mut StackFrame, caller_pc: Pointer) {
    // A caller's pc is the return address, which is the instruction
    // after the CALL that caused us to arrive at the callee. Set
    // the value to one less than that, so it points within the
    // CALL instruction.
    if caller_pc > 0 {
        frame.instruction = caller_pc as u64 - 1;
    }
}

impl Unwind for ArmContext {
    fn get_caller_frame<P>(
        &self,
        valid: &MinidumpContextValidity,
        trust: FrameTrust,
        stack_memory: Option<&MinidumpMemory>,
        grand_callee_frame: Option<&StackFrame>,
        modules: &MinidumpModuleList,
        syms: &P,
    ) -> Option<StackFrame>
    where
        P: SymbolProvider,
    {
        stack_memory
            .as_ref()
            .and_then(|stack| {
                get_caller_by_cfi(self, valid, trust, stack, grand_callee_frame, modules, syms)
                    .or_else(|| {
                        get_caller_by_frame_pointer(
                            self,
                            valid,
                            trust,
                            stack,
                            grand_callee_frame,
                            modules,
                            syms,
                        )
                    })
                    .or_else(|| get_caller_by_scan(self, valid, trust, stack, modules, syms))
            })
            .and_then(|frame| {
                // Treat an instruction address of 0 as end-of-stack.
                if frame.context.get_instruction_pointer() == 0 {
                    return None;
                }
                // If the new stack pointer is at a lower address than the old,
                // then that's clearly incorrect. Treat this as end-of-stack to
                // enforce progress and avoid infinite loops.
                if frame.context.get_stack_pointer() < self.get_register_always("sp") {
                    return None;
                }
                Some(frame)
            })
    }
}
<|endoftext|>