<commit_before>use rustc_attr as attr;
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_middle::hir::map::blocks::FnLikeNode;
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::TyCtxt;
use rustc_span::symbol::Symbol;
use rustc_target::spec::abi::Abi;

/// Whether the `def_id` counts as const fn in your current crate, considering all active
/// feature gates
pub fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
    tcx.is_const_fn_raw(def_id)
        && match is_unstable_const_fn(tcx, def_id) {
            Some(feature_name) => {
                // has a `rustc_const_unstable` attribute, check whether the user enabled the
                // corresponding feature gate.
                tcx.features().declared_lib_features.iter().any(|&(sym, _)| sym == feature_name)
            }
            // functions without const stability are either stable user written
            // const fn or the user is using feature gates and we thus don't
            // care what they do
            None => true,
        }
}

/// Whether the `def_id` is an unstable const fn and what feature gate is necessary to enable it
pub fn is_unstable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> Option<Symbol> {
    if tcx.is_const_fn_raw(def_id) {
        let const_stab = tcx.lookup_const_stability(def_id)?;
        if const_stab.level.is_unstable() { Some(const_stab.feature) } else { None }
    } else {
        None
    }
}

/// Returns `true` if this function must conform to `min_const_fn`
pub fn is_min_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
    // Bail out if the signature doesn't contain `const`
    if !tcx.is_const_fn_raw(def_id) {
        return false;
    }

    if tcx.features().staged_api {
        // In order for a libstd function to be considered min_const_fn
        // it needs to be stable and have no `rustc_const_unstable` attribute.
        match tcx.lookup_const_stability(def_id) {
            // `rustc_const_unstable` functions don't need to conform.
            Some(&attr::ConstStability { ref level, .. }) if level.is_unstable() => false,
            None => {
                if let Some(stab) = tcx.lookup_stability(def_id) {
                    if stab.level.is_stable() {
                        tcx.sess.span_err(
                            tcx.def_span(def_id),
                            "stable const functions must have either `rustc_const_stable` or \
                             `rustc_const_unstable` attribute",
                        );
                        // While we errored above, because we don't know if we need to conform, we
                        // err on the "safe" side and require min_const_fn.
                        true
                    } else {
                        // Unstable functions need not conform to min_const_fn.
                        false
                    }
                } else {
                    // Internal functions are forced to conform to min_const_fn.
                    // Annotate the internal function with a const stability attribute if
                    // you need to use unstable features.
                    // Note: this is an arbitrary choice that does not affect stability or const
                    // safety or anything, it just changes whether we need to annotate some
                    // internal functions with `rustc_const_stable` or with `rustc_const_unstable`
                    true
                }
            }
            // Everything else needs to conform, because it would be callable from
            // other `min_const_fn` functions.
            _ => true,
        }
    } else {
        // users enabling the `const_fn` feature gate can do what they want
        !tcx.features().const_fn
    }
}

pub fn is_parent_const_impl_raw(tcx: TyCtxt<'_>, hir_id: hir::HirId) -> bool {
    let parent_id = tcx.hir().get_parent_did(hir_id);
    if !parent_id.is_top_level_module() { is_const_impl_raw(tcx, parent_id) } else { false }
}

/// Checks whether the function has a `const` modifier or, in case it is an intrinsic, whether
/// said intrinsic has a `rustc_const_{un,}stable` attribute.
fn is_const_fn_raw(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
    let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local());

    let node = tcx.hir().get(hir_id);

    if let hir::Node::ForeignItem(hir::ForeignItem { kind: hir::ForeignItemKind::Fn(..), .. }) =
        node
    {
        // Intrinsics use `rustc_const_{un,}stable` attributes to indicate constness. All other
        // foreign items cannot be evaluated at compile-time.
        if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = tcx.hir().get_foreign_abi(hir_id) {
            tcx.lookup_const_stability(def_id).is_some()
        } else {
            false
        }
    } else if let Some(fn_like) = FnLikeNode::from_node(node) {
        if fn_like.constness() == hir::Constness::Const {
            return true;
        }

        // If the function itself is not annotated with `const`, it may still be a `const fn`
        // if it resides in a const trait impl.
        is_parent_const_impl_raw(tcx, hir_id)
    } else if let hir::Node::Ctor(_) = node {
        true
    } else {
        false
    }
}

/// Checks whether the given item is an `impl` that has a `const` modifier.
fn is_const_impl_raw(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
    let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
    let node = tcx.hir().get(hir_id);
    matches!(
        node,
        hir::Node::Item(hir::Item {
            kind: hir::ItemKind::Impl { constness: hir::Constness::Const, .. },
            ..
        })
    )
}

fn is_promotable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
    is_const_fn(tcx, def_id)
        && match tcx.lookup_const_stability(def_id) {
            Some(stab) => {
                if cfg!(debug_assertions) && stab.promotable {
                    let sig = tcx.fn_sig(def_id);
                    assert_eq!(
                        sig.unsafety(),
                        hir::Unsafety::Normal,
                        "don't mark const unsafe fns as promotable",
                        // https://github.com/rust-lang/rust/pull/53851#issuecomment-418760682
                    );
                }
                stab.promotable
            }
            None => false,
        }
}

fn const_fn_is_allowed_fn_ptr(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
    is_const_fn(tcx, def_id)
        && tcx.lookup_const_stability(def_id).map(|stab| stab.allow_const_fn_ptr).unwrap_or(false)
}

pub fn provide(providers: &mut Providers) {
    *providers = Providers {
        is_const_fn_raw,
        is_const_impl_raw: |tcx, def_id| is_const_impl_raw(tcx, def_id.expect_local()),
        is_promotable_const_fn,
        const_fn_is_allowed_fn_ptr,
        ..*providers
    };
}
<commit_msg>Change error in `fn_queries` to `delay_span_bug`<commit_after>use rustc_attr as attr;
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_middle::hir::map::blocks::FnLikeNode;
use rustc_middle::ty::query::Providers;
use rustc_middle::ty::TyCtxt;
use rustc_span::symbol::Symbol;
use rustc_target::spec::abi::Abi;

/// Whether the `def_id` counts as const fn in your current crate, considering all active
/// feature gates
pub fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
    tcx.is_const_fn_raw(def_id)
        && match is_unstable_const_fn(tcx, def_id) {
            Some(feature_name) => {
                // has a `rustc_const_unstable` attribute, check whether the user enabled the
                // corresponding feature gate.
                tcx.features().declared_lib_features.iter().any(|&(sym, _)| sym == feature_name)
            }
            // functions without const stability are either stable user written
            // const fn or the user is using feature gates and we thus don't
            // care what they do
            None => true,
        }
}

/// Whether the `def_id` is an unstable const fn and what feature gate is necessary to enable it
pub fn is_unstable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> Option<Symbol> {
    if tcx.is_const_fn_raw(def_id) {
        let const_stab = tcx.lookup_const_stability(def_id)?;
        if const_stab.level.is_unstable() { Some(const_stab.feature) } else { None }
    } else {
        None
    }
}

