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
//!
//! 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,
//! first construct a context object implementing the `Context`trait.
......@@ -13,27 +11,11 @@
use super::fixpoint::Context as GeneralFPContext;
use super::graph::*;
use super::interprocedural_fixpoint_generic::*;
use crate::intermediate_representation::*;
use crate::prelude::*;
use fnv::FnvHashMap;
use petgraph::graph::{EdgeIndex, NodeIndex};
use petgraph::graph::EdgeIndex;
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.
///
/// Basically, a `Context` object needs to contain a reference to the actual graph,
......@@ -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.
struct GeneralizedContext<'a, T: Context<'a>> {
pub struct GeneralizedContext<'a, T: Context<'a>> {
context: T,
_phantom_graph_reference: PhantomData<Graph<'a>>,
}
......@@ -114,6 +96,10 @@ impl<'a, T: Context<'a>> GeneralizedContext<'a, T> {
_phantom_graph_reference: PhantomData,
}
}
pub fn get_context(&self) -> &T {
&self.context
}
}
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) {
(Value(value1), Value(value2)) => Value(self.context.merge(value1, value2)),
(
CallReturnCombinator {
call: call1,
return_: return1,
CallFlowCombinator {
call_stub: call1,
interprocedural_flow: return1,
},
CallReturnCombinator {
call: call2,
return_: return2,
CallFlowCombinator {
call_stub: call2,
interprocedural_flow: return2,
},
) => CallReturnCombinator {
call: merge_option(call1, call2, |v1, v2| self.context.merge(v1, v2)),
return_: merge_option(return1, return2, |v1, v2| self.context.merge(v1, v2)),
) => CallFlowCombinator {
call_stub: merge_option(call1, call2, |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"),
}
......@@ -174,17 +162,20 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
.context
.update_call(node_value.unwrap_value(), call, &graph[end_node])
.map(NodeValue::Value),
Edge::CRCallStub => Some(NodeValue::CallReturnCombinator {
call: Some(node_value.unwrap_value().clone()),
return_: None,
Edge::CRCallStub => Some(NodeValue::CallFlowCombinator {
call_stub: Some(node_value.unwrap_value().clone()),
interprocedural_flow: None,
}),
Edge::CRReturnStub => Some(NodeValue::CallReturnCombinator {
call: None,
return_: Some(node_value.unwrap_value().clone()),
Edge::CRReturnStub => Some(NodeValue::CallFlowCombinator {
call_stub: None,
interprocedural_flow: Some(node_value.unwrap_value().clone()),
}),
Edge::ReturnCombine(call_term) => match node_value {
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) {
Some(Node::CallReturn {
call: _,
......@@ -194,8 +185,8 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
};
let return_from_jmp = &return_from_block.term.jmps[0];
match self.context.update_return(
return_.as_ref(),
call.as_ref(),
interprocedural_flow.as_ref(),
call_stub.as_ref(),
call_term,
return_from_jmp,
) {
......@@ -221,85 +212,11 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
}
}
/// An intermediate result of an interprocedural fixpoint computation.
///
/// The usage instructions are identical to the usage of the general fixpoint computation object,
/// except that you need to provide an interprocedural context object instead of a general one.
pub struct Computation<'a, T: Context<'a>> {
generalized_computation: super::fixpoint::Computation<GeneralizedContext<'a, T>>,
}
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 {
/// 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);
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,
}
super::fixpoint::Computation::new(generalized_problem, default_value.map(NodeValue::Value))
}
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 forward_interprocedural_fixpoint;
pub mod graph;
pub mod interprocedural_fixpoint;
pub mod interprocedural_fixpoint_generic;
pub mod pointer_inference;
......@@ -103,7 +103,7 @@ fn mock_project() -> (Project, Config) {
#[test]
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 Expression::*;
......@@ -266,7 +266,7 @@ fn context_problem_implementation() {
#[test]
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::Data;
let (project, config) = mock_project();
......
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;
/// Get the underlying graph on which the analysis operates.
......
......@@ -13,7 +13,9 @@
//!
//! 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::analysis::graph::{Graph, Node};
use crate::intermediate_representation::*;
......@@ -59,7 +61,7 @@ pub struct Config {
/// A wrapper struct for the pointer inference computation object.
pub struct PointerInference<'a> {
computation: Computation<'a, Context<'a>>,
computation: Computation<GeneralizedContext<'a, Context<'a>>>,
log_collector: crossbeam_channel::Sender<LogThreadMsg>,
pub collected_logs: (Vec<LogMessage>, Vec<CweWarning>),
}
......@@ -107,7 +109,7 @@ impl<'a> PointerInference<'a> {
})
.collect();
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!(
"Pointer Inference: Adding {} entry points",
entry_sub_to_entry_node_map.len()
......@@ -115,7 +117,7 @@ impl<'a> PointerInference<'a> {
for (sub_tid, start_node_index) in entry_sub_to_entry_node_map.into_iter() {
fixpoint_computation.set_node_value(
start_node_index,
super::interprocedural_fixpoint::NodeValue::Value(State::new(
super::interprocedural_fixpoint_generic::NodeValue::Value(State::new(
&project.stack_pointer_register,
sub_tid,
)),
......@@ -175,7 +177,7 @@ impl<'a> PointerInference<'a> {
}
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>> {
......@@ -235,7 +237,7 @@ impl<'a> PointerInference<'a> {
.clone();
self.computation.set_node_value(
entry,
super::interprocedural_fixpoint::NodeValue::Value(State::new(
super::interprocedural_fixpoint_generic::NodeValue::Value(State::new(
&project.stack_pointer_register,
sub_tid,
)),
......@@ -363,9 +365,10 @@ impl<'a> PointerInference<'a> {
}
Node::CallReturn { call, return_ } => {
let (call_state, return_state) = match node_value {
NodeValue::CallReturnCombinator { call, return_ } => {
(call.is_some(), return_.is_some())
}
NodeValue::CallFlowCombinator {
call_stub,
interprocedural_flow,
} => (call_stub.is_some(), interprocedural_flow.is_some()),
_ => panic!(),
};
println!(
......
......@@ -36,10 +36,10 @@
//! - For functions with more than one return value we do not distinguish between
//! 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::interprocedural_fixpoint::Computation;
use crate::analysis::interprocedural_fixpoint::Context as _;
use crate::analysis::interprocedural_fixpoint::NodeValue;
use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use crate::intermediate_representation::*;
use crate::prelude::*;
use crate::utils::log::{CweWarning, LogMessage};
......@@ -102,7 +102,7 @@ pub fn check_cwe(
Some(NodeValue::Value(val)) => Some(val.clone()),
_ => None,
};
let mut computation = Computation::new(context, None);
let mut computation = create_computation(context, None);
computation.set_node_value(
node,
NodeValue::Value(State::new(
......
......@@ -2,9 +2,9 @@ use super::State;
use super::Taint;
use super::CWE_MODULE;
use crate::abstract_domain::AbstractDomain;
use crate::analysis::forward_interprocedural_fixpoint::Context as _;
use crate::analysis::graph::{Graph, Node};
use crate::analysis::interprocedural_fixpoint::Context as _;
use crate::analysis::interprocedural_fixpoint::NodeValue;
use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use crate::analysis::pointer_inference::PointerInference as PointerInferenceComputation;
use crate::analysis::pointer_inference::State as PointerInferenceState;
use crate::intermediate_representation::*;
......@@ -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;
/// 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