Skip to content

Commit

Permalink
add unit tests for AbstractLocation, fix bug
Browse files Browse the repository at this point in the history
  • Loading branch information
Enkelmann committed Nov 21, 2023
1 parent d80c04d commit 0761220
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 10 deletions.
78 changes: 77 additions & 1 deletion src/cwe_checker_lib/src/abstract_domain/identifier/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::prelude::*;

/// An abstract location describes how to find the value of a variable in memory at a given time.
///
/// It is defined recursively, where the root is always a register.
/// It is defined recursively, where the root is either a register or a (constant) global address.
/// This way only locations that the local state knows about are representable.
/// It is also impossible to accidentally describe circular references.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
Expand Down Expand Up @@ -218,3 +218,79 @@ impl AbstractLocation {
}
}
}

#[cfg(test)]
pub mod tests {
use super::*;
use crate::variable;

impl AbstractLocation {
/// Mock an abstract location with a variable as root.
pub fn mock(
root_var: &str,
offsets: &[i64],
size: impl Into<ByteSize>,
) -> AbstractLocation {
let var = variable!(root_var);
match offsets {
[] => AbstractLocation::Register(var),
_ => AbstractLocation::Pointer(var, AbstractMemoryLocation::mock(offsets, size)),
}
}
}

#[test]
fn test_from_variants() {
let loc = AbstractLocation::from_var(&variable!("RAX:8")).unwrap();
assert_eq!(&format!("{loc}"), "RAX");
let loc = AbstractLocation::from_global_address(&Bitvector::from_u64(32));
assert_eq!(
loc,
AbstractLocation::GlobalAddress {
address: 32,
size: ByteSize::new(8)
}
);
let loc = AbstractLocation::from_stack_position(&variable!("RSP:8"), 16, ByteSize::new(8));
assert_eq!(loc, AbstractLocation::mock("RSP:8", &[16], 8));
}

#[test]
fn test_with_offset_addendum() {
let loc = AbstractLocation::mock("RAX:8", &[1, 2, 3], 4).with_offset_addendum(12);
assert_eq!(loc, AbstractLocation::mock("RAX:8", &[1, 2, 15], 4));
}

#[test]
fn test_dereferenced() {
let loc = AbstractLocation::mock("RAX:8", &[], 8)
.dereferenced(ByteSize::new(4), ByteSize::new(8));
assert_eq!(loc, AbstractLocation::mock("RAX:8", &[0], 4));
}

#[test]
fn test_recursion_depth() {
let loc = AbstractLocation::mock("RAX:8", &[1, 2, 3], 4);
assert_eq!(loc.recursion_depth(), 3);
}

#[test]
fn test_extend() {
let mut loc = AbstractLocation::mock("RAX:8", &[1, 2, 3], 4);
let extension = AbstractMemoryLocation::mock(&[4, 5, 6], 1);
loc.extend(extension, ByteSize::new(4));
assert_eq!(loc, AbstractLocation::mock("RAX:8", &[1, 2, 3, 4, 5, 6], 1));
}

#[test]
fn test_get_parent_location() {
let loc = AbstractLocation::mock("RAX:8", &[1], 4);
let (parent, last_offset) = loc.get_parent_location(ByteSize::new(8)).unwrap();
assert_eq!(parent, AbstractLocation::mock("RAX:8", &[], 8));
assert_eq!(last_offset, 1);
let loc = AbstractLocation::mock("RAX:8", &[1, 2, 3], 4);
let (parent, last_offset) = loc.get_parent_location(ByteSize::new(8)).unwrap();
assert_eq!(parent, AbstractLocation::mock("RAX:8", &[1, 2], 8));
assert_eq!(last_offset, 3);
}
}
78 changes: 78 additions & 0 deletions src/cwe_checker_lib/src/abstract_domain/identifier/mem_location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ impl AbstractMemoryLocation {
}
}

/// Add an offset to the root location of the memory location.
pub fn add_offset_at_root(&mut self, addendum: i64) {
match self {
Self::Location { offset, .. } | Self::Pointer { offset, .. } => *offset += addendum,
}
}

/// Dereference the pointer that `self` is pointing to.
///
/// Panics if the old value of `self` is not pointer-sized.
Expand Down Expand Up @@ -126,3 +133,74 @@ impl std::fmt::Display for AbstractMemoryLocation {
}
}
}

