<commit_before>//! Demangles rustc mangled names.
//!
//! This tool uses https://crates.io/crates/rustc-demangle to convert an input buffer of
//! newline-separated mangled names into their demangled translations.
//!
//! This tool can be leveraged by other applications that support third-party demanglers.
//! It takes a list of mangled names (one per line) on standard input, and prints a corresponding
//! list of demangled names. The tool is designed to support other programs that can leverage a
//! third-party demangler, such as `llvm-cov`, via the `-Xdemangler=<path-to-demangler>` option.
//!
//! To use `rust-demangler`, first build the tool with:
//!
//! ```shell
//! $ ./x.py build rust-demangler
//! ```
//!
//! Then, with `llvm-cov` for example, add the `-Xdemangler=...` option:
//!
//! ```shell
//! $ TARGET="${PWD}/build/x86_64-unknown-linux-gnu"
//! $ "${TARGET}"/llvm/bin/llvm-cov show --Xdemangler="${TARGET}"/stage0-tools-bin/rust-demangler \
//!   --instr-profile=main.profdata ./main --show-line-counts-or-regions
//! ```

use regex::Regex;
use rustc_demangle::demangle;
use std::io::{self, Read, Write};

const REPLACE_COLONS: &str = "::";

fn main() -> io::Result<()> {
    let mut strip_crate_disambiguators = Some(Regex::new(r"\[[a-f0-9]{16}\]::").unwrap());

    let mut args = std::env::args();
    let progname = args.next().unwrap();
    for arg in args {
        if arg == "--disambiguators" || arg == "-d" {
            strip_crate_disambiguators = None;
        } else {
            eprintln!();
            eprintln!("Usage: {} [-d|--disambiguators]", progname);
            eprintln!();
            eprintln!(
                "This tool converts a list of Rust mangled symbols (one per line) into a\n
                corresponding list of demangled symbols."
            );
            eprintln!();
            eprintln!(
                "With -d (--disambiguators), Rust symbols mangled with the v0 symbol mangler may\n\
                include crate disambiguators (a 16 character hex value in square brackets).\n\
                Crate disambiguators are removed by default."
            );
            eprintln!();
            std::process::exit(1)
        }
    }

    let mut buffer = String::new();
    io::stdin().read_to_string(&mut buffer)?;
    let lines = buffer.lines();
    let mut demangled_lines = Vec::new();
    for mangled in lines {
        let mut demangled = demangle(mangled).to_string();
        if let Some(re) = &strip_crate_disambiguators {
            demangled = re.replace_all(&demangled, REPLACE_COLONS).to_string();
        }
        demangled_lines.push(demangled);
    }
    demangled_lines.push("".to_string());
    io::stdout().write_all(demangled_lines.join("\n").as_bytes())?;
    Ok(())
}
<commit_msg>Auto merge of #77627 - richkadel:rust-demangler, r=tmandry<commit_after>//! Demangles rustc mangled names.
//!
//! This tool uses https://crates.io/crates/rustc-demangle to convert an input buffer of
//! newline-separated mangled names into their demangled translations.
//!
//! This tool can be leveraged by other applications that support third-party demanglers.
//! It takes a list of mangled names (one per line) on standard input, and prints a corresponding
//! list of demangled names. The tool is designed to support other programs that can leverage a
//! third-party demangler, such as `llvm-cov`, via the `-Xdemangler=<path-to-demangler>` option.
//!
//! To use `rust-demangler`, first build the tool with:
//!
//! ```shell
//! $ ./x.py build rust-demangler
//! ```
//!
//! Then, with `llvm-cov` for example, add the `-Xdemangler=...` option:
//!
//! ```shell
//! $ TARGET="${PWD}/build/x86_64-unknown-linux-gnu"
//! $ "${TARGET}"/llvm/bin/llvm-cov show --Xdemangler="${TARGET}"/stage0-tools-bin/rust-demangler \
//!   --instr-profile=main.profdata ./main --show-line-counts-or-regions
//! ```
//!
//! Note regarding crate disambiguators:
//!
//! Some demangled symbol paths can include "crate disambiguator" suffixes, represented as a large
//! hexadecimal value enclosed in square braces, and appended to the name of the crate. a suffix to the
//! original crate name. For example, the `core` crate, here, includes a disambiguator:
//!
//! ```rust
//!     <generics::Firework<f64> as core[a7a74cee373f048]::ops::drop::Drop>::drop
//! ```
//!
//! These disambiguators are known to vary depending on environmental circumstances. As a result,
//! tests that compare results including demangled names can fail across development environments,
//! particularly with cross-platform testing. Also, the resulting crate paths are not syntactically
//! valid, and don't match the original source symbol paths, which can impact development tools.
//!
//! For these reasons, by default, `rust-demangler` uses a heuristic to remove crate disambiguators
//! from their original demangled representation before printing them to standard output. If crate
//! disambiguators are required, add the `-d` (or `--disambiguators`) flag, and the disambiguators
//! will not be removed.
//!
//! Also note that the disambiguators are stripped by a Regex pattern that is tolerant to some
//! variation in the number of hexadecimal digits. The disambiguators come from a hash value, which
//! typically generates a 16-digit hex representation on a 64-bit architecture; however, leading
//! zeros are not included, which can shorten the hex digit length, and a different hash algorithm
//! that might also be dependent on the architecture, might shorten the length even further. A
//! minimum length of 5 digits is assumed, which should be more than sufficient to support hex
//! representations that generate only 8-digits of precision with an extremely rare (but not
//! impossible) result with up to 3 leading zeros.
//!
//! Using a minimum number of digits less than 5 risks the possibility of stripping demangled name
//! components with a similar pattern. For example, some closures instantiated multiple times
//! include their own disambiguators, demangled as non-hashed zero-based indexes in square brackets.
//! These disambiguators seem to have more analytical value (for instance, in coverage analysis), so
//! they are not removed.

use regex::Regex;
use rustc_demangle::demangle;
use std::io::{self, Read, Write};

const REPLACE_COLONS: &str = "::";

fn main() -> io::Result<()> {
    // FIXME(richkadel): In Issue #77615 discussed updating the `rustc-demangle` library, to provide
    // an option to generate demangled names without including crate disambiguators. If that
    // happens, update this tool to use that option (if the `-d` flag is not set) instead stripping
    // them via the Regex heuristic. The update the doc comments and help.

    // Strip hashed hexadecimal crate disambiguators. Leading zeros are not enforced, and can be
    // different across different platform/architecture types, so while 16 hex digits are common,
    // they can also be shorter.
    //
    // Also note that a demangled symbol path may include the `[<digits>]` pattern, with zero-based
    // indexes (such as for closures, and possibly for types defined in anonymous scopes). Preferably
    // these should not be stripped.
    //
    // The minimum length of 5 digits supports the possibility that some target architecture (maybe
    // a 32-bit or smaller architecture) could generate a hash value with a maximum of 8 digits,
    // and more than three leading zeros should be extremely unlikely. Conversely, it should be
    // sufficient to assume the zero-based indexes for closures and anonymous scopes will never
    // exceed the value 9999.
    let mut strip_crate_disambiguators = Some(Regex::new(r"\[[a-f0-9]{5,16}\]::").unwrap());

    let mut args = std::env::args();
    let progname = args.next().unwrap();
    for arg in args {
        if arg == "--disambiguators" || arg == "-d" {
            strip_crate_disambiguators = None;
        } else {
            eprintln!();
            eprintln!("Usage: {} [-d|--disambiguators]", progname);
            eprintln!();
            eprintln!(
                "This tool converts a list of Rust mangled symbols (one per line) into a\n\
                corresponding list of demangled symbols."
            );
            eprintln!();
            eprintln!(
                "With -d (--disambiguators), Rust symbols mangled with the v0 symbol mangler may\n\
                include crate disambiguators (a hexadecimal hash value, typically up to 16 digits\n\
                long, enclosed in square brackets)."
            );
            eprintln!();
            eprintln!(
                "By default, crate disambiguators are removed, using a heuristics-based regular\n\
                expression. (See the `rust-demangler` doc comments for more information.)"
            );
            eprintln!();
            std::process::exit(1)
        }
    }

    let mut buffer = String::new();
    io::stdin().read_to_string(&mut buffer)?;
    let lines = buffer.lines();
    let mut demangled_lines = Vec::new();
    for mangled in lines {
        let mut demangled = demangle(mangled).to_string();
        if let Some(re) = &strip_crate_disambiguators {
            demangled = re.replace_all(&demangled, REPLACE_COLONS).to_string();
        }
        demangled_lines.push(demangled);
    }
    demangled_lines.push("".to_string());
    io::stdout().write_all(demangled_lines.join("\n").as_bytes())?;
    Ok(())
}
<|endoftext|>


<commit_before>use na::Real;
use crate::pipeline::broad_phase::BroadPhasePairFilter;
use crate::pipeline::world::CollisionObject;

const SELF_COLLISION: u32 = 1 << 31;
const ALL_GROUPS: u32 = (1 << 30) - 1;
const NO_GROUP: u32 = 0;

/// Groups of collision used to filter which object interact with which other one.
///
/// There are at most 30 groups indexed from 0 to 29 (included). This identifies collidable
/// entities by combining three attributes:
///    * A set of group this structure is member of.
///    * A collision group whitelist.
///    * A collision group blacklist.
///
/// For two entities to interact, they must be member of at least one group part of each-other's
/// whitelists, and must not be part of any blacklisted group. The blacklist always has priority on
/// the whitelist.
///
/// ### Example
/// For example if the object A is such that:
///    * It is part of the groups 1, 3, and 6.
///    * It whitelists the groups 6 and 7.
///    * It blacklists the group 1.
/// Let the object B be such that:
///    * It is part of the groups 1, 3, and 7.
///    * It whitelists the groups 3 and 7.
///    * It does not blacklist anything.
/// For example if the object C is such that:
///    * It is part of the groups 6 and 9.
///    * It whitelists the groups 3 and 7.
///    * It does not blacklist anything.
///
/// Then we have:
///    * A and C can interact because A whitelists the group 6 (which C is part of), and,
///    reciprocally, C whitelists the group 3 (which A is part of).
///    * A and B will **not** interact because B is part of the group 1 which is blacklisted by A.
///    * Finally, B and C will **not** interact either because, even if C whitelists the group 3
///    (which B is part of), B does not whitelists the groups 6 nor 9 (which B is part of).
#[derive(Clone, Debug, Copy)]
pub struct CollisionGroups {
    membership: u32,
    whitelist: u32,
    blacklist: u32,
}

impl CollisionGroups {
    /// Creates a new `CollisionGroups` that enables interactions with everything except
    /// self-interaction.
    #[inline]
    pub fn new() -> CollisionGroups {
        CollisionGroups {
            membership: ALL_GROUPS,
            whitelist: ALL_GROUPS,
            blacklist: NO_GROUP,
        }
    }

    /// The maximum allowed group identifier.
    #[inline]
    pub fn max_group_id() -> usize {
        29
    }

