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
pub mod annotation;
pub mod ast_visitor;
pub mod call_checker;
pub mod check_checker;
pub mod dependency_detector;

use serde::de::Deserialize;
use serde::Serialize;

use crate::analysis::annotation::Annotation;
use crate::clarity::analysis::analysis_db::AnalysisDatabase;
use crate::clarity::analysis::types::ContractAnalysis;
use crate::clarity::diagnostic::Diagnostic;

use self::call_checker::CallChecker;
use self::check_checker::CheckChecker;
use self::dependency_detector::DependencyDetector;

pub type AnalysisResult = Result<Vec<Diagnostic>, Vec<Diagnostic>>;

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Pass {
    All,
    CheckChecker,
}

#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct Settings {
    passes: Vec<Pass>,
    check_checker: check_checker::Settings,
}

impl Settings {
    pub fn enable_all_passes(&mut self) {
        self.passes = ALL_PASSES.to_vec();
    }

    pub fn set_passes(&mut self, passes: Vec<Pass>) {
        for pass in passes {
            match pass {
                Pass::All => {
                    self.passes = ALL_PASSES.to_vec();
                    return;
                }
                pass => self.passes.push(pass),
            };
        }
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum OneOrList<T> {
    /// Allow `T` as shorthand for `[T]` in the TOML
    One(T),
    /// Allow more than one `T` in the TOML
    List(Vec<T>),
}

#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct SettingsFile {
    passes: Option<OneOrList<Pass>>,
    check_checker: Option<check_checker::SettingsFile>,
}

// Each new pass should be included in this list
static ALL_PASSES: [Pass; 1] = [Pass::CheckChecker];

impl From<SettingsFile> for Settings {
    fn from(from_file: SettingsFile) -> Self {
        let passes = if let Some(file_passes) = from_file.passes {
            match file_passes {
                OneOrList::One(pass) => match pass {
                    Pass::All => ALL_PASSES.to_vec(),
                    pass => vec![pass],
                },
                OneOrList::List(passes) => {
                    if passes.contains(&Pass::All) {
                        ALL_PASSES.to_vec()
                    } else {
                        passes
                    }
                }
            }
        } else {
            vec![]
        };

        // Each pass that has its own settings should be included here.
        let checker_settings = if let Some(checker_settings) = from_file.check_checker {
            check_checker::Settings::from(checker_settings)
        } else {
            check_checker::Settings::default()
        };

        Self {
            passes,
            check_checker: checker_settings,
        }
    }
}

pub trait AnalysisPass {
    fn run_pass(
        contract_analysis: &mut ContractAnalysis,
        analysis_db: &mut AnalysisDatabase,
        annotations: &Vec<Annotation>,
        settings: &Settings,
    ) -> AnalysisResult;
}

pub fn run_analysis(
    contract_analysis: &mut ContractAnalysis,
    analysis_db: &mut AnalysisDatabase,
    annotations: &Vec<Annotation>,
    settings: &Settings,
) -> AnalysisResult {
    let mut errors: Vec<Diagnostic> = Vec::new();
    let mut passes: Vec<
        fn(
            &mut ContractAnalysis,
            &mut AnalysisDatabase,
            &Vec<Annotation>,
            settings: &Settings,
        ) -> AnalysisResult,
    > = vec![DependencyDetector::run_pass, CallChecker::run_pass];
    for pass in &settings.passes {
        match pass {
            Pass::CheckChecker => passes.push(CheckChecker::run_pass),
            Pass::All => panic!("unexpected All in list of passes"),
        }
    }

    analysis_db.execute(|db| {
        for pass in passes {
            // Collect warnings and continue, or if there is an error, return.
            match pass(contract_analysis, db, annotations, &settings) {
                Ok(mut w) => errors.append(&mut w),
                Err(mut e) => {
                    errors.append(&mut e);
                    return Err(errors);
                }
            }
        }
        Ok(errors)
    })
}