/// Returns `true` if this function must conform to `min_const_fn`
pub fn is_min_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
    // Bail out if the signature doesn't contain `const`
    if !tcx.is_const_fn_raw(def_id) {
        return false;
    }

    if tcx.features().staged_api {
        // In order for a libstd function to be considered min_const_fn
        // it needs to be stable and have no `rustc_const_unstable` attribute.
        match tcx.lookup_const_stability(def_id) {
            // `rustc_const_unstable` functions don't need to conform.
            Some(&attr::ConstStability { ref level, .. }) if level.is_unstable() => false,
            None => {
                if let Some(stab) = tcx.lookup_stability(def_id) {
                    if stab.level.is_stable() {
                        tcx.sess.delay_span_bug(
                            tcx.def_span(def_id),
                            "stable const functions must have either `rustc_const_stable` or \
                             `rustc_const_unstable` attribute",
                        );
                        // While we errored above, because we don't know if we need to conform, we
                        // err on the "safe" side and require min_const_fn.
                        true
                    } else {
                        // Unstable functions need not conform to min_const_fn.
                        false
                    }
                } else {
                    // Internal functions are forced to conform to min_const_fn.
                    // Annotate the internal function with a const stability attribute if
                    // you need to use unstable features.
                    // Note: this is an arbitrary choice that does not affect stability or const
                    // safety or anything, it just changes whether we need to annotate some
                    // internal functions with `rustc_const_stable` or with `rustc_const_unstable`
                    true
                }
            }
            // Everything else needs to conform, because it would be callable from
            // other `min_const_fn` functions.
            _ => true,
        }
    } else {
        // users enabling the `const_fn` feature gate can do what they want
        !tcx.features().const_fn
    }
}

pub fn is_parent_const_impl_raw(tcx: TyCtxt<'_>, hir_id: hir::HirId) -> bool {
    let parent_id = tcx.hir().get_parent_did(hir_id);
    if !parent_id.is_top_level_module() { is_const_impl_raw(tcx, parent_id) } else { false }
}

/// Checks whether the function has a `const` modifier or, in case it is an intrinsic, whether
/// said intrinsic has a `rustc_const_{un,}stable` attribute.
fn is_const_fn_raw(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
    let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local());

    let node = tcx.hir().get(hir_id);

    if let hir::Node::ForeignItem(hir::ForeignItem { kind: hir::ForeignItemKind::Fn(..), .. }) =
        node
    {
        // Intrinsics use `rustc_const_{un,}stable` attributes to indicate constness. All other
        // foreign items cannot be evaluated at compile-time.
        if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = tcx.hir().get_foreign_abi(hir_id) {
            tcx.lookup_const_stability(def_id).is_some()
        } else {
            false
        }
    } else if let Some(fn_like) = FnLikeNode::from_node(node) {
        if fn_like.constness() == hir::Constness::Const {
            return true;
        }

        // If the function itself is not annotated with `const`, it may still be a `const fn`
        // if it resides in a const trait impl.
        is_parent_const_impl_raw(tcx, hir_id)
    } else if let hir::Node::Ctor(_) = node {
        true
    } else {
        false
    }
}

/// Checks whether the given item is an `impl` that has a `const` modifier.
fn is_const_impl_raw(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
    let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
    let node = tcx.hir().get(hir_id);
    matches!(
        node,
        hir::Node::Item(hir::Item {
            kind: hir::ItemKind::Impl { constness: hir::Constness::Const, .. },
            ..
        })
    )
}

fn is_promotable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
    is_const_fn(tcx, def_id)
        && match tcx.lookup_const_stability(def_id) {
            Some(stab) => {
                if cfg!(debug_assertions) && stab.promotable {
                    let sig = tcx.fn_sig(def_id);
                    assert_eq!(
                        sig.unsafety(),
                        hir::Unsafety::Normal,
                        "don't mark const unsafe fns as promotable",
                        // https://github.com/rust-lang/rust/pull/53851#issuecomment-418760682
                    );
                }
                stab.promotable
            }
            None => false,
        }
}

fn const_fn_is_allowed_fn_ptr(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
    is_const_fn(tcx, def_id)
        && tcx.lookup_const_stability(def_id).map(|stab| stab.allow_const_fn_ptr).unwrap_or(false)
}

pub fn provide(providers: &mut Providers) {
    *providers = Providers {
        is_const_fn_raw,
        is_const_impl_raw: |tcx, def_id| is_const_impl_raw(tcx, def_id.expect_local()),
        is_promotable_const_fn,
        const_fn_is_allowed_fn_ptr,
        ..*providers
    };
}
<|endoftext|>


<commit_before>//! Performs various peephole optimizations.

use crate::transform::MirPass;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::Mutability;
use rustc_index::vec::Idx;
use rustc_middle::mir::{
    visit::PlaceContext,
    visit::{MutVisitor, Visitor},
    Statement,
};
use rustc_middle::mir::{
    BinOp, Body, BorrowKind, Constant, Local, Location, Operand, Place, PlaceRef, ProjectionElem,
    Rvalue,
};
use rustc_middle::ty::{self, TyCtxt};
use std::mem;

pub struct InstCombine;

impl<'tcx> MirPass<'tcx> for InstCombine {
    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
        // First, find optimization opportunities. This is done in a pre-pass to keep the MIR
        // read-only so that we can do global analyses on the MIR in the process (e.g.
        // `Place::ty()`).
        let optimizations = {
            let mut optimization_finder = OptimizationFinder::new(body, tcx);
            optimization_finder.visit_body(body);
            optimization_finder.optimizations
        };

        if !optimizations.is_empty() {
            // Then carry out those optimizations.
            MutVisitor::visit_body(&mut InstCombineVisitor { optimizations, tcx }, body);
        }
    }
}

pub struct InstCombineVisitor<'tcx> {
    optimizations: OptimizationList<'tcx>,
    tcx: TyCtxt<'tcx>,
}

impl<'tcx> MutVisitor<'tcx> for InstCombineVisitor<'tcx> {
    fn tcx(&self) -> TyCtxt<'tcx> {
        self.tcx
    }

    fn visit_rvalue(&mut self, rvalue: &mut Rvalue<'tcx>, location: Location) {
        if self.optimizations.and_stars.remove(&location) {
            debug!("replacing `&*`: {:?}", rvalue);
            let new_place = match rvalue {
                Rvalue::Ref(_, _, place) => {
                    if let &[ref proj_l @ .., proj_r] = place.projection.as_ref() {
                        place.projection = self.tcx().intern_place_elems(&[proj_r]);

                        Place {
                            // Replace with dummy
                            local: mem::replace(&mut place.local, Local::new(0)),
                            projection: self.tcx().intern_place_elems(proj_l),
                        }
                    } else {
                        unreachable!();
                    }
                }
                _ => bug!("Detected `&*` but didn't find `&*`!"),
            };
            *rvalue = Rvalue::Use(Operand::Copy(new_place))
        }

        if let Some(constant) = self.optimizations.arrays_lengths.remove(&location) {
            debug!("replacing `Len([_; N])`: {:?}", rvalue);
            *rvalue = Rvalue::Use(Operand::Constant(box constant));
        }

        if let Some(operand) = self.optimizations.unneeded_equality_comparison.remove(&location) {
            debug!("replacing {:?} with {:?}", rvalue, operand);
            *rvalue = Rvalue::Use(operand);
        }

        if let Some(place) = self.optimizations.unneeded_deref.remove(&location) {
            debug!("unneeded_deref: replacing {:?} with {:?}", rvalue, place);
            *rvalue = Rvalue::Use(Operand::Copy(place));
        }

        // We do not call super_rvalue as we are not interested in any other parts of the tree
    }
}

struct MutatingUseVisitor {
    has_mutating_use: bool,
    local_to_look_for: Local,
}

impl MutatingUseVisitor {
    fn has_mutating_use_in_stmt(local: Local, stmt: &Statement<'tcx>, location: Location) -> bool {
        let mut _self = Self { has_mutating_use: false, local_to_look_for: local };
        _self.visit_statement(stmt, location);
        _self.has_mutating_use
    }
}

impl<'tcx> Visitor<'tcx> for MutatingUseVisitor {
    fn visit_local(&mut self, local: &Local, context: PlaceContext, _: Location) {
        if *local == self.local_to_look_for {
            self.has_mutating_use |= context.is_mutating_use();
        }
    }
}

