Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
cwe_checker
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
fact-depend
cwe_checker
Commits
e82c19ce
Unverified
Commit
e82c19ce
authored
Aug 06, 2020
by
Enkelmann
Committed by
GitHub
Aug 06, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactored fixpoint modules (#77)
parent
281a0207
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
278 additions
and
133 deletions
+278
-133
fixpoint.rs
cwe_checker_rs/src/analysis/fixpoint.rs
+85
-33
graph.rs
cwe_checker_rs/src/analysis/graph.rs
+64
-18
interprocedural_fixpoint.rs
cwe_checker_rs/src/analysis/interprocedural_fixpoint.rs
+102
-60
context.rs
cwe_checker_rs/src/analysis/pointer_inference/context.rs
+27
-22
No files found.
cwe_checker_rs/src/analysis/fixpoint.rs
View file @
e82c19ce
/*!
This module implements a generic fixpoint algorithm for dataflow analysis.
A fixpoint problem is defined as a graph where:
- Each node `n` gets assigned a value `val(n)` where the set of all values forms a partially ordered set.
- Each edge `e` defines a rule `e:value -> value` how to compute the value at the end node given the value at the start node of the edge.
A fixpoint is an assignment of values to all nodes of the graph so that for all edges
`e(val(start_node)) <= val(end_node)` holds.
For general information on dataflow analysis using fixpoint algorithms see [Wikipedia](https://en.wikipedia.org/wiki/Data-flow_analysis).
Or open an issue on github that you want more documentation here. :-)
*/
//! Creating and computing generic fixpoint computations.
//!
//! For general information on dataflow analysis using fixpoint algorithms see [Wikipedia](https://en.wikipedia.org/wiki/Data-flow_analysis).
//!
//! # General implementation notes
//!
//! A fixpoint problem is defined as a graph where:
//! - Each node `n` gets assigned a value `val(n)` where the set of all values forms a partially ordered set.
//! - Each edge `e` defines a rule `e:value -> value` how to compute the value at the end node given the value at the start node of the edge.
//!
//! A fixpoint is reached if an assignment of values to all nodes of the graph is found
//! so that for all edges `e(val(start_node)) <= val(end_node)` holds.
//! Usually one wants to find the smallest fixpoint,
//! i.e. a fixpoint such that for each node `n` the value `val(n)` is as small as possible (with respect to the partial order)
//! but also not less than a given starting value.
//!
//! As in the `graph` module, nodes are assumed to represent points in time,
//! whereas edges represent state transitions or (artificial) information flow channels.
//! In particular, only edges have transition functions and not nodes.
//!
//! In the current implementation edge transition functions are also allowed to return `None`
//! to indicate that no information flows through the edge.
//! For example, an analysis can use this to indicate edges that are never taken
//! and thus prevent dead code to affect the analysis.
//!
//! # How to compute the solution to a fixpoint problem
//!
//! To create a fixpoint computation one needs an object implementing the `Context` trait.
//! This object contains all information necessary to compute fixpoints,
//! like the graph or how to compute transition functions,
//! but not the actual starting values of a fixpoint computation.
//! With it, create a `Computation` object and then modify the node values through the object
//! to match the intended starting conditions of the fixpoint computation.
//! The `Computation` object also contains methods to actually run the fixpoint computation after the starting values are set
//! and methods to retrieve the results of the computation.
use
fnv
::
FnvHashMap
;
use
petgraph
::
graph
::{
DiGraph
,
EdgeIndex
,
NodeIndex
};
use
petgraph
::
visit
::
EdgeRef
;
use
std
::
collections
::{
BTreeMap
,
BinaryHeap
};
///
A fixpoint problem defines the context for
a fixpoint computation.
///
The context of
a fixpoint computation.
///
/// All trait methods have access to the FixpointProblem structure, so that context informations are accessible through it.
pub
trait
Problem
{
pub
trait
Context
{
/// the type of edge labels of the underlying graph
type
EdgeLabel
:
Clone
;
/// the type of node labels of the underlying graph
type
NodeLabel
;
/// The type of the value that gets assigned to each node.
/// The values should form a partially ordered set.
type
NodeValue
:
PartialEq
+
Eq
;
/// Get the graph on which the fixpoint computation operates.
fn
get_graph
(
&
self
)
->
&
DiGraph
<
Self
::
NodeLabel
,
Self
::
EdgeLabel
>
;
/// This function describes how to merge two values
...
...
@@ -36,21 +63,41 @@ pub trait Problem {
fn
update_edge
(
&
self
,
value
:
&
Self
::
NodeValue
,
edge
:
EdgeIndex
)
->
Option
<
Self
::
NodeValue
>
;
}
/// The computation struct contains an intermediate result of a fixpoint computation.
pub
struct
Computation
<
T
:
Problem
>
{
fp_problem
:
T
,
node_priority_list
:
Vec
<
usize
>
,
// maps a node index to its priority (higher priority nodes get stabilized first)
priority_to_node_list
:
Vec
<
NodeIndex
>
,
// maps a priority to the corresponding node index
/// The computation struct contains an intermediate result of a fixpoint computation
/// and provides methods for continuing the fixpoint computation
/// or extracting the (intermediate or final) results.
///
/// # Usage
///
/// ```
/// let mut computation = Computation::new(context, optional_default_node_value);
///
/// // set starting node values with computation.set_node_value(..)
/// // ...
///
/// computation.compute();
///
/// // get the resulting node values
/// if let Some(node_value) = computation.get_node_value(node_index) {
/// // ...
/// };
/// ```
pub
struct
Computation
<
T
:
Context
>
{
fp_context
:
T
,
/// maps a node index to its priority (higher priority nodes get stabilized first)
node_priority_list
:
Vec
<
usize
>
,
/// maps a priority to the corresponding node index
priority_to_node_list
:
Vec
<
NodeIndex
>
,
worklist
:
BinaryHeap
<
usize
>
,
default_value
:
Option
<
T
::
NodeValue
>
,
node_values
:
FnvHashMap
<
NodeIndex
,
T
::
NodeValue
>
,
}
impl
<
T
:
Problem
>
Computation
<
T
>
{
impl
<
T
:
Context
>
Computation
<
T
>
{
/// Create a new fixpoint computation from a fixpoint problem, the corresponding graph
/// and a default value for all nodes if one should exists.
pub
fn
new
(
fp_
problem
:
T
,
default_value
:
Option
<
T
::
NodeValue
>
)
->
Self
{
let
graph
=
fp_
problem
.get_graph
();
pub
fn
new
(
fp_
context
:
T
,
default_value
:
Option
<
T
::
NodeValue
>
)
->
Self
{
let
graph
=
fp_
context
.get_graph
();
// order the nodes in weak topological order
let
sorted_nodes
:
Vec
<
NodeIndex
>
=
petgraph
::
algo
::
kosaraju_scc
(
&
graph
)
.into_iter
()
...
...
@@ -70,7 +117,7 @@ impl<T: Problem> Computation<T> {
}
}
Computation
{
fp_
problem
,
fp_
context
,
node_priority_list
,
priority_to_node_list
:
sorted_nodes
,
worklist
,
...
...
@@ -97,7 +144,7 @@ impl<T: Problem> Computation<T> {
/// Merge the value at a node with some new value.
fn
merge_node_value
(
&
mut
self
,
node
:
NodeIndex
,
value
:
T
::
NodeValue
)
{
if
let
Some
(
old_value
)
=
self
.node_values
.get
(
&
node
)
{
let
merged_value
=
self
.fp_
problem
.merge
(
&
value
,
old_value
);
let
merged_value
=
self
.fp_
context
.merge
(
&
value
,
old_value
);
if
merged_value
!=
*
old_value
{
self
.set_node_value
(
node
,
merged_value
);
}
...
...
@@ -109,12 +156,12 @@ impl<T: Problem> Computation<T> {
/// Compute and update the value at the end node of an edge.
fn
update_edge
(
&
mut
self
,
edge
:
EdgeIndex
)
{
let
(
start_node
,
end_node
)
=
self
.fp_
problem
.fp_
context
.get_graph
()
.edge_endpoints
(
edge
)
.expect
(
"Edge not found"
);
if
let
Some
(
start_val
)
=
self
.node_values
.get
(
&
start_node
)
{
if
let
Some
(
new_end_val
)
=
self
.fp_
problem
.update_edge
(
start_val
,
edge
)
{
if
let
Some
(
new_end_val
)
=
self
.fp_
context
.update_edge
(
start_val
,
edge
)
{
self
.merge_node_value
(
end_node
,
new_end_val
);
}
}
...
...
@@ -123,7 +170,7 @@ impl<T: Problem> Computation<T> {
/// Update all outgoing edges of a node.
fn
update_node
(
&
mut
self
,
node
:
NodeIndex
)
{
let
edges
:
Vec
<
EdgeIndex
>
=
self
.fp_
problem
.fp_
context
.get_graph
()
.edges
(
node
)
.map
(|
edge_ref
|
edge_ref
.id
())
...
...
@@ -137,7 +184,7 @@ impl<T: Problem> Computation<T> {
/// Each node will be visited at most max_steps times.
/// If a node does not stabilize after max_steps visits, the end result will not be a fixpoint but only an intermediate result of a fixpoint computation.
pub
fn
compute_with_max_steps
(
&
mut
self
,
max_steps
:
u64
)
{
let
mut
steps
=
vec!
[
0
;
self
.fp_
problem
.get_graph
()
.node_count
()];
let
mut
steps
=
vec!
[
0
;
self
.fp_
context
.get_graph
()
.node_count
()];
while
let
Some
(
priority
)
=
self
.worklist
.pop
()
{
let
node
=
self
.priority_to_node_list
[
priority
];
if
steps
[
node
.index
()]
<
max_steps
{
...
...
@@ -163,7 +210,12 @@ impl<T: Problem> Computation<T> {
/// Get a reference to the underlying graph
pub
fn
get_graph
(
&
self
)
->
&
DiGraph
<
T
::
NodeLabel
,
T
::
EdgeLabel
>
{
self
.fp_problem
.get_graph
()
self
.fp_context
.get_graph
()
}
/// Get a reference to the underlying context object
pub
fn
get_context
(
&
self
)
->
&
T
{
&
self
.fp_context
}
}
...
...
@@ -171,11 +223,11 @@ impl<T: Problem> Computation<T> {
mod
tests
{
use
super
::
*
;
struct
FP
Problem
{
struct
FP
Context
{
graph
:
DiGraph
<
(),
u64
>
,
}
impl
Problem
for
FPProblem
{
impl
Context
for
FPContext
{
type
EdgeLabel
=
u64
;
type
NodeLabel
=
();
type
NodeValue
=
u64
;
...
...
@@ -207,7 +259,7 @@ mod tests {
}
graph
.add_edge
(
NodeIndex
::
new
(
100
),
NodeIndex
::
new
(
0
),
0
);
let
mut
solution
=
Computation
::
new
(
FP
Problem
{
graph
},
None
);
let
mut
solution
=
Computation
::
new
(
FP
Context
{
graph
},
None
);
solution
.set_node_value
(
NodeIndex
::
new
(
0
),
0
);
solution
.compute_with_max_steps
(
20
);
...
...
cwe_checker_rs/src/analysis/graph.rs
View file @
e82c19ce
/*!
This module implements functions to generate (interprocedural) control flow graphs out of a program term.
*/
//! Generate control flow graphs out of a program term.
//!
//! The generated graphs follow some basic principles:
//! * **Nodes** denote specific (abstract) points in time during program execution,
//! i.e. information does not change on a node.
//! So a basic block itself is not a node,
//! but the points in time before and after execution of the basic block can be nodes.
//! * **Edges** denote either transitions between the points in time of their start and end nodes during program execution
//! or they denote (artificial) information flow between nodes. See the `CRCallStub` edges of interprocedural control flow graphs
//! for an example of an edge that is only meant for information flow and not actual control flow.
//!
//! # General assumptions
//!
//! The graph construction algorithm assumes
//! that each basic block of the program term ends with zero, one or two jump instructions.
//! In the case of two jump instructions the first one is a conditional jump
//! and the second one is an unconditional jump.
//! Conditional calls are not supported.
//! Missing jump instructions are supported to indicate incomplete information about the control flow,
//! i.e. points where the control flow reconstruction failed.
//! These points are converted to dead ends in the control flow graphs.
//!
//! # Interprocedural control flow graph
//!
//! The function [`get_program_cfg`](fn.get_program_cfg.html) builds an interprocedural control flow graph out of a program term as follows:
//! * Each basic block is converted into two nodes, *BlkStart* and *BlkEnd*,
//! and a *block* edge from *BlkStart* to *BlkEnd*.
//! * Jumps and calls inside the program are converted to *Jump* or *Call* edges from the *BlkEnd* node of their source
//! to the *BlkStart* node of their target (which is the first block of the target function in case of calls).
//! * Calls to library functions outside the program are converted to *ExternCallStub* edges
//! from the *BlkEnd* node of the callsite to the *BlkStart* node of the basic block the call returns to
//! (if the call returns at all).
//! * For each in-program call and corresponding return jump one node and three edges are generated:
//! * An artificial node *CallReturn*
//! * 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.
//!
//! The artificial *CallReturn* nodes enable enriching the information flowing through a return edge
//! with information recovered from the corresponding callsite during a fixpoint computation.
use
crate
::
prelude
::
*
;
use
crate
::
term
::
*
;
use
petgraph
::
graph
::{
DiGraph
,
NodeIndex
};
use
serde
::
Serialize
;
use
std
::
collections
::{
HashMap
,
HashSet
};
/// The graph type of an interprocedural control flow graph
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 block is the callsite block (containing the call instruction)
/// and *not* the return block (containing the return instruction).
#[derive(Serialize,
Debug,
PartialEq,
Eq,
Hash,
Clone,
Copy)]
pub
enum
Node
<
'a
>
{
BlkStart
(
&
'a
Term
<
Blk
>
),
BlkEnd
(
&
'a
Term
<
Blk
>
),
CallReturn
(
&
'a
Term
<
Blk
>
),
// The block is the one from the call instruction
CallReturn
(
&
'a
Term
<
Blk
>
),
}
impl
<
'a
>
Node
<
'a
>
{
/// Get the block corresponding to the node.
pub
fn
get_block
(
&
self
)
->
&
'a
Term
<
Blk
>
{
use
Node
::
*
;
match
self
{
...
...
@@ -37,12 +79,14 @@ impl<'a> std::fmt::Display for Node<'a> {
}
}
// TODO: document that we assume that the graph only has blocks with either:
// - one unconditional call instruction
// - one return instruction
// - at most 2 intraprocedural jump instructions, i.e. at most one of them is a conditional jump
/// The node type of an interprocedural fixpoint graph
/// The edge type of an interprocedural fixpoint graph.
///
/// Where applicable the edge carries a reference to the corresponding jump instruction.
/// For `CRCombine` edges the corresponding jump is the call and not the return jump.
/// Intraprocedural jumps carry a second optional reference,
/// which is only set if the jump directly follows an conditional jump,
/// i.e. it represents the "conditional jump not taken" branch.
/// In this case the other jump reference points to the untaken conditional jump.
#[derive(Serialize,
Debug,
PartialEq,
Eq,
Hash,
Clone,
Copy)]
pub
enum
Edge
<
'a
>
{
Block
,
...
...
@@ -59,12 +103,14 @@ struct GraphBuilder<'a> {
program
:
&
'a
Term
<
Program
>
,
extern_subs
:
HashSet
<
Tid
>
,
graph
:
Graph
<
'a
>
,
jump_targets
:
HashMap
<
Tid
,
(
NodeIndex
,
NodeIndex
)
>
,
// Denotes the NodeIndices of possible jump targets
return_addresses
:
HashMap
<
Tid
,
Vec
<
(
NodeIndex
,
NodeIndex
)
>>
,
// for each function the list of return addresses of the corresponding call sites
/// Denotes the NodeIndices of possible jump targets
jump_targets
:
HashMap
<
Tid
,
(
NodeIndex
,
NodeIndex
)
>
,
/// for each function the list of return addresses of the corresponding call sites
return_addresses
:
HashMap
<
Tid
,
Vec
<
(
NodeIndex
,
NodeIndex
)
>>
,
}
impl
<
'a
>
GraphBuilder
<
'a
>
{
/// create a new builder with an
a
mtpy graph
/// create a new builder with an
e
mtpy graph
pub
fn
new
(
program
:
&
'a
Term
<
Program
>
,
extern_subs
:
HashSet
<
Tid
>
)
->
GraphBuilder
<
'a
>
{
GraphBuilder
{
program
,
...
...
@@ -161,7 +207,7 @@ impl<'a> GraphBuilder<'a> {
let
block
:
&
'a
Term
<
Blk
>
=
self
.graph
[
node
]
.get_block
();
let
jumps
=
block
.term.jmps
.as_slice
();
match
jumps
{
[]
=>
(),
//
TODO: Decide whether blocks without jumps should be considered hard errors or (silent) dead ends
[]
=>
(),
//
Blocks without jumps are dead ends corresponding to control flow reconstruction errors.
[
jump
]
=>
self
.add_jump_edge
(
node
,
jump
,
None
),
[
if_jump
,
else_jump
]
=>
{
self
.add_jump_edge
(
node
,
if_jump
,
None
);
...
...
@@ -173,8 +219,8 @@ impl<'a> GraphBuilder<'a> {
/// For each return instruction and each corresponding call, add the following to the graph:
/// - a CallReturn node.
/// - edges from the callsite and from the returning-from
-
site to the CallReturn node
/// - an edge from the CallReturn node to the return-to
-
site
/// - edges from the callsite and from the returning-from
site to the CallReturn node
/// - an edge from the CallReturn node to the return-to
site
fn
add_call_return_node_and_edges
(
&
mut
self
,
return_from_sub
:
&
Term
<
Sub
>
,
...
...
@@ -237,7 +283,7 @@ impl<'a> GraphBuilder<'a> {
}
}
///
This function builds
the interprocedural control flow graph for a program term.
///
Build
the interprocedural control flow graph for a program term.
pub
fn
get_program_cfg
(
program
:
&
Term
<
Program
>
,
extern_subs
:
HashSet
<
Tid
>
)
->
Graph
{
let
builder
=
GraphBuilder
::
new
(
program
,
extern_subs
);
builder
.build
()
...
...
cwe_checker_rs/src/analysis/interprocedural_fixpoint.rs
View file @
e82c19ce
/*!
This module defines a trait for interprocedural fixpoint problems.
## Basic usage
Define a *Context* struct containing all information that does not change during the fixpoint computation.
In particular, this includes the graph on which the fixpoint computation is run.
Then implement the *Problem* trait for the *Context* struct.
The fixpoint computation can now be run as follows:
```
let context = MyContext::new(); // MyContext needs to implement Problem
let mut computation = Computation::new(context, None);
// add starting node values here with
computation.compute();
// computation is done, get solution node values here
```
*/
// TODO: When indirect jumps are sufficiently supported, the update_jump methods need access to
// target (and maybe source) nodes/TIDs, to determine which target the current edge points to.
// Alternatively, this could be achieved through usage of the specialize_conditional function.
// Currently unclear, which way is better.
use
super
::
fixpoint
::
Problem
as
GeneralFPProblem
;
//! Creating and computing 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.
//! 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
crate
::
bil
::
Expression
;
use
crate
::
prelude
::
*
;
...
...
@@ -45,31 +35,63 @@ impl<T: PartialEq + Eq> NodeValue<T> {
}
}
/// An interprocedural fixpoint problem defines the context for a fixpoint computation.
/// The context for an 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.
pub
trait
Problem
<
'a
>
{
///
/// 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.
fn
get_graph
(
&
self
)
->
&
Graph
<
'a
>
;
/// Merge two node values.
fn
merge
(
&
self
,
value1
:
&
Self
::
Value
,
value2
:
&
Self
::
Value
)
->
Self
::
Value
;
fn
update_def
(
&
self
,
value
:
&
Self
::
Value
,
def
:
&
Term
<
Def
>
)
->
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_jump
(
&
self
,
value
:
&
Self
::
Value
,
jump
:
&
Term
<
Jmp
>
,
untaken_conditional
:
Option
<&
Term
<
Jmp
>>
,
target
:
&
Term
<
Blk
>
,
)
->
Option
<
Self
::
Value
>
;
/// Transition function for in-program calls.
fn
update_call
(
&
self
,
value
:
&
Self
::
Value
,
call
:
&
Term
<
Jmp
>
,
target
:
&
Node
,
)
->
Option
<
Self
::
Value
>
;
fn
update_call
(
&
self
,
value
:
&
Self
::
Value
,
call
:
&
Term
<
Jmp
>
,
target
:
&
Node
)
->
Self
::
Value
;
/// Transition function for return instructions.
/// Has access to the value at the callsite corresponding to the return edge.
/// This way one can recover caller-specific information on return from a function.
fn
update_return
(
&
self
,
value
:
&
Self
::
Value
,
value_before_call
:
Option
<&
Self
::
Value
>
,
call_term
:
&
Term
<
Jmp
>
,
)
->
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
:
&
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
:
&
Self
::
Value
,
...
...
@@ -78,34 +100,37 @@ pub trait Problem<'a> {
)
->
Option
<
Self
::
Value
>
;
}
/// This struct is a wrapper to create a general fixpoint
problem out of an interprocedural fixpoint problem
.
struct
Generalized
Problem
<
'a
,
T
:
Problem
<
'a
>>
{
problem
:
T
,
/// This struct is a wrapper to create a general fixpoint
context out of an interprocedural fixpoint context
.
struct
Generalized
Context
<
'a
,
T
:
Context
<
'a
>>
{
context
:
T
,
_phantom_graph_reference
:
PhantomData
<
Graph
<
'a
>>
,
}
impl
<
'a
,
T
:
Problem
<
'a
>>
GeneralizedProblem
<
'a
,
T
>
{
pub
fn
new
(
problem
:
T
)
->
Self
{
GeneralizedProblem
{
problem
,
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
,
}
}
}
impl
<
'a
,
T
:
Problem
<
'a
>>
GeneralFPProblem
for
GeneralizedProblem
<
'a
,
T
>
{
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
.
problem
.get_graph
()
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
.
problem
.merge
(
value1
,
value2
)),
(
Value
(
value1
),
Value
(
value2
))
=>
Value
(
self
.
context
.merge
(
value1
,
value2
)),
(
CallReturnCombinator
{
call
:
call1
,
...
...
@@ -116,35 +141,37 @@ impl<'a, T: Problem<'a>> GeneralFPProblem for GeneralizedProblem<'a, T> {
return_
:
return2
,
},
)
=>
CallReturnCombinator
{
call
:
merge_option
(
call1
,
call2
,
|
v1
,
v2
|
self
.
problem
.merge
(
v1
,
v2
)),
return_
:
merge_option
(
return1
,
return2
,
|
v1
,
v2
|
self
.
problem
.merge
(
v1
,
v2
)),
call
:
merge_option
(
call1
,
call2
,
|
v1
,
v2
|
self
.
context
.merge
(
v1
,
v2
)),
return_
:
merge_option
(
return1
,
return2
,
|
v1
,
v2
|
self
.
context
.merge
(
v1
,
v2
)),
},
_
=>
panic!
(
"Malformed CFG in fixpoint computation"
),
}
}
/// 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
.
problem
.get_graph
();
let
graph
=
self
.
context
.get_graph
();
let
(
start_node
,
end_node
)
=
graph
.edge_endpoints
(
edge
)
.unwrap
();
let
block_term
=
graph
.node_weight
(
start_node
)
.unwrap
()
.get_block
();
match
graph
.edge_weight
(
edge
)
.unwrap
()
{
Edge
::
Block
=>
{
let
value
=
node_value
.unwrap_value
();
let
defs
=
&
block_term
.term.defs
;
let
end_val
=
defs
.iter
()
.fold
(
value
.clone
(),
|
accum
,
def
|
{
self
.
problem
.update_def
(
&
accum
,
def
)
let
end_val
=
defs
.iter
()
.
try_
fold
(
value
.clone
(),
|
accum
,
def
|
{
self
.
context
.update_def
(
&
accum
,
def
)
});
Some
(
NodeValue
::
Value
(
end_val
)
)
end_val
.map
(
NodeValue
::
Value
)
}
Edge
::
Call
(
call
)
=>
Some
(
NodeValue
::
Value
(
self
.problem
.update_call
(
node_value
.unwrap_value
(),
call
,
&
graph
[
end_node
],
))),
Edge
::
Call
(
call
)
=>
self
.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
,
...
...
@@ -158,7 +185,7 @@ impl<'a, T: Problem<'a>> GeneralFPProblem for GeneralizedProblem<'a, T> {
NodeValue
::
CallReturnCombinator
{
call
,
return_
}
=>
{
if
let
Some
(
return_value
)
=
return_
{
match
self
.
problem
.
context
.update_return
(
return_value
,
call
.as_ref
(),
call_term
)
{
Some
(
val
)
=>
Some
(
NodeValue
::
Value
(
val
)),
...
...
@@ -170,26 +197,34 @@ impl<'a, T: Problem<'a>> GeneralFPProblem for GeneralizedProblem<'a, T> {
}
},
Edge
::
ExternCallStub
(
call
)
=>
self
.
problem
.
context
.update_call_stub
(
node_value
.unwrap_value
(),
call
)
.map
(
NodeValue
::
Value
),
Edge
::
Jump
(
jump
,
untaken_conditional
)
=>
self
.problem
.update_jump
(
node_value
.unwrap_value
(),
jump
,
*
untaken_conditional
)
.context
.update_jump
(
node_value
.unwrap_value
(),
jump
,
*
untaken_conditional
,
graph
[
end_node
]
.get_block
(),
)
.map
(
NodeValue
::
Value
),
}
}
}
/// This struct contains an intermediate result of an interprocedural fixpoint cumputation.
pub
struct
Computation
<
'a
,
T
:
Problem
<
'a
>>
{
generalized_computation
:
super
::
fixpoint
::
Computation
<
GeneralizedProblem
<
'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
:
Problem
<
'a
>>
Computation
<
'a
,
T
>
{
/// Generate a new computation from the corresponding
problem and a
default value for nodes.
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
=
Generalized
Problem
::
new
(
problem
);
let
generalized_problem
=
Generalized
Context
::
new
(
problem
);
let
computation
=
super
::
fixpoint
::
Computation
::
new
(
generalized_problem
,
default_value
.map
(
NodeValue
::
Value
),
...
...
@@ -200,7 +235,7 @@ impl<'a, T: Problem<'a>> Computation<'a, T> {
}
/// Compute the fixpoint.
/// Note that this function does not terminate if the fixpoint algorithm does not stabilize
/// Note that this function does not terminate if the fixpoint algorithm does not stabilize
.
pub
fn
compute
(
&
mut
self
)
{
self
.generalized_computation
.compute
()
}
...
...
@@ -231,8 +266,15 @@ impl<'a, T: Problem<'a>> Computation<'a, T> {
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
}
}
/// 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
,
...
...
cwe_checker_rs/src/analysis/pointer_inference/context.rs
View file @
e82c19ce
...
...
@@ -58,7 +58,7 @@ impl<'a> Context<'a> {
}
}
impl
<
'a
>
crate
::
analysis
::
interprocedural_fixpoint
::
Problem
<
'a
>
for
Context
<
'a
>
{
impl
<
'a
>
crate
::
analysis
::
interprocedural_fixpoint
::
Context
<
'a
>
for
Context
<
'a
>
{
type
Value
=
State
;
fn
get_graph
(
&
self
)
->
&
Graph
<
'a
>
{
...
...
@@ -69,7 +69,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Problem<'a> for Context<'a>
value1
.merge
(
value2
)
}
fn
update_def
(
&
self
,
state
:
&
Self
::
Value
,
def
:
&
Term
<
Def
>
)
->
Self
::
Value
{
fn
update_def
(
&
self
,
state
:
&
Self
::
Value
,
def
:
&
Term
<
Def
>
)
->
Option
<
Self
::
Value
>
{
// first check for use-after-frees
if
state
.contains_access_of_dangling_memory
(
&
def
.term.rhs
)
{
let
warning
=
CweWarning
{
...
...
@@ -91,7 +91,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Problem<'a> for Context<'a>
Expression
::
Store
{
..
}
=>
{
let
mut
state
=
state
.clone
();
self
.log_debug
(
state
.handle_store_exp
(
&
def
.term.rhs
),
Some
(
&
def
.tid
));
state
Some
(
state
)
}
Expression
::
IfThenElse
{
condition
,
...
...
@@ -120,14 +120,14 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Problem<'a> for Context<'a>
match
state
.eval
(
condition
)
{
Ok
(
Data
::
Value
(
cond
))
if
!
cond
.is_top
()
=>
{
if
cond
==
Bitvector
::
from_bit
(
true
)
.into
()
{
true_state
Some
(
true_state
)
}
else
if
cond
==
Bitvector
::
from_bit
(
false
)
.into
()
{
false_state
Some
(
false_state
)
}
else
{
panic!
(
"IfThenElse with wrong condition bitsize encountered"
)
}
}
Ok
(
_
)
=>
true_state
.merge
(
&
false_state
),
Ok
(
_
)
=>
Some
(
true_state
.merge
(
&
false_state
)
),
Err
(
err
)
=>
panic!
(
"IfThenElse-Condition evaluation failed: {}"
,
err
),
}
}
...
...
@@ -137,7 +137,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Problem<'a> for Context<'a>
new_state
.handle_register_assign
(
&
def
.term.lhs
,
expression
),
Some
(
&
def
.tid
),
);
new_state
Some
(
new_state
)
}
}
}
...
...
@@ -147,6 +147,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Problem<'a> for Context<'a>
value
:
&
State
,
_jump
:
&
Term
<
Jmp
>
,
_untaken_conditional
:
Option
<&
Term
<
Jmp
>>
,
_target
:
&
Term
<
Blk
>
,
)
->
Option
<
State
>
{
// TODO: Implement some real specialization of conditionals!
let
mut
new_value
=
value
.clone
();
...
...
@@ -159,7 +160,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Problem<'a> for Context<'a>
state
:
&
State
,
call_term
:
&
Term
<
Jmp
>
,
_target_node
:
&
crate
::
analysis
::
graph
::
Node
,
)
->
State
{
)
->
Option
<
State
>
{
let
call
=
if
let
JmpKind
::
Call
(
ref
call
)
=
call_term
.term.kind
{
call
}
else
{
...
...
@@ -220,7 +221,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Problem<'a> for Context<'a>
callee_state
.ids_known_to_caller
=
callee_state
.memory
.get_all_object_ids
();
callee_state
.ids_known_to_caller
.remove
(
&
callee_stack_id
);
callee_state
Some
(
callee_state
)
}
else
{
panic!
(
"Indirect call edges not yet supported."
)
// TODO: Support indirect call edges!
...
...
@@ -595,7 +596,7 @@ mod tests {
#[test]
fn
context_problem_implementation
()
{
use
crate
::
analysis
::
interprocedural_fixpoint
::
Problem
;
use
crate
::
analysis
::
interprocedural_fixpoint
::
Context
as
IpFpContext
;
use
crate
::
analysis
::
pointer_inference
::
Data
;
use
crate
::
bil
::
*
;
use
Expression
::
*
;
...
...
@@ -632,10 +633,10 @@ mod tests {
};
// test update_def
state
=
context
.update_def
(
&
state
,
&
def
);
state
=
context
.update_def
(
&
state
,
&
def
)
.unwrap
()
;
let
stack_pointer
=
Data
::
Pointer
(
PointerDomain
::
new
(
new_id
(
"main"
,
"RSP"
),
bv
(
-
16
)));
assert_eq!
(
state
.eval
(
&
Var
(
register
(
"RSP"
)))
.unwrap
(),
stack_pointer
);
state
=
context
.update_def
(
&
state
,
&
store_term
);
state
=
context
.update_def
(
&
state
,
&
store_term
)
.unwrap
()
;
// Test update_call
let
target_block
=
Term
{
...
...
@@ -647,7 +648,7 @@ mod tests {
};
let
target_node
=
crate
::
analysis
::
graph
::
Node
::
BlkStart
(
&
target_block
);
let
call
=
call_term
(
"func"
);
let
mut
callee_state
=
context
.update_call
(
&
state
,
&
call
,
&
target_node
);
let
mut
callee_state
=
context
.update_call
(
&
state
,
&
call
,
&
target_node
)
.unwrap
()
;
assert_eq!
(
callee_state
.stack_id
,
new_id
(
"func"
,
"RSP"
));
assert_eq!
(
callee_state
.caller_stack_ids
.len
(),
1
);
assert_eq!
(
...
...
@@ -763,7 +764,7 @@ mod tests {
#[test]
fn
update_return
()
{
use
crate
::
analysis
::
interprocedural_fixpoint
::
Problem
;
use
crate
::
analysis
::
interprocedural_fixpoint
::
Context
as
IpFpContext
;
use
crate
::
analysis
::
pointer_inference
::
object
::
ObjectType
;
use
crate
::
analysis
::
pointer_inference
::
Data
;
let
project
=
mock_project
();
...
...
@@ -771,10 +772,12 @@ mod tests {
let
(
log_sender
,
_log_receiver
)
=
crossbeam_channel
::
unbounded
();
let
context
=
Context
::
new
(
&
project
,
cwe_sender
,
log_sender
);
let
state_before_return
=
State
::
new
(
&
register
(
"RSP"
),
Tid
::
new
(
"callee"
));
let
mut
state_before_return
=
context
.update_def
(
&
state_before_return
,
&
reg_add_term
(
"RSP"
,
8
,
"stack_offset_on_return_adjustment"
),
);
let
mut
state_before_return
=
context
.update_def
(
&
state_before_return
,
&
reg_add_term
(
"RSP"
,
8
,
"stack_offset_on_return_adjustment"
),
)
.unwrap
();
let
callsite_id
=
new_id
(
"call_callee"
,
"RSP"
);
state_before_return
.memory
.add_abstract_object
(
...
...
@@ -814,10 +817,12 @@ mod tests {
.unwrap
();
let
state_before_call
=
State
::
new
(
&
register
(
"RSP"
),
Tid
::
new
(
"original_caller_id"
));
let
mut
state_before_call
=
context
.update_def
(
&
state_before_call
,
&
reg_add_term
(
"RSP"
,
-
16
,
"stack_offset_on_call_adjustment"
),
);
let
mut
state_before_call
=
context
.update_def
(
&
state_before_call
,
&
reg_add_term
(
"RSP"
,
-
16
,
"stack_offset_on_call_adjustment"
),
)
.unwrap
();
let
caller_caller_id
=
new_id
(
"caller_caller"
,
"RSP"
);
state_before_call
.memory
.add_abstract_object
(
caller_caller_id
.clone
(),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment