From 07612209305d27dc88b156d05fd5fdf8c14217d5 Mon Sep 17 00:00:00 2001 From: Enkelmann Date: Tue, 21 Nov 2023 09:44:16 +0100 Subject: [PATCH] add unit tests for AbstractLocation, fix bug --- .../abstract_domain/identifier/location.rs | 78 ++++++++++++++++++- .../identifier/mem_location.rs | 78 +++++++++++++++++++ .../src/abstract_domain/identifier/mod.rs | 8 +- .../state/memory_handling.rs | 2 +- .../analysis/function_signature/state/mod.rs | 10 ++- 5 files changed, 166 insertions(+), 10 deletions(-) diff --git a/src/cwe_checker_lib/src/abstract_domain/identifier/location.rs b/src/cwe_checker_lib/src/abstract_domain/identifier/location.rs index d270b4c63..f7f599460 100644 --- a/src/cwe_checker_lib/src/abstract_domain/identifier/location.rs +++ b/src/cwe_checker_lib/src/abstract_domain/identifier/location.rs @@ -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)] @@ -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, + ) -> 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); + } +} diff --git a/src/cwe_checker_lib/src/abstract_domain/identifier/mem_location.rs b/src/cwe_checker_lib/src/abstract_domain/identifier/mem_location.rs index 2d9862a50..19a1fed4e 100644 --- a/src/cwe_checker_lib/src/abstract_domain/identifier/mem_location.rs +++ b/src/cwe_checker_lib/src/abstract_domain/identifier/mem_location.rs @@ -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. @@ -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) -> 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); + } +} diff --git a/src/cwe_checker_lib/src/abstract_domain/identifier/mod.rs b/src/cwe_checker_lib/src/abstract_domain/identifier/mod.rs index 245872a23..38421aaf8 100644 --- a/src/cwe_checker_lib/src/abstract_domain/identifier/mod.rs +++ b/src/cwe_checker_lib/src/abstract_domain/identifier/mod.rs @@ -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, @@ -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)] diff --git a/src/cwe_checker_lib/src/analysis/function_signature/state/memory_handling.rs b/src/cwe_checker_lib/src/analysis/function_signature/state/memory_handling.rs index d1335a826..494ba6419 100644 --- a/src/cwe_checker_lib/src/analysis/function_signature/state/memory_handling.rs +++ b/src/cwe_checker_lib/src/analysis/function_signature/state/memory_handling.rs @@ -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( diff --git a/src/cwe_checker_lib/src/analysis/function_signature/state/mod.rs b/src/cwe_checker_lib/src/analysis/function_signature/state/mod.rs index 57b1d147e..c6db2ada2 100644 --- a/src/cwe_checker_lib/src/analysis/function_signature/state/mod.rs +++ b/src/cwe_checker_lib/src/analysis/function_signature/state/mod.rs @@ -205,21 +205,23 @@ impl State { value: DataDomain, mem_location: &AbstractMemoryLocation, ) -> DataDomain { - 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();