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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//! Little helpers for developers that try to understand what their code is
//! doing.

#![allow(dead_code)]
#![allow(missing_docs)]

use std::path::PathBuf;

#[derive(PartialEq, Eq, Copy, Clone, Debug, Default)]
/// Stages of the analysis that can be debugged separately.
#[non_exhaustive]
pub enum Stage {
    #[default]
    No,
    All,
    Pi,
    Ir(IrForm),
    Pcode(PcodeForm),
    Cwe,
}

#[derive(PartialEq, Eq, Copy, Clone, Debug)]
/// Substages of the IR generation that can be debugged separately.
#[non_exhaustive]
pub enum IrForm {
    Raw,
    Normalized,
    Optimized,
}

#[derive(PartialEq, Eq, Copy, Clone, Debug)]
/// Substages of the Pcode transformation that can be debugged separately.
#[non_exhaustive]
pub enum PcodeForm {
    Raw,
    Processed,
}

#[derive(PartialEq, Eq, Copy, Clone, Debug, Default)]
/// Controls generation of log messages.
#[non_exhaustive]
pub enum Verbosity {
    Quiet,
    #[default]
    Normal,
    Verbose,
}

#[derive(PartialEq, Eq, Copy, Clone, Debug, Default)]
/// Selects whether the analysis is aborted after reaching the point of
/// interest.
#[non_exhaustive]
pub enum TerminationPolicy {
    KeepRunning,
    #[default]
    EarlyExit,
    Panic,
}

#[derive(PartialEq, Eq, Clone, Default, Debug)]
/// Configuration of the debugging behavior.
pub struct Settings {
    stage: Stage,
    verbose: Verbosity,
    terminate: TerminationPolicy,
    saved_pcode_raw: Option<PathBuf>,
}

#[derive(PartialEq, Eq, Clone, Default, Debug)]
pub struct SettingsBuilder {
    inner: Settings,
}

impl SettingsBuilder {
    pub fn build(self) -> Settings {
        self.inner
    }

    pub fn set_stage(mut self, stage: Stage) -> Self {
        self.inner.stage = stage;

        self
    }

    pub fn set_verbosity(mut self, verbosity: Verbosity) -> Self {
        self.inner.verbose = verbosity;

        self
    }

    pub fn set_termination_policy(mut self, policy: TerminationPolicy) -> Self {
        self.inner.terminate = policy;

        self
    }

    pub fn set_saved_pcode_raw(mut self, saved_pcode_raw: PathBuf) -> Self {
        self.inner.saved_pcode_raw = Some(saved_pcode_raw);

        self
    }
}

impl Settings {
    pub fn get_saved_pcode_raw(&self) -> Option<PathBuf> {
        self.saved_pcode_raw.clone()
    }

    /// Returns true iff the `stage` is being debugged.
    pub fn should_debug(&self, stage: Stage) -> bool {
        debug_assert_ne!(stage, Stage::No);

        stage == self.stage || matches!(stage, Stage::All)
    }

    /// Displays the `obj`ect if the stage is being debugged.
    ///
    /// This is a possible cancellation point depending on the termination
    /// policy.
    pub fn print<T: std::fmt::Display>(&self, obj: &T, stage: Stage) {
        if self.should_debug(stage) {
            println!("{}", obj);
            self.maybe_terminate();
        }
    }

    /// Terminates the process according to the termination policy.
    fn maybe_terminate(&self) {
        match self.terminate {
            TerminationPolicy::EarlyExit => std::process::exit(0),
            TerminationPolicy::Panic => panic!(),
            _ => (),
        }
    }

    /// Returns true if the logging level is at least verbose.
    pub fn verbose(&self) -> bool {
        matches!(self.verbose, Verbosity::Verbose)
    }
}

/// Central utility for debug printing in the `cwe_checker`.
///
/// The canonical way to do printf-debugging in `cwe_checker` development is to
/// implement this trait for the type you want to inspect and then print it
/// via `value.print_compact_json()`.
pub trait ToJsonCompact {
    /// Returns a json representation of values of type `self` that is
    /// suitable for debugging purposes.
    ///
    /// The idea is that printing of complex types is facilitated by
    /// implementing `to_json_compact` for all of their constituent parts.
    fn to_json_compact(&self) -> serde_json::Value;

    /// Print values of type `Self` for debugging purposes.
    fn print_compact_json(&self) {
        println!("{:#}", self.to_json_compact())
    }
}