/// Finds optimization opportunities on the MIR.
struct OptimizationFinder<'b, 'tcx> {
    body: &'b Body<'tcx>,
    tcx: TyCtxt<'tcx>,
    optimizations: OptimizationList<'tcx>,
}

impl OptimizationFinder<'b, 'tcx> {
    fn new(body: &'b Body<'tcx>, tcx: TyCtxt<'tcx>) -> OptimizationFinder<'b, 'tcx> {
        OptimizationFinder { body, tcx, optimizations: OptimizationList::default() }
    }

    fn find_deref_of_address(&mut self, rvalue: &Rvalue<'tcx>, location: Location) -> Option<()> {
        // FIXME(#78192): This optimization can result in unsoundness.
        if !self.tcx.sess.opts.debugging_opts.unsound_mir_opts {
            return None;
        }

        // Look for the sequence
        //
        // _2 = &_1;
        // ...
        // _5 = (*_2);
        //
        // which we can replace the last statement with `_5 = _1;` to avoid the load of `_2`.
        if let Rvalue::Use(op) = rvalue {
            let local_being_derefed = match op.place()?.as_ref() {
                PlaceRef { local, projection: [ProjectionElem::Deref] } => Some(local),
                _ => None,
            }?;

            let mut dead_locals_seen = vec![];

            let stmt_index = location.statement_index;
            // Look behind for statement that assigns the local from a address of operator.
            // 6 is chosen as a heuristic determined by seeing the number of times
            // the optimization kicked in compiling rust std.
            let lower_index = stmt_index.saturating_sub(6);
            let statements_to_look_in = self.body.basic_blocks()[location.block].statements
                [lower_index..stmt_index]
                .iter()
                .rev();
            for stmt in statements_to_look_in {
                match &stmt.kind {
                    // Exhaustive match on statements to detect conditions that warrant we bail out of the optimization.
                    rustc_middle::mir::StatementKind::Assign(box (l, r))
                        if l.local == local_being_derefed =>
                    {
                        match r {
                            // Looking for immutable reference e.g _local_being_deref = &_1;
                            Rvalue::Ref(
                                _,
                                // Only apply the optimization if it is an immutable borrow.
                                BorrowKind::Shared,
                                place_taken_address_of,
                            ) => {
                                // Make sure that the place has not been marked dead
                                if dead_locals_seen.contains(&place_taken_address_of.local) {
                                    return None;
                                }

                                self.optimizations
                                    .unneeded_deref
                                    .insert(location, *place_taken_address_of);
                                return Some(());
                            }

                            // We found an assignment of `local_being_deref` that is not an immutable ref, e.g the following sequence
                            // _2 = &_1;
                            // _3 = &5
                            // _2 = _3;  <-- this means it is no longer valid to replace the last statement with `_5 = _1;`
                            // _5 = (*_2);
                            _ => return None,
                        }
                    }

                    // Inline asm can do anything, so bail out of the optimization.
                    rustc_middle::mir::StatementKind::LlvmInlineAsm(_) => return None,

                    // Remember `StorageDead`s, as the local being marked dead could be the
                    // place RHS we are looking for, in which case we need to abort to avoid UB
                    // using an uninitialized place
                    rustc_middle::mir::StatementKind::StorageDead(dead) => {
                        dead_locals_seen.push(*dead)
                    }

                    // Check that `local_being_deref` is not being used in a mutating way which can cause misoptimization.
                    rustc_middle::mir::StatementKind::Assign(box (_, _))
                    | rustc_middle::mir::StatementKind::Coverage(_)
                    | rustc_middle::mir::StatementKind::Nop
                    | rustc_middle::mir::StatementKind::FakeRead(_, _)
                    | rustc_middle::mir::StatementKind::StorageLive(_)
                    | rustc_middle::mir::StatementKind::Retag(_, _)
                    | rustc_middle::mir::StatementKind::AscribeUserType(_, _)
                    | rustc_middle::mir::StatementKind::SetDiscriminant { .. } => {
                        if MutatingUseVisitor::has_mutating_use_in_stmt(
                            local_being_derefed,
                            stmt,
                            location,
                        ) {
                            return None;
                        }
                    }
                }
            }
        }
        Some(())
    }

    fn find_unneeded_equality_comparison(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
        // find Ne(_place, false) or Ne(false, _place)
        // or   Eq(_place, true) or Eq(true, _place)
        if let Rvalue::BinaryOp(op, l, r) = rvalue {
            let const_to_find = if *op == BinOp::Ne {
                false
            } else if *op == BinOp::Eq {
                true
            } else {
                return;
            };
            // (const, _place)
            if let Some(o) = self.find_operand_in_equality_comparison_pattern(l, r, const_to_find) {
                self.optimizations.unneeded_equality_comparison.insert(location, o.clone());
            }
            // (_place, const)
            else if let Some(o) =
                self.find_operand_in_equality_comparison_pattern(r, l, const_to_find)
            {
                self.optimizations.unneeded_equality_comparison.insert(location, o.clone());
            }
        }
    }

    fn find_operand_in_equality_comparison_pattern(
        &self,
        l: &Operand<'tcx>,
        r: &'a Operand<'tcx>,
        const_to_find: bool,
    ) -> Option<&'a Operand<'tcx>> {
        let const_ = l.constant()?;
        if const_.literal.ty == self.tcx.types.bool
            && const_.literal.val.try_to_bool() == Some(const_to_find)
        {
            if r.place().is_some() {
                return Some(r);
            }
        }

        None
    }
}

impl Visitor<'tcx> for OptimizationFinder<'b, 'tcx> {
    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
        if let Rvalue::Ref(_, _, place) = rvalue {
            if let PlaceRef { local, projection: &[ref proj_base @ .., ProjectionElem::Deref] } =
                place.as_ref()
            {
                // The dereferenced place must have type `&_`.
                let ty = Place::ty_from(local, proj_base, self.body, self.tcx).ty;
                if let ty::Ref(_, _, Mutability::Not) = ty.kind() {
                    self.optimizations.and_stars.insert(location);
                }
            }
        }

        if let Rvalue::Len(ref place) = *rvalue {
            let place_ty = place.ty(&self.body.local_decls, self.tcx).ty;
            if let ty::Array(_, len) = place_ty.kind() {
                let span = self.body.source_info(location).span;
                let constant = Constant { span, literal: len, user_ty: None };
                self.optimizations.arrays_lengths.insert(location, constant);
            }
        }

        let _ = self.find_deref_of_address(rvalue, location);

        self.find_unneeded_equality_comparison(rvalue, location);

        // We do not call super_rvalue as we are not interested in any other parts of the tree
    }
}

#[derive(Default)]
struct OptimizationList<'tcx> {
    and_stars: FxHashSet<Location>,
    arrays_lengths: FxHashMap<Location, Constant<'tcx>>,
    unneeded_equality_comparison: FxHashMap<Location, Operand<'tcx>>,
    unneeded_deref: FxHashMap<Location, Place<'tcx>>,
}

impl<'tcx> OptimizationList<'tcx> {
    fn is_empty(&self) -> bool {
        self.and_stars.is_empty()
            && self.arrays_lengths.is_empty()
            && self.unneeded_equality_comparison.is_empty()
            && self.unneeded_deref.is_empty()
    }
}
<commit_msg>use exhaustive pattern match to prevent future bugs<commit_after>//! Performs various peephole optimizations.

