<commit_before>/*
#[macro_use]
extern crate nom;
extern crate bytes;

use nom::{Compare,CompareResult,InputLength,InputIter,Slice,HexDisplay};

use std::str;
use std::str::FromStr;
use bytes::{Buf,MutBuf};
use bytes::buf::{BlockBuf,BlockBufCursor};
use std::ops::{Range,RangeTo,RangeFrom,RangeFull};
use std::iter::{Enumerate,Iterator};
use std::fmt;
use std::cmp::{min,PartialEq};

#[derive(Clone,Copy)]
#[repr(C)]
pub struct BlockSlice<'a> {
  buf: &'a BlockBuf,
  start: usize,
  end:   usize,
}

impl<'a> BlockSlice<'a> {
  fn cursor(&self) -> WrapCursor<'a> {
    let mut cur = self.buf.buf();
    cur.advance(self.start);
    WrapCursor {
      cursor: cur,
      length: self.end - self.start,
    }
  }
}

impl<'a> fmt::Debug for BlockSlice<'a> {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "BlockSlice {{ start: {}, end: {}, data:\n{}\n}}", self.start, self.end, self.buf.bytes().unwrap_or(&b""[..]).to_hex(16))
  }
}

impl<'a> PartialEq for BlockSlice<'a> {
  fn eq(&self, other: &BlockSlice<'a>) -> bool {
    let bufs = (self.buf as *const BlockBuf) == (other.buf as *const BlockBuf);
    self.start == other.start && self.end == other.end && bufs
  }
}

impl<'a> Slice<Range<usize>> for BlockSlice<'a> {
  fn slice(&self, range:Range<usize>) -> Self {
    BlockSlice {
      buf:   self.buf,
      start: self.start + range.start,
      //FIXME: check for valid end here
      end:   self.start + range.end,
    }
  }
}

impl<'a> Slice<RangeTo<usize>> for BlockSlice<'a> {
  fn slice(&self, range:RangeTo<usize>) -> Self {
    self.slice(0..range.end)
  }
}

impl<'a> Slice<RangeFrom<usize>> for BlockSlice<'a> {
  fn slice(&self, range:RangeFrom<usize>) -> Self {
    self.slice(range.start..self.end - self.start)
  }
}

impl<'a> Slice<RangeFull> for BlockSlice<'a> {
  fn slice(&self, _:RangeFull) -> Self {
    BlockSlice {
      buf:   self.buf,
      start: self.start,
      end:   self.end,
    }
  }
}

impl<'a> InputIter for BlockSlice<'a> {
    type Item     = u8;
    type RawItem  = u8;
    type Iter     = Enumerate<WrapCursor<'a>>;
    type IterElem = WrapCursor<'a>;

    fn iter_indices(&self)  -> Self::Iter {
      self.cursor().enumerate()
    }
    fn iter_elements(&self) -> Self::IterElem {
      self.cursor()
    }
    fn position<P>(&self, predicate: P) -> Option<usize> where P: Fn(Self::RawItem) -> bool {
      self.cursor().position(|b| predicate(b))
    }
    fn slice_index(&self, count:usize) -> Option<usize> {
      if self.end - self.start >= count {
        Some(count)
      } else {
        None
      }
    }
}

impl<'a> InputLength for BlockSlice<'a> {
  fn input_len(&self) -> usize {
    self.end - self.start
  }
}

impl<'a,'b> Compare<&'b[u8]> for BlockSlice<'a> {
  fn compare(&self, t: &'b[u8]) -> CompareResult {
    let len     = self.end - self.start;
    let blen    = t.len();
    let m       = if len < blen { len } else { blen };
    let reduced = self.slice(..m);
    let b       = &t[..m];

    for (a,b) in reduced.cursor().zip(b.iter()) {
      if a != *b {
        return CompareResult::Error;
      }
    }
    if m < blen {
      CompareResult::Incomplete
    } else {
      CompareResult::Ok
    }
  }

  #[inline(always)]
  fn compare_no_case(&self, t: &'b[u8]) -> CompareResult {
    let len     = self.end - self.start;
    let blen    = t.len();
    let m       = if len < blen { len } else { blen };
    let reduced = self.slice(..m);
    let other   = &t[..m];

    if !reduced.cursor().zip(other).all(|(a, b)| {
      match (a,*b) {
        (0...64, 0...64) | (91...96, 91...96) | (123...255, 123...255) => a == *b,
        (65...90, 65...90) | (97...122, 97...122) | (65...90, 97...122 ) |(97...122, 65...90) => {
          a & 0b01000000 == *b & 0b01000000
        }
        _ => false
      }
    }) {
      CompareResult::Error
    } else if m < blen {
      CompareResult::Incomplete
    } else {
      CompareResult::Ok
    }
  }
}

impl<'a,'b> Compare<&'b str> for BlockSlice<'a> {
  fn compare(&self, t: &'b str) -> CompareResult {
    self.compare(str::as_bytes(t))
  }
  fn compare_no_case(&self, t: &'b str) -> CompareResult {
    self.compare_no_case(str::as_bytes(t))
  }
}

//Wrapper to implement Iterator on BlockBufCursor
pub struct WrapCursor<'a> {
  pub cursor: BlockBufCursor<'a>,
  pub length: usize,
}

impl<'a> Iterator for WrapCursor<'a> {
  type Item = u8;
  fn next(&mut self) -> Option<u8> {
    //println!("NEXT: length={}, remaining={}", self.length, self.cursor.remaining());
    if min(self.length, self.cursor.remaining()) > 0 {
      self.length -=1;
      Some(self.cursor.read_u8())
    } else {
      None
    }
  }
}

//Reimplement eat_separator instead of fixing iterators
#[macro_export]
macro_rules! block_eat_separator (
  ($i:expr, $arr:expr) => (
    {
      use nom::{InputLength,InputIter,Slice};
      if ($i).input_len() == 0 {
        nom::IResult::Done($i, ($i).slice(0..0))
      } else {
        match ($i).iter_indices().position(|(_, item)| {
          for (_,c) in ($arr).iter_indices() {
            if *c == item { return false; }
          }
          true
        }) {
          Some(index) => {
            nom::IResult::Done(($i).slice(index..), ($i).slice(..index))
          },
          None => {
            nom::IResult::Done(($i).slice(($i).input_len()..), $i)
          }
        }
      }
    }
  )
);

#[macro_export]
macro_rules! block_named (
  ($name:ident, $submac:ident!( $($args:tt)* )) => (
    fn $name<'a>( i: BlockSlice<'a> ) -> nom::IResult<BlockSlice<'a>, BlockSlice<'a>, u32> {
      $submac!(i, $($args)*)
    }
  );
  ($name:ident<$o:ty>, $submac:ident!( $($args:tt)* )) => (
    fn $name<'a>( i: BlockSlice<'a> ) -> nom::IResult<BlockSlice<'a>, $o, u32> {
      $submac!(i, $($args)*)
    }
  );
);

block_named!(sp, block_eat_separator!(&b" \t\r\n"[..]));

macro_rules! block_ws (
  ($i:expr, $($args:tt)*) => (
    {
      sep!($i, sp, $($args)*)
    }
  )
);

block_named!(digit, is_a!("0123456789"));

block_named!(parens<i64>, block_ws!(delimited!( tag!("("), expr, tag!(")") )) );

block_named!(factor<i64>, alt!(
      map_res!(
        block_ws!(digit),
        to_i64
    )
  | parens
  )
);

block_named!(term <i64>, do_parse!(
    init: factor >>
    res:  fold_many0!(
        pair!(alt!(tag!("*") | tag!("/")), factor),
        init,
        |acc, (op, val): (BlockSlice, i64)| {
            if (op.cursor().next().unwrap() as char) == '*' { acc * val } else { acc / val }
        }
    ) >>
    (res)
  )
);

block_named!(expr <i64>, do_parse!(
    init: term >>
    res:  fold_many0!(
        pair!(alt!(tag!("+") | tag!("-")), term),
        init,
        |acc, (op, val): (BlockSlice, i64)| {
            if (op.cursor().next().unwrap() as char) == '+' { acc + val } else { acc - val }
        }
    ) >>
    (res)
  )
);

fn blockbuf_from(input: &[u8]) -> BlockBuf {
  let mut b = BlockBuf::new(2, 100);
  b.copy_from(input);
  b
}

fn sl<'a>(input: &'a BlockBuf) -> BlockSlice<'a> {
  BlockSlice {
    buf: input,
    start: 0,
    end:   input.len(),
  }
}

fn to_i64<'a>(input: BlockSlice<'a>) -> Result<i64, ()> {
  let v: Vec<u8> = input.cursor().collect();

  match str::from_utf8(&v) {
    Err(_) => Err(()),
    Ok(s) => match FromStr::from_str(s) {
      Err(_) => Err(()),
      Ok(i)  => Ok(i)
    }
  }
}

#[test]
fn factor_test() {
  let a = blockbuf_from(&b"3"[..]);
  println!("calculated: {:?}", factor(sl(&a)));
}

#[test]
fn parens_test() {
  let input1 = blockbuf_from(&b" 2* (  3 + 4 ) "[..]);
  println!("calculated 1: {:?}", expr(sl(&input1)));
  let input2 = blockbuf_from(&b"  2*2 / ( 5 - 1) + 3"[..]);
  println!("calculated 2: {:?}", expr(sl(&input2)));
}
*/
<commit_msg>fix the blockbuf arithmetic test<commit_after>/*
#[macro_use]
extern crate nom;
extern crate bytes;

use nom::{Compare,CompareResult,InputLength,InputIter,Slice,HexDisplay};

use std::str;
use std::str::FromStr;
use bytes::{Buf,MutBuf};
use bytes::buf::{BlockBuf,BlockBufCursor};
use std::ops::{Range,RangeTo,RangeFrom,RangeFull};
use std::iter::{Enumerate,Iterator};
use std::fmt;
use std::cmp::{min,PartialEq};

#[derive(Clone,Copy)]
#[repr(C)]
pub struct BlockSlice<'a> {
  buf: &'a BlockBuf,
  start: usize,
  end:   usize,
}

impl<'a> BlockSlice<'a> {
  fn cursor(&self) -> WrapCursor<'a> {
    let mut cur = self.buf.buf();
    cur.advance(self.start);
    WrapCursor {
      cursor: cur,
      length: self.end - self.start,
    }
  }
}

impl<'a> fmt::Debug for BlockSlice<'a> {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "BlockSlice {{ start: {}, end: {}, data:\n{}\n}}", self.start, self.end, self.buf.bytes().unwrap_or(&b""[..]).to_hex(16))
  }
}

impl<'a> PartialEq for BlockSlice<'a> {
  fn eq(&self, other: &BlockSlice<'a>) -> bool {
    let bufs = (self.buf as *const BlockBuf) == (other.buf as *const BlockBuf);
    self.start == other.start && self.end == other.end && bufs
  }
}

impl<'a> Slice<Range<usize>> for BlockSlice<'a> {
  fn slice(&self, range:Range<usize>) -> Self {
    BlockSlice {
      buf:   self.buf,
      start: self.start + range.start,
      //FIXME: check for valid end here
      end:   self.start + range.end,
    }
  }
}

impl<'a> Slice<RangeTo<usize>> for BlockSlice<'a> {
  fn slice(&self, range:RangeTo<usize>) -> Self {
    self.slice(0..range.end)
  }
}

impl<'a> Slice<RangeFrom<usize>> for BlockSlice<'a> {
  fn slice(&self, range:RangeFrom<usize>) -> Self {
    self.slice(range.start..self.end - self.start)
  }
}

impl<'a> Slice<RangeFull> for BlockSlice<'a> {
  fn slice(&self, _:RangeFull) -> Self {
    BlockSlice {
      buf:   self.buf,
      start: self.start,
      end:   self.end,
    }
  }
}

impl<'a> InputIter for BlockSlice<'a> {
    type Item     = u8;
    type RawItem  = u8;
    type Iter     = Enumerate<WrapCursor<'a>>;
    type IterElem = WrapCursor<'a>;

    fn iter_indices(&self)  -> Self::Iter {
      self.cursor().enumerate()
    }
    fn iter_elements(&self) -> Self::IterElem {
      self.cursor()
    }
    fn position<P>(&self, predicate: P) -> Option<usize> where P: Fn(Self::RawItem) -> bool {
      self.cursor().position(|b| predicate(b))
    }
    fn slice_index(&self, count:usize) -> Option<usize> {
      if self.end - self.start >= count {
        Some(count)
      } else {
        None
      }
    }
}

impl<'a> InputLength for BlockSlice<'a> {
  fn input_len(&self) -> usize {
    self.end - self.start
  }
}

impl<'a,'b> Compare<&'b[u8]> for BlockSlice<'a> {
  fn compare(&self, t: &'b[u8]) -> CompareResult {
    let len     = self.end - self.start;
    let blen    = t.len();
    let m       = if len < blen { len } else { blen };
    let reduced = self.slice(..m);
    let b       = &t[..m];

    for (a,b) in reduced.cursor().zip(b.iter()) {
      if a != *b {
        return CompareResult::Error;
      }
    }
    if m < blen {
      CompareResult::Incomplete
    } else {
      CompareResult::Ok
    }
  }

  #[inline(always)]
  fn compare_no_case(&self, t: &'b[u8]) -> CompareResult {
    let len     = self.end - self.start;
    let blen    = t.len();
    let m       = if len < blen { len } else { blen };
    let reduced = self.slice(..m);
    let other   = &t[..m];

    if !reduced.cursor().zip(other).all(|(a, b)| {
      match (a,*b) {
        (0...64, 0...64) | (91...96, 91...96) | (123...255, 123...255) => a == *b,
        (65...90, 65...90) | (97...122, 97...122) | (65...90, 97...122 ) |(97...122, 65...90) => {
          a & 0b01000000 == *b & 0b01000000
        }
        _ => false
      }
    }) {
      CompareResult::Error
    } else if m < blen {
      CompareResult::Incomplete
    } else {
      CompareResult::Ok
    }
  }
}

impl<'a,'b> Compare<&'b str> for BlockSlice<'a> {
  fn compare(&self, t: &'b str) -> CompareResult {
    self.compare(str::as_bytes(t))
  }
  fn compare_no_case(&self, t: &'b str) -> CompareResult {
    self.compare_no_case(str::as_bytes(t))
  }
}

//Wrapper to implement Iterator on BlockBufCursor
pub struct WrapCursor<'a> {
  pub cursor: BlockBufCursor<'a>,
  pub length: usize,
}

impl<'a> Iterator for WrapCursor<'a> {
  type Item = u8;
  fn next(&mut self) -> Option<u8> {
    //println!("NEXT: length={}, remaining={}", self.length, self.cursor.remaining());
    if min(self.length, self.cursor.remaining()) > 0 {
      self.length -=1;
      Some(self.cursor.read_u8())
    } else {
      None
    }
  }
}

//Reimplement eat_separator instead of fixing iterators
#[macro_export]
macro_rules! block_eat_separator (
  ($i:expr, $arr:expr) => (
    {
      use nom::{InputLength,InputIter,Slice};
      if ($i).input_len() == 0 {
        Ok(($i, ($i).slice(0..0)))
      } else {
        match ($i).iter_indices().position(|(_, item)| {
          for (_,c) in ($arr).iter_indices() {
            if *c == item { return false; }
          }
          true
        }) {
          Some(index) => {
            Ok((($i).slice(index..), ($i).slice(..index)))
          },
          None => {
            Ok((($i).slice(($i).input_len()..), $i))
          }
        }
      }
    }
  )
);

#[macro_export]
macro_rules! block_named (
  ($name:ident, $submac:ident!( $($args:tt)* )) => (
    fn $name<'a>( i: BlockSlice<'a> ) -> nom::IResult<BlockSlice<'a>, BlockSlice<'a>, u32> {
      $submac!(i, $($args)*)
    }
  );
  ($name:ident<$o:ty>, $submac:ident!( $($args:tt)* )) => (
    fn $name<'a>( i: BlockSlice<'a> ) -> nom::IResult<BlockSlice<'a>, $o, u32> {
      $submac!(i, $($args)*)
    }
  );
);

block_named!(sp, block_eat_separator!(&b" \t\r\n"[..]));

macro_rules! block_ws (
  ($i:expr, $($args:tt)*) => (
    {
      sep!($i, sp, $($args)*)
    }
  )
);

block_named!(digit, is_a!("0123456789"));

block_named!(parens<i64>, block_ws!(delimited!( tag!("("), expr, tag!(")") )) );

block_named!(factor<i64>, alt!(
      map_res!(
        block_ws!(digit),
        to_i64
    )
  | parens
  )
);

block_named!(term <i64>, do_parse!(
    init: factor >>
    res:  fold_many0!(
        pair!(alt!(tag!("*") | tag!("/")), factor),
        init,
        |acc, (op, val): (BlockSlice, i64)| {
            if (op.cursor().next().unwrap() as char) == '*' { acc * val } else { acc / val }
        }
    ) >>
    (res)
  )
);

block_named!(expr <i64>, do_parse!(
    init: term >>
    res:  fold_many0!(
        pair!(alt!(tag!("+") | tag!("-")), term),
        init,
        |acc, (op, val): (BlockSlice, i64)| {
            if (op.cursor().next().unwrap() as char) == '+' { acc + val } else { acc - val }
        }
    ) >>
    (res)
  )
);

fn blockbuf_from(input: &[u8]) -> BlockBuf {
  let mut b = BlockBuf::new(2, 100);
  b.copy_from(input);
  b
}

fn sl<'a>(input: &'a BlockBuf) -> BlockSlice<'a> {
  BlockSlice {
    buf: input,
    start: 0,
    end:   input.len(),
  }
}

fn to_i64<'a>(input: BlockSlice<'a>) -> Result<i64, ()> {
  let v: Vec<u8> = input.cursor().collect();

  match str::from_utf8(&v) {
    Err(_) => Err(()),
    Ok(s) => match FromStr::from_str(s) {
      Err(_) => Err(()),
      Ok(i)  => Ok(i)
    }
  }
}

#[test]
fn factor_test() {
  let a = blockbuf_from(&b"3"[..]);
  println!("calculated: {:?}", factor(sl(&a)));
}

#[test]
fn parens_test() {
  let input1 = blockbuf_from(&b" 2* (  3 + 4 ) "[..]);
  println!("calculated 1: {:?}", expr(sl(&input1)));
  let input2 = blockbuf_from(&b"  2*2 / ( 5 - 1) + 3"[..]);
  println!("calculated 2: {:?}", expr(sl(&input2)));
}
*/
<|endoftext|>