    #[inline]
    fn modify_mask(mask: &mut u32, group_id: usize, add: bool) {
        assert!(
            group_id < 30,
            "There are at most 30 groups indexed from 0 to 29 (included)."
        );

        if add {
            *mask = *mask | (1 << group_id)
        } else {
            *mask = *mask & !(1 << group_id)
        }
    }

    #[inline]
    fn set_mask(mask: &mut u32, groups: &[usize]) {
        *mask = 0;
        for g in groups.iter() {
            CollisionGroups::modify_mask(mask, *g, true);
        }
    }

    /// Adds or removes this entity from the given group.
    #[inline]
    pub fn modify_membership(&mut self, group_id: usize, add: bool) {
        CollisionGroups::modify_mask(&mut self.membership, group_id, add);
    }

    /// Adds or removes the given group from this entity whitelist.
    #[inline]
    pub fn modify_whitelist(&mut self, group_id: usize, add: bool) {
        CollisionGroups::modify_mask(&mut self.whitelist, group_id, add);
    }

    /// Adds or removes this entity from the given group.
    #[inline]
    pub fn modify_blacklist(&mut self, group_id: usize, add: bool) {
        CollisionGroups::modify_mask(&mut self.blacklist, group_id, add);
    }

    /// Make this object member of the given groups only.
    #[inline]
    pub fn set_membership(&mut self, groups: &[usize]) {
        CollisionGroups::set_mask(&mut self.membership, groups);
    }

    /// Whitelists the given groups only (others will be un-whitelisted).
    #[inline]
    pub fn set_whitelist(&mut self, groups: &[usize]) {
        CollisionGroups::set_mask(&mut self.whitelist, groups);
    }

    /// Blacklists the given groups only (others will be un-blacklisted).
    #[inline]
    pub fn set_blacklist(&mut self, groups: &[usize]) {
        CollisionGroups::set_mask(&mut self.blacklist, groups);
    }

    /// Copies the membership of another collision groups.
    #[inline]
    pub fn copy_membership(&mut self, other: &CollisionGroups) {
        self.membership = other.membership
    }

    /// Copies the whitelist of another collision groups.
    #[inline]
    pub fn copy_whitelist(&mut self, other: &CollisionGroups) {
        self.whitelist = other.whitelist
    }

    /// Copies the blacklist of another collision groups.
    #[inline]
    pub fn copy_blacklist(&mut self, other: &CollisionGroups) {
        self.blacklist = other.blacklist
    }

    /// Allows the object to interact with itself.
    #[inline]
    pub fn enable_self_interaction(&mut self) {
        self.whitelist = self.whitelist | SELF_COLLISION;
    }

    /// Prevents the object from interacting with itself.
    #[inline]
    pub fn disable_self_interaction(&mut self) {
        self.whitelist = self.whitelist & !SELF_COLLISION;
    }

    #[inline]
    fn is_inside_mask(mask: u32, group_id: usize) -> bool {
        assert!(
            group_id < 30,
            "There are at most 30 groups indexed from 0 to 29 (included)."
        );
        mask & (1 << group_id) != 0
    }

    /// Tests if this entity is part of the given group.
    #[inline]
    pub fn is_member_of(&self, group_id: usize) -> bool {
        CollisionGroups::is_inside_mask(self.membership, group_id)
    }

    /// Tests if the given group is whitelisted.
    #[inline]
    pub fn is_group_whitelisted(&self, group_id: usize) -> bool {
        CollisionGroups::is_inside_mask(self.whitelist, group_id)
    }

    /// Tests if the given group is blacklisted.
    #[inline]
    pub fn is_group_blacklisted(&self, group_id: usize) -> bool {
        CollisionGroups::is_inside_mask(self.blacklist, group_id)
    }

    /// Tests whether interactinos with a given group is possible.
    ///
    /// Collision is possible if `group_id` is whitelisted but not blacklisted.
    #[inline]
    pub fn can_interact_with(&self, group_id: usize) -> bool {
        !CollisionGroups::is_inside_mask(self.blacklist, group_id)
            && CollisionGroups::is_inside_mask(self.whitelist, group_id)
    }

    /// Tests whether two collision groups have at least one group in common.
    #[inline]
    pub fn can_interact_with_groups(&self, other: &CollisionGroups) -> bool {
        // FIXME: is there a more bitwise-y way of doing this?
        self.membership & other.blacklist == 0
            && other.membership & self.blacklist == 0
            && self.membership & other.whitelist != 0
            && other.membership & self.whitelist != 0
    }

    /// Tests whether self-interaction is enabled.
    #[inline]
    pub fn can_interact_with_self(&self) -> bool {
        self.whitelist & SELF_COLLISION != 0
    }
}

/// A collision filter based collision groups.
pub struct CollisionGroupsPairFilter;

impl CollisionGroupsPairFilter {
    /// Creates a new collision filter based collision groups.
    #[inline]
    pub fn new() -> CollisionGroupsPairFilter {
        CollisionGroupsPairFilter
    }
}

impl<N: Real, T> BroadPhasePairFilter<N, T> for CollisionGroupsPairFilter {
    fn is_pair_valid(&self, co1: &CollisionObject<N, T>, co2: &CollisionObject<N, T>) -> bool {
        if co1.handle() == co2.handle() {
            co1.collision_groups().can_interact_with_self()
        } else {
            co1.collision_groups()
                .can_interact_with_groups(co2.collision_groups())
        }
    }
}

impl Default for CollisionGroups {
    #[inline]
    fn default() -> Self {
        CollisionGroups::new()
    }
}<commit_msg>Add builder-style construction for collision groups.<commit_after>use na::Real;
use crate::pipeline::broad_phase::BroadPhasePairFilter;
use crate::pipeline::world::CollisionObject;

const SELF_COLLISION: u32 = 1 << 31;
const ALL_GROUPS: u32 = (1 << 30) - 1;
const NO_GROUP: u32 = 0;

/// Groups of collision used to filter which object interact with which other one.
///
/// There are at most 30 groups indexed from 0 to 29 (included). This identifies collidable
/// entities by combining three attributes:
///    * A set of group this structure is member of.
///    * A collision group whitelist.
///    * A collision group blacklist.
///
/// For two entities to interact, they must be member of at least one group part of each-other's
/// whitelists, and must not be part of any blacklisted group. The blacklist always has priority on
/// the whitelist.
///
/// ### Example
/// For example if the object A is such that:
///    * It is part of the groups 1, 3, and 6.
///    * It whitelists the groups 6 and 7.
///    * It blacklists the group 1.
/// Let the object B be such that:
///    * It is part of the groups 1, 3, and 7.
///    * It whitelists the groups 3 and 7.
///    * It does not blacklist anything.
/// For example if the object C is such that:
///    * It is part of the groups 6 and 9.
///    * It whitelists the groups 3 and 7.
///    * It does not blacklist anything.
///
/// Then we have:
///    * A and C can interact because A whitelists the group 6 (which C is part of), and,
///    reciprocally, C whitelists the group 3 (which A is part of).
///    * A and B will **not** interact because B is part of the group 1 which is blacklisted by A.
///    * Finally, B and C will **not** interact either because, even if C whitelists the group 3
///    (which B is part of), B does not whitelists the groups 6 nor 9 (which B is part of).
#[derive(Clone, Debug, Copy)]
pub struct CollisionGroups {
    membership: u32,
    whitelist: u32,
    blacklist: u32,
}

impl CollisionGroups {
    /// Creates a new `CollisionGroups` that enables interactions with everything except
    /// self-interaction.
    #[inline]
    pub fn new() -> CollisionGroups {
        CollisionGroups {
            membership: ALL_GROUPS,
            whitelist: ALL_GROUPS,
            blacklist: NO_GROUP,
        }
    }

    /// The maximum allowed group identifier.
    #[inline]
    pub fn max_group_id() -> usize {
        29
    }

    #[inline]
    fn modify_mask(mask: &mut u32, group_id: usize, add: bool) {
        assert!(
            group_id < 30,
            "There are at most 30 groups indexed from 0 to 29 (included)."
        );

        if add {
            *mask = *mask | (1 << group_id)
        } else {
            *mask = *mask & !(1 << group_id)
        }
    }

    #[inline]
    fn set_mask(mask: &mut u32, groups: &[usize]) {
        *mask = 0;
        for g in groups.iter() {
            CollisionGroups::modify_mask(mask, *g, true);
        }
    }

    /// Adds or removes this entity from the given group.
    #[inline]
    pub fn modify_membership(&mut self, group_id: usize, add: bool) {
        CollisionGroups::modify_mask(&mut self.membership, group_id, add);
    }

    /// Adds or removes the given group from this entity whitelist.
    #[inline]
    pub fn modify_whitelist(&mut self, group_id: usize, add: bool) {
        CollisionGroups::modify_mask(&mut self.whitelist, group_id, add);
    }

    /// Adds or removes this entity from the given group.
    #[inline]
    pub fn modify_blacklist(&mut self, group_id: usize, add: bool) {
        CollisionGroups::modify_mask(&mut self.blacklist, group_id, add);
    }

    /// Make this object member of the given groups only.
    #[inline]
    pub fn set_membership(&mut self, groups: &[usize]) {
        CollisionGroups::set_mask(&mut self.membership, groups);
    }

    /// Whitelists the given groups only (others will be un-whitelisted).
    #[inline]
    pub fn set_whitelist(&mut self, groups: &[usize]) {
        CollisionGroups::set_mask(&mut self.whitelist, groups);
    }

    /// Blacklists the given groups only (others will be un-blacklisted).
    #[inline]
    pub fn set_blacklist(&mut self, groups: &[usize]) {
        CollisionGroups::set_mask(&mut self.blacklist, groups);
    }

    /// Make this object member of the given groups only.
    #[inline]
    pub fn with_membership(mut self, groups: &[usize]) -> Self {
        CollisionGroups::set_mask(&mut self.membership, groups);
        self
    }

    /// Whitelists the given groups only (others will be un-whitelisted).
    #[inline]
    pub fn with_whitelist(mut self, groups: &[usize]) -> Self {
        CollisionGroups::set_mask(&mut self.whitelist, groups);
        self
    }

    /// Blacklists the given groups only (others will be un-blacklisted).
    #[inline]
    pub fn with_blacklist(mut self, groups: &[usize]) -> Self {
        CollisionGroups::set_mask(&mut self.blacklist, groups);
        self
    }

    /// Copies the membership of another collision groups.
    #[inline]
    pub fn copy_membership(&mut self, other: &CollisionGroups) {
        self.membership = other.membership
    }

    /// Copies the whitelist of another collision groups.
    #[inline]
    pub fn copy_whitelist(&mut self, other: &CollisionGroups) {
        self.whitelist = other.whitelist
    }

    /// Copies the blacklist of another collision groups.
    #[inline]
    pub fn copy_blacklist(&mut self, other: &CollisionGroups) {
        self.blacklist = other.blacklist
    }

    /// Allows the object to interact with itself.
    #[inline]
    pub fn enable_self_interaction(&mut self) {
        self.whitelist = self.whitelist | SELF_COLLISION;
    }

    /// Prevents the object from interacting with itself.
    #[inline]
    pub fn disable_self_interaction(&mut self) {
        self.whitelist = self.whitelist & !SELF_COLLISION;
    }

    #[inline]
    fn is_inside_mask(mask: u32, group_id: usize) -> bool {
        assert!(
            group_id < 30,
            "There are at most 30 groups indexed from 0 to 29 (included)."
        );
        mask & (1 << group_id) != 0
    }

    /// Tests if this entity is part of the given group.
    #[inline]
    pub fn is_member_of(&self, group_id: usize) -> bool {
        CollisionGroups::is_inside_mask(self.membership, group_id)
    }

    /// Tests if the given group is whitelisted.
    #[inline]
    pub fn is_group_whitelisted(&self, group_id: usize) -> bool {
        CollisionGroups::is_inside_mask(self.whitelist, group_id)
    }

    /// Tests if the given group is blacklisted.
    #[inline]
    pub fn is_group_blacklisted(&self, group_id: usize) -> bool {
        CollisionGroups::is_inside_mask(self.blacklist, group_id)
    }

    /// Tests whether interactinos with a given group is possible.
    ///
    /// Collision is possible if `group_id` is whitelisted but not blacklisted.
    #[inline]
    pub fn can_interact_with(&self, group_id: usize) -> bool {
        !CollisionGroups::is_inside_mask(self.blacklist, group_id)
            && CollisionGroups::is_inside_mask(self.whitelist, group_id)
    }

    /// Tests whether two collision groups have at least one group in common.
    #[inline]
    pub fn can_interact_with_groups(&self, other: &CollisionGroups) -> bool {
        // FIXME: is there a more bitwise-y way of doing this?
        self.membership & other.blacklist == 0
            && other.membership & self.blacklist == 0
            && self.membership & other.whitelist != 0
            && other.membership & self.whitelist != 0
    }

    /// Tests whether self-interaction is enabled.
    #[inline]
    pub fn can_interact_with_self(&self) -> bool {
        self.whitelist & SELF_COLLISION != 0
    }
}

/// A collision filter based collision groups.
pub struct CollisionGroupsPairFilter;

impl CollisionGroupsPairFilter {
    /// Creates a new collision filter based collision groups.
    #[inline]
    pub fn new() -> CollisionGroupsPairFilter {
        CollisionGroupsPairFilter
    }
}

impl<N: Real, T> BroadPhasePairFilter<N, T> for CollisionGroupsPairFilter {
    fn is_pair_valid(&self, co1: &CollisionObject<N, T>, co2: &CollisionObject<N, T>) -> bool {
        if co1.handle() == co2.handle() {
            co1.collision_groups().can_interact_with_self()
        } else {
            co1.collision_groups()
                .can_interact_with_groups(co2.collision_groups())
        }
    }
}

impl Default for CollisionGroups {
    #[inline]
    fn default() -> Self {
        CollisionGroups::new()
    }
}<|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/. */

//! Gecko's restyle damage computation (aka change hints, aka `nsChangeHint`).

use gecko_bindings::bindings;
use gecko_bindings::structs;
use gecko_bindings::structs::nsChangeHint;
use matching::{StyleChange, StyleDifference};
use properties::ComputedValues;
use std::ops::{BitAnd, BitOr, BitOrAssign, Not};

/// The representation of Gecko's restyle damage is just a wrapper over
/// `nsChangeHint`.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GeckoRestyleDamage(nsChangeHint);

impl GeckoRestyleDamage {
    /// Trivially construct a new `GeckoRestyleDamage`.
    #[inline]
    pub fn new(raw: nsChangeHint) -> Self {
        GeckoRestyleDamage(raw)
    }

    /// Get the inner change hint for this damage.
    #[inline]
    pub fn as_change_hint(&self) -> nsChangeHint {
        self.0
    }

    /// Get an empty change hint, that is (`nsChangeHint(0)`).
    #[inline]
    pub fn empty() -> Self {
        GeckoRestyleDamage(nsChangeHint(0))
    }

    /// Returns whether this restyle damage represents the empty damage.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.0 == nsChangeHint(0)
    }

    /// Computes the `StyleDifference` (including the appropriate change hint)
    /// given an old and a new style.
    pub fn compute_style_difference(
        old_style: &ComputedValues,
        new_style: &ComputedValues,
    ) -> StyleDifference {
        let mut any_style_changed = false;
        let mut reset_only = false;
        let hint = unsafe {
            bindings::Gecko_CalcStyleDifference(
                old_style,
                new_style,
                &mut any_style_changed,
                &mut reset_only,
            )
        };
        let change = if any_style_changed {
            StyleChange::Changed { reset_only }
        } else {
            StyleChange::Unchanged
        };
        StyleDifference::new(GeckoRestyleDamage(nsChangeHint(hint)), change)
    }

    /// Returns true if this restyle damage contains all the damage of |other|.
    pub fn contains(self, other: Self) -> bool {
        self & other == other
    }

    /// Gets restyle damage to reconstruct the entire frame, subsuming all
    /// other damage.
    pub fn reconstruct() -> Self {
        GeckoRestyleDamage(structs::nsChangeHint_nsChangeHint_ReconstructFrame)
    }
}

impl Default for GeckoRestyleDamage {
    fn default() -> Self {
        Self::empty()
    }
}

impl BitOr for GeckoRestyleDamage {
    type Output = Self;
    fn bitor(self, other: Self) -> Self {
        GeckoRestyleDamage(self.0 | other.0)
    }
}

impl BitOrAssign for GeckoRestyleDamage {
    fn bitor_assign(&mut self, other: Self) {
        *self = *self | other;
    }
}

impl BitAnd for GeckoRestyleDamage {
    type Output = Self;
    fn bitand(self, other: Self) -> Self {
        GeckoRestyleDamage(nsChangeHint((self.0).0 & (other.0).0))
    }
}

impl Not for GeckoRestyleDamage {
    type Output = Self;
    fn not(self) -> Self {
        GeckoRestyleDamage(nsChangeHint(!(self.0).0))
    }
}
<commit_msg>Auto merge of #18842 - heycam:skip-var-check, r=emilio<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/. */

//! Gecko's restyle damage computation (aka change hints, aka `nsChangeHint`).

use gecko_bindings::bindings;
use gecko_bindings::structs;
use gecko_bindings::structs::nsChangeHint;
use matching::{StyleChange, StyleDifference};
use properties::ComputedValues;
use std::ops::{BitAnd, BitOr, BitOrAssign, Not};

/// The representation of Gecko's restyle damage is just a wrapper over
/// `nsChangeHint`.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct GeckoRestyleDamage(nsChangeHint);

impl GeckoRestyleDamage {
    /// Trivially construct a new `GeckoRestyleDamage`.
    #[inline]
    pub fn new(raw: nsChangeHint) -> Self {
        GeckoRestyleDamage(raw)
    }

    /// Get the inner change hint for this damage.
    #[inline]
    pub fn as_change_hint(&self) -> nsChangeHint {
        self.0
    }

    /// Get an empty change hint, that is (`nsChangeHint(0)`).
    #[inline]
    pub fn empty() -> Self {
        GeckoRestyleDamage(nsChangeHint(0))
    }

    /// Returns whether this restyle damage represents the empty damage.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.0 == nsChangeHint(0)
    }

    /// Computes the `StyleDifference` (including the appropriate change hint)
    /// given an old and a new style.
    pub fn compute_style_difference(
        old_style: &ComputedValues,
        new_style: &ComputedValues,
    ) -> StyleDifference {
        let mut any_style_changed = false;
        let mut reset_only = false;
        let hint = unsafe {
            bindings::Gecko_CalcStyleDifference(
                old_style,
                new_style,
                &mut any_style_changed,
                &mut reset_only,
            )
        };
        if reset_only &&
           old_style.custom_properties() != new_style.custom_properties() {
            // The Gecko_CalcStyleDifference call only checks the non-custom
            // property structs, so we check the custom properties here. Since
            // they generate no damage themselves, we can skip this check if we
            // already know we had some inherited (regular) property
            // differences.
            any_style_changed = true;
            reset_only = false;
        }
        let change = if any_style_changed {
            StyleChange::Changed { reset_only }
        } else {
            StyleChange::Unchanged
        };
        StyleDifference::new(GeckoRestyleDamage(nsChangeHint(hint)), change)
    }

    /// Returns true if this restyle damage contains all the damage of |other|.
    pub fn contains(self, other: Self) -> bool {
        self & other == other
    }

    /// Gets restyle damage to reconstruct the entire frame, subsuming all
    /// other damage.
    pub fn reconstruct() -> Self {
        GeckoRestyleDamage(structs::nsChangeHint_nsChangeHint_ReconstructFrame)
    }
}

impl Default for GeckoRestyleDamage {
    fn default() -> Self {
        Self::empty()
    }
}

impl BitOr for GeckoRestyleDamage {
    type Output = Self;
    fn bitor(self, other: Self) -> Self {
        GeckoRestyleDamage(self.0 | other.0)
    }
}

impl BitOrAssign for GeckoRestyleDamage {
    fn bitor_assign(&mut self, other: Self) {
        *self = *self | other;
    }
}

impl BitAnd for GeckoRestyleDamage {
    type Output = Self;
    fn bitand(self, other: Self) -> Self {
        GeckoRestyleDamage(nsChangeHint((self.0).0 & (other.0).0))
    }
}

impl Not for GeckoRestyleDamage {
    type Output = Self;
    fn not(self) -> Self {
        GeckoRestyleDamage(nsChangeHint(!(self.0).0))
    }
}
<|endoftext|>


<commit_before>// Kevin Cantu
// Boyer-Moore string searching

use std;

// Function: findn_bytes
//
// Boyer-Moore string search, which returns
// up to `nn` byte positions of matched substrings
fn findn_bytes (needle: str, haystack: str, nn: uint) -> [uint] {

   let results = [];

   // empties
   if str::len(haystack) == 0u {
      ret results;
   }

   if str::len(needle) == 0u {
      vec::push(results, 0u);
      ret results;
   }

   if str::len(haystack) < str::len(needle) {
      ret results;
   }

   // generate the tables
   let ct = char_table(needle);
   let pt = prefix_table(needle);

   // simplify the table referencing
   let getShift = fn@(pos: uint) -> uint {
      let charShift, prefShift; // also: ct, pt, needle

      assert needle != "";
      assert pos >= 0u;
      assert pos < str::len(needle);

      alt ct.find(needle[pos] as uint) {
         option::none { charShift = str::len(needle);}
         option::some(x) { charShift = x;}
      }

      let offset = str::len(needle) - pos;

      assert offset >= 0u;
      assert offset < str::len(needle);

      alt pt.find(offset) {
         option::none { fail "something is out of range" }
         option::some(x) { prefShift = x;}
      }

      ret greaterOf(charShift, prefShift);
   };

   let hii = 0u;
   let njj = 0u;

   while hii < str::len(haystack) {

      njj = str::len(needle);

      // reverse through needle
      while 0u < njj {
         njj -= 1u;

         if njj+hii >= str::len(needle) {
            ret results;
         }

         // if still matching
         if needle[njj] == haystack[hii+njj] {

            // if full match
            if njj == 0u {
               vec::push(results, hii);
               log(warn, results);
               if vec::len(results) >= nn { ret results; }
            }

            hii += 1u;

         // if partial match
         } else {
            let shift = getShift(njj);

            hii += shift;
            njj = str::len(needle);
         }
      }
   }

   ret results;
}

