Artifact Content
Not logged in

Artifact a4cb9e9704c39d9b27b2f1d73c50cc6e8d454178:


use std::cell::RefCell;
use std::io::{self, Write};
use std::{ops, str};

use rustc::hir::def_id::DefId;
use rustc::mir::{self, BasicBlock, Body, Location};
use rustc_index::bit_set::{BitSet, HybridBitSet};
use rustc_index::vec::Idx;

use crate::util::graphviz_safe_def_name;
use super::{Analysis, Results, ResultsRefCursor};

pub struct Formatter<'a, 'tcx, A>
where
    A: Analysis<'tcx>,
{
    body: &'a Body<'tcx>,
    def_id: DefId,

    // This must be behind a `RefCell` because `dot::Labeller` takes `&self`.
    block_formatter: RefCell<BlockFormatter<'a, 'tcx, A>>,
}

impl<A> Formatter<'a, 'tcx, A>
where
    A: Analysis<'tcx>,
{
    pub fn new(
        body: &'a Body<'tcx>,
        def_id: DefId,
        results: &'a Results<'tcx, A>,
    ) -> Self {
        let block_formatter = BlockFormatter {
            bg: Background::Light,
            prev_state: BitSet::new_empty(results.analysis.bits_per_block(body)),
            results: ResultsRefCursor::new(body, results),
        };

        Formatter {
            body,
            def_id,
            block_formatter: RefCell::new(block_formatter),
        }
    }
}

/// A pair of a basic block and an index into that basic blocks `successors`.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct CfgEdge {
    source: BasicBlock,
    index: usize,
}

fn outgoing_edges(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
    body[bb]
        .terminator()
        .successors()
        .enumerate()
        .map(|(index, _)| CfgEdge { source: bb, index })
        .collect()
}

impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
where
    A: Analysis<'tcx>,
{
    type Node = BasicBlock;
    type Edge = CfgEdge;

    fn graph_id(&self) -> dot::Id<'_> {
        let name = graphviz_safe_def_name(self.def_id);
        dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap()
    }

    fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
        dot::Id::new(format!("bb_{}", n.index())).unwrap()
    }

    fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
        let mut label = Vec::new();
        self.block_formatter
            .borrow_mut()
            .write_node_label(&mut label, self.body, *block)
            .unwrap();
        dot::LabelText::html(String::from_utf8(label).unwrap())
    }

    fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
        Some(dot::LabelText::label("none"))
    }

    fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
        let label = &self.body
            [e.source]
            .terminator()
            .kind
            .fmt_successor_labels()
            [e.index];
        dot::LabelText::label(label.clone())
    }
}

impl<A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A>
where
    A: Analysis<'tcx>,
{
    type Node = BasicBlock;
    type Edge = CfgEdge;

    fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
        self.body
            .basic_blocks()
            .indices()
            .collect::<Vec<_>>()
            .into()
    }

    fn edges(&self) -> dot::Edges<'_, Self::Edge> {
        self.body
            .basic_blocks()
            .indices()
            .flat_map(|bb| outgoing_edges(self.body, bb))
            .collect::<Vec<_>>()
            .into()
    }

    fn source(&self, edge: &Self::Edge) -> Self::Node {
        edge.source
    }

    fn target(&self, edge: &Self::Edge) -> Self::Node {
        self.body
            [edge.source]
            .terminator()
            .successors()
            .nth(edge.index)
            .copied()
            .unwrap()
    }
}

struct BlockFormatter<'a, 'tcx, A>
where
    A: Analysis<'tcx>,
{
    prev_state: BitSet<A::Idx>,
    results: ResultsRefCursor<'a, 'a, 'tcx, A>,
    bg: Background,
}

