Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global pre-processing for leveraging callgraphs #605

Merged
merged 32 commits into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7dcebd6
investigator structure
TilakMaddy Jul 20, 2024
26fc140
reverse cg
TilakMaddy Jul 20, 2024
3756053
investigate func
TilakMaddy Jul 20, 2024
4781019
tests
TilakMaddy Jul 20, 2024
319bf31
added in driver
TilakMaddy Jul 20, 2024
9b5ccd4
report md
TilakMaddy Jul 20, 2024
4af7a95
println tests
TilakMaddy Jul 20, 2024
34e43f9
assert tests
TilakMaddy Jul 20, 2024
0cad9a8
Merge branch 'dev' into context/investigator-pattern
TilakMaddy Jul 20, 2024
240beb8
cli/reportgen
TilakMaddy Jul 20, 2024
565645e
patch
TilakMaddy Jul 20, 2024
e6a8ba8
SubWithoutIf
TilakMaddy Jul 20, 2024
3aaf169
reverseable -> transpose
TilakMaddy Jul 21, 2024
d363168
SubWithoutIf
TilakMaddy Jul 21, 2024
4a9a78d
trait fallback
TilakMaddy Jul 21, 2024
2a03a81
side effects
TilakMaddy Jul 21, 2024
020c447
tests for side effects added
TilakMaddy Jul 21, 2024
ce30d8f
cli/reportgen
TilakMaddy Jul 21, 2024
17e1fa5
clippy
TilakMaddy Jul 21, 2024
52fa8e4
helpers
TilakMaddy Jul 21, 2024
6790b31
send ether without checks detector
TilakMaddy Jul 21, 2024
46c8920
delegatecall with no address checks detector
TilakMaddy Jul 21, 2024
eda4685
Detector: `x.sub(y)` without `if` checks to avoid unexpected reverts …
TilakMaddy Jul 21, 2024
1899620
cli/reportgen
TilakMaddy Jul 21, 2024
c341460
warning message
TilakMaddy Jul 21, 2024
a6d78e8
merge dev
alexroan Jul 22, 2024
e59826d
separated test file for call graph and sending ether
Jul 23, 2024
1fefba4
completed other patterns of sending payment
TilakMaddy Jul 23, 2024
f884bff
cli/reportgen
TilakMaddy Jul 26, 2024
3b6b025
moved tests
TilakMaddy Jul 26, 2024
6ba8fe4
visit_fallback -> visit_any
TilakMaddy Jul 26, 2024
95ebe4c
Remove Sub without If
alexroan Jul 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions aderyn_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ serde_repr = "0.1.12"
strum = { version = "0.26", features = ["derive"] }
toml = "0.8.2"
cyfrin-foundry-compilers = { version = "0.3.20-aderyn", features = ["svm-solc"] }
derive_more = "0.99.18"

[dev-dependencies]
serial_test = "3.0.0"
Expand Down
2 changes: 1 addition & 1 deletion aderyn_core/src/audit/public_functions_no_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl AuditorDetector for PublicFunctionsNoSenderChecksDetector {
});
// Check if the function has a `msg.sender` BinaryOperation check
let has_msg_sender_binary_operation =
has_msg_sender_binary_operation(function_definition);
has_msg_sender_binary_operation(&((*function_definition).into()));
// TODO Check if the function has a hasRole identifier with msg.sender as an arg
does_not_have_an_owner_modifier && !has_msg_sender_binary_operation
});
Expand Down
37 changes: 37 additions & 0 deletions aderyn_core/src/context/browser/extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,40 @@ impl ASTConstVisitor for ExtractImmediateChildrenIDs {
Ok(())
}
}

// Extract Reference Declaration IDs
#[derive(Default)]
pub struct ExtractReferencedDeclarations {
pub extracted: Vec<NodeID>,
}

impl ExtractReferencedDeclarations {
pub fn from<T: Node + ?Sized>(node: &T) -> Self {
let mut extractor: ExtractReferencedDeclarations = Self::default();
node.accept(&mut extractor).unwrap_or_default();
extractor
}
}

impl ASTConstVisitor for ExtractReferencedDeclarations {
fn visit_member_access(&mut self, node: &MemberAccess) -> Result<bool> {
if let Some(referenced_id) = node.referenced_declaration {
self.extracted.push(referenced_id);
}
Ok(true)
}
fn visit_identifier(&mut self, node: &Identifier) -> Result<bool> {
if let Some(referenced_id) = node.referenced_declaration {
self.extracted.push(referenced_id);
}
Ok(true)
}
fn visit_identifier_path(&mut self, node: &IdentifierPath) -> Result<bool> {
self.extracted.push(node.referenced_declaration as i64);
Ok(true)
}
fn visit_user_defined_type_name(&mut self, node: &UserDefinedTypeName) -> Result<bool> {
self.extracted.push(node.referenced_declaration);
Ok(true)
}
}
32 changes: 32 additions & 0 deletions aderyn_core/src/context/graph/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
pub mod traits;
mod workspace_callgraph;

pub use workspace_callgraph::*;

use derive_more::From;

pub type Result<T> = core::result::Result<T, Error>;