fn char_table (needle: str) -> std::map::map<uint, uint> {
   let mm: std::map::map<uint, uint> = std::map::new_uint_hash();
   let len = str::len(needle);

   let jj = len - 1u; // drop the last byte

   assert jj >= 0u;
   assert jj < str::len(needle);

   // from last-1 to first
   while jj > 0u {
      jj -= 1u;
      if ! mm.contains_key(needle[jj] as uint) {
         // store reverse indexes
         mm.insert(needle[jj] as uint, len - 1u - jj);
      }
   }

   ret mm;
}

#[test]
fn test_char_table () {
   let ct = char_table("ANPANMAN");
   assert 1u == ct.get('A' as uint);
   assert 2u == ct.get('M' as uint);
   assert 3u == ct.get('N' as uint);
   assert 5u == ct.get('P' as uint);
   assert option::none == ct.find('z' as uint);
}

fn prefix_table (needle: str) -> std::map::map<uint, uint> {
   let mm: std::map::map<uint, uint> = std::map::new_uint_hash();

   // WAIT, HOW IS THIS ALLOWED TO MUTATE mm?
   let fill = fn@(kk: uint, vv: uint) {
      if ! mm.contains_key(kk) {
         mm.insert(kk, vv);
         //std::io::println(#fmt("%u => %u", kk, vv));
      }
   };

   let lim   = str::len(needle);
   assert 0u < lim;

   // step to larger suffixes
   let sii = 0u;
   while sii < lim {

      // tail of the needle we seek
      let suffix      = str::slice(needle, lim - sii,      lim);
      let suffix_plus = str::slice(needle, lim - sii - 1u, lim);
      let slen = str::len(suffix);

      // step to smaller prefixes
      let pii = lim - 1u;
      while pii > 0u {

         // a prefix of the needle that might be matched by
         // a partial match of a suffix
         // (which we might want to jump to)
         let prefix = str::slice(needle, 0u, pii);

         //let msg  = "<"+suffix+"("+#fmt("%u",sii)+")";
         //let msg2 = prefix+"("+#fmt("%u",pii)+")>";
         //std::io::println(msg + " × " + msg2);

         // suffix might be fully matched
         let is_suffix_matched =
            str::ends_with(prefix, suffix)
            && !str::ends_with(prefix, suffix_plus)
            &&  slen < str::len(prefix);

         // prefix is bigger than suffix, only tail can match
         let is_suffix_partially_matched =
            str::len(prefix) <= str::len(suffix)
            && str::ends_with(suffix, prefix);

         if is_suffix_matched || is_suffix_partially_matched {
            fill(sii, lim-pii);
         }

         pii -= 1u;
      }

      // no matching prefix
      fill(sii, lim-pii);

      sii += 1u;
   }

   ret mm;
}

#[test]
fn test_prefix_table() {
   let pt = prefix_table("ANPANMAN");
                            //      ... 8

   assert 1u == pt.get(0u); //        (n)
   assert 8u == pt.get(1u); //       (a)n
   assert 3u == pt.get(2u); //      (m)an
   assert 6u == pt.get(3u); //     (n)man
   assert 6u == pt.get(4u); //    (a)nman
   assert 6u == pt.get(5u); //   (p)anman
   assert 6u == pt.get(6u); //  (n)panman
   assert 6u == pt.get(7u); // (a)npanman
   //assert 0u == pt.get(8u); // fail
}

////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////

// prefixes, smallest first
fn prefixes(ss: str) -> [str] unsafe {
   assert str::len(ss) > 0u;

   let vv = [];
   str::chars_iteri(ss) {|ii, _bb|
      vec::push(vv, str::slice(ss, 0u, ii));
   }
   vec::push(vv, ss);
   ret vv;
}

#[test]
fn test_prefs() {
   assert ["", "a", "ab", "abc", "abcd"] == prefixes("abcd");
}

// suffixes, largest first
fn suffixes(ss: str) -> [str] unsafe {
   assert str::len(ss) > 0u;

   let vv = [];
   let lim = str::len(ss);
   str::chars_iteri(ss) {|ii, _bb|
      vec::push(vv, str::slice(ss, ii, lim));
   }
   vec::push(vv, "");
   ret vv;
}

#[test]
fn test_sufs() {
   assert ["abcd", "bcd", "cd", "d", ""] == suffixes("abcd");
}

fn greaterOf(a: uint, b: uint) -> uint {
   if a > b { ret a; } else { ret b; }
}

#[test]
fn test_greaterOf() {
   assert greaterOf(15u, 17u) == 17u;
   assert greaterOf(17u, 15u) == 17u;
}

fn find_bytes_(needle: str, haystack: str) -> option<uint> {
   let found = findn_bytes(needle, haystack, 1u);

   alt vec::len(found) {
      0u { option::none }
      xx { option::some(found[0u]) }
   }
}

#[test]
fn test_findn() {
   assert [] == findn_bytes("banana", "apple pie", 1u);
}

#[test]
fn test_find_bytes_A() {
  // byte positions
  assert (find_bytes_("banana", "apple pie") == option::none);
}

#[test]
fn test_find_bytes_B() {
  assert (find_bytes_("", "") == option::none);
}

#[test]
fn test_find_bytes_C() {
  let data = "ประเทศไทย中华Việt Nam";
//  assert (find_bytes_("", data)     == option::some( 0u));
// PENDING
  assert (find_bytes_("ประเ", data) == option::some( 0u));
//  assert (find_bytes_("ะเ", data)   == option::some( 6u));
//  assert (find_bytes_("中华", data) == option::some(27u));
//  assert (find_bytes_("ไท华", data) == option::none);
}

<commit_msg>ok, now the tables work again...<commit_after>// Kevin Cantu
// Boyer-Moore string searching

use std;

// Function: findn_bytes
//
// Boyer-Moore string search, which returns
// up to `nn` byte positions of matched substrings
fn findn_bytes (needle: str, haystack: str, nn: uint) -> [uint] {

   let results = [];

   // empties
   if str::len(haystack) == 0u {
      ret results;
   }

   if str::len(needle) == 0u {
      vec::push(results, 0u);
      ret results;
   }

   if str::len(haystack) < str::len(needle) {
      ret results;
   }

   // generate the tables
   let ct = char_table(needle);
   let pt = prefix_table(needle);

   // simplify the table referencing
   let getShift = fn@(pos: uint) -> uint {
      let charShift, prefShift; // also: ct, pt, needle

      assert needle != "";
      assert pos >= 0u;
      assert pos < str::len(needle);

      alt ct.find(needle[pos] as uint) {
         option::none { charShift = str::len(needle);}
         option::some(x) { charShift = x;}
      }

      let offset = str::len(needle) - pos;

      assert offset >= 0u;
      assert offset < str::len(needle);

      alt pt.find(offset) {
         option::none { fail "something is out of range" }
         option::some(x) { prefShift = x;}
      }

      ret greaterOf(charShift, prefShift);
   };

   let hii = 0u;
   let njj = 0u;

   while hii < str::len(haystack) {

      njj = str::len(needle);

      // reverse through needle
      while 0u < njj {
         njj -= 1u;

         if njj+hii >= str::len(needle) {
            ret results;
         }

         // if still matching
         if needle[njj] == haystack[hii+njj] {

            // if full match
            if njj == 0u {
               vec::push(results, hii);
               log(warn, results);
               if vec::len(results) >= nn { ret results; }
            }

            hii += 1u;

         // if partial match
         } else {
            let shift = getShift(njj);

            hii += shift;
            njj = str::len(needle);
         }
      }
   }

   ret results;
}

fn char_table (needle: str) -> std::map::map<uint, uint> {
   let mm: std::map::map<uint, uint> = std::map::new_uint_hash();
   let len = str::len(needle);

   let jj = len - 1u; // drop the last byte

   assert jj >= 0u;
   assert jj < str::len(needle);

   // from last-1 to first
   while jj > 0u {
      jj -= 1u;
      if ! mm.contains_key(needle[jj] as uint) {
         // store reverse indexes
         mm.insert(needle[jj] as uint, len - 1u - jj);
      }
   }

   ret mm;
}

#[test]
fn test_char_table () {
   let ct = char_table("ANPANMAN");
   assert 1u == ct.get('A' as uint);
   assert 2u == ct.get('M' as uint);
   assert 3u == ct.get('N' as uint);
   assert 5u == ct.get('P' as uint);
   assert option::none == ct.find('z' as uint);
}

fn prefix_table (needle: str) -> std::map::map<uint, uint> {
   let mm: std::map::map<uint, uint> = std::map::new_uint_hash();

   // WAIT, HOW IS THIS ALLOWED TO MUTATE mm?
   let fill = fn@(kk: uint, vv: uint) {
      if ! mm.contains_key(kk) {
         mm.insert(kk, vv);
         //std::io::println(#fmt("%u => %u", kk, vv));
      }
   };

   let lim   = str::len(needle);
   assert 0u < lim;

   // step to larger suffixes
   let sii = 0u;
   while sii < lim {

      // tail of the needle we seek
      let suffix      = str::slice(needle, lim - sii,      lim);
      let suffix_plus = str::slice(needle, lim - sii - 1u, lim);
      let slen = str::len(suffix);

      // step to smaller prefixes
      let pii = lim - 1u;
      while pii > 0u {

         // a prefix of the needle that might be matched by
         // a partial match of a suffix
         // (which we might want to jump to)
         let prefix = str::slice(needle, 0u, pii);

         //let msg  = "<"+suffix+"("+#fmt("%u",sii)+")";
         //let msg2 = prefix+"("+#fmt("%u",pii)+")>";
         //std::io::println(msg + " × " + msg2);

         // suffix might be fully matched
         let is_suffix_matched =
            str::ends_with(prefix, suffix)
            && !str::ends_with(prefix, suffix_plus)
            &&  slen < str::len(prefix);

         // prefix is bigger than suffix, only tail can match
         let is_suffix_partially_matched =
            str::len(prefix) <= str::len(suffix)
            && str::ends_with(suffix, prefix);

         if is_suffix_matched || is_suffix_partially_matched {
            fill(sii, lim-pii);
         }

         pii -= 1u;
      }

      // no matching prefix
      fill(sii, lim-pii);

      sii += 1u;
   }

   ret mm;
}

#[test]
fn test_prefix_table() {
   let pt = prefix_table("ANPANMAN");
                            //      ... 8

   assert 1u == pt.get(0u); //        (n)
   assert 8u == pt.get(1u); //       (a)n
   assert 3u == pt.get(2u); //      (m)an
   assert 6u == pt.get(3u); //     (n)man
   assert 6u == pt.get(4u); //    (a)nman
   assert 6u == pt.get(5u); //   (p)anman
   assert 6u == pt.get(6u); //  (n)panman
   assert 6u == pt.get(7u); // (a)npanman
   //assert 0u == pt.get(8u); // fail
}

////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////

