Useful Detector Patterns
Common AST traversal techniques
Useful Detector Patterns
Learn common patterns for navigating the AST in custom detectors.
Traversing Down
Extractor Pattern
Use extractors to find specific node types:
use crate::extractors::*;
// Find all function definitions
for function in context.get_functions() {
// Process function
}
// Find all state variables
for variable in context.get_state_variables() {
// Process variable
}
.children() Method
Navigate to child nodes:
for child in node.children() {
// Process each child node
}
Traversing Up
.parent() Method
Access parent node:
if let Some(parent) = node.parent() {
// Check parent node
}
.closest_ancestor_of_type()
Find nearest ancestor of specific type:
if let Some(contract) = function.closest_ancestor_of_type(NodeType::Contract) {
// Function is inside this contract
}
.ancestral_line()
Get all ancestors up to root:
for ancestor in node.ancestral_line() {
// Process each ancestor
}
Common Patterns
Find Functions with Modifier
for function in context.get_functions() {
if function.has_modifier("nonReentrant") {
// Function uses nonReentrant
}
}
Find State Variables of Type
for variable in context.get_state_variables() {
if variable.type_name == "address" {
// Address state variable
}
}
Find External Calls
for function in context.get_functions() {
for statement in function.body.statements() {
if let Statement::ExternalCall(call) = statement {
// Found external call
}
}
}
Check Function Visibility
for function in context.get_functions() {
if function.visibility == Visibility::Public {
// Public function
}
}
Example: Find Unprotected Functions
impl Detector for UnprotectedFunctionDetector {
fn detect(&self, context: &WorkspaceContext) -> Vec<Finding> {
context.get_functions()
.filter(|f| f.visibility == Visibility::Public)
.filter(|f| !f.has_modifier("onlyOwner"))
.filter(|f| !f.has_modifier("nonReentrant"))
.map(|f| self.capture(f))
.collect()
}
}
Advanced Patterns
Recursive Tree Walk
fn walk_node(node: &Node, findings: &mut Vec<Finding>) {
// Check current node
if is_vulnerable(node) {
findings.push(create_finding(node));
}
// Recursively check children
for child in node.children() {
walk_node(child, findings);
}
}
Context-Aware Detection
// Check if function is in a library
if let Some(parent_contract) = function.closest_ancestor_of_type(NodeType::Contract) {
if parent_contract.kind == ContractKind::Library {
// Different rules for library functions
}
}