Commit 76936d8c by Enkelmann

Refactor MemRegion for usage with new DataDomain (#211)

parent a20a5379
......@@ -2,10 +2,8 @@ use super::{AbstractDomain, HasTop, SizedDomain};
use crate::intermediate_representation::ByteSize;
use crate::prelude::*;
use apint::{Int, Width};
use derive_more::Deref;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::ops::DerefMut;
use std::sync::Arc;
/// A memory region is an abstract domain representing a continuous region of memory, e.g. the stack frame of a function.
......@@ -20,15 +18,27 @@ use std::sync::Arc;
/// Thus an empty memory region actually represents the *Top* element of its abstract domain.
///
/// To allow cheap cloning of a `MemRegion`, the actual data is wrapped inside an `Arc`.
#[derive(Serialize, Deserialize, Debug, Hash, Clone, PartialEq, Eq, Deref)]
#[deref(forward)]
pub struct MemRegion<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug>(
Arc<MemRegionData<T>>,
);
impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> DerefMut for MemRegion<T> {
fn deref_mut(&mut self) -> &mut MemRegionData<T> {
Arc::make_mut(&mut self.0)
#[derive(Serialize, Deserialize, Debug, Hash, Clone, PartialEq, Eq)]
pub struct MemRegion<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> {
inner: Arc<Inner<T>>,
}
/// The internal data of a memory region. See the description of `MemRegion` for more.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
struct Inner<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> {
address_bytesize: ByteSize,
values: BTreeMap<i64, T>,
}
#[allow(clippy::from_over_into)]
impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> std::convert::Into<MemRegion<T>>
for Inner<T>
{
/// Wrap the contents of a `MemRegion` into an `Arc<..>`.
fn into(self) -> MemRegion<T> {
MemRegion {
inner: Arc::new(self),
}
}
}
......@@ -39,13 +49,13 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> AbstractDomain
if self == other {
self.clone()
} else {
MemRegion(Arc::new(self.0.merge(&other.0)))
self.merge_inner(other)
}
}
/// The *Top* element is represented by an empty memory region.
fn is_top(&self) -> bool {
self.values.is_empty()
self.inner.values.is_empty()
}
}
......@@ -58,58 +68,46 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> HasTop for MemR
impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegion<T> {
/// Create a new, empty memory region.
pub fn new(address_bytesize: ByteSize) -> Self {
MemRegion(Arc::new(MemRegionData::new(address_bytesize)))
}
}
/// The internal data of a memory region. See the description of `MemRegion` for more.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct MemRegionData<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> {
address_bytesize: ByteSize,
values: BTreeMap<i64, T>,
}
impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T> {
/// create a new, empty MemRegion
pub fn new(address_bytesize: ByteSize) -> MemRegionData<T> {
MemRegionData {
pub fn new(address_bytesize: ByteSize) -> MemRegion<T> {
Inner {
address_bytesize,
values: BTreeMap::new(),
}
.into()
}
/// Get the bitsize of pointers for the address space that the memory region belongs to.
/// Get the bytesize of pointers for the address space that the memory region belongs to.
pub fn get_address_bytesize(&self) -> ByteSize {
self.address_bytesize
self.inner.address_bytesize
}
/// Remove all elements intersecting the provided interval.
/// This function does not sanitize its inputs.
fn clear_interval(&mut self, position: i64, size: i64) {
let inner = Arc::make_mut(&mut self.inner);
// If the previous element intersects the range, remove it
if let Some((prev_pos, prev_size)) = self
if let Some((prev_pos, prev_size)) = inner
.values
.range(..position)
.map(|(pos, elem)| (*pos, u64::from(elem.bytesize()) as i64))
.last()
{
if prev_pos + prev_size > position {
self.values.remove(&prev_pos);
inner.values.remove(&prev_pos);
}
}
// remove all other intersecting elements
let intersecting_elements: Vec<i64> = self
let intersecting_elements: Vec<i64> = inner
.values
.range(position..(position + size))
.map(|(pos, _elem)| *pos)
.collect();
for index in intersecting_elements {
self.values.remove(&index);
inner.values.remove(&index);
}
}
/// Clear all values that might be overwritten if one writes a value with byte size `value_size`
/// Clear all values that might be fully or partially overwritten if one writes a value with byte size `value_size`
/// to an offset contained in the interval from `start` to `end` (both bounds included in the interval).
///
/// This represents the effect of writing an arbitrary value (with known byte size)
......@@ -121,7 +119,10 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T
/// Add a value to the memory region.
pub fn add(&mut self, value: T, position: Bitvector) {
assert_eq!(ByteSize::from(position.width()), self.address_bytesize);
assert_eq!(
ByteSize::from(position.width()),
self.inner.address_bytesize
);
let position = Int::from(position).try_to_i64().unwrap();
self.insert_at_byte_index(value, position);
}
......@@ -135,17 +136,22 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T
self.clear_interval(position, size_in_bytes);
if !value.is_top() {
// top()-values do not need to be explicitly saved, as they don't contain any information anyway.
self.values.insert(position, value);
Arc::make_mut(&mut self.inner)
.values
.insert(position, value);
}
}
/// Get the value at the given position.
/// If there is no value at the position or the size of the element is not the same as the provided size, return `T::new_top()`.
pub fn get(&self, position: Bitvector, size_in_bytes: ByteSize) -> T {
assert_eq!(ByteSize::from(position.width()), self.address_bytesize);
assert_eq!(
ByteSize::from(position.width()),
self.inner.address_bytesize
);
let position = Int::from(position).try_to_i64().unwrap();
if let Some(elem) = self.values.get(&position) {
if let Some(elem) = self.inner.values.get(&position) {
if elem.bytesize() == size_in_bytes {
return elem.clone();
}
......@@ -156,15 +162,21 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T
/// Get the value at the given position regardless of the value size.
/// Return `None` if there is no value at that position in the memory region.
pub fn get_unsized(&self, position: Bitvector) -> Option<T> {
assert_eq!(ByteSize::from(position.width()), self.address_bytesize);
assert_eq!(
ByteSize::from(position.width()),
self.inner.address_bytesize
);
let position = Int::from(position).try_to_i64().unwrap();
self.values.get(&position).cloned()
self.inner.values.get(&position).cloned()
}
/// Remove all elements intersecting the provided interval.
pub fn remove(&mut self, position: Bitvector, size_in_bytes: Bitvector) {
assert_eq!(ByteSize::from(position.width()), self.address_bytesize);
assert_eq!(
ByteSize::from(position.width()),
self.inner.address_bytesize
);
let position = Int::from(position).try_to_i64().unwrap();
let size = Int::from(size_in_bytes).try_to_i64().unwrap();
assert!(size > 0);
......@@ -172,60 +184,147 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T
self.clear_interval(position, size);
}
/// If the `MemRegion` contains an element at the given position and with the given size
/// then merge it with a `Top` element.
/// Else clear all values intersecting the range from `position` to `position + size`.
pub fn merge_write_top(&mut self, position: Bitvector, size: ByteSize) {
let position = Int::from(position).try_to_i64().unwrap();
if let Some(prev_value) = self.inner.values.get(&position) {
if prev_value.bytesize() == size {
let merged_value = prev_value.merge(&prev_value.top());
let inner = Arc::make_mut(&mut self.inner);
if merged_value.is_top() {
inner.values.remove(&position);
} else {
inner.values.insert(position, merged_value);
}
return;
}
}
self.clear_interval(position, u64::from(size) as i64)
}
/// Emulate a write operation of a value to an unknown offset in the range between `start` and `end`
/// by merging all values in the range with `Top` (as we don't exactly know which values are overwritten).
pub fn mark_interval_values_as_top(&mut self, start: i64, end: i64, elem_size: ByteSize) {
self.merge_values_intersecting_range_with_top(start, end + u64::from(elem_size) as i64)
}
/// Merge all values intersecting the given range with `Top`.
/// If `Top` is a maximal element of the value abstract domain,
/// this effectively removes all values intersecting the range.
fn merge_values_intersecting_range_with_top(&mut self, start: i64, end: i64) {
let inner = Arc::make_mut(&mut self.inner);
// If the previous element intersects the range, merge it with Top
if let Some((prev_pos, prev_size)) = inner
.values
.range(..start)
.map(|(pos, elem)| (*pos, u64::from(elem.bytesize()) as i64))
.last()
{
if prev_pos + prev_size > start {
let value = inner.values.get(&prev_pos).unwrap();
let merged_value = value.merge(&value.top());
if merged_value.is_top() {
inner.values.remove(&prev_pos);
} else {
inner.values.insert(prev_pos, merged_value);
}
}
}
// Merge all other intersecting elements with Top
let intersecting_elements: Vec<_> = inner
.values
.range(start..end)
.map(|(pos, elem)| (*pos, elem.merge(&elem.top())))
.collect();
for (index, merged_value) in intersecting_elements {
if merged_value.is_top() {
inner.values.remove(&index);
} else {
inner.values.insert(index, merged_value);
}
}
}
/// Merge two memory regions.
///
/// Values at the same position and with the same size get merged via their merge function.
/// Other values are *not* added to the merged region, because they could be anything in at least one of the two regions.
pub fn merge(&self, other: &MemRegionData<T>) -> MemRegionData<T> {
assert_eq!(self.address_bytesize, other.address_bytesize);
/// Values intersecting other values but with not exactly matching position or size are not added to the merged region.
/// Values that do not intersect a value from the other `MemRegion`
/// are merged with `Top` before adding them.
/// This can only add elements to the merged domain if the `Top` value is not a maximal element of the abstract domain.
fn merge_inner(&self, other: &MemRegion<T>) -> MemRegion<T> {
assert_eq!(self.inner.address_bytesize, other.inner.address_bytesize);
let mut zipped: BTreeMap<i64, (Option<&T>, Option<&T>)> = BTreeMap::new();
for (index, elem) in self.inner.values.iter() {
if let Some(other_elem) = other.inner.values.get(index) {
zipped.insert(*index, (Some(elem), Some(other_elem)));
} else {
zipped.insert(*index, (Some(elem), None));
}
}
for (index, other_elem) in other.inner.values.iter() {
if self.inner.values.get(index).is_none() {
zipped.insert(*index, (None, Some(other_elem)));
}
}
let mut merged_values: BTreeMap<i64, T> = BTreeMap::new();
// add all elements contained in both memory regions
for (pos_left, elem_left) in self.values.iter() {
if let Some((_pos_right, elem_right)) = other.values.get_key_value(pos_left) {
if elem_left.bytesize() == elem_right.bytesize() {
let merged_val = elem_left.merge(elem_right);
if !merged_val.is_top() {
// we discard top()-values, as they don't contain information
merged_values.insert(*pos_left, merged_val);
let mut merged_range_end = i64::MIN;
for (index, (left, right)) in zipped.iter() {
let elem_range_end = compute_range_end(*index, *left, *right);
if *index >= merged_range_end {
// The element does not overlap a previous element
if let Some((next_index, _)) = zipped.range((index + 1)..).next() {
if *next_index >= elem_range_end {
// The element does not overlap a subsequent element
if let Some(merged) = merge_or_merge_with_top(*left, *right) {
merged_values.insert(*index, merged);
}
}
} else if let Some(merged) = merge_or_merge_with_top(*left, *right) {
merged_values.insert(*index, merged);
}
}
merged_range_end = std::cmp::max(merged_range_end, elem_range_end);
}
MemRegionData {
address_bytesize: self.address_bytesize,
Inner {
address_bytesize: self.inner.address_bytesize,
values: merged_values,
}
.into()
}
/// Get an iterator over all elements together with their offset into the memory region.
pub fn iter(&self) -> std::collections::btree_map::Iter<i64, T> {
self.values.iter()
self.inner.values.iter()
}
/// Get an iterator over all values in the memory region
pub fn values(&self) -> std::collections::btree_map::Values<i64, T> {
self.values.values()
self.inner.values.values()
}
/// Get the map of all elements including their offset into the memory region.
pub fn entry_map(&self) -> &BTreeMap<i64, T> {
&self.values
&self.inner.values
}
/// Get an iterator over all values in the memory region for in-place manipulation.
/// Note that one can changes values to *Top* using the iterator.
/// These values should be removed from the memory region using `clear_top_values()`.
pub fn values_mut(&mut self) -> std::collections::btree_map::ValuesMut<i64, T> {
self.values.values_mut()
Arc::make_mut(&mut self.inner).values.values_mut()
}
/// Remove all values representing the *Top* element from the internal value store,
/// as these should not be saved in the internal representation.
pub fn clear_top_values(&mut self) {
let indices_to_remove: Vec<i64> = self
.inner
.values
.iter()
.filter_map(
......@@ -238,165 +337,57 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T
},
)
.collect();
let inner = Arc::make_mut(&mut self.inner);
for index in indices_to_remove {
self.values.remove(&index);
inner.values.remove(&index);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::abstract_domain::RegisterDomain;
use crate::intermediate_representation::*;
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, PartialOrd, Ord)]
struct MockDomain(i64, ByteSize);
impl AbstractDomain for MockDomain {
fn merge(&self, other: &Self) -> Self {
assert_eq!(self.1, other.1);
if self == other {
self.clone()
} else {
self.top()
/// Helper function that either merges `left` and `right`
/// or, if one of them is `None`, merges the other with a `Top` value.
/// Furthermore, if `left` and `right` have different byte sizes
/// or the merge operation returns a `Top` value, then `None` is returned.
/// Panics if both `left` and `right` are `None`.
fn merge_or_merge_with_top<T: AbstractDomain + SizedDomain>(
left: Option<&T>,
right: Option<&T>,
) -> Option<T> {
match (left, right) {
(Some(elem_left), Some(elem_right)) => {
if elem_left.bytesize() == elem_right.bytesize() {
let merged = elem_left.merge(elem_right);
if !merged.is_top() {
return Some(merged);
}
}
None
}
fn is_top(&self) -> bool {
self == &self.top()
}
}
impl SizedDomain for MockDomain {
fn bytesize(&self) -> ByteSize {
self.1
}
fn new_top(bytesize: ByteSize) -> MockDomain {
MockDomain(0, bytesize)
}
}
impl HasTop for MockDomain {
fn top(&self) -> Self {
Self::new_top(self.1)
}
}
impl RegisterDomain for MockDomain {
fn bin_op(&self, _op: BinOpType, _rhs: &Self) -> Self {
Self::new_top(self.1)
}
fn un_op(&self, _op: UnOpType) -> Self {
Self::new_top(self.1)
}
fn cast(&self, _kind: CastOpType, width: ByteSize) -> Self {
Self::new_top(width)
}
fn subpiece(&self, _low_byte: ByteSize, size: ByteSize) -> Self {
Self::new_top(size)
(Some(elem), None) | (None, Some(elem)) => {
let merged = elem.merge(&T::new_top(elem.bytesize()));
if !merged.is_top() {
Some(merged)
} else {
None
}
}
(None, None) => panic!(),
}
}
fn mock(val: i64, bytesize: impl Into<ByteSize>) -> MockDomain {
MockDomain(val, bytesize.into())
}
fn bv(val: i64) -> Bitvector {
Bitvector::from_i64(val)
}
#[test]
fn mem_region() {
let mut region: MemRegion<MockDomain> = MemRegion::new(ByteSize::from(8u64));
region.add(mock(5, 3u64), bv(5));
assert_eq!(region.get(bv(5), ByteSize::from(3u64)), mock(5, 3u64));
region.add(mock(7, 2u64), bv(8));
assert_eq!(region.get(bv(8), ByteSize::from(2u64)), mock(7, 2u64));
assert_eq!(region.get(bv(5), ByteSize::from(3u64)), mock(5, 3u64));
assert_eq!(
region.get(bv(5), ByteSize::from(2u64)),
MockDomain::new_top(ByteSize::new(2))
);
region.add(mock(9, 2u64), bv(6));
assert_eq!(region.get(bv(6), ByteSize::from(2u64)), mock(9, 2u64));
assert_eq!(
region.get(bv(5), ByteSize::from(3u64)),
MockDomain::new_top(ByteSize::new(3))
);
assert_eq!(region.get(bv(8), ByteSize::from(2u64)), mock(7, 2u64));
region.add(mock(9, 11u64), bv(-3));
assert_eq!(region.get(bv(-3), ByteSize::from(11u64)), mock(9, 11u64));
assert_eq!(
region.get(bv(6), ByteSize::from(2u64)),
MockDomain::new_top(ByteSize::new(2))
);
assert_eq!(region.get(bv(8), ByteSize::from(2u64)), mock(7, 2u64));
let mut other_region = MemRegion::new(ByteSize::from(8u64));
other_region.add(mock(7, 2u64), bv(8));
assert!(region != other_region);
let merged_region = region.merge(&other_region);
assert_eq!(
merged_region.get(bv(8), ByteSize::from(2u64)),
mock(7, 2u64)
);
assert_eq!(
merged_region.get(bv(-3), ByteSize::from(11u64)),
MockDomain::new_top(ByteSize::from(11u64))
);
other_region.add(mock(9, 11u64), bv(-3));
assert_eq!(region, other_region);
}
#[test]
fn do_not_save_top_elements() {
let mut region: MemRegionData<MockDomain> = MemRegionData::new(ByteSize::from(8u64));
region.add(MockDomain::new_top(ByteSize::from(4u64)), bv(5));
assert_eq!(region.values.len(), 0);
let mut other_region: MemRegionData<MockDomain> = MemRegionData::new(ByteSize::from(8u64));
region.add(mock(5, 4u64), bv(5));
other_region.add(mock(7, 4u64), bv(5));
let merged_region = region.merge(&other_region);
assert_eq!(region.values.len(), 1);
assert_eq!(other_region.values.len(), 1);
assert_eq!(merged_region.values.len(), 0);
}
#[test]
fn value_removals() {
let mut region: MemRegionData<MockDomain> = MemRegionData::new(ByteSize::from(8u64));
region.add(mock(1, 8u64), bv(0));
region.add(mock(2, 8u64), bv(8));
region.add(mock(3, 8u64), bv(16));
region.add(mock(4, 8u64), bv(24));
region.add(mock(5, 8u64), bv(32));
assert_eq!(region.values.len(), 5);
region.remove(bv(2), bv(3));
assert_eq!(region.values.len(), 4);
region.remove(bv(7), bv(1));
assert_eq!(region.values.len(), 4);
region.remove(bv(7), bv(2));
assert_eq!(region.values.len(), 3);
region.clear_interval(15, 1);
assert_eq!(region.values.len(), 3);
region.clear_interval(15, 3);
assert_eq!(region.values.len(), 2);
for val in region.values_mut() {
if *val == mock(5, 8u64) {
*val = mock(0, 8u64); // This is a *Top* element
}
/// Helper function computing `index` plus the maximum of the bytesizes of `left` and `right`.
/// Panics if both `left` and `right` are `None`.
fn compute_range_end<T: SizedDomain>(index: i64, left: Option<&T>, right: Option<&T>) -> i64 {
match (left, right) {
(Some(left_elem), Some(right_elem)) => {
let left_size = u64::from(left_elem.bytesize()) as i64;
let right_size = u64::from(right_elem.bytesize()) as i64;
index + std::cmp::max(left_size, right_size)
}
region.clear_top_values();
assert_eq!(region.values.len(), 1);
assert_eq!(region.get(bv(24), ByteSize::from(8u64)), mock(4, 8u64));
(Some(elem), None) | (None, Some(elem)) => index + u64::from(elem.bytesize()) as i64,
(None, None) => panic!(),
}
}
#[cfg(test)]
mod tests;
use super::*;
use crate::abstract_domain::DataDomain;
use crate::abstract_domain::IntervalDomain;
use crate::abstract_domain::RegisterDomain;
use crate::intermediate_representation::*;
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, PartialOrd, Ord)]
struct MockDomain(i64, ByteSize);
impl AbstractDomain for MockDomain {
fn merge(&self, other: &Self) -> Self {
assert_eq!(self.1, other.1);
if self == other {
self.clone()
} else {
self.top()
}
}
fn is_top(&self) -> bool {
self == &self.top()
}
}
impl SizedDomain for MockDomain {
fn bytesize(&self) -> ByteSize {
self.1
}
fn new_top(bytesize: ByteSize) -> MockDomain {
MockDomain(0, bytesize)
}
}
impl HasTop for MockDomain {
fn top(&self) -> Self {
Self::new_top(self.1)
}
}
impl RegisterDomain for MockDomain {
fn bin_op(&self, _op: BinOpType, _rhs: &Self) -> Self {
Self::new_top(self.1)
}
fn un_op(&self, _op: UnOpType) -> Self {
Self::new_top(self.1)
}
fn cast(&self, _kind: CastOpType, width: ByteSize) -> Self {
Self::new_top(width)
}
fn subpiece(&self, _low_byte: ByteSize, size: ByteSize) -> Self {
Self::new_top(size)
}
}
fn mock(val: i64, bytesize: impl Into<ByteSize>) -> MockDomain {
MockDomain(val, bytesize.into())
}
fn bv(val: i64) -> Bitvector {
Bitvector::from_i64(val)
}
#[test]
fn mem_region() {
let mut region: MemRegion<MockDomain> = MemRegion::new(ByteSize::from(8u64));
region.add(mock(5, 3u64), bv(5));
assert_eq!(region.get(bv(5), ByteSize::from(3u64)), mock(5, 3u64));
region.add(mock(7, 2u64), bv(8));
assert_eq!(region.get(bv(8), ByteSize::from(2u64)), mock(7, 2u64));
assert_eq!(region.get(bv(5), ByteSize::from(3u64)), mock(5, 3u64));
assert_eq!(
region.get(bv(5), ByteSize::from(2u64)),
MockDomain::new_top(ByteSize::new(2))
);
region.add(mock(9, 2u64), bv(6));
assert_eq!(region.get(bv(6), ByteSize::from(2u64)), mock(9, 2u64));
assert_eq!(
region.get(bv(5), ByteSize::from(3u64)),
MockDomain::new_top(ByteSize::new(3))
);
assert_eq!(region.get(bv(8), ByteSize::from(2u64)), mock(7, 2u64));
region.add(mock(9, 11u64), bv(-3));
assert_eq!(region.get(bv(-3), ByteSize::from(11u64)), mock(9, 11u64));
assert_eq!(
region.get(bv(6), ByteSize::from(2u64)),
MockDomain::new_top(ByteSize::new(2))
);
assert_eq!(region.get(bv(8), ByteSize::from(2u64)), mock(7, 2u64));
let mut other_region = MemRegion::new(ByteSize::from(8u64));
other_region.add(mock(7, 2u64), bv(8));
assert!(region != other_region);
let merged_region = region.merge(&other_region);
assert_eq!(
merged_region.get(bv(8), ByteSize::from(2u64)),
mock(7, 2u64)
);
assert_eq!(
merged_region.get(bv(-3), ByteSize::from(11u64)),
MockDomain::new_top(ByteSize::from(11u64))
);
other_region.add(mock(9, 11u64), bv(-3));
assert_eq!(region, other_region);
}
#[test]
fn merge_test() {
let data: fn(u64) -> DataDomain<IntervalDomain> =
|val| DataDomain::from(Bitvector::from_u64(val));
let mut region: MemRegion<DataDomain<IntervalDomain>> = MemRegion::new(ByteSize::new(8));
region.add(data(0), Bitvector::from_u64(0));
region.add(data(8), Bitvector::from_u64(8));
region.add(data(22), Bitvector::from_u64(32));
region.add(data(42), Bitvector::from_u64(50));
region.add(data(70), Bitvector::from_u64(70));
let mut other_region: MemRegion<DataDomain<IntervalDomain>> = MemRegion::new(ByteSize::new(8));
other_region.add(data(1), Bitvector::from_u64(0));
other_region.add(data(15), Bitvector::from_u64(15));
other_region.add(data(26), Bitvector::from_u64(25));
other_region.add(data(42), Bitvector::from_u64(58));
other_region.add(Bitvector::from_u8(70).into(), Bitvector::from_u64(70));
let merged_region = region.merge(&&other_region);
// Merge elements at target address.
assert_eq!(
merged_region.get_unsized(Bitvector::from_u64(0)),
Some(IntervalDomain::mock(0, 1).into())
);
// Overlapping elements are not added to the merged memory region.
assert_eq!(merged_region.get_unsized(Bitvector::from_u64(8)), None);
assert_eq!(merged_region.get_unsized(Bitvector::from_u64(15)), None);
assert_eq!(merged_region.get_unsized(Bitvector::from_u64(25)), None);
assert_eq!(merged_region.get_unsized(Bitvector::from_u64(32)), None);
// Elements only contained in one region are merged with `Top`.
let mut elem_plus_top: DataDomain<IntervalDomain> = Bitvector::from_u64(42).into();
elem_plus_top.set_contains_top_flag();
assert!(!elem_plus_top.is_top());
assert_eq!(
merged_region.get_unsized(Bitvector::from_u64(50)),
Some(elem_plus_top.clone())
);
assert_eq!(
merged_region.get_unsized(Bitvector::from_u64(58)),
Some(elem_plus_top)
);
// Elements with differing bytesizes are not added to the merged domain.
assert_eq!(merged_region.get_unsized(Bitvector::from_u64(70)), None);
// Check that no other unexpected elements are contained in the merged region.
assert_eq!(merged_region.values().len(), 3);
}
#[test]
fn do_not_save_top_elements() {
let mut region: MemRegion<MockDomain> = MemRegion::new(ByteSize::from(8u64));
region.add(MockDomain::new_top(ByteSize::from(4u64)), bv(5));
assert_eq!(region.values().len(), 0);
let mut other_region: MemRegion<MockDomain> = MemRegion::new(ByteSize::from(8u64));
region.add(mock(5, 4u64), bv(5));
other_region.add(mock(7, 4u64), bv(5));
let merged_region = region.merge(&other_region);
assert_eq!(region.values().len(), 1);
assert_eq!(other_region.values().len(), 1);
assert_eq!(merged_region.values().len(), 0);
}
#[test]
fn value_removals() {
let mut region: MemRegion<MockDomain> = MemRegion::new(ByteSize::from(8u64));
region.add(mock(1, 8u64), bv(0));
region.add(mock(2, 8u64), bv(8));
region.add(mock(3, 8u64), bv(16));
region.add(mock(4, 8u64), bv(24));
region.add(mock(5, 8u64), bv(32));
assert_eq!(region.values().len(), 5);
region.remove(bv(2), bv(3));
assert_eq!(region.values().len(), 4);
region.remove(bv(7), bv(1));
assert_eq!(region.values().len(), 4);
region.remove(bv(7), bv(2));
assert_eq!(region.values().len(), 3);
region.clear_interval(15, 1);
assert_eq!(region.values().len(), 3);
region.clear_interval(15, 3);
assert_eq!(region.values().len(), 2);
for val in region.values_mut() {
if *val == mock(5, 8u64) {
*val = mock(0, 8u64); // This is a *Top* element
}
}
region.clear_top_values();
assert_eq!(region.values().len(), 1);
assert_eq!(region.get(bv(24), ByteSize::from(8u64)), mock(4, 8u64));
}
#[test]
fn merge_writes_with_top() {
let data: DataDomain<IntervalDomain> = DataDomain::from(Bitvector::from_u64(0));
let mut data_with_top = data.clone();
data_with_top.set_contains_top_flag();
let mut region: MemRegion<DataDomain<IntervalDomain>> = MemRegion::new(ByteSize::new(8));
// Test `merge_write_top` method.
region.add(data.clone(), bv(0));
region.merge_write_top(bv(0), ByteSize::new(8));
assert_eq!(region.get_unsized(bv(0)), Some(data_with_top.clone()));
// `merge_write_top` removes intersecting values if position or size do not match.
region.add(data.clone(), bv(8));
region.merge_write_top(bv(5), ByteSize::new(8));
assert!(region.inner.values.is_empty());
// Test `mark_interval_values_as_top` method.
region.add(data.clone(), bv(0));
region.add(data.clone(), bv(8));
region.add(data.clone(), bv(16));
region.mark_interval_values_as_top(9, 16, ByteSize::new(1));
assert_eq!(region.get_unsized(bv(0)), Some(data));
assert_eq!(region.get_unsized(bv(8)), Some(data_with_top.clone()));
assert_eq!(region.get_unsized(bv(16)), Some(data_with_top.clone()));
}
......@@ -470,9 +470,14 @@ mod tests {
other_object.set_value(new_data(0), &bv(0)).unwrap();
let merged_object = object.merge(&other_object);
assert_eq!(
merged_object.get_value(Bitvector::from_i64(-12), ByteSize::new(8)),
Data::new_top(ByteSize::new(8))
merged_object
.get_value(Bitvector::from_i64(-12), ByteSize::new(8))
.get_absolute_value(),
Some(&IntervalDomain::mock(4, 23).with_stride(19).into())
);
assert!(merged_object
.get_value(Bitvector::from_i64(-12), ByteSize::new(8))
.contains_top());
assert_eq!(
merged_object.get_value(Bitvector::from_i64(0), ByteSize::new(8)),
new_data(0)
......
......@@ -491,10 +491,10 @@ mod tests {
let mut merged = obj_list.merge(&other_obj_list);
assert_eq!(merged.get_value(&pointer, ByteSize::new(8)), bv(42).into());
assert_eq!(
merged.get_value(&second_pointer, ByteSize::new(8)),
Data::new_top(ByteSize::new(8))
);
assert!(merged
.get_value(&second_pointer, ByteSize::new(8))
.contains_top());
assert_eq!(
merged.get_value(&heap_pointer, ByteSize::new(8)),
bv(3).into()
......
......@@ -61,12 +61,10 @@ fn state() {
let merged_state = state.merge(&other_state);
assert_eq!(merged_state.register[&register("RAX")], bv(42).into());
assert_eq!(merged_state.register.get(&register("RBX")), None);
assert_eq!(
merged_state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap(),
Data::new_top(ByteSize::new(8))
);
assert!(merged_state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap()
.contains_top());
// Test pointer adjustment on reads
state.memory.add_abstract_object(
......
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