<commit_before>/*
 * Copyright (c) 2016 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

use quote::Tokens;
use syn::{Attribute, Body, Ident, MacroInput, Path, VariantData};
use syn::Body::{Enum, Struct};
use syn::MetaItem::Word;
use syn::Ty;

use string::{snake_to_camel, to_dash_name};

/// Expand the required trais for the derive Setting attribute.
pub fn expand_setting_enum(mut ast: MacroInput) -> Tokens {
    let name = ast.ident.clone();
    let mut default = None;

    let mut variant_names = vec![];
    if let Enum(ref mut variants) = ast.body {
        for variant in variants {
            variant_names.push(variant.ident.clone());
            if !variant.attrs.is_empty() {
                for attribute in &variant.attrs {
                    if let &Attribute { value: Word(ref ident), .. } = attribute {
                        if ident == "default" {
                            default = Some(variant.ident.clone());
                        }
                    }
                }
                variant.attrs.clear();
            }
        }
    }
    let choice_names: Vec<_> = variant_names.iter()
        .map(|name| to_dash_name(&name.to_string()))
        .collect();
    let choice_names1 = &choice_names;
    let choice_names2 = &choice_names;

    let qualified_names = variant_names.iter()
        .map(|variant_name| quote! {
            #name::#variant_name
        });

    let from_str_fn = quote! {
        fn from_str(string: &str) -> Result<Self, Self::Err> {
            match string {
                #(#choice_names1 => Ok(#qualified_names),)*
                _ => Err(::mg_settings::error::SettingError::UnknownChoice {
                    actual: string.to_string(),
                    expected: vec![#(#choice_names2),*],
                }),
            }
        }
    };

    let default_impl =
        if let Some(ident) = default {
            quote! {
                impl Default for #name {
                    fn default() -> Self {
                        #ident
                    }
                }
            }
        }
        else {
            quote! {
            }
        };

    quote! {
        #[derive(Clone)]
        #ast

        #default_impl

        impl ::std::str::FromStr for #name {
            type Err = ::mg_settings::error::SettingError;

            #from_str_fn
        }
    }
}

/// Expand the required traits for the derive Settings attribute.
pub fn expand_settings_enum(ast: MacroInput) -> Tokens {
    let name = ast.ident.clone();
    let variant_name = Ident::new(format!("{}Variant", name));
    let variant_enum = to_enums(&ast.ident, &variant_name, &ast.body);
    let settings_impl = to_settings_impl(&name, &variant_name, &ast.body);
    quote! {
        #[derive(Default)]
        #ast

        #variant_enum

        #settings_impl
    }
}

/// Check if a type is a custom type (including enum).
fn is_custom_type(ident: &Ident) -> bool {
    match ident.to_string().as_ref() {
        "bool" | "f64" | "i64" | "String" => false,
        _ => true,
    }
}

/// Create the variant enums for getters and setters.
fn to_enums(name: &Ident, variant_name: &Ident, settings_struct: &Body) -> Tokens {
    if let &Struct(VariantData::Struct(ref strct)) = settings_struct {
        let mut field_names = vec![];
        let mut names = vec![];
        let mut types = vec![];
        for field in strct {
            if let Some(ref ident) = field.ident {
                field_names.push(ident);
                let ident = Ident::new(snake_to_camel(&ident.to_string()));
                names.push(ident);
                types.push(field.ty.clone());
            }
        }
        let names1 = &names;
        quote! {
            #[derive(Clone)]
            pub enum #variant_name {
                #(#names1(#types)),*
            }

            impl #name {
                pub fn new() -> #name {
                    #name::default()
                }
            }
        }
    }
    else {
        panic!("Not a struct");
    }
}

/// Create the impl Settings.
fn to_settings_impl(name: &Ident, variant_name: &Ident, settings_struct: &Body) -> Tokens {
    if let &Struct(VariantData::Struct(ref strct)) = settings_struct {
        let mut names = vec![];
        let mut capitalized_names = vec![];
        let mut original_types = vec![];
        let mut types = vec![];
        for field in strct {
            if let Some(ref ident) = field.ident {
                let ident_string = ident.to_string();
                let ident = Ident::new(ident_string.clone());
                names.push(ident);
                let ident = Ident::new(snake_to_camel(&ident_string));
                capitalized_names.push(
                    quote! {
                        #variant_name::#ident
                    });

                if let Ty::Path(_, Path { ref segments, .. }) = field.ty {
                    original_types.push(&segments[0].ident);
                    types.push(to_value_type(&segments[0].ident));
                }
            }
        }
        let string_names: Vec<_> = names.iter()
            .map(|ident| ident.to_string().replace("_", "-"))
            .collect();
        let string_names = &string_names;
        let capitalized_names = &capitalized_names;
        let names1 = &names;
        let variant_exprs = names.iter().zip(original_types.iter())
            .map(|(name, typ)|
                 if is_custom_type(typ) {
                     quote! {
                         ::std::str::FromStr::from_str(&#name)?
                     }
                 }
                 else {
                     quote! { #name }
                 }
            );
        let names2 = &names;
        let names3 = &names;
        let types1 = &types;
        let type_names = types.iter()
            .map(|ident| value_type_to_type(&ident));

        let to_variant_fn_variant = quote! {
            #(#string_names => {
                if let ::mg_settings::Value::#types1(#names1) = value {
                    Ok(#capitalized_names(#variant_exprs))
                }
                else {
                    Err(::mg_settings::error::SettingError::WrongType {
                        actual: value.to_type().to_string(),
                        expected: #type_names.to_string(),
                    })
                }
            },)*
        };

        let to_variant_fn = quote! {
            fn to_variant(name: &str, value: ::mg_settings::Value) -> Result<Self::Variant, ::mg_settings::error::SettingError> {
                match name {
                    #to_variant_fn_variant
                    _ => Err(::mg_settings::error::SettingError::UnknownSetting(name.to_string())),
                }
            }
        };

        quote! {
            impl ::mg_settings::settings::Settings for #name {
                type Variant = #variant_name;

                #to_variant_fn

                fn set_value(&mut self, value: Self::Variant) {
                    match value {
                        #(#capitalized_names(#names1) => {
                            self.#names2 = #names3
                        }),*
                    }
                }
            }
        }
    }
    else {
        panic!("Not a struct");
    }
}

/// Convert a Rust type to a `Value` type.
fn to_value_type(ident: &Ident) -> Ident {
    let value_type =
        match ident.to_string().as_ref() {
            "bool" => "Bool",
            "f64" => "Float",
            "i64" => "Int",
            _ => "Str",
        };
    Ident::new(value_type)
}

/// Convert a `Value` type to a string representation of the type.
fn value_type_to_type(ident: &Ident) -> &str {
    match ident.to_string().as_ref() {
        "Bool" => "bool",
        "Float" => "float",
        "Int" => "integer",
        "Str" => "string",
        ty => panic!("Unknown Value type {}", ty),
    }
}
<commit_msg>Fixed to use qualified name in Default implementation.<commit_after>/*
 * Copyright (c) 2016 Boucher, Antoni <bouanto@zoho.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

use quote::Tokens;
use syn::{Attribute, Body, Ident, MacroInput, Path, VariantData};
use syn::Body::{Enum, Struct};
use syn::MetaItem::Word;
use syn::Ty;

use string::{snake_to_camel, to_dash_name};

/// Expand the required trais for the derive Setting attribute.
pub fn expand_setting_enum(mut ast: MacroInput) -> Tokens {
    let name = ast.ident.clone();
    let mut default = None;

    let mut variant_names = vec![];
    if let Enum(ref mut variants) = ast.body {
        for variant in variants {
            variant_names.push(variant.ident.clone());
            if !variant.attrs.is_empty() {
                for attribute in &variant.attrs {
                    if let &Attribute { value: Word(ref ident), .. } = attribute {
                        if ident == "default" {
                            default = Some(variant.ident.clone());
                        }
                    }
                }
                variant.attrs.clear();
            }
        }
    }
    let choice_names: Vec<_> = variant_names.iter()
        .map(|name| to_dash_name(&name.to_string()))
        .collect();
    let choice_names1 = &choice_names;
    let choice_names2 = &choice_names;

    let qualified_names = variant_names.iter()
        .map(|variant_name| quote! {
            #name::#variant_name
        });

    let from_str_fn = quote! {
        fn from_str(string: &str) -> Result<Self, Self::Err> {
            match string {
                #(#choice_names1 => Ok(#qualified_names),)*
                _ => Err(::mg_settings::error::SettingError::UnknownChoice {
                    actual: string.to_string(),
                    expected: vec![#(#choice_names2),*],
                }),
            }
        }
    };

    let default_impl =
        if let Some(ident) = default {
            quote! {
                impl Default for #name {
                    fn default() -> Self {
                        #name::#ident
                    }
                }
            }
        }
        else {
            quote! {
            }
        };

    quote! {
        #[derive(Clone)]
        #ast

        #default_impl

        impl ::std::str::FromStr for #name {
            type Err = ::mg_settings::error::SettingError;

            #from_str_fn
        }
    }
}

/// Expand the required traits for the derive Settings attribute.
pub fn expand_settings_enum(ast: MacroInput) -> Tokens {
    let name = ast.ident.clone();
    let variant_name = Ident::new(format!("{}Variant", name));
    let variant_enum = to_enums(&ast.ident, &variant_name, &ast.body);
    let settings_impl = to_settings_impl(&name, &variant_name, &ast.body);
    quote! {
        #[derive(Default)]
        #ast

        #variant_enum

        #settings_impl
    }
}

/// Check if a type is a custom type (including enum).
fn is_custom_type(ident: &Ident) -> bool {
    match ident.to_string().as_ref() {
        "bool" | "f64" | "i64" | "String" => false,
        _ => true,
    }
}

/// Create the variant enums for getters and setters.
fn to_enums(name: &Ident, variant_name: &Ident, settings_struct: &Body) -> Tokens {
    if let &Struct(VariantData::Struct(ref strct)) = settings_struct {
        let mut field_names = vec![];
        let mut names = vec![];
        let mut types = vec![];
        for field in strct {
            if let Some(ref ident) = field.ident {
                field_names.push(ident);
                let ident = Ident::new(snake_to_camel(&ident.to_string()));
                names.push(ident);
                types.push(field.ty.clone());
            }
        }
        let names1 = &names;
        quote! {
            #[derive(Clone)]
            pub enum #variant_name {
                #(#names1(#types)),*
            }

            impl #name {
                pub fn new() -> #name {
                    #name::default()
                }
            }
        }
    }
    else {
        panic!("Not a struct");
    }
}

/// Create the impl Settings.
fn to_settings_impl(name: &Ident, variant_name: &Ident, settings_struct: &Body) -> Tokens {
    if let &Struct(VariantData::Struct(ref strct)) = settings_struct {
        let mut names = vec![];
        let mut capitalized_names = vec![];
        let mut original_types = vec![];
        let mut types = vec![];
        for field in strct {
            if let Some(ref ident) = field.ident {
                let ident_string = ident.to_string();
                let ident = Ident::new(ident_string.clone());
                names.push(ident);
                let ident = Ident::new(snake_to_camel(&ident_string));
                capitalized_names.push(
                    quote! {
                        #variant_name::#ident
                    });

                if let Ty::Path(_, Path { ref segments, .. }) = field.ty {
                    original_types.push(&segments[0].ident);
                    types.push(to_value_type(&segments[0].ident));
                }
            }
        }
        let string_names: Vec<_> = names.iter()
            .map(|ident| ident.to_string().replace("_", "-"))
            .collect();
        let string_names = &string_names;
        let capitalized_names = &capitalized_names;
        let names1 = &names;
        let variant_exprs = names.iter().zip(original_types.iter())
            .map(|(name, typ)|
                 if is_custom_type(typ) {
                     quote! {
                         ::std::str::FromStr::from_str(&#name)?
                     }
                 }
                 else {
                     quote! { #name }
                 }
            );
        let names2 = &names;
        let names3 = &names;
        let types1 = &types;
        let type_names = types.iter()
            .map(|ident| value_type_to_type(&ident));

        let to_variant_fn_variant = quote! {
            #(#string_names => {
                if let ::mg_settings::Value::#types1(#names1) = value {
                    Ok(#capitalized_names(#variant_exprs))
                }
                else {
                    Err(::mg_settings::error::SettingError::WrongType {
                        actual: value.to_type().to_string(),
                        expected: #type_names.to_string(),
                    })
                }
            },)*
        };

        let to_variant_fn = quote! {
            fn to_variant(name: &str, value: ::mg_settings::Value) -> Result<Self::Variant, ::mg_settings::error::SettingError> {
                match name {
                    #to_variant_fn_variant
                    _ => Err(::mg_settings::error::SettingError::UnknownSetting(name.to_string())),
                }
            }
        };

        quote! {
            impl ::mg_settings::settings::Settings for #name {
                type Variant = #variant_name;

                #to_variant_fn

                fn set_value(&mut self, value: Self::Variant) {
                    match value {
                        #(#capitalized_names(#names1) => {
                            self.#names2 = #names3
                        }),*
                    }
                }
            }
        }
    }
    else {
        panic!("Not a struct");
    }
}

/// Convert a Rust type to a `Value` type.
fn to_value_type(ident: &Ident) -> Ident {
    let value_type =
        match ident.to_string().as_ref() {
            "bool" => "Bool",
            "f64" => "Float",
            "i64" => "Int",
            _ => "Str",
        };
    Ident::new(value_type)
}

/// Convert a `Value` type to a string representation of the type.
fn value_type_to_type(ident: &Ident) -> &str {
    match ident.to_string().as_ref() {
        "Bool" => "bool",
        "Float" => "float",
        "Int" => "integer",
        "Str" => "string",
        ty => panic!("Unknown Value type {}", ty),
    }
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Functions for dealing with devices.

use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::os::unix::prelude::AsRawFd;
use std::path::Path;

use devicemapper::{devnode_to_devno, Bytes, Device};
use stratis::{ErrorEnum, StratisError, StratisResult};

use super::super::super::types::{DevUuid, PoolUuid};
use super::metadata::StaticHeader;
use super::util::get_udev_block_device;

ioctl!(read blkgetsize64 with 0x12, 114; u64);

pub fn blkdev_size(file: &File) -> StratisResult<Bytes> {
    let mut val: u64 = 0;

    match unsafe { blkgetsize64(file.as_raw_fd(), &mut val) } {
        Err(x) => Err(StratisError::Nix(x)),
        Ok(_) => Ok(Bytes(val)),
    }
}

/// Resolve a list of Paths of some sort to a set of unique Devices.
/// Return an IOError if there was a problem resolving any particular device.
/// The set of devices maps each device to one of the paths passed.
/// Returns an error if any path does not correspond to a block device.
pub fn resolve_devices<'a>(paths: &'a [&Path]) -> StratisResult<HashMap<Device, &'a Path>> {
    let mut map = HashMap::new();
    for path in paths {
        match devnode_to_devno(path)? {
            Some(devno) => {
                let _ = map.insert(Device::from(devno), *path);
            }
            None => {
                let err_msg = format!("path {} does not refer to a block device", path.display());
                return Err(StratisError::Engine(ErrorEnum::Invalid, err_msg));
            }
        }
    }
    Ok(map)
}

#[derive(Debug, PartialEq, Eq)]
pub enum DevOwnership {
    Ours(PoolUuid, DevUuid),
    Unowned,
    Theirs(String), // String is something useful to give back to end user about what's on device
}

/// Returns true if a device has no signature, yes this is a bit convoluted.  Logic gleaned from
/// blivet library.
fn empty(device: &HashMap<String, String>) -> bool {
    !((device.contains_key("ID_PART_TABLE_TYPE") && !device.contains_key("ID_PART_ENTRY_DISK"))
        || device.contains_key("ID_FS_USAGE"))
}

/// Generate some kind of human readable text about what's on a device.
fn signature(device: &HashMap<String, String>) -> String {
    if empty(device) {
        String::from("empty")
    } else {
        device
            .iter()
            .filter(|&(k, _)| k.contains("ID_FS_") || k.contains("ID_PART_TABLE_"))
            .map(|(k, v)| format!("{}={}", k, v))
            .collect::<Vec<String>>()
            .join(" ")
    }
}

/// Determine what a block device is used for.
pub fn identify(devnode: &Path) -> StratisResult<DevOwnership> {
    if let Some(device) = get_udev_block_device(devnode)? {
        if empty(&device) {
            // The device is either really empty or we are running on a distribution that hasn't
            // picked up the latest libblkid, lets read down to the device and find out for sure.
            // TODO: At some point in the future we can remove this and just return Unowned.
            if let Some((pool_uuid, device_uuid)) = StaticHeader::device_identifiers(
                &mut OpenOptions::new().read(true).open(&devnode)?,
            )? {
                Ok(DevOwnership::Ours(pool_uuid, device_uuid))
            } else {
                Ok(DevOwnership::Unowned)
            }
        } else if device.contains_key("ID_FS_TYPE") && device["ID_FS_TYPE"] == "stratis" {
            // Device is ours, but we don't get everything we need from udev db, lets go to disk.
            if let Some((pool_uuid, device_uuid)) = StaticHeader::device_identifiers(
                &mut OpenOptions::new().read(true).open(&devnode)?,
            )? {
                Ok(DevOwnership::Ours(pool_uuid, device_uuid))
            } else {
                // In this case the udev db says it's ours, but our check says otherwise.  We should
                // trust ourselves.  Should we raise an error here?
                Ok(DevOwnership::Theirs(String::from(
                    "Udev db says stratis, disk meta says no",
                )))
            }
        } else {
            Ok(DevOwnership::Theirs(signature(&device)))
        }
    } else {
        Err(StratisError::Engine(
            ErrorEnum::NotFound,
            format!(
                "We expected to find the block device {:?} \
                 in the udev db",
                devnode
            ),
        ))
    }
}

/// Determine if devnode is a Stratis device. Return the device's Stratis
/// pool UUID if it belongs to Stratis.
pub fn is_stratis_device(devnode: &Path) -> StratisResult<Option<PoolUuid>> {
    match identify(devnode)? {
        DevOwnership::Ours(pool_uuid, _) => Ok(Some(pool_uuid)),
        _ => Ok(None),
    }
}

#[cfg(test)]
mod test {
    use std::path::Path;

    use super::super::super::cmd::{create_ext3_fs, udev_settle};
    use super::super::super::tests::{loopbacked, real};

    use super::super::device;

    /// Verify that the device is not stratis by creating a device with XFS fs.
    fn test_other_ownership(paths: &[&Path]) {
        create_ext3_fs(paths[0]).unwrap();

        udev_settle().unwrap();

        assert_eq!(device::is_stratis_device(paths[0]).unwrap(), None);

        assert!(match device::identify(paths[0]).unwrap() {
            device::DevOwnership::Theirs(identity) => {
                assert!(identity.contains("ID_FS_USAGE=filesystem"));
                assert!(identity.contains("ID_FS_TYPE=ext3"));
                assert!(identity.contains("ID_FS_UUID"));
                true
            }
            _ => false,
        });
    }

    /// Test a blank device and ensure it comes up as device::Usage::Unowned
    fn test_empty(paths: &[&Path]) {
        udev_settle().unwrap();

        assert_eq!(device::is_stratis_device(paths[0]).unwrap(), None);

        assert!(match device::identify(paths[0]).unwrap() {
            device::DevOwnership::Unowned => true,
            _ => false,
        });

        assert_eq!(device::is_stratis_device(paths[0]).unwrap(), None);
    }

    #[test]
    pub fn loop_test_device_other_ownership() {
        loopbacked::test_with_spec(
            loopbacked::DeviceLimits::Range(1, 3, None),
            test_other_ownership,
        );
    }

    #[test]
    pub fn real_test_device_other_ownership() {
        real::test_with_spec(
            real::DeviceLimits::AtLeast(1, None, None),
            test_other_ownership,
        );
    }

    #[test]
    pub fn loop_test_device_empty() {
        loopbacked::test_with_spec(loopbacked::DeviceLimits::Range(1, 3, None), test_empty);
    }

    #[test]
    pub fn real_test_device_empty() {
        real::test_with_spec(real::DeviceLimits::AtLeast(1, None, None), test_empty);
    }
}
<commit_msg>Get around already fixed rustfmt bug by using module prefix<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Functions for dealing with devices.

use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::os::unix::prelude::AsRawFd;
use std::path::Path;

use devicemapper::{devnode_to_devno, Bytes, Device};
use stratis::{ErrorEnum, StratisError, StratisResult};

use super::super::super::types::{DevUuid, PoolUuid};
use super::metadata::StaticHeader;
use super::util::get_udev_block_device;

ioctl!(read blkgetsize64 with 0x12, 114; u64);

pub fn blkdev_size(file: &File) -> StratisResult<Bytes> {
    let mut val: u64 = 0;

    match unsafe { blkgetsize64(file.as_raw_fd(), &mut val) } {
        Err(x) => Err(StratisError::Nix(x)),
        Ok(_) => Ok(Bytes(val)),
    }
}

/// Resolve a list of Paths of some sort to a set of unique Devices.
/// Return an IOError if there was a problem resolving any particular device.
/// The set of devices maps each device to one of the paths passed.
/// Returns an error if any path does not correspond to a block device.
pub fn resolve_devices<'a>(paths: &'a [&Path]) -> StratisResult<HashMap<Device, &'a Path>> {
    let mut map = HashMap::new();
    for path in paths {
        match devnode_to_devno(path)? {
            Some(devno) => {
                let _ = map.insert(Device::from(devno), *path);
            }
            None => {
                let err_msg = format!("path {} does not refer to a block device", path.display());
                return Err(StratisError::Engine(ErrorEnum::Invalid, err_msg));
            }
        }
    }
    Ok(map)
}

#[derive(Debug, PartialEq, Eq)]
pub enum DevOwnership {
    Ours(PoolUuid, DevUuid),
    Unowned,
    Theirs(String), // String is something useful to give back to end user about what's on device
}

/// Returns true if a device has no signature, yes this is a bit convoluted.  Logic gleaned from
/// blivet library.
fn empty(device: &HashMap<String, String>) -> bool {
    !((device.contains_key("ID_PART_TABLE_TYPE") && !device.contains_key("ID_PART_ENTRY_DISK"))
        || device.contains_key("ID_FS_USAGE"))
}

/// Generate some kind of human readable text about what's on a device.
fn signature(device: &HashMap<String, String>) -> String {
    if empty(device) {
        String::from("empty")
    } else {
        device
            .iter()
            .filter(|&(k, _)| k.contains("ID_FS_") || k.contains("ID_PART_TABLE_"))
            .map(|(k, v)| format!("{}={}", k, v))
            .collect::<Vec<String>>()
            .join(" ")
    }
}

/// Determine what a block device is used for.
pub fn identify(devnode: &Path) -> StratisResult<DevOwnership> {
    if let Some(device) = get_udev_block_device(devnode)? {
        if empty(&device) {
            // The device is either really empty or we are running on a distribution that hasn't
            // picked up the latest libblkid, lets read down to the device and find out for sure.
            // TODO: At some point in the future we can remove this and just return Unowned.
            if let Some((pool_uuid, device_uuid)) = StaticHeader::device_identifiers(
                &mut OpenOptions::new().read(true).open(&devnode)?,
            )? {
                Ok(DevOwnership::Ours(pool_uuid, device_uuid))
            } else {
                Ok(DevOwnership::Unowned)
            }
        } else if device.contains_key("ID_FS_TYPE") && device["ID_FS_TYPE"] == "stratis" {
            // Device is ours, but we don't get everything we need from udev db, lets go to disk.
            if let Some((pool_uuid, device_uuid)) = StaticHeader::device_identifiers(
                &mut OpenOptions::new().read(true).open(&devnode)?,
            )? {
                Ok(DevOwnership::Ours(pool_uuid, device_uuid))
            } else {
                // In this case the udev db says it's ours, but our check says otherwise.  We should
                // trust ourselves.  Should we raise an error here?
                Ok(DevOwnership::Theirs(String::from(
                    "Udev db says stratis, disk meta says no",
                )))
            }
        } else {
            Ok(DevOwnership::Theirs(signature(&device)))
        }
    } else {
        Err(StratisError::Engine(
            ErrorEnum::NotFound,
            format!(
                "We expected to find the block device {:?} \
                 in the udev db",
                devnode
            ),
        ))
    }
}

/// Determine if devnode is a Stratis device. Return the device's Stratis
/// pool UUID if it belongs to Stratis.
pub fn is_stratis_device(devnode: &Path) -> StratisResult<Option<PoolUuid>> {
    match identify(devnode)? {
        DevOwnership::Ours(pool_uuid, _) => Ok(Some(pool_uuid)),
        _ => Ok(None),
    }
}

#[cfg(test)]
mod test {
    use std::path::Path;

    use super::super::super::cmd;
    use super::super::super::tests::{loopbacked, real};

    use super::super::device;

    /// Verify that the device is not stratis by creating a device with XFS fs.
    fn test_other_ownership(paths: &[&Path]) {
        cmd::create_ext3_fs(paths[0]).unwrap();

        cmd::udev_settle().unwrap();

        assert_eq!(device::is_stratis_device(paths[0]).unwrap(), None);

        assert!(match device::identify(paths[0]).unwrap() {
            device::DevOwnership::Theirs(identity) => {
                assert!(identity.contains("ID_FS_USAGE=filesystem"));
                assert!(identity.contains("ID_FS_TYPE=ext3"));
                assert!(identity.contains("ID_FS_UUID"));
                true
            }
            _ => false,
        });
    }

    /// Test a blank device and ensure it comes up as device::Usage::Unowned
    fn test_empty(paths: &[&Path]) {
        cmd::udev_settle().unwrap();

        assert_eq!(device::is_stratis_device(paths[0]).unwrap(), None);

        assert!(match device::identify(paths[0]).unwrap() {
            device::DevOwnership::Unowned => true,
            _ => false,
        });

        assert_eq!(device::is_stratis_device(paths[0]).unwrap(), None);
    }

    #[test]
    pub fn loop_test_device_other_ownership() {
        loopbacked::test_with_spec(
            loopbacked::DeviceLimits::Range(1, 3, None),
            test_other_ownership,
        );
    }

    #[test]
    pub fn real_test_device_other_ownership() {
        real::test_with_spec(
            real::DeviceLimits::AtLeast(1, None, None),
            test_other_ownership,
        );
    }

    #[test]
    pub fn loop_test_device_empty() {
        loopbacked::test_with_spec(loopbacked::DeviceLimits::Range(1, 3, None), test_empty);
    }

    #[test]
    pub fn real_test_device_empty() {
        real::test_with_spec(real::DeviceLimits::AtLeast(1, None, None), test_empty);
    }
}
<|endoftext|>


<commit_before>// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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. This file may not be copied, modified, or distributed
// except according to those terms.

#[inline]
pub unsafe fn cbrtf(n: f32) -> f32 {
    f64::cbrt(n as f64) as f32
}

#[inline]
pub unsafe fn expm1f(n: f32) -> f32 {
    f64::exp_m1(n as f64) as f32
}

#[inline]
#[allow(deprecated)]
pub unsafe fn fdimf(a: f32, b: f32) -> f32 {
    f64::abs_sub(a as f64, b as f64) as f32
}

#[inline]
pub unsafe fn log1pf(n: f32) -> f32 {
    f64::ln_1p(n as f64) as f32
}

#[inline]
pub unsafe fn hypotf(x: f32, y: f32) -> f32 {
    f64::hypot(x as f64, y as f64) as f32
}

#[inline]
pub unsafe fn acosf(n: f32) -> f32 {
    f64::acos(n as f64) as f32
}

#[inline]
pub unsafe fn asinf(n: f32) -> f32 {
    f64::asin(n as f64) as f32
}

#[inline]
pub unsafe fn atan2f(n: f32, b: f32) -> f32 {
    f64::atan2(n as f64, b as f64) as f32
}

#[inline]
pub unsafe fn atanf(n: f32) -> f32 {
    f64::atan(n as f64) as f32
}

#[inline]
pub unsafe fn coshf(n: f32) -> f32 {
    f64::cosh(n as f64) as f32
}

#[inline]
pub unsafe fn sinhf(n: f32) -> f32 {
    f64::sinh(n as f64) as f32
}

#[inline]
pub unsafe fn tanf(n: f32) -> f32 {
    f64::tan(n as f64) as f32
}

#[inline]
pub unsafe fn tanhf(n: f32) -> f32 {
    f64::tanh(n as f64) as f32
}

// Right now all these functions, the f64 version of the functions above, all
// shell out to random names. These names aren't actually defined anywhere, per
// se, but we need this to compile somehow.
//
// The idea with this is that when you're using wasm then, for now, we have no
// way of providing an implementation of these which delegates to a "correct"
// implementation. For example most wasm applications probably just want to
// delegate to the javascript `Math` object and its related functions, but wasm
// doesn't currently have the ability to seamlessly do that (when you
// instantiate a module you have to set that up).
//
// As a result these are just defined here with "hopefully helpful" names. The
// symbols won't ever be needed or show up unless these functions are called,
// and hopefully when they're called the errors are self-explanatory enough to
// figure out what's going on.

extern {
    #[link_name = "Math_acos"]
    pub fn acos(n: f64) -> f64;
    #[link_name = "Math_asin"]
    pub fn asin(n: f64) -> f64;
    #[link_name = "Math_atan"]
    pub fn atan(n: f64) -> f64;
    #[link_name = "Math_atan2"]
    pub fn atan2(a: f64, b: f64) -> f64;
    #[link_name = "Math_cbrt"]
    pub fn cbrt(n: f64) -> f64;
    #[link_name = "Math_cosh"]
    pub fn cosh(n: f64) -> f64;
    #[link_name = "Math_expm1"]
    pub fn expm1(n: f64) -> f64;
    pub fn fdim(a: f64, b: f64) -> f64;
    #[link_name = "Math_log1p"]
    pub fn log1p(n: f64) -> f64;
    #[link_name = "Math_sinh"]
    pub fn sinh(n: f64) -> f64;
    #[link_name = "Math_tan"]
    pub fn tan(n: f64) -> f64;
    #[link_name = "Math_tanh"]
    pub fn tanh(n: f64) -> f64;
    #[link_name = "Math_hypot"]
    pub fn hypot(x: f64, y: f64) -> f64;
}
<commit_msg>Rollup merge of #54257 - alexcrichton:wasm-math-symbols, r=TimNN<commit_after>// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// 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. This file may not be copied, modified, or distributed
// except according to those terms.

#[inline]
pub unsafe fn cbrtf(n: f32) -> f32 {
    f64::cbrt(n as f64) as f32
}

#[inline]
pub unsafe fn expm1f(n: f32) -> f32 {
    f64::exp_m1(n as f64) as f32
}

#[inline]
#[allow(deprecated)]
pub unsafe fn fdimf(a: f32, b: f32) -> f32 {
    f64::abs_sub(a as f64, b as f64) as f32
}

#[inline]
pub unsafe fn log1pf(n: f32) -> f32 {
    f64::ln_1p(n as f64) as f32
}

#[inline]
pub unsafe fn hypotf(x: f32, y: f32) -> f32 {
    f64::hypot(x as f64, y as f64) as f32
}

#[inline]
pub unsafe fn acosf(n: f32) -> f32 {
    f64::acos(n as f64) as f32
}

#[inline]
pub unsafe fn asinf(n: f32) -> f32 {
    f64::asin(n as f64) as f32
}

#[inline]
pub unsafe fn atan2f(n: f32, b: f32) -> f32 {
    f64::atan2(n as f64, b as f64) as f32
}

#[inline]
pub unsafe fn atanf(n: f32) -> f32 {
    f64::atan(n as f64) as f32
}

#[inline]
pub unsafe fn coshf(n: f32) -> f32 {
    f64::cosh(n as f64) as f32
}

#[inline]
pub unsafe fn sinhf(n: f32) -> f32 {
    f64::sinh(n as f64) as f32
}

#[inline]
pub unsafe fn tanf(n: f32) -> f32 {
    f64::tan(n as f64) as f32
}

#[inline]
pub unsafe fn tanhf(n: f32) -> f32 {
    f64::tanh(n as f64) as f32
}

// These symbols are all defined in `compiler-builtins`
extern {
    pub fn acos(n: f64) -> f64;
    pub fn asin(n: f64) -> f64;
    pub fn atan(n: f64) -> f64;
    pub fn atan2(a: f64, b: f64) -> f64;
    pub fn cbrt(n: f64) -> f64;
    pub fn cosh(n: f64) -> f64;
    pub fn expm1(n: f64) -> f64;
    pub fn fdim(a: f64, b: f64) -> f64;
    pub fn log1p(n: f64) -> f64;
    pub fn sinh(n: f64) -> f64;
    pub fn tan(n: f64) -> f64;
    pub fn tanh(n: f64) -> f64;
    pub fn hypot(x: f64, y: f64) -> f64;
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use api::{BorderRadius, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag, LayoutPoint};
use api::{LayoutPrimitiveInfo, LayoutRect, PipelineId, WorldPoint};
use clip::{ClipDataStore, ClipNode, ClipItem, ClipStore};
use clip::{rounded_rectangle_contains_point};
use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
use internal_types::FastHashMap;
use prim_store::ScrollNodeAndClipChain;
use std::u32;
use util::LayoutToWorldFastTransform;

/// A copy of important clip scroll node data to use during hit testing. This a copy of
/// data from the ClipScrollTree that will persist as a new frame is under construction,
/// allowing hit tests consistent with the currently rendered frame.
#[derive(MallocSizeOf)]
pub struct HitTestSpatialNode {
    /// The pipeline id of this node.
    pipeline_id: PipelineId,

    /// World transform for content transformed by this node.
    world_content_transform: LayoutToWorldFastTransform,

    /// World viewport transform for content transformed by this node.
    world_viewport_transform: LayoutToWorldFastTransform,
}

#[derive(MallocSizeOf)]
pub struct HitTestClipNode {
    /// A particular point must be inside all of these regions to be considered clipped in
    /// for the purposes of a hit test.
    region: HitTestRegion,
}

impl HitTestClipNode {
    fn new(local_pos: LayoutPoint, node: &ClipNode) -> Self {
        let region = match node.item {
            ClipItem::Rectangle(size, mode) => {
                let rect = LayoutRect::new(local_pos, size);
                HitTestRegion::Rectangle(rect, mode)
            }
            ClipItem::RoundedRectangle(size, radii, mode) => {
                let rect = LayoutRect::new(local_pos, size);
                HitTestRegion::RoundedRectangle(rect, radii, mode)
            }
            ClipItem::Image { size, .. } => {
                let rect = LayoutRect::new(local_pos, size);
                HitTestRegion::Rectangle(rect, ClipMode::Clip)
            }
            ClipItem::BoxShadow(_) => HitTestRegion::Invalid,
        };

        HitTestClipNode {
            region,
        }
    }
}

// A hit testing clip chain node is the same as a
// normal clip chain node, except that the clip
// node is embedded inside the clip chain, rather
// than referenced. This means we don't need to
// copy the complete interned clip data store for
// hit testing.

#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq, Eq, Hash)]
pub struct HitTestClipChainId(u32);

impl HitTestClipChainId {
    pub const NONE: Self = HitTestClipChainId(u32::MAX);
}

#[derive(MallocSizeOf)]
pub struct HitTestClipChainNode {
    pub region: HitTestClipNode,
    pub spatial_node_index: SpatialNodeIndex,
    pub parent_clip_chain_id: HitTestClipChainId,
}

#[derive(Clone, MallocSizeOf)]
pub struct HitTestingItem {
    rect: LayoutRect,
    clip_rect: LayoutRect,
    tag: ItemTag,
    is_backface_visible: bool,
}

impl HitTestingItem {
    pub fn new(tag: ItemTag, info: &LayoutPrimitiveInfo) -> HitTestingItem {
        HitTestingItem {
            rect: info.rect,
            clip_rect: info.clip_rect,
            tag,
            is_backface_visible: info.is_backface_visible,
        }
    }
}

#[derive(Clone, MallocSizeOf)]
pub struct HitTestingRun(pub Vec<HitTestingItem>, pub ScrollNodeAndClipChain);

#[derive(MallocSizeOf)]
enum HitTestRegion {
    Invalid,
    Rectangle(LayoutRect, ClipMode),
    RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
}

impl HitTestRegion {
    pub fn contains(&self, point: &LayoutPoint) -> bool {
        match *self {
            HitTestRegion::Rectangle(ref rectangle, ClipMode::Clip) =>
                rectangle.contains(point),
            HitTestRegion::Rectangle(ref rectangle, ClipMode::ClipOut) =>
                !rectangle.contains(point),
            HitTestRegion::RoundedRectangle(rect, radii, ClipMode::Clip) =>
                rounded_rectangle_contains_point(point, &rect, &radii),
            HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
                !rounded_rectangle_contains_point(point, &rect, &radii),
            HitTestRegion::Invalid => true,
        }
    }
}

#[derive(MallocSizeOf)]
pub struct HitTester {
    runs: Vec<HitTestingRun>,
    spatial_nodes: Vec<HitTestSpatialNode>,
    clip_chains: Vec<HitTestClipChainNode>,
    pipeline_root_nodes: FastHashMap<PipelineId, SpatialNodeIndex>,
}

impl HitTester {
    pub fn new(
        runs: &Vec<HitTestingRun>,
        clip_scroll_tree: &ClipScrollTree,
        clip_store: &ClipStore,
        clip_data_store: &ClipDataStore,
    ) -> HitTester {
        let mut hit_tester = HitTester {
            runs: runs.clone(),
            spatial_nodes: Vec::new(),
            clip_chains: Vec::new(),
            pipeline_root_nodes: FastHashMap::default(),
        };
        hit_tester.read_clip_scroll_tree(
            clip_scroll_tree,
            clip_store,
            clip_data_store,
        );
        hit_tester
    }

    fn read_clip_scroll_tree(
        &mut self,
        clip_scroll_tree: &ClipScrollTree,
        clip_store: &ClipStore,
        clip_data_store: &ClipDataStore,
    ) {
        self.spatial_nodes.clear();
        self.clip_chains.clear();

        for (index, node) in clip_scroll_tree.spatial_nodes.iter().enumerate() {
            let index = SpatialNodeIndex::new(index);

            // If we haven't already seen a node for this pipeline, record this one as the root
            // node.
            self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);

            self.spatial_nodes.push(HitTestSpatialNode {
                pipeline_id: node.pipeline_id,
                world_content_transform: node.world_content_transform,
                world_viewport_transform: node.world_viewport_transform,
            });
        }

        // For each clip chain node, extract the clip node from the clip
        // data store, and store it inline with the clip chain node.
        for node in &clip_store.clip_chain_nodes {
            let clip_node = &clip_data_store[node.handle];
            self.clip_chains.push(HitTestClipChainNode {
                region: HitTestClipNode::new(node.local_pos, clip_node),
                spatial_node_index: node.spatial_node_index,
                parent_clip_chain_id: HitTestClipChainId(node.parent_clip_chain_id.0),
            });
        }
    }

    fn is_point_clipped_in_for_clip_chain(
        &self,
        point: WorldPoint,
        clip_chain_id: HitTestClipChainId,
        test: &mut HitTest
    ) -> bool {
        if clip_chain_id == HitTestClipChainId::NONE {
            return true;
        }

        if let Some(result) = test.get_from_clip_chain_cache(clip_chain_id) {
            return result == ClippedIn::ClippedIn;
        }

        let descriptor = &self.clip_chains[clip_chain_id.0 as usize];
        let parent_clipped_in = self.is_point_clipped_in_for_clip_chain(
            point,
            descriptor.parent_clip_chain_id,
            test,
        );

        if !parent_clipped_in {
            test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
            return false;
        }

        if !self.is_point_clipped_in_for_clip_node(
            point,
            clip_chain_id,
            descriptor.spatial_node_index,
            test,
        ) {
            test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
            return false;
        }

        test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::ClippedIn);
        true
    }

    fn is_point_clipped_in_for_clip_node(
        &self,
        point: WorldPoint,
        clip_chain_node_id: HitTestClipChainId,
        spatial_node_index: SpatialNodeIndex,
        test: &mut HitTest
    ) -> bool {
        if let Some(clipped_in) = test.node_cache.get(&clip_chain_node_id) {
            return *clipped_in == ClippedIn::ClippedIn;
        }

        let node = &self.clip_chains[clip_chain_node_id.0 as usize].region;
        let transform = self
            .spatial_nodes[spatial_node_index.0 as usize]
            .world_viewport_transform;
        let transformed_point = match transform
            .inverse()
            .and_then(|inverted| inverted.transform_point2d(&point))
        {
            Some(point) => point,
            None => {
                test.node_cache.insert(clip_chain_node_id, ClippedIn::NotClippedIn);
                return false;
            }
        };

        if !node.region.contains(&transformed_point) {
            test.node_cache.insert(clip_chain_node_id, ClippedIn::NotClippedIn);
            return false;
        }

        test.node_cache.insert(clip_chain_node_id, ClippedIn::ClippedIn);
        true
    }

    pub fn find_node_under_point(&self, mut test: HitTest) -> Option<SpatialNodeIndex> {
        let point = test.get_absolute_point(self);

        for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
            let spatial_node_index = clip_and_scroll.spatial_node_index;
            let scroll_node = &self.spatial_nodes[spatial_node_index.0 as usize];
            let transform = scroll_node.world_content_transform;
            let point_in_layer = match transform
                .inverse()
                .and_then(|inverted| inverted.transform_point2d(&point))
            {
                Some(point) => point,
                None => continue,
            };

            let mut clipped_in = false;
            for item in items.iter().rev() {
                if !item.rect.contains(&point_in_layer) ||
                    !item.clip_rect.contains(&point_in_layer) {
                    continue;
                }

                let clip_chain_id = HitTestClipChainId(clip_and_scroll.clip_chain_id.0);
                clipped_in |=
                    self.is_point_clipped_in_for_clip_chain(point, clip_chain_id, &mut test);
                if !clipped_in {
                    break;
                }

                return Some(spatial_node_index);
            }
        }

        None
    }

    pub fn hit_test(&self, mut test: HitTest) -> HitTestResult {
        let point = test.get_absolute_point(self);

        let mut result = HitTestResult::default();
        for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
            let spatial_node_index = clip_and_scroll.spatial_node_index;
            let scroll_node = &self.spatial_nodes[spatial_node_index.0 as usize];
            let pipeline_id = scroll_node.pipeline_id;
            match (test.pipeline_id, pipeline_id) {
                (Some(id), node_id) if node_id != id => continue,
                _ => {},
            }

            let transform = scroll_node.world_content_transform;
            let mut facing_backwards: Option<bool> = None;  // will be computed on first use
            let point_in_layer = match transform
                .inverse()
                .and_then(|inverted| inverted.transform_point2d(&point))
            {
                Some(point) => point,
                None => continue,
            };

            let mut clipped_in = false;
            for item in items.iter().rev() {
                if !item.rect.contains(&point_in_layer) ||
                    !item.clip_rect.contains(&point_in_layer) {
                    continue;
                }

                let clip_chain_id = HitTestClipChainId(clip_and_scroll.clip_chain_id.0);
                clipped_in = clipped_in ||
                    self.is_point_clipped_in_for_clip_chain(point, clip_chain_id, &mut test);
                if !clipped_in {
                    break;
                }

                // Don't hit items with backface-visibility:hidden if they are facing the back.
                if !item.is_backface_visible {
                    if *facing_backwards.get_or_insert_with(|| transform.is_backface_visible()) {
                        continue;
                    }
                }

                // We need to calculate the position of the test point relative to the origin of
                // the pipeline of the hit item. If we cannot get a transformed point, we are
                // in a situation with an uninvertible transformation so we should just skip this
                // result.
                let root_node = &self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0 as usize];
                let point_in_viewport = match root_node.world_viewport_transform
                    .inverse()
                    .and_then(|inverted| inverted.transform_point2d(&point))
                {
                    Some(point) => point,
                    None => continue,
                };

                result.items.push(HitTestItem {
                    pipeline: pipeline_id,
                    tag: item.tag,
                    point_in_viewport,
                    point_relative_to_item: point_in_layer - item.rect.origin.to_vector(),
                });
                if !test.flags.contains(HitTestFlags::FIND_ALL) {
                    return result;
                }
            }
        }

        result.items.dedup();
        result
    }

    pub fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestSpatialNode {
        &self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0 as usize]
    }
}

#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
enum ClippedIn {
    ClippedIn,
    NotClippedIn,
}

#[derive(MallocSizeOf)]
pub struct HitTest {
    pipeline_id: Option<PipelineId>,
    point: WorldPoint,
    flags: HitTestFlags,
    node_cache: FastHashMap<HitTestClipChainId, ClippedIn>,
    clip_chain_cache: Vec<Option<ClippedIn>>,
}

impl HitTest {
    pub fn new(
        pipeline_id: Option<PipelineId>,
        point: WorldPoint,
        flags: HitTestFlags,
    ) -> HitTest {
        HitTest {
            pipeline_id,
            point,
            flags,
            node_cache: FastHashMap::default(),
            clip_chain_cache: Vec::new(),
        }
    }

    fn get_from_clip_chain_cache(&mut self, index: HitTestClipChainId) -> Option<ClippedIn> {
        let index = index.0 as usize;
        if index >= self.clip_chain_cache.len() {
            None
        } else {
            self.clip_chain_cache[index]
        }
    }

    fn set_in_clip_chain_cache(&mut self, index: HitTestClipChainId, value: ClippedIn) {
        let index = index.0 as usize;
        if index >= self.clip_chain_cache.len() {
            self.clip_chain_cache.resize(index + 1, None);
        }
        self.clip_chain_cache[index] = Some(value);
    }

    fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
        if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
            return self.point;
        }

        let point =  &LayoutPoint::new(self.point.x, self.point.y);
        self.pipeline_id
            .and_then(|id|
                hit_tester
                    .get_pipeline_root(id)
                    .world_viewport_transform
                    .transform_point2d(point)
            )
            .unwrap_or_else(|| {
                WorldPoint::new(self.point.x, self.point.y)
            })
    }
}
<commit_msg>Bug 1476368 - Pre-allocate vectors in HitTester::read_clip_scroll_tree. r=kvark<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use api::{BorderRadius, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag, LayoutPoint};
use api::{LayoutPrimitiveInfo, LayoutRect, PipelineId, WorldPoint};
use clip::{ClipDataStore, ClipNode, ClipItem, ClipStore};
use clip::{rounded_rectangle_contains_point};
use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
use internal_types::FastHashMap;
use prim_store::ScrollNodeAndClipChain;
use std::u32;
use util::LayoutToWorldFastTransform;

/// A copy of important clip scroll node data to use during hit testing. This a copy of
/// data from the ClipScrollTree that will persist as a new frame is under construction,
/// allowing hit tests consistent with the currently rendered frame.
#[derive(MallocSizeOf)]
pub struct HitTestSpatialNode {
    /// The pipeline id of this node.
    pipeline_id: PipelineId,

    /// World transform for content transformed by this node.
    world_content_transform: LayoutToWorldFastTransform,

    /// World viewport transform for content transformed by this node.
    world_viewport_transform: LayoutToWorldFastTransform,
}

#[derive(MallocSizeOf)]
pub struct HitTestClipNode {
    /// A particular point must be inside all of these regions to be considered clipped in
    /// for the purposes of a hit test.
    region: HitTestRegion,
}

impl HitTestClipNode {
    fn new(local_pos: LayoutPoint, node: &ClipNode) -> Self {
        let region = match node.item {
            ClipItem::Rectangle(size, mode) => {
                let rect = LayoutRect::new(local_pos, size);
                HitTestRegion::Rectangle(rect, mode)
            }
            ClipItem::RoundedRectangle(size, radii, mode) => {
                let rect = LayoutRect::new(local_pos, size);
                HitTestRegion::RoundedRectangle(rect, radii, mode)
            }
            ClipItem::Image { size, .. } => {
                let rect = LayoutRect::new(local_pos, size);
                HitTestRegion::Rectangle(rect, ClipMode::Clip)
            }
            ClipItem::BoxShadow(_) => HitTestRegion::Invalid,
        };

        HitTestClipNode {
            region,
        }
    }
}

// A hit testing clip chain node is the same as a
// normal clip chain node, except that the clip
// node is embedded inside the clip chain, rather
// than referenced. This means we don't need to
// copy the complete interned clip data store for
// hit testing.

#[derive(Debug, Copy, Clone, MallocSizeOf, PartialEq, Eq, Hash)]
pub struct HitTestClipChainId(u32);

impl HitTestClipChainId {
    pub const NONE: Self = HitTestClipChainId(u32::MAX);
}

#[derive(MallocSizeOf)]
pub struct HitTestClipChainNode {
    pub region: HitTestClipNode,
    pub spatial_node_index: SpatialNodeIndex,
    pub parent_clip_chain_id: HitTestClipChainId,
}

#[derive(Clone, MallocSizeOf)]
pub struct HitTestingItem {
    rect: LayoutRect,
    clip_rect: LayoutRect,
    tag: ItemTag,
    is_backface_visible: bool,
}

impl HitTestingItem {
    pub fn new(tag: ItemTag, info: &LayoutPrimitiveInfo) -> HitTestingItem {
        HitTestingItem {
            rect: info.rect,
            clip_rect: info.clip_rect,
            tag,
            is_backface_visible: info.is_backface_visible,
        }
    }
}

#[derive(Clone, MallocSizeOf)]
pub struct HitTestingRun(pub Vec<HitTestingItem>, pub ScrollNodeAndClipChain);

#[derive(MallocSizeOf)]
enum HitTestRegion {
    Invalid,
    Rectangle(LayoutRect, ClipMode),
    RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
}

impl HitTestRegion {
    pub fn contains(&self, point: &LayoutPoint) -> bool {
        match *self {
            HitTestRegion::Rectangle(ref rectangle, ClipMode::Clip) =>
                rectangle.contains(point),
            HitTestRegion::Rectangle(ref rectangle, ClipMode::ClipOut) =>
                !rectangle.contains(point),
            HitTestRegion::RoundedRectangle(rect, radii, ClipMode::Clip) =>
                rounded_rectangle_contains_point(point, &rect, &radii),
            HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
                !rounded_rectangle_contains_point(point, &rect, &radii),
            HitTestRegion::Invalid => true,
        }
    }
}

#[derive(MallocSizeOf)]
pub struct HitTester {
    runs: Vec<HitTestingRun>,
    spatial_nodes: Vec<HitTestSpatialNode>,
    clip_chains: Vec<HitTestClipChainNode>,
    pipeline_root_nodes: FastHashMap<PipelineId, SpatialNodeIndex>,
}

impl HitTester {
    pub fn new(
        runs: &Vec<HitTestingRun>,
        clip_scroll_tree: &ClipScrollTree,
        clip_store: &ClipStore,
        clip_data_store: &ClipDataStore,
    ) -> HitTester {
        let mut hit_tester = HitTester {
            runs: runs.clone(),
            spatial_nodes: Vec::new(),
            clip_chains: Vec::new(),
            pipeline_root_nodes: FastHashMap::default(),
        };
        hit_tester.read_clip_scroll_tree(
            clip_scroll_tree,
            clip_store,
            clip_data_store,
        );
        hit_tester
    }

    fn read_clip_scroll_tree(
        &mut self,
        clip_scroll_tree: &ClipScrollTree,
        clip_store: &ClipStore,
        clip_data_store: &ClipDataStore,
    ) {
        self.spatial_nodes.clear();
        self.clip_chains.clear();

        self.spatial_nodes.reserve(clip_scroll_tree.spatial_nodes.len());
        for (index, node) in clip_scroll_tree.spatial_nodes.iter().enumerate() {
            let index = SpatialNodeIndex::new(index);

            // If we haven't already seen a node for this pipeline, record this one as the root
            // node.
            self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);

            self.spatial_nodes.push(HitTestSpatialNode {
                pipeline_id: node.pipeline_id,
                world_content_transform: node.world_content_transform,
                world_viewport_transform: node.world_viewport_transform,
            });
        }

        // For each clip chain node, extract the clip node from the clip
        // data store, and store it inline with the clip chain node.
        self.clip_chains.reserve(clip_store.clip_chain_nodes.len());
        for node in &clip_store.clip_chain_nodes {
            let clip_node = &clip_data_store[node.handle];
            self.clip_chains.push(HitTestClipChainNode {
                region: HitTestClipNode::new(node.local_pos, clip_node),
                spatial_node_index: node.spatial_node_index,
                parent_clip_chain_id: HitTestClipChainId(node.parent_clip_chain_id.0),
            });
        }
    }

    fn is_point_clipped_in_for_clip_chain(
        &self,
        point: WorldPoint,
        clip_chain_id: HitTestClipChainId,
        test: &mut HitTest
    ) -> bool {
        if clip_chain_id == HitTestClipChainId::NONE {
            return true;
        }

        if let Some(result) = test.get_from_clip_chain_cache(clip_chain_id) {
            return result == ClippedIn::ClippedIn;
        }

        let descriptor = &self.clip_chains[clip_chain_id.0 as usize];
        let parent_clipped_in = self.is_point_clipped_in_for_clip_chain(
            point,
            descriptor.parent_clip_chain_id,
            test,
        );

        if !parent_clipped_in {
            test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
            return false;
        }

        if !self.is_point_clipped_in_for_clip_node(
            point,
            clip_chain_id,
            descriptor.spatial_node_index,
            test,
        ) {
            test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
            return false;
        }

        test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::ClippedIn);
        true
    }

    fn is_point_clipped_in_for_clip_node(
        &self,
        point: WorldPoint,
        clip_chain_node_id: HitTestClipChainId,
        spatial_node_index: SpatialNodeIndex,
        test: &mut HitTest
    ) -> bool {
        if let Some(clipped_in) = test.node_cache.get(&clip_chain_node_id) {
            return *clipped_in == ClippedIn::ClippedIn;
        }

        let node = &self.clip_chains[clip_chain_node_id.0 as usize].region;
        let transform = self
            .spatial_nodes[spatial_node_index.0 as usize]
            .world_viewport_transform;
        let transformed_point = match transform
            .inverse()
            .and_then(|inverted| inverted.transform_point2d(&point))
        {
            Some(point) => point,
            None => {
                test.node_cache.insert(clip_chain_node_id, ClippedIn::NotClippedIn);
                return false;
            }
        };

        if !node.region.contains(&transformed_point) {
            test.node_cache.insert(clip_chain_node_id, ClippedIn::NotClippedIn);
            return false;
        }

        test.node_cache.insert(clip_chain_node_id, ClippedIn::ClippedIn);
        true
    }

    pub fn find_node_under_point(&self, mut test: HitTest) -> Option<SpatialNodeIndex> {
        let point = test.get_absolute_point(self);

        for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
            let spatial_node_index = clip_and_scroll.spatial_node_index;
            let scroll_node = &self.spatial_nodes[spatial_node_index.0 as usize];
            let transform = scroll_node.world_content_transform;
            let point_in_layer = match transform
                .inverse()
                .and_then(|inverted| inverted.transform_point2d(&point))
            {
                Some(point) => point,
                None => continue,
            };

            let mut clipped_in = false;
            for item in items.iter().rev() {
                if !item.rect.contains(&point_in_layer) ||
                    !item.clip_rect.contains(&point_in_layer) {
                    continue;
                }

                let clip_chain_id = HitTestClipChainId(clip_and_scroll.clip_chain_id.0);
                clipped_in |=
                    self.is_point_clipped_in_for_clip_chain(point, clip_chain_id, &mut test);
                if !clipped_in {
                    break;
                }

                return Some(spatial_node_index);
            }
        }

        None
    }

    pub fn hit_test(&self, mut test: HitTest) -> HitTestResult {
        let point = test.get_absolute_point(self);

        let mut result = HitTestResult::default();
        for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
            let spatial_node_index = clip_and_scroll.spatial_node_index;
            let scroll_node = &self.spatial_nodes[spatial_node_index.0 as usize];
            let pipeline_id = scroll_node.pipeline_id;
            match (test.pipeline_id, pipeline_id) {
                (Some(id), node_id) if node_id != id => continue,
                _ => {},
            }

            let transform = scroll_node.world_content_transform;
            let mut facing_backwards: Option<bool> = None;  // will be computed on first use
            let point_in_layer = match transform
                .inverse()
                .and_then(|inverted| inverted.transform_point2d(&point))
            {
                Some(point) => point,
                None => continue,
            };

            let mut clipped_in = false;
            for item in items.iter().rev() {
                if !item.rect.contains(&point_in_layer) ||
                    !item.clip_rect.contains(&point_in_layer) {
                    continue;
                }

                let clip_chain_id = HitTestClipChainId(clip_and_scroll.clip_chain_id.0);
                clipped_in = clipped_in ||
                    self.is_point_clipped_in_for_clip_chain(point, clip_chain_id, &mut test);
                if !clipped_in {
                    break;
                }

                // Don't hit items with backface-visibility:hidden if they are facing the back.
                if !item.is_backface_visible {
                    if *facing_backwards.get_or_insert_with(|| transform.is_backface_visible()) {
                        continue;
                    }
                }

                // We need to calculate the position of the test point relative to the origin of
                // the pipeline of the hit item. If we cannot get a transformed point, we are
                // in a situation with an uninvertible transformation so we should just skip this
                // result.
                let root_node = &self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0 as usize];
                let point_in_viewport = match root_node.world_viewport_transform
                    .inverse()
                    .and_then(|inverted| inverted.transform_point2d(&point))
                {
                    Some(point) => point,
                    None => continue,
                };

                result.items.push(HitTestItem {
                    pipeline: pipeline_id,
                    tag: item.tag,
                    point_in_viewport,
                    point_relative_to_item: point_in_layer - item.rect.origin.to_vector(),
                });
                if !test.flags.contains(HitTestFlags::FIND_ALL) {
                    return result;
                }
            }
        }

        result.items.dedup();
        result
    }

    pub fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestSpatialNode {
        &self.spatial_nodes[self.pipeline_root_nodes[&pipeline_id].0 as usize]
    }
}

#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
enum ClippedIn {
    ClippedIn,
    NotClippedIn,
}

#[derive(MallocSizeOf)]
pub struct HitTest {
    pipeline_id: Option<PipelineId>,
    point: WorldPoint,
    flags: HitTestFlags,
    node_cache: FastHashMap<HitTestClipChainId, ClippedIn>,
    clip_chain_cache: Vec<Option<ClippedIn>>,
}

impl HitTest {
    pub fn new(
        pipeline_id: Option<PipelineId>,
        point: WorldPoint,
        flags: HitTestFlags,
    ) -> HitTest {
        HitTest {
            pipeline_id,
            point,
            flags,
            node_cache: FastHashMap::default(),
            clip_chain_cache: Vec::new(),
        }
    }

    fn get_from_clip_chain_cache(&mut self, index: HitTestClipChainId) -> Option<ClippedIn> {
        let index = index.0 as usize;
        if index >= self.clip_chain_cache.len() {
            None
        } else {
            self.clip_chain_cache[index]
        }
    }

    fn set_in_clip_chain_cache(&mut self, index: HitTestClipChainId, value: ClippedIn) {
        let index = index.0 as usize;
        if index >= self.clip_chain_cache.len() {
            self.clip_chain_cache.resize(index + 1, None);
        }
        self.clip_chain_cache[index] = Some(value);
    }

    fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
        if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
            return self.point;
        }

        let point =  &LayoutPoint::new(self.point.x, self.point.y);
        self.pipeline_id
            .and_then(|id|
                hit_tester
                    .get_pipeline_root(id)
                    .world_viewport_transform
                    .transform_point2d(point)
            )
            .unwrap_or_else(|| {
                WorldPoint::new(self.point.x, self.point.y)
            })
    }
}
<|endoftext|>


<commit_before>//! A fixed-point engine for data-flow analysis.

use error::*;
use il;
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;

/// A trait which implements a forward, flow-sensitive analysis to a
/// fixed point.
pub trait FixedPointAnalysis<'f, State: 'f + Clone + Debug + PartialOrd> {
    /// Given an input state for a block, create an output state for this
    /// block.
    fn trans(
        &self,
        location: il::RefProgramLocation<'f>,
        state: Option<State>
    ) -> Result<State>;

    /// Given two states, join them into one state.
    fn join(&self, state0: State, state1: &State) -> Result<State>;
}

/// A forward, work-list data-flow analysis algorithm.
///
/// When force is true, the partial order over inputs is forced by joining
/// states which do not inherently enforce the partial order.
pub fn fixed_point_forward_options<'f, Analysis, State> (
    analysis: Analysis,
    function: &'f il::Function,
    force: bool
) -> Result<HashMap<il::RefProgramLocation<'f>, State>>
where Analysis: FixedPointAnalysis<'f, State>, State: 'f + Clone + Debug + PartialOrd {
    let mut states: HashMap<il::RefProgramLocation<'f>, State> = HashMap::new();

    let mut queue: VecDeque<il::RefProgramLocation<'f>> = VecDeque::new();

    // Find the entry block to the function.
    let entry_index = function.control_flow_graph()
                              .entry()
                              .ok_or("Function's control flow graph must have entry")?;
    let entry_block = function.control_flow_graph()
                              .block(entry_index)
                              .ok_or(format!("Could not find block for entry {}", entry_index))?;

    match entry_block.instructions().first() {
        Some(ref instruction) => {
            let location = il::RefFunctionLocation::Instruction(entry_block, instruction);
            let location = il::RefProgramLocation::new(function, location);
            queue.push_back(location.clone());
        },
        None => {
            let location = il::RefFunctionLocation::EmptyBlock(entry_block);
            let location = il::RefProgramLocation::new(function, location);
            queue.push_back(location.clone());
        }
    }

    while !queue.is_empty() {
        let location = queue.pop_front().unwrap();

        let location_predecessors = location.backward()?;

        let state = location_predecessors.iter().fold(None, |s, p| {
            match states.get(p) {
                Some(in_state) => match s {
                    Some(s) => Some(analysis.join(s, in_state).unwrap()),
                    None => Some(in_state.clone())
                },
                None => s
            }
        });

        let mut state = analysis.trans(location.clone(), state)?;

        if let Some(in_state) = states.get(&location) {
            let ordering = match state.partial_cmp(in_state) {
                Some (ordering) => match ordering {
                    ::std::cmp::Ordering::Less => Some("less"),
                    ::std::cmp::Ordering::Equal => { continue; },
                    ::std::cmp::Ordering::Greater => None
                },
                None => { Some("no relation") }
            };
            if force {
                state = analysis.join(state, in_state)?;
            }
            else {
                if let Some(ordering) = ordering {
                    bail!("Found a state which was not >= previous state (it was {}) @ {}",
                        ordering, location);
                }
            }
        }

        states.insert(location.clone(), state);

        for successor in location.forward()? {
            if !queue.contains(&successor) {
                queue.push_back(successor);
            }
        }
    }

    Ok(states)
}

/// A guaranteed sound analysis, which enforces the partial order over states.
pub fn fixed_point_forward<'f, Analysis, State> (
    analysis: Analysis,
    function: &'f il::Function
) -> Result<HashMap<il::RefProgramLocation<'f>, State>>
where Analysis: FixedPointAnalysis<'f, State>, State: 'f + Clone + Debug + PartialOrd  {
    fixed_point_forward_options(analysis, function, false)
}<commit_msg>fixed_point_backward<commit_after>//! A fixed-point engine for data-flow analysis.

use error::*;
use il;
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;

/// A trait which implements a forward, flow-sensitive analysis to a
/// fixed point.
pub trait FixedPointAnalysis<'f, State: 'f + Clone + Debug + PartialOrd> {
    /// Given an input state for a block, create an output state for this
    /// block.
    fn trans(
        &self,
        location: il::RefProgramLocation<'f>,
        state: Option<State>
    ) -> Result<State>;

    /// Given two states, join them into one state.
    fn join(&self, state0: State, state1: &State) -> Result<State>;
}

/// A forward, work-list data-flow analysis algorithm.
///
/// When force is true, the partial order over inputs is forced by joining
/// states which do not inherently enforce the partial order.
pub fn fixed_point_forward_options<'f, Analysis, State> (
    analysis: Analysis,
    function: &'f il::Function,
    force: bool
) -> Result<HashMap<il::RefProgramLocation<'f>, State>>
where Analysis: FixedPointAnalysis<'f, State>, State: 'f + Clone + Debug + PartialOrd {
    let mut states: HashMap<il::RefProgramLocation<'f>, State> = HashMap::new();

    let mut queue: VecDeque<il::RefProgramLocation<'f>> = VecDeque::new();

    // Find the entry block to the function.
    let entry_index = function.control_flow_graph()
                              .entry()
                              .ok_or("Function's control flow graph must have entry")?;
    let entry_block = function.control_flow_graph()
                              .block(entry_index)
                              .ok_or(format!("Could not find block for entry {}", entry_index))?;

    match entry_block.instructions().first() {
        Some(ref instruction) => {
            let location = il::RefFunctionLocation::Instruction(entry_block, instruction);
            let location = il::RefProgramLocation::new(function, location);
            queue.push_back(location.clone());
        },
        None => {
            let location = il::RefFunctionLocation::EmptyBlock(entry_block);
            let location = il::RefProgramLocation::new(function, location);
            queue.push_back(location.clone());
        }
    }

    while !queue.is_empty() {
        let location = queue.pop_front().unwrap();

        let location_predecessors = location.backward()?;

        let state = location_predecessors.iter().fold(None, |s, p| {
            match states.get(p) {
                Some(in_state) => match s {
                    Some(s) => Some(analysis.join(s, in_state).unwrap()),
                    None => Some(in_state.clone())
                },
                None => s
            }
        });

        let mut state = analysis.trans(location.clone(), state)?;

        if let Some(in_state) = states.get(&location) {
            let ordering = match state.partial_cmp(in_state) {
                Some (ordering) => match ordering {
                    ::std::cmp::Ordering::Less => Some("less"),
                    ::std::cmp::Ordering::Equal => { continue; },
                    ::std::cmp::Ordering::Greater => None
                },
                None => { Some("no relation") }
            };
            if force {
                state = analysis.join(state, in_state)?;
            }
            else {
                if let Some(ordering) = ordering {
                    bail!("Found a state which was not >= previous state (it was {}) @ {}",
                        ordering, location);
                }
            }
        }

        states.insert(location.clone(), state);

        for successor in location.forward()? {
            if !queue.contains(&successor) {
                queue.push_back(successor);
            }
        }
    }

    Ok(states)
}

/// A guaranteed sound analysis, which enforces the partial order over states.
pub fn fixed_point_forward<'f, Analysis, State> (
    analysis: Analysis,
    function: &'f il::Function
) -> Result<HashMap<il::RefProgramLocation<'f>, State>>
where Analysis: FixedPointAnalysis<'f, State>, State: 'f + Clone + Debug + PartialOrd  {
    fixed_point_forward_options(analysis, function, false)
}

/// A backward, work-list data-flow analysis algorithm.
///
/// When force is true, the partial order over inputs is forced by joining
/// states which do not inherently enforce the partial order.
pub fn fixed_point_backward_options<'f, Analysis, State> (
    analysis: Analysis,
    function: &'f il::Function,
    force: bool
) -> Result<HashMap<il::RefProgramLocation<'f>, State>>
where Analysis: FixedPointAnalysis<'f, State>, State: 'f + Clone + Debug + PartialOrd {
    let mut states: HashMap<il::RefProgramLocation<'f>, State> = HashMap::new();

    let mut queue: VecDeque<il::RefProgramLocation<'f>> = VecDeque::new();

    // Find the entry block to the function.
    let exit_index = function.control_flow_graph()
                             .entry()
                             .ok_or("Function's control flow graph must have entry")?;
    let exit_block = function.control_flow_graph()
                             .block(exit_index)
                             .ok_or(format!("Could not find block for entry {}", exit_index))?;

    match exit_block.instructions().last() {
        Some(ref instruction) => {
            let location = il::RefFunctionLocation::Instruction(exit_block, instruction);
            let location = il::RefProgramLocation::new(function, location);
            queue.push_back(location.clone());
        },
        None => {
            let location = il::RefFunctionLocation::EmptyBlock(exit_block);
            let location = il::RefProgramLocation::new(function, location);
            queue.push_back(location.clone());
        }
    }

    while !queue.is_empty() {
        let location = queue.pop_front().unwrap();

        let location_successors = location.forward()?;

        let state = location_successors.iter().fold(None, |s, p| {
            match states.get(p) {
                Some(in_state) => match s {
                    Some(s) => Some(analysis.join(s, in_state).unwrap()),
                    None => Some(in_state.clone())
                },
                None => s
            }
        });

        let mut state = analysis.trans(location.clone(), state)?;

        if let Some(in_state) = states.get(&location) {
            let ordering = match state.partial_cmp(in_state) {
                Some (ordering) => match ordering {
                    ::std::cmp::Ordering::Less => Some("less"),
                    ::std::cmp::Ordering::Equal => { continue; },
                    ::std::cmp::Ordering::Greater => None
                },
                None => { Some("no relation") }
            };
            if force {
                state = analysis.join(state, in_state)?;
            }
            else {
                if let Some(ordering) = ordering {
                    bail!("Found a state which was not >= previous state (it was {}) @ {}",
                        ordering, location);
                }
            }
        }

        states.insert(location.clone(), state);

        for successor in location.backward()? {
            if !queue.contains(&successor) {
                queue.push_back(successor);
            }
        }
    }

    Ok(states)
}

/// A guaranteed sound analysis, which enforces the partial order over states.
pub fn fixed_point_backward<'f, Analysis, State> (
    analysis: Analysis,
    function: &'f il::Function
) -> Result<HashMap<il::RefProgramLocation<'f>, State>>
where Analysis: FixedPointAnalysis<'f, State>, State: 'f + Clone + Debug + PartialOrd  {
    fixed_point_backward_options(analysis, function, false)
}<|endoftext|>


<commit_before>use std::borrow::ToOwned;
use std::collections::HashSet;
use std::io::{Read, Write};
use std::fs::File;
use std::path::{Path, PathBuf};

use core::{TargetKind};
use ops::{Context, Unit};
use util::{CargoResult, internal, human};

#[derive(Debug)]
struct DepLine {
    target: String,
    deps: Vec<String>,
}

struct DepFile {
    dir: String,
    deps: Vec<DepLine>,
}

fn render_filename<P: AsRef<Path>>(path: P, basedir: Option<&str>) -> CargoResult<String> {
    let path = path.as_ref();
    let relpath = match basedir {
        None => path,
        Some(base) => match path.strip_prefix(base) {
            Ok(relpath) => relpath,
            _ => path,
        }
    };
    relpath.to_str().ok_or(internal("path not utf-8")).map(ToOwned::to_owned)
}

fn read_dep_file<P: AsRef<Path>>(path: P) -> CargoResult<DepFile> {
    let mut file = File::open(&path).map_err(|_|
        human("error opening ".to_string() + path.as_ref().to_str().unwrap_or("(bad unicode")))?;
    let mut contents = String::new();
    let _ = file.read_to_string(&mut contents)?;
    let mut spl = contents.split('\0');
    let dir = spl.next().ok_or(internal("dependency file empty"))?;
    let dep_txt = spl.next().ok_or(internal("dependency file missing null byte"))?;
    let mut result = Vec::new();
    for line in dep_txt.lines() {
        let mut line_spl = line.split(": ");
        if let Some(target) = line_spl.next() {
            if let Some(deps) = line_spl.next() {
                let deps = deps.split_whitespace().map(ToOwned::to_owned).collect();
                result.push(DepLine {
                    target: target.to_string(),
                    deps: deps,
                });
            }
        }
    }
    Ok(DepFile {
        dir: dir.to_string(),
        deps: result,
    })
}

fn add_deps(depfile: &DepFile, deps: &mut HashSet<PathBuf>) {
    let dep_dir = PathBuf::from(&depfile.dir);
    for depline in &depfile.deps {
        for dep in &depline.deps {
            deps.insert(dep_dir.join(dep));
        }
    }
}

// TODO: probably better to use Context::target_filenames for this
fn target_filename(context: &mut Context, unit: &Unit) -> CargoResult<PathBuf> {
    let (dir, base) = context.link_stem(&unit).ok_or(internal("can't get link stem"))?;
    if unit.target.is_lib() {
        Ok(dir.join(["lib", &base, ".rlib"].concat()))
    } else {
        Ok(dir.join(base))
    }
}

fn add_deps_for_unit(deps: &mut HashSet<PathBuf>, context: &mut Context, unit: &Unit)
    -> CargoResult<()>
{
    // TODO: this is duplicated against filename in fingerprint.rs
    let kind = match *unit.target.kind() {
        TargetKind::Lib(..) => "lib",
        TargetKind::Bin => "bin",
        TargetKind::Test => "integration-test",
        TargetKind::Example => "example",
        TargetKind::Bench => "bench",
        TargetKind::CustomBuild => "build-script",
    };
    let flavor = if unit.profile.test {
        "test-"
    } else if unit.profile.doc {
        "doc-"
    } else {
        ""
    };
    let dep_filename = ["dep-", flavor, kind, "-", &context.file_stem(&unit)].concat();
    let path = context.fingerprint_dir(&unit).join(&dep_filename);
    let depfile = read_dep_file(&path)?;
    add_deps(&depfile, deps);
    Ok(())
}

pub fn output_depinfo(context: &mut Context, unit: &Unit) -> CargoResult<()> {
    let mut deps = HashSet::new();
    add_deps_for_unit(&mut deps, context, unit)?;
    for dep_unit in &context.dep_targets(unit)? {
        let source_id = dep_unit.pkg.package_id().source_id();
        if source_id.is_path() {
            add_deps_for_unit(&mut deps, context, dep_unit)?;
        }
    }
    let filename = target_filename(context, unit)?;
    let mut output_path = filename.clone().into_os_string();
    output_path.push(".d");
    let basedir = None; // TODO
    let target_fn = render_filename(filename, basedir)?;
    let mut outfile = File::create(output_path)?;
    write!(outfile, "{}:", target_fn)?;
    for dep in &deps {
        write!(outfile, " {}", render_filename(dep, basedir)?)?;
    }
    writeln!(outfile, "")?;
    Ok(())
}
<commit_msg>Track TargetKind::Example split<commit_after>use std::borrow::ToOwned;
use std::collections::HashSet;
use std::io::{Read, Write};
use std::fs::File;
use std::path::{Path, PathBuf};

use core::{TargetKind};
use ops::{Context, Unit};
use util::{CargoResult, internal, human};

#[derive(Debug)]
struct DepLine {
    target: String,
    deps: Vec<String>,
}

struct DepFile {
    dir: String,
    deps: Vec<DepLine>,
}

fn render_filename<P: AsRef<Path>>(path: P, basedir: Option<&str>) -> CargoResult<String> {
    let path = path.as_ref();
    let relpath = match basedir {
        None => path,
        Some(base) => match path.strip_prefix(base) {
            Ok(relpath) => relpath,
            _ => path,
        }
    };
    relpath.to_str().ok_or(internal("path not utf-8")).map(ToOwned::to_owned)
}

fn read_dep_file<P: AsRef<Path>>(path: P) -> CargoResult<DepFile> {
    let mut file = File::open(&path).map_err(|_|
        human("error opening ".to_string() + path.as_ref().to_str().unwrap_or("(bad unicode")))?;
    let mut contents = String::new();
    let _ = file.read_to_string(&mut contents)?;
    let mut spl = contents.split('\0');
    let dir = spl.next().ok_or(internal("dependency file empty"))?;
    let dep_txt = spl.next().ok_or(internal("dependency file missing null byte"))?;
    let mut result = Vec::new();
    for line in dep_txt.lines() {
        let mut line_spl = line.split(": ");
        if let Some(target) = line_spl.next() {
            if let Some(deps) = line_spl.next() {
                let deps = deps.split_whitespace().map(ToOwned::to_owned).collect();
                result.push(DepLine {
                    target: target.to_string(),
                    deps: deps,
                });
            }
        }
    }
    Ok(DepFile {
        dir: dir.to_string(),
        deps: result,
    })
}

fn add_deps(depfile: &DepFile, deps: &mut HashSet<PathBuf>) {
    let dep_dir = PathBuf::from(&depfile.dir);
    for depline in &depfile.deps {
        for dep in &depline.deps {
            deps.insert(dep_dir.join(dep));
        }
    }
}

// TODO: probably better to use Context::target_filenames for this
fn target_filename(context: &mut Context, unit: &Unit) -> CargoResult<PathBuf> {
    let (dir, base) = context.link_stem(&unit).ok_or(internal("can't get link stem"))?;
    if unit.target.is_lib() {
        Ok(dir.join(["lib", &base, ".rlib"].concat()))
    } else {
        Ok(dir.join(base))
    }
}

fn add_deps_for_unit(deps: &mut HashSet<PathBuf>, context: &mut Context, unit: &Unit)
    -> CargoResult<()>
{
    // TODO: this is duplicated against filename in fingerprint.rs
    let kind = match *unit.target.kind() {
        TargetKind::Lib(..) => "lib",
        TargetKind::Bin => "bin",
        TargetKind::Test => "integration-test",
        TargetKind::ExampleBin |
        TargetKind::ExampleLib(..) => "example",
        TargetKind::Bench => "bench",
        TargetKind::CustomBuild => "build-script",
    };
    let flavor = if unit.profile.test {
        "test-"
    } else if unit.profile.doc {
        "doc-"
    } else {
        ""
    };
    let dep_filename = ["dep-", flavor, kind, "-", &context.file_stem(&unit)].concat();
    let path = context.fingerprint_dir(&unit).join(&dep_filename);
    let depfile = read_dep_file(&path)?;
    add_deps(&depfile, deps);
    Ok(())
}

pub fn output_depinfo(context: &mut Context, unit: &Unit) -> CargoResult<()> {
    let mut deps = HashSet::new();
    add_deps_for_unit(&mut deps, context, unit)?;
    for dep_unit in &context.dep_targets(unit)? {
        let source_id = dep_unit.pkg.package_id().source_id();
        if source_id.is_path() {
            add_deps_for_unit(&mut deps, context, dep_unit)?;
        }
    }
    let filename = target_filename(context, unit)?;
    let mut output_path = filename.clone().into_os_string();
    output_path.push(".d");
    let basedir = None; // TODO
    let target_fn = render_filename(filename, basedir)?;
    let mut outfile = File::create(output_path)?;
    write!(outfile, "{}:", target_fn)?;
    for dep in &deps {
        write!(outfile, " {}", render_filename(dep, basedir)?)?;
    }
    writeln!(outfile, "")?;
    Ok(())
}
<|endoftext|>