<commit_before>//! Support for log4rs configuration from TOML files.
//!
//! # Syntax
//!
//! ```toml
//! # If set, log4rs will scan the file at the specified rate in seconds for
//! # changes and automatically reconfigure the logger.
//! refresh_rate = 30
//!
//! # Appenders are configured as tables inside the "appender" table. This
//! # appender is named "foo".
//! [appender.foo]
//! # All appenders must specify a "kind", which must match the kind of an
//! # appender mapping provided to the `Creator` used to deserialize the
//! # config file.
//! kind = "console"
//!
//! # Arbitrary fields may be added to appender configurations. Remaining
//! # entries will be passed to the `CreateAppender` object associated with
//! # the specified kind.
//! pattern = "%d [%t] %m"
//!
//! # Filters attached to an appender are configured inside the "filter" array.
//! [[appender.foo.filter]]
//! # Like appenders, filters must specify a "kind".
//! kind = "threshold"
//!
//! # Also like appenders, arbitrary fields may be added to filter
//! # configurations.
//! level = "error"
//!
//! # The root logger is configured by the "root" table. It is optional.
//! # If the "root" table is not specified, the root will default to a level of
//! # "debug" and no appenders.
//! [root]
//! # The maximum log level for the root logger. Must be specified if the
//! # "root" table is defined.
//! level = "warn"
//!
//! # The list of names of appenders attached to the root logger. If not
//! # specified, defaults to an empty list.
//! appenders = ["foo"]
//!
//! # Loggers are configured as tables inside of the "logger" array.
//! [[logger]]
//! # The name of the logger. Must be specified.
//! name = "foo::bar::baz"
//!
//! # The maximum . If it is not present, the level of the logger's parent is used.
//! level = "trace"
//!
//! # A list of names of appenders attached to the logger. If not specified,
//! # defaults to an empty list.
//! appenders = ["foo"]
//!
//! # The additivity of the logger. If true, the appenders attached to this
//! # logger's parent will also be attached to this logger. If not specified,
//! # defaults to true.
//! additive = false
//! ```
use log::LogLevelFilter;
use std::collections::HashMap;
use std::default::Default;
use std::error;
use std::fmt;
use time::Duration;
use toml_parser::{self, Value};

use appender::{FileAppender, ConsoleAppender};
use filter::{ThresholdFilter};
use config;
use pattern::PatternLayout;
use {Append, Filter, PrivateTomlConfigExt, PrivateConfigErrorsExt};

mod raw;

/// A trait implemented by types that can create appenders.
pub trait CreateAppender: Send + 'static {
    /// Creates an appender with the specified config.
    fn create_appender(&self, config: toml_parser::Table)
                       -> Result<Box<Append>, Box<error::Error>>;
}

/// A trait implemented by types that can create filters.
pub trait CreateFilter: Send + 'static {
    /// Creates a filter with the specified config.
    fn create_filter(&self, config: toml_parser::Table)
                     -> Result<Box<Filter>, Box<error::Error>>;
}

/// A type that can create appenders.
///
/// `Creator` implements `Default`, which returns a `Creator` with the
/// following mappings:
///
/// * Appenders
///     * "file" -> `FileAppenderCreator`
///     * "console" -> `ConsoleAppenderCreator`
/// * Filters
///     * "threshold" -> `ThresholdFilterCreator`
pub struct Creator {
    appenders: HashMap<String, Box<CreateAppender>>,
    filters: HashMap<String, Box<CreateFilter>>,
}

impl Default for Creator {
    fn default() -> Creator {
        let mut creator = Creator::new();
        creator.add_appender("file", Box::new(FileAppenderCreator));
        creator.add_appender("console", Box::new(ConsoleAppenderCreator));
        creator.add_filter("threshold", Box::new(ThresholdFilterCreator));
        creator
    }
}

impl Creator {
    /// Creates a new `Creator` with no appender or filter mappings.
    pub fn new() -> Creator {
        Creator {
            appenders: HashMap::new(),
            filters: HashMap::new(),
        }
    }

    /// Adds a mapping from the specified `kind` to the specified appender
    /// creator.
    pub fn add_appender(&mut self, kind: &str, creator: Box<CreateAppender>) {
        self.appenders.insert(kind.to_string(), creator);
    }

    /// Adds a mapping from the specified `kind` to the specified filter
    /// creator.
    pub fn add_filter(&mut self, kind: &str, creator: Box<CreateFilter>) {
        self.filters.insert(kind.to_string(), creator);
    }

    fn create_appender(&self, kind: &str, config: toml_parser::Table)
                       -> Result<Box<Append>, Box<error::Error>> {
        match self.appenders.get(kind) {
            Some(creator) => creator.create_appender(config),
            None => Err(Box::new(StringError(format!("No creator registered for appender kind \"{}\"", kind))))
        }
    }

    fn create_filter(&self, kind: &str, config: toml_parser::Table)
                     -> Result<Box<Filter>, Box<error::Error>> {
        match self.filters.get(kind) {
            Some(creator) => creator.create_filter(config),
            None => Err(Box::new(StringError(format!("No creator registered for filter kind \"{}\"", kind))))
        }
    }
}

/// Errors encountered when parsing a log4rs TOML config.
#[derive(Debug)]
pub struct ParseErrors {
    errors: Vec<String>,
}

impl fmt::Display for ParseErrors {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        for error in &self.errors {
            try!(writeln!(fmt, "{}", error));
        }
        Ok(())
    }
}

impl error::Error for ParseErrors {
    fn description(&self) -> &str {
        "Errors encountered when parsing a log4rs TOML config"
    }
}

/// An error returned when deserializing a TOML configuration into a log4rs `Config`.
#[derive(Debug)]
pub enum Error {
    /// An error instantiating an appender.
    AppenderCreation(String, Box<error::Error>),
    /// An error instantiating a filter.
    FilterCreation(String, Box<error::Error>),
    /// An error when creating the log4rs `Config`.
    Config(config::Error),
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::AppenderCreation(ref appender, ref err) => {
                write!(fmt, "Error creating appender `{}`: {}", appender, err)
            }
            Error::FilterCreation(ref appender, ref err) => {
                write!(fmt, "Error creating filter for appender `{}`: {}", appender, err)
            }
            Error::Config(ref err) => write!(fmt, "Error creating config: {}", err),
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        "An error encountered when deserializing a TOML configuration into a log4rs `Config`"
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::AppenderCreation(_, ref err) => Some(&**err),
            Error::FilterCreation(_, ref err) => Some(&**err),
            Error::Config(ref err) => Some(err),
        }
    }
}

/// Errors encountered when deserializing a TOML configuration into a log4rs `Config`.
#[derive(Debug)]
pub struct Errors {
    errors: Vec<Error>,
}

impl Errors {
    /// Returns the list of errors encountered.
    pub fn errors(&self) -> &[Error] {
        &self.errors
    }
}

impl fmt::Display for Errors {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        for error in &self.errors {
            try!(writeln!(fmt, "{}", error));
        }
        Ok(())
    }
}

impl error::Error for Errors {
    fn description(&self) -> &str {
        "Errors encountered when deserializing a TOML configuration into a log4rs `Config`"
    }
}

/// A deserialized TOML log4rs configuration.
pub struct Config {
    refresh_rate: Option<Duration>,
    config: config::Config,
}

impl Config {
    /// Creates a log4rs `Config` from the specified TOML config string and `Creator`.
    pub fn parse(config: &str, creator: &Creator)
                 -> Result<(Config, Result<(), Errors>), ParseErrors> {
        let mut errors = vec![];

        let config = match raw::parse(config) {
            Ok(config) => config,
            Err(errors) => return Err(ParseErrors { errors: errors }),
        };

        let raw::Config {
            refresh_rate,
            root: raw_root,
            appenders: raw_appenders,
            loggers: raw_loggers,
        } = config;

        let root = match raw_root {
            Some(raw_root) => {
                let mut root = config::Root::builder(raw_root.level);
                if let Some(appenders) = raw_root.appenders {
                    root = root.appenders(appenders);
                }
                root.build()
            }
            None => config::Root::builder(LogLevelFilter::Debug).build(),
        };

        let mut config = config::Config::builder(root);

        for (name, raw::Appender { kind, config: raw_config, filters }) in raw_appenders {
            match creator.create_appender(&kind, raw_config) {
                Ok(appender_obj) => {
                    let mut builder = config::Appender::builder(name.clone(), appender_obj);
                    for raw::Filter { kind, config } in filters.unwrap_or(vec![]) {
                        match creator.create_filter(&kind, config) {
                            Ok(filter) => builder = builder.filter(filter),
                            Err(err) => errors.push(Error::FilterCreation(name.clone(), err)),
                        }
                    }
                    config = config.appender(builder.build());
                }
                Err(err) => errors.push(Error::AppenderCreation(name, err)),
            }
        }

        for logger in raw_loggers {
            let raw::Logger { name, level, appenders, additive } = logger;
            let mut logger = config::Logger::builder(name, level);
            if let Some(appenders) = appenders {
                logger = logger.appenders(appenders);
            }
            if let Some(additive) = additive {
                logger = logger.additive(additive);
            }
            config = config.logger(logger.build());
        }

        let (config, config_errors) = config.build_lossy();
        if let Err(config_errors) = config_errors {
            for error in config_errors.unpack() {
                errors.push(Error::Config(error));
            }
        }

        let config = Config {
            refresh_rate: refresh_rate,
            config: config
        };

        let errors = if errors.is_empty() {
            Ok(())
        } else {
            Err(Errors {
                errors: errors
            })
        };

        Ok((config, errors))
    }

    /// Returns the requested refresh rate.
    pub fn refresh_rate(&self) -> Option<Duration> {
        self.refresh_rate
    }

    /// Returns the log4rs `Config`.
    pub fn config(&self) -> &config::Config {
        &self.config
    }
}

impl PrivateTomlConfigExt for Config {
    fn unpack(self) -> (Option<Duration>, config::Config) {
        let Config { refresh_rate, config } = self;
        (refresh_rate, config)
    }
}

#[derive(Debug)]
struct StringError(String);

impl fmt::Display for StringError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.write_str(&self.0)
    }
}

impl error::Error for StringError {
    fn description(&self) -> &str {
        &self.0
    }
}

impl From<String> for StringError {
    fn from(s: String) -> StringError {
        StringError(s)
    }
}

