<commit_before>use apply_rewards::process_rewards_and_penalties;
use errors::EpochProcessingError as Error;
use process_slashings::process_slashings;
use registry_updates::process_registry_updates;
use std::collections::HashMap;
use tree_hash::TreeHash;
use types::*;
use validator_statuses::{TotalBalances, ValidatorStatuses};
use winning_root::{winning_root, WinningRoot};

pub mod apply_rewards;
pub mod errors;
pub mod process_slashings;
pub mod registry_updates;
pub mod tests;
pub mod validator_statuses;
pub mod winning_root;

/// Maps a shard to a winning root.
///
/// It is generated during crosslink processing and later used to reward/penalize validators.
pub type WinningRootHashSet = HashMap<u64, WinningRoot>;

/// Performs per-epoch processing on some BeaconState.
///
/// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is
/// returned, a state might be "half-processed" and therefore in an invalid state.
///
/// Spec v0.6.1
pub fn per_epoch_processing<T: EthSpec>(
    state: &mut BeaconState<T>,
    spec: &ChainSpec,
) -> Result<(), Error> {
    // Ensure the previous and next epoch caches are built.
    state.build_committee_cache(RelativeEpoch::Previous, spec)?;
    state.build_committee_cache(RelativeEpoch::Current, spec)?;

    // Load the struct we use to assign validators into sets based on their participation.
    //
    // E.g., attestation in the previous epoch, attested to the head, etc.
    let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
    validator_statuses.process_attestations(&state, spec)?;

    // Justification and finalization.
    process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;

    // Crosslinks.
    let winning_root_for_shards = process_crosslinks(state, spec)?;

    // Rewards and Penalities.
    process_rewards_and_penalties(
        state,
        &mut validator_statuses,
        &winning_root_for_shards,
        spec,
    )?;

    // Registry Updates.
    process_registry_updates(state, spec)?;

    // Slashings.
    process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?;

    // Final updates.
    process_final_updates(state, spec)?;

    // Rotate the epoch caches to suit the epoch transition.
    state.advance_caches();

    Ok(())
}

/// Update the following fields on the `BeaconState`:
///
/// - `justification_bitfield`.
/// - `previous_justified_epoch`
/// - `previous_justified_root`
/// - `current_justified_epoch`
/// - `current_justified_root`
/// - `finalized_epoch`
/// - `finalized_root`
///
/// Spec v0.6.1
pub fn process_justification_and_finalization<T: EthSpec>(
    state: &mut BeaconState<T>,
    total_balances: &TotalBalances,
    spec: &ChainSpec,
) -> Result<(), Error> {
    if state.current_epoch() == spec.genesis_epoch {
        return Ok(());
    }

    let previous_epoch = state.previous_epoch();
    let current_epoch = state.current_epoch();

    let old_previous_justified_epoch = state.previous_justified_epoch;
    let old_current_justified_epoch = state.current_justified_epoch;

    // Process justifications
    state.previous_justified_epoch = state.current_justified_epoch;
    state.previous_justified_root = state.current_justified_root;
    state.justification_bitfield <<= 1;

    if total_balances.previous_epoch_target_attesters * 3 >= total_balances.previous_epoch * 2 {
        state.current_justified_epoch = previous_epoch;
        state.current_justified_root =
            *state.get_block_root_at_epoch(state.current_justified_epoch, spec)?;
        state.justification_bitfield |= 2;
    }
    // If the current epoch gets justified, fill the last bit.
    if total_balances.current_epoch_target_attesters * 3 >= total_balances.current_epoch * 2 {
        state.current_justified_epoch = current_epoch;
        state.current_justified_root =
            *state.get_block_root_at_epoch(state.current_justified_epoch, spec)?;
        state.justification_bitfield |= 1;
    }

    let bitfield = state.justification_bitfield;

    // The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
    if (bitfield >> 1) % 8 == 0b111 && old_previous_justified_epoch == current_epoch - 3 {
        state.finalized_epoch = old_previous_justified_epoch;
        state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
    }
    // The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
    if (bitfield >> 1) % 4 == 0b11 && state.previous_justified_epoch == current_epoch - 2 {
        state.finalized_epoch = old_previous_justified_epoch;
        state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
    }
    // The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 2nd as source.
    if bitfield % 8 == 0b111 && state.current_justified_epoch == current_epoch - 2 {
        state.finalized_epoch = old_current_justified_epoch;
        state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
    }
    // The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
    if bitfield % 4 == 0b11 && state.current_justified_epoch == current_epoch - 1 {
        state.finalized_epoch = old_current_justified_epoch;
        state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
    }

    Ok(())
}

/// Updates the following fields on the `BeaconState`:
///
/// - `previous_crosslinks`
/// - `current_crosslinks`
///
/// Also returns a `WinningRootHashSet` for later use during epoch processing.
///
/// Spec v0.6.1
pub fn process_crosslinks<T: EthSpec>(
    state: &mut BeaconState<T>,
    spec: &ChainSpec,
) -> Result<WinningRootHashSet, Error> {
    let mut winning_root_for_shards: WinningRootHashSet = HashMap::new();

    state.previous_crosslinks = state.current_crosslinks.clone();

    for relative_epoch in vec![RelativeEpoch::Previous, RelativeEpoch::Current] {
        let epoch = relative_epoch.into_epoch(state.current_epoch());
        for offset in 0..state.get_epoch_committee_count(relative_epoch)? {
            let shard =
                (state.get_epoch_start_shard(relative_epoch)? + offset) % T::ShardCount::to_u64();
            let crosslink_committee =
                state.get_crosslink_committee_for_shard(shard, relative_epoch)?;

            let winning_root = winning_root(state, shard, epoch, spec)?;

            if let Some(winning_root) = winning_root {
                let total_committee_balance =
                    state.get_total_balance(&crosslink_committee.committee, spec)?;

                if 3 * winning_root.total_attesting_balance >= 2 * total_committee_balance {
                    state.current_crosslinks[shard as usize] = winning_root.crosslink.clone();
                }
                winning_root_for_shards.insert(shard, winning_root);
            }
        }
    }

    Ok(winning_root_for_shards)
}

/// Finish up an epoch update.
///
/// Spec v0.6.1
pub fn process_final_updates<T: EthSpec>(
    state: &mut BeaconState<T>,
    spec: &ChainSpec,
) -> Result<(), Error> {
    let current_epoch = state.current_epoch();
    let next_epoch = state.next_epoch();

    // Reset eth1 data votes.
    if (state.slot + 1) % spec.slots_per_eth1_voting_period == 0 {
        state.eth1_data_votes = vec![];
    }

    // Update effective balances with hysteresis (lag).
    for (index, validator) in state.validator_registry.iter_mut().enumerate() {
        let balance = state.balances[index];
        let half_increment = spec.effective_balance_increment / 2;
        if balance < validator.effective_balance
            || validator.effective_balance + 3 * half_increment < balance
        {
            validator.effective_balance = std::cmp::min(
                balance - balance % spec.effective_balance_increment,
                spec.max_effective_balance,
            );
        }
    }

    // Update start shard.
    state.latest_start_shard = state.next_epoch_start_shard()?;

    // This is a hack to allow us to update index roots and slashed balances for the next epoch.
    //
    // The indentation here is to make it obvious where the weird stuff happens.
    {
        state.slot += 1;

        // Set active index root
        let active_index_root = Hash256::from_slice(
            &state
                .get_active_validator_indices(next_epoch + spec.activation_exit_delay)
                .tree_hash_root()[..],
        );
        state.set_active_index_root(next_epoch, active_index_root, spec)?;

        // Set total slashed balances
        state.set_slashed_balance(next_epoch, state.get_slashed_balance(current_epoch)?)?;

        // Set randao mix
        state.set_randao_mix(next_epoch, *state.get_randao_mix(current_epoch)?)?;

        state.slot -= 1;
    }

    if next_epoch.as_u64() % (T::SlotsPerHistoricalRoot::to_u64() / spec.slots_per_epoch) == 0 {
        let historical_batch = state.historical_batch();
        state
            .historical_roots
            .push(Hash256::from_slice(&historical_batch.tree_hash_root()[..]));
    }

    // Rotate current/previous epoch attestations
    state.previous_epoch_attestations =
        std::mem::replace(&mut state.current_epoch_attestations, vec![]);

    Ok(())
}
<commit_msg>epoch proc: set correct active index root<commit_after>use apply_rewards::process_rewards_and_penalties;
use errors::EpochProcessingError as Error;
use process_slashings::process_slashings;
use registry_updates::process_registry_updates;
use std::collections::HashMap;
use tree_hash::TreeHash;
use types::*;
use validator_statuses::{TotalBalances, ValidatorStatuses};
use winning_root::{winning_root, WinningRoot};

pub mod apply_rewards;
pub mod errors;
pub mod process_slashings;
pub mod registry_updates;
pub mod tests;
pub mod validator_statuses;
pub mod winning_root;

/// Maps a shard to a winning root.
///
/// It is generated during crosslink processing and later used to reward/penalize validators.
pub type WinningRootHashSet = HashMap<u64, WinningRoot>;

/// Performs per-epoch processing on some BeaconState.
///
/// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is
/// returned, a state might be "half-processed" and therefore in an invalid state.
///
/// Spec v0.6.1
pub fn per_epoch_processing<T: EthSpec>(
    state: &mut BeaconState<T>,
    spec: &ChainSpec,
) -> Result<(), Error> {
    // Ensure the previous and next epoch caches are built.
    state.build_committee_cache(RelativeEpoch::Previous, spec)?;
    state.build_committee_cache(RelativeEpoch::Current, spec)?;

    // Load the struct we use to assign validators into sets based on their participation.
    //
    // E.g., attestation in the previous epoch, attested to the head, etc.
    let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
    validator_statuses.process_attestations(&state, spec)?;

    // Justification and finalization.
    process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;

    // Crosslinks.
    let winning_root_for_shards = process_crosslinks(state, spec)?;

    // Rewards and Penalities.
    process_rewards_and_penalties(
        state,
        &mut validator_statuses,
        &winning_root_for_shards,
        spec,
    )?;

    // Registry Updates.
    process_registry_updates(state, spec)?;

    // Slashings.
    process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?;

    // Final updates.
    process_final_updates(state, spec)?;

    // Rotate the epoch caches to suit the epoch transition.
    state.advance_caches();

    Ok(())
}

