Skip to content

Commit

Permalink
Merge pull request #18 from FuzzingLabs/feat/symbolic_execution
Browse files Browse the repository at this point in the history
Implement symbolic execution
  • Loading branch information
Rog3rSm1th authored Aug 6, 2024
2 parents 7468b04 + d564003 commit a8c5e3d
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 39 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,4 @@ Examples can be found [here](/lib/examples/).
- [x] Call Graph
- [X] Informational & Security detectors
- [x] Fetching contracts from Starknet
- [ ] Symbolic execution
- [x] Symbolic execution
26 changes: 26 additions & 0 deletions lib/examples/cfg_paths.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use sierra_analyzer_lib::sierra_program::SierraProgram;

fn main() {
let content = include_str!("../../examples/sierra/fib_match.sierra").to_string();

// Init a new SierraProgram with the .sierra file content
let program = SierraProgram::new(content);

// Don't use the verbose output
let verbose_output = false;

// Decompile the Sierra program
let mut decompiler = program.decompiler(verbose_output);

// Decompile the Sierra program
let use_color = false;
decompiler.decompile(use_color);

// Print the number of paths in the first function
// It should be 10 in examples::fib_match::fib function
decompiler.functions[0].create_cfg();
println!(
"Number of possible paths : {:#?}",
decompiler.functions[0].cfg.as_ref().unwrap().paths().len()
);
}
38 changes: 32 additions & 6 deletions lib/src/decompiler/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,44 @@ impl<'a> ControlFlowGraph {
self.basic_blocks.push(current_basic_block);
}

/// Returns all the possible paths in a function
pub fn paths(&self) -> Vec<Vec<&BasicBlock>> {
let mut paths = Vec::new();

// Find the starting blocks
let mut start_blocks = Vec::new();
for block in &self.basic_blocks {
if self.parents(block).is_empty() {
start_blocks.push(block);
}
}

// Perform DFS from each start block to find all paths
for start_block in start_blocks {
let mut stack = vec![(vec![start_block], start_block)];
while let Some((current_path, current_block)) = stack.pop() {
let children = self.children(current_block);
if children.is_empty() {
paths.push(current_path.clone());
} else {
for child in children {
let mut new_path = current_path.clone();
new_path.push(child);
stack.push((new_path, child));
}
}
}
}

paths
}

