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

Implement symbolic execution #18

Merged
merged 9 commits into from
Aug 6, 2024
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
Loading