// prefixes, smallest first
fn prefixes(ss: str) -> [str] unsafe {
   let vv = [];

   vec::push(vv, "");
   str::chars_iteri(ss) {|ii, _bb|
      vec::push(vv, str::slice(ss, 0u, ii+1u));
   }

   ret vv;
}

#[test]
fn test_prefs() {
   assert ["", "a", "ab", "abc", "abcd"] == prefixes("abcd");
   assert [""] == prefixes("");
}

// suffixes, largest first
fn suffixes(ss: str) -> [str] unsafe {
   let vv = [];
   let lim = str::len(ss);

   str::chars_iteri(ss) {|ii, _bb|
      vec::push(vv, str::slice(ss, ii, lim));
   }
   vec::push(vv, "");

   ret vv;
}

#[test]
fn test_sufs() {
   assert ["abcd", "bcd", "cd", "d", ""] == suffixes("abcd");
   assert [""] == suffixes("");
}

fn greaterOf(a: uint, b: uint) -> uint {
   if a > b { ret a; } else { ret b; }
}

#[test]
fn test_greaterOf() {
   assert greaterOf(15u, 17u) == 17u;
   assert greaterOf(17u, 15u) == 17u;
}

fn find_bytes_(needle: str, haystack: str) -> option<uint> {
   let found = findn_bytes(needle, haystack, 1u);

   alt vec::len(found) {
      0u { option::none }
      xx { option::some(found[0u]) }
   }
}

#[test]
fn test_findn() {
   assert [] == findn_bytes("banana", "apple pie", 1u);
}

#[test]
fn test_find_bytes_A() {
  // byte positions
  assert (find_bytes_("banana", "apple pie") == option::none);
}

#[test]
fn test_find_bytes_B() {
  assert (find_bytes_("", "") == option::none);
}

#[test]
fn test_find_bytes_C() {
  let data = "ประเทศไทย中华Việt Nam";
//  assert (find_bytes_("", data)     == option::some( 0u));
// PENDING
  assert (find_bytes_("ประเ", data) == option::some( 0u));
//  assert (find_bytes_("ะเ", data)   == option::some( 6u));
//  assert (find_bytes_("中华", data) == option::some(27u));
//  assert (find_bytes_("ไท华", data) == option::none);
}

<|endoftext|>


<commit_before>#![no_main]

use arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;

#[derive(Arbitrary, Debug, Clone)]
enum IntegerInput {
    I8(i8),
    U8(u8),
    I16(i16),
    U16(u16),
    I32(i32),
    U32(u32),
    I64(i64),
    U64(u64),
    ISIZE(isize),
    USIZE(usize),
    I128(i128),
    U128(u128),
}

#[derive(Arbitrary, Debug, Clone)]
struct Inputs {
    inputs: Vec<IntegerInput>,
}

fuzz_target!(|input: Inputs| {
    let mut buffer = itoa::Buffer::new();
    for input_integer in input.inputs {
        match input_integer {
            IntegerInput::I8(val) => buffer.format(val),
            IntegerInput::U8(val) => buffer.format(val),
            IntegerInput::I16(val) => buffer.format(val),
            IntegerInput::U16(val) => buffer.format(val),
            IntegerInput::I32(val) => buffer.format(val),
            IntegerInput::U32(val) => buffer.format(val),
            IntegerInput::I64(val) => buffer.format(val),
            IntegerInput::U64(val) => buffer.format(val),
            IntegerInput::ISIZE(val) => buffer.format(val),
            IntegerInput::USIZE(val) => buffer.format(val),
            IntegerInput::I128(val) => buffer.format(val),
            IntegerInput::U128(val) => buffer.format(val),
        };
    }
});
<commit_msg>Remove unneeded Clone impls from fuzz target<commit_after>#![no_main]

use arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;

#[derive(Arbitrary, Debug)]
enum IntegerInput {
    I8(i8),
    U8(u8),
    I16(i16),
    U16(u16),
    I32(i32),
    U32(u32),
    I64(i64),
    U64(u64),
    ISIZE(isize),
    USIZE(usize),
    I128(i128),
    U128(u128),
}

#[derive(Arbitrary, Debug)]
struct Inputs {
    inputs: Vec<IntegerInput>,
}

fuzz_target!(|input: Inputs| {
    let mut buffer = itoa::Buffer::new();
    for input_integer in input.inputs {
        match input_integer {
            IntegerInput::I8(val) => buffer.format(val),
            IntegerInput::U8(val) => buffer.format(val),
            IntegerInput::I16(val) => buffer.format(val),
            IntegerInput::U16(val) => buffer.format(val),
            IntegerInput::I32(val) => buffer.format(val),
            IntegerInput::U32(val) => buffer.format(val),
            IntegerInput::I64(val) => buffer.format(val),
            IntegerInput::U64(val) => buffer.format(val),
            IntegerInput::ISIZE(val) => buffer.format(val),
            IntegerInput::USIZE(val) => buffer.format(val),
            IntegerInput::I128(val) => buffer.format(val),
            IntegerInput::U128(val) => buffer.format(val),
        };
    }
});
<|endoftext|>


<commit_before>use regex::Regex;
use std::fmt::Write as _;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::path::MAIN_SEPARATOR;
use std::str;

use protobuf_test_common::build::copy_tests_v2_v3;
use protobuf_test_common::build::glob_simple;
use std::process::Command;
use std::process::Stdio;

fn to_paths(iter: impl IntoIterator<Item = impl Into<String>>) -> Vec<PathBuf> {
    iter.into_iter()
        .map(|item| item.into().replace(MAIN_SEPARATOR, "/").into())
        .collect()
}

#[derive(Default, Debug)]
struct TestStats {
    passed: u32,
    passed_marked_skip: u32,
    skipped: u32,
    failed: u32,
}

fn normalize_generated_file(contents: &str) -> String {
    let parsed_by = Regex::new("^// \\.proto file is parsed by.*").unwrap();

    let mut r = String::new();
    let mut inside_descriptor_data = false;
    for line in contents.lines() {
        let line = if inside_descriptor_data {
            if line == "\";" {
                inside_descriptor_data = false;
                line.to_owned()
            } else {
                continue;
            }
        } else if line.starts_with("static file_descriptor_proto_data") {
            inside_descriptor_data = true;
            line.to_owned()
        } else {
            parsed_by.replace(line, "").into_owned()
        };

        writeln!(&mut r, "{}", line).unwrap();
    }

    // sanity check
    assert!(!inside_descriptor_data);

    r
}

fn normalize_generated_file_in_place(path: &Path) {
    let contents = fs::read_to_string(path).unwrap();
    let contents = normalize_generated_file(&contents);
    fs::write(path, &contents).unwrap();
}

fn print_diff(dir: &Path, a: &Path, b: &Path) {
    if cfg!(windows) {
        // likely we don't have `diff` command on Windows
        return;
    }

    // Not using difference crate because it's slow for large files
    let output = Command::new("diff")
        .current_dir(dir)
        .arg("-u")
        .arg(a)
        .arg(b)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .unwrap();

    print!("{}", str::from_utf8(&output.stdout).unwrap());
    print!("{}", str::from_utf8(&output.stderr).unwrap());
}

fn test_diff_in<F>(root: &str, s: &str, include: &str, should_skip: F)
where
    F: Fn(&str) -> bool,
{
    let mut stats = TestStats {
        passed: 0,
        passed_marked_skip: 0,
        skipped: 0,
        failed: 0,
    };

    let include_full = format!("{}/{}", root, include);
    let s_full = format!("{}/{}", root, s);

    let inputs_glob = format!("{}/*.proto", s_full);
    let inputs = to_paths(glob_simple(&inputs_glob));
    assert!(!inputs.is_empty(), "glob is empty: {}", inputs_glob);
    let includes = to_paths(vec![include_full.as_str(), "../proto"]);

    let temp_dir = tempfile::Builder::new()
        .prefix("protobuf-codegen-identical-test")
        .tempdir()
        .unwrap();

    let protoc_dir = format!("{}/protoc", temp_dir.path().to_str().unwrap());
    let pure_dir = format!("{}/pure", temp_dir.path().to_str().unwrap());

    fs::create_dir(&protoc_dir).unwrap();
    fs::create_dir(&pure_dir).unwrap();

    protoc_rust::Codegen::new()
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&protoc_dir)
        .run()
        .unwrap();

    protobuf_codegen_pure::Codegen::new()
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&pure_dir)
        .run()
        .unwrap();

    for input in &inputs {
        let label = input.display().to_string();
        assert!(label.starts_with(&format!("{}/", root)));
        let label = &label[format!("{}/", root).len()..];
        let proto_name = input.file_name().unwrap().to_str().unwrap();
        let rs_name = protobuf_codegen::proto_name_to_rs(proto_name);
        let protoc_rs = format!("{}/{}", protoc_dir, rs_name);
        let pure_rs = format!("{}/{}", pure_dir, rs_name);

        normalize_generated_file_in_place(Path::new(&protoc_rs));
        normalize_generated_file_in_place(Path::new(&pure_rs));

        let protoc_rs_contents =
            fs::read_to_string(&protoc_rs).expect(&format!("while reading {}", protoc_rs));
        let pure_rs_contents =
            fs::read_to_string(&pure_rs).expect(&format!("while reading {}", pure_rs));
        let skip = should_skip(input.to_str().unwrap());
        if protoc_rs_contents == pure_rs_contents {
            if !skip {
                stats.passed += 1;
                println!("{}: PASSED", label);
            } else {
                stats.passed_marked_skip += 1;
                println!("{}: PASSED BUT MARKED SKIP", label);
            }
        } else {
            if skip {
                stats.skipped += 1;
                println!("{} SKIPPED", label);
            } else {
                stats.failed += 1;
                println!("{} FAILED", label);
            }

            print_diff(temp_dir.path(), Path::new(&protoc_rs), Path::new(&pure_rs));
        }
    }

    println!("{:?}", stats);
    assert!(
        stats.passed != 0 || s == "src/google/protobuf",
        "sanity check"
    );
    assert!(stats.failed == 0, "at least one test failed");
}

fn should_skip_with_marker(path: &str) -> bool {
    fs::read_to_string(path)
        .unwrap()
        .contains("@skip-codegen-identical-test")
}

#[test]
fn common_v2() {
    test_diff_in(
        "../protobuf-test",
        "src/common/v2",
        "src/common/v2",
        should_skip_with_marker,
    );
}

#[test]
fn common_v3() {
    let common_v3_root = tempfile::Builder::new()
        .prefix("common-v3")
        .tempdir()
        .unwrap();
    fs::create_dir_all(format!(
        "{}/src/common/v3",
        common_v3_root.path().to_str().unwrap()
    ))
    .unwrap();

    copy_tests_v2_v3(
        "../protobuf-test/src/common/v2",
        &format!("{}/src/common/v3", common_v3_root.path().to_str().unwrap()),
    );

    test_diff_in(
        common_v3_root.path().to_str().unwrap(),
        "src/common/v3",
        "src/common/v3",
        should_skip_with_marker,
    );
}

#[test]
fn v2() {
    test_diff_in(
        "../protobuf-test",
        "src/v2",
        "src/v2",
        should_skip_with_marker,
    );
}

