solidhunter_lib/rules/best_practises/
reason_string.rs1use osmium_libs_solidity_ast_extractor::*;
2
3use crate::linter::SolidFile;
4use crate::rules::types::{RuleEntry, RuleType};
5use crate::types::{LintDiag, Position, Range, Severity};
6
7pub const RULE_ID: &str = "reason-string";
9
10const 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}