<commit_before>//! Checks interface
// see: https://developer.github.com/v3/checks/suites/
use serde::{Deserialize, Serialize};

use self::super::{AuthenticationConstraint, Future, Github, MediaType};

pub struct CheckRuns {
    github: Github,
    owner: String,
    repo: String,
}

impl<'a> CheckRuns {
    #[doc(hidden)]
    pub(crate) fn new<O, R>(github: Github, owner: O, repo: R) -> Self
    where
        O: Into<String>,
        R: Into<String>,
    {
        CheckRuns {
            github,
            owner: owner.into(),
            repo: repo.into(),
        }
    }

    fn path(&self, more: &str) -> String {
        format!("/repos/{}/{}/check-runs{}", self.owner, self.repo, more)
    }

    pub fn create(&self, check_run_options: &CheckRunOptions) -> Future<CheckRun> {
        match serde_json::to_string(check_run_options) {
            Ok(data) => self.github.post_media::<CheckRun>(
                &self.path(""),
                data.into_bytes(),
                MediaType::Preview("antiope"),
                AuthenticationConstraint::Unconstrained,
            ),
            Err(e) => Box::pin(futures::future::err(e.into())),
        }
    }

    pub fn update(
        &self,
        check_run_id: &str,
        check_run_options: &CheckRunUpdateOptions,
    ) -> Future<CheckRun> {
        match serde_json::to_string(check_run_options) {
            Ok(data) => self.github.post_media::<CheckRun>(
                &self.path(&format!("/{}", check_run_id)),
                data.into_bytes(),
                MediaType::Preview("antiope"),
                AuthenticationConstraint::Unconstrained,
            ),
            Err(e) => Box::pin(futures::future::err(e.into())),
        }
    }

    pub fn list_for_suite(&self, suite_id: &str) -> Future<Vec<CheckRun>> {
        // !!! does this actually work?
        // https://developer.github.com/v3/checks/runs/#list-check-runs-in-a-check-suite
        self.github.get_media::<Vec<CheckRun>>(
            &self.path(&format!("/{}/check-runs", suite_id)),
            MediaType::Preview("antiope"),
        )
    }
}