#[test]
fn v3() {
    test_diff_in(
        "../protobuf-test",
        "src/v3",
        "src/v3",
        should_skip_with_marker,
    );
}

#[test]
fn interop() {
    test_diff_in(
        "../protobuf-test",
        "../interop/cxx",
        "../interop/cxx",
        should_skip_with_marker,
    );
}

#[test]
fn google() {
    test_diff_in("../protobuf-test", "src/google/protobuf", "src", |_| true);
}
<commit_msg>Exclude temp dir name from diff output<commit_after>use regex::Regex;
use std::fmt::Write as _;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
use std::path::MAIN_SEPARATOR;
use std::str;

use protobuf_test_common::build::copy_tests_v2_v3;
use protobuf_test_common::build::glob_simple;
use std::process::Command;
use std::process::Stdio;

fn to_paths(iter: impl IntoIterator<Item = impl Into<String>>) -> Vec<PathBuf> {
    iter.into_iter()
        .map(|item| item.into().replace(MAIN_SEPARATOR, "/").into())
        .collect()
}

#[derive(Default, Debug)]
struct TestStats {
    passed: u32,
    passed_marked_skip: u32,
    skipped: u32,
    failed: u32,
}

fn normalize_generated_file(contents: &str) -> String {
    let parsed_by = Regex::new("^// \\.proto file is parsed by.*").unwrap();

    let mut r = String::new();
    let mut inside_descriptor_data = false;
    for line in contents.lines() {
        let line = if inside_descriptor_data {
            if line == "\";" {
                inside_descriptor_data = false;
                line.to_owned()
            } else {
                continue;
            }
        } else if line.starts_with("static file_descriptor_proto_data") {
            inside_descriptor_data = true;
            line.to_owned()
        } else {
            parsed_by.replace(line, "").into_owned()
        };

        writeln!(&mut r, "{}", line).unwrap();
    }

    // sanity check
    assert!(!inside_descriptor_data);

    r
}

fn normalize_generated_file_in_place(path: &Path) {
    let contents = fs::read_to_string(path).unwrap();
    let contents = normalize_generated_file(&contents);
    fs::write(path, &contents).unwrap();
}

fn print_diff(dir: &Path, a: &Path, b: &Path) {
    if cfg!(windows) {
        // likely we don't have `diff` command on Windows
        return;
    }

    // Not using difference crate because it's slow for large files
    let output = Command::new("diff")
        .current_dir(dir)
        .arg("-u")
        .arg(a)
        .arg(b)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .unwrap();

    print!("{}", str::from_utf8(&output.stdout).unwrap());
    print!("{}", str::from_utf8(&output.stderr).unwrap());
}

fn test_diff_in<F>(root: &str, s: &str, include: &str, should_skip: F)
where
    F: Fn(&str) -> bool,
{
    let mut stats = TestStats {
        passed: 0,
        passed_marked_skip: 0,
        skipped: 0,
        failed: 0,
    };

    let include_full = format!("{}/{}", root, include);
    let s_full = format!("{}/{}", root, s);

    let inputs_glob = format!("{}/*.proto", s_full);
    let inputs = to_paths(glob_simple(&inputs_glob));
    assert!(!inputs.is_empty(), "glob is empty: {}", inputs_glob);
    let includes = to_paths(vec![include_full.as_str(), "../proto"]);

    let temp_dir = tempfile::Builder::new()
        .prefix("protobuf-codegen-identical-test")
        .tempdir()
        .unwrap();

    let protoc_dir = format!("{}/protoc", temp_dir.path().to_str().unwrap());
    let pure_dir = format!("{}/pure", temp_dir.path().to_str().unwrap());

    fs::create_dir(&protoc_dir).unwrap();
    fs::create_dir(&pure_dir).unwrap();

    protoc_rust::Codegen::new()
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&protoc_dir)
        .run()
        .unwrap();

    protobuf_codegen_pure::Codegen::new()
        .inputs(&inputs)
        .includes(&includes)
        .out_dir(&pure_dir)
        .run()
        .unwrap();

    for input in &inputs {
        let label = input.display().to_string();
        assert!(label.starts_with(&format!("{}/", root)));
        let label = &label[format!("{}/", root).len()..];
        let proto_name = input.file_name().unwrap().to_str().unwrap();
        let rs_name = protobuf_codegen::proto_name_to_rs(proto_name);
        let protoc_rs = format!("{}/{}", protoc_dir, rs_name);
        let pure_rs = format!("{}/{}", pure_dir, rs_name);

        normalize_generated_file_in_place(Path::new(&protoc_rs));
        normalize_generated_file_in_place(Path::new(&pure_rs));

        let protoc_rs_contents =
            fs::read_to_string(&protoc_rs).expect(&format!("while reading {}", protoc_rs));
        let pure_rs_contents =
            fs::read_to_string(&pure_rs).expect(&format!("while reading {}", pure_rs));
        let skip = should_skip(input.to_str().unwrap());
        if protoc_rs_contents == pure_rs_contents {
            if !skip {
                stats.passed += 1;
                println!("{}: PASSED", label);
            } else {
                stats.passed_marked_skip += 1;
                println!("{}: PASSED BUT MARKED SKIP", label);
            }
        } else {
            if skip {
                stats.skipped += 1;
                println!("{} SKIPPED", label);
            } else {
                stats.failed += 1;
                println!("{} FAILED", label);
            }

            print_diff(
                temp_dir.path(),
                Path::new(&protoc_rs).strip_prefix(temp_dir.path()).unwrap(),
                Path::new(&pure_rs).strip_prefix(temp_dir.path()).unwrap(),
            );
        }
    }

    println!("{:?}", stats);
    assert!(
        stats.passed != 0 || s == "src/google/protobuf",
        "sanity check"
    );
    assert!(stats.failed == 0, "at least one test failed");
}

fn should_skip_with_marker(path: &str) -> bool {
    fs::read_to_string(path)
        .unwrap()
        .contains("@skip-codegen-identical-test")
}

#[test]
fn common_v2() {
    test_diff_in(
        "../protobuf-test",
        "src/common/v2",
        "src/common/v2",
        should_skip_with_marker,
    );
}

#[test]
fn common_v3() {
    let common_v3_root = tempfile::Builder::new()
        .prefix("common-v3")
        .tempdir()
        .unwrap();
    fs::create_dir_all(format!(
        "{}/src/common/v3",
        common_v3_root.path().to_str().unwrap()
    ))
    .unwrap();

    copy_tests_v2_v3(
        "../protobuf-test/src/common/v2",
        &format!("{}/src/common/v3", common_v3_root.path().to_str().unwrap()),
    );

    test_diff_in(
        common_v3_root.path().to_str().unwrap(),
        "src/common/v3",
        "src/common/v3",
        should_skip_with_marker,
    );
}

#[test]
fn v2() {
    test_diff_in(
        "../protobuf-test",
        "src/v2",
        "src/v2",
        should_skip_with_marker,
    );
}

#[test]
fn v3() {
    test_diff_in(
        "../protobuf-test",
        "src/v3",
        "src/v3",
        should_skip_with_marker,
    );
}

#[test]
fn interop() {
    test_diff_in(
        "../protobuf-test",
        "../interop/cxx",
        "../interop/cxx",
        should_skip_with_marker,
    );
}

#[test]
fn google() {
    test_diff_in("../protobuf-test", "src/google/protobuf", "src", |_| true);
}
<|endoftext|>


<commit_before>use rustc::lint::{LintArray, LateLintPass, LateContext, LintPass};
use rustc::hir::*;
use rustc::hir::intravisit::*;
use syntax::ast::{LitKind, DUMMY_NODE_ID};
use syntax::codemap::{DUMMY_SP, dummy_spanned};
use syntax::util::ThinVec;
use utils::{span_lint_and_then, in_macro, snippet_opt, SpanlessEq};

/// **What it does:** Checks for boolean expressions that can be written more
/// concisely.
///
/// **Why is this bad?** Readability of boolean expressions suffers from
/// unnecessary duplication.
///
/// **Known problems:** Ignores short circuiting behavior of `||` and
/// `&&`. Ignores `|`, `&` and `^`.
///
/// **Example:**
/// ```rust
/// if a && true  // should be: if a
/// if !(a == b)  // should be: if a != b
declare_lint! {
    pub NONMINIMAL_BOOL,
    Allow,
    "boolean expressions that can be written more concisely"
}

/// **What it does:** Checks for boolean expressions that contain terminals that
/// can be eliminated.
///
/// **Why is this bad?** This is most likely a logic bug.
///
/// **Known problems:** Ignores short circuiting behavior.
///
/// **Example:**
/// ```rust
/// if a && b || a { ... }
/// ```
/// The `b` is unnecessary, the expression is equivalent to `if a`.
declare_lint! {
    pub LOGIC_BUG,
    Warn,
    "boolean expressions that contain terminals which can be eliminated"
}

#[derive(Copy,Clone)]
pub struct NonminimalBool;

impl LintPass for NonminimalBool {
    fn get_lints(&self) -> LintArray {
        lint_array!(NONMINIMAL_BOOL, LOGIC_BUG)
    }
}

impl LateLintPass for NonminimalBool {
    fn check_item(&mut self, cx: &LateContext, item: &Item) {
        NonminimalBoolVisitor(cx).visit_item(item)
    }
}

struct NonminimalBoolVisitor<'a, 'tcx: 'a>(&'a LateContext<'a, 'tcx>);

use quine_mc_cluskey::Bool;
struct Hir2Qmm<'a, 'tcx: 'a, 'v> {
    terminals: Vec<&'v Expr>,
    cx: &'a LateContext<'a, 'tcx>,
}

impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
    fn extract(&mut self, op: BinOp_, a: &[&'v Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
        for a in a {
            if let ExprBinary(binop, ref lhs, ref rhs) = a.node {
                if binop.node == op {
                    v = self.extract(op, &[lhs, rhs], v)?;
                    continue;
                }
            }
            v.push(self.run(a)?);
        }
        Ok(v)
    }

    fn run(&mut self, e: &'v Expr) -> Result<Bool, String> {
        // prevent folding of `cfg!` macros and the like
        if !in_macro(self.cx, e.span) {
            match e.node {
                ExprUnary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
                ExprBinary(binop, ref lhs, ref rhs) => {
                    match binop.node {
                        BiOr => return Ok(Bool::Or(self.extract(BiOr, &[lhs, rhs], Vec::new())?)),
                        BiAnd => return Ok(Bool::And(self.extract(BiAnd, &[lhs, rhs], Vec::new())?)),
                        _ => (),
                    }
                }
                ExprLit(ref lit) => {
                    match lit.node {
                        LitKind::Bool(true) => return Ok(Bool::True),
                        LitKind::Bool(false) => return Ok(Bool::False),
                        _ => (),
                    }
                }
                _ => (),
            }
        }
        for (n, expr) in self.terminals.iter().enumerate() {
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Term(n as u8));
            }
            let negated = match e.node {
                ExprBinary(binop, ref lhs, ref rhs) => {
                    let mk_expr = |op| {
                        Expr {
                            id: DUMMY_NODE_ID,
                            span: DUMMY_SP,
                            attrs: ThinVec::new(),
                            node: ExprBinary(dummy_spanned(op), lhs.clone(), rhs.clone()),
                        }
                    };
                    match binop.node {
                        BiEq => mk_expr(BiNe),
                        BiNe => mk_expr(BiEq),
                        BiGt => mk_expr(BiLe),
                        BiGe => mk_expr(BiLt),
                        BiLt => mk_expr(BiGe),
                        BiLe => mk_expr(BiGt),
                        _ => continue,
                    }
                }
                _ => continue,
            };
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(&negated, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
            }
        }
        let n = self.terminals.len();
        self.terminals.push(e);
        if n < 32 {
            #[allow(cast_possible_truncation)]
            Ok(Bool::Term(n as u8))
        } else {
            Err("too many literals".to_owned())
        }
    }
}