impl<A> BlockFormatter<'a, 'tcx, A>
where
    A: Analysis<'tcx>,
{
    fn toggle_background(&mut self) -> Background {
        let bg = self.bg;
        self.bg = !bg;
        bg
    }

    fn write_node_label(
        &mut self,
        w: &mut impl io::Write,
        body: &'a Body<'tcx>,
        block: BasicBlock,
    ) -> io::Result<()> {
        //   Sample output:
        //   +-+-----------------------------------------------+
        // A |                      bb4                        |
        //   +-+----------------------------------+------------+
        // B |                MIR                 |   STATE    |
        //   +-+----------------------------------+------------+
        // C | | (on entry)                       | {_0,_2,_3} |
        //   +-+----------------------------------+------------+
        // D |0| StorageLive(_7)                  |            |
        //   +-+----------------------------------+------------+
        //   |1| StorageLive(_8)                  |            |
        //   +-+----------------------------------+------------+
        //   |2| _8 = &mut _1                     | +_8        |
        //   +-+----------------------------------+------------+
        // E |T| _4 = const Foo::twiddle(move _2) | -_2        |
        //   +-+----------------------------------+------------+
        // F | | (on unwind)                      | {_0,_3,_8} |
        //   +-+----------------------------------+------------+
        //   | | (on successful return)           | +_4        |
        //   +-+----------------------------------+------------+

        write!(
            w,
            r#"<table border="1" cellborder="1" cellspacing="0" cellpadding="3" sides="rb">"#,
        )?;

        // A: Block info
        write!(
            w,
            r#"<tr>
                 <td colspan="{num_headers}" sides="tl">bb{block_id}</td>
               </tr>"#,
            num_headers = 3,
            block_id = block.index(),
        )?;

        // B: Column headings
        write!(
            w,
            r#"<tr>
                 <td colspan="2" {fmt}>MIR</td>
                 <td {fmt}>STATE</td>
               </tr>"#,
            fmt = r##"bgcolor="#a0a0a0" sides="tl""##,
        )?;

        // C: Entry state
        self.bg = Background::Light;
        self.results.seek_to_block_start(block);
        self.write_row_with_curr_state(w, "", "(on entry)")?;
        self.prev_state.overwrite(self.results.get());

        // D: Statement transfer functions
        for (i, statement) in body[block].statements.iter().enumerate() {
            let location = Location { block, statement_index: i };

            let mir_col = format!("{:?}", statement);
            let i_col = i.to_string();

            self.results.seek_after(location);
            self.write_row_with_curr_diff(w, &i_col, &mir_col)?;
            self.prev_state.overwrite(self.results.get());
        }

        // E: Terminator transfer function
        let terminator = body[block].terminator();
        let location = body.terminator_loc(block);

        let mut mir_col = String::new();
        terminator.kind.fmt_head(&mut mir_col).unwrap();

        self.results.seek_after(location);
        self.write_row_with_curr_diff(w, "T", &mir_col)?;
        self.prev_state.overwrite(self.results.get());

        // F: Exit state
        if let mir::TerminatorKind::Call { destination: Some(_), ..  } = &terminator.kind {
            self.write_row_with_curr_state(w, "", "(on unwind)")?;

            self.results.seek_after_assume_call_returns(location);
            self.write_row_with_curr_diff(w, "", "(on successful return)")?;
        } else {
            self.write_row_with_curr_state(w, "", "(on exit)")?;
        }

        write!(w, "</table>")
    }

    fn write_row_with_curr_state(
        &mut self,
        w: &mut impl io::Write,
        i: &str,
        mir: &str,
    ) -> io::Result<()> {
        let bg = self.toggle_background();

        let mut out = Vec::new();
        write!(&mut out, "{{")?;
        pretty_print_state_elems(&mut out, self.results.analysis(), self.results.get().iter())?;
        write!(&mut out, "}}")?;

        write!(
            w,
            r#"<tr>
                 <td {fmt} align="right">{i}</td>
                 <td {fmt} align="left">{mir}</td>
                 <td {fmt} align="left">{state}</td>
               </tr>"#,
            fmt = &["sides=\"tl\"", bg.attr()].join(" "),
            i = i,
            mir = dot::escape_html(mir),
            state = dot::escape_html(str::from_utf8(&out).unwrap()),
        )
    }

    fn write_row_with_curr_diff(
        &mut self,
        w: &mut impl io::Write,
        i: &str,
        mir: &str,
    ) -> io::Result<()> {
        let bg = self.toggle_background();
        let analysis = self.results.analysis();

        let diff = BitSetDiff::compute(&self.prev_state, self.results.get());

        let mut set = Vec::new();
        pretty_print_state_elems(&mut set, analysis, diff.set.iter())?;

        let mut clear = Vec::new();
        pretty_print_state_elems(&mut clear, analysis, diff.clear.iter())?;

        write!(
            w,
            r#"<tr>
                 <td {fmt} align="right">{i}</td>
                 <td {fmt} align="left">{mir}</td>
                 <td {fmt} align="left">"#,
            i = i,
            fmt = &["sides=\"tl\"", bg.attr()].join(" "),
            mir = dot::escape_html(mir),
        )?;

        if !set.is_empty() {
            write!(
                w,
                r#"<font color="darkgreen">+{}</font>"#,
                dot::escape_html(str::from_utf8(&set).unwrap()),
            )?;
        }

        if !set.is_empty() && !clear.is_empty() {
            write!(w, "  ")?;
        }

        if !clear.is_empty() {
            write!(
                w,
                r#"<font color="red">-{}</font>"#,
                dot::escape_html(str::from_utf8(&clear).unwrap()),
            )?;
        }

        write!(w, "</td></tr>")
    }
}

/// The operations required to transform one `BitSet` into another.
struct BitSetDiff<T: Idx> {
    set: HybridBitSet<T>,
    clear: HybridBitSet<T>,
}

impl<T: Idx> BitSetDiff<T> {
    fn compute(from: &BitSet<T>, to: &BitSet<T>) -> Self {
        assert_eq!(from.domain_size(), to.domain_size());
        let len = from.domain_size();

        let mut set = HybridBitSet::new_empty(len);
        let mut clear = HybridBitSet::new_empty(len);

        // FIXME: This could be made faster if `BitSet::xor` were implemented.
        for i in (0..len).map(|i| T::new(i)) {
            match (from.contains(i), to.contains(i)) {
                (false, true) => set.insert(i),
                (true, false) => clear.insert(i),
                _ => continue,
            };
        }

        BitSetDiff {
            set,
            clear,
        }
    }
}

/// Formats each `elem` using the pretty printer provided by `analysis` into a comma-separated
/// list.
fn pretty_print_state_elems<A>(
    w: &mut impl io::Write,
    analysis: &A,
    elems: impl Iterator<Item = A::Idx>,
) -> io::Result<()>
where
    A: Analysis<'tcx>,
{
    let mut first = true;
    for idx in elems {
        if first {
            first = false;
        } else {
            write!(w, ",")?;
        }

        analysis.pretty_print_idx(w, idx)?;
    }

    Ok(())
}

/// The background color used for zebra-striping the table.
#[derive(Clone, Copy)]
enum Background {
    Light,
    Dark,
}

impl Background {
    fn attr(self) -> &'static str {
        match self {
            Self::Dark => "bgcolor=\"#f0f0f0\"",
            Self::Light => "",
        }
    }
}

impl ops::Not for Background {
    type Output = Self;

    fn not(self) -> Self {
        match self {
            Self::Light => Self::Dark,
            Self::Dark => Self::Light,
        }
    }
}