use crate::transform::MirPass;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::Mutability;
use rustc_index::vec::Idx;
use rustc_middle::mir::{
    visit::PlaceContext,
    visit::{MutVisitor, Visitor},
    Statement,
};
use rustc_middle::mir::{
    BinOp, Body, BorrowKind, Constant, Local, Location, Operand, Place, PlaceRef, ProjectionElem,
    Rvalue,
};
use rustc_middle::ty::{self, TyCtxt};
use std::mem;

pub struct InstCombine;

impl<'tcx> MirPass<'tcx> for InstCombine {
    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
        // First, find optimization opportunities. This is done in a pre-pass to keep the MIR
        // read-only so that we can do global analyses on the MIR in the process (e.g.
        // `Place::ty()`).
        let optimizations = {
            let mut optimization_finder = OptimizationFinder::new(body, tcx);
            optimization_finder.visit_body(body);
            optimization_finder.optimizations
        };

        if !optimizations.is_empty() {
            // Then carry out those optimizations.
            MutVisitor::visit_body(&mut InstCombineVisitor { optimizations, tcx }, body);
        }
    }
}

pub struct InstCombineVisitor<'tcx> {
    optimizations: OptimizationList<'tcx>,
    tcx: TyCtxt<'tcx>,
}

impl<'tcx> MutVisitor<'tcx> for InstCombineVisitor<'tcx> {
    fn tcx(&self) -> TyCtxt<'tcx> {
        self.tcx
    }

    fn visit_rvalue(&mut self, rvalue: &mut Rvalue<'tcx>, location: Location) {
        if self.optimizations.and_stars.remove(&location) {
            debug!("replacing `&*`: {:?}", rvalue);
            let new_place = match rvalue {
                Rvalue::Ref(_, _, place) => {
                    if let &[ref proj_l @ .., proj_r] = place.projection.as_ref() {
                        place.projection = self.tcx().intern_place_elems(&[proj_r]);

                        Place {
                            // Replace with dummy
                            local: mem::replace(&mut place.local, Local::new(0)),
                            projection: self.tcx().intern_place_elems(proj_l),
                        }
                    } else {
                        unreachable!();
                    }
                }
                _ => bug!("Detected `&*` but didn't find `&*`!"),
            };
            *rvalue = Rvalue::Use(Operand::Copy(new_place))
        }

        if let Some(constant) = self.optimizations.arrays_lengths.remove(&location) {
            debug!("replacing `Len([_; N])`: {:?}", rvalue);
            *rvalue = Rvalue::Use(Operand::Constant(box constant));
        }

        if let Some(operand) = self.optimizations.unneeded_equality_comparison.remove(&location) {
            debug!("replacing {:?} with {:?}", rvalue, operand);
            *rvalue = Rvalue::Use(operand);
        }

        if let Some(place) = self.optimizations.unneeded_deref.remove(&location) {
            debug!("unneeded_deref: replacing {:?} with {:?}", rvalue, place);
            *rvalue = Rvalue::Use(Operand::Copy(place));
        }

        // We do not call super_rvalue as we are not interested in any other parts of the tree
    }
}

struct MutatingUseVisitor {
    has_mutating_use: bool,
    local_to_look_for: Local,
}

impl MutatingUseVisitor {
    fn has_mutating_use_in_stmt(local: Local, stmt: &Statement<'tcx>, location: Location) -> bool {
        let mut _self = Self { has_mutating_use: false, local_to_look_for: local };
        _self.visit_statement(stmt, location);
        _self.has_mutating_use
    }
}

impl<'tcx> Visitor<'tcx> for MutatingUseVisitor {
    fn visit_local(&mut self, local: &Local, context: PlaceContext, _: Location) {
        if *local == self.local_to_look_for {
            self.has_mutating_use |= context.is_mutating_use();
        }
    }
}

/// Finds optimization opportunities on the MIR.
struct OptimizationFinder<'b, 'tcx> {
    body: &'b Body<'tcx>,
    tcx: TyCtxt<'tcx>,
    optimizations: OptimizationList<'tcx>,
}

impl OptimizationFinder<'b, 'tcx> {
    fn new(body: &'b Body<'tcx>, tcx: TyCtxt<'tcx>) -> OptimizationFinder<'b, 'tcx> {
        OptimizationFinder { body, tcx, optimizations: OptimizationList::default() }
    }

    fn find_deref_of_address(&mut self, rvalue: &Rvalue<'tcx>, location: Location) -> Option<()> {
        // FIXME(#78192): This optimization can result in unsoundness.
        if !self.tcx.sess.opts.debugging_opts.unsound_mir_opts {
            return None;
        }

        // Look for the sequence
        //
        // _2 = &_1;
        // ...
        // _5 = (*_2);
        //
        // which we can replace the last statement with `_5 = _1;` to avoid the load of `_2`.
        if let Rvalue::Use(op) = rvalue {
            let local_being_derefed = match op.place()?.as_ref() {
                PlaceRef { local, projection: [ProjectionElem::Deref] } => Some(local),
                _ => None,
            }?;

            let mut dead_locals_seen = vec![];

            let stmt_index = location.statement_index;
            // Look behind for statement that assigns the local from a address of operator.
            // 6 is chosen as a heuristic determined by seeing the number of times
            // the optimization kicked in compiling rust std.
            let lower_index = stmt_index.saturating_sub(6);
            let statements_to_look_in = self.body.basic_blocks()[location.block].statements
                [lower_index..stmt_index]
                .iter()
                .rev();
            for stmt in statements_to_look_in {
                match &stmt.kind {
                    // Exhaustive match on statements to detect conditions that warrant we bail out of the optimization.
                    rustc_middle::mir::StatementKind::Assign(box (l, r))
                        if l.local == local_being_derefed =>
                    {
                        match r {
                            // Looking for immutable reference e.g _local_being_deref = &_1;
                            Rvalue::Ref(
                                _,
                                // Only apply the optimization if it is an immutable borrow.
                                BorrowKind::Shared,
                                place_taken_address_of,
                            ) => {
                                // Make sure that the place has not been marked dead
                                if dead_locals_seen.contains(&place_taken_address_of.local) {
                                    return None;
                                }

                                self.optimizations
                                    .unneeded_deref
                                    .insert(location, *place_taken_address_of);
                                return Some(());
                            }

                            // We found an assignment of `local_being_deref` that is not an immutable ref, e.g the following sequence
                            // _2 = &_1;
                            // _3 = &5
                            // _2 = _3;  <-- this means it is no longer valid to replace the last statement with `_5 = _1;`
                            // _5 = (*_2);
                            _ => return None,
                        }
                    }

                    // Inline asm can do anything, so bail out of the optimization.
                    rustc_middle::mir::StatementKind::LlvmInlineAsm(_) => return None,

                    // Remember `StorageDead`s, as the local being marked dead could be the
                    // place RHS we are looking for, in which case we need to abort to avoid UB
                    // using an uninitialized place
                    rustc_middle::mir::StatementKind::StorageDead(dead) => {
                        dead_locals_seen.push(*dead)
                    }

                    // Check that `local_being_deref` is not being used in a mutating way which can cause misoptimization.
                    rustc_middle::mir::StatementKind::Assign(box (_, _))
                    | rustc_middle::mir::StatementKind::Coverage(_)
                    | rustc_middle::mir::StatementKind::Nop
                    | rustc_middle::mir::StatementKind::FakeRead(_, _)
                    | rustc_middle::mir::StatementKind::StorageLive(_)
                    | rustc_middle::mir::StatementKind::Retag(_, _)
                    | rustc_middle::mir::StatementKind::AscribeUserType(_, _)
                    | rustc_middle::mir::StatementKind::SetDiscriminant { .. } => {
                        if MutatingUseVisitor::has_mutating_use_in_stmt(
                            local_being_derefed,
                            stmt,
                            location,
                        ) {
                            return None;
                        }
                    }
                }
            }
        }
        Some(())
    }