fn ensure_empty(config: &toml_parser::Table) -> Result<(), Box<error::Error>> {
    let remaining_keys: Vec<_> = config.keys().collect();
    if remaining_keys.is_empty() {
        Ok(())
    } else {
        Err(Box::new(StringError(format!("Unknown keys: {:?}", remaining_keys))))
    }
}

/// An appender creator for the `FileAppender`.
///
/// The `path` key is required, and specifies the path to the log file. The
/// `pattern` key is optional and specifies a `PatternLayout` pattern to be
/// used for output. The `append` key is optional and specifies whether the
/// output file should be truncated or appended to.
pub struct FileAppenderCreator;

impl CreateAppender for FileAppenderCreator {
    fn create_appender(&self, mut config: toml_parser::Table)
                       -> Result<Box<Append>, Box<error::Error>> {
        let path = match config.remove("path") {
            Some(Value::String(path)) => path,
            Some(_) => return Err(Box::new(StringError("`path` must be a string".to_string()))),
            None => return Err(Box::new(StringError("`path` is required".to_string()))),
        };

        let mut appender = FileAppender::builder(&path);
        match config.remove("pattern") {
            Some(Value::String(pattern)) => {
                appender = appender.pattern(try!(PatternLayout::new(&pattern)));
            }
            Some(_) => return Err(Box::new(StringError("`pattern` must be a string".to_string()))),
            None => {}
        }

        match config.remove("append") {
            Some(Value::Boolean(append)) => appender = appender.append(append),
            None => {}
            Some(_) => return Err(Box::new(StringError("`append` must be a bool".to_string()))),
        }

        try!(ensure_empty(&config));
        match appender.build() {
            Ok(appender) => Ok(Box::new(appender)),
            Err(err) => Err(Box::new(err))
        }
    }
}

/// An appender creator for the `ConsoleAppender`.
///
/// The `pattern` key is optional and specifies a `PatternLayout` pattern to be
/// used for output.
pub struct ConsoleAppenderCreator;

impl CreateAppender for ConsoleAppenderCreator {
    fn create_appender(&self, mut config: toml_parser::Table)
                       -> Result<Box<Append>, Box<error::Error>> {
        let mut appender = ConsoleAppender::builder();
        match config.remove("pattern") {
            Some(Value::String(pattern)) => {
                appender = appender.pattern(try!(PatternLayout::new(&pattern)));
            }
            Some(_) => return Err(Box::new(StringError("`pattern` must be a string".to_string()))),
            None => {}
        }

        try!(ensure_empty(&config));
        Ok(Box::new(appender.build()))
    }
}

/// A filter creator for the `ThresholdFilter`.
///
/// The `level` key is required and specifies the threshold for the filter.
pub struct ThresholdFilterCreator;

impl CreateFilter for ThresholdFilterCreator {
    fn create_filter(&self, mut config: toml_parser::Table)
                     -> Result<Box<Filter>, Box<error::Error>> {
        let level = match config.remove("level") {
            Some(Value::String(level)) => level,
            Some(_) => return Err(Box::new(StringError("`level` must be a string".to_string()))),
            None => return Err(Box::new(StringError("`level` must be provided".to_string()))),
        };

        let level = match level.parse() {
            Ok(level) => level,
            Err(_) => return Err(Box::new(StringError(format!("Invalid `level` \"{}\"", level)))),
        };

        try!(ensure_empty(&config));
        Ok(Box::new(ThresholdFilter::new(level)))
    }
}
<commit_msg>Fix doc typo<commit_after>//! Support for log4rs configuration from TOML files.
//!
//! # Syntax
//!
//! ```toml
//! # If set, log4rs will scan the file at the specified rate in seconds for
//! # changes and automatically reconfigure the logger.
//! refresh_rate = 30
//!
//! # Appenders are configured as tables inside the "appender" table. This
//! # appender is named "foo".
//! [appender.foo]
//! # All appenders must specify a "kind", which must match the kind of an
//! # appender mapping provided to the `Creator` used to deserialize the
//! # config file.
//! kind = "console"
//!
//! # Arbitrary fields may be added to appender configurations. Remaining
//! # entries will be passed to the `CreateAppender` object associated with
//! # the specified kind.
//! pattern = "%d [%t] %m"
//!
//! # Filters attached to an appender are configured inside the "filter" array.
//! [[appender.foo.filter]]
//! # Like appenders, filters must specify a "kind".
//! kind = "threshold"
//!
//! # Also like appenders, arbitrary fields may be added to filter
//! # configurations.
//! level = "error"
//!
//! # The root logger is configured by the "root" table. It is optional.
//! # If the "root" table is not specified, the root will default to a level of
//! # "debug" and no appenders.
//! [root]
//! # The maximum log level for the root logger. Must be specified if the
//! # "root" table is defined.
//! level = "warn"
//!
//! # The list of names of appenders attached to the root logger. If not
//! # specified, defaults to an empty list.
//! appenders = ["foo"]
//!
//! # Loggers are configured as tables inside of the "logger" array.
//! [[logger]]
//! # The name of the logger. Must be specified.
//! name = "foo::bar::baz"
//!
//! # The maximum log level. If it is not present, the level of the logger's
//! # parent is used.
//! level = "trace"
//!
//! # A list of names of appenders attached to the logger. If not specified,
//! # defaults to an empty list.
//! appenders = ["foo"]
//!
//! # The additivity of the logger. If true, the appenders attached to this
//! # logger's parent will also be attached to this logger. If not specified,
//! # defaults to true.
//! additive = false
//! ```
use log::LogLevelFilter;
use std::collections::HashMap;
use std::default::Default;
use std::error;
use std::fmt;
use time::Duration;
use toml_parser::{self, Value};

use appender::{FileAppender, ConsoleAppender};
use filter::{ThresholdFilter};
use config;
use pattern::PatternLayout;
use {Append, Filter, PrivateTomlConfigExt, PrivateConfigErrorsExt};

mod raw;

/// A trait implemented by types that can create appenders.
pub trait CreateAppender: Send + 'static {
    /// Creates an appender with the specified config.
    fn create_appender(&self, config: toml_parser::Table)
                       -> Result<Box<Append>, Box<error::Error>>;
}

/// A trait implemented by types that can create filters.
pub trait CreateFilter: Send + 'static {
    /// Creates a filter with the specified config.
    fn create_filter(&self, config: toml_parser::Table)
                     -> Result<Box<Filter>, Box<error::Error>>;
}

/// A type that can create appenders.
///
/// `Creator` implements `Default`, which returns a `Creator` with the
/// following mappings:
///
/// * Appenders
///     * "file" -> `FileAppenderCreator`
///     * "console" -> `ConsoleAppenderCreator`
/// * Filters
///     * "threshold" -> `ThresholdFilterCreator`
pub struct Creator {
    appenders: HashMap<String, Box<CreateAppender>>,
    filters: HashMap<String, Box<CreateFilter>>,
}

impl Default for Creator {
    fn default() -> Creator {
        let mut creator = Creator::new();
        creator.add_appender("file", Box::new(FileAppenderCreator));
        creator.add_appender("console", Box::new(ConsoleAppenderCreator));
        creator.add_filter("threshold", Box::new(ThresholdFilterCreator));
        creator
    }
}

impl Creator {
    /// Creates a new `Creator` with no appender or filter mappings.
    pub fn new() -> Creator {
        Creator {
            appenders: HashMap::new(),
            filters: HashMap::new(),
        }
    }

    /// Adds a mapping from the specified `kind` to the specified appender
    /// creator.
    pub fn add_appender(&mut self, kind: &str, creator: Box<CreateAppender>) {
        self.appenders.insert(kind.to_string(), creator);
    }

    /// Adds a mapping from the specified `kind` to the specified filter
    /// creator.
    pub fn add_filter(&mut self, kind: &str, creator: Box<CreateFilter>) {
        self.filters.insert(kind.to_string(), creator);
    }

    fn create_appender(&self, kind: &str, config: toml_parser::Table)
                       -> Result<Box<Append>, Box<error::Error>> {
        match self.appenders.get(kind) {
            Some(creator) => creator.create_appender(config),
            None => Err(Box::new(StringError(format!("No creator registered for appender kind \"{}\"", kind))))
        }
    }

    fn create_filter(&self, kind: &str, config: toml_parser::Table)
                     -> Result<Box<Filter>, Box<error::Error>> {
        match self.filters.get(kind) {
            Some(creator) => creator.create_filter(config),
            None => Err(Box::new(StringError(format!("No creator registered for filter kind \"{}\"", kind))))
        }
    }
}

/// Errors encountered when parsing a log4rs TOML config.
#[derive(Debug)]
pub struct ParseErrors {
    errors: Vec<String>,
}

impl fmt::Display for ParseErrors {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        for error in &self.errors {
            try!(writeln!(fmt, "{}", error));
        }
        Ok(())
    }
}

impl error::Error for ParseErrors {
    fn description(&self) -> &str {
        "Errors encountered when parsing a log4rs TOML config"
    }
}

/// An error returned when deserializing a TOML configuration into a log4rs `Config`.
#[derive(Debug)]
pub enum Error {
    /// An error instantiating an appender.
    AppenderCreation(String, Box<error::Error>),
    /// An error instantiating a filter.
    FilterCreation(String, Box<error::Error>),
    /// An error when creating the log4rs `Config`.
    Config(config::Error),
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::AppenderCreation(ref appender, ref err) => {
                write!(fmt, "Error creating appender `{}`: {}", appender, err)
            }
            Error::FilterCreation(ref appender, ref err) => {
                write!(fmt, "Error creating filter for appender `{}`: {}", appender, err)
            }
            Error::Config(ref err) => write!(fmt, "Error creating config: {}", err),
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        "An error encountered when deserializing a TOML configuration into a log4rs `Config`"
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::AppenderCreation(_, ref err) => Some(&**err),
            Error::FilterCreation(_, ref err) => Some(&**err),
            Error::Config(ref err) => Some(err),
        }
    }
}

/// Errors encountered when deserializing a TOML configuration into a log4rs `Config`.
#[derive(Debug)]
pub struct Errors {
    errors: Vec<Error>,
}

impl Errors {
    /// Returns the list of errors encountered.
    pub fn errors(&self) -> &[Error] {
        &self.errors
    }
}

impl fmt::Display for Errors {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        for error in &self.errors {
            try!(writeln!(fmt, "{}", error));
        }
        Ok(())
    }
}

impl error::Error for Errors {
    fn description(&self) -> &str {
        "Errors encountered when deserializing a TOML configuration into a log4rs `Config`"
    }
}

