1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
use super::{
AbstractDomain, AbstractIdentifier, HasTop, Interval, RegisterDomain, SizedDomain,
SpecializeByConditional, TryToBitvec, TryToInterval,
};
use crate::intermediate_representation::*;
use crate::prelude::*;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Display;
use std::iter::FromIterator;
mod arithmetics;
mod conditional_specialization;
mod trait_impl;
/// An abstract domain representing a set of base values plus offsets or an absolute value (or both).
///
/// The base values are represented as abstract IDs,
/// i.e. they are treated as variables with unknown absolute value, e.g. the returned pointer by malloc.
/// For each base value the offset is given by an abstract domain `T`,
/// which should specialize in representing absolute values (e.g. an interval domain).
/// Note that the domain assumes pointer semantics for these values.
/// That means if one applies operations to the domain that are not used in pointer arithmetics,
/// the abstract ID of the base value might be removed from the domain.
///
/// If the domain also represents absolute values,
/// then the values are given by a single instance of the abstract domain `T`.
///
/// The domain also contains a flag to indicate that it includes `Top` values,
/// i.e. values of fully unknown origin and offset.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct DataDomain<T: RegisterDomain> {
/// The byte size of the represented values.
size: ByteSize,
/// A map from base values to the corresponding offset.
relative_values: BTreeMap<AbstractIdentifier, T>,
/// An absolute value if the domain may represent an absolute value.
absolute_value: Option<T>,
/// An indicator whether the domain also represents values for which both the base and the offset are unknown.
contains_top_values: bool,
}
impl<T: RegisterDomain> DataDomain<T> {
/// Returns true if the domain does not represent any value.
///
/// The meaning of an empty value depends on the usage of the domain.
/// E.g. it may indicate an impossible runtime state of a program in one analysis
/// or simply no value of interest in another analysis.
///
/// An empty value represents the bottom value in the partial order of the domain.
pub fn is_empty(&self) -> bool {
self.relative_values.is_empty()
&& self.absolute_value.is_none()
&& !self.contains_top_values
}
/// Return a new empty value with the given bytesize.
pub fn new_empty(size: ByteSize) -> Self {
DataDomain {
size,
relative_values: BTreeMap::new(),
absolute_value: None,
contains_top_values: false,
}
}
/// For pointer values replace an abstract identifier with another one and add the offset_adjustment to the pointer offset.
/// This is needed to adjust stack pointer on call and return instructions.
pub fn replace_abstract_id(
&mut self,
old_id: &AbstractIdentifier,
new_id: &AbstractIdentifier,
offset_adjustment: &T,
) {
if let Some(old_offset) = self.relative_values.get(old_id) {
let new_offset = old_offset.bin_op(BinOpType::IntAdd, offset_adjustment);
self.relative_values.remove(old_id);
self.relative_values.insert(new_id.clone(), new_offset);
}
}
/// Replace all abstract IDs in self with the corresponding values given by the `replacement_map`.
///
/// For IDs without a replacement value the `contains_top_values` flag will be set.
pub fn replace_all_ids(&mut self, replacement_map: &BTreeMap<AbstractIdentifier, Self>) {
let mut new_self = DataDomain {
size: self.size,
relative_values: BTreeMap::new(),
absolute_value: self.absolute_value.clone(),
contains_top_values: self.contains_top_values,
};
for (id, offset) in self.relative_values.iter() {
if let Some(replacement_value) = replacement_map.get(id) {
new_self = new_self.merge(&(replacement_value.clone() + offset.clone().into()));
} else {
new_self.contains_top_values = true;
}
}
*self = new_self;
}
/// Return an iterator over all referenced abstract IDs.
pub fn referenced_ids(&self) -> impl Iterator<Item = &AbstractIdentifier> {
self.relative_values.keys()
}
/// Return the relative values contained in the domain.
pub fn get_relative_values(&self) -> &BTreeMap<AbstractIdentifier, T> {
&self.relative_values
}
/// Replace the map of relative values with the given one.
pub fn set_relative_values(&mut self, relative_values: BTreeMap<AbstractIdentifier, T>) {
self.relative_values = relative_values;
}
/// Return the absolute value contained in the domain if present
pub fn get_absolute_value(&self) -> Option<&T> {
self.absolute_value.as_ref()
}
/// Replace the absolute value contained in the domain with the given one.
/// A value of `None` means that the domain does not contain an absolute value.
pub fn set_absolute_value(&mut self, value: Option<T>) {
self.absolute_value = value
}
/// Returns `true` if the domain contains `Top` values,
/// i.e. values for which neither a value nor an abstract identifier is known.
///
/// Note that the `DataDomain` itself has no maximal value,
/// i.e. this does not indicate a `Top` value of the abstract domain.
pub fn contains_top(&self) -> bool {
self.contains_top_values
}
/// Indicate that the domain may contain `Top` values
/// in addition to the contained absolute and relative values.
///
/// This does not remove absolute or relative value information from the domain.
pub fn set_contains_top_flag(&mut self) {
self.contains_top_values = true;
}
/// Indicate that the domain does not contain any `Top` values
/// in addition to the contained absolute and relative values.
pub fn unset_contains_top_flag(&mut self) {
self.contains_top_values = false;
}
/// Return a new value representing a variable plus an offset,
/// where the variable is represented by the given abstract ID.
pub fn from_target(id: AbstractIdentifier, offset: T) -> Self {
DataDomain {
size: offset.bytesize(),
relative_values: BTreeMap::from_iter([(id, offset)]),
absolute_value: None,
contains_top_values: false,
}
}
/// Remove all provided IDs from the list of relative values.
pub fn remove_ids(&mut self, ids_to_remove: &BTreeSet<AbstractIdentifier>) {
self.relative_values = self
.relative_values
.iter()
.filter_map(|(id, offset)| {
if ids_to_remove.get(id).is_none() {
Some((id.clone(), offset.clone()))
} else {
None
}
})
.collect();
}
/// Return the contained absolute value
/// only if `self` contains no other (relative or `Top`) values.
pub fn get_if_absolute_value(&self) -> Option<&T> {
if self.relative_values.is_empty() && !self.contains_top_values {
self.absolute_value.as_ref()
} else {
None
}
}
/// Return the contained absolute value
/// if `self` only contains absolute or `Top` values.
fn get_if_absolute_value_or_top(&self) -> Option<&T> {
if self.relative_values.is_empty() {
self.absolute_value.as_ref()
} else {
None
}
}
/// Return the target ID and offset of the contained relative value
/// if `self` contains exactly one relative value and no absolute or `Top` values.
pub fn get_if_unique_target(&self) -> Option<(&AbstractIdentifier, &T)> {
if self.relative_values.len() == 1
&& self.absolute_value.is_none()
&& !self.contains_top_values
{
Some(self.relative_values.iter().next().unwrap())
} else {
None
}
}
}
impl<T: RegisterDomain + Display> DataDomain<T> {
/// Get a more compact json-representation of the data domain.
/// Intended for pretty printing, not useable for serialization/deserialization.
pub fn to_json_compact(&self) -> serde_json::Value {
let mut values = Vec::new();
if !self.relative_values.is_empty() {
let target_iter = self.relative_values.iter().map(|(id, offset)| {
(
format!("{id}"),
serde_json::Value::String(format!("{offset}")),
)
});
let targets = serde_json::Value::Object(target_iter.collect());
let mut obj_map = serde_json::Map::new();
obj_map.insert("Pointer".to_string(), targets);
values.push(serde_json::Value::Object(obj_map));
}
if let Some(absolute_value) = &self.absolute_value {
values.push(serde_json::Value::String(format!(
"Value: {absolute_value}"
)));
}
if self.contains_top_values {
values.push(serde_json::Value::String(format!(
"Top:i{}",
self.bytesize().as_bit_length()
)));
}
match values.len() {
0 => serde_json::Value::String(format!("Empty:{}", self.bytesize())),
1 => values.pop().unwrap(),
_ => serde_json::Value::Array(values),
}
}
}
#[cfg(test)]
mod tests {
use super::super::*;
use super::*;
use crate::{bitvec, variable};
impl<T: RegisterDomain> DataDomain<T> {
/// Return a new domain representing a set of relative values.
/// Note that this function will panic if given an empty set as input.
pub fn mock_from_target_map(targets: BTreeMap<AbstractIdentifier, T>) -> Self {
DataDomain {
size: targets.values().next().unwrap().bytesize(),
relative_values: targets,
absolute_value: None,
contains_top_values: false,
}
}
pub fn insert_relative_value(&mut self, id: AbstractIdentifier, offset: T) {
self.relative_values.insert(id, offset);
}
}
fn bv(value: i64) -> BitvectorDomain {
bitvec!(format!("{}:8", value)).into()
}
fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("time0"),
AbstractLocation::Register(variable!(format!("{}:8", name))),
)
}
#[test]
fn replace_abstract_ids() {
let mut targets = BTreeMap::new();
targets.insert(new_id("Rax"), bv(1));
targets.insert(new_id("Rbx"), bv(2));
targets.insert(new_id("Rcx"), bv(3));
// Test replacing exactly one ID.
let mut data = DataDomain::mock_from_target_map(targets.clone());
data.replace_abstract_id(&new_id("Rbx"), &new_id("replaced_Rbx"), &bv(10));
assert_eq!(data.relative_values.len(), 3);
assert_eq!(*data.relative_values.get(&new_id("Rax")).unwrap(), bv(1));
assert_eq!(
*data.relative_values.get(&new_id("replaced_Rbx")).unwrap(),
bv(12)
);
// Test replacing all IDs using a replacement map.
let mut data = DataDomain::mock_from_target_map(targets);
let replacement_map = BTreeMap::from_iter([
(
new_id("Rax"),
DataDomain::from_target(new_id("replaced_Rax"), bv(0)),
),
(new_id("Rbx"), bv(10).into()),
]);
data.replace_all_ids(&replacement_map);
assert_eq!(data.relative_values.len(), 1);
assert_eq!(
*data.relative_values.get(&new_id("replaced_Rax")).unwrap(),
bv(1)
);
assert!(data.contains_top());
assert_eq!(data.absolute_value.unwrap(), bv(12));
}
#[test]
fn remove_ids() {
let mut targets = BTreeMap::new();
targets.insert(new_id("Rax"), bv(1));
targets.insert(new_id("Rbx"), bv(2));
let mut data = DataDomain::mock_from_target_map(targets);
let mut ids_to_remove = BTreeSet::new();
ids_to_remove.insert(new_id("Rbx"));
ids_to_remove.insert(new_id("Rcx"));
data.remove_ids(&ids_to_remove);
assert_eq!(
data.referenced_ids()
.cloned()
.collect::<Vec<AbstractIdentifier>>(),
vec![new_id("Rax")]
);
data = bv(42).into();
data.remove_ids(&ids_to_remove);
assert_eq!(data, bv(42).into());
}
}