/// Update the following fields on the `BeaconState`:
///
/// - `justification_bitfield`.
/// - `previous_justified_epoch`
/// - `previous_justified_root`
/// - `current_justified_epoch`
/// - `current_justified_root`
/// - `finalized_epoch`
/// - `finalized_root`
///
/// Spec v0.6.1
pub fn process_justification_and_finalization<T: EthSpec>(
    state: &mut BeaconState<T>,
    total_balances: &TotalBalances,
    spec: &ChainSpec,
) -> Result<(), Error> {
    if state.current_epoch() == spec.genesis_epoch {
        return Ok(());
    }

    let previous_epoch = state.previous_epoch();
    let current_epoch = state.current_epoch();

    let old_previous_justified_epoch = state.previous_justified_epoch;
    let old_current_justified_epoch = state.current_justified_epoch;

    // Process justifications
    state.previous_justified_epoch = state.current_justified_epoch;
    state.previous_justified_root = state.current_justified_root;
    state.justification_bitfield <<= 1;

    if total_balances.previous_epoch_target_attesters * 3 >= total_balances.previous_epoch * 2 {
        state.current_justified_epoch = previous_epoch;
        state.current_justified_root =
            *state.get_block_root_at_epoch(state.current_justified_epoch, spec)?;
        state.justification_bitfield |= 2;
    }
    // If the current epoch gets justified, fill the last bit.
    if total_balances.current_epoch_target_attesters * 3 >= total_balances.current_epoch * 2 {
        state.current_justified_epoch = current_epoch;
        state.current_justified_root =
            *state.get_block_root_at_epoch(state.current_justified_epoch, spec)?;
        state.justification_bitfield |= 1;
    }

    let bitfield = state.justification_bitfield;

    // The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
    if (bitfield >> 1) % 8 == 0b111 && old_previous_justified_epoch == current_epoch - 3 {
        state.finalized_epoch = old_previous_justified_epoch;
        state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
    }
    // The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
    if (bitfield >> 1) % 4 == 0b11 && state.previous_justified_epoch == current_epoch - 2 {
        state.finalized_epoch = old_previous_justified_epoch;
        state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
    }
    // The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 2nd as source.
    if bitfield % 8 == 0b111 && state.current_justified_epoch == current_epoch - 2 {
        state.finalized_epoch = old_current_justified_epoch;
        state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
    }
    // The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
    if bitfield % 4 == 0b11 && state.current_justified_epoch == current_epoch - 1 {
        state.finalized_epoch = old_current_justified_epoch;
        state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
    }

    Ok(())
}

/// Updates the following fields on the `BeaconState`:
///
/// - `previous_crosslinks`
/// - `current_crosslinks`
///
/// Also returns a `WinningRootHashSet` for later use during epoch processing.
///
/// Spec v0.6.1
pub fn process_crosslinks<T: EthSpec>(
    state: &mut BeaconState<T>,
    spec: &ChainSpec,
) -> Result<WinningRootHashSet, Error> {
    let mut winning_root_for_shards: WinningRootHashSet = HashMap::new();

    state.previous_crosslinks = state.current_crosslinks.clone();

    for relative_epoch in vec![RelativeEpoch::Previous, RelativeEpoch::Current] {
        let epoch = relative_epoch.into_epoch(state.current_epoch());
        for offset in 0..state.get_epoch_committee_count(relative_epoch)? {
            let shard =
                (state.get_epoch_start_shard(relative_epoch)? + offset) % T::ShardCount::to_u64();
            let crosslink_committee =
                state.get_crosslink_committee_for_shard(shard, relative_epoch)?;

            let winning_root = winning_root(state, shard, epoch, spec)?;

            if let Some(winning_root) = winning_root {
                let total_committee_balance =
                    state.get_total_balance(&crosslink_committee.committee, spec)?;

                if 3 * winning_root.total_attesting_balance >= 2 * total_committee_balance {
                    state.current_crosslinks[shard as usize] = winning_root.crosslink.clone();
                }
                winning_root_for_shards.insert(shard, winning_root);
            }
        }
    }

    Ok(winning_root_for_shards)
}

/// Finish up an epoch update.
///
/// Spec v0.6.1
pub fn process_final_updates<T: EthSpec>(
    state: &mut BeaconState<T>,
    spec: &ChainSpec,
) -> Result<(), Error> {
    let current_epoch = state.current_epoch();
    let next_epoch = state.next_epoch();

    // Reset eth1 data votes.
    if (state.slot + 1) % spec.slots_per_eth1_voting_period == 0 {
        state.eth1_data_votes = vec![];
    }

    // Update effective balances with hysteresis (lag).
    for (index, validator) in state.validator_registry.iter_mut().enumerate() {
        let balance = state.balances[index];
        let half_increment = spec.effective_balance_increment / 2;
        if balance < validator.effective_balance
            || validator.effective_balance + 3 * half_increment < balance
        {
            validator.effective_balance = std::cmp::min(
                balance - balance % spec.effective_balance_increment,
                spec.max_effective_balance,
            );
        }
    }

    // Update start shard.
    state.latest_start_shard = state.next_epoch_start_shard()?;

    // This is a hack to allow us to update index roots and slashed balances for the next epoch.
    //
    // The indentation here is to make it obvious where the weird stuff happens.
    {
        state.slot += 1;

        // Set active index root
        let active_index_root = Hash256::from_slice(
            &state
                .get_active_validator_indices(next_epoch + spec.activation_exit_delay)
                .tree_hash_root()[..],
        );
        state.set_active_index_root(
            next_epoch + spec.activation_exit_delay,
            active_index_root,
            spec,
        )?;

        // Set total slashed balances
        state.set_slashed_balance(next_epoch, state.get_slashed_balance(current_epoch)?)?;

        // Set randao mix
        state.set_randao_mix(next_epoch, *state.get_randao_mix(current_epoch)?)?;

        state.slot -= 1;
    }

    if next_epoch.as_u64() % (T::SlotsPerHistoricalRoot::to_u64() / spec.slots_per_epoch) == 0 {
        let historical_batch = state.historical_batch();
        state
            .historical_roots
            .push(Hash256::from_slice(&historical_batch.tree_hash_root()[..]));
    }

    // Rotate current/previous epoch attestations
    state.previous_epoch_attestations =
        std::mem::replace(&mut state.current_epoch_attestations, vec![]);

    Ok(())
}
<|endoftext|>


<commit_before>use crate::prolog_parser::ast::*;

use crate::heap_print::*;
use crate::machine::*;
use crate::machine::compile::*;
use crate::machine::machine_errors::*;
use crate::machine::streams::*;

use std::convert::TryFrom;

impl Machine {
    pub(super) fn atom_tbl_of(&self, name: &ClauseName) -> TabledData<Atom> {
        match name {
            &ClauseName::User(ref rc) => rc.table.clone(),
            _ => self.indices.atom_tbl(),
        }
    }

    fn compile_into_machine(
        &mut self,
        src: Stream,
        name: ClauseName,
        arity: usize,
    ) -> EvalSession {
        match name.owning_module().as_str() {
            "user" => match self.indices.code_dir.get(&(name.clone(), arity)).cloned() {
                Some(idx) => {
                    let module = idx.0.borrow().1.clone();

                    match module.as_str() {
                        "user" => compile_user_module(self, src, true, ListingSource::User),
                        _ => compile_into_module(self, module, src, name)
                    }
                }
                None => compile_user_module(self, src, true, ListingSource::User),
            },
            _ => compile_into_module(self, name.owning_module(), src, name),
        }
    }

    fn get_predicate_key(&self, name: RegType, arity: RegType) -> PredicateKey {
        let name = self.machine_st[name].clone();
        let arity = self.machine_st[arity].clone();

        let name = match self.machine_st.store(self.machine_st.deref(name)) {
            Addr::Con(h) =>
                if let HeapCellValue::Atom(ref name, _) = &self.machine_st.heap[h] {
                    name.clone()
                } else {
                    unreachable!()
                },
            _ => unreachable!(),
        };

        let arity = match self.machine_st.store(self.machine_st.deref(arity)) {
            Addr::Con(h) => {
                match &self.machine_st.heap[h] {
                    HeapCellValue::Integer(ref arity) => {
                        arity.to_usize().unwrap()
                    }
                    HeapCellValue::Addr(Addr::Fixnum(arity)) => {
                        usize::try_from(*arity).unwrap()
                    }
                    _ => {
                        unreachable!()
                    }
                }
            }
            Addr::Usize(n) => {
                n
            }
            _ => {
                unreachable!()
            }
        };

        (name, arity)
    }

    fn print_new_dynamic_clause(
        &self,
        addrs: VecDeque<Addr>,
        name: ClauseName,
        arity: usize,
    ) -> String {
        let mut output = PrinterOutputter::new();
        output.append(format!(":- dynamic({}/{}). ", name.as_str(), arity).as_str());

        for addr in addrs {
            let mut printer = HCPrinter::new(&self.machine_st, &self.indices.op_dir, output);
            printer.quoted = true;

            output = printer.print(addr);
            output.append(". ");
        }

        output.result()
    }

    fn make_undefined(&mut self, name: ClauseName, arity: usize) {
        if let Some(idx) = self.indices.code_dir.get(&(name, arity)) {
            set_code_index!(idx, IndexPtr::DynamicUndefined, clause_name!("user"));
        }
    }

    fn make_undefined_in_module(&mut self, module_name: ClauseName, name: ClauseName, arity: usize) {
        if let Some(idx) = self.indices.code_dir.get(&(name, arity)) {
            if idx.module_name() == module_name {
                set_code_index!(idx, IndexPtr::DynamicUndefined, clause_name!("user"));
            }
        }
    }

    fn abolish_dynamic_clause(&mut self, name: RegType, arity: RegType) {
        let (name, arity) = self.get_predicate_key(name, arity);

        self.make_undefined(name.clone(), arity);

        self.indices.remove_code_index((name.clone(), arity));
        self.indices.remove_clause_subsection(name.owning_module(), name, arity);
    }

    fn abolish_dynamic_clause_in_module(&mut self, name: RegType, arity: RegType, module: RegType) {
        let (name, arity) = self.get_predicate_key(name, arity);
        let module_addr = self.machine_st[module].clone();

        let module_name = match self.machine_st.store(self.machine_st.deref(module_addr)) {
            Addr::Con(h) =>
                if let HeapCellValue::Atom(ref module, _) = &self.machine_st.heap[h] {
                    match self.indices.modules.get_mut(module) {
                        Some(ref mut module) => {
                            module.code_dir.remove(&(name.clone(), arity));
                            module.module_decl.name.clone()
                        }
                        _ => {
                            self.machine_st.fail = true;
                            return;
                        }
                    }
                } else {
                    unreachable!()
                },
            _ => unreachable!(),
        };

        self.make_undefined_in_module(module_name.clone(), name.clone(), arity);

        self.indices.remove_code_index((name.clone(), arity));
        self.indices.remove_clause_subsection(module_name, name, arity);
    }