fn suggest(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr]) -> String {
    fn recurse(brackets: bool, cx: &LateContext, suggestion: &Bool, terminals: &[&Expr], mut s: String) -> String {
        use quine_mc_cluskey::Bool::*;
        let snip = |e: &Expr| snippet_opt(cx, e.span).expect("don't try to improve booleans created by macros");
        match *suggestion {
            True => {
                s.push_str("true");
                s
            }
            False => {
                s.push_str("false");
                s
            }
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => {
                        s.push('!');
                        recurse(true, cx, inner, terminals, s)
                    }
                    Term(n) => {
                        if let ExprBinary(binop, ref lhs, ref rhs) = terminals[n as usize].node {
                            let op = match binop.node {
                                BiEq => " != ",
                                BiNe => " == ",
                                BiLt => " >= ",
                                BiGt => " <= ",
                                BiLe => " > ",
                                BiGe => " < ",
                                _ => {
                                    s.push('!');
                                    return recurse(true, cx, inner, terminals, s);
                                }
                            };
                            s.push_str(&snip(lhs));
                            s.push_str(op);
                            s.push_str(&snip(rhs));
                            s
                        } else {
                            s.push('!');
                            recurse(false, cx, inner, terminals, s)
                        }
                    }
                    _ => {
                        s.push('!');
                        recurse(false, cx, inner, terminals, s)
                    }
                }
            }
            And(ref v) => {
                if brackets {
                    s.push('(');
                }
                if let Or(_) = v[0] {
                    s = recurse(true, cx, &v[0], terminals, s);
                } else {
                    s = recurse(false, cx, &v[0], terminals, s);
                }
                for inner in &v[1..] {
                    s.push_str(" && ");
                    if let Or(_) = *inner {
                        s = recurse(true, cx, inner, terminals, s);
                    } else {
                        s = recurse(false, cx, inner, terminals, s);
                    }
                }
                if brackets {
                    s.push(')');
                }
                s
            }
            Or(ref v) => {
                if brackets {
                    s.push('(');
                }
                s = recurse(false, cx, &v[0], terminals, s);
                for inner in &v[1..] {
                    s.push_str(" || ");
                    s = recurse(false, cx, inner, terminals, s);
                }
                if brackets {
                    s.push(')');
                }
                s
            }
            Term(n) => {
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push('(');
                    }
                }
                s.push_str(&snip(terminals[n as usize]));
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push(')');
                    }
                }
                s
            }
        }
    }
    recurse(false, cx, suggestion, terminals, String::new())
}

fn simple_negate(b: Bool) -> Bool {
    use quine_mc_cluskey::Bool::*;
    match b {
        True => False,
        False => True,
        t @ Term(_) => Not(Box::new(t)),
        And(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            Or(v)
        }
        Or(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            And(v)
        }
        Not(inner) => *inner,
    }
}

#[derive(Default)]
struct Stats {
    terminals: [usize; 32],
    negations: usize,
    ops: usize,
}

fn terminal_stats(b: &Bool) -> Stats {
    fn recurse(b: &Bool, stats: &mut Stats) {
        match *b {
            True | False => stats.ops += 1,
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => stats.ops += 1, // brackets are also operations
                    _ => stats.negations += 1,
                }
                recurse(inner, stats);
            }
            And(ref v) | Or(ref v) => {
                stats.ops += v.len() - 1;
                for inner in v {
                    recurse(inner, stats);
                }
            }
            Term(n) => stats.terminals[n as usize] += 1,
        }
    }
    use quine_mc_cluskey::Bool::*;
    let mut stats = Stats::default();
    recurse(b, &mut stats);
    stats
}

impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
    fn bool_expr(&self, e: &Expr) {
        let mut h2q = Hir2Qmm {
            terminals: Vec::new(),
            cx: self.0,
        };
        if let Ok(expr) = h2q.run(e) {

            if h2q.terminals.len() > 8 {
                // QMC has exponentially slow behavior as the number of terminals increases
                // 8 is reasonable, it takes approximately 0.2 seconds.
                // See #825
                return;
            }

            let stats = terminal_stats(&expr);
            let mut simplified = expr.simplify();
            for simple in Bool::Not(Box::new(expr.clone())).simplify() {
                match simple {
                    Bool::Not(_) | Bool::True | Bool::False => {}
                    _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
                }
                let simple_negated = simple_negate(simple);
                if simplified.iter().any(|s| *s == simple_negated) {
                    continue;
                }
                simplified.push(simple_negated);
            }
            let mut improvements = Vec::new();
            'simplified: for suggestion in &simplified {
                let simplified_stats = terminal_stats(suggestion);
                let mut improvement = false;
                for i in 0..32 {
                    // ignore any "simplifications" that end up requiring a terminal more often
                    // than in the original expression
                    if stats.terminals[i] < simplified_stats.terminals[i] {
                        continue 'simplified;
                    }
                    if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
                        span_lint_and_then(self.0,
                                           LOGIC_BUG,
                                           e.span,
                                           "this boolean expression contains a logic bug",
                                           |db| {
                            db.span_help(h2q.terminals[i].span,
                                         "this expression can be optimized out by applying boolean operations to the \
                                          outer expression");
                            db.span_suggestion(e.span,
                                               "it would look like the following",
                                               suggest(self.0, suggestion, &h2q.terminals));
                        });
                        // don't also lint `NONMINIMAL_BOOL`
                        return;
                    }
                    // if the number of occurrences of a terminal decreases or any of the stats
                    // decreases while none increases
                    improvement |= (stats.terminals[i] > simplified_stats.terminals[i]) ||
                                   (stats.negations > simplified_stats.negations &&
                                    stats.ops == simplified_stats.ops) ||
                                   (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
                }
                if improvement {
                    improvements.push(suggestion);
                }
            }
            if !improvements.is_empty() {
                span_lint_and_then(self.0,
                                   NONMINIMAL_BOOL,
                                   e.span,
                                   "this boolean expression can be simplified",
                                   |db| {
                    for suggestion in &improvements {
                        db.span_suggestion(e.span, "try", suggest(self.0, suggestion, &h2q.terminals));
                    }
                });
            }
        }
    }
}

impl<'a, 'v, 'tcx> Visitor<'v> for NonminimalBoolVisitor<'a, 'tcx> {
    fn visit_expr(&mut self, e: &'v Expr) {
        if in_macro(self.0, e.span) {
            return;
        }
        match e.node {
            ExprBinary(binop, _, _) if binop.node == BiOr || binop.node == BiAnd => self.bool_expr(e),
            ExprUnary(UnNot, ref inner) => {
                if self.0.tcx.node_types()[&inner.id].is_bool() {
                    self.bool_expr(e);
                } else {
                    walk_expr(self, e);
                }
            }
            _ => walk_expr(self, e),
        }
    }
}
<commit_msg>Close code block in docstring<commit_after>use rustc::lint::{LintArray, LateLintPass, LateContext, LintPass};
use rustc::hir::*;
use rustc::hir::intravisit::*;
use syntax::ast::{LitKind, DUMMY_NODE_ID};
use syntax::codemap::{DUMMY_SP, dummy_spanned};
use syntax::util::ThinVec;
use utils::{span_lint_and_then, in_macro, snippet_opt, SpanlessEq};

/// **What it does:** Checks for boolean expressions that can be written more
/// concisely.
///
/// **Why is this bad?** Readability of boolean expressions suffers from
/// unnecessary duplication.
///
/// **Known problems:** Ignores short circuiting behavior of `||` and
/// `&&`. Ignores `|`, `&` and `^`.
///
/// **Example:**
/// ```rust
/// if a && true  // should be: if a
/// if !(a == b)  // should be: if a != b
/// ```
declare_lint! {
    pub NONMINIMAL_BOOL,
    Allow,
    "boolean expressions that can be written more concisely"
}

/// **What it does:** Checks for boolean expressions that contain terminals that
/// can be eliminated.
///
/// **Why is this bad?** This is most likely a logic bug.
///
/// **Known problems:** Ignores short circuiting behavior.
///
/// **Example:**
/// ```rust
/// if a && b || a { ... }
/// ```
/// The `b` is unnecessary, the expression is equivalent to `if a`.
declare_lint! {
    pub LOGIC_BUG,
    Warn,
    "boolean expressions that contain terminals which can be eliminated"
}

#[derive(Copy,Clone)]
pub struct NonminimalBool;

impl LintPass for NonminimalBool {
    fn get_lints(&self) -> LintArray {
        lint_array!(NONMINIMAL_BOOL, LOGIC_BUG)
    }
}

impl LateLintPass for NonminimalBool {
    fn check_item(&mut self, cx: &LateContext, item: &Item) {
        NonminimalBoolVisitor(cx).visit_item(item)
    }
}

struct NonminimalBoolVisitor<'a, 'tcx: 'a>(&'a LateContext<'a, 'tcx>);

use quine_mc_cluskey::Bool;
struct Hir2Qmm<'a, 'tcx: 'a, 'v> {
    terminals: Vec<&'v Expr>,
    cx: &'a LateContext<'a, 'tcx>,
}

impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
    fn extract(&mut self, op: BinOp_, a: &[&'v Expr], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> {
        for a in a {
            if let ExprBinary(binop, ref lhs, ref rhs) = a.node {
                if binop.node == op {
                    v = self.extract(op, &[lhs, rhs], v)?;
                    continue;
                }
            }
            v.push(self.run(a)?);
        }
        Ok(v)
    }

    fn run(&mut self, e: &'v Expr) -> Result<Bool, String> {
        // prevent folding of `cfg!` macros and the like
        if !in_macro(self.cx, e.span) {
            match e.node {
                ExprUnary(UnNot, ref inner) => return Ok(Bool::Not(box self.run(inner)?)),
                ExprBinary(binop, ref lhs, ref rhs) => {
                    match binop.node {
                        BiOr => return Ok(Bool::Or(self.extract(BiOr, &[lhs, rhs], Vec::new())?)),
                        BiAnd => return Ok(Bool::And(self.extract(BiAnd, &[lhs, rhs], Vec::new())?)),
                        _ => (),
                    }
                }
                ExprLit(ref lit) => {
                    match lit.node {
                        LitKind::Bool(true) => return Ok(Bool::True),
                        LitKind::Bool(false) => return Ok(Bool::False),
                        _ => (),
                    }
                }
                _ => (),
            }
        }
        for (n, expr) in self.terminals.iter().enumerate() {
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Term(n as u8));
            }
            let negated = match e.node {
                ExprBinary(binop, ref lhs, ref rhs) => {
                    let mk_expr = |op| {
                        Expr {
                            id: DUMMY_NODE_ID,
                            span: DUMMY_SP,
                            attrs: ThinVec::new(),
                            node: ExprBinary(dummy_spanned(op), lhs.clone(), rhs.clone()),
                        }
                    };
                    match binop.node {
                        BiEq => mk_expr(BiNe),
                        BiNe => mk_expr(BiEq),
                        BiGt => mk_expr(BiLe),
                        BiGe => mk_expr(BiLt),
                        BiLt => mk_expr(BiGe),
                        BiLe => mk_expr(BiGt),
                        _ => continue,
                    }
                }
                _ => continue,
            };
            if SpanlessEq::new(self.cx).ignore_fn().eq_expr(&negated, expr) {
                #[allow(cast_possible_truncation)]
                return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
            }
        }
        let n = self.terminals.len();
        self.terminals.push(e);
        if n < 32 {
            #[allow(cast_possible_truncation)]
            Ok(Bool::Term(n as u8))
        } else {
            Err("too many literals".to_owned())
        }
    }
}