#[cfg(test)]
pub mod tests {
use super::*;

impl AbstractMemoryLocation {
/// Mock a memory location with a given sequence of offsets.
/// The first element in the sequence is the root offset.
pub fn mock(offsets: &[i64], size: impl Into<ByteSize>) -> AbstractMemoryLocation {
match offsets {
[] => panic!(),
[offset] => AbstractMemoryLocation::Location {
offset: *offset,
size: size.into(),
},
[offset, tail @ ..] => AbstractMemoryLocation::Pointer {
offset: *offset,
target: Box::new(AbstractMemoryLocation::mock(tail, size)),
},
}
}
}

#[test]
fn test_mock() {
let loc = AbstractMemoryLocation::mock(&[1, 2, 3], 4);
assert_eq!(&format!("{loc}"), "(1)->(2)->(3)");
}

#[test]
fn test_get_parent_location() {
let loc = AbstractMemoryLocation::mock(&[1, 2, 3], 4);
let (parent_loc, last_offset) = loc.get_parent_location(ByteSize::new(8)).unwrap();
assert_eq!(parent_loc, AbstractMemoryLocation::mock(&[1, 2], 8));
assert_eq!(last_offset, 3);
let loc = AbstractMemoryLocation::mock(&[1], 4);
assert!(loc.get_parent_location(ByteSize::new(8)).is_err());
}

#[test]
fn test_offset_addendums() {
let mut loc = AbstractMemoryLocation::mock(&[1, 2, 3], 4);
loc.add_offset(6);
assert_eq!(&loc, &AbstractMemoryLocation::mock(&[1, 2, 9], 4));
loc.add_offset_at_root(-5);
assert_eq!(&loc, &AbstractMemoryLocation::mock(&[-4, 2, 9], 4));
}

#[test]
fn test_dereference() {
let mut loc = AbstractMemoryLocation::mock(&[1, 2, 3], 4);
loc.dereference(ByteSize::new(8), ByteSize::new(4));
assert_eq!(loc, AbstractMemoryLocation::mock(&[1, 2, 3, 0], 8))
}

#[test]
fn test_extend() {
let mut loc = AbstractMemoryLocation::mock(&[1, 2, 3], 4);
let extension = AbstractMemoryLocation::mock(&[4, 5, 6], 1);
loc.extend(extension, ByteSize::new(4));
assert_eq!(loc, AbstractMemoryLocation::mock(&[1, 2, 3, 4, 5, 6], 1));
}

#[test]
fn test_recursion_depth() {
let loc = AbstractMemoryLocation::mock(&[1, 2, 3], 4);
assert_eq!(loc.recursion_depth(), 2);
let loc = AbstractMemoryLocation::mock(&[1], 4);
assert_eq!(loc.recursion_depth(), 0);
}
}
8 changes: 4 additions & 4 deletions src/cwe_checker_lib/src/abstract_domain/identifier/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use mem_location::AbstractMemoryLocation;
/// Since many program states can be represented by the same abstract state in data-flow analysis,
/// one sometimes needs a way to uniquely identify a variable or a memory object in all of the represented program states.
/// Abstract identifiers achieve this by identifying a *time*, i.e. a specific abstract state,
/// and a *location*, i.e. a recipe for abstracting a concrete value from any concrete state that is represented by the abstract state.
/// and a *location*, i.e. a recipe for computing a concrete value from any concrete state that is represented by the abstract state.
/// The value in question then serves as the identifier.
/// For example, a pointer may uniquely determine the memory object it is pointing to.
/// Or a value may represent the value of a variable at a certain time,
Expand All @@ -25,15 +25,15 @@ pub use mem_location::AbstractMemoryLocation;
/// E.g. it may represent the union of all values at the specific *location* for each time the program point is visited during an execution trace
/// or it may only represent the value at the last time the program point was visited.
///
/// Alternatively one can also add path hints to an identifier to further distinguish points in time in an execution trace.
/// Alternatively, one can also add path hints to an identifier to further distinguish points in time in an execution trace.
/// Path hints are given as a possibly empty array of time identifiers.
/// To prevent infinitely long path hints, each time identifier is only allowed to appear at most once in the array.
/// The specific meaning of the path hints depends upon the use case.
///
/// An abstract identifier is given by a time identifier, a location identifier and a path hints array (containing time identifiers).
///
/// For the location identifier see `AbstractLocation`.
/// The time identifier is given by a `Tid`.
/// For the location identifier see [`AbstractLocation`].
/// The time identifier is given by a [`Tid`].
/// If it is the `Tid` of a basic block, then it describes the point in time *before* execution of the first instruction in the block.
/// If it is the `Tid` of a `Def` or `Jmp`, then it describes the point in time *after* the execution of the `Def` or `Jmp`.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Deref)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl State {
}

/// Load the value whose position is given by derefencing the given ID and then adding an offset.
///
///
/// If the ID is the stack then this function actually loads the value at the given stack position.
/// Otherwise it only generates the abstract location of the value and returns it as a relative value.
fn load_value_via_id_and_offset(
Expand Down
10 changes: 6 additions & 4 deletions src/cwe_checker_lib/src/analysis/function_signature/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,21 +205,23 @@ impl State {
value: DataDomain<BitvectorDomain>,
mem_location: &AbstractMemoryLocation,
) -> DataDomain<BitvectorDomain> {
let mut eval_result = DataDomain::new_empty(mem_location.bytesize());
let target_size = mem_location.bytesize();
let mut eval_result = DataDomain::new_empty(target_size);
for (id, offset) in value.get_relative_values() {
let mut location = id.get_location().clone();
let mut mem_location = mem_location.clone();
match offset.try_to_offset() {
Ok(concrete_offset) => location = location.with_offset_addendum(concrete_offset),
Ok(concrete_offset) => mem_location.add_offset_at_root(concrete_offset),
Err(_) => {
eval_result.set_contains_top_flag();
continue;
}
};
location.extend(mem_location.clone(), self.stack_id.bytesize());
location.extend(mem_location, self.stack_id.bytesize());
if location.recursion_depth() <= POINTER_RECURSION_DEPTH_LIMIT {
eval_result = eval_result.merge(&DataDomain::from_target(
AbstractIdentifier::new(id.get_tid().clone(), location),
Bitvector::zero(mem_location.bytesize().into()).into(),
Bitvector::zero(target_size.into()).into(),
));
} else {
eval_result.set_contains_top_flag();
Expand Down

0 comments on commit 0761220

Please sign in to comment.