Unverified Commit 542db66e by Melvin Klimke Committed by GitHub

Direction independent graph (#118)

Adds CallSource nodes to the control flow graph, which have the same role for backwards analysis as the CallReturn nodes have for forward analysis.
parent 434d0727
......@@ -32,11 +32,12 @@
//! (if the call returns at all).
//! * Right now indirect calls are handled as if they were extern calls, i.e. an *ExternCallStub* edge is added.
//! This behaviour will change in the future, when better indirect call handling is implemented.
//! * For each in-program call ([`image`](../../../../../doc/images/internal_function_call.png)) and corresponding return jump one node and three edges are generated:
//! * An artificial node *CallReturn*
//! * For each in-program call ([`image`](../../../../../doc/images/internal_function_call.png)) and corresponding return jump two nodes and four edges are generated:
//! * An artificial node *CallReturn* and node *CallSource*
//! * A *CRCallStub* edge from the *BlkEnd* node of the callsite to *CallReturn*
//! * A *CRReturnStub* edge from the *BlkEnd* node of the returning from block to *CallReturn*
//! * A *CRCombine* edge from *CallReturn* to the *BlkStart* node of the returned to block.
//! * A *ReturnCombine* edge from *CallReturn* to the *BlkStart* node of the returned to block.
//! * A *CallCombine* edge from the *BlkEnd* node to the *CallSource* node.
//!
//! The artificial *CallReturn* nodes enable enriching the information flowing through a return edge
//! with information recovered from the corresponding callsite during a fixpoint computation.
......@@ -52,9 +53,12 @@ pub type Graph<'a> = DiGraph<Node<'a>, Edge<'a>>;
/// The node type of an interprocedural control flow graph
///
/// Each node carries a pointer to its associated block with it.
/// For `CallReturn`nodes the associated blocks are both the callsite block (containing the call instruction)
/// For `CallReturn`nodes the associated blocks are both the `CallSource`block (containing the call instruction)
/// and the returning-from block (containing the return instruction).
///
/// For `CallSource`nodes the associated block is the callsite block (source)
/// and the target block of the call.
///
/// Basic blocks are allowed to be contained in more than one `Sub`.
/// In the control flow graph such basic blocks occur once per subroutine they are contained in.
/// For this reason, the nodes also carry a pointer to the corresponding subroutine with them
......@@ -67,6 +71,10 @@ pub enum Node<'a> {
call: (&'a Term<Blk>, &'a Term<Sub>),
return_: (&'a Term<Blk>, &'a Term<Sub>),
},
CallSource {
source: (&'a Term<Blk>, &'a Term<Sub>),
target: (&'a Term<Blk>, &'a Term<Sub>),
},
}
impl<'a> Node<'a> {
......@@ -76,7 +84,9 @@ impl<'a> Node<'a> {
use Node::*;
match self {
BlkStart(blk, _sub) | BlkEnd(blk, _sub) => blk,
CallReturn { .. } => panic!("get_block() is undefined for CallReturn nodes"),
CallSource { .. } | CallReturn { .. } => {
panic!("get_block() is undefined for CallReturn and CallSource nodes")
}
}
}
}
......@@ -95,6 +105,11 @@ impl<'a> std::fmt::Display for Node<'a> {
"CallReturn @ {} (sub {}) (caller @ {} (sub {}))",
return_.0.tid, return_.1.tid, call.0.tid, call.1.tid
),
Self::CallSource { source, target } => write!(
formatter,
"CallSource @ {} (sub {}) (caller @ {} (sub {}))",
target.0.tid, target.1.tid, source.0.tid, source.1.tid
),
}
}
}
......@@ -115,7 +130,8 @@ pub enum Edge<'a> {
ExternCallStub(&'a Term<Jmp>),
CRCallStub,
CRReturnStub,
CRCombine(&'a Term<Jmp>),
CallCombine(&'a Term<Jmp>),
ReturnCombine(&'a Term<Jmp>),
}
/// A builder struct for building graphs
......@@ -176,7 +192,7 @@ impl<'a> GraphBuilder<'a> {
}
/// add all subs to the call targets so that call instructions can be linked to the starting block of the corresponding sub.
fn add_subs_to_jump_targets(&mut self) {
fn add_subs_to_call_targets(&mut self) {
for sub in self.program.term.subs.iter() {
if !sub.term.blocks.is_empty() {
let start_block = &sub.term.blocks[0];
......@@ -194,8 +210,8 @@ impl<'a> GraphBuilder<'a> {
jump: &'a Term<Jmp>,
untaken_conditional: Option<&'a Term<Jmp>>,
) {
let sub_term = match self.graph[source] {
Node::BlkEnd(_source_block, sub_term) => sub_term,
let (source_block, sub_term) = match self.graph[source] {
Node::BlkEnd(source_block, sub_term) => (source_block, sub_term),
_ => panic!(),
};
match &jump.term {
......@@ -242,14 +258,34 @@ impl<'a> GraphBuilder<'a> {
.add_edge(source, return_to_node, Edge::ExternCallStub(jump));
}
} else {
let mut call_source_node: Option<NodeIndex> = None;
if let Some((target_node, _)) = self.call_targets.get(&target) {
self.graph.add_edge(source, *target_node, Edge::Call(jump));
let (target_block, target_sub) = match self.graph[*target_node] {
Node::BlkStart(target_block, target_sub) => (target_block, target_sub),
_ => panic!(),
};
call_source_node = Some(self.graph.add_node(Node::CallSource {
source: (source_block, sub_term),
target: (target_block, target_sub),
}));
self.graph.add_edge(
source,
*call_source_node.as_ref().unwrap(),
Edge::CallCombine(jump),
);
self.graph.add_edge(
*call_source_node.as_ref().unwrap(),
*target_node,
Edge::Call(jump),
);
} // TODO: Log message for the else-case?
if let Some(return_node) = return_to_node_option {
if let Some(cs_node) = call_source_node {
self.return_addresses
.entry(target.clone())
.and_modify(|vec| vec.push((source, return_node)))
.or_insert_with(|| vec![(source, return_node)]);
.and_modify(|vec| vec.push((cs_node, return_node)))
.or_insert_with(|| vec![(cs_node, return_node)]);
}
}
}
}
......@@ -310,7 +346,7 @@ impl<'a> GraphBuilder<'a> {
}
for (call_node, return_to_node) in self.return_addresses[&return_from_sub.tid].iter() {
let (call_block, caller_sub) = match self.graph[*call_node] {
Node::BlkEnd(block, sub) => (block, sub),
Node::CallSource { source, .. } => source,
_ => panic!(),
};
let return_from_block = self.graph[return_source].get_block();
......@@ -320,16 +356,19 @@ impl<'a> GraphBuilder<'a> {
.iter()
.find(|jump| matches!(jump.term, Jmp::Call{..}))
.unwrap();
let cr_combine_node = self.graph.add_node(Node::CallReturn {
let return_combine_node = self.graph.add_node(Node::CallReturn {
call: (call_block, caller_sub),
return_: (return_from_block, return_from_sub),
});
self.graph
.add_edge(*call_node, cr_combine_node, Edge::CRCallStub);
self.graph
.add_edge(return_source, cr_combine_node, Edge::CRReturnStub);
.add_edge(*call_node, return_combine_node, Edge::CRCallStub);
self.graph
.add_edge(cr_combine_node, *return_to_node, Edge::CRCombine(call_term));
.add_edge(return_source, return_combine_node, Edge::CRReturnStub);
self.graph.add_edge(
return_combine_node,
*return_to_node,
Edge::ReturnCombine(call_term),
);
}
}
......@@ -367,7 +406,7 @@ impl<'a> GraphBuilder<'a> {
/// Build the interprocedural control flow graph.
pub fn build(mut self) -> Graph<'a> {
self.add_program_blocks();
self.add_subs_to_jump_targets();
self.add_subs_to_call_targets();
self.add_jump_and_call_edges();
self.add_return_edges();
self.graph
......@@ -471,7 +510,7 @@ mod tests {
let program = mock_program();
let graph = get_program_cfg(&program, HashSet::new());
println!("{}", serde_json::to_string_pretty(&graph).unwrap());
assert_eq!(graph.node_count(), 14);
assert_eq!(graph.edge_count(), 18);
assert_eq!(graph.node_count(), 16);
assert_eq!(graph.edge_count(), 20);
}
}
......@@ -148,7 +148,7 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
}
}
/// Edge transition function.
/// Forward edge transition function.
/// Applies the transition functions from the interprocedural context object
/// corresponding to the type of the provided edge.
fn update_edge(
......@@ -169,6 +169,7 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
});
end_val.map(NodeValue::Value)
}
Edge::CallCombine(_) => Some(Self::NodeValue::Value(node_value.unwrap_value().clone())),
Edge::Call(call) => self
.context
.update_call(node_value.unwrap_value(), call, &graph[end_node])
......@@ -181,7 +182,7 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
call: None,
return_: Some(node_value.unwrap_value().clone()),
}),
Edge::CRCombine(call_term) => match node_value {
Edge::ReturnCombine(call_term) => match node_value {
NodeValue::Value(_) => panic!("Unexpected interprocedural fixpoint graph state"),
NodeValue::CallReturnCombinator { call, return_ } => {
let return_from_block = match graph.node_weight(start_node) {
......
......@@ -358,6 +358,9 @@ impl<'a> PointerInference<'a> {
Node::BlkStart(block, _sub) => {
println!("{}: ERROR: Block start without successor state!", block.tid)
}
Node::CallSource { source, .. } => {
println!("{}: ERROR: Call source without target!", source.0.tid)
}
Node::CallReturn { call, return_ } => {
let (call_state, return_state) = match node_value {
NodeValue::CallReturnCombinator { call, return_ } => {
......
......@@ -79,7 +79,8 @@ fn is_reachable(
match edge.weight() {
Edge::Block
| Edge::CRCallStub
| Edge::CRCombine(_)
| Edge::CallCombine(_)
| Edge::ReturnCombine(_)
| Edge::Jump(_, _)
| Edge::ExternCallStub(_) => {
if visited_nodes.get(&edge.target()).is_none() {
......
doc/images/internal_function_call.png

23.4 KB | W: | H:

doc/images/internal_function_call.png

35.3 KB | W: | H:

doc/images/internal_function_call.png
doc/images/internal_function_call.png
doc/images/internal_function_call.png
doc/images/internal_function_call.png
  • 2-up
  • Swipe
  • Onion skin
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