solidhunter_lib/rules/best_practises/
reason_string.rs

1use osmium_libs_solidity_ast_extractor::*;
2
3use crate::linter::SolidFile;
4use crate::rules::types::{RuleEntry, RuleType};
5use crate::types::{LintDiag, Position, Range, Severity};
6
7// global
8pub const RULE_ID: &str = "reason-string";
9
10// Specific
11const DEFAULT_SEVERITY: Severity = Severity::WARNING;
12const DEFAULT_LENGTH: usize = 32;
13
14pub struct ReasonString {
15    max_length: usize,
16    data: RuleEntry,
17}
18
19impl ReasonString {
20    fn create_diag(
21        &self,
22        file: &SolidFile,
23        location: (LineColumn, LineColumn),
24        message: String,
25    ) -> LintDiag {
26        LintDiag {
27            id: RULE_ID.to_string(),
28            range: Range {
29                start: Position {
30                    line: location.0.line,
31                    character: location.0.column,
32                },
33                end: Position {
34                    line: location.1.line,
35                    character: location.1.column,
36                },
37            },
38            message,
39            severity: self.data.severity,
40            code: None,
41            source: None,
42            uri: file.path.clone(),
43        }
44    }
45}
46
47impl RuleType for ReasonString {
48    fn diagnose(&self, file: &SolidFile, _files: &[SolidFile]) -> Vec<LintDiag> {
49        let mut res = Vec::new();
50
51        for contract in retriever::retrieve_contract_nodes(&file.data) {
52            for stmt in retriever::retrieve_stmts_nodes(&contract) {
53                if let Stmt::Revert(revert) = &stmt {
54                    if let Expr::Tuple(tuple) = &revert.expr {
55                        if let Some(Expr::Lit(Lit::Str(string))) = tuple.elems.first() {
56                            if string.values.len() == 1
57                                && string.values[0].value().len() > self.max_length
58                            {
59                                let location = (string.span().start(), string.span().end());
60                                res.push(self.create_diag(file, location, format!("Error message for revert is too long. Should be less than {} characters", self.max_length)));
61                            }
62                        } else {
63                            let location = (
64                                revert.revert_token.span().start(),
65                                revert.revert_token.span().end(),
66                            );
67                            res.push(self.create_diag(
68                                file,
69                                location,
70                                "Provide an error message for revert".to_string(),
71                            ));
72                        }
73                    }
74                }
75                if let Stmt::Expr(expr) = &stmt {
76                    if let Expr::Call(call) = &expr.expr {
77                        if let Expr::Ident(ref ident) = *(call.expr) {
78                            if *ident == "require" || *ident == "assert" {
79                                let expr_args = match &call.args.list {
80                                    ArgListImpl::Named(_) => continue,
81                                    ArgListImpl::Unnamed(args) => args,
82                                };
83
84                                if let Some(expr_string) = expr_args.iter().find(|&x| {
85                                    if let Expr::Lit(lit) = x {
86                                        matches!(
87                                            lit,
88                                            osmium_libs_solidity_ast_extractor::Lit::Str(_)
89                                        )
90                                    } else {
91                                        false
92                                    }
93                                }) {
94                                    if let Expr::Lit(Lit::Str(lit_str)) = expr_string {
95                                        let actual_string = lit_str.values[0].token().to_string();
96
97                                        if actual_string.len() > self.max_length {
98                                            let location = (
99                                                lit_str.values[0].span().start(),
100                                                lit_str.values[0].span().end(),
101                                            );
102                                            res.push(
103                                                self.create_diag(
104                                                    file,
105                                                    location,
106                                                    format!(
107                                                        "Error message for revert is too long. Should be less than {} characters",
108                                                        self.max_length
109                                                    ),
110                                                ),
111                                            );
112                                        }
113                                    }
114                                } else {
115                                    let location = (ident.0.span().start(), ident.0.span().end());
116                                    res.push(self.create_diag(
117                                        file,
118                                        location,
119                                        "Provide an error message for revert".to_string(),
120                                    ));
121                                }
122                            }
123                        }
124                    }
125                }
126            }
127        }
128        res
129    }
130}
131
132impl ReasonString {
133    pub fn create(data: RuleEntry) -> Box<dyn RuleType> {
134        let mut max_length = DEFAULT_LENGTH;
135
136        if let Some(data) = &data.data {
137            let parsed: Result<usize, serde_json::Error> = serde_json::from_value(data.clone());
138            match parsed {
139                Ok(val) => max_length = val,
140                Err(_) => {
141                    eprintln!("{} rule : bad config data", RULE_ID);
142                }
143            }
144        } else {
145            eprintln!("{} rule : bad config data", RULE_ID);
146        }
147        let rule = ReasonString { max_length, data };
148        Box::new(rule)
149    }
150
151    pub fn create_default() -> RuleEntry {
152        RuleEntry {
153            id: RULE_ID.to_string(),
154            severity: DEFAULT_SEVERITY,
155            data: Some(DEFAULT_LENGTH.into()),
156        }
157    }
158}