    fn handle_eval_result_from_dynamic_compile(
        &mut self,
        pred_str: String,
        name: ClauseName,
        arity: usize,
        src: ClauseName,
    ) {
        let machine_st = mem::replace(&mut self.machine_st, MachineState::new());

        let result = self.compile_into_machine(
            Stream::from(pred_str),
            name,
            arity,
        );

        self.machine_st = machine_st;

        if let EvalSession::Error(err) = result {
            let h = self.machine_st.heap.h();
            let stub = MachineError::functor_stub(src, 1);
            let err = MachineError::session_error(h, err);
            let err = self.machine_st.error_form(err, stub);

            self.machine_st.throw_exception(err);
        }
    }

    fn recompile_dynamic_predicate_impl(
        &mut self,
        place: DynamicAssertPlace,
        name: ClauseName,
        arity: usize,
    ) {
        let stub = MachineError::functor_stub(place.predicate_name(), 1);
        let pred_str = match self.machine_st.try_from_list(temp_v!(2), stub) {
            Ok(addrs) => {
                let mut addrs = VecDeque::from(addrs);
                let added_clause = self.machine_st[temp_v!(1)].clone();

                place.push_to_queue(&mut addrs, added_clause);
                self.print_new_dynamic_clause(addrs, name.clone(), arity)
            }
            Err(err) => {
                return self.machine_st.throw_exception(err);
            }
        };

        self.handle_eval_result_from_dynamic_compile(
            pred_str,
            name,
            arity,
            place.predicate_name(),
        );
    }

    fn set_module_atom_tbl(&mut self, module_addr: Addr, name: &mut ClauseName) -> bool {
        let atom_tbl = match self.machine_st.store(self.machine_st.deref(module_addr)) {
            Addr::Con(h) =>
                if let HeapCellValue::Atom(ref module, _) = &self.machine_st.heap[h] {
                    match self.indices.modules.get(module) {
                        Some(ref module) => module.atom_tbl.clone(),
                        None => {
                            self.machine_st.fail = true;
                            return false;
                        }
                    }
                } else {
                    self.machine_st.fail = true;
                    return false;
                },
            _ => unreachable!(),
        };

        if let &mut ClauseName::User(ref mut rc) = name {
            rc.table = atom_tbl;
        }

        true
    }

    fn recompile_dynamic_predicate_in_module(&mut self, place: DynamicAssertPlace) {
        let (mut name, arity) = self.get_predicate_key(temp_v!(3), temp_v!(4));
        let module_addr = self.machine_st[temp_v!(5)].clone();

        if self.set_module_atom_tbl(module_addr, &mut name) {
            self.recompile_dynamic_predicate_impl(place, name, arity);
        }
    }

    fn recompile_dynamic_predicate(&mut self, place: DynamicAssertPlace) {
        let (name, arity) = self.get_predicate_key(temp_v!(3), temp_v!(4));
        self.recompile_dynamic_predicate_impl(place, name, arity);
    }

    fn retract_from_dynamic_predicate_in_module(&mut self) {
        let index = self.machine_st[temp_v!(3)].clone();
        let index = match self.machine_st.store(self.machine_st.deref(index)) {
            Addr::Con(h) =>
                match &self.machine_st.heap[h] {
                    HeapCellValue::Integer(ref arity) => {
                        arity.to_usize().unwrap()
                    }
                    HeapCellValue::Addr(Addr::Fixnum(arity)) => {
                        usize::try_from(*arity).unwrap()
                    }
                    _ => {
                        unreachable!()
                    }
                }
            _ => unreachable!(),
        };

        let (mut name, arity) = self.get_predicate_key(temp_v!(1), temp_v!(2));
        let module_addr = self.machine_st[temp_v!(5)].clone();

        if self.set_module_atom_tbl(module_addr, &mut name) {
            let stub = MachineError::functor_stub(clause_name!("retract"), 1);
            let pred_str = match self.machine_st.try_from_list(temp_v!(4), stub) {
                Ok(addrs) => {
                    let mut addrs = VecDeque::from(addrs);
                    addrs.remove(index);

                    if addrs.is_empty() {
                        self.make_undefined(name.clone(), arity);
                    }

                    self.print_new_dynamic_clause(addrs, name.clone(), arity)
                }
                Err(err) => {
                    return self.machine_st.throw_exception(err);
                }
            };

            self.handle_eval_result_from_dynamic_compile(
                pred_str,
                name,
                arity,
                clause_name!("retract"),
            );
        }
    }

    fn retract_from_dynamic_predicate(&mut self) {
        let index = self.machine_st[temp_v!(3)].clone();
        let index = match self.machine_st.store(self.machine_st.deref(index)) {
            Addr::Con(h) => {
                match &self.machine_st.heap[h] {
                    HeapCellValue::Integer(ref arity) => {
                        arity.to_usize().unwrap()
                    }
                    HeapCellValue::Addr(Addr::Fixnum(arity)) => {
                        usize::try_from(*arity).unwrap()
                    }
                    _ => {
                        unreachable!()
                    }
                }
            }
            Addr::Usize(n) => {
                n
            }
            Addr::Fixnum(n) => {
                usize::try_from(n).unwrap()
            }
            _ => {
                unreachable!()
            }
        };

        let (name, arity) = self.get_predicate_key(temp_v!(1), temp_v!(2));

        let stub = MachineError::functor_stub(clause_name!("retract"), 1);
        let pred_str = match self.machine_st.try_from_list(temp_v!(4), stub) {
            Ok(addrs) => {
                let mut addrs = VecDeque::from(addrs);
                addrs.remove(index);

                if addrs.is_empty() {
                    self.make_undefined(name.clone(), arity);
                }

                self.print_new_dynamic_clause(addrs, name.clone(), arity)
            }
            Err(err) => {
                return self.machine_st.throw_exception(err);
            }
        };

        self.handle_eval_result_from_dynamic_compile(
            pred_str,
            name,
            arity,
            clause_name!("retract"),
        );
    }

