aderyn_core/detect/low/
require_with_string.rs

1use std::{collections::BTreeMap, error::Error};
2
3use crate::{
4    ast::NodeID,
5    capture,
6    context::workspace_context::WorkspaceContext,
7    detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
8};
9use eyre::Result;
10
11#[derive(Default)]
12pub struct RequireWithStringDetector {
13    // Keys are: [0] source file name, [1] line number, [2] character location of node.
14    // Do not add items manually, use `capture!` to add nodes to this BTreeMap.
15    found_instances: BTreeMap<(String, usize, String), NodeID>,
16}
17
18impl IssueDetector for RequireWithStringDetector {
19    fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
20        // Collect all require statements without a string literal.
21        let requires_and_reverts = context
22            .identifiers()
23            .into_iter()
24            .filter(|&id| id.name == "revert" || id.name == "require");
25
26        for id in requires_and_reverts {
27            if (id.name == "revert" && id.argument_types.as_ref().unwrap().is_empty())
28                || (id.name == "require" && id.argument_types.as_ref().unwrap().len() == 1)
29            {
30                capture!(self, context, id);
31            }
32        }
33
34        Ok(!self.found_instances.is_empty())
35    }
36
37    fn title(&self) -> String {
38        String::from("Empty `require()` / `revert()` statements")
39    }
40
41    fn description(&self) -> String {
42        String::from("Use descriptive reason strings or custom errors for revert paths.")
43    }
44
45    fn severity(&self) -> IssueSeverity {
46        IssueSeverity::Low
47    }
48
49    fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
50        self.found_instances.clone()
51    }
52
53    fn name(&self) -> String {
54        format!("{}", IssueDetectorNamePool::RequireWithString)
55    }
56}
57
58#[cfg(test)]
59mod require_with_string_tests {
60    use serial_test::serial;
61
62    use crate::detect::detector::IssueDetector;
63
64    use super::RequireWithStringDetector;
65
66    #[test]
67    #[serial]
68    fn test_require_with_string_by_loading_contract_directly() {
69        let context = crate::detect::test_utils::load_solidity_source_unit(
70            "../tests/contract-playground/src/DeprecatedOZFunctions.sol",
71        );
72
73        let mut detector = RequireWithStringDetector::default();
74        // assert that the detector finds something
75        let found = detector.detect(&context).unwrap();
76        assert!(found);
77        // assert that the detector returns the correct number of instances
78        assert_eq!(detector.instances().len(), 2);
79        // assert that the detector returns the correct severity
80        assert_eq!(
81            detector.severity(),
82            crate::detect::detector::IssueSeverity::Low
83        );
84        // assert that the detector returns the correct title
85        assert_eq!(
86            detector.title(),
87            String::from("Empty `require()` / `revert()` statements")
88        );
89        // assert that the detector returns the correct description
90        assert_eq!(
91            detector.description(),
92            String::from("Use descriptive reason strings or custom errors for revert paths.")
93        );
94    }
95}