/// A deserialized TOML log4rs configuration.
pub struct Config {
    refresh_rate: Option<Duration>,
    config: config::Config,
}

impl Config {
    /// Creates a log4rs `Config` from the specified TOML config string and `Creator`.
    pub fn parse(config: &str, creator: &Creator)
                 -> Result<(Config, Result<(), Errors>), ParseErrors> {
        let mut errors = vec![];

        let config = match raw::parse(config) {
            Ok(config) => config,
            Err(errors) => return Err(ParseErrors { errors: errors }),
        };

        let raw::Config {
            refresh_rate,
            root: raw_root,
            appenders: raw_appenders,
            loggers: raw_loggers,
        } = config;

        let root = match raw_root {
            Some(raw_root) => {
                let mut root = config::Root::builder(raw_root.level);
                if let Some(appenders) = raw_root.appenders {
                    root = root.appenders(appenders);
                }
                root.build()
            }
            None => config::Root::builder(LogLevelFilter::Debug).build(),
        };

        let mut config = config::Config::builder(root);

        for (name, raw::Appender { kind, config: raw_config, filters }) in raw_appenders {
            match creator.create_appender(&kind, raw_config) {
                Ok(appender_obj) => {
                    let mut builder = config::Appender::builder(name.clone(), appender_obj);
                    for raw::Filter { kind, config } in filters.unwrap_or(vec![]) {
                        match creator.create_filter(&kind, config) {
                            Ok(filter) => builder = builder.filter(filter),
                            Err(err) => errors.push(Error::FilterCreation(name.clone(), err)),
                        }
                    }
                    config = config.appender(builder.build());
                }
                Err(err) => errors.push(Error::AppenderCreation(name, err)),
            }
        }

        for logger in raw_loggers {
            let raw::Logger { name, level, appenders, additive } = logger;
            let mut logger = config::Logger::builder(name, level);
            if let Some(appenders) = appenders {
                logger = logger.appenders(appenders);
            }
            if let Some(additive) = additive {
                logger = logger.additive(additive);
            }
            config = config.logger(logger.build());
        }

        let (config, config_errors) = config.build_lossy();
        if let Err(config_errors) = config_errors {
            for error in config_errors.unpack() {
                errors.push(Error::Config(error));
            }
        }

        let config = Config {
            refresh_rate: refresh_rate,
            config: config
        };

        let errors = if errors.is_empty() {
            Ok(())
        } else {
            Err(Errors {
                errors: errors
            })
        };

        Ok((config, errors))
    }

    /// Returns the requested refresh rate.
    pub fn refresh_rate(&self) -> Option<Duration> {
        self.refresh_rate
    }

    /// Returns the log4rs `Config`.
    pub fn config(&self) -> &config::Config {
        &self.config
    }
}

impl PrivateTomlConfigExt for Config {
    fn unpack(self) -> (Option<Duration>, config::Config) {
        let Config { refresh_rate, config } = self;
        (refresh_rate, config)
    }
}

#[derive(Debug)]
struct StringError(String);

impl fmt::Display for StringError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.write_str(&self.0)
    }
}

impl error::Error for StringError {
    fn description(&self) -> &str {
        &self.0
    }
}

impl From<String> for StringError {
    fn from(s: String) -> StringError {
        StringError(s)
    }
}

fn ensure_empty(config: &toml_parser::Table) -> Result<(), Box<error::Error>> {
    let remaining_keys: Vec<_> = config.keys().collect();
    if remaining_keys.is_empty() {
        Ok(())
    } else {
        Err(Box::new(StringError(format!("Unknown keys: {:?}", remaining_keys))))
    }
}

/// An appender creator for the `FileAppender`.
///
/// The `path` key is required, and specifies the path to the log file. The
/// `pattern` key is optional and specifies a `PatternLayout` pattern to be
/// used for output. The `append` key is optional and specifies whether the
/// output file should be truncated or appended to.
pub struct FileAppenderCreator;

impl CreateAppender for FileAppenderCreator {
    fn create_appender(&self, mut config: toml_parser::Table)
                       -> Result<Box<Append>, Box<error::Error>> {
        let path = match config.remove("path") {
            Some(Value::String(path)) => path,
            Some(_) => return Err(Box::new(StringError("`path` must be a string".to_string()))),
            None => return Err(Box::new(StringError("`path` is required".to_string()))),
        };

        let mut appender = FileAppender::builder(&path);
        match config.remove("pattern") {
            Some(Value::String(pattern)) => {
                appender = appender.pattern(try!(PatternLayout::new(&pattern)));
            }
            Some(_) => return Err(Box::new(StringError("`pattern` must be a string".to_string()))),
            None => {}
        }

        match config.remove("append") {
            Some(Value::Boolean(append)) => appender = appender.append(append),
            None => {}
            Some(_) => return Err(Box::new(StringError("`append` must be a bool".to_string()))),
        }

        try!(ensure_empty(&config));
        match appender.build() {
            Ok(appender) => Ok(Box::new(appender)),
            Err(err) => Err(Box::new(err))
        }
    }
}

/// An appender creator for the `ConsoleAppender`.
///
/// The `pattern` key is optional and specifies a `PatternLayout` pattern to be
/// used for output.
pub struct ConsoleAppenderCreator;

impl CreateAppender for ConsoleAppenderCreator {
    fn create_appender(&self, mut config: toml_parser::Table)
                       -> Result<Box<Append>, Box<error::Error>> {
        let mut appender = ConsoleAppender::builder();
        match config.remove("pattern") {
            Some(Value::String(pattern)) => {
                appender = appender.pattern(try!(PatternLayout::new(&pattern)));
            }
            Some(_) => return Err(Box::new(StringError("`pattern` must be a string".to_string()))),
            None => {}
        }

        try!(ensure_empty(&config));
        Ok(Box::new(appender.build()))
    }
}

/// A filter creator for the `ThresholdFilter`.
///
/// The `level` key is required and specifies the threshold for the filter.
pub struct ThresholdFilterCreator;

impl CreateFilter for ThresholdFilterCreator {
    fn create_filter(&self, mut config: toml_parser::Table)
                     -> Result<Box<Filter>, Box<error::Error>> {
        let level = match config.remove("level") {
            Some(Value::String(level)) => level,
            Some(_) => return Err(Box::new(StringError("`level` must be a string".to_string()))),
            None => return Err(Box::new(StringError("`level` must be provided".to_string()))),
        };

        let level = match level.parse() {
            Ok(level) => level,
            Err(_) => return Err(Box::new(StringError(format!("Invalid `level` \"{}\"", level)))),
        };

        try!(ensure_empty(&config));
        Ok(Box::new(ThresholdFilter::new(level)))
    }
}
<|endoftext|>


<commit_before>//! Check the bodies of `const`s, `static`s and `const fn`s for illegal operations.
//!
//! This module will eventually replace the parts of `qualify_consts.rs` that check whether a local
//! has interior mutability or needs to be dropped, as well as the visitor that emits errors when
//! it finds operations that are invalid in a certain context.

use rustc::hir::{self, def_id::DefId};
use rustc::mir;
use rustc::ty::{self, TyCtxt};

use std::fmt;

pub use self::qualifs::Qualif;

pub mod ops;
pub mod qualifs;
mod resolver;
pub mod validation;

/// Information about the item currently being const-checked, as well as a reference to the global
/// context.
pub struct Item<'mir, 'tcx> {
    pub body: &'mir mir::Body<'tcx>,
    pub tcx: TyCtxt<'tcx>,
    pub def_id: DefId,
    pub param_env: ty::ParamEnv<'tcx>,
    pub const_kind: Option<ConstKind>,
}

impl Item<'mir, 'tcx> {
    pub fn new(
        tcx: TyCtxt<'tcx>,
        def_id: DefId,
        body: &'mir mir::Body<'tcx>,
    ) -> Self {
        let param_env = tcx.param_env(def_id);
        let const_kind = ConstKind::for_item(tcx, def_id);

        Item {
            body,
            tcx,
            def_id,
            param_env,
            const_kind,
        }
    }

    /// Returns the kind of const context this `Item` represents (`const`, `static`, etc.).
    ///
    /// Panics if this `Item` is not const.
    pub fn const_kind(&self) -> ConstKind {
        self.const_kind.expect("`const_kind` must not be called on a non-const fn")
    }
}

/// The kinds of items which require compile-time evaluation.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ConstKind {
    /// A `static` item.
    Static,
    /// A `static mut` item.
    StaticMut,
    /// A `const fn` item.
    ConstFn,
    /// A `const` item or an anonymous constant (e.g. in array lengths).
    Const,
}

impl ConstKind {
    /// Returns the validation mode for the item with the given `DefId`, or `None` if this item
    /// does not require validation (e.g. a non-const `fn`).
    pub fn for_item(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Self> {
        use hir::BodyOwnerKind as HirKind;

        let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();

        let mode = match tcx.hir().body_owner_kind(hir_id) {
            HirKind::Closure => return None,

            HirKind::Fn if tcx.is_const_fn(def_id) => ConstKind::ConstFn,
            HirKind::Fn => return None,

            HirKind::Const => ConstKind::Const,

            HirKind::Static(hir::Mutability::Immutable) => ConstKind::Static,
            HirKind::Static(hir::Mutability::Mutable) => ConstKind::StaticMut,
        };

        Some(mode)
    }

    pub fn is_static(self) -> bool {
        match self {
            ConstKind::Static | ConstKind::StaticMut => true,
            ConstKind::ConstFn | ConstKind::Const => false,
        }
    }

    /// Returns `true` if the value returned by this item must be `Sync`.
    ///
    /// This returns false for `StaticMut` since all accesses to one are `unsafe` anyway.
    pub fn requires_sync(self) -> bool {
        match self {
            ConstKind::Static => true,
            ConstKind::ConstFn | ConstKind::Const |  ConstKind::StaticMut => false,
        }
    }
}

impl fmt::Display for ConstKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            ConstKind::Const => write!(f, "constant"),
            ConstKind::Static | ConstKind::StaticMut => write!(f, "static"),
            ConstKind::ConstFn => write!(f, "constant function"),
        }
    }
}