    pub(super) fn dynamic_transaction(
        &mut self,
        trans_type: DynamicTransactionType,
        p: LocalCodePtr,
    ) {
        match trans_type {
            DynamicTransactionType::Abolish => {
                self.abolish_dynamic_clause(temp_v!(1), temp_v!(2))
            }
            DynamicTransactionType::Assert(place) => {
                self.recompile_dynamic_predicate(place)
            }
            DynamicTransactionType::ModuleAbolish => {
                self.abolish_dynamic_clause_in_module(temp_v!(1), temp_v!(2), temp_v!(3))
            }
            DynamicTransactionType::ModuleAssert(place) => {
                self.recompile_dynamic_predicate_in_module(place)
            }
            DynamicTransactionType::ModuleRetract => {
                self.retract_from_dynamic_predicate_in_module()
            }
            DynamicTransactionType::Retract => {
                self.retract_from_dynamic_predicate()
            }
        }

        self.machine_st.p = CodePtr::Local(p);
    }
}
<commit_msg>cover Addr::Fixnum when not referenced as an Addr::Con, undefine dynamic predicate definitions from within modules too (#588)<commit_after>use crate::prolog_parser::ast::*;

use crate::heap_print::*;
use crate::machine::*;
use crate::machine::compile::*;
use crate::machine::machine_errors::*;
use crate::machine::streams::*;

use std::convert::TryFrom;

impl Machine {
    pub(super) fn atom_tbl_of(&self, name: &ClauseName) -> TabledData<Atom> {
        match name {
            &ClauseName::User(ref rc) => rc.table.clone(),
            _ => self.indices.atom_tbl(),
        }
    }

    fn compile_into_machine(
        &mut self,
        src: Stream,
        name: ClauseName,
        arity: usize,
    ) -> EvalSession {
        match name.owning_module().as_str() {
            "user" => match self.indices.code_dir.get(&(name.clone(), arity)).cloned() {
                Some(idx) => {
                    let module = idx.0.borrow().1.clone();

                    match module.as_str() {
                        "user" => compile_user_module(self, src, true, ListingSource::User),
                        _ => compile_into_module(self, module, src, name)
                    }
                }
                None => compile_user_module(self, src, true, ListingSource::User),
            },
            _ => compile_into_module(self, name.owning_module(), src, name),
        }
    }

    fn get_predicate_key(&self, name: RegType, arity: RegType) -> PredicateKey {
        let name = self.machine_st[name].clone();
        let arity = self.machine_st[arity].clone();

        let name = match self.machine_st.store(self.machine_st.deref(name)) {
            Addr::Con(h) =>
                if let HeapCellValue::Atom(ref name, _) = &self.machine_st.heap[h] {
                    name.clone()
                } else {
                    unreachable!()
                },
            _ => unreachable!(),
        };

        let arity = match self.machine_st.store(self.machine_st.deref(arity)) {
            Addr::Con(h) => {
                match &self.machine_st.heap[h] {
                    HeapCellValue::Integer(ref arity) => {
                        arity.to_usize().unwrap()
                    }
                    HeapCellValue::Addr(Addr::Fixnum(arity)) => {
                        usize::try_from(*arity).unwrap()
                    }
                    _ => {
                        unreachable!()
                    }
                }
            }
            Addr::Fixnum(arity) => {
                usize::try_from(arity).unwrap()
            }
            Addr::Usize(n) => {
                n
            }
            _ => {
                unreachable!()
            }
        };

        (name, arity)
    }

    fn print_new_dynamic_clause(
        &self,
        addrs: VecDeque<Addr>,
        name: ClauseName,
        arity: usize,
    ) -> String {
        let mut output = PrinterOutputter::new();
        output.append(format!(":- dynamic({}/{}). ", name.as_str(), arity).as_str());

        for addr in addrs {
            let mut printer = HCPrinter::new(&self.machine_st, &self.indices.op_dir, output);
            printer.quoted = true;

            output = printer.print(addr);
            output.append(". ");
        }

        output.result()
    }

    fn make_undefined(&mut self, name: ClauseName, arity: usize) {
        let module_name = name.owning_module();

        match self.indices.modules.get(&module_name) {
            Some(ref module) => {
                if let Some(idx) = module.code_dir.get(&(name.clone(), arity)) {
                    set_code_index!(idx, IndexPtr::DynamicUndefined, module_name);
                }
            }
            None => {
            }
        }

        if let Some(idx) = self.indices.code_dir.get(&(name, arity)) {
            set_code_index!(idx, IndexPtr::DynamicUndefined, clause_name!("user"));
        }
    }

    fn make_undefined_in_module(&mut self, module_name: ClauseName, name: ClauseName, arity: usize) {
        if let Some(idx) = self.indices.code_dir.get(&(name, arity)) {
            if idx.module_name() == module_name {
                set_code_index!(idx, IndexPtr::DynamicUndefined, clause_name!("user"));
            }
        }
    }

    fn abolish_dynamic_clause(&mut self, name: RegType, arity: RegType) {
        let (name, arity) = self.get_predicate_key(name, arity);

        self.make_undefined(name.clone(), arity);

        self.indices.remove_code_index((name.clone(), arity));
        self.indices.remove_clause_subsection(name.owning_module(), name, arity);
    }

    fn abolish_dynamic_clause_in_module(&mut self, name: RegType, arity: RegType, module: RegType) {
        let (name, arity) = self.get_predicate_key(name, arity);
        let module_addr = self.machine_st[module].clone();

        let module_name = match self.machine_st.store(self.machine_st.deref(module_addr)) {
            Addr::Con(h) =>
                if let HeapCellValue::Atom(ref module, _) = &self.machine_st.heap[h] {
                    match self.indices.modules.get_mut(module) {
                        Some(ref mut module) => {
                            module.code_dir.remove(&(name.clone(), arity));
                            module.module_decl.name.clone()
                        }
                        _ => {
                            self.machine_st.fail = true;
                            return;
                        }
                    }
                } else {
                    unreachable!()
                },
            _ => unreachable!(),
        };

        self.make_undefined_in_module(module_name.clone(), name.clone(), arity);

        self.indices.remove_code_index((name.clone(), arity));
        self.indices.remove_clause_subsection(module_name, name, arity);
    }

    fn handle_eval_result_from_dynamic_compile(
        &mut self,
        pred_str: String,
        name: ClauseName,
        arity: usize,
        src: ClauseName,
    ) {
        let machine_st = mem::replace(&mut self.machine_st, MachineState::new());

        let result = self.compile_into_machine(
            Stream::from(pred_str),
            name,
            arity,
        );

        self.machine_st = machine_st;

        if let EvalSession::Error(err) = result {
            let h = self.machine_st.heap.h();
            let stub = MachineError::functor_stub(src, 1);
            let err = MachineError::session_error(h, err);
            let err = self.machine_st.error_form(err, stub);

            self.machine_st.throw_exception(err);
        }
    }

    fn recompile_dynamic_predicate_impl(
        &mut self,
        place: DynamicAssertPlace,
        name: ClauseName,
        arity: usize,
    ) {
        let stub = MachineError::functor_stub(place.predicate_name(), 1);
        let pred_str = match self.machine_st.try_from_list(temp_v!(2), stub) {
            Ok(addrs) => {
                let mut addrs = VecDeque::from(addrs);
                let added_clause = self.machine_st[temp_v!(1)].clone();

                place.push_to_queue(&mut addrs, added_clause);
                self.print_new_dynamic_clause(addrs, name.clone(), arity)
            }
            Err(err) => {
                return self.machine_st.throw_exception(err);
            }
        };

        self.handle_eval_result_from_dynamic_compile(
            pred_str,
            name,
            arity,
            place.predicate_name(),
        );
    }

    fn set_module_atom_tbl(&mut self, module_addr: Addr, name: &mut ClauseName) -> bool {
        let atom_tbl = match self.machine_st.store(self.machine_st.deref(module_addr)) {
            Addr::Con(h) =>
                if let HeapCellValue::Atom(ref module, _) = &self.machine_st.heap[h] {
                    match self.indices.modules.get(module) {
                        Some(ref module) => module.atom_tbl.clone(),
                        None => {
                            self.machine_st.fail = true;
                            return false;
                        }
                    }
                } else {
                    self.machine_st.fail = true;
                    return false;
                },
            _ => unreachable!(),
        };

        if let &mut ClauseName::User(ref mut rc) = name {
            rc.table = atom_tbl;
        }

        true
    }

    fn recompile_dynamic_predicate_in_module(&mut self, place: DynamicAssertPlace) {
        let (mut name, arity) = self.get_predicate_key(temp_v!(3), temp_v!(4));
        let module_addr = self.machine_st[temp_v!(5)].clone();

        if self.set_module_atom_tbl(module_addr, &mut name) {
            self.recompile_dynamic_predicate_impl(place, name, arity);
        }
    }

    fn recompile_dynamic_predicate(&mut self, place: DynamicAssertPlace) {
        let (name, arity) = self.get_predicate_key(temp_v!(3), temp_v!(4));
        self.recompile_dynamic_predicate_impl(place, name, arity);
    }

    fn retract_from_dynamic_predicate_in_module(&mut self) {
        let index = self.machine_st[temp_v!(3)].clone();
        let index = match self.machine_st.store(self.machine_st.deref(index)) {
            Addr::Con(h) =>
                match &self.machine_st.heap[h] {
                    HeapCellValue::Integer(ref arity) => {
                        arity.to_usize().unwrap()
                    }
                    HeapCellValue::Addr(Addr::Fixnum(arity)) => {
                        usize::try_from(*arity).unwrap()
                    }
                    _ => {
                        unreachable!()
                    }
                }
            Addr::Fixnum(arity) => {
                usize::try_from(arity).unwrap()
            }
            _ => {
                unreachable!()
            }
        };

        let (mut name, arity) = self.get_predicate_key(temp_v!(1), temp_v!(2));
        let module_addr = self.machine_st[temp_v!(5)].clone();

        if self.set_module_atom_tbl(module_addr, &mut name) {
            let stub = MachineError::functor_stub(clause_name!("retract"), 1);
            let pred_str = match self.machine_st.try_from_list(temp_v!(4), stub) {
                Ok(addrs) => {
                    let mut addrs = VecDeque::from(addrs);
                    addrs.remove(index);

                    if addrs.is_empty() {
                        self.make_undefined(name.clone(), arity);
                    }

                    self.print_new_dynamic_clause(addrs, name.clone(), arity)
                }
                Err(err) => {
                    return self.machine_st.throw_exception(err);
                }
            };

            self.handle_eval_result_from_dynamic_compile(
                pred_str,
                name,
                arity,
                clause_name!("retract"),
            );
        }
    }

    fn retract_from_dynamic_predicate(&mut self) {
        let index = self.machine_st[temp_v!(3)].clone();
        let index = match self.machine_st.store(self.machine_st.deref(index)) {
            Addr::Con(h) => {
                match &self.machine_st.heap[h] {
                    HeapCellValue::Integer(ref arity) => {
                        arity.to_usize().unwrap()
                    }
                    HeapCellValue::Addr(Addr::Fixnum(arity)) => {
                        usize::try_from(*arity).unwrap()
                    }
                    _ => {
                        unreachable!()
                    }
                }
            }
            Addr::Usize(n) => {
                n
            }
            Addr::Fixnum(n) => {
                usize::try_from(n).unwrap()
            }
            _ => {
                unreachable!()
            }
        };

        let (name, arity) = self.get_predicate_key(temp_v!(1), temp_v!(2));

        let stub = MachineError::functor_stub(clause_name!("retract"), 1);
        let pred_str = match self.machine_st.try_from_list(temp_v!(4), stub) {
            Ok(addrs) => {
                let mut addrs = VecDeque::from(addrs);
                addrs.remove(index);

                if addrs.is_empty() {
                    self.make_undefined(name.clone(), arity);
                }

                self.print_new_dynamic_clause(addrs, name.clone(), arity)
            }
            Err(err) => {
                return self.machine_st.throw_exception(err);
            }
        };

        self.handle_eval_result_from_dynamic_compile(
            pred_str,
            name,
            arity,
            clause_name!("retract"),
        );
    }

    pub(super) fn dynamic_transaction(
        &mut self,
        trans_type: DynamicTransactionType,
        p: LocalCodePtr,
    ) {
        match trans_type {
            DynamicTransactionType::Abolish => {
                self.abolish_dynamic_clause(temp_v!(1), temp_v!(2))
            }
            DynamicTransactionType::Assert(place) => {
                self.recompile_dynamic_predicate(place)
            }
            DynamicTransactionType::ModuleAbolish => {
                self.abolish_dynamic_clause_in_module(temp_v!(1), temp_v!(2), temp_v!(3))
            }
            DynamicTransactionType::ModuleAssert(place) => {
                self.recompile_dynamic_predicate_in_module(place)
            }
            DynamicTransactionType::ModuleRetract => {
                self.retract_from_dynamic_predicate_in_module()
            }
            DynamicTransactionType::Retract => {
                self.retract_from_dynamic_predicate()
            }
        }

        self.machine_st.p = CodePtr::Local(p);
    }
}
<|endoftext|>


<commit_before>use rustc::hir::intravisit as visit;
use rustc::hir::{self, *};
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::ty::layout::LayoutOf;
use rustc::ty::{self, Ty};
use rustc::util::nodemap::HirIdSet;
use rustc::{declare_tool_lint, impl_lint_pass};
use rustc_typeck::expr_use_visitor::*;
use syntax::source_map::Span;

use crate::utils::span_lint;

#[derive(Copy, Clone)]
pub struct BoxedLocal {
    pub too_large_for_stack: u64,
}

declare_clippy_lint! {
    /// **What it does:** Checks for usage of `Box<T>` where an unboxed `T` would
    /// work fine.
    ///
    /// **Why is this bad?** This is an unnecessary allocation, and bad for
    /// performance. It is only necessary to allocate if you wish to move the box
    /// into something.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// # fn foo(bar: usize) {}
    /// let x = Box::new(1);
    /// foo(*x);
    /// println!("{}", *x);
    /// ```
    pub BOXED_LOCAL,
    perf,
    "using `Box<T>` where unnecessary"
}

fn is_non_trait_box(ty: Ty<'_>) -> bool {
    ty.is_box() && !ty.boxed_ty().is_trait()
}

struct EscapeDelegate<'a, 'tcx> {
    cx: &'a LateContext<'a, 'tcx>,
    set: HirIdSet,
    too_large_for_stack: u64,
}

impl_lint_pass!(BoxedLocal => [BOXED_LOCAL]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BoxedLocal {
    fn check_fn(
        &mut self,
        cx: &LateContext<'a, 'tcx>,
        _: visit::FnKind<'tcx>,
        _: &'tcx FnDecl,
        body: &'tcx Body,
        _: Span,
        hir_id: HirId,
    ) {
        // If the method is an impl for a trait, don't warn.
        let parent_id = cx.tcx.hir().get_parent_item(hir_id);
        let parent_node = cx.tcx.hir().find(parent_id);

        if let Some(Node::Item(item)) = parent_node {
            if let ItemKind::Impl(_, _, _, _, Some(..), _, _) = item.kind {
                return;
            }
        }

        let mut v = EscapeDelegate {
            cx,
            set: HirIdSet::default(),
            too_large_for_stack: self.too_large_for_stack,
        };

        let fn_def_id = cx.tcx.hir().local_def_id(hir_id);
        cx.tcx.infer_ctxt().enter(|infcx| {
            ExprUseVisitor::new(&mut v, &infcx, fn_def_id, cx.param_env, cx.tables).consume_body(body);
        });

        for node in v.set {
            span_lint(
                cx,
                BOXED_LOCAL,
                cx.tcx.hir().span(node),
                "local variable doesn't need to be boxed here",
            );
        }
    }
}

// TODO: Replace with Map::is_argument(..) when it's fixed
fn is_argument(map: &hir::map::Map<'_>, id: HirId) -> bool {
    match map.find(id) {
        Some(Node::Binding(_)) => (),
        _ => return false,
    }

    match map.find(map.get_parent_node(id)) {
        Some(Node::Param(_)) => true,
        _ => false,
    }
}

impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> {
    fn consume(&mut self, cmt: &Place<'tcx>, mode: ConsumeMode) {
        if let PlaceBase::Local(lid) = cmt.base {
            if let ConsumeMode::Move = mode {
                // moved out or in. clearly can't be localized
                self.set.remove(&lid);
            }
        }
        let map = &self.cx.tcx.hir();
        if let PlaceBase::Local(lid) = cmt.base {
            if let Some(Node::Binding(_)) = map.find(cmt.hir_id) {
                if self.set.contains(&lid) {
                    // let y = x where x is known
                    // remove x, insert y
                    self.set.insert(cmt.hir_id);
                    self.set.remove(&lid);
                }
            }
        }
    }

    fn borrow(&mut self, cmt: &Place<'tcx>, _: ty::BorrowKind) {
        if let PlaceBase::Local(lid) = cmt.base {
            self.set.remove(&lid);
        }
    }

    fn mutate(&mut self, cmt: &Place<'tcx>) {
        let map = &self.cx.tcx.hir();
        if is_argument(map, cmt.hir_id) {
            // Skip closure arguments
            let parent_id = map.get_parent_node(cmt.hir_id);
            if let Some(Node::Expr(..)) = map.find(map.get_parent_node(parent_id)) {
                return;
            }

            if is_non_trait_box(cmt.ty) && !self.is_large_box(cmt.ty) {
                self.set.insert(cmt.hir_id);
            }
            return;
        }
    }
}

impl<'a, 'tcx> EscapeDelegate<'a, 'tcx> {
    fn is_large_box(&self, ty: Ty<'tcx>) -> bool {
        // Large types need to be boxed to avoid stack overflows.
        if ty.is_box() {
            self.cx.layout_of(ty.boxed_ty()).map_or(0, |l| l.size.bytes()) > self.too_large_for_stack
        } else {
            false
        }
    }
}
<commit_msg>Add projections check to EUV for escape analysis<commit_after>use rustc::hir::intravisit as visit;
use rustc::hir::{self, *};
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::ty::layout::LayoutOf;
use rustc::ty::{self, Ty};
use rustc::util::nodemap::HirIdSet;
use rustc::{declare_tool_lint, impl_lint_pass};
use rustc_typeck::expr_use_visitor::*;
use syntax::source_map::Span;

use crate::utils::span_lint;

#[derive(Copy, Clone)]
pub struct BoxedLocal {
    pub too_large_for_stack: u64,
}

declare_clippy_lint! {
    /// **What it does:** Checks for usage of `Box<T>` where an unboxed `T` would
    /// work fine.
    ///
    /// **Why is this bad?** This is an unnecessary allocation, and bad for
    /// performance. It is only necessary to allocate if you wish to move the box
    /// into something.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// # fn foo(bar: usize) {}
    /// let x = Box::new(1);
    /// foo(*x);
    /// println!("{}", *x);
    /// ```
    pub BOXED_LOCAL,
    perf,
    "using `Box<T>` where unnecessary"
}

fn is_non_trait_box(ty: Ty<'_>) -> bool {
    ty.is_box() && !ty.boxed_ty().is_trait()
}

struct EscapeDelegate<'a, 'tcx> {
    cx: &'a LateContext<'a, 'tcx>,
    set: HirIdSet,
    too_large_for_stack: u64,
}

impl_lint_pass!(BoxedLocal => [BOXED_LOCAL]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for BoxedLocal {
    fn check_fn(
        &mut self,
        cx: &LateContext<'a, 'tcx>,
        _: visit::FnKind<'tcx>,
        _: &'tcx FnDecl,
        body: &'tcx Body,
        _: Span,
        hir_id: HirId,
    ) {
        // If the method is an impl for a trait, don't warn.
        let parent_id = cx.tcx.hir().get_parent_item(hir_id);
        let parent_node = cx.tcx.hir().find(parent_id);

        if let Some(Node::Item(item)) = parent_node {
            if let ItemKind::Impl(_, _, _, _, Some(..), _, _) = item.kind {
                return;
            }
        }

        let mut v = EscapeDelegate {
            cx,
            set: HirIdSet::default(),
            too_large_for_stack: self.too_large_for_stack,
        };

        let fn_def_id = cx.tcx.hir().local_def_id(hir_id);
        cx.tcx.infer_ctxt().enter(|infcx| {
            ExprUseVisitor::new(&mut v, &infcx, fn_def_id, cx.param_env, cx.tables).consume_body(body);
        });

        for node in v.set {
            span_lint(
                cx,
                BOXED_LOCAL,
                cx.tcx.hir().span(node),
                "local variable doesn't need to be boxed here",
            );
        }
    }
}

// TODO: Replace with Map::is_argument(..) when it's fixed
fn is_argument(map: &hir::map::Map<'_>, id: HirId) -> bool {
    match map.find(id) {
        Some(Node::Binding(_)) => (),
        _ => return false,
    }

    match map.find(map.get_parent_node(id)) {
        Some(Node::Param(_)) => true,
        _ => false,
    }
}

impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> {
    fn consume(&mut self, cmt: &Place<'tcx>, mode: ConsumeMode) {
        if cmt.projections.is_empty() {
            if let PlaceBase::Local(lid) = cmt.base {
                if let ConsumeMode::Move = mode {
                    // moved out or in. clearly can't be localized
                    self.set.remove(&lid);
                }
                let map = &self.cx.tcx.hir();
                if let Some(Node::Binding(_)) = map.find(cmt.hir_id) {
                    if self.set.contains(&lid) {
                        // let y = x where x is known
                        // remove x, insert y
                        self.set.insert(cmt.hir_id);
                        self.set.remove(&lid);
                    }
                }
            }
        }
    }

    fn borrow(&mut self, cmt: &Place<'tcx>, _: ty::BorrowKind) {
        if cmt.projections.is_empty() {
            if let PlaceBase::Local(lid) = cmt.base {
                self.set.remove(&lid);
            }
        }
    }

    fn mutate(&mut self, cmt: &Place<'tcx>) {
        if cmt.projections.is_empty() {
            let map = &self.cx.tcx.hir();
            if is_argument(map, cmt.hir_id) {
                // Skip closure arguments
                let parent_id = map.get_parent_node(cmt.hir_id);
                if let Some(Node::Expr(..)) = map.find(map.get_parent_node(parent_id)) {
                    return;
                }

                if is_non_trait_box(cmt.ty) && !self.is_large_box(cmt.ty) {
                    self.set.insert(cmt.hir_id);
                }
                return;
            }
        }
    }
}

impl<'a, 'tcx> EscapeDelegate<'a, 'tcx> {
    fn is_large_box(&self, ty: Ty<'tcx>) -> bool {
        // Large types need to be boxed to avoid stack overflows.
        if ty.is_box() {
            self.cx.layout_of(ty.boxed_ty()).map_or(0, |l| l.size.bytes()) > self.too_large_for_stack
        } else {
            false
        }
    }
}
<|endoftext|>


<commit_before>//! rev4 of the verification dataset from HSLuv
//! https://github.com/hsluv/hsluv/blob/master/snapshots/snapshot-rev4.json

use approx::assert_relative_eq;
use lazy_static::lazy_static;
use serde_json;

use palette::convert::IntoColorUnclamped;
use palette::white_point::D65;
use palette::{Hsluv, Lchuv, Luv, LuvHue, Xyz};
use std::collections::HashMap;

#[derive(Clone, Debug)]
struct HsluvExample {
    name: String,
    lchuv: Lchuv<D65, f64>,
    hsluv: Hsluv<D65, f64>,
    luv: Luv<D65, f64>,
    xyz: Xyz<D65, f64>,
}

type Examples = HashMap<String, HsluvExample>;

fn load_data() -> Examples {
    let filename = "tests/hsluv_dataset/hsluv_dataset.json";
    let data_str = std::fs::read_to_string(filename).unwrap();
    let raw_data: serde_json::Value = serde_json::from_str(&data_str).unwrap();

    let m = raw_data.as_object().expect("failed to parse dataset");
    let to_vec = |c: &serde_json::Value| {
        c.as_array()
            .unwrap()
            .iter()
            .flat_map(|x| x.as_f64())
            .collect()
    };

    m.iter()
        .map(|(k, v)| {
            let colors = v.as_object().unwrap();
            let luv_data: Vec<f64> = to_vec(&colors["luv"]);
            let lchuv_data: Vec<f64> = to_vec(&colors["lch"]);
            let hsluv_data: Vec<f64> = to_vec(&colors["hsluv"]);
            let xyz_data: Vec<f64> = to_vec(&colors["xyz"]);

            (
                k.clone(),
                HsluvExample {
                    name: k.clone(),
                    luv: Luv::new(luv_data[0], luv_data[1], luv_data[2]),
                    hsluv: Hsluv::new(hsluv_data[0], hsluv_data[1], hsluv_data[2]),
                    lchuv: Lchuv::new(lchuv_data[0], lchuv_data[1], lchuv_data[2]),
                    xyz: Xyz::new(xyz_data[0], xyz_data[1], xyz_data[2]),
                },
            )
        })
        .collect()
}

lazy_static! {
    static ref TEST_DATA: Examples = load_data();
}

#[test]
pub fn run_xyz_to_luv_tests() {
    for (_, v) in TEST_DATA.iter() {
        let to_luv: Luv<D65, f64> = v.xyz.into_color_unclamped();
        assert_relative_eq!(to_luv, v.luv, epsilon = 0.1);
    }
}

#[test]
pub fn run_luv_to_xyz_tests() {
    for (_, v) in TEST_DATA.iter() {
        let to_xyz: Xyz<D65, f64> = v.luv.into_color_unclamped();
        assert_relative_eq!(to_xyz, v.xyz, epsilon = 0.001);
    }
}

#[test]
pub fn run_lchuv_to_luv_tests() {
    for (_, v) in TEST_DATA.iter() {
        let to_luv: Luv<D65, f64> = v.lchuv.into_color_unclamped();
        assert_relative_eq!(to_luv, v.luv, epsilon = 0.001);
    }
}

#[test]
pub fn run_luv_to_lchuv_tests() {
    for (_, v) in TEST_DATA.iter() {
        let mut to_lchuv: Lchuv<D65, f64> = v.luv.into_color_unclamped();
        if to_lchuv.chroma < 1e-8 {
            to_lchuv.hue = LuvHue::from_degrees(0.0);
        }
        assert_relative_eq!(to_lchuv, v.lchuv, epsilon = 0.001);
    }
}

#[test]
pub fn run_lchuv_to_hsluv_tests() {
    for (_, v) in TEST_DATA.iter() {
        let mut to_hsluv: Hsluv<D65, f64> = v.lchuv.into_color_unclamped();
        if to_hsluv.l > 100.0 - 1e-5 {
            to_hsluv.saturation = 0.0;
        }
        assert_relative_eq!(to_hsluv, v.hsluv, epsilon = 1e-5);
    }
}

#[test]
pub fn run_hsluv_to_lchuv_tests() {
    for (_, v) in TEST_DATA.iter() {
        let to_lchuv: Lchuv<D65, f64> = v.hsluv.into_color_unclamped();
        assert_relative_eq!(to_lchuv, v.lchuv, epsilon = 1e-5);
    }
}
<commit_msg>Remove unused dataset name<commit_after>//! rev4 of the verification dataset from HSLuv
//! https://github.com/hsluv/hsluv/blob/master/snapshots/snapshot-rev4.json

use approx::assert_relative_eq;
use lazy_static::lazy_static;
use serde_json;

use palette::convert::IntoColorUnclamped;
use palette::white_point::D65;
use palette::{Hsluv, Lchuv, Luv, LuvHue, Xyz};
use std::collections::HashMap;

#[derive(Clone, Debug)]
struct HsluvExample {
    lchuv: Lchuv<D65, f64>,
    hsluv: Hsluv<D65, f64>,
    luv: Luv<D65, f64>,
    xyz: Xyz<D65, f64>,
}

type Examples = HashMap<String, HsluvExample>;

fn load_data() -> Examples {
    let filename = "tests/hsluv_dataset/hsluv_dataset.json";
    let data_str = std::fs::read_to_string(filename).unwrap();
    let raw_data: serde_json::Value = serde_json::from_str(&data_str).unwrap();

    let m = raw_data.as_object().expect("failed to parse dataset");
    let to_vec = |c: &serde_json::Value| {
        c.as_array()
            .unwrap()
            .iter()
            .flat_map(|x| x.as_f64())
            .collect()
    };

    m.iter()
        .map(|(k, v)| {
            let colors = v.as_object().unwrap();
            let luv_data: Vec<f64> = to_vec(&colors["luv"]);
            let lchuv_data: Vec<f64> = to_vec(&colors["lch"]);
            let hsluv_data: Vec<f64> = to_vec(&colors["hsluv"]);
            let xyz_data: Vec<f64> = to_vec(&colors["xyz"]);

            (
                k.clone(),
                HsluvExample {
                    luv: Luv::new(luv_data[0], luv_data[1], luv_data[2]),
                    hsluv: Hsluv::new(hsluv_data[0], hsluv_data[1], hsluv_data[2]),
                    lchuv: Lchuv::new(lchuv_data[0], lchuv_data[1], lchuv_data[2]),
                    xyz: Xyz::new(xyz_data[0], xyz_data[1], xyz_data[2]),
                },
            )
        })
        .collect()
}

lazy_static! {
    static ref TEST_DATA: Examples = load_data();
}

#[test]
pub fn run_xyz_to_luv_tests() {
    for (_, v) in TEST_DATA.iter() {
        let to_luv: Luv<D65, f64> = v.xyz.into_color_unclamped();
        assert_relative_eq!(to_luv, v.luv, epsilon = 0.1);
    }
}

#[test]
pub fn run_luv_to_xyz_tests() {
    for (_, v) in TEST_DATA.iter() {
        let to_xyz: Xyz<D65, f64> = v.luv.into_color_unclamped();
        assert_relative_eq!(to_xyz, v.xyz, epsilon = 0.001);
    }
}

#[test]
pub fn run_lchuv_to_luv_tests() {
    for (_, v) in TEST_DATA.iter() {
        let to_luv: Luv<D65, f64> = v.lchuv.into_color_unclamped();
        assert_relative_eq!(to_luv, v.luv, epsilon = 0.001);
    }
}

#[test]
pub fn run_luv_to_lchuv_tests() {
    for (_, v) in TEST_DATA.iter() {
        let mut to_lchuv: Lchuv<D65, f64> = v.luv.into_color_unclamped();
        if to_lchuv.chroma < 1e-8 {
            to_lchuv.hue = LuvHue::from_degrees(0.0);
        }
        assert_relative_eq!(to_lchuv, v.lchuv, epsilon = 0.001);
    }
}

#[test]
pub fn run_lchuv_to_hsluv_tests() {
    for (_, v) in TEST_DATA.iter() {
        let mut to_hsluv: Hsluv<D65, f64> = v.lchuv.into_color_unclamped();
        if to_hsluv.l > 100.0 - 1e-5 {
            to_hsluv.saturation = 0.0;
        }
        assert_relative_eq!(to_hsluv, v.hsluv, epsilon = 1e-5);
    }
}

#[test]
pub fn run_hsluv_to_lchuv_tests() {
    for (_, v) in TEST_DATA.iter() {
        let to_lchuv: Lchuv<D65, f64> = v.hsluv.into_color_unclamped();
        assert_relative_eq!(to_lchuv, v.lchuv, epsilon = 1e-5);
    }
}
<|endoftext|>


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

use std::boxed::Box;
use std::env;
use std::fs::File;
use std::io::Write;
use std::ops::Deref;
use std::panic;
use std::path::Path;
use std::str::FromStr;
use std::time::Duration;

use minidump::*;
use minidump_processor::{
    http_symbol_supplier, simple_symbol_supplier, MultiSymbolProvider, ProcessorOptions, Symbolizer,
};

use clap::{crate_version, App, AppSettings, Arg, ArgGroup};
use log::error;
use simplelog::{
    ColorChoice, ConfigBuilder, Level, LevelFilter, TermLogger, TerminalMode, WriteLogger,
};

#[cfg_attr(test, allow(dead_code))]
fn main() {
    let matches = App::new("minidump-stackwalk")
        .version(crate_version!())
        .about("Analyzes minidumps and produces a report (either human-readable or JSON).")
        .setting(AppSettings::NextLineHelp)
        .setting(AppSettings::ColoredHelp)
        .setting(AppSettings::DeriveDisplayOrder)
        .usage("minidump-stackwalk [FLAGS] [OPTIONS] <minidump> [--] [symbols-path]...")
        .arg(
            Arg::with_name("json")
                .long("json")
                .long_help("Emit a machine-readable JSON report (the default).

Official schema TBD, but the output *is* stabilized. That said, sometimes values can't be computed \
(e.g. because the minidump doesn't contain that information), so nearly every value \
*can* be null. And of course, we reserve the right to introduce new fields/values \
for new features.

In general we try to emit an explicit `null` for a field that *can* appear in the JSON but \
doesn't, so every JSON report is *kind of* reporting the current schema (but of course, \
missing the possible values and their meanings).\n\n\n")
        )
        .arg(
            Arg::with_name("human")
                .long("human")
                .long_help("Emit a human-readable report.

The human-readable report does not have a specified format, and may not have as \
many details as the default JSON format. It is intended for quickly inspecting \
a crash or debugging rust-minidump itself.\n\n\n")
        )
        .arg(
            Arg::with_name("cyborg")
                .long("cyborg")
                .takes_value(true)
                .long_help("Combine --human and --json

Because this creates two output streams, you must specify a path to write the --json
output to. The --human output will be the 'primary' output and default to stdout, which
can be configured with --output-file as normal.\n\n\n")
        )
        .group(ArgGroup::with_name("output-format")
            .args(&["json", "human", "cyborg"])
        )
        .arg(
            Arg::with_name("output-file")
                .long("output-file")
                .takes_value(true)
                .help("Where to write the output to (if unspecified, stdout is used)")
        )
        .arg(
            Arg::with_name("log-file")
                .long("log-file")
                .takes_value(true)
                .help("Where to write logs to (if unspecified, stderr is used)")
        )
        .arg(
            Arg::with_name("verbose")
                .long("verbose")
                .possible_values(&["off", "error", "warn", "info", "debug", "trace"])
                .default_value("error")
                .takes_value(true)
                .long_help("Set the logging level.

The unwinder has been heavily instrumented with `trace` logging, so if you want to debug why \
an unwind happened the way it did, --verbose=trace is very useful (all unwinder logging will \
be prefixed with `unwind:`).\n\n\n")
        )
        .arg(
            Arg::with_name("pretty")
                .long("pretty")
                .help("Pretty-print --json output.")
        )
        .arg(
            Arg::with_name("brief")
                .long("brief")
                .help("Provide a briefer --human report.

Only provides the top-level summary and a backtrace of the crashing thread.")
        )
        .arg(
            Arg::with_name("raw-json")
                .long("raw-json")
                .takes_value(true)
                .long_help("An input JSON file with the extra information.

This is a gross hack for some legacy side-channel information that mozilla uses. It will \
hopefully be phased out and deprecated in favour of just using custom streams in the \
minidump itself.\n\n\n")
        )
        .arg(
            Arg::with_name("symbols-url")
                .long("symbols-url")
                .multiple(true)
                .takes_value(true)
                .long_help("base URL from which URLs to symbol files can be constructed.

If multiple symbols-url values are provided, they will each be tried in order until \
one resolves.

The server the base URL points to is expected to conform to the Tecken \
symbol server protocol. For more details, see the Tecken docs:

https://tecken.readthedocs.io/en/latest/

Example symbols-url value: https://symbols.mozilla.org/\n\n\n")
        )
        .arg(
            Arg::with_name("symbols-cache")
                .long("symbols-cache")
                .takes_value(true)
                .long_help("A directory in which downloaded symbols can be stored.

Symbol files can be very large, so we recommend placing cached files in your \
system's temp directory so that it can garbage collect unused ones for you. \
To this end, the default value for this flag is a `rust-minidump-cache` \
subdirectory of `std::env::temp_dir()` (usually /tmp/rust-minidump-cache on linux).

symbols-cache must be on the same filesystem as symbols-tmp (if that doesn't mean anything to \
you, don't worry about it, you're probably not doing something that will run afoul of it).\n\n\n")
        )
        .arg(
            Arg::with_name("symbols-tmp")
                .long("symbols-tmp")
                .takes_value(true)
                .long_help("A directory to use as temp space for downloading symbols.

A temp dir is necessary to allow for multiple rust-minidump instances to share a cache without \
race conditions. Files to be added to the cache will be constructed in this location before \
being atomically moved to the cache.

If no path is specified, `std::env::temp_dir()` will be used to improve portability. \
See the rust documentation for how to set that value if you wish to use something other than \
your system's default temp directory.

symbols-tmp must be on the same filesystem as symbols-cache (if that doesn't mean anything to \
you, don't worry about it, you're probably not doing something that will run afoul of it).\n\n\n")
        )
        .arg(
            Arg::with_name("symbol-download-timeout-secs")
                .long("symbol-download-timeout-secs")
                .default_value("1000")
                .takes_value(true)
                .help("The maximum amount of time (in seconds) a symbol file download is allowed \
to take.

This is necessary to enforce forward progress on misbehaving http responses.\n\n")
        )
        .arg(
            Arg::with_name("minidump")
                .required(true)
                .takes_value(true)
                .help("Path to the minidump file to analyze.")
        )
        .arg(
            Arg::with_name("symbols-path")
                .multiple(true)
                .takes_value(true)
                .long_help("Path to a symbol file.

If multiple symbols-path values are provided, all symbol files will be merged \
into minidump-stackwalk's symbol database.\n\n\n")
        )
        .after_help("

Purpose of Symbols:

  Symbols are used for two purposes:

  1. To fill in more information about each frame of the backtraces. (function names, lines, etc.)

  2. To do produce a more *accurate* backtrace. This is primarily accomplished with \
call frame information (CFI), but just knowing what parts of a module maps to actual \
code is also useful!

Supported Symbol Formats:

  Currently only breakpad text symbol files are supported, although we hope to eventually \
support native formats like PDB and DWARF as well.

Breakpad Symbol Files:

  Breakpad symbol files are basically a simplified version of the information found in \
native debuginfo formats. We recommend using a version of dump_syms to generate them.

  See:
    * symbol file docs: https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/symbol_files.md
    * mozilla's dump_syms (co-developed with this program): https://github.com/mozilla/dump_syms

")
        .get_matches();

    let output_file = matches
        .value_of_os("output-file")
        .map(|os_str| Path::new(os_str).to_owned());

    let log_file = matches
        .value_of_os("log-file")
        .map(|os_str| Path::new(os_str).to_owned());

    let verbosity = match matches.value_of("verbose").unwrap() {
        "off" => LevelFilter::Off,
        "warn" => LevelFilter::Warn,
        "info" => LevelFilter::Info,
        "debug" => LevelFilter::Debug,
        "trace" => LevelFilter::Trace,
        _ => LevelFilter::Error,
    };

    // Init the logger (and make trace logging less noisy)
    if let Some(log_path) = log_file {
        let log_file = File::create(log_path).unwrap();
        let _ = WriteLogger::init(
            verbosity,
            ConfigBuilder::new()
                .set_location_level(LevelFilter::Off)
                .set_time_level(LevelFilter::Off)
                .set_thread_level(LevelFilter::Off)
                .set_target_level(LevelFilter::Off)
                .build(),
            log_file,
        )
        .unwrap();
    } else {
        let _ = TermLogger::init(
            verbosity,
            ConfigBuilder::new()
                .set_location_level(LevelFilter::Off)
                .set_time_level(LevelFilter::Off)
                .set_thread_level(LevelFilter::Off)
                .set_target_level(LevelFilter::Off)
                .set_level_color(Level::Trace, None)
                .build(),
            TerminalMode::Stderr,
            ColorChoice::Auto,
        );
    }

    // Set a panic hook to redirect to the logger
    panic::set_hook(Box::new(|panic_info| {
        let (filename, line) = panic_info
            .location()
            .map(|loc| (loc.file(), loc.line()))
            .unwrap_or(("<unknown>", 0));
        let cause = panic_info
            .payload()
            .downcast_ref::<String>()
            .map(String::deref)
            .unwrap_or_else(|| {
                panic_info
                    .payload()
                    .downcast_ref::<&str>()
                    .copied()
                    .unwrap_or("<cause unknown>")
            });
        error!("A panic occurred at {}:{}: {}", filename, line, cause);
    }));

    let mut options = ProcessorOptions::default();

    options.evil_json = matches.value_of_os("raw-json").map(Path::new);

    let temp_dir = std::env::temp_dir();

    let symbols_paths = matches
        .values_of_os("symbols-path")
        .map(|v| {
            v.map(|os_str| Path::new(os_str).to_owned())
                .collect::<Vec<_>>()
        })
        .unwrap_or_else(Vec::new);

    // Default to env::temp_dir()/rust-minidump-cache
    let symbols_cache = matches
        .value_of_os("symbols-cache")
        .map(|os_str| Path::new(os_str).to_owned())
        .unwrap_or_else(|| temp_dir.join("rust-minidump-cache"));

    // Default to env::temp_dir()
    let symbols_tmp = matches
        .value_of_os("symbols-tmp")
        .map(|os_str| Path::new(os_str).to_owned())
        .unwrap_or(temp_dir);

    let symbols_urls = matches
        .values_of("symbols-url")
        .map(|v| v.map(String::from).collect::<Vec<_>>())
        .unwrap_or_else(Vec::new);

    let timeout = matches
        .value_of("symbol-download-timeout-secs")
        .and_then(|x| u64::from_str(x).ok())
        .map(Duration::from_secs)
        .unwrap();

    let minidump_path = matches.value_of_os("minidump").map(Path::new).unwrap();

    // Determine the kind of output we're producing -- json, human, or cyborg (both).
    // Although we have a --json argument it's mostly just there to make the documentation
    // more clear. json output is enabled by default, and --human disables it.
    // Mutual exclusion is enforced by an ArgGroup.
    let mut human = matches.is_present("human");
    let mut json = !human;
    let cyborg = matches.value_of_os("cyborg").map(Path::new);

    if cyborg.is_some() {
        human = true;
        json = true;
    }

    // Now check if arguments that tweak the output are valid. We can't use
    // Arg::requires because clap doesn't understand --json being implicitly enabled.
    let pretty = matches.is_present("pretty");
    let brief = matches.is_present("brief");

    if pretty && !json {
        error!("Humans must be hideous! (The --pretty and --human flags cannot both be set)");
        std::process::exit(1);
    }

    if brief && !human {
        error!("Robots cannot be brief! (The --brief flag is only valid for --human output (or --cyborg)");
        std::process::exit(1);
    }

    // Ok now let's do the thing!!!!

    match Minidump::read_path(minidump_path) {
        Ok(dump) => {
            let mut provider = MultiSymbolProvider::new();

            if !symbols_urls.is_empty() {
                provider.add(Box::new(Symbolizer::new(http_symbol_supplier(
                    symbols_paths,
                    symbols_urls,
                    symbols_cache,
                    symbols_tmp,
                    timeout,
                ))));
            } else if !symbols_paths.is_empty() {
                provider.add(Box::new(Symbolizer::new(simple_symbol_supplier(
                    symbols_paths,
                ))));
            }

            match minidump_processor::process_minidump_with_options(&dump, &provider, options) {
                Ok(state) => {
                    let mut stdout;
                    let mut output_f;

                    let mut output: &mut dyn Write = if let Some(output_path) = output_file {
                        output_f = File::create(output_path).unwrap();
                        &mut output_f
                    } else {
                        stdout = std::io::stdout();
                        &mut stdout
                    };

                    if human {
                        state.print(&mut output).unwrap();
                    } else {
                        state.print_json(&mut output, pretty).unwrap();
                    }
                }
                Err(err) => {
                    error!("Error processing dump: {}", err);
                    std::process::exit(1);
                }
            }
        }
        Err(err) => {
            error!("Error reading dump: {}", err);
            std::process::exit(1);
        }
    }
}
<commit_msg>Implement cyborg output<commit_after>// Copyright 2015 Ted Mielczarek. See the COPYRIGHT
// file at the top-level directory of this distribution.

use std::boxed::Box;
use std::env;
use std::fs::File;
use std::io::Write;
use std::ops::Deref;
use std::panic;
use std::path::Path;
use std::str::FromStr;
use std::time::Duration;

use minidump::*;
use minidump_processor::{
    http_symbol_supplier, simple_symbol_supplier, MultiSymbolProvider, ProcessorOptions, Symbolizer,
};

use clap::{crate_version, App, AppSettings, Arg, ArgGroup};
use log::error;
use simplelog::{
    ColorChoice, ConfigBuilder, Level, LevelFilter, TermLogger, TerminalMode, WriteLogger,
};

#[cfg_attr(test, allow(dead_code))]
fn main() {
    let matches = App::new("minidump-stackwalk")
        .version(crate_version!())
        .about("Analyzes minidumps and produces a report (either human-readable or JSON).")
        .setting(AppSettings::NextLineHelp)
        .setting(AppSettings::ColoredHelp)
        .setting(AppSettings::DeriveDisplayOrder)
        .usage("minidump-stackwalk [FLAGS] [OPTIONS] <minidump> [--] [symbols-path]...")
        .arg(
            Arg::with_name("json")
                .long("json")
                .long_help("Emit a machine-readable JSON report (the default).

Official schema TBD, but the output *is* stabilized. That said, sometimes values can't be computed \
(e.g. because the minidump doesn't contain that information), so nearly every value \
*can* be null. And of course, we reserve the right to introduce new fields/values \
for new features.

In general we try to emit an explicit `null` for a field that *can* appear in the JSON but \
doesn't, so every JSON report is *kind of* reporting the current schema (but of course, \
missing the possible values and their meanings).\n\n\n")
        )
        .arg(
            Arg::with_name("human")
                .long("human")
                .long_help("Emit a human-readable report.

The human-readable report does not have a specified format, and may not have as \
many details as the default JSON format. It is intended for quickly inspecting \
a crash or debugging rust-minidump itself.\n\n\n")
        )
        .arg(
            Arg::with_name("cyborg")
                .long("cyborg")
                .takes_value(true)
                .long_help("Combine --human and --json

Because this creates two output streams, you must specify a path to write the --json
output to. The --human output will be the 'primary' output and default to stdout, which
can be configured with --output-file as normal.\n\n\n")
        )
        .group(ArgGroup::with_name("output-format")
            .args(&["json", "human", "cyborg"])
        )
        .arg(
            Arg::with_name("output-file")
                .long("output-file")
                .takes_value(true)
                .help("Where to write the output to (if unspecified, stdout is used)")
        )
        .arg(
            Arg::with_name("log-file")
                .long("log-file")
                .takes_value(true)
                .help("Where to write logs to (if unspecified, stderr is used)")
        )
        .arg(
            Arg::with_name("verbose")
                .long("verbose")
                .possible_values(&["off", "error", "warn", "info", "debug", "trace"])
                .default_value("error")
                .takes_value(true)
                .long_help("Set the logging level.

The unwinder has been heavily instrumented with `trace` logging, so if you want to debug why \
an unwind happened the way it did, --verbose=trace is very useful (all unwinder logging will \
be prefixed with `unwind:`).\n\n\n")
        )
        .arg(
            Arg::with_name("pretty")
                .long("pretty")
                .help("Pretty-print --json output.")
        )
        .arg(
            Arg::with_name("brief")
                .long("brief")
                .help("Provide a briefer --human report.

Only provides the top-level summary and a backtrace of the crashing thread.")
        )
        .arg(
            Arg::with_name("raw-json")
                .long("raw-json")
                .takes_value(true)
                .long_help("An input JSON file with the extra information.

This is a gross hack for some legacy side-channel information that mozilla uses. It will \
hopefully be phased out and deprecated in favour of just using custom streams in the \
minidump itself.\n\n\n")
        )
        .arg(
            Arg::with_name("symbols-url")
                .long("symbols-url")
                .multiple(true)
                .takes_value(true)
                .long_help("base URL from which URLs to symbol files can be constructed.

If multiple symbols-url values are provided, they will each be tried in order until \
one resolves.

The server the base URL points to is expected to conform to the Tecken \
symbol server protocol. For more details, see the Tecken docs:

https://tecken.readthedocs.io/en/latest/

Example symbols-url value: https://symbols.mozilla.org/\n\n\n")
        )
        .arg(
            Arg::with_name("symbols-cache")
                .long("symbols-cache")
                .takes_value(true)
                .long_help("A directory in which downloaded symbols can be stored.

Symbol files can be very large, so we recommend placing cached files in your \
system's temp directory so that it can garbage collect unused ones for you. \
To this end, the default value for this flag is a `rust-minidump-cache` \
subdirectory of `std::env::temp_dir()` (usually /tmp/rust-minidump-cache on linux).

symbols-cache must be on the same filesystem as symbols-tmp (if that doesn't mean anything to \
you, don't worry about it, you're probably not doing something that will run afoul of it).\n\n\n")
        )
        .arg(
            Arg::with_name("symbols-tmp")
                .long("symbols-tmp")
                .takes_value(true)
                .long_help("A directory to use as temp space for downloading symbols.

A temp dir is necessary to allow for multiple rust-minidump instances to share a cache without \
race conditions. Files to be added to the cache will be constructed in this location before \
being atomically moved to the cache.

If no path is specified, `std::env::temp_dir()` will be used to improve portability. \
See the rust documentation for how to set that value if you wish to use something other than \
your system's default temp directory.

symbols-tmp must be on the same filesystem as symbols-cache (if that doesn't mean anything to \
you, don't worry about it, you're probably not doing something that will run afoul of it).\n\n\n")
        )
        .arg(
            Arg::with_name("symbol-download-timeout-secs")
                .long("symbol-download-timeout-secs")
                .default_value("1000")
                .takes_value(true)
                .help("The maximum amount of time (in seconds) a symbol file download is allowed \
to take.

This is necessary to enforce forward progress on misbehaving http responses.\n\n")
        )
        .arg(
            Arg::with_name("minidump")
                .required(true)
                .takes_value(true)
                .help("Path to the minidump file to analyze.")
        )
        .arg(
            Arg::with_name("symbols-path")
                .multiple(true)
                .takes_value(true)
                .long_help("Path to a symbol file.

If multiple symbols-path values are provided, all symbol files will be merged \
into minidump-stackwalk's symbol database.\n\n\n")
        )
        .after_help("

Purpose of Symbols:

  Symbols are used for two purposes:

  1. To fill in more information about each frame of the backtraces. (function names, lines, etc.)

  2. To do produce a more *accurate* backtrace. This is primarily accomplished with \
call frame information (CFI), but just knowing what parts of a module maps to actual \
code is also useful!

Supported Symbol Formats:

  Currently only breakpad text symbol files are supported, although we hope to eventually \
support native formats like PDB and DWARF as well.

Breakpad Symbol Files:

  Breakpad symbol files are basically a simplified version of the information found in \
native debuginfo formats. We recommend using a version of dump_syms to generate them.

  See:
    * symbol file docs: https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/symbol_files.md
    * mozilla's dump_syms (co-developed with this program): https://github.com/mozilla/dump_syms

")
        .get_matches();

    let output_file = matches
        .value_of_os("output-file")
        .map(|os_str| Path::new(os_str).to_owned());

    let log_file = matches
        .value_of_os("log-file")
        .map(|os_str| Path::new(os_str).to_owned());

    let verbosity = match matches.value_of("verbose").unwrap() {
        "off" => LevelFilter::Off,
        "warn" => LevelFilter::Warn,
        "info" => LevelFilter::Info,
        "debug" => LevelFilter::Debug,
        "trace" => LevelFilter::Trace,
        _ => LevelFilter::Error,
    };

    // Init the logger (and make trace logging less noisy)
    if let Some(log_path) = log_file {
        let log_file = File::create(log_path).unwrap();
        let _ = WriteLogger::init(
            verbosity,
            ConfigBuilder::new()
                .set_location_level(LevelFilter::Off)
                .set_time_level(LevelFilter::Off)
                .set_thread_level(LevelFilter::Off)
                .set_target_level(LevelFilter::Off)
                .build(),
            log_file,
        )
        .unwrap();
    } else {
        let _ = TermLogger::init(
            verbosity,
            ConfigBuilder::new()
                .set_location_level(LevelFilter::Off)
                .set_time_level(LevelFilter::Off)
                .set_thread_level(LevelFilter::Off)
                .set_target_level(LevelFilter::Off)
                .set_level_color(Level::Trace, None)
                .build(),
            TerminalMode::Stderr,
            ColorChoice::Auto,
        );
    }

    // Set a panic hook to redirect to the logger
    panic::set_hook(Box::new(|panic_info| {
        let (filename, line) = panic_info
            .location()
            .map(|loc| (loc.file(), loc.line()))
            .unwrap_or(("<unknown>", 0));
        let cause = panic_info
            .payload()
            .downcast_ref::<String>()
            .map(String::deref)
            .unwrap_or_else(|| {
                panic_info
                    .payload()
                    .downcast_ref::<&str>()
                    .copied()
                    .unwrap_or("<cause unknown>")
            });
        error!("A panic occurred at {}:{}: {}", filename, line, cause);
    }));

    let mut options = ProcessorOptions::default();

    options.evil_json = matches.value_of_os("raw-json").map(Path::new);

    let temp_dir = std::env::temp_dir();

    let symbols_paths = matches
        .values_of_os("symbols-path")
        .map(|v| {
            v.map(|os_str| Path::new(os_str).to_owned())
                .collect::<Vec<_>>()
        })
        .unwrap_or_else(Vec::new);

    // Default to env::temp_dir()/rust-minidump-cache
    let symbols_cache = matches
        .value_of_os("symbols-cache")
        .map(|os_str| Path::new(os_str).to_owned())
        .unwrap_or_else(|| temp_dir.join("rust-minidump-cache"));

    // Default to env::temp_dir()
    let symbols_tmp = matches
        .value_of_os("symbols-tmp")
        .map(|os_str| Path::new(os_str).to_owned())
        .unwrap_or(temp_dir);

    let symbols_urls = matches
        .values_of("symbols-url")
        .map(|v| v.map(String::from).collect::<Vec<_>>())
        .unwrap_or_else(Vec::new);

    let timeout = matches
        .value_of("symbol-download-timeout-secs")
        .and_then(|x| u64::from_str(x).ok())
        .map(Duration::from_secs)
        .unwrap();

    let minidump_path = matches.value_of_os("minidump").map(Path::new).unwrap();

    // Determine the kind of output we're producing -- json, human, or cyborg (both).
    // Although we have a --json argument it's mostly just there to make the documentation
    // more clear. json output is enabled by default, and --human disables it.
    // Mutual exclusion is enforced by an ArgGroup.
    let mut human = matches.is_present("human");
    let mut json = !human;
    let cyborg = matches.value_of_os("cyborg").map(Path::new);

    if cyborg.is_some() {
        human = true;
        json = true;
    }

    // Now check if arguments that tweak the output are valid. We can't use
    // Arg::requires because clap doesn't understand --json being implicitly enabled.
    let pretty = matches.is_present("pretty");
    let brief = matches.is_present("brief");

    if pretty && !json {
        error!("Humans must be hideous! (The --pretty and --human flags cannot both be set)");
        std::process::exit(1);
    }

    if brief && !human {
        error!("Robots cannot be brief! (The --brief flag is only valid for --human output (or --cyborg)");
        std::process::exit(1);
    }

    // Ok now let's do the thing!!!!

    match Minidump::read_path(minidump_path) {
        Ok(dump) => {
            let mut provider = MultiSymbolProvider::new();

            if !symbols_urls.is_empty() {
                provider.add(Box::new(Symbolizer::new(http_symbol_supplier(
                    symbols_paths,
                    symbols_urls,
                    symbols_cache,
                    symbols_tmp,
                    timeout,
                ))));
            } else if !symbols_paths.is_empty() {
                provider.add(Box::new(Symbolizer::new(simple_symbol_supplier(
                    symbols_paths,
                ))));
            }

            match minidump_processor::process_minidump_with_options(&dump, &provider, options) {
                Ok(state) => {
                    let mut stdout;
                    let mut output_f;
                    let cyborg_output_f = cyborg.map(|path| File::create(path).unwrap());

                    let mut output: &mut dyn Write = if let Some(output_path) = output_file {
                        output_f = File::create(output_path).unwrap();
                        &mut output_f
                    } else {
                        stdout = std::io::stdout();
                        &mut stdout
                    };

                    // Print the human output if requested (always uses the "real" output).
                    if human {
                        state.print(&mut output).unwrap();
                    }

                    // Print the json output if requested (using "cyborg" output if available).
                    if json {
                        if let Some(mut cyborg_output_f) = cyborg_output_f {
                            state.print_json(&mut cyborg_output_f, pretty).unwrap();
                        } else {
                            state.print_json(&mut output, pretty).unwrap();
                        }
                    }
                }
                Err(err) => {
                    error!("Error processing dump: {}", err);
                    std::process::exit(1);
                }
            }
        }
        Err(err) => {
            error!("Error reading dump: {}", err);
            std::process::exit(1);
        }
    }
}
<|endoftext|>