Unverified Commit 809b7ca8 by Melvin Klimke Committed by GitHub

Backward analysis trait (#123)

parent 95566833
use super::*;
use crate::analysis::graph::Graph;
use petgraph::graph::NodeIndex;
use std::collections::{HashMap, HashSet};
/// Identifier for BlkStart and BlkEnd nodes
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum StartEnd {
Start,
End,
}
/// A simple mock context, only containing the program cfg
#[derive(Clone)]
pub struct Context<'a> {
pub graph: Graph<'a>,
pub tid_to_node_index: HashMap<(Tid, Tid, StartEnd), NodeIndex>,
}
impl<'a> Context<'a> {
pub fn new(project: &'a Project) -> Self {
let mut graph = crate::analysis::graph::get_program_cfg(&project.program, HashSet::new());
graph.reverse();
let mut tid_to_node_index: HashMap<(Tid, Tid, StartEnd), NodeIndex> = HashMap::new();
for node in graph.node_indices() {
let node_value = graph.node_weight(node).unwrap();
match node_value {
Node::BlkStart {
0: block,
1: subroutine,
} => {
tid_to_node_index.insert(
(subroutine.tid.clone(), block.tid.clone(), StartEnd::Start),
node,
);
}
Node::BlkEnd {
0: block,
1: subroutine,
} => {
tid_to_node_index.insert(
(subroutine.tid.clone(), block.tid.clone(), StartEnd::End),
node,
);
}
_ => (),
}
}
Context {
graph,
tid_to_node_index,
}
}
}
impl<'a> crate::analysis::backward_interprocedural_fixpoint::Context<'a> for Context<'a> {
type Value = u64;
fn get_graph(&self) -> &Graph<'a> {
&self.graph
}
/// Take the minimum of two values when merging
fn merge(&self, val1: &u64, val2: &u64) -> u64 {
std::cmp::min(*val1, *val2)
}
/// Increase the Def count when parsing one
fn update_def(&self, val: &u64, _def: &Term<Def>) -> Option<u64> {
let updated_value = *val + 1;
Some(updated_value)
}
/// Simply copy the value at the jumpsite
fn update_jumpsite(
&self,
value_after_jump: &u64,
_jump: &Term<Jmp>,
_untaken_conditional: Option<&Term<Jmp>>,
_jumpsite: &Term<Blk>,
) -> Option<u64> {
Some(*value_after_jump)
}
/// Merge two values at the callsite if both exist
/// If there is only one, simply copy it
fn update_callsite(
&self,
target_value: Option<&u64>,
return_value: Option<&u64>,
_call: &Term<Jmp>,
_return_: &Term<Jmp>,
) -> Option<u64> {
match (target_value, return_value) {
(Some(call), Some(fall)) => Some(self.merge(call, fall)),
(Some(call), _) => Some(*call),
(_, Some(fall)) => Some(*fall),
_ => panic!("No values to merge at callsite!"),
}
}
/// Simply copy the value
fn split_call_stub(&self, combined_value: &u64) -> Option<u64> {
Some(*combined_value)
}
/// Simply copy the value
fn split_return_stub(&self, combined_value: &u64) -> Option<u64> {
Some(*combined_value)
}
/// Simply copy the value
fn update_call_stub(&self, value_after_call: &u64, _call: &Term<Jmp>) -> Option<u64> {
Some(*value_after_call)
}
/// Simply copy the value
fn specialize_conditional(
&self,
value_after_jump: &u64,
_condition: &Expression,
_is_true: bool,
) -> Option<u64> {
Some(*value_after_jump)
}
}
//! Creating and computing backward interprocedural fixpoint problems.
//!
//! # General notes
//!
//! This module supports computation of fixpoint problems on the control flow graphs generated by the `graph` module.
//!
//!
//! To compute a generalized fixpoint problem,
//! first construct a context object implementing the `Context`trait.
//! Use it to construct a `Computation` object.
//! The `Computation` object provides the necessary methods for the actual fixpoint computation.
use super::fixpoint::Context as GeneralFPContext;
use super::graph::*;
use super::interprocedural_fixpoint_generic::*;
use crate::intermediate_representation::*;
use petgraph::graph::EdgeIndex;
use std::marker::PhantomData;
/// The context for an backward interprocedural fixpoint computation.
///
/// Basically, a `Context` object needs to contain a reference to the actual graph,
/// a method for merging node values,
/// and methods for computing the edge transitions for each different edge type.
///
/// All trait methods have access to the FixpointProblem structure, so that context informations are accessible through it.
///
/// All edge transition functions can return `None` to indicate that no information flows through the edge.
/// For example, this can be used to indicate edges that can never been taken.
pub trait Context<'a> {
type Value: PartialEq + Eq + Clone;
/// Get a reference to the graph that the fixpoint is computed on.
/// The return value is expected to be the reversed CFG.
fn get_graph(&self) -> &Graph<'a>;
/// Merge two node values.
fn merge(&self, value1: &Self::Value, value2: &Self::Value) -> Self::Value;
/// Transition function for `Def` terms.
/// The transition function for a basic block is computed
/// by iteratively applying this function to the starting value for each `Def` term in the basic block.
/// The iteration short-circuits and returns `None` if `update_def` returns `None` at any point.
fn update_def(&self, value: &Self::Value, def: &Term<Def>) -> Option<Self::Value>;
/// Transition function for (conditional and unconditional) `Jmp` terms.
fn update_jumpsite(
&self,
value_after_jump: &Self::Value,
jump: &Term<Jmp>,
untaken_conditional: Option<&Term<Jmp>>,
jumpsite: &Term<Blk>,
) -> Option<Self::Value>;
/// Transition function for in-program calls.
/// The target value is coming in via the call edge from the BlkStart node of the called subroutine and
/// the return_value is coming in via the call stub edge from the returned-to node of the caller
fn update_callsite(
&self,
target_value: Option<&Self::Value>,
return_value: Option<&Self::Value>,
call: &Term<Jmp>,
return_: &Term<Jmp>,
) -> Option<Self::Value>;
/// Transition function for call stub split.
/// Has access to the value at the ReturnCombine node and
/// decides which data is transferred along the Call Stub Edge.
fn split_call_stub(&self, combined_value: &Self::Value) -> Option<Self::Value>;
/// Transition function for return stub split.
/// Has access to the value at the ReturnCombine node and
/// decides which data is transferred along the Return Stub Edge.
fn split_return_stub(&self, combined_value: &Self::Value) -> Option<Self::Value>;
/// Transition function for calls to functions not contained in the binary.
/// The corresponding edge goes from the callsite to the returned-to block.
fn update_call_stub(
&self,
value_after_call: &Self::Value,
call: &Term<Jmp>,
) -> Option<Self::Value>;
/// This function is used to refine the value using the information on which branch was taken on a conditional jump.
fn specialize_conditional(
&self,
value_after_jump: &Self::Value,
condition: &Expression,
is_true: bool,
) -> Option<Self::Value>;
}
impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
type EdgeLabel = Edge<'a>;
type NodeLabel = Node<'a>;
type NodeValue = NodeValue<T::Value>;
/// Get a reference to the underlying graph.
fn get_graph(&self) -> &Graph<'a> {
self.context.get_graph()
}
/// Merge two values using the merge function from the interprocedural context object.
fn merge(&self, val1: &Self::NodeValue, val2: &Self::NodeValue) -> Self::NodeValue {
use NodeValue::*;
match (val1, val2) {
(Value(value1), Value(value2)) => Value(self.context.merge(value1, value2)),
(
CallFlowCombinator {
call_stub: call1,
interprocedural_flow: target1,
},
CallFlowCombinator {
call_stub: call2,
interprocedural_flow: target2,
},
) => CallFlowCombinator {
call_stub: merge_option(call1, call2, |v1, v2| self.context.merge(v1, v2)),
interprocedural_flow: merge_option(target1, target2, |v1, v2| {
self.context.merge(v1, v2)
}),
},
_ => panic!("Malformed CFG in fixpoint computation"),
}
}
/// Backward edge transition function.
/// Applies the transition functions from the interprocedural context object
/// corresponding to the type of the provided edge.
fn update_edge(
&self,
node_value: &Self::NodeValue,
edge: EdgeIndex,
) -> Option<Self::NodeValue> {
let graph = self.context.get_graph();
let (start_node, end_node) = graph.edge_endpoints(edge).unwrap();
match graph.edge_weight(edge).unwrap() {
// Added rev() function to iterator to iterate backwards over the definitions
Edge::Block => {
let block_term = graph.node_weight(start_node).unwrap().get_block();
let value = node_value.unwrap_value();
let defs = &block_term.term.defs;
let end_val = defs.iter().rev().try_fold(value.clone(), |accum, def| {
self.context.update_def(&accum, def)
});
end_val.map(NodeValue::Value)
}
Edge::ReturnCombine(_) => {
Some(Self::NodeValue::Value(node_value.unwrap_value().clone()))
}
// The Call Edge value is added to the CallSourceCombinator.
// The end node will be the callsite node and the node_value parameter is the value at the
// called subroutine's BlkStart node
Edge::Call(_) => Some(NodeValue::CallFlowCombinator {
call_stub: None,
interprocedural_flow: Some(node_value.unwrap_value().clone()),
}),
// The CallStub Edge value is added to the CallSourceCombinator
// The user has the ability to split the node value at the BlkStart return to node
// to only send specific data along the CallStub Edge to the callsite
Edge::CRCallStub => Some(NodeValue::CallFlowCombinator {
call_stub: self.context.split_call_stub(node_value.unwrap_value()),
interprocedural_flow: None,
}),
// The user has the ability to split the node value at the BlkStart return node
// to only send specific data along the ReturnStub Edge to the last BlkEnd node called subroutine
Edge::CRReturnStub => self
.context
.split_return_stub(node_value.unwrap_value())
.map(NodeValue::Value),
// The CallCombine Edge merges the values coming in from the CallStub Edge and Call Edge
// It also gives the user access to the call and return term.
Edge::CallCombine(return_term) => match node_value {
NodeValue::Value(_) => panic!("Unexpected interprocedural fixpoint graph state"),
NodeValue::CallFlowCombinator {
call_stub,
interprocedural_flow,
} => {
let call_block = match graph.node_weight(start_node) {
Some(Node::CallSource {
source: (call_block, ..),
target: _,
}) => call_block,
_ => panic!("Malformed Control flow graph"),
};
let call_term = &call_block.term.jmps[0];
match self.context.update_callsite(
interprocedural_flow.as_ref(),
call_stub.as_ref(),
call_term,
return_term,
) {
Some(val) => Some(NodeValue::Value(val)),
None => None,
}
}
},
Edge::ExternCallStub(call) => self
.context
.update_call_stub(node_value.unwrap_value(), call)
.map(NodeValue::Value),
Edge::Jump(jump, untaken_conditional) => self
.context
.update_jumpsite(
node_value.unwrap_value(),
jump,
*untaken_conditional,
graph[end_node].get_block(),
)
.map(NodeValue::Value),
}
}
}
/// This struct is a wrapper to create a general fixpoint context out of an interprocedural fixpoint context.
pub struct GeneralizedContext<'a, T: Context<'a>> {
context: T,
_phantom_graph_reference: PhantomData<Graph<'a>>,
}
impl<'a, T: Context<'a>> GeneralizedContext<'a, T> {
/// Create a new generalized context out of an interprocedural context object.
pub fn new(context: T) -> Self {
GeneralizedContext {
context,
_phantom_graph_reference: PhantomData,
}
}
pub fn get_context(&self) -> &T {
&self.context
}
}
/// Generate a new computation from the corresponding context and an optional default value for nodes.
pub fn create_computation<'a, T: Context<'a>>(
problem: T,
default_value: Option<T::Value>,
) -> super::fixpoint::Computation<GeneralizedContext<'a, T>> {
let generalized_problem = GeneralizedContext::new(problem);
super::fixpoint::Computation::new(generalized_problem, default_value.map(NodeValue::Value))
}
#[cfg(test)]
pub mod tests;
#[cfg(test)]
pub mod mock_context;
use crate::{bil::Bitvector, intermediate_representation::*};
use super::{create_computation, mock_context, NodeValue};
use mock_context::Context;
use mock_context::StartEnd;
fn mock_program() -> Term<Program> {
let var = Variable {
name: String::from("RAX"),
size: ByteSize::new(8),
is_temp: false,
};
let value = Expression::UnOp {
op: UnOpType::IntNegate,
arg: Box::new(Expression::Var(var.clone())),
};
let def_term1 = Term {
tid: Tid::new("def1".to_string()),
term: Def::Assign {
var: var.clone(),
value: value.clone(),
},
};
let def_term2 = Term {
tid: Tid::new("def2".to_string()),
term: Def::Assign {
var: var.clone(),
value: value.clone(),
},
};
let def_term3 = Term {
tid: Tid::new("def3".to_string()),
term: Def::Assign {
var: var.clone(),
value: value.clone(),
},
};
let def_term4 = Term {
tid: Tid::new("def4".to_string()),
term: Def::Assign {
var: var.clone(),
value: value.clone(),
},
};
let def_term5 = Term {
tid: Tid::new("def5".to_string()),
term: Def::Assign {
var: var.clone(),
value: value.clone(),
},
};
let call_term = Term {
tid: Tid::new("call".to_string()),
term: Jmp::Call {
target: Tid::new("sub2"),
return_: Some(Tid::new("sub1_blk2")),
},
};
let return_term = Term {
tid: Tid::new("return".to_string()),
term: Jmp::Return(Expression::Const(Bitvector::zero(64.into()))), // The return term does not matter
};
let jmp = Jmp::Branch(Tid::new("sub1_blk1"));
let jmp_term = Term {
tid: Tid::new("jump"),
term: jmp,
};
let sub1_blk1 = Term {
tid: Tid::new("sub1_blk1"),
term: Blk {
defs: vec![def_term1],
jmps: vec![call_term],
},
};
let sub1_blk2 = Term {
tid: Tid::new("sub1_blk2"),
term: Blk {
defs: vec![def_term5],
jmps: vec![jmp_term],
},
};
let sub1 = Term {
tid: Tid::new("sub1"),
term: Sub {
name: "sub1".to_string(),
blocks: vec![sub1_blk1, sub1_blk2],
},
};
let cond_jump = Jmp::CBranch {
target: Tid::new("sub1_blk1"),
condition: Expression::Const(Bitvector::from_u8(0)),
};
let cond_jump_term = Term {
tid: Tid::new("cond_jump"),
term: cond_jump,
};
let jump_term_2 = Term {
tid: Tid::new("jump2"),
term: Jmp::Branch(Tid::new("sub2_blk2")),
};
let sub2_blk1 = Term {
tid: Tid::new("sub2_blk1"),
term: Blk {
defs: vec![def_term2, def_term3],
jmps: vec![cond_jump_term, jump_term_2],
},
};
let sub2_blk2 = Term {
tid: Tid::new("sub2_blk2"),
term: Blk {
defs: vec![def_term4],
jmps: vec![return_term],
},
};
let sub2 = Term {
tid: Tid::new("sub2"),
term: Sub {
name: "sub2".to_string(),
blocks: vec![sub2_blk1, sub2_blk2],
},
};
let program = Term {
tid: Tid::new("program"),
term: Program {
subs: vec![sub1, sub2],
extern_symbols: Vec::new(),
entry_points: Vec::new(),
},
};
program
}
#[test]
fn backward_fixpoint() {
let project = Project {
program: mock_program(),
cpu_architecture: String::from("x86"),
stack_pointer_register: Variable {
name: String::from("RSP"),
size: ByteSize::new(8),
is_temp: false,
},
calling_conventions: Vec::new(),
};
let mock_con = Context::new(&project);
let mut computation = create_computation(mock_con.clone(), None);
computation.set_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub1"), Tid::new("sub1_blk1"), StartEnd::Start))
.unwrap(),
NodeValue::Value(0),
);
computation.compute_with_max_steps(100);
// The fixpoint values of all 12 BlockStart/BlockEnd nodes are compared with their expected value
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub1"), Tid::new("sub1_blk1"), StartEnd::Start))
.unwrap()
)
.unwrap()
.unwrap_value(),
0 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub1"), Tid::new("sub1_blk1"), StartEnd::End))
.unwrap()
)
.unwrap()
.unwrap_value(),
1 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub1"), Tid::new("sub1_blk2"), StartEnd::Start))
.unwrap()
)
.unwrap()
.unwrap_value(),
1 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub1"), Tid::new("sub1_blk2"), StartEnd::End))
.unwrap()
)
.unwrap()
.unwrap_value(),
0 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub2"), Tid::new("sub2_blk1"), StartEnd::Start))
.unwrap()
)
.unwrap()
.unwrap_value(),
4 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub2"), Tid::new("sub2_blk1"), StartEnd::End))
.unwrap()
)
.unwrap()
.unwrap_value(),
2 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub2"), Tid::new("sub2_blk2"), StartEnd::Start))
.unwrap()
)
.unwrap()
.unwrap_value(),
2 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub2"), Tid::new("sub2_blk2"), StartEnd::End))
.unwrap()
)
.unwrap()
.unwrap_value(),
1 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub2"), Tid::new("sub1_blk1"), StartEnd::Start))
.unwrap()
)
.unwrap()
.unwrap_value(),
5 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub2"), Tid::new("sub1_blk1"), StartEnd::End))
.unwrap()
)
.unwrap()
.unwrap_value(),
4 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub2"), Tid::new("sub1_blk2"), StartEnd::Start))
.unwrap()
)
.unwrap()
.unwrap_value(),
6 as u64
);
assert_eq!(
*computation
.get_node_value(
*mock_con
.tid_to_node_index
.get(&(Tid::new("sub2"), Tid::new("sub1_blk2"), StartEnd::End))
.unwrap()
)
.unwrap()
.unwrap_value(),
5 as u64
);
}
//! Creating and computing interprocedural fixpoint problems. //! Creating and computing forward interprocedural fixpoint problems.
//! //!
//! # General notes //! # General notes
//! //!
//! This module supports computation of fixpoint problems on the control flow graphs generated by the `graph` module. //! This module supports computation of fixpoint problems on the control flow graphs generated by the `graph` module.
//! As of this writing, only forward analyses are possible,
//! backward analyses are not yet implemented.
//! //!
//! To compute a generalized fixpoint problem, //! To compute a generalized fixpoint problem,
//! first construct a context object implementing the `Context`trait. //! first construct a context object implementing the `Context`trait.
...@@ -13,27 +11,11 @@ ...@@ -13,27 +11,11 @@
use super::fixpoint::Context as GeneralFPContext; use super::fixpoint::Context as GeneralFPContext;
use super::graph::*; use super::graph::*;
use super::interprocedural_fixpoint_generic::*;
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
use crate::prelude::*; use petgraph::graph::EdgeIndex;
use fnv::FnvHashMap;
use petgraph::graph::{EdgeIndex, NodeIndex};
use std::marker::PhantomData; use std::marker::PhantomData;
#[derive(PartialEq, Eq, Serialize, Deserialize)]
pub enum NodeValue<T: PartialEq + Eq> {
Value(T),
CallReturnCombinator { call: Option<T>, return_: Option<T> },
}
impl<T: PartialEq + Eq> NodeValue<T> {
pub fn unwrap_value(&self) -> &T {
match self {
NodeValue::Value(value) => value,
_ => panic!("Unexpected node value type"),
}
}
}
/// The context for an interprocedural fixpoint computation. /// The context for an interprocedural fixpoint computation.
/// ///
/// Basically, a `Context` object needs to contain a reference to the actual graph, /// Basically, a `Context` object needs to contain a reference to the actual graph,
...@@ -101,7 +83,7 @@ pub trait Context<'a> { ...@@ -101,7 +83,7 @@ pub trait Context<'a> {
} }
/// This struct is a wrapper to create a general fixpoint context out of an interprocedural fixpoint context. /// This struct is a wrapper to create a general fixpoint context out of an interprocedural fixpoint context.
struct GeneralizedContext<'a, T: Context<'a>> { pub struct GeneralizedContext<'a, T: Context<'a>> {
context: T, context: T,
_phantom_graph_reference: PhantomData<Graph<'a>>, _phantom_graph_reference: PhantomData<Graph<'a>>,
} }
...@@ -114,6 +96,10 @@ impl<'a, T: Context<'a>> GeneralizedContext<'a, T> { ...@@ -114,6 +96,10 @@ impl<'a, T: Context<'a>> GeneralizedContext<'a, T> {
_phantom_graph_reference: PhantomData, _phantom_graph_reference: PhantomData,
} }
} }
pub fn get_context(&self) -> &T {
&self.context
}
} }
impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> { impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
...@@ -132,17 +118,19 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> { ...@@ -132,17 +118,19 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
match (val1, val2) { match (val1, val2) {
(Value(value1), Value(value2)) => Value(self.context.merge(value1, value2)), (Value(value1), Value(value2)) => Value(self.context.merge(value1, value2)),
( (
CallReturnCombinator { CallFlowCombinator {
call: call1, call_stub: call1,
return_: return1, interprocedural_flow: return1,
}, },
CallReturnCombinator { CallFlowCombinator {
call: call2, call_stub: call2,
return_: return2, interprocedural_flow: return2,
}, },
) => CallReturnCombinator { ) => CallFlowCombinator {
call: merge_option(call1, call2, |v1, v2| self.context.merge(v1, v2)), call_stub: merge_option(call1, call2, |v1, v2| self.context.merge(v1, v2)),
return_: merge_option(return1, return2, |v1, v2| self.context.merge(v1, v2)), interprocedural_flow: merge_option(return1, return2, |v1, v2| {
self.context.merge(v1, v2)
}),
}, },
_ => panic!("Malformed CFG in fixpoint computation"), _ => panic!("Malformed CFG in fixpoint computation"),
} }
...@@ -174,17 +162,20 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> { ...@@ -174,17 +162,20 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
.context .context
.update_call(node_value.unwrap_value(), call, &graph[end_node]) .update_call(node_value.unwrap_value(), call, &graph[end_node])
.map(NodeValue::Value), .map(NodeValue::Value),
Edge::CRCallStub => Some(NodeValue::CallReturnCombinator { Edge::CRCallStub => Some(NodeValue::CallFlowCombinator {
call: Some(node_value.unwrap_value().clone()), call_stub: Some(node_value.unwrap_value().clone()),
return_: None, interprocedural_flow: None,
}), }),
Edge::CRReturnStub => Some(NodeValue::CallReturnCombinator { Edge::CRReturnStub => Some(NodeValue::CallFlowCombinator {
call: None, call_stub: None,
return_: Some(node_value.unwrap_value().clone()), interprocedural_flow: Some(node_value.unwrap_value().clone()),
}), }),
Edge::ReturnCombine(call_term) => match node_value { Edge::ReturnCombine(call_term) => match node_value {
NodeValue::Value(_) => panic!("Unexpected interprocedural fixpoint graph state"), NodeValue::Value(_) => panic!("Unexpected interprocedural fixpoint graph state"),
NodeValue::CallReturnCombinator { call, return_ } => { NodeValue::CallFlowCombinator {
call_stub,
interprocedural_flow,
} => {
let return_from_block = match graph.node_weight(start_node) { let return_from_block = match graph.node_weight(start_node) {
Some(Node::CallReturn { Some(Node::CallReturn {
call: _, call: _,
...@@ -194,8 +185,8 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> { ...@@ -194,8 +185,8 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
}; };
let return_from_jmp = &return_from_block.term.jmps[0]; let return_from_jmp = &return_from_block.term.jmps[0];
match self.context.update_return( match self.context.update_return(
return_.as_ref(), interprocedural_flow.as_ref(),
call.as_ref(), call_stub.as_ref(),
call_term, call_term,
return_from_jmp, return_from_jmp,
) { ) {
...@@ -221,85 +212,11 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> { ...@@ -221,85 +212,11 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
} }
} }
/// An intermediate result of an interprocedural fixpoint computation. /// Generate a new computation from the corresponding context and an optional default value for nodes.
/// pub fn create_computation<'a, T: Context<'a>>(
/// The usage instructions are identical to the usage of the general fixpoint computation object, problem: T,
/// except that you need to provide an interprocedural context object instead of a general one. default_value: Option<T::Value>,
pub struct Computation<'a, T: Context<'a>> { ) -> super::fixpoint::Computation<GeneralizedContext<'a, T>> {
generalized_computation: super::fixpoint::Computation<GeneralizedContext<'a, T>>, let generalized_problem = GeneralizedContext::new(problem);
} super::fixpoint::Computation::new(generalized_problem, default_value.map(NodeValue::Value))
impl<'a, T: Context<'a>> Computation<'a, T> {
/// Generate a new computation from the corresponding context and an optional default value for nodes.
pub fn new(problem: T, default_value: Option<T::Value>) -> Self {
let generalized_problem = GeneralizedContext::new(problem);
let computation = super::fixpoint::Computation::new(
generalized_problem,
default_value.map(NodeValue::Value),
);
Computation {
generalized_computation: computation,
}
}
/// Compute the fixpoint.
/// Note that this function does not terminate if the fixpoint algorithm does not stabilize.
pub fn compute(&mut self) {
self.generalized_computation.compute()
}
/// Compute the fixpoint while updating each node at most max_steps times.
/// Note that the result may not be a stabilized fixpoint, but only an intermediate result of a fixpoint computation.
pub fn compute_with_max_steps(&mut self, max_steps: u64) {
self.generalized_computation
.compute_with_max_steps(max_steps)
}
/// Get the value of a node.
pub fn get_node_value(&self, node: NodeIndex) -> Option<&NodeValue<T::Value>> {
self.generalized_computation.get_node_value(node)
}
/// Set the value of a node and mark the node as not yet stabilized
pub fn set_node_value(&mut self, node: NodeIndex, value: NodeValue<T::Value>) {
self.generalized_computation.set_node_value(node, value)
}
/// Get a reference to the internal map where one can look up the current values of all nodes
pub fn node_values(&self) -> &FnvHashMap<NodeIndex, NodeValue<T::Value>> {
self.generalized_computation.node_values()
}
/// Get a reference to the underlying graph
pub fn get_graph(&self) -> &Graph {
self.generalized_computation.get_graph()
}
/// Get a reference to the underlying context object
pub fn get_context(&self) -> &T {
&self.generalized_computation.get_context().context
}
/// Returns `True` if the computation has stabilized, i.e. the internal worklist is empty.
pub fn has_stabilized(&self) -> bool {
self.generalized_computation.has_stabilized()
}
/// Return a list of all nodes which are marked as not-stabilized
pub fn get_worklist(&self) -> Vec<NodeIndex> {
self.generalized_computation.get_worklist()
}
}
/// Helper function to merge to values wrapped in `Option<..>`.
/// Merges `(Some(x), None)` to `Some(x)`.
fn merge_option<T: Clone, F>(opt1: &Option<T>, opt2: &Option<T>, merge: F) -> Option<T>
where
F: Fn(&T, &T) -> T,
{
match (opt1, opt2) {
(Some(value1), Some(value2)) => Some(merge(value1, value2)),
(Some(value), None) | (None, Some(value)) => Some(value.clone()),
(None, None) => None,
}
} }
use crate::prelude::*;
/// NodeValue that can either be a single abstract value or a
/// composition of the abstract value computed following an interprocedural
/// call in the graph and of the abstract value when the call is not taken.
/// The CallFlowCombinator then allows for a merge of the values computed
/// over both paths.
///
/// The call_stub value will either be transferred from the callsite to the return site
/// in a forward analysis or the other way around in a backward analysis.
///
/// The interprocedural_flow value will either be transferred from the end of the called subroutine
/// to the return site in case of a forward analysis or from the beginning of the called subroutine
/// to the callsite in a backward analysis.
#[derive(PartialEq, Eq, Serialize, Deserialize)]
pub enum NodeValue<T: PartialEq + Eq> {
Value(T),
CallFlowCombinator {
call_stub: Option<T>,
interprocedural_flow: Option<T>,
},
}
impl<T: PartialEq + Eq> NodeValue<T> {
pub fn unwrap_value(&self) -> &T {
match self {
NodeValue::Value(value) => value,
_ => panic!("Unexpected node value type"),
}
}
}
/// Helper function to merge to values wrapped in `Option<..>`.
/// Merges `(Some(x), None)` to `Some(x)`.
pub fn merge_option<T: Clone, F>(opt1: &Option<T>, opt2: &Option<T>, merge: F) -> Option<T>
where
F: Fn(&T, &T) -> T,
{
match (opt1, opt2) {
(Some(value1), Some(value2)) => Some(merge(value1, value2)),
(Some(value), None) | (None, Some(value)) => Some(value.clone()),
(None, None) => None,
}
}
pub mod backward_interprocedural_fixpoint;
pub mod fixpoint; pub mod fixpoint;
pub mod forward_interprocedural_fixpoint;
pub mod graph; pub mod graph;
pub mod interprocedural_fixpoint; pub mod interprocedural_fixpoint_generic;
pub mod pointer_inference; pub mod pointer_inference;
...@@ -103,7 +103,7 @@ fn mock_project() -> (Project, Config) { ...@@ -103,7 +103,7 @@ fn mock_project() -> (Project, Config) {
#[test] #[test]
fn context_problem_implementation() { fn context_problem_implementation() {
use crate::analysis::interprocedural_fixpoint::Context as IpFpContext; use crate::analysis::forward_interprocedural_fixpoint::Context as IpFpContext;
use crate::analysis::pointer_inference::Data; use crate::analysis::pointer_inference::Data;
use Expression::*; use Expression::*;
...@@ -266,7 +266,7 @@ fn context_problem_implementation() { ...@@ -266,7 +266,7 @@ fn context_problem_implementation() {
#[test] #[test]
fn update_return() { fn update_return() {
use crate::analysis::interprocedural_fixpoint::Context as IpFpContext; use crate::analysis::forward_interprocedural_fixpoint::Context as IpFpContext;
use crate::analysis::pointer_inference::object::ObjectType; use crate::analysis::pointer_inference::object::ObjectType;
use crate::analysis::pointer_inference::Data; use crate::analysis::pointer_inference::Data;
let (project, config) = mock_project(); let (project, config) = mock_project();
......
use super::*; use super::*;
impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> { impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
type Value = State; type Value = State;
/// Get the underlying graph on which the analysis operates. /// Get the underlying graph on which the analysis operates.
......
...@@ -13,7 +13,9 @@ ...@@ -13,7 +13,9 @@
//! //!
//! See the `Config` struct for configurable analysis parameters. //! See the `Config` struct for configurable analysis parameters.
use super::interprocedural_fixpoint::{Computation, NodeValue}; use super::fixpoint::Computation;
use super::forward_interprocedural_fixpoint::GeneralizedContext;
use super::interprocedural_fixpoint_generic::NodeValue;
use crate::abstract_domain::{BitvectorDomain, DataDomain}; use crate::abstract_domain::{BitvectorDomain, DataDomain};
use crate::analysis::graph::{Graph, Node}; use crate::analysis::graph::{Graph, Node};
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
...@@ -59,7 +61,7 @@ pub struct Config { ...@@ -59,7 +61,7 @@ pub struct Config {
/// A wrapper struct for the pointer inference computation object. /// A wrapper struct for the pointer inference computation object.
pub struct PointerInference<'a> { pub struct PointerInference<'a> {
computation: Computation<'a, Context<'a>>, computation: Computation<GeneralizedContext<'a, Context<'a>>>,
log_collector: crossbeam_channel::Sender<LogThreadMsg>, log_collector: crossbeam_channel::Sender<LogThreadMsg>,
pub collected_logs: (Vec<LogMessage>, Vec<CweWarning>), pub collected_logs: (Vec<LogMessage>, Vec<CweWarning>),
} }
...@@ -107,7 +109,7 @@ impl<'a> PointerInference<'a> { ...@@ -107,7 +109,7 @@ impl<'a> PointerInference<'a> {
}) })
.collect(); .collect();
let mut fixpoint_computation = let mut fixpoint_computation =
super::interprocedural_fixpoint::Computation::new(context, None); super::forward_interprocedural_fixpoint::create_computation(context, None);
let _ = log_sender.send(LogThreadMsg::Log(LogMessage::new_debug(format!( let _ = log_sender.send(LogThreadMsg::Log(LogMessage::new_debug(format!(
"Pointer Inference: Adding {} entry points", "Pointer Inference: Adding {} entry points",
entry_sub_to_entry_node_map.len() entry_sub_to_entry_node_map.len()
...@@ -115,7 +117,7 @@ impl<'a> PointerInference<'a> { ...@@ -115,7 +117,7 @@ impl<'a> PointerInference<'a> {
for (sub_tid, start_node_index) in entry_sub_to_entry_node_map.into_iter() { for (sub_tid, start_node_index) in entry_sub_to_entry_node_map.into_iter() {
fixpoint_computation.set_node_value( fixpoint_computation.set_node_value(
start_node_index, start_node_index,
super::interprocedural_fixpoint::NodeValue::Value(State::new( super::interprocedural_fixpoint_generic::NodeValue::Value(State::new(
&project.stack_pointer_register, &project.stack_pointer_register,
sub_tid, sub_tid,
)), )),
...@@ -175,7 +177,7 @@ impl<'a> PointerInference<'a> { ...@@ -175,7 +177,7 @@ impl<'a> PointerInference<'a> {
} }
pub fn get_context(&self) -> &Context { pub fn get_context(&self) -> &Context {
self.computation.get_context() self.computation.get_context().get_context()
} }
pub fn get_node_value(&self, node_id: NodeIndex) -> Option<&NodeValue<State>> { pub fn get_node_value(&self, node_id: NodeIndex) -> Option<&NodeValue<State>> {
...@@ -235,7 +237,7 @@ impl<'a> PointerInference<'a> { ...@@ -235,7 +237,7 @@ impl<'a> PointerInference<'a> {
.clone(); .clone();
self.computation.set_node_value( self.computation.set_node_value(
entry, entry,
super::interprocedural_fixpoint::NodeValue::Value(State::new( super::interprocedural_fixpoint_generic::NodeValue::Value(State::new(
&project.stack_pointer_register, &project.stack_pointer_register,
sub_tid, sub_tid,
)), )),
...@@ -363,9 +365,10 @@ impl<'a> PointerInference<'a> { ...@@ -363,9 +365,10 @@ impl<'a> PointerInference<'a> {
} }
Node::CallReturn { call, return_ } => { Node::CallReturn { call, return_ } => {
let (call_state, return_state) = match node_value { let (call_state, return_state) = match node_value {
NodeValue::CallReturnCombinator { call, return_ } => { NodeValue::CallFlowCombinator {
(call.is_some(), return_.is_some()) call_stub,
} interprocedural_flow,
} => (call_stub.is_some(), interprocedural_flow.is_some()),
_ => panic!(), _ => panic!(),
}; };
println!( println!(
......
...@@ -36,10 +36,10 @@ ...@@ -36,10 +36,10 @@
//! - For functions with more than one return value we do not distinguish between //! - For functions with more than one return value we do not distinguish between
//! the return values. //! the return values.
use crate::analysis::forward_interprocedural_fixpoint::create_computation;
use crate::analysis::forward_interprocedural_fixpoint::Context as _;
use crate::analysis::graph::{Edge, Node}; use crate::analysis::graph::{Edge, Node};
use crate::analysis::interprocedural_fixpoint::Computation; use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use crate::analysis::interprocedural_fixpoint::Context as _;
use crate::analysis::interprocedural_fixpoint::NodeValue;
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::utils::log::{CweWarning, LogMessage}; use crate::utils::log::{CweWarning, LogMessage};
...@@ -102,7 +102,7 @@ pub fn check_cwe( ...@@ -102,7 +102,7 @@ pub fn check_cwe(
Some(NodeValue::Value(val)) => Some(val.clone()), Some(NodeValue::Value(val)) => Some(val.clone()),
_ => None, _ => None,
}; };
let mut computation = Computation::new(context, None); let mut computation = create_computation(context, None);
computation.set_node_value( computation.set_node_value(
node, node,
NodeValue::Value(State::new( NodeValue::Value(State::new(
......
...@@ -2,9 +2,9 @@ use super::State; ...@@ -2,9 +2,9 @@ use super::State;
use super::Taint; use super::Taint;
use super::CWE_MODULE; use super::CWE_MODULE;
use crate::abstract_domain::AbstractDomain; use crate::abstract_domain::AbstractDomain;
use crate::analysis::forward_interprocedural_fixpoint::Context as _;
use crate::analysis::graph::{Graph, Node}; use crate::analysis::graph::{Graph, Node};
use crate::analysis::interprocedural_fixpoint::Context as _; use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use crate::analysis::interprocedural_fixpoint::NodeValue;
use crate::analysis::pointer_inference::PointerInference as PointerInferenceComputation; use crate::analysis::pointer_inference::PointerInference as PointerInferenceComputation;
use crate::analysis::pointer_inference::State as PointerInferenceState; use crate::analysis::pointer_inference::State as PointerInferenceState;
use crate::intermediate_representation::*; use crate::intermediate_representation::*;
...@@ -251,7 +251,7 @@ impl<'a> Context<'a> { ...@@ -251,7 +251,7 @@ impl<'a> Context<'a> {
} }
} }
impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> { impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
type Value = State; type Value = State;
/// Get the underlying graph of the fixpoint computation /// Get the underlying graph of the fixpoint computation
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment