<commit_before>use std::collections::HashMap;

use common::Count;
use peephole::{Statement, Program};

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
/// The net movement of a loop.
enum LoopBalance {
    /// May move right but not left.
    NoLeft,
    /// May move left but not right.
    NoRight,
    /// No net movement.
    Balanced,
    /// Net movement may be either direction.
    Unknown,
}

/// An index to a loop.
///
/// This is represented as the address of the first instruction of the loop.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
struct LoopIndex(usize);

impl LoopIndex {
    /// Gets the loop index from a boxed loop.
    fn from_loop_body(body: &Box<[Statement]>) -> Self {
        LoopIndex(&body[0] as *const Statement as usize)
    }
}

/// The abstract interpreter tracks an abstraction of the pointer position.
///
/// In particular, it tracks the minimum distances from each end of memory. This can be used to
/// prove some bounds checks unnecessary.
#[derive(Debug, Clone)]
pub struct AbstractInterpreter {
    /// The minimum distance from the bottom of memory.
    left_mark: usize,
    /// The minimum distance from the top of memory.
    right_mark: usize,
    /// The computed net movement for each loop.
    loop_balances: HashMap<LoopIndex, LoopBalance>,
}

impl AbstractInterpreter {
    /// Initialize the interpreter with the body of the program.
    ///
    /// The interpreter initially analyzes the program for loop balances.
    pub fn new(program: &Program) -> Self {
        AbstractInterpreter {
            left_mark: 0,
            right_mark: 0,
            loop_balances: HashMap::new(),
        }
    }

    /// Moves the pointer the given distance to the left.
    ///
    /// Returns whether we can prove that this move will not underflow.
    pub fn left(&mut self, count: Count) -> bool {
        let count = count as usize;

        self.right_mark += count;
        if count <= self.left_mark {
            self.left_mark -= count;
            true
        } else {
            self.left_mark = 0;
            false
        }
    }

    /// Moves the pointer the given distance to the right.
    ///
    /// Returns whether we can prove that this move will not overflow.
    pub fn right(&mut self, count: Count) -> bool {
        let count = count as usize;

        self.left_mark += count;
        if count <= self.right_mark {
            self.right_mark -= count;
            true
        } else {
            self.right_mark = 0;
            false
        }
    }

    /// Resets the left mark.
    ///
    /// This is used when we may move an arbitrary distance to the left.
    pub fn reset_left(&mut self) {
        self.left_mark = 0;
    }

    /// Resets the right mark.
    ///
    /// This is used when we may move an arbitrary distance to the right.
    pub fn reset_right(&mut self) {
        self.right_mark = 0;
    }

    /// Resets both marks.
    ///
    /// This is used when we enter and leave a loop.
    pub fn reset(&mut self) {
        self.reset_left();
        self.reset_right();
    }
}
<commit_msg>Performs loop balance analysis.<commit_after>use std::collections::HashMap;

use common::Count;
use peephole::{Statement, Program};

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
/// The net movement of a loop.
enum LoopBalance {
    /// The exact movement of one iteration.
    Exact(isize),
    /// May move right but not left.
    RightOnly,
    /// May move left but not right.
    LeftOnly,
    /// Net movement may be either direction.
    Unknown,
}

impl LoopBalance {
    /// Is the loop body exactly balanced between right and left?
    fn is_balanced(self) -> bool {
        self == LoopBalance::Exact(0)
    }

    /// Does the loop move net right (if at all)?
    fn is_right_only(self) -> bool {
        use self::LoopBalance::*;

        match self {
            Exact(disp) => disp >= 0,
            RightOnly   => true,
            LeftOnly    => false,
            Unknown     => false,
        }
    }

    /// Does the loop move net left (if at all)?
    fn is_left_only(self) -> bool {
        use self::LoopBalance::*;

        match self {
            Exact(disp) => disp <= 0,
            RightOnly   => false,
            LeftOnly    => true,
            Unknown     => false,
        }
    }
}

/// An index to a loop.
///
/// This is represented as the address of the first instruction of the loop.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
struct LoopIndex(usize);

impl LoopIndex {
    /// Gets the loop index from a boxed loop.
    fn from_loop_body(body: &Box<[Statement]>) -> Self {
        LoopIndex(body.as_ptr() as usize)
    }
}

/// The abstract interpreter tracks an abstraction of the pointer position.
///
/// In particular, it tracks the minimum distances from each end of memory. This can be used to
/// prove some bounds checks unnecessary.
#[derive(Debug, Clone)]
pub struct AbstractInterpreter {
    /// The minimum distance from the bottom of memory.
    left_mark: usize,
    /// The minimum distance from the top of memory.
    right_mark: usize,
    /// The computed net movement for each loop.
    loop_balances: HashMap<LoopIndex, LoopBalance>,
}

impl AbstractInterpreter {
    /// Initialize the interpreter with the body of the program.
    ///
    /// The interpreter initially analyzes the program for loop balances.
    pub fn new(program: &Program) -> Self {
        let mut result = AbstractInterpreter {
            left_mark: 0,
            right_mark: 0,
            loop_balances: HashMap::new(),
        };

        result.analyze(program);

        result
    }

    fn analyze(&mut self, statements: &[Statement]) -> LoopBalance {
        use peephole::Statement::*;
        use common::Instruction::*;
        use self::LoopBalance::*;

        let mut net: LoopBalance = Exact(0);

        for statement in statements {
            match *statement {
                Instr(Right(count)) => net = match net {
                    Exact(disp)         => Exact(disp + count as isize),
                    RightOnly           => RightOnly,
                    _                   => Unknown,
                },

                Instr(Left(count)) => net = match net {
                    Exact(disp)         => Exact(disp - count as isize),
                    LeftOnly            => LeftOnly,
                    _                   => Unknown,
                },

                Instr(Add(_)) | Instr(In) | Instr(Out) => (),

                Instr(JumpZero(_)) | Instr(JumpNotZero(_)) =>
                    panic!("unexpected jump instruction"),

                Instr(SetZero) | Instr(OffsetAddRight(_)) | Instr(OffsetAddLeft(_)) => (),

                Instr(FindZeroRight(_)) =>
                    net = if net.is_right_only() {RightOnly} else {Unknown},

                Instr(FindZeroLeft(_)) =>
                    net = if net.is_left_only() {LeftOnly} else {Unknown},

                Loop(ref body) => {
                    let index = LoopIndex::from_loop_body(body);
                    let body  = self.analyze(body);

                    self.loop_balances.insert(index, body);

                    net = match net {
                        Exact(disp) if body.is_balanced()                   => Exact(disp),
                        _ if net.is_right_only() && body.is_right_only()    => RightOnly,
                        _ if net.is_left_only() && body.is_left_only()      => LeftOnly,
                        _                                                   => Unknown,
                    }
                }
            }
        }

        net
    }

    /// Moves the pointer the given distance to the left.
    ///
    /// Returns whether we can prove that this move will not underflow.
    pub fn left(&mut self, count: Count) -> bool {
        let count = count as usize;

        self.right_mark += count;
        if count <= self.left_mark {
            self.left_mark -= count;
            true
        } else {
            self.left_mark = 0;
            false
        }
    }

    /// Moves the pointer the given distance to the right.
    ///
    /// Returns whether we can prove that this move will not overflow.
    pub fn right(&mut self, count: Count) -> bool {
        let count = count as usize;

        self.left_mark += count;
        if count <= self.right_mark {
            self.right_mark -= count;
            true
        } else {
            self.right_mark = 0;
            false
        }
    }

    /// Resets the left mark.
    ///
    /// This is used when we may move an arbitrary distance to the left.
    pub fn reset_left(&mut self) {
        self.left_mark = 0;
    }

    /// Resets the right mark.
    ///
    /// This is used when we may move an arbitrary distance to the right.
    pub fn reset_right(&mut self) {
        self.right_mark = 0;
    }

    /// Resets both marks.
    ///
    /// This is used when we enter and leave a loop.
    pub fn reset(&mut self) {
        self.reset_left();
        self.reset_right();
    }
}
<|endoftext|>


<commit_before>use dynasmrt::x64::Assembler;
use dynasmrt::{DynasmApi, DynasmLabelApi};

use super::*;
use peephole;

dynasm!(asm
    ; .alias pointer, r12
    ; .alias mem_start, r13
    ; .alias mem_limit, r14
    ; .alias rts, r15
);

pub fn compile(program: &peephole::Program) -> Program {
    let mut asm = dynasmrt::x64::Assembler::new();
    let start = asm.offset();

    dynasm!(asm
        ; push r12
        ; push r13
        ; push r14
        ; push r15
        ; mov pointer, rcx
        ; mov mem_start, rcx
        ; mov mem_limit, rcx
        ; add mem_limit, rdx
        ; mov rts, r8
    );

    compile_sequence(&mut asm, program);

    dynasm!(asm
        ; mov rax, rts::OKAY as i32
        ; jmp ->finish
        ; ->underflow:
        ; mov rax, rts::UNDERFLOW as i32
        ; jmp ->finish
        ; ->overflow:
        ; mov rax, rts::OVERFLOW as i32
        ; ->finish:
        ; pop r15
        ; pop r14
        ; pop r13
        ; pop r12
        ; ret
    );

    let code = asm.finalize().unwrap();

    Program {
        code: code,
        start: start,
    }
}

pub fn compile_sequence(asm: &mut Assembler, program: &[peephole::Instruction]) {
    for instruction in program {
        compile_instruction(asm, instruction);
    }
}

macro_rules! check_pos_offset {
    ($asm:ident, $offset:expr) => {{
        dynasm!($asm
            ; mov rax, QWORD $offset as i64
            ; mov rcx, mem_limit
            ; sub rcx, pointer
            ; cmp rcx, rax
            ; jle ->overflow
        );
    }}
}

macro_rules! check_neg_offset {
    ($asm:ident, $offset:expr) => {{
        dynasm!($asm
            ; mov rax, QWORD $offset as i64
            ; mov rcx, pointer
            ; sub rcx, mem_start
            ; cmp rcx, rax
            ; jl ->underflow
        );
    }}
}

pub fn compile_instruction(asm: &mut Assembler, instruction: &peephole::Instruction) {
    use peephole::Instruction::*;

    match *instruction {
        Right(count) => {
            dynasm!(asm
                ;; check_pos_offset!(asm, count)
                ; add pointer, rax
            );
        }

        Left(count) => {
            dynasm!(asm
                ;; check_neg_offset!(asm, count)
                ; sub pointer, rax
            );
        }

        Change(count) => {
            dynasm!(asm
                ; add [pointer], BYTE u8_to_i8(count)
            );
        }

        In => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::read as _
                ; mov rcx, rts
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
                ; mov [pointer], al
            );
        }

        Out => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::write as _
                ; mov rcx, rts
                ; xor rdx, rdx
                ; mov dl, [pointer]
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
            );
        }

        SetZero => {
            dynasm!(asm
                ; mov BYTE [pointer], 0
            )
        }

        FindZeroRight(skip) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >end_loop
                ; begin_loop:
                ;; check_pos_offset!(asm, skip)
                ; add pointer, rax
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
                ; end_loop:
            )
        }

        FindZeroLeft(skip) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >end_loop
                ; begin_loop:
                ;; check_neg_offset!(asm, skip)
                ; sub pointer, rax
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
                ; end_loop:
            )
        }

        OffsetAddRight(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; check_pos_offset!(asm, offset)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; add BYTE [pointer + rax], cl
                ; skip:
            );
        }

        OffsetAddLeft(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; check_neg_offset!(asm, offset)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; mov rdx, pointer
                ; sub rdx, rax
                ; add BYTE [rdx], cl
                ; skip:
            );
        }

        Loop(ref body) => {
            let begin_label = asm.new_dynamic_label();
            let end_label   = asm.new_dynamic_label();

            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz =>end_label
                ; =>begin_label
            );

            compile_sequence(asm, body);

            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jnz =>begin_label
                ; =>end_label
            );
        }
    }
}

fn u8_to_i8(n: u8) -> i8 {
    let mut n = n as isize % 256;
    if n > 127 { n -= 256 };
    n as i8
}
<commit_msg>Added some blank lines.<commit_after>use dynasmrt::x64::Assembler;
use dynasmrt::{DynasmApi, DynasmLabelApi};

use super::*;
use peephole;

dynasm!(asm
    ; .alias pointer, r12
    ; .alias mem_start, r13
    ; .alias mem_limit, r14
    ; .alias rts, r15
);

pub fn compile(program: &peephole::Program) -> Program {
    let mut asm = dynasmrt::x64::Assembler::new();
    let start = asm.offset();

    dynasm!(asm
        ; push r12
        ; push r13
        ; push r14
        ; push r15
        ; mov pointer, rcx
        ; mov mem_start, rcx
        ; mov mem_limit, rcx
        ; add mem_limit, rdx
        ; mov rts, r8
    );

    compile_sequence(&mut asm, program);

    dynasm!(asm
        ; mov rax, rts::OKAY as i32
        ; jmp ->finish

        ; ->underflow:
        ; mov rax, rts::UNDERFLOW as i32
        ; jmp ->finish

        ; ->overflow:
        ; mov rax, rts::OVERFLOW as i32

        ; ->finish:
        ; pop r15
        ; pop r14
        ; pop r13
        ; pop r12
        ; ret
    );

    let code = asm.finalize().unwrap();

    Program {
        code: code,
        start: start,
    }
}

pub fn compile_sequence(asm: &mut Assembler, program: &[peephole::Instruction]) {
    for instruction in program {
        compile_instruction(asm, instruction);
    }
}

macro_rules! check_pos_offset {
    ($asm:ident, $offset:expr) => {{
        dynasm!($asm
            ; mov rax, QWORD $offset as i64
            ; mov rcx, mem_limit
            ; sub rcx, pointer
            ; cmp rcx, rax
            ; jle ->overflow
        );
    }}
}

macro_rules! check_neg_offset {
    ($asm:ident, $offset:expr) => {{
        dynasm!($asm
            ; mov rax, QWORD $offset as i64
            ; mov rcx, pointer
            ; sub rcx, mem_start
            ; cmp rcx, rax
            ; jl ->underflow
        );
    }}
}

pub fn compile_instruction(asm: &mut Assembler, instruction: &peephole::Instruction) {
    use peephole::Instruction::*;

    match *instruction {
        Right(count) => {
            dynasm!(asm
                ;; check_pos_offset!(asm, count)
                ; add pointer, rax
            );
        }

        Left(count) => {
            dynasm!(asm
                ;; check_neg_offset!(asm, count)
                ; sub pointer, rax
            );
        }

        Change(count) => {
            dynasm!(asm
                ; add [pointer], BYTE u8_to_i8(count)
            );
        }

        In => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::read as _
                ; mov rcx, rts
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
                ; mov [pointer], al
            );
        }

        Out => {
            dynasm!(asm
                ; mov rax, QWORD rts::RtsState::write as _
                ; mov rcx, rts
                ; xor rdx, rdx
                ; mov dl, [pointer]
                ; sub rsp, BYTE 0x28
                ; call rax
                ; add rsp, BYTE 0x28
            );
        }

        SetZero => {
            dynasm!(asm
                ; mov BYTE [pointer], 0
            )
        }

        FindZeroRight(skip) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >end_loop
                ; begin_loop:
                ;; check_pos_offset!(asm, skip)
                ; add pointer, rax
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
                ; end_loop:
            )
        }

        FindZeroLeft(skip) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >end_loop
                ; begin_loop:
                ;; check_neg_offset!(asm, skip)
                ; sub pointer, rax
                ; cmp BYTE [pointer], 0
                ; jnz <begin_loop
                ; end_loop:
            )
        }

        OffsetAddRight(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; check_pos_offset!(asm, offset)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; add BYTE [pointer + rax], cl
                ; skip:
            );
        }

        OffsetAddLeft(offset) => {
            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz >skip
                ;; check_neg_offset!(asm, offset)
                ; mov cl, BYTE [pointer]
                ; mov BYTE [pointer], 0
                ; mov rdx, pointer
                ; sub rdx, rax
                ; add BYTE [rdx], cl
                ; skip:
            );
        }

        Loop(ref body) => {
            let begin_label = asm.new_dynamic_label();
            let end_label   = asm.new_dynamic_label();

            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jz =>end_label
                ; =>begin_label
            );

            compile_sequence(asm, body);

            dynasm!(asm
                ; cmp BYTE [pointer], 0
                ; jnz =>begin_label
                ; =>end_label
            );
        }
    }
}

fn u8_to_i8(n: u8) -> i8 {
    let mut n = n as isize % 256;
    if n > 127 { n -= 256 };
    n as i8
}
<|endoftext|>


<commit_before>use std::sync::Arc;
use std::hash::Hash;
use std::fmt;
use std::collections::{HashMap, HashSet};
use std::collections::hash_map::Entry::{Occupied, Vacant};

use serde::ser::{Serialize, Serializer, SerializeMap};
use serde::de::{self, Deserialize, Deserializer, Visitor, MapAccess};

use intern::{LatticeKey as Key, LatticeVar as Var, SessionId};
use metrics::{Integer};

lazy_static! {
    pub static ref SHARED_KEYS: Integer = Integer::new();
    pub static ref SHARED_COUNTERS: Integer = Integer::new();
    pub static ref SHARED_SETS: Integer = Integer::new();
    pub static ref PRIVATE_KEYS: Integer = Integer::new();
    pub static ref PRIVATE_COUNTERS: Integer = Integer::new();
    pub static ref PRIVATE_SETS: Integer = Integer::new();
    pub static ref SET_ITEMS: Integer = Integer::new();
}

#[derive(Debug, Clone)]
pub struct Counter(u64);
// TODO(tailhook) implement some persistent hash set
// TODO(tailhook) optimize set of only int-like values
#[derive(Debug, Clone)]
pub struct Set(Arc<HashSet<String>>);

trait Crdt: Clone + Sized {
    /// Updates returning `true` if value changed
    fn update(&mut self, other: &Self) -> bool;
}

#[derive(Debug, Clone)]
pub struct Values {
    counters: HashMap<Var, Counter>,
    sets: HashMap<Var, Set>,
}

pub struct Lattice {
    pub shared: HashMap<Key, Values>,
    pub private: HashMap<SessionId, HashMap<Key, Values>>,
    pub subscriptions: HashMap<Key, HashSet<SessionId>>,
}

#[derive(Debug, Clone, Serialize)]
pub struct Delta {
    pub shared: HashMap<Key, Values>,
    pub private: HashMap<SessionId, HashMap<Key, Values>>,
}

impl Values {
    pub fn new() -> Values {
        Values {
            counters: HashMap::new(),
            sets: HashMap::new(),
        }
    }
    pub fn update(&mut self, other: &Values) {
        for (key, value) in &other.counters {
            self.counters.insert(key.clone(), value.clone());
        }
        for (key, value) in &other.sets {
            self.sets.insert(key.clone(), value.clone());
        }
    }
    pub fn is_empty(&self) -> bool {
        self.counters.len() == 0 && self.sets.len() == 0
    }
}

impl Serialize for Values {
    fn serialize<S: Serializer>(&self, serialize: S)
        -> Result<S::Ok, S::Error>
    {
        let mut map = serialize.serialize_map(
            Some(self.counters.len() + self.sets.len()))?;
        for (k, counter) in &self.counters {
            map.serialize_key(&format!("{}_counter", k))?;
            map.serialize_value(&counter.0)?;
        }
        for (k, set) in &self.sets {
            map.serialize_key(&format!("{}_set", k))?;
            map.serialize_value(&set.0)?;
        }
        map.end()
    }
}

impl Lattice {
    pub fn new() -> Lattice {
        Lattice {
            shared: HashMap::new(),
            private: HashMap::new(),
            subscriptions: HashMap::new(),
        }
    }
    /// Updates lattice to be up to date with Delta and returns modified delta
    /// that contains only data that really changed and not out of date
    pub fn update(&mut self, mut delta: Delta) -> Delta {
        let mut del = Vec::new();
        for (room, values) in &mut delta.shared {
            let mine = self.shared.entry(room.clone())
                        .or_insert_with(|| {
                            SHARED_KEYS.incr(1);
                            Values::new()
                        });

            crdt_update(&mut mine.counters, &mut values.counters,
                &*SHARED_COUNTERS);
            crdt_update(&mut mine.sets, &mut values.sets,
                &*SHARED_SETS);

            if values.is_empty() {
                del.push(room.clone());
            }
        }
        for key in &del {
            delta.shared.remove(key);
        }

        for (session_id, rooms) in &mut delta.private {
            let mysess = self.private.entry(session_id.clone())
                .or_insert_with(HashMap::new);
            let mut del_rooms = Vec::new();
            for (room, values) in rooms.iter_mut() {
                let mine = mysess.entry(room.clone())
                    .or_insert_with(|| {
                        PRIVATE_KEYS.incr(1);
                        Values::new()
                    });

                crdt_update(&mut mine.counters, &mut values.counters,
                    &*PRIVATE_COUNTERS);
                crdt_update(&mut mine.sets, &mut values.sets,
                    &*PRIVATE_SETS);

                if values.is_empty() {
                    del_rooms.push(room.clone());
                }
            }
            for key in &del_rooms {
                rooms.remove(key);
            }
        }
        return delta
    }

    pub fn remove_session(&mut self, sid: &SessionId) {
        if let Some(skeys) = self.private.remove(sid) {
            PRIVATE_KEYS.decr(skeys.len() as i64);
            for (key, value) in skeys {
                if let Occupied(mut subs) = self.subscriptions.entry(key.clone()) {
                    subs.get_mut().remove(sid);
                    if subs.get().is_empty() {
                        subs.remove_entry();
                        if self.shared.remove(&key).is_some() {
                            SHARED_KEYS.decr(1);
                        }
                    }
                } else {
                    error!("Subscription inconsistency {:?} {:?}", sid, key);
                }
            }
        }
    }

    pub fn is_empty(&self) -> bool {
        self.shared.is_empty()
        && self.private.is_empty()
        && self.subscriptions.is_empty()
    }
}

impl Crdt for Counter {
    fn update(&mut self, other: &Self) -> bool {
        if self.0 < other.0 {
            self.0 = other.0;
            true
        } else {
            false
        }
    }
}

impl Crdt for Set {
    fn update(&mut self, other: &Self) -> bool {
        let mut iter = other.0.iter();
        let ref mut arc = self.0;
        while let Some(key) = iter.next() {
            if !arc.contains(key) {
                let nset = Arc::make_mut(arc);
                if nset.insert(key.clone()) {
                    SET_ITEMS.incr(1);
                }
                for item in iter {
                    if nset.insert(item.clone()) {
                        SET_ITEMS.incr(1);
                    }
                }
                return true;
            }
        }
        return false;
    }
}

fn crdt_update<K, V>(original: &mut HashMap<K, V>, delta: &mut HashMap<K, V>,
    number: &Integer)
    where K: Clone + Hash + Eq + ::std::fmt::Debug, V: Crdt
{
    let mut del = Vec::new();
    for (key, crdt) in delta {
        match original.entry(key.clone()) {
            Occupied(mut entry) => {
                if !entry.get_mut().update(crdt) {
                    del.push(key.clone());
                }
            }
            Vacant(entry) => {
                number.incr(1);
                entry.insert(crdt.clone());
            }
        }
    }
    for key in &del {
        if original.remove(key).is_some() {
            number.decr(1);
        }
    }
}

impl<'de> Deserialize<'de> for Delta {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        #[derive(Deserialize)]
        struct DeltaOpt {
            pub shared: Option<HashMap<Key, Values>>,
            pub private: Option<HashMap<SessionId, HashMap<Key, Values>>>,
        }
        let tmp = DeltaOpt::deserialize(deserializer)?;
        Ok(Delta {
            shared: tmp.shared.unwrap_or_else(HashMap::new),
            private: tmp.private.unwrap_or_else(HashMap::new),
        })
    }
}

struct ValuesVisitor;

impl<'de> Visitor<'de> for ValuesVisitor {
    type Value = Values;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("map expected")
    }

    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
        where M: MapAccess<'de>
    {
        let mut values = Values {
            counters: HashMap::new(),
            sets: HashMap::new(),
        };
        while let Some(key) = access.next_key::<&str>()? {
            if key.ends_with("_counter") {
                let val = access.next_value()?;
                let key = key[..key.len() - "_counter".len()].parse()
                    .map_err(de::Error::custom)?;
                values.counters.insert(key, Counter(val));
            } else if key.ends_with("_set") {
                let val = access.next_value()?;
                let key = key[..key.len() - "_set".len()].parse()
                    .map_err(de::Error::custom)?;
                values.sets.insert(key, val);
            } else {
                return Err(de::Error::custom(format!(
                    "Unsupported key {:?}", key)))
            }
        }
        Ok(values)
    }
}

impl<'de> Deserialize<'de> for Values {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        deserializer.deserialize_map(ValuesVisitor)
    }
}

impl<'de> Deserialize<'de> for Set {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        Ok(Set(Deserialize::deserialize(deserializer)?))
    }
}

#[cfg(test)]
mod test {
    use std::str::FromStr;
    use serde_json::from_str as json_decode;

    use super::{Delta, Values, Set};
    use intern::{LatticeKey as Key, LatticeVar as Var};

    #[test]
    fn decode_delta() {
        let val = r#"{"shared": {"room_1": {"last_message_counter": 125}},
            "private": {"user:1": {"room_1": {
                "last_seen_set": ["123", "124"]
            }}}}"#;

        let delta: Delta = json_decode(val).unwrap();
        assert_eq!(delta.shared.len(), 1);
        assert_eq!(delta.private.len(), 1);
        assert!(delta.shared.contains_key(&Key::from_str("room_1").unwrap()));
    }

    #[test]
    fn decode_values() {
        let val = r#"{"last_message_counter": 123}"#;
        let val: Values = json_decode(val).unwrap();
        assert_eq!(val.counters.len(), 1);
        assert_eq!(val.sets.len(), 0);

        let key = Var::from_str("last_message").unwrap();
        assert!(val.counters.contains_key(&key));
        assert_eq!(val.counters.get(&key).unwrap().0, 123u64);
    }

    #[test]
    fn decode_set() {
        let set: Set = json_decode(r#"["123", "123", "abc"]"#).unwrap();
        assert_eq!(set.0.len(), 2);
    }
}
<commit_msg>Fixes premature removal of the lattice keys<commit_after>use std::sync::Arc;
use std::hash::Hash;
use std::fmt;
use std::collections::{HashMap, HashSet};
use std::collections::hash_map::Entry::{Occupied, Vacant};

use serde::ser::{Serialize, Serializer, SerializeMap};
use serde::de::{self, Deserialize, Deserializer, Visitor, MapAccess};

use intern::{LatticeKey as Key, LatticeVar as Var, SessionId};
use metrics::{Integer};

lazy_static! {
    pub static ref SHARED_KEYS: Integer = Integer::new();
    pub static ref SHARED_COUNTERS: Integer = Integer::new();
    pub static ref SHARED_SETS: Integer = Integer::new();
    pub static ref PRIVATE_KEYS: Integer = Integer::new();
    pub static ref PRIVATE_COUNTERS: Integer = Integer::new();
    pub static ref PRIVATE_SETS: Integer = Integer::new();
    pub static ref SET_ITEMS: Integer = Integer::new();
}

#[derive(Debug, Clone)]
pub struct Counter(u64);
// TODO(tailhook) implement some persistent hash set
// TODO(tailhook) optimize set of only int-like values
#[derive(Debug, Clone)]
pub struct Set(Arc<HashSet<String>>);

trait Crdt: Clone + Sized {
    /// Updates returning `true` if value changed
    fn update(&mut self, other: &Self) -> bool;
}

#[derive(Debug, Clone)]
pub struct Values {
    counters: HashMap<Var, Counter>,
    sets: HashMap<Var, Set>,
}

pub struct Lattice {
    pub shared: HashMap<Key, Values>,
    pub private: HashMap<SessionId, HashMap<Key, Values>>,
    pub subscriptions: HashMap<Key, HashSet<SessionId>>,
}

#[derive(Debug, Clone, Serialize)]
pub struct Delta {
    pub shared: HashMap<Key, Values>,
    pub private: HashMap<SessionId, HashMap<Key, Values>>,
}

impl Values {
    pub fn new() -> Values {
        Values {
            counters: HashMap::new(),
            sets: HashMap::new(),
        }
    }
    pub fn update(&mut self, other: &Values) {
        for (key, value) in &other.counters {
            self.counters.insert(key.clone(), value.clone());
        }
        for (key, value) in &other.sets {
            self.sets.insert(key.clone(), value.clone());
        }
    }
    pub fn is_empty(&self) -> bool {
        self.counters.len() == 0 && self.sets.len() == 0
    }
}

impl Serialize for Values {
    fn serialize<S: Serializer>(&self, serialize: S)
        -> Result<S::Ok, S::Error>
    {
        let mut map = serialize.serialize_map(
            Some(self.counters.len() + self.sets.len()))?;
        for (k, counter) in &self.counters {
            map.serialize_key(&format!("{}_counter", k))?;
            map.serialize_value(&counter.0)?;
        }
        for (k, set) in &self.sets {
            map.serialize_key(&format!("{}_set", k))?;
            map.serialize_value(&set.0)?;
        }
        map.end()
    }
}

impl Lattice {
    pub fn new() -> Lattice {
        Lattice {
            shared: HashMap::new(),
            private: HashMap::new(),
            subscriptions: HashMap::new(),
        }
    }
    /// Updates lattice to be up to date with Delta and returns modified delta
    /// that contains only data that really changed and not out of date
    pub fn update(&mut self, mut delta: Delta) -> Delta {
        let mut del = Vec::new();
        for (room, values) in &mut delta.shared {
            let mine = self.shared.entry(room.clone())
                        .or_insert_with(|| {
                            SHARED_KEYS.incr(1);
                            Values::new()
                        });

            crdt_update(&mut mine.counters, &mut values.counters,
                &*SHARED_COUNTERS);
            crdt_update(&mut mine.sets, &mut values.sets,
                &*SHARED_SETS);

            if values.is_empty() {
                del.push(room.clone());
            }
        }
        for key in &del {
            delta.shared.remove(key);
        }

        for (session_id, rooms) in &mut delta.private {
            let mysess = self.private.entry(session_id.clone())
                .or_insert_with(HashMap::new);
            let mut del_rooms = Vec::new();
            for (room, values) in rooms.iter_mut() {
                let mine = mysess.entry(room.clone())
                    .or_insert_with(|| {
                        PRIVATE_KEYS.incr(1);
                        Values::new()
                    });

                crdt_update(&mut mine.counters, &mut values.counters,
                    &*PRIVATE_COUNTERS);
                crdt_update(&mut mine.sets, &mut values.sets,
                    &*PRIVATE_SETS);

                if values.is_empty() {
                    del_rooms.push(room.clone());
                }
            }
            for key in &del_rooms {
                rooms.remove(key);
            }
        }
        return delta
    }

    pub fn remove_session(&mut self, sid: &SessionId) {
        if let Some(skeys) = self.private.remove(sid) {
            PRIVATE_KEYS.decr(skeys.len() as i64);
            PRIVATE_COUNTERS.decr(skeys.values()
                .map(|v| v.counters.len() as i64).sum());
            PRIVATE_SETS.decr(skeys.values()
                .map(|v| v.sets.len() as i64).sum());
            for (key, value) in skeys {
                if let Occupied(mut subs) = self.subscriptions.entry(key.clone()) {
                    subs.get_mut().remove(sid);
                    if subs.get().is_empty() {
                        subs.remove_entry();
                        if let Some(vals) = self.shared.remove(&key) {
                            SHARED_KEYS.decr(1);
                            SHARED_COUNTERS.decr(vals.counters.len() as i64);
                            SHARED_SETS.decr(vals.sets.len() as i64);
                        }
                    }
                } else {
                    error!("Subscription inconsistency {:?} {:?}", sid, key);
                }
            }
        }
    }

    pub fn is_empty(&self) -> bool {
        self.shared.is_empty()
        && self.private.is_empty()
        && self.subscriptions.is_empty()
    }
}

impl Crdt for Counter {
    fn update(&mut self, other: &Self) -> bool {
        if self.0 < other.0 {
            self.0 = other.0;
            true
        } else {
            false
        }
    }
}

impl Crdt for Set {
    fn update(&mut self, other: &Self) -> bool {
        let mut iter = other.0.iter();
        let ref mut arc = self.0;
        while let Some(key) = iter.next() {
            if !arc.contains(key) {
                let nset = Arc::make_mut(arc);
                if nset.insert(key.clone()) {
                    SET_ITEMS.incr(1);
                }
                for item in iter {
                    if nset.insert(item.clone()) {
                        SET_ITEMS.incr(1);
                    }
                }
                return true;
            }
        }
        return false;
    }
}

fn crdt_update<K, V>(original: &mut HashMap<K, V>, delta: &mut HashMap<K, V>,
    number: &Integer)
    where K: Clone + Hash + Eq + ::std::fmt::Debug, V: Crdt
{
    let mut del = Vec::new();
    for (key, crdt) in delta.iter() {
        match original.entry(key.clone()) {
            Occupied(mut entry) => {
                if !entry.get_mut().update(crdt) {
                    del.push(key.clone());
                }
            }
            Vacant(entry) => {
                number.incr(1);
                entry.insert(crdt.clone());
            }
        }
    }
    for key in &del {
        delta.remove(key);
    }
}

impl<'de> Deserialize<'de> for Delta {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        #[derive(Deserialize)]
        struct DeltaOpt {
            pub shared: Option<HashMap<Key, Values>>,
            pub private: Option<HashMap<SessionId, HashMap<Key, Values>>>,
        }
        let tmp = DeltaOpt::deserialize(deserializer)?;
        Ok(Delta {
            shared: tmp.shared.unwrap_or_else(HashMap::new),
            private: tmp.private.unwrap_or_else(HashMap::new),
        })
    }
}

struct ValuesVisitor;

impl<'de> Visitor<'de> for ValuesVisitor {
    type Value = Values;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("map expected")
    }

    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
        where M: MapAccess<'de>
    {
        let mut values = Values {
            counters: HashMap::new(),
            sets: HashMap::new(),
        };
        while let Some(key) = access.next_key::<&str>()? {
            if key.ends_with("_counter") {
                let val = access.next_value()?;
                let key = key[..key.len() - "_counter".len()].parse()
                    .map_err(de::Error::custom)?;
                values.counters.insert(key, Counter(val));
            } else if key.ends_with("_set") {
                let val = access.next_value()?;
                let key = key[..key.len() - "_set".len()].parse()
                    .map_err(de::Error::custom)?;
                values.sets.insert(key, val);
            } else {
                return Err(de::Error::custom(format!(
                    "Unsupported key {:?}", key)))
            }
        }
        Ok(values)
    }
}

impl<'de> Deserialize<'de> for Values {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        deserializer.deserialize_map(ValuesVisitor)
    }
}

impl<'de> Deserialize<'de> for Set {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        Ok(Set(Deserialize::deserialize(deserializer)?))
    }
}

#[cfg(test)]
mod test {
    use std::str::FromStr;
    use serde_json::from_str as json_decode;

    use super::{Delta, Values, Set};
    use intern::{LatticeKey as Key, LatticeVar as Var};

    #[test]
    fn decode_delta() {
        let val = r#"{"shared": {"room_1": {"last_message_counter": 125}},
            "private": {"user:1": {"room_1": {
                "last_seen_set": ["123", "124"]
            }}}}"#;

        let delta: Delta = json_decode(val).unwrap();
        assert_eq!(delta.shared.len(), 1);
        assert_eq!(delta.private.len(), 1);
        assert!(delta.shared.contains_key(&Key::from_str("room_1").unwrap()));
    }

    #[test]
    fn decode_values() {
        let val = r#"{"last_message_counter": 123}"#;
        let val: Values = json_decode(val).unwrap();
        assert_eq!(val.counters.len(), 1);
        assert_eq!(val.sets.len(), 0);

        let key = Var::from_str("last_message").unwrap();
        assert!(val.counters.contains_key(&key));
        assert_eq!(val.counters.get(&key).unwrap().0, 123u64);
    }

    #[test]
    fn decode_set() {
        let set: Set = json_decode(r#"["123", "123", "abc"]"#).unwrap();
        assert_eq!(set.0.len(), 2);
    }
}
<|endoftext|>


<commit_before>use crate::{LateContext, LateLintPass, LintContext};
use rustc_ast as ast;
use rustc_errors::{pluralize, Applicability};
use rustc_hir as hir;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_parse_format::{ParseMode, Parser, Piece};
use rustc_session::lint::FutureIncompatibilityReason;
use rustc_span::edition::Edition;
use rustc_span::{hygiene, sym, symbol::kw, symbol::SymbolStr, InnerSpan, Span, Symbol};

declare_lint! {
    /// The `non_fmt_panics` lint detects `panic!(..)` invocations where the first
    /// argument is not a formatting string.
    ///
    /// ### Example
    ///
    /// ```rust,no_run
    /// panic!("{}");
    /// panic!(123);
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// In Rust 2018 and earlier, `panic!(x)` directly uses `x` as the message.
    /// That means that `panic!("{}")` panics with the message `"{}"` instead
    /// of using it as a formatting string, and `panic!(123)` will panic with
    /// an `i32` as message.
    ///
    /// Rust 2021 always interprets the first argument as format string.
    NON_FMT_PANICS,
    Warn,
    "detect single-argument panic!() invocations in which the argument is not a format string",
    @future_incompatible = FutureIncompatibleInfo {
        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
        explain_reason: false,
    };
    report_in_external_macro
}

declare_lint_pass!(NonPanicFmt => [NON_FMT_PANICS]);

impl<'tcx> LateLintPass<'tcx> for NonPanicFmt {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
        if let hir::ExprKind::Call(f, [arg]) = &expr.kind {
            if let &ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(f).kind() {
                if Some(def_id) == cx.tcx.lang_items().begin_panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_str()
                {
                    if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
                        if cx.tcx.is_diagnostic_item(sym::std_panic_2015_macro, id)
                            || cx.tcx.is_diagnostic_item(sym::core_panic_2015_macro, id)
                        {
                            check_panic(cx, f, arg);
                        }
                    }
                }
            }
        }
    }
}

fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>) {
    if let hir::ExprKind::Lit(lit) = &arg.kind {
        if let ast::LitKind::Str(sym, _) = lit.node {
            // The argument is a string literal.
            check_panic_str(cx, f, arg, &sym.as_str());
            return;
        }
    }

    // The argument is *not* a string literal.

    let (span, panic, symbol_str) = panic_call(cx, f);

    if in_external_macro(cx.sess(), span) {
        // Nothing that can be done about it in the current crate.
        return;
    }

    // Find the span of the argument to `panic!()`, before expansion in the
    // case of `panic!(some_macro!())`.
    // We don't use source_callsite(), because this `panic!(..)` might itself
    // be expanded from another macro, in which case we want to stop at that
    // expansion.
    let mut arg_span = arg.span;
    let mut arg_macro = None;
    while !span.contains(arg_span) {
        let expn = arg_span.ctxt().outer_expn_data();
        if expn.is_root() {
            break;
        }
        arg_macro = expn.macro_def_id;
        arg_span = expn.call_site;
    }

    cx.struct_span_lint(NON_FMT_PANICS, arg_span, |lint| {
        let mut l = lint.build("panic message is not a string literal");
        l.note("this usage of panic!() is deprecated; it will be a hard error in Rust 2021");
        l.note("for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/panic-macro-consistency.html>");
        if !span.contains(arg_span) {
            // No clue where this argument is coming from.
            l.emit();
            return;
        }
        if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) {
            // A case of `panic!(format!(..))`.
            l.note(format!("the {}!() macro supports formatting, so there's no need for the format!() macro here", symbol_str).as_str());
            if let Some((open, close, _)) = find_delimiters(cx, arg_span) {
                l.multipart_suggestion(
                    "remove the `format!(..)` macro call",
                    vec![
                        (arg_span.until(open.shrink_to_hi()), "".into()),
                        (close.until(arg_span.shrink_to_hi()), "".into()),
                    ],
                    Applicability::MachineApplicable,
                );
            }
        } else {
            l.span_suggestion_verbose(
                arg_span.shrink_to_lo(),
                "add a \"{}\" format string to Display the message",
                "\"{}\", ".into(),
                Applicability::MaybeIncorrect,
            );
            if panic == sym::std_panic_macro {
                if let Some((open, close, del)) = find_delimiters(cx, span) {
                    l.multipart_suggestion(
                        "or use std::panic::panic_any instead",
                        if del == '(' {
                            vec![(span.until(open), "std::panic::panic_any".into())]
                        } else {
                            vec![
                                (span.until(open.shrink_to_hi()), "std::panic::panic_any(".into()),
                                (close, ")".into()),
                            ]
                        },
                        Applicability::MachineApplicable,
                    );
                }
            }
        }
        l.emit();
    });
}