/// Returns the children blocks of a basic block
/// Unused for now but will be useful to construct the graphs
#[allow(dead_code)]
fn children(&self, block: &BasicBlock) -> Vec<&BasicBlock> {
let mut children = Vec::new();
let edges_destinations: HashSet<_> =
block.edges.iter().map(|edge| edge.destination).collect();

// Find all blocks having an edge with the current block as source
for basic_block in &self.basic_blocks {
if edges_destinations.contains(&basic_block.start_offset) {
children.push(basic_block);
Expand All @@ -171,13 +200,10 @@ impl<'a> ControlFlowGraph {
}

/// Returns the parent blocks of a basic block
/// Unused for now but will be useful to construct the graphs
#[allow(dead_code)]
fn parents(&self, block: &BasicBlock) -> Vec<&BasicBlock> {
let mut parents = Vec::new();
let start_offset = block.start_offset;

// Find all blocks having an edge with the current block as destination
for basic_block in &self.basic_blocks {
let edges_offset: Vec<_> = basic_block
.edges
Expand Down
42 changes: 33 additions & 9 deletions lib/src/decompiler/decompiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,27 +250,35 @@ impl<'a> Decompiler<'a> {

/// Decompiles the functions prototypes
pub fn decompile_functions_prototypes(&mut self) -> String {
let prototypes: Vec<String> = self
let prototypes_and_arguments: Vec<(String, Vec<(String, String)>)> = self
.sierra_program
.program()
.funcs
.iter()
.map(|function_prototype| self.decompile_function_prototype(function_prototype))
.collect();

// Set prototypes for corresponding Function structs
for (prototype, function) in prototypes.iter().zip(self.functions.iter_mut()) {
// Set prototypes and arguments for corresponding Function structs
for ((prototype, arguments), function) in prototypes_and_arguments
.iter()
.zip(self.functions.iter_mut())
{
function.set_prototype(prototype.clone());
function.set_arguments(arguments.clone());
}

prototypes.join("\n")
prototypes_and_arguments
.iter()
.map(|(prototype, _)| prototype.clone())
.collect::<Vec<_>>()
.join("\n")
}

/// Decompiles a single function prototype
/// Decompiles a function prototype and returns both the formatted prototype & the arguments
fn decompile_function_prototype(
&self,
function_declaration: &GenFunction<StatementIdx>,
) -> String {
) -> (String, Vec<(String, String)>) {
// Parse the function name
let id = format!("{}", parse_element_name!(function_declaration.id)).bold();

Expand All @@ -280,8 +288,8 @@ impl<'a> Decompiler<'a> {
.param_types
.iter()
.map(|param_type| {
// We use `parse_element_name_with_fallback` and not `parse_element_name` because
// we try to match the type id with it's corresponding name if it's a remote contract
// We use `parse_element_name_with_fallback` and not `parse_element_name` because
// we try to match the type id with its corresponding name if it's a remote contract
parse_element_name_with_fallback!(param_type, self.declared_types_names)
})
.collect();
Expand All @@ -303,6 +311,20 @@ impl<'a> Decompiler<'a> {
})
.collect();

// Collect arguments as a vector of tuples
let arguments: Vec<(String, String)> = param_types
.iter()
.zip(function_declaration.params.iter())
.map(|(param_type, param)| {
let param_name_string = if let Some(debug_name) = &param.id.debug_name {
debug_name.to_string()
} else {
format!("v{}", param.id.id)
};
(param_name_string, param_type.clone())
})
.collect();

// Join the parameter strings into a single string, separated by commas
let param_str = format!("{}", param_strings.join(", "));

Expand All @@ -326,7 +348,9 @@ impl<'a> Decompiler<'a> {
let ret_types_str = format!("{}", ret_types.join(", "));

// Construct the function declaration string
format!("func {} ({}) -> ({})", id, param_str, ret_types_str)
let prototype = format!("func {} ({}) -> ({})", id, param_str, ret_types_str);

(prototype, arguments)
}

/// Sets the start and end offsets for each function in the Sierra program
Expand Down
14 changes: 9 additions & 5 deletions lib/src/decompiler/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use crate::decompiler::utils::replace_types_id;
use crate::extract_parameters;
use crate::parse_element_name;
use crate::parse_element_name_with_fallback;
use crate::sym_exec::sym_exec::SymbolicExecution;

/// A struct representing a statement
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -431,8 +430,8 @@ pub struct Function<'a> {
pub cfg: Option<ControlFlowGraph>,
/// The prototype of the function
pub prototype: Option<String>,
/// Symbolic execution solver
pub symbolic_execution: SymbolicExecution,
/// Arguments of the function
pub arguments: Vec<(String, String)>,
}

impl<'a> Function<'a> {
Expand All @@ -445,8 +444,7 @@ impl<'a> Function<'a> {
end_offset: None,
cfg: None,
prototype: None,
// Initialize symbolic execution
symbolic_execution: SymbolicExecution::new(),
arguments: Vec::new(),
}
}

Expand Down Expand Up @@ -488,4 +486,10 @@ impl<'a> Function<'a> {
pub fn set_prototype(&mut self, prototype: String) {
self.prototype = Some(prototype);
}

/// Sets the arguments of the function
#[inline]
pub fn set_arguments(&mut self, arguments: Vec<(String, String)>) {
self.arguments = arguments;
}
}
Loading

0 comments on commit a8c5e3d

Please sign in to comment.