// representations

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum CheckRunState {
    Queued,
    InProgress,
    Completed,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum Conclusion {
    Success,
    Failure,
    Neutral,
    Cancelled,
    TimedOut,
    ActionRequired,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum AnnotationLevel {
    Notice,
    Warning,
    Failure,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Output {
    pub title: String,
    pub summary: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub text: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub annotations: Option<Vec<Annotation>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub images: Option<Vec<Image>>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Action {
    pub label: String,
    pub description: String,
    pub identifier: String,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Annotation {
    pub path: String,
    pub start_line: u32,
    pub end_line: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_column: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_column: Option<u32>,
    pub annotation_level: AnnotationLevel,
    pub message: String,
    pub title: String,
    pub raw_details: String,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Image {
    pub alt: String,
    pub image_url: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub caption: Option<String>,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct CheckRunOptions {
    pub name: String,
    pub head_sha: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub external_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status: Option<CheckRunState>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub started_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conclusion: Option<Conclusion>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub completed_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output: Option<Output>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub actions: Option<Vec<Action>>,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct CheckRunUpdateOptions {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub external_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status: Option<CheckRunState>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub started_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conclusion: Option<Conclusion>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub completed_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output: Option<Output>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub actions: Option<Vec<Action>>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct CheckRun {
    pub id: i32,
    pub name: String,
    pub head_sha: String,
    pub url: String,
    pub check_suite: CheckSuite,
    pub details_url: Option<String>,
    pub external_id: Option<String>,
    pub status: Option<CheckRunState>,
    pub started_at: Option<String>,
    pub conclusion: Option<Conclusion>,
    pub completed_at: Option<String>,
    /*
    Deleted for now:

    GitHub's API returns:

      "output": {
        "title": null,
        "summary": null,
        "text": null,
        "annotations_count": 0,
        "annotations_url": "https://api.github.com/repos/grahamc/notpkgs/check-runs/30726963/annotations"
      },

    if there is no Output, which confuses serde.

    pub output: Option<Output>,
     */
    pub actions: Option<Vec<Action>>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct CheckSuite {
    pub id: u32,
}
<commit_msg>CheckRun: i32 -> u64 for id<commit_after>//! Checks interface
// see: https://developer.github.com/v3/checks/suites/
use serde::{Deserialize, Serialize};

use self::super::{AuthenticationConstraint, Future, Github, MediaType};

pub struct CheckRuns {
    github: Github,
    owner: String,
    repo: String,
}

impl<'a> CheckRuns {
    #[doc(hidden)]
    pub(crate) fn new<O, R>(github: Github, owner: O, repo: R) -> Self
    where
        O: Into<String>,
        R: Into<String>,
    {
        CheckRuns {
            github,
            owner: owner.into(),
            repo: repo.into(),
        }
    }

    fn path(&self, more: &str) -> String {
        format!("/repos/{}/{}/check-runs{}", self.owner, self.repo, more)
    }

    pub fn create(&self, check_run_options: &CheckRunOptions) -> Future<CheckRun> {
        match serde_json::to_string(check_run_options) {
            Ok(data) => self.github.post_media::<CheckRun>(
                &self.path(""),
                data.into_bytes(),
                MediaType::Preview("antiope"),
                AuthenticationConstraint::Unconstrained,
            ),
            Err(e) => Box::pin(futures::future::err(e.into())),
        }
    }

    pub fn update(
        &self,
        check_run_id: &str,
        check_run_options: &CheckRunUpdateOptions,
    ) -> Future<CheckRun> {
        match serde_json::to_string(check_run_options) {
            Ok(data) => self.github.post_media::<CheckRun>(
                &self.path(&format!("/{}", check_run_id)),
                data.into_bytes(),
                MediaType::Preview("antiope"),
                AuthenticationConstraint::Unconstrained,
            ),
            Err(e) => Box::pin(futures::future::err(e.into())),
        }
    }

    pub fn list_for_suite(&self, suite_id: &str) -> Future<Vec<CheckRun>> {
        // !!! does this actually work?
        // https://developer.github.com/v3/checks/runs/#list-check-runs-in-a-check-suite
        self.github.get_media::<Vec<CheckRun>>(
            &self.path(&format!("/{}/check-runs", suite_id)),
            MediaType::Preview("antiope"),
        )
    }
}

// representations

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum CheckRunState {
    Queued,
    InProgress,
    Completed,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum Conclusion {
    Success,
    Failure,
    Neutral,
    Cancelled,
    TimedOut,
    ActionRequired,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum AnnotationLevel {
    Notice,
    Warning,
    Failure,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Output {
    pub title: String,
    pub summary: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub text: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub annotations: Option<Vec<Annotation>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub images: Option<Vec<Image>>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Action {
    pub label: String,
    pub description: String,
    pub identifier: String,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Annotation {
    pub path: String,
    pub start_line: u32,
    pub end_line: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_column: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_column: Option<u32>,
    pub annotation_level: AnnotationLevel,
    pub message: String,
    pub title: String,
    pub raw_details: String,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Image {
    pub alt: String,
    pub image_url: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub caption: Option<String>,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct CheckRunOptions {
    pub name: String,
    pub head_sha: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub external_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status: Option<CheckRunState>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub started_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conclusion: Option<Conclusion>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub completed_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output: Option<Output>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub actions: Option<Vec<Action>>,
}

#[derive(Debug, Serialize, PartialEq)]
pub struct CheckRunUpdateOptions {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub external_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status: Option<CheckRunState>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub started_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conclusion: Option<Conclusion>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub completed_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output: Option<Output>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub actions: Option<Vec<Action>>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct CheckRun {
    pub id: u64,
    pub name: String,
    pub head_sha: String,
    pub url: String,
    pub check_suite: CheckSuite,
    pub details_url: Option<String>,
    pub external_id: Option<String>,
    pub status: Option<CheckRunState>,
    pub started_at: Option<String>,
    pub conclusion: Option<Conclusion>,
    pub completed_at: Option<String>,
    /*
    Deleted for now:

    GitHub's API returns:

      "output": {
        "title": null,
        "summary": null,
        "text": null,
        "annotations_count": 0,
        "annotations_url": "https://api.github.com/repos/grahamc/notpkgs/check-runs/30726963/annotations"
      },

    if there is no Output, which confuses serde.

    pub output: Option<Output>,
     */
    pub actions: Option<Vec<Action>>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct CheckSuite {
    pub id: u32,
}
<|endoftext|>


<commit_before>use rustc_serialize::hex::{ToHex, FromHex, FromHexError};
use rustc_serialize::base64::{FromBase64Error, ToBase64, FromBase64, STANDARD};
use std::str;
use std::io;
use std::io::BufWriter;
use std::fs::File;
use std::path::Path;
use std::io::prelude::*;
use std::cmp;
use std::fs::OpenOptions;
use std::fs;

enum WriteMethod {
    Truncate,
    Append,
}

pub struct CipherText(Vec<u8>);

impl CipherText {
    pub fn new(bytes: &[u8]) -> CipherText {
        CipherText(bytes.to_vec())
    }

    pub fn from_hex(hex: &str) -> Result<CipherText, Error> {
        let cipher_bytes: Vec<u8> = try!(hex.from_hex());
        Ok(CipherText(cipher_bytes))
    }

    pub fn to_hex(&self) -> String {
        let &CipherText(ref vec_bytes) = self;
        vec_bytes.to_hex()
    }

    pub fn from_b64<S: AsRef<str>>(b64: S) -> Result<CipherText, Error> {
        let cipher_bytes: Vec<u8> = try!(b64.as_ref().from_base64());
        Ok(CipherText(cipher_bytes))
    }

    pub fn to_b64(&self) -> String {
        let &CipherText(ref vec_bytes) = self;
        vec_bytes.to_base64(STANDARD)
    }

    fn from_file<P: AsRef<Path>>(path: P) -> Result<CipherText, Error> {
        buffer_file(path.as_ref()).and_then(CipherText::from_b64)
    }

    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
        let text_b64 = self.to_b64();
        let mut file = try!(create_file(path.as_ref(), WriteMethod::Append));

        let mut i = 0;
        let mut j = cmp::min(text_b64.len(), 80);
        while i != j {
            try!(file.write_all(&text_b64[i..j].as_bytes()));
            try!(file.write_all("\n".as_bytes()));

            i = j;
            j = cmp::min(text_b64.len(), j + 80);
        }
        Ok(())
    }
}
pub struct PlainText(Vec<u8>);

impl PlainText {
    pub fn from_string<S: AsRef<str>>(string: S) -> PlainText {
        PlainText(string.as_ref().as_bytes().to_vec())
    }

    pub fn from_bytes(bytes: &[u8]) -> PlainText {
        PlainText(bytes.to_vec())
    }

    fn from_file<P: AsRef<Path>>(path: P) -> Result<PlainText, Error> {
        buffer_file(path.as_ref()).map(PlainText::from_string)
    }

    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
        let &PlainText(ref vec_bytes) = self;
        let file = try!(create_file(path.as_ref(), WriteMethod::Truncate));
        let mut out_file = BufWriter::new(file);
        try!(out_file.write_all(&vec_bytes));
        Ok(())
    }

    pub fn to_utf8(&self) -> Result<String, Error> {
        let &PlainText(ref vec_bytes) = self;
        let plain = try!(str::from_utf8(&vec_bytes));
        Ok(plain.to_string())
    }
}

fn buffer_file(path: &Path) -> Result<String, Error> {
    let mut file = try!(File::open(Path::new(path)));
    let capacity = file.metadata().ok().map_or(0, |x| x.len());
    let mut buffer = String::with_capacity(capacity as usize);
    try!(file.read_to_string(&mut buffer));
    Ok(buffer)
}

fn create_file(path: &Path, method: WriteMethod) -> Result<File, Error> {
    if path.exists() {
        try!(fs::remove_file(path));
    }

    match method {
        WriteMethod::Append => {
            OpenOptions::new()
                .write(true)
                .append(true)
                .create(true)
                .open(path)
                .map_err(|e| Error::from(e))
        }
        WriteMethod::Truncate => {
            OpenOptions::new()
                .write(true)
                .truncate(true)
                .create(true)
                .open(path)
                .map_err(|e| Error::from(e))
        }
    }

}

pub fn encrypt(text: &PlainText, key: &str) -> CipherText {
    let &PlainText(ref bytes) = text;
    let xored: Vec<u8> = repeating_xor(bytes, key.as_bytes());
    CipherText::new(&xored)
}

pub fn decrypt(cipher_text: &CipherText, key: &str) -> Result<PlainText, Error> {
    let &CipherText(ref bytes) = cipher_text;
    let xored: Vec<u8> = repeating_xor(bytes, key.as_bytes());
    Ok(PlainText::from_bytes(&xored))
}

pub fn decrypt_file(input_path: &str, output_path: &str, key: &str) -> Result<(), Error> {
    let cipher = try!(CipherText::from_file(input_path));
    let plain = try!(decrypt(&cipher, key));
    try!(plain.to_file(output_path));
    Ok(())
}

pub fn encrypt_file(input_path: &str, output_path: &str, key: &str) -> Result<(), Error> {
    let plain = try!(PlainText::from_file(input_path));
    let cipher = encrypt(&plain, key);
    try!(cipher.to_file(output_path));
    Ok(())
}

#[derive(Debug)]
pub enum Error {
    Hex(String),
    Base64(String),
    UTF8(String),
    File(String),
}

impl From<FromBase64Error> for Error {
    fn from(err: FromBase64Error) -> Error {
        Error::Base64(err.to_string())
    }
}

impl From<FromHexError> for Error {
    fn from(err: FromHexError) -> Error {
        Error::Hex(err.to_string())
    }
}

impl From<str::Utf8Error> for Error {
    fn from(err: str::Utf8Error) -> Error {
        Error::UTF8(err.to_string())
    }
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Error {
        Error::File(err.to_string())
    }
}

fn repeating_xor(input: &[u8], key: &[u8]) -> Vec<u8> {
    let mut out: Vec<u8> = Vec::with_capacity(input.len());
    for (i, in_val) in input.iter().enumerate() {
        out.push(in_val ^ key[i % key.len()]);
    }
    out
}

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

    #[test]
    fn test_encrypt_and_decrypt() {
        check_text("This is the plain text\n");
        check_text("123, 456");
        check_text("!£$%^&*@~:");
        check_text("日本語");
    }

    #[test]
    fn test_encrypt_and_decrypt_files() {
        check_text_file("This is the plain text\n");
        check_text_file("123, 456");
        check_text_file("!£$%^&*@~:");
        check_text_file("日本語");
        check_text_file("This is the plain text\nThis is the plain text\nThis is the plain text\n");
        check_text_file("dsklfshdfsfsdfsdfsdfsdfsgfssssssssssssssssssssssfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
    }

    #[test]
    fn test_file_storage() {
        check_to_from_file("This is the plain text\n");
        check_to_from_file("123, 456");
        check_to_from_file("!£$%^&*@~:");
        check_to_from_file("日本語");
        check_to_from_file("This is the plain text\nThis is the plain text\nThis is the plain \
                            text\n");
        check_to_from_file("dsklfshdfsfsdfsdfsdfsdfsgfssssssssssssssssssssssfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
    }

    #[test]
    fn test_wrong_key() {
        let text = "This is the plain text";
        let plain_text = PlainText::from_string(text);
        let key: &str = "toy";
        let decoded_text = decrypt(&encrypt(&plain_text, key), "wrong_key").unwrap();
        assert!(text != decoded_text.to_utf8().unwrap());
    }

    fn check_to_from_file(text: &str) {
        let plain_text = PlainText::from_string(text);
        let key: &str = "toy";
        let cipher_text = encrypt(&plain_text, key);
        let path = "./target/debug/test.txt";

        plain_text.to_file(&path).unwrap();
        let rec_plain = PlainText::from_file(&path).unwrap();
        assert_eq!(plain_text.to_utf8().unwrap(), rec_plain.to_utf8().unwrap());

        cipher_text.to_file(&path).unwrap();
        let rec_cipher = CipherText::from_file(&path).unwrap();
        assert_eq!(cipher_text.to_b64(), rec_cipher.to_b64());

    }

    fn check_text(text: &str) {
        let plain_text = PlainText::from_string(text);
        let key: &str = "toy";
        let decoded_text = decrypt(&encrypt(&plain_text, key), key).unwrap();
        assert_eq!(text, decoded_text.to_utf8().unwrap());
    }

    fn check_text_file(text: &str) {
        let plain_text = PlainText::from_string(text);
        let key: &str = "toy";
        let plain_path = "./target/test-plain.txt";
        let cipher_path = "./target/test-cipher.txt";
        let decoded_path = "./target/test-decoded.txt";

        plain_text.to_file(plain_path).unwrap();
        encrypt_file(plain_path, cipher_path, key).unwrap();
        decrypt_file(cipher_path, decoded_path, key).unwrap();
        let decoded_text = PlainText::from_file(decoded_path).unwrap();

        assert_eq!(text, decoded_text.to_utf8().unwrap());
    }
}
<commit_msg>Added feedback from reddit<commit_after>use rustc_serialize::hex::{ToHex, FromHex, FromHexError};
use rustc_serialize::base64::{FromBase64Error, ToBase64, FromBase64, STANDARD};
use std::str;
use std::io;
use std::io::BufWriter;
use std::fs::File;
use std::path::Path;
use std::io::prelude::*;
use std::cmp;
use std::fs::OpenOptions;
use std::fs;

enum WriteMethod {
    Truncate,
    Append,
}

pub struct CipherText(Vec<u8>);

impl CipherText {
    pub fn new(bytes: &[u8]) -> CipherText {
        CipherText(bytes.to_vec())
    }

    pub fn from_hex(hex: &str) -> Result<CipherText, Error> {
        let cipher_bytes: Vec<u8> = try!(hex.from_hex());
        Ok(CipherText(cipher_bytes))
    }

    pub fn to_hex(&self) -> String {
        let ref vec_bytes = self.0;
        vec_bytes.to_hex()
    }

    pub fn from_b64<S: AsRef<str>>(b64: S) -> Result<CipherText, Error> {
        let cipher_bytes: Vec<u8> = try!(b64.as_ref().from_base64());
        Ok(CipherText(cipher_bytes))
    }

    pub fn to_b64(&self) -> String {
        let ref vec_bytes = self.0;
        vec_bytes.to_base64(STANDARD)
    }

    fn from_file<P: AsRef<Path>>(path: P) -> Result<CipherText, Error> {
        buffer_file(path.as_ref()).and_then(CipherText::from_b64)
    }

    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
        let text_b64 = self.to_b64();
        let mut file = try!(create_file(path.as_ref(), WriteMethod::Append));

        let mut i = 0;
        let mut j = cmp::min(text_b64.len(), 80);
        while i != j {
            try!(file.write_all(&text_b64[i..j].as_bytes()));
            try!(file.write_all("\n".as_bytes()));

            i = j;
            j = cmp::min(text_b64.len(), j + 80);
        }
        Ok(())
    }
}
pub struct PlainText(Vec<u8>);

impl PlainText {
    pub fn from_string<S: AsRef<str>>(string: S) -> PlainText {
        PlainText(string.as_ref().as_bytes().to_vec())
    }

    pub fn from_bytes(bytes: &[u8]) -> PlainText {
        PlainText(bytes.to_vec())
    }

    fn from_file<P: AsRef<Path>>(path: P) -> Result<PlainText, Error> {
        buffer_file(path.as_ref()).map(PlainText::from_string)
    }

    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
        let ref vec_bytes = self.0;
        let file = try!(create_file(path.as_ref(), WriteMethod::Truncate));
        let mut out_file = BufWriter::new(file);
        try!(out_file.write_all(&vec_bytes));
        Ok(())
    }

    pub fn to_utf8(&self) -> Result<String, Error> {
        let ref vec_bytes = self.0;
        let plain = try!(str::from_utf8(&vec_bytes));
        Ok(plain.to_string())
    }
}

fn buffer_file(path: &Path) -> Result<String, Error> {
    let mut file = try!(File::open(Path::new(path)));
    let capacity = file.metadata().ok().map_or(0, |x| x.len());
    let mut buffer = String::with_capacity(capacity as usize);
    try!(file.read_to_string(&mut buffer));
    Ok(buffer)
}

fn create_file(path: &Path, method: WriteMethod) -> Result<File, Error> {
    if path.exists() {
        try!(fs::remove_file(path));
    }

    match method {
        WriteMethod::Append => {
            OpenOptions::new()
                .write(true)
                .append(true)
                .create(true)
                .open(path)
                .map_err(|e| Error::from(e))
        }
        WriteMethod::Truncate => {
            OpenOptions::new()
                .write(true)
                .truncate(true)
                .create(true)
                .open(path)
                .map_err(|e| Error::from(e))
        }
    }

}

pub fn encrypt(text: &PlainText, key: &str) -> CipherText {
    let &PlainText(ref bytes) = text;
    let xored: Vec<u8> = repeating_xor(bytes, key.as_bytes());
    CipherText::new(&xored)
}

pub fn decrypt(cipher_text: &CipherText, key: &str) -> Result<PlainText, Error> {
    let &CipherText(ref bytes) = cipher_text;
    let xored: Vec<u8> = repeating_xor(bytes, key.as_bytes());
    Ok(PlainText::from_bytes(&xored))
}

pub fn decrypt_file(input_path: &str, output_path: &str, key: &str) -> Result<(), Error> {
    let cipher = try!(CipherText::from_file(input_path));
    let plain = try!(decrypt(&cipher, key));
    try!(plain.to_file(output_path));
    Ok(())
}

pub fn encrypt_file(input_path: &str, output_path: &str, key: &str) -> Result<(), Error> {
    let plain = try!(PlainText::from_file(input_path));
    let cipher = encrypt(&plain, key);
    try!(cipher.to_file(output_path));
    Ok(())
}

#[derive(Debug)]
pub enum Error {
    Hex(String),
    Base64(String),
    UTF8(String),
    File(String),
}

impl From<FromBase64Error> for Error {
    fn from(err: FromBase64Error) -> Error {
        Error::Base64(err.to_string())
    }
}

impl From<FromHexError> for Error {
    fn from(err: FromHexError) -> Error {
        Error::Hex(err.to_string())
    }
}

impl From<str::Utf8Error> for Error {
    fn from(err: str::Utf8Error) -> Error {
        Error::UTF8(err.to_string())
    }
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Error {
        Error::File(err.to_string())
    }
}

fn repeating_xor(input: &[u8], key: &[u8]) -> Vec<u8> {
    let mut out: Vec<u8> = Vec::with_capacity(input.len());
    for (i, in_val) in input.iter().enumerate() {
        out.push(in_val ^ key[i % key.len()]);
    }
    out
}

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

    #[test]
    fn test_encrypt_and_decrypt() {
        check_text("This is the plain text\n");
        check_text("123, 456");
        check_text("!£$%^&*@~:");
        check_text("日本語");
    }

    #[test]
    fn test_encrypt_and_decrypt_files() {
        check_text_file("This is the plain text\n");
        check_text_file("123, 456");
        check_text_file("!£$%^&*@~:");
        check_text_file("日本語");
        check_text_file("This is the plain text\nThis is the plain text\nThis is the plain text\n");
        check_text_file("dsklfshdfsfsdfsdfsdfsdfsgfssssssssssssssssssssssfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
    }

    #[test]
    fn test_file_storage() {
        check_to_from_file("This is the plain text\n");
        check_to_from_file("123, 456");
        check_to_from_file("!£$%^&*@~:");
        check_to_from_file("日本語");
        check_to_from_file("This is the plain text\nThis is the plain text\nThis is the plain \
                            text\n");
        check_to_from_file("dsklfshdfsfsdfsdfsdfsdfsgfssssssssssssssssssssssfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
    }

    #[test]
    fn test_wrong_key() {
        let text = "This is the plain text";
        let plain_text = PlainText::from_string(text);
        let key: &str = "toy";
        let decoded_text = decrypt(&encrypt(&plain_text, key), "wrong_key").unwrap();
        assert!(text != decoded_text.to_utf8().unwrap());
    }

    fn check_to_from_file(text: &str) {
        let plain_text = PlainText::from_string(text);
        let key: &str = "toy";
        let cipher_text = encrypt(&plain_text, key);
        let path = "./target/debug/test.txt";

        plain_text.to_file(&path).unwrap();
        let rec_plain = PlainText::from_file(&path).unwrap();
        assert_eq!(plain_text.to_utf8().unwrap(), rec_plain.to_utf8().unwrap());

        cipher_text.to_file(&path).unwrap();
        let rec_cipher = CipherText::from_file(&path).unwrap();
        assert_eq!(cipher_text.to_b64(), rec_cipher.to_b64());

    }

    fn check_text(text: &str) {
        let plain_text = PlainText::from_string(text);
        let key: &str = "toy";
        let decoded_text = decrypt(&encrypt(&plain_text, key), key).unwrap();
        assert_eq!(text, decoded_text.to_utf8().unwrap());
    }

    fn check_text_file(text: &str) {
        let plain_text = PlainText::from_string(text);
        let key: &str = "toy";
        let plain_path = "./target/test-plain.txt";
        let cipher_path = "./target/test-cipher.txt";
        let decoded_path = "./target/test-decoded.txt";

        plain_text.to_file(plain_path).unwrap();
        encrypt_file(plain_path, cipher_path, key).unwrap();
        decrypt_file(cipher_path, decoded_path, key).unwrap();
        let decoded_text = PlainText::from_file(decoded_path).unwrap();

        assert_eq!(text, decoded_text.to_utf8().unwrap());
    }
}
<|endoftext|>


<commit_before>//! A solver for dataflow problems.

use std::borrow::BorrowMut;
use std::ffi::OsString;
use std::fs;
use std::path::PathBuf;

use rustc_ast as ast;
use rustc_data_structures::work_queue::WorkQueue;
use rustc_graphviz as dot;
use rustc_hir::def_id::DefId;
use rustc_index::bit_set::BitSet;
use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::mir::{self, traversal, BasicBlock};
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::symbol::{sym, Symbol};

use super::fmt::DebugWithContext;
use super::graphviz;
use super::{
    visit_results, Analysis, Direction, GenKill, GenKillAnalysis, GenKillSet, JoinSemiLattice,
    ResultsCursor, ResultsVisitor,
};
use crate::util::pretty::dump_enabled;

/// A dataflow analysis that has converged to fixpoint.
pub struct Results<'tcx, A>
where
    A: Analysis<'tcx>,
{
    pub analysis: A,
    pub(super) entry_sets: IndexVec<BasicBlock, A::Domain>,
}

impl<A> Results<'tcx, A>
where
    A: Analysis<'tcx>,
{
    /// Creates a `ResultsCursor` that can inspect these `Results`.
    pub fn into_results_cursor(self, body: &'mir mir::Body<'tcx>) -> ResultsCursor<'mir, 'tcx, A> {
        ResultsCursor::new(body, self)
    }

    /// Gets the dataflow state for the given block.
    pub fn entry_set_for_block(&self, block: BasicBlock) -> &A::Domain {
        &self.entry_sets[block]
    }

    pub fn visit_with(
        &self,
        body: &'mir mir::Body<'tcx>,
        blocks: impl IntoIterator<Item = BasicBlock>,
        vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
    ) {
        visit_results(body, blocks, self, vis)
    }

    pub fn visit_reachable_with(
        &self,
        body: &'mir mir::Body<'tcx>,
        vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
    ) {
        let blocks = mir::traversal::reachable(body);
        visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
    }

    pub fn visit_in_rpo_with(
        &self,
        body: &'mir mir::Body<'tcx>,
        vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
    ) {
        let blocks = mir::traversal::reverse_postorder(body);
        visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
    }
}

/// A solver for dataflow problems.
pub struct Engine<'a, 'tcx, A>
where
    A: Analysis<'tcx>,
{
    tcx: TyCtxt<'tcx>,
    body: &'a mir::Body<'tcx>,
    dead_unwinds: Option<&'a BitSet<BasicBlock>>,
    entry_sets: IndexVec<BasicBlock, A::Domain>,
    pass_name: Option<&'static str>,
    analysis: A,

    /// Cached, cumulative transfer functions for each block.
    //
    // FIXME(ecstaticmorse): This boxed `Fn` trait object is invoked inside a tight loop for
    // gen/kill problems on cyclic CFGs. This is not ideal, but it doesn't seem to degrade
    // performance in practice. I've tried a few ways to avoid this, but they have downsides. See
    // the message for the commit that added this FIXME for more information.
    apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
}

impl<A, D, T> Engine<'a, 'tcx, A>
where
    A: GenKillAnalysis<'tcx, Idx = T, Domain = D>,
    D: Clone + JoinSemiLattice + GenKill<T> + BorrowMut<BitSet<T>>,
    T: Idx,
{
    /// Creates a new `Engine` to solve a gen-kill dataflow problem.
    pub fn new_gen_kill(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A) -> Self {
        // If there are no back-edges in the control-flow graph, we only ever need to apply the
        // transfer function for each block exactly once (assuming that we process blocks in RPO).
        //
        // In this case, there's no need to compute the block transfer functions ahead of time.
        if !body.is_cfg_cyclic() {
            return Self::new(tcx, body, analysis, None);
        }

        // Otherwise, compute and store the cumulative transfer function for each block.

        let identity = GenKillSet::identity(analysis.bottom_value(body).borrow().domain_size());
        let mut trans_for_block = IndexVec::from_elem(identity, body.basic_blocks());

        for (block, block_data) in body.basic_blocks().iter_enumerated() {
            let trans = &mut trans_for_block[block];
            A::Direction::gen_kill_effects_in_block(&analysis, trans, block, block_data);
        }

        let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| {
            trans_for_block[bb].apply(state.borrow_mut());
        });

        Self::new(tcx, body, analysis, Some(apply_trans as Box<_>))
    }
}

impl<A, D> Engine<'a, 'tcx, A>
where
    A: Analysis<'tcx, Domain = D>,
    D: Clone + JoinSemiLattice,
{
    /// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
    /// function.
    ///
    /// Gen-kill problems should use `new_gen_kill`, which will coalesce transfer functions for
    /// better performance.
    pub fn new_generic(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A) -> Self {
        Self::new(tcx, body, analysis, None)
    }

    fn new(
        tcx: TyCtxt<'tcx>,
        body: &'a mir::Body<'tcx>,
        analysis: A,
        apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
    ) -> Self {
        let bottom_value = analysis.bottom_value(body);
        let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), body.basic_blocks());
        analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);

        if A::Direction::is_backward() && entry_sets[mir::START_BLOCK] != bottom_value {
            bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
        }

        Engine {
            analysis,
            tcx,
            body,
            dead_unwinds: None,
            pass_name: None,
            entry_sets,
            apply_trans_for_block,
        }
    }

    /// Signals that we do not want dataflow state to propagate across unwind edges for these
    /// `BasicBlock`s.
    ///
    /// You must take care that `dead_unwinds` does not contain a `BasicBlock` that *can* actually
    /// unwind during execution. Otherwise, your dataflow results will not be correct.
    pub fn dead_unwinds(mut self, dead_unwinds: &'a BitSet<BasicBlock>) -> Self {
        self.dead_unwinds = Some(dead_unwinds);
        self
    }

    /// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
    ///
    /// Some analyses are run multiple times in the compilation pipeline. Give them a `pass_name`
    /// to differentiate them. Otherwise, only the results for the latest run will be saved.
    pub fn pass_name(mut self, name: &'static str) -> Self {
        self.pass_name = Some(name);
        self
    }

    /// Computes the fixpoint for this dataflow problem and returns it.
    pub fn iterate_to_fixpoint(self) -> Results<'tcx, A>
    where
        A::Domain: DebugWithContext<A>,
    {
        let Engine {
            analysis,
            body,
            dead_unwinds,
            mut entry_sets,
            tcx,
            apply_trans_for_block,
            pass_name,
            ..
        } = self;

        let mut dirty_queue: WorkQueue<BasicBlock> =
            WorkQueue::with_none(body.basic_blocks().len());

        if A::Direction::is_forward() {
            for (bb, _) in traversal::reverse_postorder(body) {
                dirty_queue.insert(bb);
            }
        } else {
            // Reverse post-order on the reverse CFG may generate a better iteration order for
            // backward dataflow analyses, but probably not enough to matter.
            for (bb, _) in traversal::postorder(body) {
                dirty_queue.insert(bb);
            }
        }

        let mut state = analysis.bottom_value(body);
        while let Some(bb) = dirty_queue.pop() {
            let bb_data = &body[bb];

            // Apply the block transfer function, using the cached one if it exists.
            state.clone_from(&entry_sets[bb]);
            match &apply_trans_for_block {
                Some(apply) => apply(bb, &mut state),
                None => A::Direction::apply_effects_in_block(&analysis, &mut state, bb, bb_data),
            }

            A::Direction::join_state_into_successors_of(
                &analysis,
                tcx,
                body,
                dead_unwinds,
                &mut state,
                (bb, bb_data),
                |target: BasicBlock, state: &A::Domain| {
                    let set_changed = entry_sets[target].join(state);
                    if set_changed {
                        dirty_queue.insert(target);
                    }
                },
            );
        }

        let results = Results { analysis, entry_sets };

        let res = write_graphviz_results(tcx, &body, &results, pass_name);
        if let Err(e) = res {
            warn!("Failed to write graphviz dataflow results: {}", e);
        }

        results
    }
}

// Graphviz

/// Writes a DOT file containing the results of a dataflow analysis if the user requested it via
/// `rustc_mir` attributes.
fn write_graphviz_results<A>(
    tcx: TyCtxt<'tcx>,
    body: &mir::Body<'tcx>,
    results: &Results<'tcx, A>,
    pass_name: Option<&'static str>,
) -> std::io::Result<()>
where
    A: Analysis<'tcx>,
    A::Domain: DebugWithContext<A>,
{
    let def_id = body.source.def_id();
    let attrs = match RustcMirAttrs::parse(tcx, def_id) {
        Ok(attrs) => attrs,

        // Invalid `rustc_mir` attrs are reported in `RustcMirAttrs::parse`
        Err(()) => return Ok(()),
    };

    let path = match attrs.output_path(A::NAME) {
        Some(path) => path,

        None if tcx.sess.opts.debugging_opts.dump_mir_dataflow
            && dump_enabled(tcx, A::NAME, def_id) =>
        {
            // FIXME: Use some variant of `pretty::dump_path` for this
            let mut path = PathBuf::from(&tcx.sess.opts.debugging_opts.dump_mir_dir);

            let crate_name = tcx.crate_name(def_id.krate);
            let item_name = ty::print::with_forced_impl_filename_line(|| {
                tcx.def_path(def_id).to_filename_friendly_no_crate()
            });

            let pass_name = pass_name.map(|s| format!(".{}", s)).unwrap_or_default();

            path.push(format!("{}.{}.{}{}.dot", crate_name, item_name, A::NAME, pass_name));
            path
        }

        None => return Ok(()),
    };

    let style = match attrs.formatter {
        Some(sym::two_phase) => graphviz::OutputStyle::BeforeAndAfter,
        _ => graphviz::OutputStyle::AfterOnly,
    };

    debug!("printing dataflow results for {:?} to {}", def_id, path.display());
    let mut buf = Vec::new();

    let graphviz = graphviz::Formatter::new(body, results, style);
    let mut render_opts =
        vec![dot::RenderOption::Fontname(tcx.sess.opts.debugging_opts.graphviz_font.clone())];
    if tcx.sess.opts.debugging_opts.graphviz_dark_mode {
        render_opts.push(dot::RenderOption::DarkTheme);
    }
    dot::render_opts(&graphviz, &mut buf, &render_opts)?;

    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(&path, buf)?;

    Ok(())
}

#[derive(Default)]
struct RustcMirAttrs {
    basename_and_suffix: Option<PathBuf>,
    formatter: Option<Symbol>,
}

impl RustcMirAttrs {
    fn parse(tcx: TyCtxt<'tcx>, def_id: DefId) -> Result<Self, ()> {
        let attrs = tcx.get_attrs(def_id);

        let mut result = Ok(());
        let mut ret = RustcMirAttrs::default();

        let rustc_mir_attrs = attrs
            .iter()
            .filter(|attr| tcx.sess.check_name(attr, sym::rustc_mir))
            .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter()));

        for attr in rustc_mir_attrs {
            let attr_result = if attr.has_name(sym::borrowck_graphviz_postflow) {
                Self::set_field(&mut ret.basename_and_suffix, tcx, &attr, |s| {
                    let path = PathBuf::from(s.to_string());
                    match path.file_name() {
                        Some(_) => Ok(path),
                        None => {
                            tcx.sess.span_err(attr.span(), "path must end in a filename");
                            Err(())
                        }
                    }
                })
            } else if attr.has_name(sym::borrowck_graphviz_format) {
                Self::set_field(&mut ret.formatter, tcx, &attr, |s| match s {
                    sym::gen_kill | sym::two_phase => Ok(s),
                    _ => {
                        tcx.sess.span_err(attr.span(), "unknown formatter");
                        Err(())
                    }
                })
            } else {
                Ok(())
            };

            result = result.and(attr_result);
        }

        result.map(|()| ret)
    }

    fn set_field<T>(
        field: &mut Option<T>,
        tcx: TyCtxt<'tcx>,
        attr: &ast::NestedMetaItem,
        mapper: impl FnOnce(Symbol) -> Result<T, ()>,
    ) -> Result<(), ()> {
        if field.is_some() {
            tcx.sess
                .span_err(attr.span(), &format!("duplicate values for `{}`", attr.name_or_empty()));

            return Err(());
        }

        if let Some(s) = attr.value_str() {
            *field = Some(mapper(s)?);
            Ok(())
        } else {
            tcx.sess
                .span_err(attr.span(), &format!("`{}` requires an argument", attr.name_or_empty()));
            Err(())
        }
    }

    /// Returns the path where dataflow results should be written, or `None`
    /// `borrowck_graphviz_postflow` was not specified.
    ///
    /// This performs the following transformation to the argument of `borrowck_graphviz_postflow`:
    ///
    /// "path/suffix.dot" -> "path/analysis_name_suffix.dot"
    fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
        let mut ret = self.basename_and_suffix.as_ref().cloned()?;
        let suffix = ret.file_name().unwrap(); // Checked when parsing attrs

        let mut file_name: OsString = analysis_name.into();
        file_name.push("_");
        file_name.push(suffix);
        ret.set_file_name(file_name);

        Some(ret)
    }
}
<commit_msg>Auto merge of #77581 - ecstatic-morse:dataflow-dump-mir-graphviz, r=davidtwco<commit_after>//! A solver for dataflow problems.

use std::borrow::BorrowMut;
use std::ffi::OsString;
use std::path::PathBuf;

use rustc_ast as ast;
use rustc_data_structures::work_queue::WorkQueue;
use rustc_graphviz as dot;
use rustc_hir::def_id::DefId;
use rustc_index::bit_set::BitSet;
use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::mir::{self, traversal, BasicBlock};
use rustc_middle::ty::TyCtxt;
use rustc_span::symbol::{sym, Symbol};

use super::fmt::DebugWithContext;
use super::graphviz;
use super::{
    visit_results, Analysis, Direction, GenKill, GenKillAnalysis, GenKillSet, JoinSemiLattice,
    ResultsCursor, ResultsVisitor,
};
use crate::util::pretty::{create_dump_file, dump_enabled};

/// A dataflow analysis that has converged to fixpoint.
pub struct Results<'tcx, A>
where
    A: Analysis<'tcx>,
{
    pub analysis: A,
    pub(super) entry_sets: IndexVec<BasicBlock, A::Domain>,
}

impl<A> Results<'tcx, A>
where
    A: Analysis<'tcx>,
{
    /// Creates a `ResultsCursor` that can inspect these `Results`.
    pub fn into_results_cursor(self, body: &'mir mir::Body<'tcx>) -> ResultsCursor<'mir, 'tcx, A> {
        ResultsCursor::new(body, self)
    }

    /// Gets the dataflow state for the given block.
    pub fn entry_set_for_block(&self, block: BasicBlock) -> &A::Domain {
        &self.entry_sets[block]
    }

    pub fn visit_with(
        &self,
        body: &'mir mir::Body<'tcx>,
        blocks: impl IntoIterator<Item = BasicBlock>,
        vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
    ) {
        visit_results(body, blocks, self, vis)
    }

    pub fn visit_reachable_with(
        &self,
        body: &'mir mir::Body<'tcx>,
        vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
    ) {
        let blocks = mir::traversal::reachable(body);
        visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
    }

    pub fn visit_in_rpo_with(
        &self,
        body: &'mir mir::Body<'tcx>,
        vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = A::Domain>,
    ) {
        let blocks = mir::traversal::reverse_postorder(body);
        visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
    }
}

/// A solver for dataflow problems.
pub struct Engine<'a, 'tcx, A>
where
    A: Analysis<'tcx>,
{
    tcx: TyCtxt<'tcx>,
    body: &'a mir::Body<'tcx>,
    dead_unwinds: Option<&'a BitSet<BasicBlock>>,
    entry_sets: IndexVec<BasicBlock, A::Domain>,
    pass_name: Option<&'static str>,
    analysis: A,

    /// Cached, cumulative transfer functions for each block.
    //
    // FIXME(ecstaticmorse): This boxed `Fn` trait object is invoked inside a tight loop for
    // gen/kill problems on cyclic CFGs. This is not ideal, but it doesn't seem to degrade
    // performance in practice. I've tried a few ways to avoid this, but they have downsides. See
    // the message for the commit that added this FIXME for more information.
    apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
}

impl<A, D, T> Engine<'a, 'tcx, A>
where
    A: GenKillAnalysis<'tcx, Idx = T, Domain = D>,
    D: Clone + JoinSemiLattice + GenKill<T> + BorrowMut<BitSet<T>>,
    T: Idx,
{
    /// Creates a new `Engine` to solve a gen-kill dataflow problem.
    pub fn new_gen_kill(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A) -> Self {
        // If there are no back-edges in the control-flow graph, we only ever need to apply the
        // transfer function for each block exactly once (assuming that we process blocks in RPO).
        //
        // In this case, there's no need to compute the block transfer functions ahead of time.
        if !body.is_cfg_cyclic() {
            return Self::new(tcx, body, analysis, None);
        }

        // Otherwise, compute and store the cumulative transfer function for each block.

        let identity = GenKillSet::identity(analysis.bottom_value(body).borrow().domain_size());
        let mut trans_for_block = IndexVec::from_elem(identity, body.basic_blocks());

        for (block, block_data) in body.basic_blocks().iter_enumerated() {
            let trans = &mut trans_for_block[block];
            A::Direction::gen_kill_effects_in_block(&analysis, trans, block, block_data);
        }

        let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| {
            trans_for_block[bb].apply(state.borrow_mut());
        });

        Self::new(tcx, body, analysis, Some(apply_trans as Box<_>))
    }
}

impl<A, D> Engine<'a, 'tcx, A>
where
    A: Analysis<'tcx, Domain = D>,
    D: Clone + JoinSemiLattice,
{
    /// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
    /// function.
    ///
    /// Gen-kill problems should use `new_gen_kill`, which will coalesce transfer functions for
    /// better performance.
    pub fn new_generic(tcx: TyCtxt<'tcx>, body: &'a mir::Body<'tcx>, analysis: A) -> Self {
        Self::new(tcx, body, analysis, None)
    }

    fn new(
        tcx: TyCtxt<'tcx>,
        body: &'a mir::Body<'tcx>,
        analysis: A,
        apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
    ) -> Self {
        let bottom_value = analysis.bottom_value(body);
        let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), body.basic_blocks());
        analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);

        if A::Direction::is_backward() && entry_sets[mir::START_BLOCK] != bottom_value {
            bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
        }

        Engine {
            analysis,
            tcx,
            body,
            dead_unwinds: None,
            pass_name: None,
            entry_sets,
            apply_trans_for_block,
        }
    }

    /// Signals that we do not want dataflow state to propagate across unwind edges for these
    /// `BasicBlock`s.
    ///
    /// You must take care that `dead_unwinds` does not contain a `BasicBlock` that *can* actually
    /// unwind during execution. Otherwise, your dataflow results will not be correct.
    pub fn dead_unwinds(mut self, dead_unwinds: &'a BitSet<BasicBlock>) -> Self {
        self.dead_unwinds = Some(dead_unwinds);
        self
    }

    /// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
    ///
    /// Some analyses are run multiple times in the compilation pipeline. Give them a `pass_name`
    /// to differentiate them. Otherwise, only the results for the latest run will be saved.
    pub fn pass_name(mut self, name: &'static str) -> Self {
        self.pass_name = Some(name);
        self
    }

    /// Computes the fixpoint for this dataflow problem and returns it.
    pub fn iterate_to_fixpoint(self) -> Results<'tcx, A>
    where
        A::Domain: DebugWithContext<A>,
    {
        let Engine {
            analysis,
            body,
            dead_unwinds,
            mut entry_sets,
            tcx,
            apply_trans_for_block,
            pass_name,
            ..
        } = self;

        let mut dirty_queue: WorkQueue<BasicBlock> =
            WorkQueue::with_none(body.basic_blocks().len());

        if A::Direction::is_forward() {
            for (bb, _) in traversal::reverse_postorder(body) {
                dirty_queue.insert(bb);
            }
        } else {
            // Reverse post-order on the reverse CFG may generate a better iteration order for
            // backward dataflow analyses, but probably not enough to matter.
            for (bb, _) in traversal::postorder(body) {
                dirty_queue.insert(bb);
            }
        }

        let mut state = analysis.bottom_value(body);
        while let Some(bb) = dirty_queue.pop() {
            let bb_data = &body[bb];

            // Apply the block transfer function, using the cached one if it exists.
            state.clone_from(&entry_sets[bb]);
            match &apply_trans_for_block {
                Some(apply) => apply(bb, &mut state),
                None => A::Direction::apply_effects_in_block(&analysis, &mut state, bb, bb_data),
            }

            A::Direction::join_state_into_successors_of(
                &analysis,
                tcx,
                body,
                dead_unwinds,
                &mut state,
                (bb, bb_data),
                |target: BasicBlock, state: &A::Domain| {
                    let set_changed = entry_sets[target].join(state);
                    if set_changed {
                        dirty_queue.insert(target);
                    }
                },
            );
        }

        let results = Results { analysis, entry_sets };

        let res = write_graphviz_results(tcx, &body, &results, pass_name);
        if let Err(e) = res {
            error!("Failed to write graphviz dataflow results: {}", e);
        }

        results
    }
}

// Graphviz

/// Writes a DOT file containing the results of a dataflow analysis if the user requested it via
/// `rustc_mir` attributes.
fn write_graphviz_results<A>(
    tcx: TyCtxt<'tcx>,
    body: &mir::Body<'tcx>,
    results: &Results<'tcx, A>,
    pass_name: Option<&'static str>,
) -> std::io::Result<()>
where
    A: Analysis<'tcx>,
    A::Domain: DebugWithContext<A>,
{
    use std::fs;
    use std::io::{self, Write};

    let def_id = body.source.def_id();
    let attrs = match RustcMirAttrs::parse(tcx, def_id) {
        Ok(attrs) => attrs,

        // Invalid `rustc_mir` attrs are reported in `RustcMirAttrs::parse`
        Err(()) => return Ok(()),
    };

    let mut file = match attrs.output_path(A::NAME) {
        Some(path) => {
            debug!("printing dataflow results for {:?} to {}", def_id, path.display());
            if let Some(parent) = path.parent() {
                fs::create_dir_all(parent)?;
            }
            io::BufWriter::new(fs::File::create(&path)?)
        }

        None if tcx.sess.opts.debugging_opts.dump_mir_dataflow
            && dump_enabled(tcx, A::NAME, def_id) =>
        {
            create_dump_file(
                tcx,
                ".dot",
                None,
                A::NAME,
                &pass_name.unwrap_or("-----"),
                body.source,
            )?
        }

        _ => return Ok(()),
    };

    let style = match attrs.formatter {
        Some(sym::two_phase) => graphviz::OutputStyle::BeforeAndAfter,
        _ => graphviz::OutputStyle::AfterOnly,
    };

    let mut buf = Vec::new();

    let graphviz = graphviz::Formatter::new(body, results, style);
    let mut render_opts =
        vec![dot::RenderOption::Fontname(tcx.sess.opts.debugging_opts.graphviz_font.clone())];
    if tcx.sess.opts.debugging_opts.graphviz_dark_mode {
        render_opts.push(dot::RenderOption::DarkTheme);
    }
    dot::render_opts(&graphviz, &mut buf, &render_opts)?;

    file.write_all(&buf)?;

    Ok(())
}

#[derive(Default)]
struct RustcMirAttrs {
    basename_and_suffix: Option<PathBuf>,
    formatter: Option<Symbol>,
}

impl RustcMirAttrs {
    fn parse(tcx: TyCtxt<'tcx>, def_id: DefId) -> Result<Self, ()> {
        let attrs = tcx.get_attrs(def_id);

        let mut result = Ok(());
        let mut ret = RustcMirAttrs::default();

        let rustc_mir_attrs = attrs
            .iter()
            .filter(|attr| tcx.sess.check_name(attr, sym::rustc_mir))
            .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter()));

        for attr in rustc_mir_attrs {
            let attr_result = if attr.has_name(sym::borrowck_graphviz_postflow) {
                Self::set_field(&mut ret.basename_and_suffix, tcx, &attr, |s| {
                    let path = PathBuf::from(s.to_string());
                    match path.file_name() {
                        Some(_) => Ok(path),
                        None => {
                            tcx.sess.span_err(attr.span(), "path must end in a filename");
                            Err(())
                        }
                    }
                })
            } else if attr.has_name(sym::borrowck_graphviz_format) {
                Self::set_field(&mut ret.formatter, tcx, &attr, |s| match s {
                    sym::gen_kill | sym::two_phase => Ok(s),
                    _ => {
                        tcx.sess.span_err(attr.span(), "unknown formatter");
                        Err(())
                    }
                })
            } else {
                Ok(())
            };

            result = result.and(attr_result);
        }

        result.map(|()| ret)
    }

    fn set_field<T>(
        field: &mut Option<T>,
        tcx: TyCtxt<'tcx>,
        attr: &ast::NestedMetaItem,
        mapper: impl FnOnce(Symbol) -> Result<T, ()>,
    ) -> Result<(), ()> {
        if field.is_some() {
            tcx.sess
                .span_err(attr.span(), &format!("duplicate values for `{}`", attr.name_or_empty()));

            return Err(());
        }

        if let Some(s) = attr.value_str() {
            *field = Some(mapper(s)?);
            Ok(())
        } else {
            tcx.sess
                .span_err(attr.span(), &format!("`{}` requires an argument", attr.name_or_empty()));
            Err(())
        }
    }

    /// Returns the path where dataflow results should be written, or `None`
    /// `borrowck_graphviz_postflow` was not specified.
    ///
    /// This performs the following transformation to the argument of `borrowck_graphviz_postflow`:
    ///
    /// "path/suffix.dot" -> "path/analysis_name_suffix.dot"
    fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
        let mut ret = self.basename_and_suffix.as_ref().cloned()?;
        let suffix = ret.file_name().unwrap(); // Checked when parsing attrs

        let mut file_name: OsString = analysis_name.into();
        file_name.push("_");
        file_name.push(suffix);
        ret.set_file_name(file_name);

        Some(ret)
    }
}
<|endoftext|>