/// Returns `true` if this `DefId` points to one of the official `panic` lang items.
pub fn is_lang_panic_fn(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool {
    Some(def_id) == tcx.lang_items().panic_fn() ||
    Some(def_id) == tcx.lang_items().begin_panic_fn()
}
<commit_msg>Remove `requires_sync`<commit_after>//! Check the bodies of `const`s, `static`s and `const fn`s for illegal operations.
//!
//! This module will eventually replace the parts of `qualify_consts.rs` that check whether a local
//! has interior mutability or needs to be dropped, as well as the visitor that emits errors when
//! it finds operations that are invalid in a certain context.

use rustc::hir::{self, def_id::DefId};
use rustc::mir;
use rustc::ty::{self, TyCtxt};

use std::fmt;

pub use self::qualifs::Qualif;

pub mod ops;
pub mod qualifs;
mod resolver;
pub mod validation;

/// Information about the item currently being const-checked, as well as a reference to the global
/// context.
pub struct Item<'mir, 'tcx> {
    pub body: &'mir mir::Body<'tcx>,
    pub tcx: TyCtxt<'tcx>,
    pub def_id: DefId,
    pub param_env: ty::ParamEnv<'tcx>,
    pub const_kind: Option<ConstKind>,
}

impl Item<'mir, 'tcx> {
    pub fn new(
        tcx: TyCtxt<'tcx>,
        def_id: DefId,
        body: &'mir mir::Body<'tcx>,
    ) -> Self {
        let param_env = tcx.param_env(def_id);
        let const_kind = ConstKind::for_item(tcx, def_id);

        Item {
            body,
            tcx,
            def_id,
            param_env,
            const_kind,
        }
    }

    /// Returns the kind of const context this `Item` represents (`const`, `static`, etc.).
    ///
    /// Panics if this `Item` is not const.
    pub fn const_kind(&self) -> ConstKind {
        self.const_kind.expect("`const_kind` must not be called on a non-const fn")
    }
}

/// The kinds of items which require compile-time evaluation.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ConstKind {
    /// A `static` item.
    Static,
    /// A `static mut` item.
    StaticMut,
    /// A `const fn` item.
    ConstFn,
    /// A `const` item or an anonymous constant (e.g. in array lengths).
    Const,
}

impl ConstKind {
    /// Returns the validation mode for the item with the given `DefId`, or `None` if this item
    /// does not require validation (e.g. a non-const `fn`).
    pub fn for_item(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Self> {
        use hir::BodyOwnerKind as HirKind;

        let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();

        let mode = match tcx.hir().body_owner_kind(hir_id) {
            HirKind::Closure => return None,

            HirKind::Fn if tcx.is_const_fn(def_id) => ConstKind::ConstFn,
            HirKind::Fn => return None,

            HirKind::Const => ConstKind::Const,

            HirKind::Static(hir::Mutability::Immutable) => ConstKind::Static,
            HirKind::Static(hir::Mutability::Mutable) => ConstKind::StaticMut,
        };

        Some(mode)
    }

    pub fn is_static(self) -> bool {
        match self {
            ConstKind::Static | ConstKind::StaticMut => true,
            ConstKind::ConstFn | ConstKind::Const => false,
        }
    }
}

impl fmt::Display for ConstKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            ConstKind::Const => write!(f, "constant"),
            ConstKind::Static | ConstKind::StaticMut => write!(f, "static"),
            ConstKind::ConstFn => write!(f, "constant function"),
        }
    }
}