    fn find_unneeded_equality_comparison(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
        // find Ne(_place, false) or Ne(false, _place)
        // or   Eq(_place, true) or Eq(true, _place)
        if let Rvalue::BinaryOp(op, l, r) = rvalue {
            let const_to_find = if *op == BinOp::Ne {
                false
            } else if *op == BinOp::Eq {
                true
            } else {
                return;
            };
            // (const, _place)
            if let Some(o) = self.find_operand_in_equality_comparison_pattern(l, r, const_to_find) {
                self.optimizations.unneeded_equality_comparison.insert(location, o.clone());
            }
            // (_place, const)
            else if let Some(o) =
                self.find_operand_in_equality_comparison_pattern(r, l, const_to_find)
            {
                self.optimizations.unneeded_equality_comparison.insert(location, o.clone());
            }
        }
    }

    fn find_operand_in_equality_comparison_pattern(
        &self,
        l: &Operand<'tcx>,
        r: &'a Operand<'tcx>,
        const_to_find: bool,
    ) -> Option<&'a Operand<'tcx>> {
        let const_ = l.constant()?;
        if const_.literal.ty == self.tcx.types.bool
            && const_.literal.val.try_to_bool() == Some(const_to_find)
        {
            if r.place().is_some() {
                return Some(r);
            }
        }

        None
    }
}

impl Visitor<'tcx> for OptimizationFinder<'b, 'tcx> {
    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
        if let Rvalue::Ref(_, _, place) = rvalue {
            if let PlaceRef { local, projection: &[ref proj_base @ .., ProjectionElem::Deref] } =
                place.as_ref()
            {
                // The dereferenced place must have type `&_`.
                let ty = Place::ty_from(local, proj_base, self.body, self.tcx).ty;
                if let ty::Ref(_, _, Mutability::Not) = ty.kind() {
                    self.optimizations.and_stars.insert(location);
                }
            }
        }

        if let Rvalue::Len(ref place) = *rvalue {
            let place_ty = place.ty(&self.body.local_decls, self.tcx).ty;
            if let ty::Array(_, len) = place_ty.kind() {
                let span = self.body.source_info(location).span;
                let constant = Constant { span, literal: len, user_ty: None };
                self.optimizations.arrays_lengths.insert(location, constant);
            }
        }

        let _ = self.find_deref_of_address(rvalue, location);

        self.find_unneeded_equality_comparison(rvalue, location);

        // We do not call super_rvalue as we are not interested in any other parts of the tree
    }
}

#[derive(Default)]
struct OptimizationList<'tcx> {
    and_stars: FxHashSet<Location>,
    arrays_lengths: FxHashMap<Location, Constant<'tcx>>,
    unneeded_equality_comparison: FxHashMap<Location, Operand<'tcx>>,
    unneeded_deref: FxHashMap<Location, Place<'tcx>>,
}

impl<'tcx> OptimizationList<'tcx> {
    fn is_empty(&self) -> bool {
        match self {
            OptimizationList {
                and_stars,
                arrays_lengths,
                unneeded_equality_comparison,
                unneeded_deref,
            } => {
                and_stars.is_empty()
                    && arrays_lengths.is_empty()
                    && unneeded_equality_comparison.is_empty()
                    && unneeded_deref.is_empty()
            }
        }
    }
}
<|endoftext|>


<commit_before>use nom::{
  IResult,
  ErrorCode, Err,
  Needed,
};

use frame;
use frame::subframe;

fn leading_zeros(input: (&[u8], usize)) -> IResult<(&[u8], usize), u32> {
  let (bytes, mut offset) = input;

  let mut index     = 0;
  let mut count     = 0;
  let mut is_parsed = false;
  let bytes_len     = bytes.len();

  for i in 0..bytes_len {
    // Clear the number of offset bits
    let byte  = bytes[i] << offset;
    let zeros = byte.leading_zeros() as usize;

    if byte > 0 {
      index     = i;
      is_parsed = true;
      count    += zeros;
      offset   += zeros + 1;

      if offset >= 8 {
        index  += 1;
        offset -= 8;
      }

      break;
    } else {
      count += zeros - offset;
      offset = 0;
    }
  }

  if is_parsed {
    IResult::Done((&bytes[index..], offset), count as u32)
  } else if index + 1 > bytes_len {
    IResult::Incomplete(Needed::Size(index + 1))
  } else {
    IResult::Error(Err::Position(ErrorCode::TakeUntil as u32, bytes))
  }
}

fn header(input: (&[u8], usize)) -> IResult<(&[u8], usize), (u8, bool)> {
  let result = chain!(input,
    bit_padding: take_bits!(u8, 1) ~
    subframe_type: take_bits!(u8, 6) ~
    wasted_bit_flag: take_bits!(u8, 1),
    || {
      let is_valid        = bit_padding == 0;
      let has_wasted_bits = wasted_bit_flag == 1;

      (is_valid, subframe_type, has_wasted_bits)
    }
  );

  match result {
    IResult::Done(i, data)    => {
      let (is_valid, subframe_type, has_wasted_bits) = data;

      if is_valid {
        IResult::Done(i, (subframe_type, has_wasted_bits))
      } else {
        IResult::Error(Err::Position(ErrorCode::Digit as u32, input.0))
      }
    }
    IResult::Error(error)     => IResult::Error(error),
    IResult::Incomplete(need) => IResult::Incomplete(need),
  }
}

fn data<'a>(input: (&'a [u8], usize),
            frame_header: &frame::Header,
            subframe_type: usize,
            wasted_bits: usize)
            -> IResult<'a, (&'a [u8], usize), subframe::Data> {
  let bits_per_sample = frame_header.bits_per_sample - wasted_bits;
  let block_size      = frame_header.block_size as usize;

  match subframe_type {
    0b000000 => constant(input, bits_per_sample),
    _        => IResult::Error(Err::Position(ErrorCode::Alt as u32, input.0))
  }
}

fn constant(input: (&[u8], usize), bits_per_sample: usize)
            -> IResult<(&[u8], usize), subframe::Data> {
  map!(input, take_bits!(i32, bits_per_sample), subframe::Data::Constant)
}
<commit_msg>Add `Verbatim` subframe data parser<commit_after>use nom::{
  IResult,
  ErrorCode, Err,
  Needed,
};

use frame;
use frame::subframe;

fn leading_zeros(input: (&[u8], usize)) -> IResult<(&[u8], usize), u32> {
  let (bytes, mut offset) = input;

  let mut index     = 0;
  let mut count     = 0;
  let mut is_parsed = false;
  let bytes_len     = bytes.len();

  for i in 0..bytes_len {
    // Clear the number of offset bits
    let byte  = bytes[i] << offset;
    let zeros = byte.leading_zeros() as usize;

    if byte > 0 {
      index     = i;
      is_parsed = true;
      count    += zeros;
      offset   += zeros + 1;

      if offset >= 8 {
        index  += 1;
        offset -= 8;
      }

      break;
    } else {
      count += zeros - offset;
      offset = 0;
    }
  }

  if is_parsed {
    IResult::Done((&bytes[index..], offset), count as u32)
  } else if index + 1 > bytes_len {
    IResult::Incomplete(Needed::Size(index + 1))
  } else {
    IResult::Error(Err::Position(ErrorCode::TakeUntil as u32, bytes))
  }
}