fn check_panic_str<'tcx>(
    cx: &LateContext<'tcx>,
    f: &'tcx hir::Expr<'tcx>,
    arg: &'tcx hir::Expr<'tcx>,
    fmt: &str,
) {
    if !fmt.contains(&['{', '}'][..]) {
        // No brace, no problem.
        return;
    }

    let (span, _, _) = panic_call(cx, f);

    if in_external_macro(cx.sess(), span) && in_external_macro(cx.sess(), arg.span) {
        // Nothing that can be done about it in the current crate.
        return;
    }

    let fmt_span = arg.span.source_callsite();

    let (snippet, style) = match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) {
        Ok(snippet) => {
            // Count the number of `#`s between the `r` and `"`.
            let style = snippet.strip_prefix('r').and_then(|s| s.find('"'));
            (Some(snippet), style)
        }
        Err(_) => (None, None),
    };

    let mut fmt_parser =
        Parser::new(fmt.as_ref(), style, snippet.clone(), false, ParseMode::Format);
    let n_arguments = (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count();

    if n_arguments > 0 && fmt_parser.errors.is_empty() {
        let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] {
            [] => vec![fmt_span],
            v => v.iter().map(|span| fmt_span.from_inner(*span)).collect(),
        };
        cx.struct_span_lint(NON_FMT_PANICS, arg_spans, |lint| {
            let mut l = lint.build(match n_arguments {
                1 => "panic message contains an unused formatting placeholder",
                _ => "panic message contains unused formatting placeholders",
            });
            l.note("this message is not used as a format string when given without arguments, but will be in Rust 2021");
            if span.contains(arg.span) {
                l.span_suggestion(
                    arg.span.shrink_to_hi(),
                    &format!("add the missing argument{}", pluralize!(n_arguments)),
                    ", ...".into(),
                    Applicability::HasPlaceholders,
                );
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "or add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    } else {
        let brace_spans: Option<Vec<_>> =
            snippet.filter(|s| s.starts_with('"') || s.starts_with("r#")).map(|s| {
                s.char_indices()
                    .filter(|&(_, c)| c == '{' || c == '}')
                    .map(|(i, _)| fmt_span.from_inner(InnerSpan { start: i, end: i + 1 }))
                    .collect()
            });
        let msg = match &brace_spans {
            Some(v) if v.len() == 1 => "panic message contains a brace",
            _ => "panic message contains braces",
        };
        cx.struct_span_lint(NON_FMT_PANICS, brace_spans.unwrap_or_else(|| vec![span]), |lint| {
            let mut l = lint.build(msg);
            l.note("this message is not used as a format string, but will be in Rust 2021");
            if span.contains(arg.span) {
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    }
}

/// Given the span of `some_macro!(args);`, gives the span of `(` and `)`,
/// and the type of (opening) delimiter used.
fn find_delimiters<'tcx>(cx: &LateContext<'tcx>, span: Span) -> Option<(Span, Span, char)> {
    let snippet = cx.sess().parse_sess.source_map().span_to_snippet(span).ok()?;
    let (open, open_ch) = snippet.char_indices().find(|&(_, c)| "([{".contains(c))?;
    let close = snippet.rfind(|c| ")]}".contains(c))?;
    Some((
        span.from_inner(InnerSpan { start: open, end: open + 1 }),
        span.from_inner(InnerSpan { start: close, end: close + 1 }),
        open_ch,
    ))
}

fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol, SymbolStr) {
    let mut expn = f.span.ctxt().outer_expn_data();

    let mut panic_macro = kw::Empty;

    // Unwrap more levels of macro expansion, as panic_2015!()
    // was likely expanded from panic!() and possibly from
    // [debug_]assert!().
    for &i in
        &[sym::std_panic_macro, sym::core_panic_macro, sym::assert_macro, sym::debug_assert_macro]
    {
        let parent = expn.call_site.ctxt().outer_expn_data();
        if parent.macro_def_id.map_or(false, |id| cx.tcx.is_diagnostic_item(i, id)) {
            expn = parent;
            panic_macro = i;
        }
    }

    let macro_symbol =
        if let hygiene::ExpnKind::Macro(_, symbol) = expn.kind { symbol } else { sym::panic };
    (expn.call_site, panic_macro, macro_symbol.as_str())
}
<commit_msg>non_fmt_panic: machine app. suggestion for assert with string msg.<commit_after>use crate::{LateContext, LateLintPass, LintContext};
use rustc_ast as ast;
use rustc_errors::{pluralize, Applicability};
use rustc_hir as hir;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_parse_format::{ParseMode, Parser, Piece};
use rustc_session::lint::FutureIncompatibilityReason;
use rustc_span::edition::Edition;
use rustc_span::{hygiene, sym, symbol::kw, symbol::SymbolStr, InnerSpan, Span, Symbol};

declare_lint! {
    /// The `non_fmt_panics` lint detects `panic!(..)` invocations where the first
    /// argument is not a formatting string.
    ///
    /// ### Example
    ///
    /// ```rust,no_run
    /// panic!("{}");
    /// panic!(123);
    /// ```
    ///
    /// {{produces}}
    ///
    /// ### Explanation
    ///
    /// In Rust 2018 and earlier, `panic!(x)` directly uses `x` as the message.
    /// That means that `panic!("{}")` panics with the message `"{}"` instead
    /// of using it as a formatting string, and `panic!(123)` will panic with
    /// an `i32` as message.
    ///
    /// Rust 2021 always interprets the first argument as format string.
    NON_FMT_PANICS,
    Warn,
    "detect single-argument panic!() invocations in which the argument is not a format string",
    @future_incompatible = FutureIncompatibleInfo {
        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
        explain_reason: false,
    };
    report_in_external_macro
}

declare_lint_pass!(NonPanicFmt => [NON_FMT_PANICS]);

impl<'tcx> LateLintPass<'tcx> for NonPanicFmt {
    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
        if let hir::ExprKind::Call(f, [arg]) = &expr.kind {
            if let &ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(f).kind() {
                if Some(def_id) == cx.tcx.lang_items().begin_panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_fn()
                    || Some(def_id) == cx.tcx.lang_items().panic_str()
                {
                    if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
                        if cx.tcx.is_diagnostic_item(sym::std_panic_2015_macro, id)
                            || cx.tcx.is_diagnostic_item(sym::core_panic_2015_macro, id)
                        {
                            check_panic(cx, f, arg);
                        }
                    }
                }
            }
        }
    }
}

fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>) {
    if let hir::ExprKind::Lit(lit) = &arg.kind {
        if let ast::LitKind::Str(sym, _) = lit.node {
            // The argument is a string literal.
            check_panic_str(cx, f, arg, &sym.as_str());
            return;
        }
    }

    // The argument is *not* a string literal.

    let (span, panic, symbol_str) = panic_call(cx, f);

    if in_external_macro(cx.sess(), span) {
        // Nothing that can be done about it in the current crate.
        return;
    }

    // Find the span of the argument to `panic!()`, before expansion in the
    // case of `panic!(some_macro!())`.
    // We don't use source_callsite(), because this `panic!(..)` might itself
    // be expanded from another macro, in which case we want to stop at that
    // expansion.
    let mut arg_span = arg.span;
    let mut arg_macro = None;
    while !span.contains(arg_span) {
        let expn = arg_span.ctxt().outer_expn_data();
        if expn.is_root() {
            break;
        }
        arg_macro = expn.macro_def_id;
        arg_span = expn.call_site;
    }

    cx.struct_span_lint(NON_FMT_PANICS, arg_span, |lint| {
        let mut l = lint.build("panic message is not a string literal");
        l.note("this usage of panic!() is deprecated; it will be a hard error in Rust 2021");
        l.note("for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/panic-macro-consistency.html>");
        if !span.contains(arg_span) {
            // No clue where this argument is coming from.
            l.emit();
            return;
        }
        if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) {
            // A case of `panic!(format!(..))`.
            l.note(format!("the {}!() macro supports formatting, so there's no need for the format!() macro here", symbol_str).as_str());
            if let Some((open, close, _)) = find_delimiters(cx, arg_span) {
                l.multipart_suggestion(
                    "remove the `format!(..)` macro call",
                    vec![
                        (arg_span.until(open.shrink_to_hi()), "".into()),
                        (close.until(arg_span.shrink_to_hi()), "".into()),
                    ],
                    Applicability::MachineApplicable,
                );
            }
        } else {
            let ty = cx.typeck_results().expr_ty(arg);
            // If this is a &str or String, we can confidently give the `"{}", ` suggestion.
            let is_str = matches!(
                ty.kind(),
                ty::Ref(_, r, _) if *r.kind() == ty::Str
            ) || matches!(
                (ty.ty_adt_def(), cx.tcx.get_diagnostic_item(sym::string_type)),
                (Some(ty_def), Some(string_type)) if ty_def.did == string_type
            );
            l.span_suggestion_verbose(
                arg_span.shrink_to_lo(),
                "add a \"{}\" format string to Display the message",
                "\"{}\", ".into(),
                if is_str {
                    Applicability::MachineApplicable
                } else {
                    Applicability::MaybeIncorrect
                },
            );
            if !is_str && panic == sym::std_panic_macro {
                if let Some((open, close, del)) = find_delimiters(cx, span) {
                    l.multipart_suggestion(
                        "or use std::panic::panic_any instead",
                        if del == '(' {
                            vec![(span.until(open), "std::panic::panic_any".into())]
                        } else {
                            vec![
                                (span.until(open.shrink_to_hi()), "std::panic::panic_any(".into()),
                                (close, ")".into()),
                            ]
                        },
                        Applicability::MachineApplicable,
                    );
                }
            }
        }
        l.emit();
    });
}

fn check_panic_str<'tcx>(
    cx: &LateContext<'tcx>,
    f: &'tcx hir::Expr<'tcx>,
    arg: &'tcx hir::Expr<'tcx>,
    fmt: &str,
) {
    if !fmt.contains(&['{', '}'][..]) {
        // No brace, no problem.
        return;
    }

    let (span, _, _) = panic_call(cx, f);

    if in_external_macro(cx.sess(), span) && in_external_macro(cx.sess(), arg.span) {
        // Nothing that can be done about it in the current crate.
        return;
    }

    let fmt_span = arg.span.source_callsite();

    let (snippet, style) = match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) {
        Ok(snippet) => {
            // Count the number of `#`s between the `r` and `"`.
            let style = snippet.strip_prefix('r').and_then(|s| s.find('"'));
            (Some(snippet), style)
        }
        Err(_) => (None, None),
    };

    let mut fmt_parser =
        Parser::new(fmt.as_ref(), style, snippet.clone(), false, ParseMode::Format);
    let n_arguments = (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count();

    if n_arguments > 0 && fmt_parser.errors.is_empty() {
        let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] {
            [] => vec![fmt_span],
            v => v.iter().map(|span| fmt_span.from_inner(*span)).collect(),
        };
        cx.struct_span_lint(NON_FMT_PANICS, arg_spans, |lint| {
            let mut l = lint.build(match n_arguments {
                1 => "panic message contains an unused formatting placeholder",
                _ => "panic message contains unused formatting placeholders",
            });
            l.note("this message is not used as a format string when given without arguments, but will be in Rust 2021");
            if span.contains(arg.span) {
                l.span_suggestion(
                    arg.span.shrink_to_hi(),
                    &format!("add the missing argument{}", pluralize!(n_arguments)),
                    ", ...".into(),
                    Applicability::HasPlaceholders,
                );
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "or add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    } else {
        let brace_spans: Option<Vec<_>> =
            snippet.filter(|s| s.starts_with('"') || s.starts_with("r#")).map(|s| {
                s.char_indices()
                    .filter(|&(_, c)| c == '{' || c == '}')
                    .map(|(i, _)| fmt_span.from_inner(InnerSpan { start: i, end: i + 1 }))
                    .collect()
            });
        let msg = match &brace_spans {
            Some(v) if v.len() == 1 => "panic message contains a brace",
            _ => "panic message contains braces",
        };
        cx.struct_span_lint(NON_FMT_PANICS, brace_spans.unwrap_or_else(|| vec![span]), |lint| {
            let mut l = lint.build(msg);
            l.note("this message is not used as a format string, but will be in Rust 2021");
            if span.contains(arg.span) {
                l.span_suggestion(
                    arg.span.shrink_to_lo(),
                    "add a \"{}\" format string to use the message literally",
                    "\"{}\", ".into(),
                    Applicability::MachineApplicable,
                );
            }
            l.emit();
        });
    }
}

/// Given the span of `some_macro!(args);`, gives the span of `(` and `)`,
/// and the type of (opening) delimiter used.
fn find_delimiters<'tcx>(cx: &LateContext<'tcx>, span: Span) -> Option<(Span, Span, char)> {
    let snippet = cx.sess().parse_sess.source_map().span_to_snippet(span).ok()?;
    let (open, open_ch) = snippet.char_indices().find(|&(_, c)| "([{".contains(c))?;
    let close = snippet.rfind(|c| ")]}".contains(c))?;
    Some((
        span.from_inner(InnerSpan { start: open, end: open + 1 }),
        span.from_inner(InnerSpan { start: close, end: close + 1 }),
        open_ch,
    ))
}

fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol, SymbolStr) {
    let mut expn = f.span.ctxt().outer_expn_data();

    let mut panic_macro = kw::Empty;

    // Unwrap more levels of macro expansion, as panic_2015!()
    // was likely expanded from panic!() and possibly from
    // [debug_]assert!().
    for &i in
        &[sym::std_panic_macro, sym::core_panic_macro, sym::assert_macro, sym::debug_assert_macro]
    {
        let parent = expn.call_site.ctxt().outer_expn_data();
        if parent.macro_def_id.map_or(false, |id| cx.tcx.is_diagnostic_item(i, id)) {
            expn = parent;
            panic_macro = i;
        }
    }

    let macro_symbol =
        if let hygiene::ExpnKind::Macro(_, symbol) = expn.kind { symbol } else { sym::panic };
    (expn.call_site, panic_macro, macro_symbol.as_str())
}
<|endoftext|>


<commit_before>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::layout::Layout;
use diem_crypto::ed25519::Ed25519PublicKey;
use diem_global_constants::{DIEM_ROOT_KEY, OPERATOR_KEY, OWNER_KEY};
use diem_management::{config::ConfigPath, constants, error::Error, secure_backend::SharedBackend};
use diem_transaction_builder::stdlib as transaction_builder;
use diem_types::{
    account_address,
    chain_id::ChainId,
    transaction::{Transaction, TransactionPayload},
};
use std::{fs::File, io::Write, path::PathBuf};
use structopt::StructOpt;
use vm_genesis::{OperatorAssignment, OperatorRegistration};

/// Note, it is implicitly expected that the storage supports
/// a namespace but one has not been set.
#[derive(Debug, StructOpt)]
pub struct Genesis {
    #[structopt(flatten)]
    pub config: ConfigPath,
    #[structopt(long, required_unless("config"))]
    pub chain_id: Option<ChainId>,
    #[structopt(flatten)]
    pub backend: SharedBackend,
    #[structopt(long)]
    pub path: Option<PathBuf>,
}

impl Genesis {
    fn config(&self) -> Result<diem_management::config::Config, Error> {
        self.config
            .load()?
            .override_chain_id(self.chain_id)
            .override_shared_backend(&self.backend.shared_backend)
    }

    pub fn execute(self) -> Result<Transaction, Error> {
        let layout = self.layout()?;
        let diem_root_key = self.diem_root_key(&layout)?;
        let treasury_compliance_key = self.treasury_compliance_key(&layout)?;
        let operator_assignments = self.operator_assignments(&layout)?;
        let operator_registrations = self.operator_registrations(&layout)?;

        let chain_id = self.config()?.chain_id;
        // Only have an allowlist of stdlib scripts
        let script_policy = None;

        let genesis = vm_genesis::encode_genesis_transaction(
            diem_root_key,
            treasury_compliance_key,
            &operator_assignments,
            &operator_registrations,
            script_policy,
            chain_id,
        );

        if let Some(path) = self.path {
            let mut file = File::create(path).map_err(|e| {
                Error::UnexpectedError(format!("Unable to create genesis file: {}", e.to_string()))
            })?;
            let bytes = bcs::to_bytes(&genesis).map_err(|e| {
                Error::UnexpectedError(format!("Unable to serialize genesis: {}", e.to_string()))
            })?;
            file.write_all(&bytes).map_err(|e| {
                Error::UnexpectedError(format!("Unable to write genesis file: {}", e.to_string()))
            })?;
        }

        Ok(genesis)
    }

    /// Retrieves the diem root key from the remote storage. Note, at this point in time, genesis
    /// only supports a single diem root key.
    pub fn diem_root_key(&self, layout: &Layout) -> Result<Ed25519PublicKey, Error> {
        let config = self.config()?;
        let storage = config.shared_backend_with_namespace(layout.diem_root.clone());
        storage.ed25519_key(DIEM_ROOT_KEY)
    }

    /// Retrieves a layout from the remote storage.
    pub fn layout(&self) -> Result<Layout, Error> {
        let config = self.config()?;
        let storage = config.shared_backend_with_namespace(constants::COMMON_NS.into());

        let layout = storage.string(constants::LAYOUT)?;
        Layout::parse(&layout).map_err(|e| Error::UnableToParse(constants::LAYOUT, e.to_string()))
    }

    /// Produces a set of OperatorAssignments from the remote storage.
    pub fn operator_assignments(&self, layout: &Layout) -> Result<Vec<OperatorAssignment>, Error> {
        let config = self.config()?;
        let mut operator_assignments = Vec::new();

        for owner in layout.owners.iter() {
            let owner_storage = config.shared_backend_with_namespace(owner.into());
            let owner_key = owner_storage.ed25519_key(OWNER_KEY).ok();

            let operator_name = owner_storage.string(constants::VALIDATOR_OPERATOR)?;
            let operator_storage = config.shared_backend_with_namespace(operator_name.clone());
            let operator_key = operator_storage.ed25519_key(OPERATOR_KEY)?;
            let operator_account = account_address::from_public_key(&operator_key);

            let set_operator_script =
                transaction_builder::encode_set_validator_operator_script_function(
                    operator_name.as_bytes().to_vec(),
                    operator_account,
                )
                .into_script_function();

            let owner_name_vec = owner.as_bytes().to_vec();
            operator_assignments.push((owner_key, owner_name_vec, set_operator_script));
        }

        Ok(operator_assignments)
    }

    /// Produces a set of OperatorRegistrations from the remote storage.
    pub fn operator_registrations(
        &self,
        layout: &Layout,
    ) -> Result<Vec<OperatorRegistration>, Error> {
        let config = self.config()?;
        let mut registrations = Vec::new();

        for operator in layout.operators.iter() {
            let operator_storage = config.shared_backend_with_namespace(operator.into());
            let operator_key = operator_storage.ed25519_key(OPERATOR_KEY)?;
            let validator_config_tx = operator_storage.transaction(constants::VALIDATOR_CONFIG)?;
            let validator_config_tx = validator_config_tx.as_signed_user_txn().unwrap().payload();
            let validator_config_tx =
                if let TransactionPayload::ScriptFunction(script_function) = validator_config_tx {
                    script_function.clone()
                } else {
                    return Err(Error::UnexpectedError("Found invalid registration".into()));
                };

            registrations.push((
                operator_key,
                operator.as_bytes().to_vec(),
                validator_config_tx,
            ));
        }

        Ok(registrations)
    }

    /// Retrieves the treasury root key from the remote storage.
    pub fn treasury_compliance_key(&self, layout: &Layout) -> Result<Ed25519PublicKey, Error> {
        let config = self.config()?;
        let storage = config.shared_backend_with_namespace(layout.diem_root.clone());
        storage.ed25519_key(diem_global_constants::TREASURY_COMPLIANCE_KEY)
    }
}
<commit_msg>genesis: convert the genesis command to build genesis with the GenesisBuilder<commit_after>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::builder::GenesisBuilder;
use diem_management::{config::ConfigPath, error::Error, secure_backend::SharedBackend};
use diem_secure_storage::Storage;
use diem_types::{chain_id::ChainId, transaction::Transaction};
use std::{fs::File, io::Write, path::PathBuf};
use structopt::StructOpt;

/// Note, it is implicitly expected that the storage supports
/// a namespace but one has not been set.
#[derive(Debug, StructOpt)]
pub struct Genesis {
    #[structopt(flatten)]
    pub config: ConfigPath,
    #[structopt(long, required_unless("config"))]
    pub chain_id: Option<ChainId>,
    #[structopt(flatten)]
    pub backend: SharedBackend,
    #[structopt(long)]
    pub path: Option<PathBuf>,
}

impl Genesis {
    fn config(&self) -> Result<diem_management::config::Config, Error> {
        self.config
            .load()?
            .override_chain_id(self.chain_id)
            .override_shared_backend(&self.backend.shared_backend)
    }