fn suggest(cx: &LateContext, suggestion: &Bool, terminals: &[&Expr]) -> String {
    fn recurse(brackets: bool, cx: &LateContext, suggestion: &Bool, terminals: &[&Expr], mut s: String) -> String {
        use quine_mc_cluskey::Bool::*;
        let snip = |e: &Expr| snippet_opt(cx, e.span).expect("don't try to improve booleans created by macros");
        match *suggestion {
            True => {
                s.push_str("true");
                s
            }
            False => {
                s.push_str("false");
                s
            }
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => {
                        s.push('!');
                        recurse(true, cx, inner, terminals, s)
                    }
                    Term(n) => {
                        if let ExprBinary(binop, ref lhs, ref rhs) = terminals[n as usize].node {
                            let op = match binop.node {
                                BiEq => " != ",
                                BiNe => " == ",
                                BiLt => " >= ",
                                BiGt => " <= ",
                                BiLe => " > ",
                                BiGe => " < ",
                                _ => {
                                    s.push('!');
                                    return recurse(true, cx, inner, terminals, s);
                                }
                            };
                            s.push_str(&snip(lhs));
                            s.push_str(op);
                            s.push_str(&snip(rhs));
                            s
                        } else {
                            s.push('!');
                            recurse(false, cx, inner, terminals, s)
                        }
                    }
                    _ => {
                        s.push('!');
                        recurse(false, cx, inner, terminals, s)
                    }
                }
            }
            And(ref v) => {
                if brackets {
                    s.push('(');
                }
                if let Or(_) = v[0] {
                    s = recurse(true, cx, &v[0], terminals, s);
                } else {
                    s = recurse(false, cx, &v[0], terminals, s);
                }
                for inner in &v[1..] {
                    s.push_str(" && ");
                    if let Or(_) = *inner {
                        s = recurse(true, cx, inner, terminals, s);
                    } else {
                        s = recurse(false, cx, inner, terminals, s);
                    }
                }
                if brackets {
                    s.push(')');
                }
                s
            }
            Or(ref v) => {
                if brackets {
                    s.push('(');
                }
                s = recurse(false, cx, &v[0], terminals, s);
                for inner in &v[1..] {
                    s.push_str(" || ");
                    s = recurse(false, cx, inner, terminals, s);
                }
                if brackets {
                    s.push(')');
                }
                s
            }
            Term(n) => {
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push('(');
                    }
                }
                s.push_str(&snip(terminals[n as usize]));
                if brackets {
                    if let ExprBinary(..) = terminals[n as usize].node {
                        s.push(')');
                    }
                }
                s
            }
        }
    }
    recurse(false, cx, suggestion, terminals, String::new())
}

fn simple_negate(b: Bool) -> Bool {
    use quine_mc_cluskey::Bool::*;
    match b {
        True => False,
        False => True,
        t @ Term(_) => Not(Box::new(t)),
        And(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            Or(v)
        }
        Or(mut v) => {
            for el in &mut v {
                *el = simple_negate(::std::mem::replace(el, True));
            }
            And(v)
        }
        Not(inner) => *inner,
    }
}

#[derive(Default)]
struct Stats {
    terminals: [usize; 32],
    negations: usize,
    ops: usize,
}

fn terminal_stats(b: &Bool) -> Stats {
    fn recurse(b: &Bool, stats: &mut Stats) {
        match *b {
            True | False => stats.ops += 1,
            Not(ref inner) => {
                match **inner {
                    And(_) | Or(_) => stats.ops += 1, // brackets are also operations
                    _ => stats.negations += 1,
                }
                recurse(inner, stats);
            }
            And(ref v) | Or(ref v) => {
                stats.ops += v.len() - 1;
                for inner in v {
                    recurse(inner, stats);
                }
            }
            Term(n) => stats.terminals[n as usize] += 1,
        }
    }
    use quine_mc_cluskey::Bool::*;
    let mut stats = Stats::default();
    recurse(b, &mut stats);
    stats
}

impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
    fn bool_expr(&self, e: &Expr) {
        let mut h2q = Hir2Qmm {
            terminals: Vec::new(),
            cx: self.0,
        };
        if let Ok(expr) = h2q.run(e) {

            if h2q.terminals.len() > 8 {
                // QMC has exponentially slow behavior as the number of terminals increases
                // 8 is reasonable, it takes approximately 0.2 seconds.
                // See #825
                return;
            }

            let stats = terminal_stats(&expr);
            let mut simplified = expr.simplify();
            for simple in Bool::Not(Box::new(expr.clone())).simplify() {
                match simple {
                    Bool::Not(_) | Bool::True | Bool::False => {}
                    _ => simplified.push(Bool::Not(Box::new(simple.clone()))),
                }
                let simple_negated = simple_negate(simple);
                if simplified.iter().any(|s| *s == simple_negated) {
                    continue;
                }
                simplified.push(simple_negated);
            }
            let mut improvements = Vec::new();
            'simplified: for suggestion in &simplified {
                let simplified_stats = terminal_stats(suggestion);
                let mut improvement = false;
                for i in 0..32 {
                    // ignore any "simplifications" that end up requiring a terminal more often
                    // than in the original expression
                    if stats.terminals[i] < simplified_stats.terminals[i] {
                        continue 'simplified;
                    }
                    if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
                        span_lint_and_then(self.0,
                                           LOGIC_BUG,
                                           e.span,
                                           "this boolean expression contains a logic bug",
                                           |db| {
                            db.span_help(h2q.terminals[i].span,
                                         "this expression can be optimized out by applying boolean operations to the \
                                          outer expression");
                            db.span_suggestion(e.span,
                                               "it would look like the following",
                                               suggest(self.0, suggestion, &h2q.terminals));
                        });
                        // don't also lint `NONMINIMAL_BOOL`
                        return;
                    }
                    // if the number of occurrences of a terminal decreases or any of the stats
                    // decreases while none increases
                    improvement |= (stats.terminals[i] > simplified_stats.terminals[i]) ||
                                   (stats.negations > simplified_stats.negations &&
                                    stats.ops == simplified_stats.ops) ||
                                   (stats.ops > simplified_stats.ops && stats.negations == simplified_stats.negations);
                }
                if improvement {
                    improvements.push(suggestion);
                }
            }
            if !improvements.is_empty() {
                span_lint_and_then(self.0,
                                   NONMINIMAL_BOOL,
                                   e.span,
                                   "this boolean expression can be simplified",
                                   |db| {
                    for suggestion in &improvements {
                        db.span_suggestion(e.span, "try", suggest(self.0, suggestion, &h2q.terminals));
                    }
                });
            }
        }
    }
}

impl<'a, 'v, 'tcx> Visitor<'v> for NonminimalBoolVisitor<'a, 'tcx> {
    fn visit_expr(&mut self, e: &'v Expr) {
        if in_macro(self.0, e.span) {
            return;
        }
        match e.node {
            ExprBinary(binop, _, _) if binop.node == BiOr || binop.node == BiAnd => self.bool_expr(e),
            ExprUnary(UnNot, ref inner) => {
                if self.0.tcx.node_types()[&inner.id].is_bool() {
                    self.bool_expr(e);
                } else {
                    walk_expr(self, e);
                }
            }
            _ => walk_expr(self, e),
        }
    }
}
<|endoftext|>


<commit_before>#![feature(test)]

extern crate gosol;
extern crate test;
extern crate rand;

use test::Bencher;
use gosol::game::simpleboard::*;
use gosol::game::board::*;
use rand::Rng;

#[bench]
fn bench_play_200_moves(b: &mut Bencher) {
    b.iter(|| {
        let mut game_board = SimpleBoard::new(19, 19);

        for i in (0..200) {
            let x = rand::thread_rng().gen_range(0, 19);
            let y = rand::thread_rng().gen_range(0, 19);

            if i % 2 == 0 {
                game_board.play_move(GoMove::Move{x: x, y: y, player: State::Black});
            }
            else {
                game_board.play_move(GoMove::Move{x: x, y: y, player: State::White});
            }
        }
    })
}

#[bench]
fn bench_play_10_moves_small(b: &mut Bencher) {
    b.iter(|| {
        let mut game_board = SimpleBoard::new(10, 10);

        for i in (0..10) {
            let x = rand::thread_rng().gen_range(0, 10);
            let y = rand::thread_rng().gen_range(0, 10);

            if i % 2 == 0 {
                game_board.play_move(GoMove::Move{x: x, y: y, player: State::Black});
            }
            else {
                game_board.play_move(GoMove::Move{x: x, y: y, player: State::White});
            }
        }
    })
}
<commit_msg>Fixed some warning in benches<commit_after>#![feature(test)]

extern crate gosol;
extern crate test;
extern crate rand;

use test::Bencher;
use gosol::game::simpleboard::*;
use gosol::game::board::*;
use rand::Rng;

#[bench]
fn bench_play_200_moves(b: &mut Bencher) {
    b.iter(|| {
        let mut game_board = SimpleBoard::new(19, 19);

        for i in 0..200 {
            let x = rand::thread_rng().gen_range(0, 19);
            let y = rand::thread_rng().gen_range(0, 19);

            if i % 2 == 0 {
                game_board.play_move(GoMove::Move{x: x, y: y, player: State::Black});
            }
            else {
                game_board.play_move(GoMove::Move{x: x, y: y, player: State::White});
            }
        }
    })
}

#[bench]
fn bench_play_10_moves_small(b: &mut Bencher) {
    b.iter(|| {
        let mut game_board = SimpleBoard::new(10, 10);

        for i in 0..10 {
            let x = rand::thread_rng().gen_range(0, 10);
            let y = rand::thread_rng().gen_range(0, 10);

            if i % 2 == 0 {
                game_board.play_move(GoMove::Move{x: x, y: y, player: State::Black});
            }
            else {
                game_board.play_move(GoMove::Move{x: x, y: y, player: State::White});
            }
        }
    })
}
<|endoftext|>