fn header(input: (&[u8], usize)) -> IResult<(&[u8], usize), (u8, bool)> {
  let result = chain!(input,
    bit_padding: take_bits!(u8, 1) ~
    subframe_type: take_bits!(u8, 6) ~
    wasted_bit_flag: take_bits!(u8, 1),
    || {
      let is_valid        = bit_padding == 0;
      let has_wasted_bits = wasted_bit_flag == 1;

      (is_valid, subframe_type, has_wasted_bits)
    }
  );

  match result {
    IResult::Done(i, data)    => {
      let (is_valid, subframe_type, has_wasted_bits) = data;

      if is_valid {
        IResult::Done(i, (subframe_type, has_wasted_bits))
      } else {
        IResult::Error(Err::Position(ErrorCode::Digit as u32, input.0))
      }
    }
    IResult::Error(error)     => IResult::Error(error),
    IResult::Incomplete(need) => IResult::Incomplete(need),
  }
}

fn data<'a>(input: (&'a [u8], usize),
            frame_header: &frame::Header,
            subframe_type: usize,
            wasted_bits: usize)
            -> IResult<'a, (&'a [u8], usize), subframe::Data> {
  let bits_per_sample = frame_header.bits_per_sample - wasted_bits;
  let block_size      = frame_header.block_size as usize;

  match subframe_type {
    0b000000 => constant(input, bits_per_sample),
    _        => IResult::Error(Err::Position(ErrorCode::Alt as u32, input.0))
  }
}

fn constant(input: (&[u8], usize), bits_per_sample: usize)
            -> IResult<(&[u8], usize), subframe::Data> {
  map!(input, take_bits!(i32, bits_per_sample), subframe::Data::Constant)
}

fn verbatim(input: (&[u8], usize), bits_per_sample: usize, block_size: usize)
            -> IResult<(&[u8], usize), subframe::Data> {
  // TODO: Use nom's `count!` macro as soon as it is fixed for bit parsers.
  map!(input, count_bits!(take_bits!(i32, bits_per_sample), block_size),
       subframe::Data::Verbatim)
}
<|endoftext|>


<commit_before>use std::path::Path;
use std::collections::BTreeMap;

use serde_json;
use handlebars::{Context, Handlebars, Helper, RenderContext, RenderError, Renderable};

// Handlebars helper for navigation

pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    debug!("[fn]: previous (handlebars helper)");

    debug!("[*]: Get data from context");
    let chapters = rc.evaluate_absolute("chapters").and_then(|c| {
        serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
            .map_err(|_| RenderError::new("Could not decode the JSON data"))
    })?;

    let current = rc.evaluate_absolute("path")?
                    .as_str()
                    .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
                    .replace("\"", "");

    let mut previous: Option<BTreeMap<String, String>> = None;

    debug!("[*]: Search for current Chapter");
    // Search for current chapter and return previous entry
    for item in chapters {
        match item.get("path") {
            Some(path) if !path.is_empty() => {
                if path == &current {
                    debug!("[*]: Found current chapter");
                    if let Some(previous) = previous {
                        debug!("[*]: Creating BTreeMap to inject in context");
                        // Create new BTreeMap to extend the context: 'title' and 'link'
                        let mut previous_chapter = BTreeMap::new();

                        // Chapter title
                        previous.get("name")
                                .ok_or_else(|| {
                                                RenderError::new("No title found for chapter in \
                                                                  JSON data")
                                            })
                                .and_then(|n| {
                                              previous_chapter.insert("title".to_owned(), json!(n));
                                              Ok(())
                                          })?;

                        // Chapter link
                        previous.get("path")
                                .ok_or_else(|| {
                                                RenderError::new("No path found for chapter in \
                                                                  JSON data")
                                            })
                                .and_then(|p| {
                            Path::new(p).with_extension("html")
                                        .to_str()
                                        .ok_or_else(|| {
                                                        RenderError::new("Link could not be \
                                                                          converted to str")
                                                    })
                                        .and_then(|p| {
                                previous_chapter
                                            .insert("link".to_owned(), json!(p.replace("\\", "/")));
                                Ok(())
                            })
                        })?;

                        debug!("[*]: Render template");
                        // Render template
                        _h.template()
                          .ok_or_else(|| RenderError::new("Error with the handlebars template"))
                          .and_then(|t| {
                            let mut local_rc = rc.with_context(Context::wraps(&previous_chapter)?);
                            t.render(r, &mut local_rc)
                        })?;
                    }
                    break;
                } else {
                    previous = Some(item.clone());
                }
            }
            _ => continue,
        }
    }

    Ok(())
}

pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    debug!("[fn]: next (handlebars helper)");

    debug!("[*]: Get data from context");
    let chapters = rc.evaluate_absolute("chapters").and_then(|c| {
        serde_json::value::from_value::<Vec<BTreeMap<String, String>>>(c.clone())
            .map_err(|_| RenderError::new("Could not decode the JSON data"))
    })?;
    let current = rc.evaluate_absolute("path")?
                    .as_str()
                    .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
                    .replace("\"", "");

    let mut previous: Option<BTreeMap<String, String>> = None;

    debug!("[*]: Search for current Chapter");
    // Search for current chapter and return previous entry
    for item in chapters {
        match item.get("path") {
            Some(path) if !path.is_empty() => {
                if let Some(previous) = previous {
                    let previous_path = previous.get("path").ok_or_else(|| {
                        RenderError::new("No path found for chapter in JSON data")
                    })?;

                    if previous_path == &current {
                        debug!("[*]: Found current chapter");
                        debug!("[*]: Creating BTreeMap to inject in context");
                        // Create new BTreeMap to extend the context: 'title' and 'link'
                        let mut next_chapter = BTreeMap::new();

                        item.get("name")
                            .ok_or_else(|| {
                                            RenderError::new("No title found for chapter in JSON \
                                                              data")
                                        })
                            .and_then(|n| {
                                          next_chapter.insert("title".to_owned(), json!(n));
                                          Ok(())
                                      })?;

                        Path::new(path).with_extension("html")
                                       .to_str()
                                       .ok_or_else(|| {
                                                       RenderError::new("Link could not converted \
                                                                         to str")
                                                   })
                                       .and_then(|l| {
                            debug!("[*]: Inserting link: {:?}", l);
                            // Hack for windows who tends to use `\` as separator instead of `/`
                            next_chapter.insert("link".to_owned(), json!(l.replace("\\", "/")));
                            Ok(())
                        })?;

                        debug!("[*]: Render template");

                        // Render template
                        _h.template()
                          .ok_or_else(|| RenderError::new("Error with the handlebars template"))
                          .and_then(|t| {
                            let mut local_rc = rc.with_context(Context::wraps(&next_chapter)?);
                            t.render(r, &mut local_rc)
                        })?;
                        break;
                    }
                }

                previous = Some(item.clone());
            }

            _ => continue,
        }
    }
    Ok(())
}
<commit_msg>Refactor navigation helpers (#465)<commit_after>use std::path::Path;
use std::collections::BTreeMap;

use serde_json;
use handlebars::{Context, Handlebars, Helper, RenderContext, RenderError, Renderable};

type StringMap = BTreeMap<String, String>;

/// Target for `find_chapter`.
enum Target {
    Previous,
    Next,
}

impl Target {
    /// Returns target if found.
    fn find(&self,
            base_path: &String,
            current_path: &String,
            current_item: &StringMap,
            previous_item: &StringMap,
            ) -> Result<Option<StringMap>, RenderError> {
        match self {
            &Target::Next => {
                let previous_path = previous_item.get("path").ok_or_else(|| {
                    RenderError::new("No path found for chapter in JSON data")
                })?;

                if previous_path == base_path {
                    return Ok(Some(current_item.clone()));
                }
            },

            &Target::Previous => {
                if current_path == base_path {
                    return Ok(Some(previous_item.clone()));
                }
            }
        }

        Ok(None)
    }
}

fn find_chapter(
    rc: &mut RenderContext,
    target: Target
) -> Result<Option<StringMap>, RenderError> {
    debug!("[*]: Get data from context");

    let chapters = rc.evaluate_absolute("chapters").and_then(|c| {
        serde_json::value::from_value::<Vec<StringMap>>(c.clone())
            .map_err(|_| RenderError::new("Could not decode the JSON data"))
    })?;

    let base_path = rc.evaluate_absolute("path")?
                      .as_str()
                      .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
                      .replace("\"", "");

    let mut previous: Option<StringMap> = None;

    debug!("[*]: Search for chapter");

    for item in chapters {
        match item.get("path") {
            Some(path) if !path.is_empty() => {
                if let Some(previous) = previous {
                    if let Some(item) = target.find(&base_path, &path, &item, &previous)? {
                        return Ok(Some(item));
                    }
                }

                previous = Some(item.clone());
            }
            _ => continue,
        }
    }

   Ok(None)
}

fn render(
    _h: &Helper,
    r: &Handlebars,
    rc: &mut RenderContext,
    chapter: &StringMap,
) -> Result<(), RenderError> {
    debug!("[*]: Creating BTreeMap to inject in context");

    let mut context = BTreeMap::new();

    chapter.get("name")
            .ok_or_else(|| RenderError::new("No title found for chapter in JSON data"))
            .map(|name| context.insert("title".to_owned(), json!(name)))?;

    chapter.get("path")
            .ok_or_else(|| RenderError::new("No path found for chapter in JSON data"))
            .and_then(|p| {
                    Path::new(p).with_extension("html")
                    .to_str()
                    .ok_or_else(|| RenderError::new("Link could not be converted to str"))
                    .map(|p| context.insert("link".to_owned(), json!(p.replace("\\", "/"))))
            })?;

    debug!("[*]: Render template");

    _h.template()
        .ok_or_else(|| RenderError::new("Error with the handlebars template"))
        .and_then(|t| {
            let mut local_rc = rc.with_context(Context::wraps(&context)?);
            t.render(r, &mut local_rc)
        })?;

    Ok(())
}

pub fn previous(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    debug!("[fn]: previous (handlebars helper)");

    if let Some(previous) = find_chapter(rc, Target::Previous)? {
        render(_h, r, rc, &previous)?;
    }

    Ok(())
}

pub fn next(_h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    debug!("[fn]: next (handlebars helper)");

    if let Some(next) = find_chapter(rc, Target::Next)? {
        render(_h, r, rc, &next)?;
    }

    Ok(())
}

#[cfg(test)]
mod tests {
   use super::*;

   static TEMPLATE: &'static str =
      "{{#previous}}{{title}}: {{link}}{{/previous}}|{{#next}}{{title}}: {{link}}{{/next}}";

   #[test]
   fn test_next_previous() {
      let data = json!({
         "name": "two",
         "path": "two.path",
         "chapters": [
            {
               "name": "one",
               "path": "one.path"
            },
            {
               "name": "two",
               "path": "two.path",
            },
            {
               "name": "three",
               "path": "three.path"
            }
         ]
      });

      let mut h = Handlebars::new();
      h.register_helper("previous", Box::new(previous));
      h.register_helper("next", Box::new(next));

      assert_eq!(
         h.template_render(TEMPLATE, &data).unwrap(),
         "one: one.html|three: three.html");
   }

   #[test]
   fn test_first() {
      let data = json!({
         "name": "one",
         "path": "one.path",
         "chapters": [
            {
               "name": "one",
               "path": "one.path"
            },
            {
               "name": "two",
               "path": "two.path",
            },
            {
               "name": "three",
               "path": "three.path"
            }
         ]
      });

      let mut h = Handlebars::new();
      h.register_helper("previous", Box::new(previous));
      h.register_helper("next", Box::new(next));

      assert_eq!(
         h.template_render(TEMPLATE, &data).unwrap(),
         "|two: two.html");
   }
   #[test]
   fn test_last() {
      let data = json!({
         "name": "three",
         "path": "three.path",
         "chapters": [
            {
               "name": "one",
               "path": "one.path"
            },
            {
               "name": "two",
               "path": "two.path",
            },
            {
               "name": "three",
               "path": "three.path"
            }
         ]
      });

      let mut h = Handlebars::new();
      h.register_helper("previous", Box::new(previous));
      h.register_helper("next", Box::new(next));

      assert_eq!(
         h.template_render(TEMPLATE, &data).unwrap(),
         "two: two.html|");
   }
}
<|endoftext|>


<commit_before>use chrono::*;
use http_status::HttpStatus;

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Request {
    pub id: i32,
    pub time: DateTime<FixedOffset>,
    pub url: String,
    pub original_log_line: String
}

impl Request {
    pub fn new_from_log_line(log_line: &String) -> Result<Request, &'static str> {
        let parts: Vec<&str> = log_line.split(" ").collect();

        let id = match parts.get(2) {
            Some(id) =>  id,
            None => return Err("Uncomprehensible request logline")
        };

        // Shortest valid id format is "[1]"
        if id.len() < 3 {
            return Err("Uncomprehensible request logline");
        }

        let id_parsed: i32 = match id[1..id.len()-1].parse() {
            Ok(id) =>  id,
            Err(_) => return Err("Uncomprehensible request logline")
        };

        let url = match parts.get(5) {
            Some(url) =>  url,
            None => return Err("Uncomprehensible request logline")
        };

        let date = &format!("{} {}", parts[0], parts[1]);

        let date_parsed = match DateTime::parse_from_str(date, "%d/%b/%Y:%H:%M:%S %z") {
            Ok(date_time) => date_time,
            Err(_) => return Err("Uncomprehensible request logline")
        };

        Ok(Request {
            id: id_parsed,
            time: date_parsed,
            url: url.to_string(),
            original_log_line: log_line.clone()
        })
    }
}

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Response {
    pub id: i32,
    pub time: DateTime<FixedOffset>,
    pub mime_type: String,
    pub response_time: Duration,
    pub http_status: HttpStatus,
    pub original_log_line: String,
}

impl Response {
    pub fn new_from_log_line(log_line: &String) -> Result<Response, &'static str> {
        let parts: Vec<&str> = log_line.split(" ").collect();

        let id = parts[2];

        // Shortest valid id format is "[1]"
        if id.len() < 3 {
            return Err("Uncomprehensible response logline");
        }

        let id_numeric: i32 = match id[1..id.len()-1].parse() {
            Ok(number) => number,
            Err(_) => return Err("Uncomprehensible response logline")
        };

        let time = match DateTime::parse_from_str(&format!("{} {}", parts[0], parts[1]), "%d/%b/%Y:%H:%M:%S %z") {
            Ok(time) => time,
            Err(_) => return Err("Uncomprehensible response logline")
        };

        let response_time = parts[parts.len()-1];
        if response_time.len() < 3 {
            return Err("Uncomprehensible response logline");
        }

        let response_time_duration = match response_time[0..response_time.len()-2].parse() {
            Ok(number) => Duration::milliseconds(number),
            Err(_) => return Err("Uncomprehensible response logline")
        };

        // Handle special case where the mime type sometimes contains
        // a space, so we need to re-assemble it
        let mime_type = match parts.len() {
            8 => format!("{} {}", parts[5], parts[6]),
            _ => parts[5].to_string()
        };

        let status_code = match parts[4].parse() {
            Ok(number) => HttpStatus::from_code(number),
            Err(_) => return Err("Uncomprehensible response logline")
        };

        Ok(Response {
            id: id_numeric,
            time: time,
            response_time: response_time_duration,
            mime_type: mime_type,
            http_status: status_code,
            original_log_line: log_line.clone(),
        })
    }
}

