1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! Transformation which injects trace instructions which are used to visualize execution.
//!
//! This transformation should run before copy propagation and any other bytecode modifications.
//! It emits instructions of the form `trace_local[original_idx](idx)`. Initially
//! `original_idx == idx`, where the temp `idx` is a named variable from the Move
//! compiler. Later transformations may replace `idx` but `original_idx` will be preserved so
//! the user sees the value of their named variable.

use crate::{
    function_data_builder::FunctionDataBuilder,
    function_target::FunctionData,
    function_target_pipeline::{FunctionTargetProcessor, FunctionTargetsHolder},
    stackless_bytecode::{Bytecode, Operation},
};

use move_model::{exp_generator::ExpGenerator, model::FunctionEnv};
use std::collections::BTreeSet;

pub struct DebugInstrumenter {}

impl DebugInstrumenter {
    pub fn new() -> Box<Self> {
        Box::new(Self {})
    }
}

impl FunctionTargetProcessor for DebugInstrumenter {
    fn process(
        &self,
        _targets: &mut FunctionTargetsHolder,
        fun_env: &FunctionEnv<'_>,
        data: FunctionData,
    ) -> FunctionData {
        use Bytecode::*;

        if fun_env.is_native() {
            // Nothing to do
            return data;
        }

        let mut builder = FunctionDataBuilder::new(fun_env, data);
        let code = std::mem::take(&mut builder.data.code);

        // Emit trace instructions for parameters at entry.
        builder.set_loc(builder.fun_env.get_loc().at_start());
        for i in 0..builder.fun_env.get_parameter_count() {
            builder.emit_with(|id| Call(id, vec![], Operation::TraceLocal(i), vec![i], None));
        }

        for bc in code {
            match &bc {
                Ret(id, locals) => {
                    // Emit trace instructions for return values.
                    builder.set_loc_from_attr(*id);
                    for (i, l) in locals.iter().enumerate() {
                        builder.emit_with(|id| {
                            Call(id, vec![], Operation::TraceReturn(i), vec![*l], None)
                        });
                    }
                    builder.emit(bc);
                }
                Abort(id, l) => {
                    builder.set_loc_from_attr(*id);
                    builder.emit_with(|id| Call(id, vec![], Operation::TraceAbort, vec![*l], None));
                    builder.emit(bc);
                }
                Call(_, _, Operation::WriteRef, srcs, _) if srcs[0] < fun_env.get_local_count() => {
                    builder.set_loc_from_attr(bc.get_attr_id());
                    builder.emit(bc.clone());
                    builder.emit_with(|id| {
                        Call(
                            id,
                            vec![],
                            Operation::TraceLocal(srcs[0]),
                            vec![srcs[0]],
                            None,
                        )
                    });
                }
                _ => {
                    builder.set_loc_from_attr(bc.get_attr_id());
                    builder.emit(bc.clone());
                    // Emit trace instructions for modified values.
                    let (val_targets, mut_targets) = bc.modifies(&builder.get_target());
                    let affected_variables: BTreeSet<_> = val_targets
                        .into_iter()
                        .chain(mut_targets.into_iter().map(|(idx, _)| idx))
                        .collect();
                    for idx in affected_variables {
                        // Only emit this for user declared locals, not for ones introduced
                        // by stack elimination.
                        if idx < fun_env.get_local_count() {
                            builder.emit_with(|id| {
                                Call(id, vec![], Operation::TraceLocal(idx), vec![idx], None)
                            });
                        }
                    }
                }
            }
        }

        builder.data
    }

    fn name(&self) -> String {
        "debug_instrumenter".to_string()
    }
}