#[derive(Debug, From)]
pub enum Error {
#[from]
Custom(String),

// region: -- standard::* errors
WorkspaceCallGraphDFSError,
// endregion
}

impl core::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}

impl From<&str> for Error {
fn from(value: &str) -> Self {
Error::Custom(value.to_string())
}
}

impl std::error::Error for Error {}
4 changes: 4 additions & 0 deletions aderyn_core/src/context/graph/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// Trait to support reversing of callgraph. (Because, direct impl is not allowed on Foreign Types)
pub trait Transpose {
fn reverse(&self) -> Self;
}
134 changes: 134 additions & 0 deletions aderyn_core/src/context/graph/workspace_callgraph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use std::collections::{hash_map, HashMap, HashSet};

use crate::{
ast::{Expression, IdentifierOrIdentifierPath, NodeID, NodeType},
context::{
browser::{ExtractFunctionCalls, ExtractModifierInvocations},
workspace_context::WorkspaceContext,
},
};

use super::traits::Transpose;

#[derive(Debug)]
pub struct WorkspaceCallGraph {
pub graph: CallGraph,
}

/**
* Every NodeID in CallGraph should corresponds to [`crate::ast::FunctionDefinition`] or [`crate::ast::ModifierDefinition`]
*/
pub type CallGraph = HashMap<NodeID, Vec<NodeID>>;

impl WorkspaceCallGraph {
/// Formula to create [`WorkspaceCallGraph`] for global preprocessing .
pub fn from_context(context: &WorkspaceContext) -> super::Result<WorkspaceCallGraph> {
let mut graph: CallGraph = HashMap::new();
let mut visited: HashSet<NodeID> = HashSet::new();

let funcs = context
.function_definitions()
.into_iter()
.filter(|func| func.implemented)
.collect::<Vec<_>>();

let modifier_definitions = context.modifier_definitions();

for func in funcs {
dfs_to_create_graph(func.id, &mut graph, &mut visited, context)
.map_err(|_| super::Error::WorkspaceCallGraphDFSError)?;
}

for modifier in modifier_definitions {
dfs_to_create_graph(modifier.id, &mut graph, &mut visited, context)
.map_err(|_| super::Error::WorkspaceCallGraphDFSError)?;
}

Ok(WorkspaceCallGraph { graph })
}
}

/// Make connections from each of the nodes of [`crate::ast::FunctionDefinition`] and [`crate::ast::ModifierDefinition`]
/// with their connected counterparts.
fn dfs_to_create_graph(
id: NodeID,
graph: &mut CallGraph,
visited: &mut HashSet<NodeID>,
context: &WorkspaceContext,
) -> super::Result<()> {
if visited.contains(&id) {
return Ok(());
}

visited.insert(id);

// Only deal with `id`s that are in scope right now
if let Some(from_node) = context.nodes.get(&id) {
// referenced_declarations from previous calls in the recursion stack need to be vetted
if from_node.node_type() != NodeType::FunctionDefinition
&& from_node.node_type() != NodeType::ModifierDefinition
{
return Ok(());
}

// connections to FunctionDefinition
let function_calls = ExtractFunctionCalls::from(from_node).extracted;
for function_call in function_calls {
if let Expression::Identifier(identifier) = function_call.expression.as_ref() {
if let Some(referenced_function_id) = identifier.referenced_declaration {
create_connection_if_not_exsits(id, referenced_function_id, graph);
dfs_to_create_graph(referenced_function_id, graph, visited, context)?;
}
}
}

// connections to ModifierDefinition
let modifier_invocations = ExtractModifierInvocations::from(from_node).extracted;
for modifier_invocation in &modifier_invocations {
match &modifier_invocation.modifier_name {
IdentifierOrIdentifierPath::Identifier(identifier) => {
if let Some(reference_modifier_id) = identifier.referenced_declaration {
create_connection_if_not_exsits(id, reference_modifier_id, graph);
dfs_to_create_graph(reference_modifier_id, graph, visited, context)?;
}
}
IdentifierOrIdentifierPath::IdentifierPath(identifier_path) => {
let referenced_modifier_id = identifier_path.referenced_declaration;
create_connection_if_not_exsits(id, referenced_modifier_id as i64, graph);
dfs_to_create_graph(referenced_modifier_id as i64, graph, visited, context)?;
}
}
}
}

// Change the default return to error later in "strict mode" maybe, because if we
// can't find the node that means, the file was not in scope and hence it is not
// available in the context although references to it exist.
Ok(())
}

fn create_connection_if_not_exsits(from_id: NodeID, to_id: NodeID, graph: &mut CallGraph) {
match graph.entry(from_id) {
hash_map::Entry::Occupied(mut o) => {
// Performance Tip: Maybe later use binary search (it requires keeping ascending order while inserting tho)
if !o.get().contains(&to_id) {
o.get_mut().push(to_id);
}
}
hash_map::Entry::Vacant(v) => {
v.insert(vec![to_id]);
}
}
}

impl Transpose for CallGraph {
fn reverse(&self) -> Self {
let mut reversed_callgraph = CallGraph::default();
for (from_id, tos) in self {
for to_id in tos {
create_connection_if_not_exsits(*to_id, *from_id, &mut reversed_callgraph);
}
}
reversed_callgraph
}
}
Loading
Loading