#[cfg(test)]
mod tests {
    use::chrono::*;
    use http_status::HttpStatus;

    use super::*;

    #[test]
    fn test_parse_request_line() {
        let line = "08/Apr/2016:09:58:47 +0200 [02] -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected = Request {
            id: 2,
            time: DateTime::parse_from_str("08/Apr/2016:09:58:47 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            url: "/content/some/other.html".to_string(),
            original_log_line: line.clone()
        };

        let result = Request::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_response_line() {
        let line = "08/Apr/2016:09:58:48 +0200 [02] <- 200 text/html 10ms".to_string();

        let expected = Response {
            id: 2,
            time: DateTime::parse_from_str("08/Apr/2016:09:58:48 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            mime_type: "text/html".to_string(),
            response_time: Duration::milliseconds(10),
            http_status: HttpStatus::OK,
            original_log_line: line.clone(),
        };

        let result = Response::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

}
<commit_msg>Copy Request/Response parse tests into new module<commit_after>//! A module to parse invidual log lines into Requests and Responses

use chrono::*;
use http_status::HttpStatus;

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Request {
    pub id: i32,
    pub time: DateTime<FixedOffset>,
    pub url: String,
    pub original_log_line: String
}

impl Request {
    pub fn new_from_log_line(log_line: &String) -> Result<Request, &'static str> {
        let parts: Vec<&str> = log_line.split(" ").collect();

        let id = match parts.get(2) {
            Some(id) =>  id,
            None => return Err("Uncomprehensible request logline")
        };

        // Shortest valid id format is "[1]"
        if id.len() < 3 {
            return Err("Uncomprehensible request logline");
        }

        let id_parsed: i32 = match id[1..id.len()-1].parse() {
            Ok(id) =>  id,
            Err(_) => return Err("Uncomprehensible request logline")
        };

        let url = match parts.get(5) {
            Some(url) =>  url,
            None => return Err("Uncomprehensible request logline")
        };

        let date = &format!("{} {}", parts[0], parts[1]);

        let date_parsed = match DateTime::parse_from_str(date, "%d/%b/%Y:%H:%M:%S %z") {
            Ok(date_time) => date_time,
            Err(_) => return Err("Uncomprehensible request logline")
        };

        Ok(Request {
            id: id_parsed,
            time: date_parsed,
            url: url.to_string(),
            original_log_line: log_line.clone()
        })
    }
}

#[derive(Eq, PartialEq, Clone)]
#[derive(Debug)]
pub struct Response {
    pub id: i32,
    pub time: DateTime<FixedOffset>,
    pub mime_type: String,
    pub response_time: Duration,
    pub http_status: HttpStatus,
    pub original_log_line: String,
}

impl Response {
    pub fn new_from_log_line(log_line: &String) -> Result<Response, &'static str> {
        let parts: Vec<&str> = log_line.split(" ").collect();

        let id = parts[2];

        // Shortest valid id format is "[1]"
        if id.len() < 3 {
            return Err("Uncomprehensible response logline");
        }

        let id_numeric: i32 = match id[1..id.len()-1].parse() {
            Ok(number) => number,
            Err(_) => return Err("Uncomprehensible response logline")
        };

        let time = match DateTime::parse_from_str(&format!("{} {}", parts[0], parts[1]), "%d/%b/%Y:%H:%M:%S %z") {
            Ok(time) => time,
            Err(_) => return Err("Uncomprehensible response logline")
        };

        let response_time = parts[parts.len()-1];
        if response_time.len() < 3 {
            return Err("Uncomprehensible response logline");
        }

        let response_time_duration = match response_time[0..response_time.len()-2].parse() {
            Ok(number) => Duration::milliseconds(number),
            Err(_) => return Err("Uncomprehensible response logline")
        };

        // Handle special case where the mime type sometimes contains
        // a space, so we need to re-assemble it
        let mime_type = match parts.len() {
            8 => format!("{} {}", parts[5], parts[6]),
            _ => parts[5].to_string()
        };

        let status_code = match parts[4].parse() {
            Ok(number) => HttpStatus::from_code(number),
            Err(_) => return Err("Uncomprehensible response logline")
        };

        Ok(Response {
            id: id_numeric,
            time: time,
            response_time: response_time_duration,
            mime_type: mime_type,
            http_status: status_code,
            original_log_line: log_line.clone(),
        })
    }
}

#[cfg(test)]
mod tests {
    use::chrono::*;
    use http_status::HttpStatus;

    use super::*;

    #[test]
    fn test_parse_request_line() {
        let line = "08/Apr/2016:09:58:47 +0200 [02] -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected = Request {
            id: 2,
            time: DateTime::parse_from_str("08/Apr/2016:09:58:47 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            url: "/content/some/other.html".to_string(),
            original_log_line: line.clone()
        };

        let result = Request::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_request_line_bad_format() {
        let line = "08/A16:09:58:47 justsomegarbage".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_request_line_bad_format_but_enough_parts() {
        let line = "just some garbage with more parts at the end".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_request_line_bad_date_format() {
        let line = "99/XYZ/9999:09:99:99 +9900 [02] -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_request_line_bad_id_format() {
        let line = "08/Apr/2016:09:58:47 +0200 2 -> GET /content/some/other.html HTTP/1.1".to_string();

        let expected: Result<Request, &'static str> = Err("Uncomprehensible request logline");
        let result: Result<Request, &'static str> = Request::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line() {
        let line = "08/Apr/2016:09:58:48 +0200 [02] <- 200 text/html 10ms".to_string();

        let expected = Response {
            id: 2,
            time: DateTime::parse_from_str("08/Apr/2016:09:58:48 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            mime_type: "text/html".to_string(),
            response_time: Duration::milliseconds(10),
            http_status: HttpStatus::OK,
            original_log_line: line.clone(),
        };

        let result = Response::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_response_line_inconsistent_space() {
        let line = "06/Apr/2016:14:54:16 +0200 [200] <- 200 text/html; charset=utf-8 250ms".to_string();

        let expected = Response {
            id: 200,
            time: DateTime::parse_from_str("06/Apr/2016:14:54:16 +0200", "%d/%b/%Y:%H:%M:%S %z").unwrap(),
            mime_type: "text/html; charset=utf-8".to_string(),
            response_time: Duration::milliseconds(250),
            http_status: HttpStatus::OK,
            original_log_line: line.clone(),
        };

        let result = Response::new_from_log_line(&line);

        assert_eq!(result.unwrap(), expected)
    }

    #[test]
    fn test_parse_response_line_bad_id_format() {
        let line = "08/Apr/2016:09:58:48 +0200 2 <- 200 text/html 10ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_id_format_no_number() {
        let line = "08/Apr/2016:09:58:48 +0200 [XXX] <- 200 text/html 10ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_time_format() {
        let line = "08/Apr/2016:5:2X:48 +0200 [83940] <- 200 text/html 14642ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_response_time_too_short() {
        let line = "08/Apr/2016:09:57:47 +0200 [001] <- 200 text/html X".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_response_time_not_a_number() {
        let line = "08/Apr/2016:09:57:47 +0200 [001] <- 200 text/html XXXms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }

    #[test]
    fn test_parse_response_line_bad_status_code() {
        let line = "08/Apr/2016:09:57:47 +0200 [001] <- FOO text/html 10ms".to_string();

        let expected: Result<Response, &'static str> = Err("Uncomprehensible response logline");
        let result: Result<Response, &'static str> = Response::new_from_log_line(&line);

        assert_eq!(result.is_err(), true);
        assert_eq!(result, expected);
    }
}
<|endoftext|>