/// Returns `true` if this `DefId` points to one of the official `panic` lang items.
pub fn is_lang_panic_fn(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool {
    Some(def_id) == tcx.lang_items().panic_fn() ||
    Some(def_id) == tcx.lang_items().begin_panic_fn()
}
<|endoftext|>


<commit_before>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program 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
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io
extern crate log;
extern crate osmpbfreader;
extern crate mimir;
extern crate osm_builder;

use std::collections::HashMap;
use mimir::objects::{Polygon, MultiPolygon};
use osm_builder::named_node;

struct BoundaryPart {
    nodes: Vec<osmpbfreader::Node>,
}

impl BoundaryPart {
    pub fn new(nodes: Vec<osmpbfreader::Node>) -> BoundaryPart {
        BoundaryPart { nodes: nodes }
    }
    pub fn first(&self) -> i64 {
        self.nodes.first().unwrap().id
    }
    pub fn last(&self) -> i64 {
        self.nodes.last().unwrap().id
    }
}

fn get_nodes(way: &osmpbfreader::Way,
             objects: &HashMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>)
             -> Vec<osmpbfreader::Node> {
    way.nodes
       .iter()
       .filter_map(|node_id| objects.get(&osmpbfreader::OsmId::Node(*node_id)))
       .filter_map(|node_obj| {
           if let &osmpbfreader::OsmObj::Node(ref node) = node_obj {
               Some(node.clone())
           } else {
               None
           }
       })
       .collect()
}

#[test]
fn test_get_nodes() {
    let mut objects = HashMap::new();
    let way = osmpbfreader::Way {
        id: 12,
        nodes: vec![12, 15, 8, 68],
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Way(12),
                   osmpbfreader::OsmObj::Way(way.clone()));
    let node_12 = osmpbfreader::Node {
        id: 12,
        lat: 1.2,
        lon: 3.7,
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Node(12),
                   osmpbfreader::OsmObj::Node(node_12));
    let node_13 = osmpbfreader::Node {
        id: 13,
        lat: 1.5,
        lon: 3.5,
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Node(13),
                   osmpbfreader::OsmObj::Node(node_13));
    let node_15 = osmpbfreader::Node {
        id: 15,
        lat: 7.5,
        lon: 13.5,
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Node(15),
                   osmpbfreader::OsmObj::Node(node_15));
    let node_8 = osmpbfreader::Node {
        id: 8,
        lat: 5.5,
        lon: 63.5,
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Node(8),
                   osmpbfreader::OsmObj::Node(node_8));
    let node_68 = osmpbfreader::Node {
        id: 68,
        lat: 45.5,
        lon: 53.5,
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Node(68),
                   osmpbfreader::OsmObj::Node(node_68));

    let nodes = get_nodes(&way, &objects);
    assert_eq!(nodes.len(), 4);
    assert_eq!(nodes[0].id, 12);
    assert_eq!(nodes[1].id, 15);
    assert_eq!(nodes[2].id, 8);
    assert_eq!(nodes[3].id, 68);
}

pub fn build_boundary(relation: &osmpbfreader::Relation,
                      objects: &HashMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>)
                      -> Option<MultiPolygon> {
    let mut boundary_parts: Vec<BoundaryPart> =
        relation.refs
                .iter()
                .filter(|rf| rf.role == "outer" || rf.role == "" || rf.role == "enclave")
                .filter_map(|refe| {
                    objects.get(&refe.member).or_else(|| {
                        warn!("missing element for relation {}", relation.id);
                        None
                    })
                })
                .filter_map(|way_obj| {
                    if let &osmpbfreader::OsmObj::Way(ref way) = way_obj {
                        Some(way)
                    } else {
                        None
                    }
                })
                .map(|way| get_nodes(&way, objects))
                .filter(|nodes| nodes.len() > 1)
                .map(|nodes| BoundaryPart::new(nodes))
                .collect();
    let mut multipoly = MultiPolygon { polygons: Vec::new() };
    // we want to try build a polygon for a least each way
    while !boundary_parts.is_empty() {
        let mut current = -1;
        let mut first = -2;
        let mut nb_try = 0;

        let mut outer: Vec<osmpbfreader::Node> = Vec::new();
        // we try to close the polygon, if we can't we want to at least have tried one time per
        // way. We could improve that latter by trying to attach the way to both side of the
        // polygon
        let max_try = boundary_parts.len();
        while current != first && nb_try < max_try {
            let mut i = 0;
            while i < boundary_parts.len() {
                if outer.is_empty() {
                    // our polygon is empty, we initialise it with the current way
                    first = boundary_parts[i].first();
                    current = boundary_parts[i].last();
                    outer.append(&mut boundary_parts[i].nodes);
                    // this way has been used, we remove it from the pool
                    boundary_parts.remove(i);
                    continue;
                }
                if current == boundary_parts[i].first() {
                    // the start of current way touch the polygon, we add it and remove it from the
                    // pool
                    current = boundary_parts[i].last();
                    outer.append(&mut boundary_parts[i].nodes);
                    boundary_parts.remove(i);
                } else if current == boundary_parts[i].last() {
                    // the end of the current way touch the polygon, we reverse the way and add it
                    current = boundary_parts[i].first();
                    boundary_parts[i].nodes.reverse();
                    outer.append(&mut boundary_parts[i].nodes);
                    boundary_parts.remove(i);
                } else {
                    i += 1;
                    // didnt do anything, we want to explore the next way, if we had do something we
                    // will have removed the current way and there will be no need to increment
                }
                if current == first {
                    // our polygon is closed, we create it and add it to the multipolygon
                    let polygon = Polygon::new(outer.iter()
                                                    .map(|n| {
                                                        mimir::Coord {
                                                            lat: n.lat,
                                                            lon: n.lon,
                                                        }
                                                    })
                                                    .collect());
                    multipoly.polygons.push(polygon);
                    break;
                }
            }
            nb_try += 1;
        }
    }
    debug!("polygon for relation {};\"{}\"",
           relation.id,
           multipoly.to_wkt());
    if multipoly.polygons.is_empty() {
        None
    } else {
        Some(multipoly)
    }
}

#[test]
fn test_build_bounadry_empty() {
    let objects = HashMap::new();
    let mut relation = osmpbfreader::Relation {
        id: 12,
        refs: vec![],
        tags: osmpbfreader::Tags::new(),
    };
    relation.refs.push(osmpbfreader::Ref {
        member: osmpbfreader::OsmId::Way(4),
        role: "outer".to_string(),
    });
    relation.refs.push(osmpbfreader::Ref {
        member: osmpbfreader::OsmId::Way(65),
        role: "outer".to_string(),
    });
    relation.refs.push(osmpbfreader::Ref {
        member: osmpbfreader::OsmId::Way(22),
        role: "".to_string(),
    });
    assert!(build_boundary(&relation, &objects).is_none());
}

#[test]
fn test_build_bounadry_not_closed() {
    let mut builder = osm_builder::OsmBuilder::new();
    let rel_id: osmpbfreader::OsmId;
    {
        let mut rel = builder.relation();
        rel_id = rel.relation_id;
        rel.outer(vec![named_node(3.4, 5.2, "start"),
                       named_node(5.4, 5.1, "1")])
           .outer(vec![named_node(5.4, 5.1, "1"),
                       named_node(2.4, 3.1, "2")])
           .outer(vec![named_node(2.4, 3.2, "2"), named_node(6.4, 6.1, "end")]);
    }
    if let osmpbfreader::OsmObj::Relation(ref relation) = builder.objects[&rel_id] {
        assert!(build_boundary(&relation, &builder.objects).is_none());
    } else {
        assert!(false);//this should not happen
    }
}

#[test]
fn test_build_bounadry_closed() {
    let mut builder = osm_builder::OsmBuilder::new();
    let rel_id: osmpbfreader::OsmId;
    {
        let mut rel = builder.relation();
        rel_id = rel.relation_id;
        rel.outer(vec![named_node(3.4, 5.2, "start"), named_node(5.4, 5.1, "1")])
           .outer(vec![named_node(5.4, 5.1, "1"), named_node(2.4, 3.1, "2")])
           .outer(vec![named_node(2.4, 3.2, "2"), named_node(6.4, 6.1, "start")]);
    }
    if let osmpbfreader::OsmObj::Relation(ref relation) = builder.objects[&rel_id] {
        let multipolygon = build_boundary(&relation, &builder.objects);
        assert!(multipolygon.is_some());
        let multipolygon = multipolygon.unwrap();
        assert_eq!(multipolygon.polygons.len(), 1);

    } else {
        assert!(false);//this should not happen
    }
}
<commit_msg>remove useless import<commit_after>// Copyright © 2016, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program 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
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io
extern crate log;
extern crate osmpbfreader;
extern crate mimir;
extern crate osm_builder;

use std::collections::HashMap;
use mimir::objects::{Polygon, MultiPolygon};

struct BoundaryPart {
    nodes: Vec<osmpbfreader::Node>,
}

impl BoundaryPart {
    pub fn new(nodes: Vec<osmpbfreader::Node>) -> BoundaryPart {
        BoundaryPart { nodes: nodes }
    }
    pub fn first(&self) -> i64 {
        self.nodes.first().unwrap().id
    }
    pub fn last(&self) -> i64 {
        self.nodes.last().unwrap().id
    }
}

fn get_nodes(way: &osmpbfreader::Way,
             objects: &HashMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>)
             -> Vec<osmpbfreader::Node> {
    way.nodes
       .iter()
       .filter_map(|node_id| objects.get(&osmpbfreader::OsmId::Node(*node_id)))
       .filter_map(|node_obj| {
           if let &osmpbfreader::OsmObj::Node(ref node) = node_obj {
               Some(node.clone())
           } else {
               None
           }
       })
       .collect()
}

#[test]
fn test_get_nodes() {
    let mut objects = HashMap::new();
    let way = osmpbfreader::Way {
        id: 12,
        nodes: vec![12, 15, 8, 68],
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Way(12),
                   osmpbfreader::OsmObj::Way(way.clone()));
    let node_12 = osmpbfreader::Node {
        id: 12,
        lat: 1.2,
        lon: 3.7,
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Node(12),
                   osmpbfreader::OsmObj::Node(node_12));
    let node_13 = osmpbfreader::Node {
        id: 13,
        lat: 1.5,
        lon: 3.5,
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Node(13),
                   osmpbfreader::OsmObj::Node(node_13));
    let node_15 = osmpbfreader::Node {
        id: 15,
        lat: 7.5,
        lon: 13.5,
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Node(15),
                   osmpbfreader::OsmObj::Node(node_15));
    let node_8 = osmpbfreader::Node {
        id: 8,
        lat: 5.5,
        lon: 63.5,
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Node(8),
                   osmpbfreader::OsmObj::Node(node_8));
    let node_68 = osmpbfreader::Node {
        id: 68,
        lat: 45.5,
        lon: 53.5,
        tags: osmpbfreader::Tags::new(),
    };
    objects.insert(osmpbfreader::OsmId::Node(68),
                   osmpbfreader::OsmObj::Node(node_68));

    let nodes = get_nodes(&way, &objects);
    assert_eq!(nodes.len(), 4);
    assert_eq!(nodes[0].id, 12);
    assert_eq!(nodes[1].id, 15);
    assert_eq!(nodes[2].id, 8);
    assert_eq!(nodes[3].id, 68);
}

pub fn build_boundary(relation: &osmpbfreader::Relation,
                      objects: &HashMap<osmpbfreader::OsmId, osmpbfreader::OsmObj>)
                      -> Option<MultiPolygon> {
    let mut boundary_parts: Vec<BoundaryPart> =
        relation.refs
                .iter()
                .filter(|rf| rf.role == "outer" || rf.role == "" || rf.role == "enclave")
                .filter_map(|refe| {
                    objects.get(&refe.member).or_else(|| {
                        warn!("missing element for relation {}", relation.id);
                        None
                    })
                })
                .filter_map(|way_obj| {
                    if let &osmpbfreader::OsmObj::Way(ref way) = way_obj {
                        Some(way)
                    } else {
                        None
                    }
                })
                .map(|way| get_nodes(&way, objects))
                .filter(|nodes| nodes.len() > 1)
                .map(|nodes| BoundaryPart::new(nodes))
                .collect();
    let mut multipoly = MultiPolygon { polygons: Vec::new() };
    // we want to try build a polygon for a least each way
    while !boundary_parts.is_empty() {
        let mut current = -1;
        let mut first = -2;
        let mut nb_try = 0;

        let mut outer: Vec<osmpbfreader::Node> = Vec::new();
        // we try to close the polygon, if we can't we want to at least have tried one time per
        // way. We could improve that latter by trying to attach the way to both side of the
        // polygon
        let max_try = boundary_parts.len();
        while current != first && nb_try < max_try {
            let mut i = 0;
            while i < boundary_parts.len() {
                if outer.is_empty() {
                    // our polygon is empty, we initialise it with the current way
                    first = boundary_parts[i].first();
                    current = boundary_parts[i].last();
                    outer.append(&mut boundary_parts[i].nodes);
                    // this way has been used, we remove it from the pool
                    boundary_parts.remove(i);
                    continue;
                }
                if current == boundary_parts[i].first() {
                    // the start of current way touch the polygon, we add it and remove it from the
                    // pool
                    current = boundary_parts[i].last();
                    outer.append(&mut boundary_parts[i].nodes);
                    boundary_parts.remove(i);
                } else if current == boundary_parts[i].last() {
                    // the end of the current way touch the polygon, we reverse the way and add it
                    current = boundary_parts[i].first();
                    boundary_parts[i].nodes.reverse();
                    outer.append(&mut boundary_parts[i].nodes);
                    boundary_parts.remove(i);
                } else {
                    i += 1;
                    // didnt do anything, we want to explore the next way, if we had do something we
                    // will have removed the current way and there will be no need to increment
                }
                if current == first {
                    // our polygon is closed, we create it and add it to the multipolygon
                    let polygon = Polygon::new(outer.iter()
                                                    .map(|n| {
                                                        mimir::Coord {
                                                            lat: n.lat,
                                                            lon: n.lon,
                                                        }
                                                    })
                                                    .collect());
                    multipoly.polygons.push(polygon);
                    break;
                }
            }
            nb_try += 1;
        }
    }
    debug!("polygon for relation {};\"{}\"",
           relation.id,
           multipoly.to_wkt());
    if multipoly.polygons.is_empty() {
        None
    } else {
        Some(multipoly)
    }
}

#[test]
fn test_build_bounadry_empty() {
    let objects = HashMap::new();
    let mut relation = osmpbfreader::Relation {
        id: 12,
        refs: vec![],
        tags: osmpbfreader::Tags::new(),
    };
    relation.refs.push(osmpbfreader::Ref {
        member: osmpbfreader::OsmId::Way(4),
        role: "outer".to_string(),
    });
    relation.refs.push(osmpbfreader::Ref {
        member: osmpbfreader::OsmId::Way(65),
        role: "outer".to_string(),
    });
    relation.refs.push(osmpbfreader::Ref {
        member: osmpbfreader::OsmId::Way(22),
        role: "".to_string(),
    });
    assert!(build_boundary(&relation, &objects).is_none());
}

#[test]
fn test_build_bounadry_not_closed() {
    let mut builder = osm_builder::OsmBuilder::new();
    let rel_id: osmpbfreader::OsmId;
    {
        let mut rel = builder.relation();
        rel_id = rel.relation_id;
        rel.outer(vec![named_node(3.4, 5.2, "start"),
                       named_node(5.4, 5.1, "1")])
           .outer(vec![named_node(5.4, 5.1, "1"),
                       named_node(2.4, 3.1, "2")])
           .outer(vec![named_node(2.4, 3.2, "2"), named_node(6.4, 6.1, "end")]);
    }
    if let osmpbfreader::OsmObj::Relation(ref relation) = builder.objects[&rel_id] {
        assert!(build_boundary(&relation, &builder.objects).is_none());
    } else {
        assert!(false);//this should not happen
    }
}

#[test]
fn test_build_bounadry_closed() {
    let mut builder = osm_builder::OsmBuilder::new();
    let rel_id: osmpbfreader::OsmId;
    {
        let mut rel = builder.relation();
        rel_id = rel.relation_id;
        rel.outer(vec![named_node(3.4, 5.2, "start"), named_node(5.4, 5.1, "1")])
           .outer(vec![named_node(5.4, 5.1, "1"), named_node(2.4, 3.1, "2")])
           .outer(vec![named_node(2.4, 3.2, "2"), named_node(6.4, 6.1, "start")]);
    }
    if let osmpbfreader::OsmObj::Relation(ref relation) = builder.objects[&rel_id] {
        let multipolygon = build_boundary(&relation, &builder.objects);
        assert!(multipolygon.is_some());
        let multipolygon = multipolygon.unwrap();
        assert_eq!(multipolygon.polygons.len(), 1);

    } else {
        assert!(false);//this should not happen
    }
}
<|endoftext|>


<commit_before>//! # TreeView Sample
//!
//! This sample demonstrates how to create a TreeView with either a ListStore or TreeStore.

extern crate glib;
extern crate gtk;
extern crate gdk_pixbuf;

use gtk::prelude::*;
use gtk::{
    ButtonsType, CellRendererPixbuf, CellRendererText, MessageDialog, MessageType, Orientation,
    TreeStore, TreeView, TreeViewColumn, Window, WindowPosition, WindowType, DIALOG_MODAL
};
use gdk_pixbuf::Pixbuf;

fn append_text_column(tree: &TreeView) {
    let column = TreeViewColumn::new();
    let cell = CellRendererText::new();

    column.pack_start(&cell, true);
    column.add_attribute(&cell, "text", 0);
    tree.append_column(&column);
}

fn main() {
    if gtk::init().is_err() {
        println!("Failed to initialize GTK.");
        return;
    }

    let window = Window::new(WindowType::Toplevel);

    window.set_title("TreeView Sample");
    window.set_position(WindowPosition::Center);

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    // left pane

    let left_tree = TreeView::new();
    let left_store = TreeStore::new(&[String::static_type()]);

    left_tree.set_model(Some(&left_store));
    left_tree.set_headers_visible(false);
    append_text_column(&left_tree);

    for i in 0..10 {
        // insert_with_values takes two slices: column indices and ToValue
        // trait objects. ToValue is implemented for strings, numeric types,
        // bool and Object descendants
        let iter = left_store.insert_with_values(None, None, &[0], &[&format!("Hello {}", i)]);

        for _ in 0..i {
            left_store.insert_with_values(Some(&iter), None, &[0], &[&"I'm a child node"]);
        }
    }

    // right pane
    let right_tree = TreeView::new();
    let right_column_types = [Pixbuf::static_type(), String::static_type()];
    let right_store = TreeStore::new(&right_column_types);
    let renderer = CellRendererPixbuf::new();
    let col = TreeViewColumn::new();

    col.set_title("Picture");
    col.pack_start(&renderer, false);

    col.add_attribute(&renderer, "pixbuf", 0);

    let renderer2 = CellRendererText::new();
    col.pack_start(&renderer2, true);
    col.add_attribute(&renderer2, "text", 1);
    let image = Pixbuf::new_from_file("./resources/eye.png").or_else(|err| {
        let mut msg = err.to_string();
        if err.kind() == Some(glib::FileError::Noent) {
            msg.push_str(&format!("\nRelaunch this example from the same level \
                                  as the `resources` folder"));
        }
        let window = window.clone();

        gtk::idle_add(move || {
            let dialog = MessageDialog::new(Some(&window), DIALOG_MODAL,
                MessageType::Error, ButtonsType::Ok, &msg);
            dialog.run();
            dialog.destroy();
            Continue(false)
        });

        Err(())
    }).ok();

    right_tree.append_column(&col);
    right_tree.set_model(Some(&right_store));
    right_tree.set_headers_visible(true);

    for _ in 0..10 {
        right_store.insert_with_values(None, None, &[0, 1],
                                       &[&image, &"I'm a child node with an image"]);
    }

    // selection and path manipulation

    let left_selection = left_tree.get_selection();
    let right_tree1 = right_tree.clone();
    left_selection.connect_changed(move |tree_selection| {
        let (left_model, iter) = tree_selection.get_selected().unwrap();
        let mut path = left_model.get_path(&iter).unwrap();
        // get the top-level element path
        while path.get_depth() > 1 {
            path.up();
        }
        right_tree1.get_selection().select_path(&path);
    });

    // display the panes

    let split_pane = gtk::Box::new(Orientation::Horizontal, 10);

    split_pane.set_size_request(-1, -1);
    split_pane.add(&left_tree);
    split_pane.add(&right_tree);

    window.add(&split_pane);
    window.show_all();
    gtk::main();
}
<commit_msg>Revert "Fix call TreeModel.get_path"<commit_after>//! # TreeView Sample
//!
//! This sample demonstrates how to create a TreeView with either a ListStore or TreeStore.

extern crate glib;
extern crate gtk;
extern crate gdk_pixbuf;

use gtk::prelude::*;
use gtk::{
    ButtonsType, CellRendererPixbuf, CellRendererText, MessageDialog, MessageType, Orientation,
    TreeStore, TreeView, TreeViewColumn, Window, WindowPosition, WindowType, DIALOG_MODAL
};
use gdk_pixbuf::Pixbuf;

fn append_text_column(tree: &TreeView) {
    let column = TreeViewColumn::new();
    let cell = CellRendererText::new();

    column.pack_start(&cell, true);
    column.add_attribute(&cell, "text", 0);
    tree.append_column(&column);
}

fn main() {
    if gtk::init().is_err() {
        println!("Failed to initialize GTK.");
        return;
    }

    let window = Window::new(WindowType::Toplevel);

    window.set_title("TreeView Sample");
    window.set_position(WindowPosition::Center);

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    // left pane

    let left_tree = TreeView::new();
    let left_store = TreeStore::new(&[String::static_type()]);

    left_tree.set_model(Some(&left_store));
    left_tree.set_headers_visible(false);
    append_text_column(&left_tree);

    for i in 0..10 {
        // insert_with_values takes two slices: column indices and ToValue
        // trait objects. ToValue is implemented for strings, numeric types,
        // bool and Object descendants
        let iter = left_store.insert_with_values(None, None, &[0], &[&format!("Hello {}", i)]);

        for _ in 0..i {
            left_store.insert_with_values(Some(&iter), None, &[0], &[&"I'm a child node"]);
        }
    }

    // right pane
    let right_tree = TreeView::new();
    let right_column_types = [Pixbuf::static_type(), String::static_type()];
    let right_store = TreeStore::new(&right_column_types);
    let renderer = CellRendererPixbuf::new();
    let col = TreeViewColumn::new();

    col.set_title("Picture");
    col.pack_start(&renderer, false);

    col.add_attribute(&renderer, "pixbuf", 0);

    let renderer2 = CellRendererText::new();
    col.pack_start(&renderer2, true);
    col.add_attribute(&renderer2, "text", 1);
    let image = Pixbuf::new_from_file("./resources/eye.png").or_else(|err| {
        let mut msg = err.to_string();
        if err.kind() == Some(glib::FileError::Noent) {
            msg.push_str(&format!("\nRelaunch this example from the same level \
                                  as the `resources` folder"));
        }
        let window = window.clone();

        gtk::idle_add(move || {
            let dialog = MessageDialog::new(Some(&window), DIALOG_MODAL,
                MessageType::Error, ButtonsType::Ok, &msg);
            dialog.run();
            dialog.destroy();
            Continue(false)
        });

        Err(())
    }).ok();

    right_tree.append_column(&col);
    right_tree.set_model(Some(&right_store));
    right_tree.set_headers_visible(true);

    for _ in 0..10 {
        right_store.insert_with_values(None, None, &[0, 1],
                                       &[&image, &"I'm a child node with an image"]);
    }

    // selection and path manipulation

    let left_selection = left_tree.get_selection();
    let right_tree1 = right_tree.clone();
    left_selection.connect_changed(move |tree_selection| {
        let (left_model, iter) = tree_selection.get_selected().unwrap();
        let mut path = left_model.get_path(&iter);
        // get the top-level element path
        while path.get_depth() > 1 {
            path.up();
        }
        right_tree1.get_selection().select_path(&path);
    });

    // display the panes

    let split_pane = gtk::Box::new(Orientation::Horizontal, 10);

    split_pane.set_size_request(-1, -1);
    split_pane.add(&left_tree);
    split_pane.add(&right_tree);

    window.add(&split_pane);
    window.show_all();
    gtk::main();
}
<|endoftext|>


<commit_before>use std::f64::NAN;

use js_types::js_var::JsVar;
use js_types::js_var::JsType::*;

pub trait AsBool {
    fn as_bool(&self) -> bool;
}

impl AsBool for JsVar {
    fn as_bool(&self) -> bool {
        match self.t {
            JsBool(b) => b,
            JsUndef=> false,
            JsNull => false,
            JsNum(n) =>
                if n == 0.0f64 || n == -0.0f64 || n.is_nan() {
                    false
                } else {
                    true
                },
            JsPtr(_) => true, // TODO - this is incorrect
            //JsString(ref s) =>
            //    if s.len() == 0 {
            //        false
            //    } else {
            //        true
            //    },
            //JsSymbol(_) => true,
            //JsObject | JsError(_) | JsFunction(_, _, _) => true,
        }
    }
}

pub trait AsNumber {
    fn as_number(&self) -> f64;
}

impl AsNumber for JsVar {
    fn as_number(&self) -> f64 {
        match self.t {
            JsBool(b) => if b { 1f64 } else { 0f64 },
            JsUndef => NAN,
            JsNull => 0f64,
            JsNum(n) => n,
            JsPtr(_) => NAN,
            //JsString(ref s) =>
            //    if s.len() == 0 {
            //        JsNumber(0f64)
            //    } else {
            //        let num = s.parse();
            //        match num {
            //            Ok(n)  => JsNumber(n),
            //            Err(_) => JsNumber(NAN),
            //        }
            //    },
            //JsSymbol(_) => panic!("Cannot convert a Symbol to a number."),
            //JsObject | JsError(_) | JsFunction(_, _, _) => JsNumber(NAN),
        }
    }
}
<commit_msg>add initial functionality for implicit conversion to string<commit_after>use std::f64::NAN;

use js_types::js_var::JsVar;
use js_types::js_var::JsPtrEnum;
use js_types::js_var::JsType::*;

pub trait AsBool {
    fn as_bool(&self) -> bool;
}

impl AsBool for JsVar {
    fn as_bool(&self) -> bool {
        match self.t {
            JsBool(b) => b,
            JsUndef=> false,
            JsNull => false,
            JsNum(n) =>
                if n == 0.0f64 || n == -0.0f64 || n.is_nan() {
                    false
                } else {
                    true
                },
            JsPtr(_) => true, // TODO - this is incorrect
            //JsString(ref s) =>
            //    if s.len() == 0 {
            //        false
            //    } else {
            //        true
            //    },
            //JsSymbol(_) => true,
            //JsObject | JsError(_) | JsFunction(_, _, _) => true,
        }
    }
}

pub trait AsNumber {
    fn as_number(&self) -> f64;
}

impl AsNumber for JsVar {
    fn as_number(&self) -> f64 {
        match self.t {
            JsBool(b) => if b { 1f64 } else { 0f64 },
            JsUndef => NAN,
            JsNull => 0f64,
            JsNum(n) => n,
            JsPtr(_) => NAN,
            //JsString(ref s) =>
            //    if s.len() == 0 {
            //        JsNumber(0f64)
            //    } else {
            //        let num = s.parse();
            //        match num {
            //            Ok(n)  => JsNumber(n),
            //            Err(_) => JsNumber(NAN),
            //        }
            //    },
            //JsSymbol(_) => panic!("Cannot convert a Symbol to a number."),
            //JsObject | JsError(_) | JsFunction(_, _, _) => JsNumber(NAN),
        }
    }
}

fn ptr_as_str(ptr: &JsPtrEnum) -> String {
    match ptr {
        &JsPtrEnum::JsSym(ref s) => format!("Symbol({})", s),
        &JsPtrEnum::JsStr(ref s) => s.text.to_owned(),

        // TODO: Check object's `toString` method
        &JsPtrEnum::JsObj(ref s) => String::from("[object Object]"),

        // TODO: A function's string representation is apparently the string of source code that
        // created it; the AST doesn't currently support this, so we'll need to do some
        // restructuring before we can support this.
        &JsPtrEnum::JsFn(_) => String::from("[function]"),
    }
}

pub trait AsString {
    fn as_string(&self, ptr: Option<&JsPtrEnum>) -> String {
        let s = match self.t {
            JsBool(true) => "true"
            JsBool(false) => "false"
            JsUndef => "undefined",
            JsNull => "null",
            JsNum(n) => return format!("{}", n),
            JsPtr(_) => return ptr_as_str(ptr.expect("Invalid pointer")),
        };

        String::from(s)
    }
}
<|endoftext|>


<commit_before>use byteorder::{ByteOrder, LittleEndian};
use log::{debug, trace};
use goblin::{Object};
use failure::{Error, Fail};
use std::ops::Range;

use super::super::super::arch::{RVA};
use super::super::super::loader::Permissions;
use super::super::super::workspace::Workspace;
use super::super::{Analyzer};
use std::collections::{HashSet};

#[derive(Debug, Fail)]
pub enum RelocAnalyzerError {
    #[fail(display = "Invalid relocation type")]
    InvalidRelocType,
}

pub struct RelocAnalyzer {}

impl RelocAnalyzer {
    pub fn new() -> RelocAnalyzer {
        RelocAnalyzer {}
    }
}

// TODO: make this much faster
fn is_in_insn(ws: &Workspace, rva: RVA) -> bool {
    let start: usize = rva.into();
    // TODO: underflow
    // TODO: remove harded max insn length
    let end: usize = start - 0x10;

    for i in (start..end).rev() {
        let i = RVA::from(i);
        if let Some(meta) = ws.get_meta(i) {
            if !meta.is_insn() {
                continue;
            }

            if let Ok(len) = meta.get_insn_length() {
                if i + len > rva {
                    return true;
                }
            }
        }
    }
    return false;
}

fn is_ptr(ws: &Workspace, rva: RVA) -> bool {
    if let Ok(ptr) = ws.read_va(rva) {
        if let Some(ptr) = ws.rva(ptr) {
            return ws.probe(ptr, 1, Permissions::R);
        }
    }
    return false;
}

pub enum RelocationType {
    ImageRelBasedAbsolute,
    ImageRelBasedHigh,
    ImageRelBasedLow,
    ImageRelBasedHighLow,
    ImageRelBasedHighAdj,

    // ImageRelBasedMIPS_JmpAddr,
    // ImageRelBasedARM_MOV32,
    // ImageRelBasedRiscV_High20,
    ImageRelArch1,

    ImageRelReserved,

    // ImageRelBasedTHUMB_MOV32,
    // ImageRelBasedRiscV_Low12I,
    ImageRelArch2,

    ImageRelBasedRiscVLow12S,
    ImageRelBasedMIPSJmpAddr16,
    ImageRelBasedDir64,
}

pub struct Reloc {
    pub typ: RelocationType,
    pub offset: RVA,
}

fn parse_reloc(base: RVA, entry: u16) -> Result<Reloc, Error> {
    let reloc_type   = (entry & 0b1111000000000000) >> 12;
    let reloc_offset =  entry & 0b0000111111111111;

    // TODO: this should probably be on `RelocationType` as a `From` trait.
    let reloc_type = match reloc_type {
        0 => RelocationType::ImageRelBasedAbsolute,
        1 => RelocationType::ImageRelBasedHigh,
        2 => RelocationType::ImageRelBasedLow,
        3 => RelocationType::ImageRelBasedHighLow,
        4 => RelocationType::ImageRelBasedHighAdj,
        5 => RelocationType::ImageRelArch1,
        6 => RelocationType::ImageRelReserved,
        7 => RelocationType::ImageRelArch2,
        8 => RelocationType::ImageRelBasedRiscVLow12S,
        9 => RelocationType::ImageRelBasedMIPSJmpAddr16,
        10 => RelocationType::ImageRelBasedDir64,
        _ => return Err(RelocAnalyzerError::InvalidRelocType.into()),
    };

    Ok(Reloc {
        typ: reloc_type,
        offset: base + RVA::from(reloc_offset as i64),
    })
}

fn split_u32(e: u32) -> (u16, u16) {
    let m = ((e & 0x0000FFFF) >>  0) as u16;
    let n = ((e & 0xFFFF0000) >> 16) as u16;
    (m, n)
}

/// ```
/// use lancelot::rsrc::*;
/// use lancelot::arch::*;
/// use lancelot::workspace::Workspace;
/// use lancelot::analysis::pe::relocs;
///
/// let mut ws = Workspace::from_bytes("k32.dll", &get_buf(Rsrc::K32))
///    .disable_analysis()
///    .load().unwrap();
/// let relocs = relocs::get_relocs(&ws).unwrap();
/// assert_eq!(relocs[0].offset,   RVA(0x76008));
/// assert_eq!(relocs[277].offset, RVA(0xA8000));
/// ```
pub fn get_relocs(ws: &Workspace) -> Result<Vec<Reloc>, Error> {
    let pe = match Object::parse(&ws.buf) {
        Ok(Object::PE(pe)) => pe,
        _ => return Ok(vec![]),
    };

    let opt_header = match pe.header.optional_header {
        Some(opt_header) => opt_header,
        _ => return Ok(vec![]),
    };

    let reloc_directory = match opt_header.data_directories.get_base_relocation_table() {
        Some(reloc_directory) => reloc_directory,
        _ => return Ok(vec![]),
    };

    let dir_start = RVA::from(reloc_directory.virtual_address as i64);
    let buf = ws.read_bytes(dir_start, reloc_directory.size as usize)?;

    let entries: Vec<u32> = buf
        .chunks_exact(0x4)
        .map(|b| LittleEndian::read_u32(b))
        .collect();

    let mut ret = vec![];
    let mut index = 0;
    loop {
        // parse base relocation chunk
        // until there's no data left

        let page_rva = entries.get(index);
        let block_size = entries.get(index+1);
        let (page_rva, block_size) = match (page_rva, block_size) {
            (Some(0),        _               ) => break,
            (_,              Some(0)         ) => break,
            (Some(page_rva), Some(block_size)) => (RVA::from(*page_rva as i64), *block_size),
            _ => break,
        };

        // cast from u32 to usize
        let chunk_entries_count = ((block_size) / 4) as usize;

        for i in 2..chunk_entries_count {
            if let Some(&entry) = entries.get(index + i) {
                let (m, n) = split_u32(entry);

                let reloc1 = parse_reloc(page_rva, m)?;
                if !ws.probe(reloc1.offset, 4, Permissions::R) {
                    break
                }
                ret.push(reloc1);

                let reloc2 = parse_reloc(page_rva, n)?;
                if !ws.probe(reloc2.offset, 4, Permissions::R) {
                    break
                }
                ret.push(reloc2);
            } else {
                break
            }
        };

        index += chunk_entries_count;
    }

    Ok(ret)
}

fn sections_contain(sections: &[Range<RVA>], rva: RVA) -> bool {
    sections.iter()
        .find(|section| section.contains(&rva))
        .is_some()
}

impl Analyzer for RelocAnalyzer {
    fn get_name(&self) -> String {
        "PE relocation analyzer".to_string()
    }

    /// ```
    /// use lancelot::rsrc::*;
    /// use lancelot::arch::*;
    /// use lancelot::analysis::Analyzer;
    /// use lancelot::workspace::Workspace;
    /// use lancelot::analysis::pe::RelocAnalyzer;
    /// lancelot::test::init_logging();
    ///
    /// let mut ws = Workspace::from_bytes("k32.dll", &get_buf(Rsrc::K32))
    ///    .disable_analysis()
    ///    .load().unwrap();
    ///
    /// let anal = RelocAnalyzer::new();
    /// anal.analyze(&mut ws).unwrap();
    ///
    /// assert!(ws.get_meta(RVA(0xC7F0)).unwrap().is_insn());
    /// ```
    fn analyze(&self, ws: &mut Workspace)-> Result<(), Error> {
        let x_sections: Vec<Range<RVA>> = ws.module.sections.iter()
            .filter(|section| section.perms.intersects(Permissions::X))
            .map(|section| Range {
                start: section.addr,
                end: section.end(),
            })
            .collect();

        let relocs: Vec<Reloc> = get_relocs(ws)?
            .into_iter()
            .filter(|r| match r.typ {
                RelocationType::ImageRelBasedHighLow => true,
                RelocationType::ImageRelBasedDir64 => true,
                _ => false,  // all other unsupported
            })
            .collect();
        debug!("found {} total relocs", relocs.len());

        for reloc in relocs.iter() {
            let target = ws.rva(ws.read_va(reloc.offset)?).expect("reloc target not in image");
            trace!("found reloc {} -> {} {}",
                   reloc.offset,
                   target,
                if sections_contain(&x_sections, target) {"(code)"} else {""}
            );
        }

        // scan for relocations to code.
        //
        // a relocation is a hardcoded offset that must be fixed up if the desired base address
        //  cannot be used.
        // for example, the function pointer passed to CreateThread will be a hardcoded address
        //  of the start of the function.
        //
        // relocated pointers may point to the .data section, e.g. strings or other constants.
        // we want to ignore these.
        // we are only interested in targets in executable sections.
        // we assume these are pointers to instructions/code.
        //
        // looking for pointers into the .text section
        // to things that
        //   1. are not already in an instruction
        //   2. don't appear to be a pointer
        // and assume this is code.

        let mut unique_targets: HashSet<RVA> = HashSet::new();
        unique_targets.extend(relocs
            .iter()
            .map(|reloc| reloc.offset)
            .map(|rva| ws.read_va(rva))
            .filter_map(Result::ok)
            .filter_map(|va| ws.rva(va)));

        debug!("reduced to {} unique reloc targets", unique_targets.len());

       let o: Vec<RVA> = unique_targets
            .iter()
            .filter(|&&rva| sections_contain(&x_sections, rva))
            .filter(|&&rva| !is_in_insn(ws, rva))
            .filter(|&&rva| !is_ptr(ws, rva))
            .map(|&rva| rva)
            // TODO: maybe ensure that the insn decodes.
            .collect();

        debug!("found {} relocs that point to instructions", o.len());

        o.iter().for_each(|&rva| {
            debug!("found pointer via relocations from executable section to {} (code)", rva);

            // TODO: consume result
            ws.make_insn(rva).unwrap();
            // TODO: consume result
            ws.analyze().unwrap();
        });
        Ok(())
    }
}
<commit_msg>analysis: reloc: don't panic on bad reloc target<commit_after>use byteorder::{ByteOrder, LittleEndian};
use log::{debug, trace};
use goblin::{Object};
use failure::{Error, Fail};
use std::ops::Range;

use super::super::super::arch::{RVA};
use super::super::super::loader::Permissions;
use super::super::super::workspace::Workspace;
use super::super::{Analyzer};
use std::collections::{HashSet};

#[derive(Debug, Fail)]
pub enum RelocAnalyzerError {
    #[fail(display = "Invalid relocation type")]
    InvalidRelocType,
    #[fail(display = "Relocation target not found in image")]
    InvalidTargetAddress,
}

pub struct RelocAnalyzer {}

impl RelocAnalyzer {
    pub fn new() -> RelocAnalyzer {
        RelocAnalyzer {}
    }
}

// TODO: make this much faster
fn is_in_insn(ws: &Workspace, rva: RVA) -> bool {
    let start: usize = rva.into();
    // TODO: underflow
    // TODO: remove harded max insn length
    let end: usize = start - 0x10;

    for i in (start..end).rev() {
        let i = RVA::from(i);
        if let Some(meta) = ws.get_meta(i) {
            if !meta.is_insn() {
                continue;
            }

            if let Ok(len) = meta.get_insn_length() {
                if i + len > rva {
                    return true;
                }
            }
        }
    }
    return false;
}

fn is_ptr(ws: &Workspace, rva: RVA) -> bool {
    if let Ok(ptr) = ws.read_va(rva) {
        if let Some(ptr) = ws.rva(ptr) {
            return ws.probe(ptr, 1, Permissions::R);
        }
    }
    return false;
}

pub enum RelocationType {
    ImageRelBasedAbsolute,
    ImageRelBasedHigh,
    ImageRelBasedLow,
    ImageRelBasedHighLow,
    ImageRelBasedHighAdj,

    // ImageRelBasedMIPS_JmpAddr,
    // ImageRelBasedARM_MOV32,
    // ImageRelBasedRiscV_High20,
    ImageRelArch1,

    ImageRelReserved,

    // ImageRelBasedTHUMB_MOV32,
    // ImageRelBasedRiscV_Low12I,
    ImageRelArch2,

    ImageRelBasedRiscVLow12S,
    ImageRelBasedMIPSJmpAddr16,
    ImageRelBasedDir64,
}

pub struct Reloc {
    pub typ: RelocationType,
    pub offset: RVA,
}

fn parse_reloc(base: RVA, entry: u16) -> Result<Reloc, Error> {
    let reloc_type   = (entry & 0b1111000000000000) >> 12;
    let reloc_offset =  entry & 0b0000111111111111;

    // TODO: this should probably be on `RelocationType` as a `From` trait.
    let reloc_type = match reloc_type {
        0 => RelocationType::ImageRelBasedAbsolute,
        1 => RelocationType::ImageRelBasedHigh,
        2 => RelocationType::ImageRelBasedLow,
        3 => RelocationType::ImageRelBasedHighLow,
        4 => RelocationType::ImageRelBasedHighAdj,
        5 => RelocationType::ImageRelArch1,
        6 => RelocationType::ImageRelReserved,
        7 => RelocationType::ImageRelArch2,
        8 => RelocationType::ImageRelBasedRiscVLow12S,
        9 => RelocationType::ImageRelBasedMIPSJmpAddr16,
        10 => RelocationType::ImageRelBasedDir64,
        _ => return Err(RelocAnalyzerError::InvalidRelocType.into()),
    };

    Ok(Reloc {
        typ: reloc_type,
        offset: base + RVA::from(reloc_offset as i64),
    })
}

fn split_u32(e: u32) -> (u16, u16) {
    let m = ((e & 0x0000FFFF) >>  0) as u16;
    let n = ((e & 0xFFFF0000) >> 16) as u16;
    (m, n)
}

/// ```
/// use lancelot::rsrc::*;
/// use lancelot::arch::*;
/// use lancelot::workspace::Workspace;
/// use lancelot::analysis::pe::relocs;
///
/// let mut ws = Workspace::from_bytes("k32.dll", &get_buf(Rsrc::K32))
///    .disable_analysis()
///    .load().unwrap();
/// let relocs = relocs::get_relocs(&ws).unwrap();
/// assert_eq!(relocs[0].offset,   RVA(0x76008));
/// assert_eq!(relocs[277].offset, RVA(0xA8000));
/// ```
pub fn get_relocs(ws: &Workspace) -> Result<Vec<Reloc>, Error> {
    let pe = match Object::parse(&ws.buf) {
        Ok(Object::PE(pe)) => pe,
        _ => return Ok(vec![]),
    };

    let opt_header = match pe.header.optional_header {
        Some(opt_header) => opt_header,
        _ => return Ok(vec![]),
    };

    let reloc_directory = match opt_header.data_directories.get_base_relocation_table() {
        Some(reloc_directory) => reloc_directory,
        _ => return Ok(vec![]),
    };

    let dir_start = RVA::from(reloc_directory.virtual_address as i64);
    let buf = ws.read_bytes(dir_start, reloc_directory.size as usize)?;

    let entries: Vec<u32> = buf
        .chunks_exact(0x4)
        .map(|b| LittleEndian::read_u32(b))
        .collect();

    let mut ret = vec![];
    let mut index = 0;
    loop {
        // parse base relocation chunk
        // until there's no data left

        let page_rva = entries.get(index);
        let block_size = entries.get(index+1);
        let (page_rva, block_size) = match (page_rva, block_size) {
            (Some(0),        _               ) => break,
            (_,              Some(0)         ) => break,
            (Some(page_rva), Some(block_size)) => (RVA::from(*page_rva as i64), *block_size),
            _ => break,
        };

        // cast from u32 to usize
        let chunk_entries_count = ((block_size) / 4) as usize;

        for i in 2..chunk_entries_count {
            if let Some(&entry) = entries.get(index + i) {
                let (m, n) = split_u32(entry);

                let reloc1 = parse_reloc(page_rva, m)?;
                if !ws.probe(reloc1.offset, 4, Permissions::R) {
                    break
                }
                ret.push(reloc1);

                let reloc2 = parse_reloc(page_rva, n)?;
                if !ws.probe(reloc2.offset, 4, Permissions::R) {
                    break
                }
                ret.push(reloc2);
            } else {
                break
            }
        };

        index += chunk_entries_count;
    }

    Ok(ret)
}

fn sections_contain(sections: &[Range<RVA>], rva: RVA) -> bool {
    sections.iter()
        .find(|section| section.contains(&rva))
        .is_some()
}

impl Analyzer for RelocAnalyzer {
    fn get_name(&self) -> String {
        "PE relocation analyzer".to_string()
    }

    /// ```
    /// use lancelot::rsrc::*;
    /// use lancelot::arch::*;
    /// use lancelot::analysis::Analyzer;
    /// use lancelot::workspace::Workspace;
    /// use lancelot::analysis::pe::RelocAnalyzer;
    /// lancelot::test::init_logging();
    ///
    /// let mut ws = Workspace::from_bytes("k32.dll", &get_buf(Rsrc::K32))
    ///    .disable_analysis()
    ///    .load().unwrap();
    ///
    /// let anal = RelocAnalyzer::new();
    /// anal.analyze(&mut ws).unwrap();
    ///
    /// assert!(ws.get_meta(RVA(0xC7F0)).unwrap().is_insn());
    /// ```
    fn analyze(&self, ws: &mut Workspace)-> Result<(), Error> {
        let x_sections: Vec<Range<RVA>> = ws.module.sections.iter()
            .filter(|section| section.perms.intersects(Permissions::X))
            .map(|section| Range {
                start: section.addr,
                end: section.end(),
            })
            .collect();

        let relocs: Vec<Reloc> = get_relocs(ws)?
            .into_iter()
            .filter(|r| match r.typ {
                RelocationType::ImageRelBasedHighLow => true,
                RelocationType::ImageRelBasedDir64 => true,
                _ => false,  // all other unsupported
            })
            .collect();
        debug!("found {} total relocs", relocs.len());

        for reloc in relocs.iter() {
            let target = ws.rva(ws.read_va(reloc.offset)?).ok_or(RelocAnalyzerError::InvalidTargetAddress)?;
            trace!("found reloc {} -> {} {}",
                   reloc.offset,
                   target,
                if sections_contain(&x_sections, target) {"(code)"} else {""}
            );
        }

        // scan for relocations to code.
        //
        // a relocation is a hardcoded offset that must be fixed up if the desired base address
        //  cannot be used.
        // for example, the function pointer passed to CreateThread will be a hardcoded address
        //  of the start of the function.
        //
        // relocated pointers may point to the .data section, e.g. strings or other constants.
        // we want to ignore these.
        // we are only interested in targets in executable sections.
        // we assume these are pointers to instructions/code.
        //
        // looking for pointers into the .text section
        // to things that
        //   1. are not already in an instruction
        //   2. don't appear to be a pointer
        // and assume this is code.

        let mut unique_targets: HashSet<RVA> = HashSet::new();
        unique_targets.extend(relocs
            .iter()
            .map(|reloc| reloc.offset)
            .map(|rva| ws.read_va(rva))
            .filter_map(Result::ok)
            .filter_map(|va| ws.rva(va)));

        debug!("reduced to {} unique reloc targets", unique_targets.len());

       let o: Vec<RVA> = unique_targets
            .iter()
            .filter(|&&rva| sections_contain(&x_sections, rva))
            .filter(|&&rva| !is_in_insn(ws, rva))
            .filter(|&&rva| !is_ptr(ws, rva))
            .map(|&rva| rva)
            // TODO: maybe ensure that the insn decodes.
            .collect();

        debug!("found {} relocs that point to instructions", o.len());

        o.iter().for_each(|&rva| {
            debug!("found pointer via relocations from executable section to {} (code)", rva);

            // TODO: consume result
            ws.make_insn(rva).unwrap();
            // TODO: consume result
            ws.analyze().unwrap();
        });
        Ok(())
    }
}
<|endoftext|>