    pub fn execute(self) -> Result<Transaction, Error> {
        let config = self.config()?;
        let chain_id = config.chain_id;
        let storage = Storage::from(&config.shared_backend);
        let genesis = GenesisBuilder::new(storage)
            .build(chain_id)
            .map_err(|e| Error::UnexpectedError(e.to_string()))?;

        if let Some(path) = self.path {
            let mut file = File::create(path).map_err(|e| {
                Error::UnexpectedError(format!("Unable to create genesis file: {}", e.to_string()))
            })?;
            let bytes = bcs::to_bytes(&genesis).map_err(|e| {
                Error::UnexpectedError(format!("Unable to serialize genesis: {}", e.to_string()))
            })?;
            file.write_all(&bytes).map_err(|e| {
                Error::UnexpectedError(format!("Unable to write genesis file: {}", e.to_string()))
            })?;
        }

        Ok(genesis)
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 - 2016, Alberto Corona <ac@albertocorona.com>
// All rights reserved. This file is part of yabs, distributed under the BSD
// 3-Clause license. For full terms please see the LICENSE file.

extern crate toml;
extern crate rustc_serialize;
extern crate walkdir;
extern crate rpf;

use toml::decode;
use error::YabsError;
use rpf::*;
use ext::*;
use rustc_serialize::{Decodable, Decoder, Encodable, Encoder, json};
use walkdir::WalkDir;

use std::ffi::OsStr;

#[derive(Debug,Default,RustcDecodable,RustcEncodable,Clone,PartialEq)]
pub struct Profile {
    name: String,
    proj_desc: Option<ProjDesc>,
    inst_desc: Option<InstallDesc>,
    doc_desc: Option<DocDesc>,
}

#[derive(Debug,Default,RustcDecodable,RustcEncodable)]
pub struct BuildFile {
    profiles: Vec<Profile>,
}

impl BuildFile {
    pub fn from_file(file: &str) -> Result<BuildFile, Vec<YabsError>> {
        let mut build_file: BuildFile = Default::default();
        parse_toml_file(file)
            .and_then(|toml| {
                for (key, table) in toml {
                    let mut profile: Profile = Default::default();
                    profile.name = key.clone();
                    match table {
                        toml::Value::Table(inner_table) => {
                            for (key, table) in inner_table {
                                match key.as_ref() {
                                    "project" => {
                                        profile.proj_desc = ProjDesc::from_toml_table(table).ok()
                                    }
                                    "install" => {
                                        profile.inst_desc = InstallDesc::from_toml_table(table).ok()
                                    }
                                    "doc" => {
                                        profile.doc_desc = DocDesc::from_toml_table(table).ok()
                                    }
                                    _ => (),
                                }
                            }
                        }
                        _ => (),
                    };
                    build_file.profiles.push(profile);
                }
                Ok(build_file)
            })
            .map_err(|err| err)
    }

    pub fn print_as_json(&self) {
        for profile in &self.profiles {
            profile.print_json();
        }
    }

    pub fn print_available_profiles(&self) {
        for profile in &self.profiles {
            print!("{} ", profile.name);
        }
        print!("\n");
    }

    pub fn print_profile_as_json(&self, name: String) {
        for profile in &self.profiles {
            if profile.name == name {
                profile.print_json();
            }
        }
    }

    pub fn gen_make(&self, name: String) -> Result<(), YabsError> {
        if let Some(index) = self.profiles.iter().position(|ref profile| profile.name == name) {
            println!("{}", self.profiles[index.clone()].clone().proj_desc.unwrap_or(ProjDesc::new()).gen_make());
            Ok(())
        } else {
            Err(YabsError::NoDesc(name))
        }
    }

    pub fn print_sources(&self) -> Result<(), YabsError> {
        for profile in &self.profiles {
            if let Some(proj) = profile.proj_desc.as_ref() {
                println!("{}", profile.name.bold());
                let sources = try!(proj.clone().gen_file_list());
                for file in sources.files {
                    println!("{}", file);
                }
                if let Some(set_sources) = proj.src.as_ref() {
                    for src in set_sources {
                        println!("{}", src);
                    }
                }
            }
        }
        Ok(())
    }
}

#[derive(Debug,Default,RustcDecodable,RustcEncodable,Clone,PartialEq)]
pub struct ProjDesc {
    name: Option<String>,
    target: Option<String>,
    lang: Option<String>,
    os: Option<String>,
    version: Option<String>,
    compiler: Option<String>,
    src: Option<Vec<String>>,
    libs: Option<Vec<String>>,
    lib_dir: Option<Vec<String>>,
    inc: Option<Vec<String>>,
    inc_dir: Option<Vec<String>>,
    cflags: Option<Vec<String>>,
    explicit_cflags: Option<String>,
    lflags: Option<String>,
    ignore: Option<Vec<String>>,
    before_script: Option<Vec<String>>,
    after_script: Option<Vec<String>>,
    lib: Option<bool>,
    ar: Option<String>,
}

#[derive(Default,RustcDecodable,RustcEncodable,PartialEq)]
struct Sources {
    files: Vec<String>,
    objects: Vec<String>,
}

impl ProjDesc {
    fn gen_file_list(&self) -> Result<Sources, YabsError> {
        let mut sources = Sources::new();
        for entry in WalkDir::new(".") {
            let entry = try!(entry);
            if entry.path().is_file() {
                let file_ext = entry.path().extension().unwrap_or(OsStr::new(""));
                if let Some(ext) = file_ext.to_str() {
                    if let Some(lang) = self.lang.clone() {
                        if ext == lang {
                            sources.files.push(entry.path().as_string());
                        }
                    }
                }
            }
        }
        Ok(sources)
    }

    fn prepend_op_vec(&self, list: &Option<Vec<String>>, prepend: String) -> String {
        let mut horrid_string = String::new();
        if let Some(items) = list.as_ref() {
            if let Some(split_last) = items.split_last() {
                for sub_item in split_last.1 {
                    horrid_string.push_str(&format!("{}{} ", prepend, sub_item));
                }
                horrid_string.push_str(&format!("{}{}", prepend, split_last.0.clone()));
            }
        }
        return horrid_string
    }

    fn gen_make_lib_list(&self) -> String {
        return self.prepend_op_vec(&self.libs, "-l".to_string())
    }

    fn gen_make_cflags_list(&self) -> String {
        return self.prepend_op_vec(&self.cflags, "-".to_string())
    }

    fn gen_make_src_list(&self) -> String {
        let mut horrid_string: String = "SRC\t= ".to_owned();
        let mut lang = self.lang.clone().unwrap_or("cpp".to_owned());
        lang.insert(0, '.');
        if let Some(source_list) = self.src.as_ref() {
            if let Some(split_first) = source_list.split_first() {
                horrid_string.push_str(&format!("{} \\\n", split_first.0));
                if let Some(split_last) = split_first.1.clone().split_last() {
                    for src in split_last.1 {
                        horrid_string.push_str(&format!("\t{} \\\n", src));
                    }
                    horrid_string.push_str(&format!("\t{}\n", split_last.0));
                }
            }
            let mut parsed_obj_list = Vec::new();
            for obj in source_list {
                parsed_obj_list.push(obj.replace(&lang, ".o"));
            }
            horrid_string.push_str("OBJ\t= ");
            if let Some(split_first) = parsed_obj_list.split_first() {
                horrid_string.push_str(&format!("{} \\\n", split_first.0));
                if let Some(split_last) = split_first.1.clone().split_last() {
                    for src in split_last.1 {
                        horrid_string.push_str(&format!("\t{} \\\n", src));
                    }
                    horrid_string.push_str(&format!("\t{}", split_last.0));
                }
            }
        }
        return horrid_string;
    }

    fn gen_make_src_deps(&self) -> String {
        let mut horrid_string = String::new();
        let mut lang = self.lang.clone().unwrap_or("cpp".to_owned());
        lang.insert(0, '.');
        if let Some(source_list) = self.src.as_ref() {
            for src in source_list {
                horrid_string.push_str(&format!("{0}: {1}\n\t\
                $(CC) -c $(CFLAGS) $(INCDIR) -o {0} {1}\n\n",
                src.replace(&lang, ".o",), src));
            }
        }
        return horrid_string
    }

    fn gen_make_inc_list(&self) -> String {
        return self.prepend_op_vec(&self.inc, "-I".to_string())
    }

    fn gen_make(&self) -> String {
        format!(
            "INSTALL\t= /usr/bin/env install\n\
                DEST\t=\n\
                PREFIX\t=\n\
                CC\t= {}\n\
                BINDIR\t=\n\
                LIBDIR\t=\n\
                TARGET\t= {}\n\
                LINK\t=\n\
                CFLAGS\t= {}\n\
                LFLAGS\t=\n\
                LIBS\t= {}\n\
                INCDIR\t= {}\n\
                LIBDIR\t=\n\
                CLEAN\t=\n\
                DEL\t= rm -f\n\
                {}\n\n\
                first: all\n\n\
                .PHONY: doc clean\n\n\
                .SUFFIXES: .o .c .cpp .cc .cxx .C\n\n\
                .cpp.o:\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \"$@\" \"$<\"\n\
                .cc.o:\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \"$@\" \"$<\"\n\n\
                .cxx.o:\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \"$@\" \"$<\"\n\n\
                .C.o:\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \"$@\" \"$<\"\n\n\
                .c.o:\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \"$@\" \"$<\"\n\n\
                all: $(TARGET)\n\n\
                $(TARGET): $(OBJ)\n\
                \t$(CC) $(LFLAGS) -o $(TARGET) $(OBJ) $(LIBS)\n\n\
                {}",
                &self.compiler.clone().unwrap_or("gcc".to_owned()),
                &self.target.clone().unwrap_or("a".to_owned()),
                &self.gen_make_cflags_list(),
                &self.gen_make_lib_list(),
                &self.gen_make_inc_list(),
                &self.gen_make_src_list(),
                &self.gen_make_src_deps())
    }
}

// Descibe how to install this project
#[derive(Debug,Default,RustcDecodable,RustcEncodable,Clone,PartialEq)]
pub struct InstallDesc {
    prefix: Option<String>,
}

#[derive(Debug,Default,RustcDecodable,RustcEncodable,Clone,PartialEq)]
pub struct DocDesc {
    doc: Option<String>,
}

// General trait for any descriptions
pub trait Desc<T> {
    fn new() -> T;
    fn from_file(file: &str, name: &str) -> Result<T, Vec<YabsError>>;
    fn from_toml_table(table: toml::Value) -> Result<T, YabsError>;
    fn print_json(&self);
}

impl<T: Decodable + Encodable + Default> Desc<T> for T {
    fn new() -> T {
        Default::default()
    }

    fn from_file(file: &str, name: &str) -> Result<T, Vec<YabsError>> {
        parse_toml_file(file).and_then(|toml| {
            toml.get(name)
                .ok_or(vec![YabsError::NoDesc(name.to_owned())])
                .and_then(|desc| {
                    Decodable::decode(&mut toml::Decoder::new(desc.clone()))
                        .map_err(|err| vec![YabsError::TomlDecode(err)])
                })
        })
    }

    fn from_toml_table(table: toml::Value) -> Result<T, YabsError> {
        Ok(try!(Decodable::decode(&mut toml::Decoder::new(table.clone()))))
    }

    fn print_json(&self) {
        println!("{}", json::as_pretty_json(&self));
    }
}
<commit_msg>yabs: finish Makefile generation<commit_after>// Copyright (c) 2015 - 2016, Alberto Corona <ac@albertocorona.com>
// All rights reserved. This file is part of yabs, distributed under the BSD
// 3-Clause license. For full terms please see the LICENSE file.

extern crate toml;
extern crate rustc_serialize;
extern crate walkdir;
extern crate rpf;

use toml::decode;
use error::YabsError;
use rpf::*;
use ext::*;
use rustc_serialize::{Decodable, Decoder, Encodable, Encoder, json};
use walkdir::WalkDir;

use std::ffi::OsStr;
use std::fs::File;
use std::io::Write;

#[derive(Debug,Default,RustcDecodable,RustcEncodable,Clone,PartialEq)]
pub struct Profile {
    name: String,
    proj_desc: Option<ProjDesc>,
    inst_desc: Option<InstallDesc>,
    doc_desc: Option<DocDesc>,
}

#[derive(Debug,Default,RustcDecodable,RustcEncodable)]
pub struct BuildFile {
    profiles: Vec<Profile>,
}

impl BuildFile {
    pub fn from_file(file: &str) -> Result<BuildFile, Vec<YabsError>> {
        let mut build_file: BuildFile = Default::default();
        parse_toml_file(file)
            .and_then(|toml| {
                for (key, table) in toml {
                    let mut profile: Profile = Default::default();
                    profile.name = key.clone();
                    match table {
                        toml::Value::Table(inner_table) => {
                            for (key, table) in inner_table {
                                match key.as_ref() {
                                    "project" => {
                                        profile.proj_desc = ProjDesc::from_toml_table(table).ok()
                                    }
                                    "install" => {
                                        profile.inst_desc = InstallDesc::from_toml_table(table).ok()
                                    }
                                    "doc" => {
                                        profile.doc_desc = DocDesc::from_toml_table(table).ok()
                                    }
                                    _ => (),
                                }
                            }
                        }
                        _ => (),
                    };
                    build_file.profiles.push(profile);
                }
                Ok(build_file)
            })
            .map_err(|err| err)
    }

    pub fn print_as_json(&self) {
        for profile in &self.profiles {
            profile.print_json();
        }
    }

    pub fn print_available_profiles(&self) {
        for profile in &self.profiles {
            print!("{} ", profile.name);
        }
        print!("\n");
    }

    pub fn print_profile_as_json(&self, name: String) {
        for profile in &self.profiles {
            if profile.name == name {
                profile.print_json();
            }
        }
    }

    pub fn gen_make(&self, name: String) -> Result<(), YabsError> {
        if let Some(index) = self.profiles.iter().position(|ref profile| profile.name == name) {
            try!(try!(File::create("Makefile")).write_all(self.profiles[index.clone()]
                                                              .clone()
                                                              .proj_desc
                                                              .unwrap_or(ProjDesc::new())
                                                              .gen_make()
                                                              .as_bytes()));
            Ok(())
        } else {
            Err(YabsError::NoDesc(name))
        }
    }

    pub fn print_sources(&self) -> Result<(), YabsError> {
        for profile in &self.profiles {
            if let Some(proj) = profile.proj_desc.as_ref() {
                println!("{}", profile.name.bold());
                let sources = try!(proj.clone().gen_file_list());
                for file in sources.files {
                    println!("{}", file);
                }
                if let Some(set_sources) = proj.src.as_ref() {
                    for src in set_sources {
                        println!("{}", src);
                    }
                }
            }
        }
        Ok(())
    }
}

#[derive(Debug,Default,RustcDecodable,RustcEncodable,Clone,PartialEq)]
pub struct ProjDesc {
    name: Option<String>,
    target: Option<String>,
    lang: Option<String>,
    os: Option<String>,
    version: Option<String>,
    compiler: Option<String>,
    src: Option<Vec<String>>,
    libs: Option<Vec<String>>,
    lib_dir: Option<Vec<String>>,
    inc: Option<Vec<String>>,
    inc_dir: Option<Vec<String>>,
    cflags: Option<Vec<String>>,
    explicit_cflags: Option<String>,
    lflags: Option<String>,
    ignore: Option<Vec<String>>,
    before_script: Option<Vec<String>>,
    after_script: Option<Vec<String>>,
    lib: Option<bool>,
    ar: Option<String>,
    clean: Option<Vec<String>>,
}

#[derive(Default,RustcDecodable,RustcEncodable,PartialEq)]
struct Sources {
    files: Vec<String>,
    objects: Vec<String>,
}

impl ProjDesc {
    fn concat_clean(&self) -> String {
        self.prepend_op_vec(&self.clean, "".to_string())
    }

    fn gen_file_list(&self) -> Result<Sources, YabsError> {
        let mut sources = Sources::new();
        for entry in WalkDir::new(".") {
            let entry = try!(entry);
            if entry.path().is_file() {
                let file_ext = entry.path().extension().unwrap_or(OsStr::new(""));
                if let Some(ext) = file_ext.to_str() {
                    if let Some(lang) = self.lang.clone() {
                        if ext == lang {
                            sources.files.push(entry.path().as_string());
                        }
                    }
                }
            }
        }
        Ok(sources)
    }

    fn prepend_op_vec(&self, list: &Option<Vec<String>>, prepend: String) -> String {
        let mut horrid_string = String::new();
        if let Some(items) = list.as_ref() {
            if let Some(split_last) = items.split_last() {
                for sub_item in split_last.1 {
                    horrid_string.push_str(&format!("{}{} ", prepend, sub_item));
                }
                horrid_string.push_str(&format!("{}{}", prepend, split_last.0.clone()));
            }
        }
        return horrid_string;
    }

    fn gen_make_lib_list(&self) -> String {
        return self.prepend_op_vec(&self.libs, "-l".to_string());
    }

    fn gen_make_cflags_list(&self) -> String {
        return self.prepend_op_vec(&self.cflags, "-".to_string());
    }

    fn gen_make_src_list(&self) -> String {
        let mut horrid_string: String = "SRC\t= ".to_owned();
        let mut lang = self.lang.clone().unwrap_or("cpp".to_owned());
        lang.insert(0, '.');
        if let Some(source_list) = self.src.as_ref() {
            if let Some(split_first) = source_list.split_first() {
                horrid_string.push_str(&format!("{} \\\n", split_first.0));
                if let Some(split_last) = split_first.1.clone().split_last() {
                    for src in split_last.1 {
                        horrid_string.push_str(&format!("\t{} \\\n", src));
                    }
                    horrid_string.push_str(&format!("\t{}\n", split_last.0));
                }
            }
            let mut parsed_obj_list = Vec::new();
            for obj in source_list {
                parsed_obj_list.push(obj.replace(&lang, ".o"));
            }
            horrid_string.push_str("OBJ\t= ");
            if let Some(split_first) = parsed_obj_list.split_first() {
                horrid_string.push_str(&format!("{} \\\n", split_first.0));
                if let Some(split_last) = split_first.1.clone().split_last() {
                    for src in split_last.1 {
                        horrid_string.push_str(&format!("\t{} \\\n", src));
                    }
                    horrid_string.push_str(&format!("\t{}", split_last.0));
                }
            }
        }
        return horrid_string;
    }

    fn gen_make_src_deps(&self) -> String {
        let mut horrid_string = String::new();
        let mut lang = self.lang.clone().unwrap_or("cpp".to_owned());
        lang.insert(0, '.');
        if let Some(source_list) = self.src.as_ref() {
            for src in source_list {
                horrid_string.push_str(&format!("{0}: {1}\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \
                                                 {0} {1}\n\n",
                                                src.replace(&lang, ".o"),
                                                src));
            }
        }
        return horrid_string;
    }

    fn gen_make_inc_list(&self) -> String {
        return self.prepend_op_vec(&self.inc, "-I".to_string());
    }

    fn gen_make(&self) -> String {
        format!(
            "INSTALL\t= /usr/bin/env install\n\
                DEST\t=\n\
                PREFIX\t=\n\
                CC\t= {compiler}\n\
                BINDIR\t=\n\
                LIBDIR\t=\n\
                TARGET\t= {target}\n\
                LINK\t=\n\
                CFLAGS\t= {cflags}\n\
                LFLAGS\t=\n\
                LIBS\t= {libs}\n\
                INCDIR\t= {incdir}\n\
                LIBDIR\t=\n\
                CLEAN\t=\n\
                DEL\t= rm -f\n\
                {srcs}\n\n\
                first: all\n\n\
                .PHONY: doc clean\n\n\
                .SUFFIXES: .o .c .cpp .cc .cxx .C\n\n\
                .cpp.o:\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \"$@\" \"$<\"\n\
                .cc.o:\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \"$@\" \"$<\"\n\n\
                .cxx.o:\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \"$@\" \"$<\"\n\n\
                .C.o:\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \"$@\" \"$<\"\n\n\
                .c.o:\n\t$(CC) -c $(CFLAGS) $(INCDIR) -o \"$@\" \"$<\"\n\n\
                all: $(TARGET)\n\n\
                $(TARGET): $(OBJ)\n\
                \t$(CC) $(LFLAGS) -o $(TARGET) $(OBJ) $(LIBS)\n\n\
                {dep_list}\n\
                clean:\n\
                \t$(DEL) $(OBJ)\n\
                \t$(DEL) {target}\n\
                \t$(DEL) {clean_list}\n\
                ",
                compiler = &self.compiler.clone().unwrap_or("gcc".to_owned()),
                target = &self.target.clone().unwrap_or("a".to_owned()),
                cflags = &self.gen_make_cflags_list(),
                libs = &self.gen_make_lib_list(),
                incdir = &self.gen_make_inc_list(),
                srcs = &self.gen_make_src_list(),
                dep_list = &self.gen_make_src_deps(),
                clean_list = &self.concat_clean())
    }
}

// Descibe how to install this project
#[derive(Debug,Default,RustcDecodable,RustcEncodable,Clone,PartialEq)]
pub struct InstallDesc {
    prefix: Option<String>,
}

#[derive(Debug,Default,RustcDecodable,RustcEncodable,Clone,PartialEq)]
pub struct DocDesc {
    doc: Option<String>,
}

// General trait for any descriptions
pub trait Desc<T> {
    fn new() -> T;
    fn from_file(file: &str, name: &str) -> Result<T, Vec<YabsError>>;
    fn from_toml_table(table: toml::Value) -> Result<T, YabsError>;
    fn print_json(&self);
}

impl<T: Decodable + Encodable + Default> Desc<T> for T {
    fn new() -> T {
        Default::default()
    }

    fn from_file(file: &str, name: &str) -> Result<T, Vec<YabsError>> {
        parse_toml_file(file).and_then(|toml| {
            toml.get(name)
                .ok_or(vec![YabsError::NoDesc(name.to_owned())])
                .and_then(|desc| {
                    Decodable::decode(&mut toml::Decoder::new(desc.clone()))
                        .map_err(|err| vec![YabsError::TomlDecode(err)])
                })
        })
    }

    fn from_toml_table(table: toml::Value) -> Result<T, YabsError> {
        Ok(try!(Decodable::decode(&mut toml::Decoder::new(table.clone()))))
    }

    fn print_json(&self) {
        println!("{}", json::as_pretty_json(&self));
    }
}
<|endoftext|>