Unverified Commit a8574343 by van den Bosch Committed by GitHub

Macros and test changes (#380)

parent 2c766f5a
......@@ -79,12 +79,12 @@ fn main() -> Result<(), Error> {
/// Return `Ok(file_path)` only if `file_path` points to an existing file.
fn check_file_existence(file_path: &str) -> Result<String, String> {
if std::fs::metadata(file_path)
.map_err(|err| format!("{}", err))?
.map_err(|err| format!("{err}"))?
.is_file()
{
Ok(file_path.to_string())
} else {
Err(format!("{} is not a file.", file_path))
Err(format!("{file_path} is not a file."))
}
}
......@@ -95,7 +95,7 @@ fn run_with_ghidra(args: &CmdlineArgs) -> Result<(), Error> {
// Only print the module versions and then quit.
println!("[cwe_checker] module_versions:");
for module in modules.iter() {
println!("{}", module);
println!("{module}");
}
return Ok(());
}
......@@ -235,7 +235,7 @@ fn filter_modules_for_partial_run(
} else if module_name.is_empty() {
None
} else {
panic!("Error: {} is not a valid module name.", module_name)
panic!("Error: {module_name} is not a valid module name.")
}
})
.collect();
......
......@@ -180,11 +180,12 @@ impl std::fmt::Display for BitvectorDomain {
}
#[cfg(test)]
mod tests {
pub mod tests {
use super::*;
use crate::bitvec;
fn bv(value: i64) -> BitvectorDomain {
BitvectorDomain::Value(Bitvector::from_i64(value))
bitvec!(format!("{}:8", value)).into()
}
#[test]
......@@ -211,11 +212,11 @@ mod tests {
assert_eq!(
sixteen.bin_op(IntEqual, &bv(16)),
BitvectorDomain::Value(Bitvector::from_u8(true as u8))
BitvectorDomain::Value(bitvec!(format!("{}:1", true as u8)))
);
assert_eq!(
sixteen.bin_op(IntNotEqual, &bv(16)),
BitvectorDomain::Value(Bitvector::from_u8(false as u8))
BitvectorDomain::Value(bitvec!(format!("{}:1", false as u8)))
);
assert_eq!(sixteen.un_op(Int2Comp), bv(-16));
......@@ -223,27 +224,26 @@ mod tests {
assert_eq!(
sixteen.subpiece(ByteSize::new(0), ByteSize::new(4)),
BitvectorDomain::Value(Bitvector::from_i32(16))
BitvectorDomain::Value(bitvec!("16:4"))
);
assert_eq!(
sixteen.subpiece(ByteSize::new(4), ByteSize::new(4)),
BitvectorDomain::Value(Bitvector::from_i32(0))
BitvectorDomain::Value(bitvec!("0:4"))
);
assert_eq!(
BitvectorDomain::Value(Bitvector::from_i32(2)),
BitvectorDomain::Value(Bitvector::from_i64(2 << 32))
.subpiece(ByteSize::new(4), ByteSize::new(4))
BitvectorDomain::Value(bitvec!("2:4")),
bv(2 << 32).subpiece(ByteSize::new(4), ByteSize::new(4))
);
assert_eq!(
BitvectorDomain::Value(Bitvector::from_i32(-1))
.bin_op(Piece, &BitvectorDomain::Value(Bitvector::from_i32(-1))),
BitvectorDomain::Value(bitvec!("-1:4"))
.bin_op(Piece, &BitvectorDomain::Value(bitvec!("-1:4"))),
bv(-1)
);
assert_eq!(
BitvectorDomain::Value(Bitvector::from_i32(-1)).cast(PopCount, ByteSize::new(8)),
BitvectorDomain::Value(bitvec!("-1:4")).cast(PopCount, ByteSize::new(8)),
bv(32)
)
}
......@@ -262,26 +262,14 @@ mod tests {
#[test]
fn arshift() {
use BinOpType::IntSRight;
let positive_x = BitvectorDomain::Value(Bitvector::from_i64(31));
let negative_x = BitvectorDomain::Value(Bitvector::from_i64(-31));
let shift_3 = BitvectorDomain::Value(Bitvector::from_u8(3));
let shift_70 = BitvectorDomain::Value(Bitvector::from_u8(70));
assert_eq!(
positive_x.bin_op(IntSRight, &shift_3),
BitvectorDomain::Value(Bitvector::from_i64(3))
);
assert_eq!(
positive_x.bin_op(IntSRight, &shift_70),
BitvectorDomain::Value(Bitvector::from_i64(0))
);
assert_eq!(
negative_x.bin_op(IntSRight, &shift_3),
BitvectorDomain::Value(Bitvector::from_i64(-4))
);
assert_eq!(
negative_x.bin_op(IntSRight, &shift_70),
BitvectorDomain::Value(Bitvector::from_i64(-1))
);
let positive_x = bv(31);
let negative_x = bv(-31);
let shift_3 = BitvectorDomain::Value(bitvec!("3:1"));
let shift_70 = BitvectorDomain::Value(bitvec!("70:1"));
assert_eq!(positive_x.bin_op(IntSRight, &shift_3), bv(3));
assert_eq!(positive_x.bin_op(IntSRight, &shift_70), bv(0));
assert_eq!(negative_x.bin_op(IntSRight, &shift_3), bv(-4));
assert_eq!(negative_x.bin_op(IntSRight, &shift_70), bv(-1));
}
#[test]
......
......@@ -197,7 +197,7 @@ impl fmt::Display for BricksDomain {
BricksDomain::Value(brick_domains) => {
write!(f, "Bricks: ")?;
for brick_domain in brick_domains.iter() {
write!(f, "{} ", brick_domain)?;
write!(f, "{brick_domain} ")?;
}
Ok(())
......
......@@ -169,7 +169,7 @@ impl fmt::Display for CharacterInclusionDomain {
match self {
CharacterInclusionDomain::Top => write!(f, "Top"),
CharacterInclusionDomain::Value((certain_set, possible_set)) => {
write!(f, "Certain: {}, Possible: {}", certain_set, possible_set)
write!(f, "Certain: {certain_set}, Possible: {possible_set}")
}
}
}
......@@ -243,7 +243,7 @@ impl fmt::Display for CharacterSet {
match self {
CharacterSet::Top => write!(f, "Top"),
CharacterSet::Value(char_set) => {
write!(f, "{:?}", char_set)
write!(f, "{char_set:?}")
}
}
}
......
......@@ -215,8 +215,8 @@ impl<T: RegisterDomain + Display> DataDomain<T> {
if !self.relative_values.is_empty() {
let target_iter = self.relative_values.iter().map(|(id, offset)| {
(
format!("{}", id),
serde_json::Value::String(format!("{}", offset)),
format!("{id}"),
serde_json::Value::String(format!("{offset}")),
)
});
let targets = serde_json::Value::Object(target_iter.collect());
......@@ -226,8 +226,7 @@ impl<T: RegisterDomain + Display> DataDomain<T> {
}
if let Some(absolute_value) = &self.absolute_value {
values.push(serde_json::Value::String(format!(
"Value: {}",
absolute_value
"Value: {absolute_value}"
)));
}
if self.contains_top_values {
......@@ -248,6 +247,7 @@ impl<T: RegisterDomain + Display> DataDomain<T> {
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.
......@@ -267,13 +267,13 @@ mod tests {
}
fn bv(value: i64) -> BitvectorDomain {
BitvectorDomain::Value(Bitvector::from_i64(value))
bitvec!(format!("{}:8", value)).into()
}
fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("time0"),
AbstractLocation::Register(Variable::mock(name, ByteSize::new(8))),
AbstractLocation::Register(variable!(format!("{}:8", name))),
)
}
......
......@@ -244,18 +244,18 @@ impl<T: RegisterDomain> std::ops::Sub for DataDomain<T> {
mod tests {
use super::super::*;
use super::*;
use crate::abstract_domain::*;
use crate::{abstract_domain::*, bitvec, variable};
type Data = DataDomain<BitvectorDomain>;
fn bv(value: i64) -> BitvectorDomain {
BitvectorDomain::Value(Bitvector::from_i64(value))
BitvectorDomain::Value(bitvec!(format!("{}:8", value)))
}
fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("time0"),
AbstractLocation::Register(Variable::mock(name, ByteSize::new(8))),
AbstractLocation::Register(variable!(format!("{}:8", name))),
)
}
......@@ -313,7 +313,7 @@ mod tests {
assert_eq!(
three.subpiece(ByteSize::new(0), ByteSize::new(4)),
BitvectorDomain::Value(Bitvector::from_i32(3)).into()
BitvectorDomain::Value(bitvec!("3:4")).into()
);
assert_eq!(
......@@ -321,8 +321,8 @@ mod tests {
ByteSize::new(16)
);
let one: Data = BitvectorDomain::Value(Bitvector::from_i32(1)).into();
let two: Data = BitvectorDomain::Value(Bitvector::from_i32(2)).into();
let one: Data = BitvectorDomain::Value(bitvec!("1:4")).into();
let two: Data = BitvectorDomain::Value(bitvec!("2:4")).into();
let concat = new_value((1 << 32) + 2);
assert_eq!(one.bin_op(Piece, &two), concat);
}
......
......@@ -118,12 +118,12 @@ impl<T: SpecializeByConditional + RegisterDomain> SpecializeByConditional for Da
#[cfg(test)]
mod tests {
use super::*;
use crate::abstract_domain::*;
use crate::{abstract_domain::*, variable};
fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("time0"),
AbstractLocation::Register(Variable::mock(name, ByteSize::new(8))),
AbstractLocation::Register(variable!(format!("{}:8", name))),
)
}
......
......@@ -109,18 +109,18 @@ impl<T: RegisterDomain + TryToInterval> TryToInterval for DataDomain<T> {
mod tests {
use super::super::*;
use super::*;
use crate::abstract_domain::*;
use crate::{abstract_domain::*, bitvec, variable};
type Data = DataDomain<BitvectorDomain>;
fn bv(value: i64) -> BitvectorDomain {
BitvectorDomain::Value(Bitvector::from_i64(value))
BitvectorDomain::Value(bitvec!(format!("{}:8", value)))
}
fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("time0"),
AbstractLocation::Register(Variable::mock(name, ByteSize::new(8))),
AbstractLocation::Register(variable!(format!("{}:8", name))),
)
}
......
......@@ -217,23 +217,22 @@ impl<K: Ord + Clone, V: AbstractDomain + HasTop> MapMergeStrategy<K, V> for Merg
#[cfg(test)]
mod tests {
use super::*;
use crate::bitvec;
use std::collections::BTreeMap;
use crate::intermediate_representation::Bitvector;
#[test]
fn test_merge_strategies() {
let map_left: BTreeMap<u64, DataDomain<BitvectorDomain>> = [
(0u64, Bitvector::from_i64(0).into()),
(1u64, Bitvector::from_i64(0).into()),
(0u64, bitvec!("0:8").into()),
(1u64, bitvec!("0:8").into()),
(5u64, DataDomain::new_top(ByteSize::new(8))),
]
.iter()
.cloned()
.collect();
let map_right: BTreeMap<u64, DataDomain<BitvectorDomain>> = [
(1u64, Bitvector::from_i64(1).into()),
(2u64, Bitvector::from_i64(1).into()),
(1u64, bitvec!("1:8").into()),
(2u64, bitvec!("1:8").into()),
(5u64, DataDomain::new_top(ByteSize::new(8))),
]
.iter()
......@@ -244,12 +243,12 @@ mod tests {
let domain_map_left: DomainMap<_, _, UnionMergeStrategy> = map_left.clone().into();
let domain_map_right: DomainMap<_, _, UnionMergeStrategy> = map_right.clone().into();
let merged_map = domain_map_left.merge(&domain_map_right);
assert_eq!(merged_map.get(&0), Some(&Bitvector::from_i64(0).into()));
assert_eq!(merged_map.get(&0), Some(&bitvec!("0:8").into()));
assert_eq!(
merged_map.get(&1),
Some(&BitvectorDomain::new_top(ByteSize::new(8)).into())
);
assert_eq!(merged_map.get(&2), Some(&Bitvector::from_i64(1).into()));
assert_eq!(merged_map.get(&2), Some(&bitvec!("1:8").into()));
assert_eq!(
merged_map.get(&5),
Some(&DataDomain::new_top(ByteSize::new(8)).into())
......@@ -273,7 +272,7 @@ mod tests {
let merged_map = domain_map_left.merge(&domain_map_right);
assert_eq!(
merged_map.get(&0).unwrap().get_absolute_value(),
Some(&Bitvector::from_i64(0).into())
Some(&bitvec!("0:8").into())
);
assert!(merged_map.get(&0).unwrap().contains_top());
assert_eq!(
......@@ -282,7 +281,7 @@ mod tests {
);
assert_eq!(
merged_map.get(&2).unwrap().get_absolute_value(),
Some(&Bitvector::from_i64(1).into())
Some(&bitvec!("1:8").into())
);
assert!(merged_map.get(&2).unwrap().contains_top());
assert_eq!(merged_map.get(&5), None);
......
......@@ -144,7 +144,7 @@ impl std::fmt::Display for AbstractIdentifier {
} else {
write!(formatter, "{}(", self.0.time)?;
for hint in &self.0.path_hints {
write!(formatter, "->{}", hint)?;
write!(formatter, "->{hint}",)?;
}
write!(formatter, ") @ {}", self.0.location)
}
......@@ -185,10 +185,10 @@ impl std::fmt::Display for AbstractLocation {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Register(var) => write!(formatter, "{}", var.name),
Self::GlobalAddress { address, size: _ } => write!(formatter, "0x{:x}", address),
Self::GlobalAddress { address, size: _ } => write!(formatter, "0x{address:x}"),
Self::Pointer(var, location) => write!(formatter, "{}->{}", var.name, location),
Self::GlobalPointer(address, location) => {
write!(formatter, "0x{:x}->{}", address, location)
write!(formatter, "0x{address:x}->{location}")
}
}
}
......@@ -275,8 +275,8 @@ impl AbstractMemoryLocation {
impl std::fmt::Display for AbstractMemoryLocation {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Location { offset, .. } => write!(formatter, "({})", offset),
Self::Pointer { offset, target } => write!(formatter, "({})->{}", offset, target),
Self::Location { offset, .. } => write!(formatter, "({offset})"),
Self::Pointer { offset, target } => write!(formatter, "({offset})->{target}"),
}
}
}
......@@ -284,6 +284,7 @@ impl std::fmt::Display for AbstractMemoryLocation {
#[cfg(test)]
pub mod tests {
use super::*;
use crate::variable;
impl AbstractIdentifier {
/// Mock an abstract identifier with the given TID name and pointing to the value in the given register name.
......@@ -294,7 +295,12 @@ pub mod tests {
) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new(tid.to_string()),
AbstractLocation::from_var(&Variable::mock(register, size_in_bytes)).unwrap(),
AbstractLocation::from_var(&variable!(format!(
"{}:{}",
register.to_string(),
size_in_bytes
)))
.unwrap(),
)
}
}
......@@ -311,7 +317,7 @@ pub mod tests {
// Test uniqueness of TIDs in path hint array.
let id = AbstractIdentifier::new(
Tid::new("time_id"),
AbstractLocation::from_var(&Variable::mock("var", 8)).unwrap(),
AbstractLocation::from_var(&variable!("var:8")).unwrap(),
);
let id = id.with_path_hint(Tid::new("first_hint")).unwrap();
let id = id.with_path_hint(Tid::new("second_hint")).unwrap();
......@@ -321,7 +327,7 @@ pub mod tests {
#[test]
fn test_bytesize() {
let location =
AbstractLocation::from_stack_position(&Variable::mock("RSP", 8), 10, ByteSize::new(4));
AbstractLocation::from_stack_position(&variable!("RSP:8"), 10, ByteSize::new(4));
let id = AbstractIdentifier::new(Tid::new("id"), location);
assert_eq!(id.bytesize(), ByteSize::new(4));
}
......
use crate::bitvec;
use super::*;
impl Interval {
pub fn mock(start: i64, end: i64) -> Interval {
Interval::new(Bitvector::from_i64(start), Bitvector::from_i64(end), 1)
Interval::new(
bitvec!(format!("{}:8", start)),
bitvec!(format!("{}:8", end)),
1,
)
}
pub fn mock_i8(start: i8, end: i8) -> Interval {
Interval::new(Bitvector::from_i8(start), Bitvector::from_i8(end), 1)
Interval::new(
bitvec!(format!("{}:1", start)),
bitvec!(format!("{}:1", end)),
1,
)
}
pub fn with_stride(mut self, stride: u64) -> Interval {
......@@ -92,7 +102,7 @@ fn subpiece_higher() {
let val = Interval::mock(3, 21).with_stride(6);
assert_eq!(
val.subpiece_higher(ByteSize::new(7)),
Interval::from(Bitvector::from_i8(0))
Interval::from(bitvec!("0:1"))
)
}
......@@ -117,8 +127,8 @@ fn piece() {
assert_eq!(
left.piece(&right),
Interval {
start: Bitvector::from_i16(256),
end: Bitvector::from_i16(1278),
start: bitvec!("256:2"),
end: bitvec!("1278:2"),
stride: 2,
}
);
......@@ -127,8 +137,8 @@ fn piece() {
assert_eq!(
left.piece(&right),
Interval {
start: Bitvector::from_i16(259),
end: Bitvector::from_i16(1039),
start: bitvec!("259:2"),
end: bitvec!("1039:2"),
stride: 2,
}
);
......@@ -145,11 +155,11 @@ fn add_and_sub() {
#[test]
fn contains() {
let interval = Interval::mock(2, 10).with_stride(4);
let elem = Bitvector::from_i64(4);
let elem = bitvec!("4:8");
assert!(!interval.contains(&elem));
let elem = Bitvector::from_i64(6);
let elem = bitvec!("6:8");
assert!(interval.contains(&elem));
let elem = Bitvector::from_i64(14);
let elem = bitvec!("14:8");
assert!(!interval.contains(&elem));
}
......
use crate::bitvec;
use super::*;
impl IntervalDomain {
/// Return a new interval domain of 8-byte integers.
pub fn mock(start: i64, end: i64) -> IntervalDomain {
IntervalDomain::new(Bitvector::from_i64(start), Bitvector::from_i64(end))
IntervalDomain::new(
bitvec!(format!("{}:8", start)),
bitvec!(format!("{}:8", end)),
)
}
/// Return a new interval domain of 1-byte integers.
pub fn mock_i8(start: i8, end: i8) -> IntervalDomain {
IntervalDomain::new(Bitvector::from_i8(start), Bitvector::from_i8(end))
IntervalDomain::new(
bitvec!(format!("{}:1", start)),
bitvec!(format!("{}:1", end)),
)
}
/// Return a new interval domain of 4-byte integers.
pub fn mock_i32(start: i32, end: i32) -> IntervalDomain {
IntervalDomain::new(Bitvector::from_i32(start), Bitvector::from_i32(end))
IntervalDomain::new(
bitvec!(format!("{}:4", start)),
bitvec!(format!("{}:4", end)),
)
}
pub fn mock_with_bounds(
......@@ -23,8 +34,8 @@ impl IntervalDomain {
upper_bound: Option<i64>,
) -> IntervalDomain {
let mut domain = IntervalDomain::mock(start, end);
domain.update_widening_lower_bound(&lower_bound.map(|b| Bitvector::from_i64(b)));
domain.update_widening_upper_bound(&upper_bound.map(|b| Bitvector::from_i64(b)));
domain.update_widening_lower_bound(&lower_bound.map(|b| bitvec!(format!("{}:8", b))));
domain.update_widening_upper_bound(&upper_bound.map(|b| bitvec!(format!("{}:8", b))));
domain
}
......@@ -35,8 +46,8 @@ impl IntervalDomain {
upper_bound: Option<i8>,
) -> IntervalDomain {
let mut domain = IntervalDomain::mock_i8(start, end);
domain.update_widening_lower_bound(&lower_bound.map(|b| Bitvector::from_i8(b)));
domain.update_widening_upper_bound(&upper_bound.map(|b| Bitvector::from_i8(b)));
domain.update_widening_lower_bound(&lower_bound.map(|b| bitvec!(format!("{}:1", b))));
domain.update_widening_upper_bound(&upper_bound.map(|b| bitvec!(format!("{}:1", b))));
domain
}
......@@ -396,11 +407,11 @@ fn shift_left() {
#[test]
fn simple_interval_contains() {
let domain = IntervalDomain::mock(-10, 5);
assert!(!domain.interval.contains(&Bitvector::from_i64(-11)));
assert!(domain.interval.contains(&Bitvector::from_i64(-10)));
assert!(domain.interval.contains(&Bitvector::from_i64(-4)));
assert!(domain.interval.contains(&Bitvector::from_i64(5)));
assert!(!domain.interval.contains(&Bitvector::from_i64(6)));
assert!(!domain.interval.contains(&bitvec!("-11:8")));
assert!(domain.interval.contains(&bitvec!("-10:8")));
assert!(domain.interval.contains(&bitvec!("-4:8")));
assert!(domain.interval.contains(&bitvec!("5:8")));
assert!(!domain.interval.contains(&bitvec!("6:8")));
}
#[test]
......@@ -410,57 +421,57 @@ fn add_signed_bounds() {
// signed_less_equal
let x = interval
.clone()
.add_signed_less_equal_bound(&Bitvector::from_i64(20));
.add_signed_less_equal_bound(&bitvec!("20:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(Some(-100), -10, 10, Some(20))
);
let x = interval
.clone()
.add_signed_less_equal_bound(&Bitvector::from_i64(-5));
.add_signed_less_equal_bound(&bitvec!("-5:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(Some(-100), -10, -5, None)
);
let x = interval
.clone()
.add_signed_less_equal_bound(&Bitvector::from_i64(-20));
.add_signed_less_equal_bound(&bitvec!("-20:8"));
assert!(x.is_err());
let x = IntervalDomain::mock(0, 10)
.with_stride(10)
.add_signed_less_equal_bound(&Bitvector::from_i64(15));
.add_signed_less_equal_bound(&bitvec!("15:8"));
assert_eq!(x.unwrap(), IntervalDomain::mock(0, 10).with_stride(10));
let x = IntervalDomain::mock(0, 10)
.with_stride(10)
.add_signed_less_equal_bound(&Bitvector::from_i64(5));
.add_signed_less_equal_bound(&bitvec!("5:8"));
assert_eq!(x.unwrap(), IntervalDomain::mock(0, 0));
//signed_greater_equal
let x = interval
.clone()
.add_signed_greater_equal_bound(&Bitvector::from_i64(20));
.add_signed_greater_equal_bound(&bitvec!("20:8"));
assert!(x.is_err());
let x = interval
.clone()
.add_signed_greater_equal_bound(&Bitvector::from_i64(-5));
.add_signed_greater_equal_bound(&bitvec!("-5:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(None, -5, 10, Some(100))
);
let x = interval
.clone()
.add_signed_greater_equal_bound(&Bitvector::from_i64(-20));
.add_signed_greater_equal_bound(&bitvec!("-20:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(Some(-20), -10, 10, Some(100))
);
let x = IntervalDomain::mock(0, 10)
.with_stride(10)
.add_signed_greater_equal_bound(&Bitvector::from_i64(-5));
.add_signed_greater_equal_bound(&bitvec!("-5:8"));
assert_eq!(x.unwrap(), IntervalDomain::mock(0, 10).with_stride(10));
let x = IntervalDomain::mock(0, 10)
.with_stride(10)
.add_signed_greater_equal_bound(&Bitvector::from_i64(5));
.add_signed_greater_equal_bound(&bitvec!("5:8"));
assert_eq!(x.unwrap(), IntervalDomain::mock(10, 10));
}
......@@ -473,67 +484,67 @@ fn add_unsigned_bounds() {
// unsigned_less_equal
let x = positive_interval
.clone()
.add_unsigned_less_equal_bound(&Bitvector::from_i64(35));
.add_unsigned_less_equal_bound(&bitvec!("35:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(Some(10), 20, 30, Some(35))
);
let x = positive_interval
.clone()
.add_unsigned_less_equal_bound(&Bitvector::from_i64(15));
.add_unsigned_less_equal_bound(&bitvec!("15:8"));
assert!(x.is_err());
let x = wrapped_interval
.clone()
.add_unsigned_less_equal_bound(&Bitvector::from_i64(35));
.add_unsigned_less_equal_bound(&bitvec!("35:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(None, 0, 10, Some(35))
);
let x = wrapped_interval
.clone()
.add_unsigned_less_equal_bound(&Bitvector::from_i64(-5));
.add_unsigned_less_equal_bound(&bitvec!("-5:8"));
assert_eq!(x.unwrap(), wrapped_interval); // Cannot remove a subinterval from the domain
let x = negative_interval
.clone()
.add_unsigned_less_equal_bound(&Bitvector::from_i64(-25));
.add_unsigned_less_equal_bound(&bitvec!("-25:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(Some(-40), -30, -25, None)
);
let x = negative_interval
.clone()
.add_unsigned_less_equal_bound(&Bitvector::from_i64(-35));
.add_unsigned_less_equal_bound(&bitvec!("-35:8"));
assert!(x.is_err());
// unsigned_greater_equal
let x = positive_interval
.clone()
.add_unsigned_greater_equal_bound(&Bitvector::from_i64(25));
.add_unsigned_greater_equal_bound(&bitvec!("25:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(None, 25, 30, Some(40))
);
let x = positive_interval
.clone()
.add_unsigned_greater_equal_bound(&Bitvector::from_i64(35));
.add_unsigned_greater_equal_bound(&bitvec!("35:8"));
assert!(x.is_err());
let x = wrapped_interval
.clone()
.add_unsigned_greater_equal_bound(&Bitvector::from_i64(5));
.add_unsigned_greater_equal_bound(&bitvec!("5:8"));
assert_eq!(x.unwrap(), wrapped_interval);
let x = wrapped_interval
.clone()
.add_unsigned_greater_equal_bound(&Bitvector::from_i64(35));
.add_unsigned_greater_equal_bound(&bitvec!("35:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(Some(-100), -10, -1, None)
);
let x = wrapped_interval
.clone()
.add_unsigned_greater_equal_bound(&Bitvector::from_i64(-50));
.add_unsigned_greater_equal_bound(&bitvec!("-50:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(Some(-50), -10, -1, None)
......@@ -541,11 +552,11 @@ fn add_unsigned_bounds() {
let x = negative_interval
.clone()
.add_unsigned_greater_equal_bound(&Bitvector::from_i64(25));
.add_unsigned_greater_equal_bound(&bitvec!("25:8"));
assert_eq!(x.unwrap(), negative_interval);
let x = negative_interval
.clone()
.add_unsigned_greater_equal_bound(&Bitvector::from_i64(-25));
.add_unsigned_greater_equal_bound(&bitvec!("-25:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(None, -25, -20, Some(-10))
......@@ -556,36 +567,28 @@ fn add_unsigned_bounds() {
fn add_not_equal_bounds() {
let interval = IntervalDomain::mock_with_bounds(None, -10, 10, None);
let x = interval
.clone()
.add_not_equal_bound(&Bitvector::from_i64(-20));
let x = interval.clone().add_not_equal_bound(&bitvec!("-20:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(Some(-19), -10, 10, None)
);
let x = interval
.clone()
.add_not_equal_bound(&Bitvector::from_i64(-0));
let x = interval.clone().add_not_equal_bound(&bitvec!("0:8"));
assert_eq!(x.unwrap(), interval);
let x = interval
.clone()
.add_not_equal_bound(&Bitvector::from_i64(20));
let x = interval.clone().add_not_equal_bound(&bitvec!("20:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(None, -10, 10, Some(19))
);
let interval = IntervalDomain::mock(5, 5);
let x = interval
.clone()
.add_not_equal_bound(&Bitvector::from_i64(5));
let x = interval.clone().add_not_equal_bound(&bitvec!("5:8"));
assert!(x.is_err());
let interval = IntervalDomain::mock(5, 6);
let x = interval.add_not_equal_bound(&Bitvector::from_i64(5));
let x = interval.add_not_equal_bound(&bitvec!("5:8"));
assert_eq!(x.unwrap(), IntervalDomain::mock(6, 6));
let interval = IntervalDomain::mock_with_bounds(None, 5, 6, Some(100));
let x = interval.add_not_equal_bound(&Bitvector::from_i64(10));
let x = interval.add_not_equal_bound(&bitvec!("10:8"));
assert_eq!(
x.unwrap(),
IntervalDomain::mock_with_bounds(None, 5, 6, Some(9))
......@@ -624,39 +627,28 @@ fn float_nan_bytesize() {
fn stride_rounding() {
let interval = Interval::mock(3, 13).with_stride(10);
assert_eq!(
Bitvector::from_i64(5)
.round_up_to_stride_of(&interval)
.unwrap(),
Bitvector::from_i64(13)
bitvec!("5:8").round_up_to_stride_of(&interval).unwrap(),
bitvec!("13:8")
);
assert_eq!(
Bitvector::from_i64(5)
.round_down_to_stride_of(&interval)
.unwrap(),
Bitvector::from_i64(3)
bitvec!("5:8").round_down_to_stride_of(&interval).unwrap(),
bitvec!("3:8")
);
assert_eq!(
Bitvector::from_i64(-277)
.round_up_to_stride_of(&interval)
.unwrap(),
Bitvector::from_i64(-277)
bitvec!("-277:8").round_up_to_stride_of(&interval).unwrap(),
bitvec!("-277:8")
);
assert_eq!(
Bitvector::from_i64(-277)
bitvec!("-277:8")
.round_down_to_stride_of(&interval)
.unwrap(),
Bitvector::from_i64(-277)
bitvec!("-277:8")
);
let interval = Interval::mock_i8(100, 110).with_stride(10);
assert_eq!(
Bitvector::from_i8(-123)
.round_up_to_stride_of(&interval)
.unwrap(),
Bitvector::from_i8(-120)
);
assert_eq!(
Bitvector::from_i8(-123).round_down_to_stride_of(&interval),
None
bitvec!("-123:1").round_up_to_stride_of(&interval).unwrap(),
bitvec!("-120:1")
);
assert_eq!(bitvec!("-123:1").round_down_to_stride_of(&interval), None);
}
......@@ -2,6 +2,7 @@ use super::*;
use crate::abstract_domain::DataDomain;
use crate::abstract_domain::IntervalDomain;
use crate::abstract_domain::RegisterDomain;
use crate::bitvec;
use crate::intermediate_representation::*;
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, PartialOrd, Ord)]
......@@ -60,94 +61,111 @@ 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));
region.add(mock(5, 3u64), bitvec!("5:8"));
assert_eq!(
region.get(bitvec!("5:8"), ByteSize::from(3u64)),
mock(5, 3u64)
);
region.add(mock(7, 2u64), bitvec!("8:8"));
assert_eq!(
region.get(bitvec!("8:8"), ByteSize::from(2u64)),
mock(7, 2u64)
);
assert_eq!(
region.get(bv(5), ByteSize::from(2u64)),
region.get(bitvec!("5:8"), ByteSize::from(3u64)),
mock(5, 3u64)
);
assert_eq!(
region.get(bitvec!("5:8"), 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));
region.add(mock(9, 2u64), bitvec!("6:8"));
assert_eq!(
region.get(bitvec!("6:8"), ByteSize::from(2u64)),
mock(9, 2u64)
);
assert_eq!(
region.get(bv(5), ByteSize::from(3u64)),
region.get(bitvec!("5:8"), 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)),
region.get(bitvec!("8:8"), ByteSize::from(2u64)),
mock(7, 2u64)
);
region.add(mock(9, 11u64), bitvec!("-3:8"));
assert_eq!(
region.get(bitvec!("-3:8"), ByteSize::from(11u64)),
mock(9, 11u64)
);
assert_eq!(
region.get(bitvec!("6:8"), ByteSize::from(2u64)),
MockDomain::new_top(ByteSize::new(2))
);
assert_eq!(region.get(bv(8), ByteSize::from(2u64)), mock(7, 2u64));
assert_eq!(
region.get(bitvec!("8:8"), ByteSize::from(2u64)),
mock(7, 2u64)
);
let mut other_region = MemRegion::new(ByteSize::from(8u64));
other_region.add(mock(7, 2u64), bv(8));
other_region.add(mock(7, 2u64), bitvec!("8:8"));
assert!(region != other_region);
let merged_region = region.merge(&other_region);
assert_eq!(
merged_region.get(bv(8), ByteSize::from(2u64)),
merged_region.get(bitvec!("8:8"), ByteSize::from(2u64)),
mock(7, 2u64)
);
assert_eq!(
merged_region.get(bv(-3), ByteSize::from(11u64)),
merged_region.get(bitvec!("-3:8"), ByteSize::from(11u64)),
MockDomain::new_top(ByteSize::from(11u64))
);
other_region.add(mock(9, 11u64), bv(-3));
other_region.add(mock(9, 11u64), bitvec!("-3:8"));
assert_eq!(region, other_region);
}
#[test]
fn merge_test() {
let data: fn(u64) -> DataDomain<IntervalDomain> =
|val| DataDomain::from(Bitvector::from_u64(val));
|val| DataDomain::from(bitvec!(format!("{}:8", 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));
region.add(data(0), bitvec!("0:8"));
region.add(data(8), bitvec!("8:8"));
region.add(data(22), bitvec!("32:8"));
region.add(data(42), bitvec!("50:8"));
region.add(data(70), bitvec!("70:8"));
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));
other_region.add(data(1), bitvec!("0:8"));
other_region.add(data(15), bitvec!("15:8"));
other_region.add(data(26), bitvec!("25:8"));
other_region.add(data(42), bitvec!("58:8"));
other_region.add(bitvec!("70:1").into(), bitvec!("70:8"));
let merged_region = region.merge(&&other_region);
// Merge elements at target address.
assert_eq!(
merged_region.get_unsized(Bitvector::from_u64(0)),
merged_region.get_unsized(bitvec!("0:8")),
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);
assert_eq!(merged_region.get_unsized(bitvec!("8:8")), None);
assert_eq!(merged_region.get_unsized(bitvec!("15:8")), None);
assert_eq!(merged_region.get_unsized(bitvec!("25:8")), None);
assert_eq!(merged_region.get_unsized(bitvec!("32:8")), None);
// Elements only contained in one region are merged with `Top`.
let mut elem_plus_top: DataDomain<IntervalDomain> = Bitvector::from_u64(42).into();
let mut elem_plus_top: DataDomain<IntervalDomain> = bitvec!("42:8").into();
elem_plus_top.set_contains_top_flag();
assert!(!elem_plus_top.is_top());
assert_eq!(
merged_region.get_unsized(Bitvector::from_u64(50)),
merged_region.get_unsized(bitvec!("50:8")),
Some(elem_plus_top.clone())
);
assert_eq!(
merged_region.get_unsized(Bitvector::from_u64(58)),
merged_region.get_unsized(bitvec!("58:8")),
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);
assert_eq!(merged_region.get_unsized(bitvec!("70:8")), None);
// Check that no other unexpected elements are contained in the merged region.
assert_eq!(merged_region.values().len(), 3);
}
......@@ -155,12 +173,12 @@ fn merge_test() {
#[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));
region.add(MockDomain::new_top(ByteSize::from(4u64)), bitvec!("5:8"));
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));
region.add(mock(5, 4u64), bitvec!("5:8"));
other_region.add(mock(7, 4u64), bitvec!("5:8"));
let merged_region = region.merge(&other_region);
assert_eq!(region.values().len(), 1);
assert_eq!(other_region.values().len(), 1);
......@@ -170,18 +188,18 @@ fn do_not_save_top_elements() {
#[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));
region.add(mock(1, 8u64), bitvec!("0:8"));
region.add(mock(2, 8u64), bitvec!("8:8"));
region.add(mock(3, 8u64), bitvec!("16:8"));
region.add(mock(4, 8u64), bitvec!("24:8"));
region.add(mock(5, 8u64), bitvec!("32:8"));
assert_eq!(region.values().len(), 5);
region.remove(bv(2), bv(3));
region.remove(bitvec!("2:8"), bitvec!("3:8"));
assert_eq!(region.values().len(), 4);
region.remove(bv(7), bv(1));
region.remove(bitvec!("7:8"), bitvec!("1:8"));
assert_eq!(region.values().len(), 4);
region.remove(bv(7), bv(2));
region.remove(bitvec!("7:8"), bitvec!("2:8"));
assert_eq!(region.values().len(), 3);
region.clear_interval(15, 1);
......@@ -196,29 +214,41 @@ fn value_removals() {
}
region.clear_top_values();
assert_eq!(region.values().len(), 1);
assert_eq!(region.get(bv(24), ByteSize::from(8u64)), mock(4, 8u64));
assert_eq!(
region.get(bitvec!("24:8"), ByteSize::from(8u64)),
mock(4, 8u64)
);
}
#[test]
fn merge_writes_with_top() {
let data: DataDomain<IntervalDomain> = DataDomain::from(Bitvector::from_u64(0));
let data: DataDomain<IntervalDomain> = DataDomain::from(bitvec!("0:8"));
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()));
region.add(data.clone(), bitvec!("0:8"));
region.merge_write_top(bitvec!("0:8"), ByteSize::new(8));
assert_eq!(
region.get_unsized(bitvec!("0:8")),
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));
region.add(data.clone(), bitvec!("8:8"));
region.merge_write_top(bitvec!("5:8"), 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.add(data.clone(), bitvec!("0:8"));
region.add(data.clone(), bitvec!("8:8"));
region.add(data.clone(), bitvec!("16:8"));
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()));
assert_eq!(region.get_unsized(bitvec!("0:8")), Some(data));
assert_eq!(
region.get_unsized(bitvec!("8:8")),
Some(data_with_top.clone())
);
assert_eq!(
region.get_unsized(bitvec!("16:8")),
Some(data_with_top.clone())
);
}
use super::{create_computation, mock_context, NodeValue};
use crate::def;
use crate::expr;
use crate::intermediate_representation::*;
use mock_context::Context;
use mock_context::StartEnd;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::iter::FromIterator;
use mock_context::Context;
use mock_context::StartEnd;
fn mock_program() -> Term<Program> {
let var = Variable {
name: String::from("RAX"),
size: ByteSize::new(8),
is_temp: false,
};
let value = Expression::UnOp {
op: UnOpType::IntNegate,
arg: Box::new(Expression::Var(var.clone())),
};
let def_term1 = Term {
tid: Tid::new("def1".to_string()),
term: Def::Assign {
var: var.clone(),
value: value.clone(),
},
};
let def_term2 = Term {
tid: Tid::new("def2".to_string()),
term: Def::Assign {
var: var.clone(),
value: value.clone(),
},
};
let def_term3 = Term {
tid: Tid::new("def3".to_string()),
term: Def::Assign {
var: var.clone(),
value: value.clone(),
},
};
let def_term4 = Term {
tid: Tid::new("def4".to_string()),
term: Def::Assign {
var: var.clone(),
value: value.clone(),
},
};
let def_term5 = Term {
tid: Tid::new("def5".to_string()),
term: Def::Assign {
var: var.clone(),
value: value.clone(),
},
};
let def_term1 = def!["def1: RAX:8 = -(RAX:8)"];
let def_term2 = def!["def2: RAX:8 = -(RAX:8)"];
let def_term3 = def!["def3: RAX:8 = -(RAX:8)"];
let def_term4 = def!["def4: RAX:8 = -(RAX:8)"];
let def_term5 = def!["def5: RAX:8 = -(RAX:8)"];
let call_term = Term {
tid: Tid::new("call".to_string()),
term: Jmp::Call {
......@@ -61,7 +24,7 @@ fn mock_program() -> Term<Program> {
};
let return_term = Term {
tid: Tid::new("return".to_string()),
term: Jmp::Return(Expression::Const(Bitvector::zero(64.into()))), // The return term does not matter
term: Jmp::Return(expr!("0:8")), // The return term does not matter
};
let jmp = Jmp::Branch(Tid::new("sub1_blk1"));
let jmp_term = Term {
......@@ -94,7 +57,7 @@ fn mock_program() -> Term<Program> {
};
let cond_jump = Jmp::CBranch {
target: Tid::new("sub1_blk1"),
condition: Expression::Const(Bitvector::from_u8(0)),
condition: expr!("0:1"),
};
let cond_jump_term = Term {
tid: Tid::new("cond_jump"),
......
......@@ -127,25 +127,18 @@ pub fn remove_dead_var_assignments(project: &mut Project) {
#[cfg(test)]
mod tests {
use super::*;
fn def_assign_term(term_index: u64, input: &str, output: &str) -> Term<Def> {
Def::assign(
&format!("def_{}", term_index),
Variable::mock(output, 8),
Expression::Var(Variable::mock(input, 8)),
)
}
use crate::defs;
#[test]
fn dead_assignment_removal() {
let defs = vec![
def_assign_term(1, "A", "B"),
def_assign_term(2, "B", "C"),
def_assign_term(3, "C", "RAX"), // dead assignment
def_assign_term(4, "B", "RAX"),
def_assign_term(5, "C", "RBX"),
def_assign_term(6, "A", "B"), // dead assignment, since the next assignment is dead
def_assign_term(7, "B", "C"), // dead assignment, since C is not a physical register
let defs = defs![
"def_1: B:8 = A:8",
"def_2: C:8 = B:8",
"def_3: RAX:8 = C:8",
"def_4: RAX:8 = B:8",
"def_5: RBX:8 = C:8",
"def_6: B:8 = A:8",
"def_7: C:8 = B:8"
];
let block = Term {
tid: Tid::new("block"),
......@@ -167,11 +160,11 @@ mod tests {
project.program.term.subs.insert(sub.tid.clone(), sub);
remove_dead_var_assignments(&mut project);
let cleaned_defs = vec![
def_assign_term(1, "A", "B"),
def_assign_term(2, "B", "C"),
def_assign_term(4, "B", "RAX"),
def_assign_term(5, "C", "RBX"),
let cleaned_defs = defs![
"def_1: B:8 = A:8",
"def_2: C:8 = B:8",
"def_4: RAX:8 = B:8",
"def_5: RBX:8 = C:8"
];
assert_eq!(
&project.program.term.subs[&Tid::new("sub")].term.blocks[0]
......
use super::*;
use crate::intermediate_representation::{Def, Expression, Variable};
use crate::{defs, expr, intermediate_representation::Def, variable};
/// Creates a specific project containing three blocks for expression propagation tests.
///
......@@ -12,11 +12,7 @@ fn mock_project() -> Project {
let callee_block = Term {
tid: Tid::new("callee_block"),
term: Blk {
defs: vec![Def::assign(
"callee_def_1",
Variable::mock("Y", 8),
Expression::var("Z", 8),
)],
defs: defs!["callee_def_1: Y:8 = Z:8"],
jmps: Vec::new(),
indirect_jmp_targets: Vec::new(),
},
......@@ -34,17 +30,9 @@ fn mock_project() -> Project {
let entry_jmp_block = Term {
tid: Tid::new("entry_jmp_block"),
term: Blk {
defs: vec![
Def::assign(
"entry_jmp_def_1",
Variable::mock("X", 8),
Expression::var("Z", 8).un_op(UnOpType::BoolNegate),
),
Def::assign(
"entry_jmp_def_2",
Variable::mock("Z", 8),
Expression::var("Z", 8).un_op(UnOpType::IntNegate),
),
defs: defs![
"entry_jmp_def_1: X:8 = ¬(Z:8)",
"entry_jmp_def_2: Z:8 = -(Z:8)"
],
jmps: vec![Term {
tid: Tid::new("call_to_called_function"),
......@@ -92,37 +80,13 @@ fn get_mock_entry_block() -> Term<Blk> {
Term {
tid: Tid::new("entry_block"),
term: Blk {
defs: vec![
Def::assign(
"tid_1",
Variable::mock("Z", 8),
Expression::const_from_i32(42).un_op(UnOpType::IntNegate),
),
Def::assign(
"tid_2",
Variable::mock("X", 8),
Expression::var("Y", 8).un_op(UnOpType::IntNegate),
),
Def::assign(
"tid_3",
Variable::mock("Y", 8),
Expression::var("X", 8).plus(Expression::var("Y", 8)),
),
Def::assign(
"tid_4",
Variable::mock("X", 8),
Expression::var("X", 8).un_op(UnOpType::IntNegate),
),
Def::assign(
"tid_5",
Variable::mock("Y", 8),
Expression::var("Y", 8).un_op(UnOpType::IntNegate),
),
Def::assign(
"tid_6",
Variable::mock("Y", 8),
Expression::var("X", 8).plus(Expression::var("Y", 8)),
),
defs: defs![
"tid_1: Z:8 = -(42:4)",
"tid_2: X:8 = -(Y:8)",
"tid_3: Y:8 = X:8 + Y:8",
"tid_4: X:8 = -(X:8)",
"tid_5: Y:8 = -(Y:8)",
"tid_6: Y:8 = X:8 + Y:8"
],
jmps: Vec::new(),
indirect_jmp_targets: Vec::new(),
......@@ -149,17 +113,13 @@ fn inter_block_propagation() {
vec![
Def::assign(
"entry_jmp_def_1",
Variable::mock("X", 8),
Expression::const_from_i32(42)
.un_op(UnOpType::IntNegate)
.un_op(UnOpType::BoolNegate),
variable!("X:8"),
expr!("-(42:4)").un_op(UnOpType::BoolNegate),
),
Def::assign(
"entry_jmp_def_2",
Variable::mock("Z", 8),
Expression::const_from_i32(42)
.un_op(UnOpType::IntNegate)
.un_op(UnOpType::IntNegate),
variable!("Z:8"),
expr!("-(42:4)").un_op(UnOpType::IntNegate),
)
]
)
......@@ -181,11 +141,7 @@ fn no_propagation_on_calls() {
.unwrap()
.term
.defs,
vec![Def::assign(
"callee_def_1",
Variable::mock("Y", 8),
Expression::var("Z", 8),
)]
defs!["callee_def_1: Y:8 = Z:8"]
)
}
#[test]
......@@ -214,10 +170,7 @@ fn insertion_table_update() {
// Assignment is inserted into table, no other changes.
assert_eq!(
update.clone().unwrap(),
HashMap::from([(
Variable::mock("Z", 8),
Expression::const_from_i32(42).un_op(UnOpType::IntNegate)
)])
HashMap::from([(variable!("Z:8"), expr!("-(42:4)"))])
);
let update = crate::analysis::forward_interprocedural_fixpoint::Context::update_def(
......@@ -229,14 +182,8 @@ fn insertion_table_update() {
assert_eq!(
update.clone().unwrap(),
HashMap::from([
(
Variable::mock("Z", 8),
Expression::const_from_i32(42).un_op(UnOpType::IntNegate)
),
(
Variable::mock("X", 8),
Expression::var("Y", 8).un_op(UnOpType::IntNegate)
)
(variable!("Z:8"), expr!("-(42:4)")),
(variable!("X:8"), expr!("-(Y:8)"))
])
);
......@@ -248,10 +195,7 @@ fn insertion_table_update() {
// Expression for X is removed and Assignment is not inserted.
assert_eq!(
update.clone().unwrap(),
HashMap::from([(
Variable::mock("Z", 8),
Expression::const_from_i32(42).un_op(UnOpType::IntNegate)
),])
HashMap::from([(variable!("Z:8"), expr!("-(42:4)")),])
);
let update = crate::analysis::forward_interprocedural_fixpoint::Context::update_def(
&context,
......@@ -261,10 +205,7 @@ fn insertion_table_update() {
// Expression for Y is removed and Assignment is not inserted.
assert_eq!(
update.clone().unwrap(),
HashMap::from([(
Variable::mock("Z", 8),
Expression::const_from_i32(42).un_op(UnOpType::IntNegate)
),])
HashMap::from([(variable!("Z:8"), expr!("-(42:4)")),])
);
let update = crate::analysis::forward_interprocedural_fixpoint::Context::update_def(
......@@ -275,10 +216,7 @@ fn insertion_table_update() {
// Assignment not inserted.
assert_eq!(
update.clone().unwrap(),
HashMap::from([(
Variable::mock("Z", 8),
Expression::const_from_i32(42).un_op(UnOpType::IntNegate)
),])
HashMap::from([(variable!("Z:8"), expr!("-(42:4)")),])
);
let update = crate::analysis::forward_interprocedural_fixpoint::Context::update_def(
......@@ -289,10 +227,7 @@ fn insertion_table_update() {
// Assignment not inserted.
assert_eq!(
update.clone().unwrap(),
HashMap::from([(
Variable::mock("Z", 8),
Expression::const_from_i32(42).un_op(UnOpType::IntNegate)
),])
HashMap::from([(variable!("Z:8"), expr!("-(42:4)")),])
);
}
#[test]
......@@ -300,35 +235,12 @@ fn insertion_table_update() {
fn expressions_inserted() {
let mut project = mock_project();
propagate_input_expression(&mut project);
let result_def_entry_block = vec![
Def::assign(
"tid_1",
Variable::mock("Z", 8),
Expression::const_from_i32(42).un_op(UnOpType::IntNegate),
),
Def::assign(
"tid_2",
Variable::mock("X", 8),
Expression::var("Y", 8).un_op(UnOpType::IntNegate),
),
Def::assign(
"tid_3",
Variable::mock("Y", 8),
Expression::var("Y", 8)
.un_op(UnOpType::IntNegate)
.plus(Expression::var("Y", 8)),
),
Def::assign(
"tid_4",
Variable::mock("X", 8),
Expression::var("X", 8).un_op(UnOpType::IntNegate),
),
// tid_5 is removed by merge_def_assignments_to_same_var()
Def::assign(
"tid_6",
Variable::mock("Y", 8),
Expression::var("X", 8).plus(Expression::var("Y", 8).un_op(UnOpType::IntNegate)),
),
let result_def_entry_block = defs![
"tid_1: Z:8 = -(42:4)",
"tid_2: X:8 = -(Y:8)",
"tid_3: Y:8 = -(Y:8) + Y:8",
"tid_4: X:8 = -(X:8)",
"tid_6: Y:8 = X:8 + -(Y:8)"
];
assert_eq!(
project
......@@ -357,17 +269,13 @@ fn expressions_inserted() {
vec![
Def::assign(
"entry_jmp_def_1",
Variable::mock("X", 8),
Expression::const_from_i32(42)
.un_op(UnOpType::IntNegate)
.un_op(UnOpType::BoolNegate),
variable!("X:8"),
expr!("-(42:4)").un_op(UnOpType::BoolNegate),
),
Def::assign(
"entry_jmp_def_2",
Variable::mock("Z", 8),
Expression::const_from_i32(42)
.un_op(UnOpType::IntNegate)
.un_op(UnOpType::IntNegate)
variable!("Z:8"),
expr!("-(42:4)").un_op(UnOpType::IntNegate)
)
]
);
......@@ -382,10 +290,6 @@ fn expressions_inserted() {
.blocks[0]
.term
.defs,
vec![Def::assign(
"callee_def_1",
Variable::mock("Y", 8),
Expression::var("Z", 8),
)]
defs!["callee_def_1: Y:8 = Z:8"]
);
}
use super::*;
use crate::{bitvec, variable};
use std::collections::HashSet;
#[test]
......@@ -25,20 +26,20 @@ fn test_compute_return_values_of_call() {
&call,
);
let expected_val = DataDomain::from_target(
AbstractIdentifier::from_var(Tid::new("call_tid"), &Variable::mock("RAX", 8)),
Bitvector::from_i64(0).into(),
AbstractIdentifier::from_var(Tid::new("call_tid"), &variable!("RAX:8")),
bitvec!("0x0:8").into(),
);
assert_eq!(return_values.iter().len(), 3);
assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val));
assert_eq!(return_values[0], (&variable!("RAX:8"), expected_val));
// Test returning a known value.
let param_ref = DataDomain::from_target(
AbstractIdentifier::from_var(Tid::new("callee"), &Variable::mock("RDI", 8)),
Bitvector::from_i64(0).into(),
AbstractIdentifier::from_var(Tid::new("callee"), &variable!("RDI:8")),
bitvec!("0x0:8").into(),
);
callee_state.set_register(&Variable::mock("RAX", 8), param_ref);
callee_state.set_register(&variable!("RAX:8"), param_ref);
let expected_val = DataDomain::from_target(
AbstractIdentifier::from_var(Tid::new("caller"), &Variable::mock("RDI", 8)),
Bitvector::from_i64(0).into(),
AbstractIdentifier::from_var(Tid::new("caller"), &variable!("RDI:8")),
bitvec!("0x0:8").into(),
);
let return_values = context.compute_return_values_of_call(
&mut caller_state,
......@@ -47,7 +48,7 @@ fn test_compute_return_values_of_call() {
&call,
);
assert_eq!(return_values.iter().len(), 3);
assert_eq!(return_values[0], (&Variable::mock("RAX", 8), expected_val));
assert_eq!(return_values[0], (&variable!("RAX:8"), expected_val));
}
#[test]
......@@ -69,17 +70,17 @@ fn test_call_stub_handling() {
assert_eq!(
state.get_params_of_current_function(),
vec![(
Arg::from_var(Variable::mock("r0", 4), None),
Arg::from_var(variable!("r0:4"), None),
AccessPattern::new().with_read_flag()
)]
);
assert_eq!(
state.get_register(&Variable::mock("r0", 4)),
state.get_register(&variable!("r0:4")),
DataDomain::from_target(
AbstractIdentifier::mock(call_tid, "r0", 4),
Bitvector::from_i32(0).into()
bitvec!("0x0:4").into()
)
.merge(&Bitvector::zero(ByteSize::new(4).into()).into())
.merge(&bitvec!("0x0:4").into())
);
// Test handling of sprintf call
......@@ -89,7 +90,7 @@ fn test_call_stub_handling() {
project.get_standard_calling_convention().unwrap(),
);
// Set the format string param register to a pointer to the string 'cat %s %s %s %s'.
state.set_register(&Variable::mock("r1", 4), Bitvector::from_i32(0x6000).into());
state.set_register(&variable!("r1:4"), bitvec!("0x6000:4").into());
let extern_symbol = ExternSymbol::mock_sprintf_symbol_arm();
let call_tid = Tid::new("call_sprintf");
context.handle_extern_symbol_call(&mut state, &extern_symbol, &call_tid);
......@@ -97,14 +98,14 @@ fn test_call_stub_handling() {
assert_eq!(
params[0],
(
Arg::from_var(Variable::mock("r0", 4), None),
Arg::from_var(variable!("r0:4"), None),
AccessPattern::new_unknown_access()
)
);
assert_eq!(
params[1],
(
Arg::from_var(Variable::mock("r2", 4), None),
Arg::from_var(variable!("r2:4"), None),
AccessPattern::new()
.with_read_flag()
.with_dereference_flag()
......@@ -121,15 +122,15 @@ fn test_get_global_mem_address() {
let context = Context::new(&project, &graph);
// Check global address from abstract ID
let global_address_id: DataDomain<BitvectorDomain> = DataDomain::from_target(
AbstractIdentifier::from_global_address(&Tid::new("fn_tid"), &Bitvector::from_i32(0x2000)),
Bitvector::from_i32(0x2).into(),
AbstractIdentifier::from_global_address(&Tid::new("fn_tid"), &bitvec!("0x2000:4")),
bitvec!("0x2:4").into(),
);
let result = context.get_global_mem_address(&global_address_id);
assert_eq!(result, Some(Bitvector::from_i32(0x2002)));
assert_eq!(result, Some(bitvec!("0x2002:4")));
// Check global address from absolute value
let global_address_const = Bitvector::from_i32(0x2003).into();
let global_address_const = bitvec!("0x2003:4").into();
let result = context.get_global_mem_address(&global_address_const);
assert_eq!(result, Some(Bitvector::from_i32(0x2003)));
assert_eq!(result, Some(bitvec!("0x2003:4")));
// Check global address not returned if it may not be unique
let value = global_address_id.merge(&global_address_const);
let result = context.get_global_mem_address(&value);
......
......@@ -277,6 +277,7 @@ impl Default for FunctionSignature {
#[cfg(test)]
pub mod tests {
use super::*;
use crate::variable;
impl FunctionSignature {
/// Create a mock x64 function signature with 2 parameters, one of which is accessed mutably,
......@@ -287,11 +288,11 @@ pub mod tests {
write_access_pattern.set_unknown_access_flags();
let parameters = HashMap::from_iter([
(
Arg::from_var(Variable::mock("RDI", 8), None),
Arg::from_var(variable!("RDI:8"), None),
AccessPattern::new(),
),
(
Arg::from_var(Variable::mock("RSI", 8), None),
Arg::from_var(variable!("RSI:8"), None),
write_access_pattern,
),
]);
......
......@@ -444,7 +444,7 @@ impl State {
let regs = self
.register
.iter()
.map(|(var, value)| (format!("{}", var), value.to_json_compact()))
.map(|(var, value)| (format!("{var}"), value.to_json_compact()))
.collect();
json_map.insert("Register".to_string(), serde_json::Value::Object(regs));
let access_patterns = self
......@@ -452,8 +452,8 @@ impl State {
.iter()
.map(|(id, pattern)| {
(
format!("{}", id),
serde_json::Value::String(format!("{}", pattern)),
format!("{id}"),
serde_json::Value::String(format!("{pattern}")),
)
})
.collect();
......
use super::*;
use crate::{bitvec, expr, variable};
impl State {
/// Generate a mock state for an ARM-32 state.
pub fn mock_arm32() -> State {
State::new(
&Tid::new("mock_fn"),
&Variable::mock("sp", 4),
&variable!("sp:4"),
&CallingConvention::mock_arm32(),
)
}
......@@ -14,7 +15,7 @@ impl State {
pub fn mock_x64(tid_name: &str) -> State {
State::new(
&Tid::new(tid_name),
&Variable::mock("RSP", 8),
&variable!("RSP:8"),
&CallingConvention::mock_x64(),
)
}
......@@ -22,7 +23,7 @@ impl State {
/// Mock an abstract ID representing the stack.
fn mock_stack_id() -> AbstractIdentifier {
AbstractIdentifier::from_var(Tid::new("mock_fn"), &Variable::mock("sp", 4))
AbstractIdentifier::from_var(Tid::new("mock_fn"), &variable!("sp:4"))
}
/// Mock an abstract ID of a stack parameter
......@@ -46,11 +47,8 @@ fn test_new() {
// Assert that the register values are as expected
assert_eq!(state.register.len(), 7); // 6 parameter register plus stack pointer
assert_eq!(
state.get_register(&Variable::mock("sp", 4)),
DataDomain::from_target(
mock_stack_id(),
Bitvector::zero(ByteSize::new(4).into()).into()
)
state.get_register(&variable!("sp:4")),
DataDomain::from_target(mock_stack_id(), bitvec!("0x0:4").into())
);
// Check the generated tracked IDs
assert_eq!(state.tracked_ids.len(), 6);
......@@ -59,7 +57,7 @@ fn test_new() {
state.get_register(id.unwrap_register()),
DataDomain::from_target(
id.clone(),
Bitvector::zero(id.unwrap_register().size.into()).into()
bitvec!(format!("0:{}", id.unwrap_register().size)).into()
)
);
assert_eq!(access_pattern, &AccessPattern::new());
......@@ -69,21 +67,20 @@ fn test_new() {
#[test]
fn test_store_and_load_from_stack() {
let mut state = State::mock_arm32();
let address = DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(-4).into());
let value: DataDomain<BitvectorDomain> = Bitvector::from_i32(0).into();
let address = DataDomain::from_target(mock_stack_id(), bitvec!("-4:4").into());
let value: DataDomain<BitvectorDomain> = bitvec!("0x0:4").into();
// write and load a value to the current stack frame
state.write_value(address.clone(), value.clone());
assert_eq!(state.stack.iter().len(), 1);
assert_eq!(
state.stack.get(Bitvector::from_i32(-4), ByteSize::new(4)),
state.stack.get(bitvec!("-4:4"), ByteSize::new(4)),
value.clone()
);
assert_eq!(state.load_value(address, ByteSize::new(4), None), value);
// Load a parameter register and check that the parameter gets generated
let address = DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(4).into());
let address = DataDomain::from_target(mock_stack_id(), bitvec!("0x4:4").into());
let stack_param_id = mock_stack_param_id(4, 4);
let stack_param =
DataDomain::from_target(stack_param_id.clone(), Bitvector::from_i32(0).into());
let stack_param = DataDomain::from_target(stack_param_id.clone(), bitvec!("0x0:4").into());
assert_eq!(state.tracked_ids.iter().len(), 6);
assert_eq!(
state.load_value(address.clone(), ByteSize::new(4), None),
......@@ -104,22 +101,21 @@ fn test_store_and_load_from_stack() {
fn test_load_unsized_from_stack() {
let mut state = State::mock_arm32();
// Load an existing stack param (generated by a sized load at the same address)
let address = DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(0).into());
let address = DataDomain::from_target(mock_stack_id(), bitvec!("0x0:4").into());
let stack_param_id = mock_stack_param_id(0, 4);
let stack_param =
DataDomain::from_target(stack_param_id.clone(), Bitvector::from_i32(0).into());
let stack_param = DataDomain::from_target(stack_param_id.clone(), bitvec!("0x0:4").into());
state.load_value(address, ByteSize::new(4), None);
let unsized_load = state.load_unsized_value_from_stack(Bitvector::from_i32(0));
let unsized_load = state.load_unsized_value_from_stack(bitvec!("0x0:4").into());
assert_eq!(unsized_load, stack_param);
assert!(state.tracked_ids.get(&stack_param_id).is_some());
// Load a non-existing stack param
let stack_param_id = mock_stack_param_id(4, 1);
let stack_param = DataDomain::from_target(stack_param_id.clone(), Bitvector::from_i8(0).into());
let unsized_load = state.load_unsized_value_from_stack(Bitvector::from_i32(4));
let stack_param = DataDomain::from_target(stack_param_id.clone(), bitvec!("0x0:1").into());
let unsized_load = state.load_unsized_value_from_stack(bitvec!("0x4:4"));
assert_eq!(unsized_load, stack_param);
assert!(state.tracked_ids.get(&stack_param_id).is_some());
// Unsized load from the current stack frame
let unsized_load = state.load_unsized_value_from_stack(Bitvector::from_i32(-4));
let unsized_load = state.load_unsized_value_from_stack(bitvec!("-4:4"));
assert_eq!(unsized_load, DataDomain::new_top(ByteSize::new(1)));
}
......@@ -127,16 +123,16 @@ fn test_load_unsized_from_stack() {
fn test_eval() {
let mut state = State::mock_arm32();
// Test the eval method
let expr = Expression::Var(Variable::mock("sp", 4)).plus_const(42);
let expr = expr!("sp:4 + 42:4");
assert_eq!(
state.eval(&expr),
DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(42).into())
DataDomain::from_target(mock_stack_id(), bitvec!("42:4").into())
);
// Test the eval_parameter_arg method
let arg = Arg::from_var(Variable::mock("sp", 4), None);
let arg = Arg::from_var(variable!("sp:4"), None);
assert_eq!(
state.eval_parameter_arg(&arg),
DataDomain::from_target(mock_stack_id(), Bitvector::from_i32(0).into())
DataDomain::from_target(mock_stack_id(), bitvec!("0x0:4").into())
);
}
......@@ -146,9 +142,8 @@ fn test_extern_symbol_handling() {
let extern_symbol = ExternSymbol::mock_arm32("mock_symbol");
let cconv = CallingConvention::mock_arm32();
let call_tid = Tid::new("call_tid");
let param_id = AbstractIdentifier::from_var(Tid::new("mock_fn"), &Variable::mock("r0", 4));
let return_val_id =
AbstractIdentifier::from_var(Tid::new("call_tid"), &Variable::mock("r0", 4));
let param_id = AbstractIdentifier::from_var(Tid::new("mock_fn"), &variable!("r0:4"));
let return_val_id = AbstractIdentifier::from_var(Tid::new("call_tid"), &variable!("r0:4"));
// Test extern symbol handling.
state.handle_generic_extern_symbol(
&call_tid,
......@@ -164,7 +159,7 @@ fn test_extern_symbol_handling() {
.is_mutably_dereferenced(),
true
);
let return_val = state.get_register(&Variable::mock("r0", 4));
let return_val = state.get_register(&variable!("r0:4"));
assert_eq!(return_val.get_relative_values().iter().len(), 2);
assert_eq!(
return_val.get_relative_values().get(&param_id).unwrap(),
......@@ -179,7 +174,7 @@ fn test_extern_symbol_handling() {
.get_relative_values()
.get(&return_val_id)
.unwrap(),
&Bitvector::from_i32(0).into()
&bitvec!("0:4").into()
);
}
......@@ -189,16 +184,16 @@ fn test_substitute_global_mem_address() {
let global_memory = RuntimeMemoryImage::mock();
// Test that addresses into non-writeable memory do not get substituted.
let global_address: DataDomain<BitvectorDomain> = Bitvector::from_i32(0x1000).into();
let global_address: DataDomain<BitvectorDomain> = bitvec!("0x1000:4").into();
let substituted_address =
state.substitute_global_mem_address(global_address.clone(), &global_memory);
assert_eq!(global_address, substituted_address);
// Test substitution for addresses into writeable global memory.
let global_address: DataDomain<BitvectorDomain> = Bitvector::from_i32(0x2000).into();
let global_address: DataDomain<BitvectorDomain> = bitvec!("0x2000:4").into();
let substituted_address = state.substitute_global_mem_address(global_address, &global_memory);
let expected_global_id = AbstractIdentifier::from_global_address(
state.get_current_function_tid(),
&Bitvector::from_i32(0x2000),
&bitvec!("0x2000:4"),
);
assert_eq!(
state.tracked_ids.get(&expected_global_id),
......@@ -206,6 +201,6 @@ fn test_substitute_global_mem_address() {
);
assert_eq!(
substituted_address,
DataDomain::from_target(expected_global_id, Bitvector::from_i32(0).into())
DataDomain::from_target(expected_global_id, bitvec!("0x0:4").into())
);
}
......@@ -89,7 +89,7 @@ impl<'a> Context<'a> {
pub fn log_debug(&self, result: Result<(), Error>, location: Option<&Tid>) {
if let Err(err) = result {
let mut log_message =
LogMessage::new_debug(format!("{}", err)).source("Pointer Inference");
LogMessage::new_debug(format!("{err}")).source("Pointer Inference");
if let Some(loc) = location {
log_message = log_message.location(loc.clone());
};
......@@ -286,7 +286,7 @@ impl<'a> Context<'a> {
name: "CWE476".to_string(),
version: VERSION.to_string(),
addresses: vec![tid.address.clone()],
tids: vec![format!("{}", tid)],
tids: vec![format!("{tid}")],
symbols: Vec::new(),
other: Vec::new(),
description: format!(
......
......@@ -157,8 +157,7 @@ impl<'a> PointerInference<'a> {
if !self.computation.has_stabilized() {
let worklist_size = self.computation.get_worklist().len();
self.log_info(format!(
"Fixpoint did not stabilize. Remaining worklist size: {}",
worklist_size,
"Fixpoint did not stabilize. Remaining worklist size: {worklist_size}"
));
}
if print_stats {
......@@ -172,11 +171,10 @@ impl<'a> PointerInference<'a> {
for (node_index, value) in self.computation.node_values().iter() {
let node = graph.node_weight(*node_index).unwrap();
if let Ok(string) = serde_yaml::to_string(&(node, value)) {
println!("{}", string);
println!("{string}");
} else {
println!(
"Serializing failed at {:?} with {:?}",
node_index,
"Serializing failed at {node_index:?} with {:?}",
serde_yaml::to_string(value)
);
}
......@@ -192,7 +190,7 @@ impl<'a> PointerInference<'a> {
for (node_index, node_value) in self.computation.node_values().iter() {
let node = graph.node_weight(*node_index).unwrap();
if let NodeValue::Value(value) = node_value {
json_nodes.insert(format!("{}", node), value.to_json_compact());
json_nodes.insert(format!("{node}"), value.to_json_compact());
}
}
serde_json::Value::Object(json_nodes)
......@@ -237,8 +235,7 @@ impl<'a> PointerInference<'a> {
}
}
self.log_info(format!(
"Blocks with state: {} / {}",
stateful_blocks, all_blocks
"Blocks with state: {stateful_blocks} / {all_blocks}"
));
}
......
......@@ -216,7 +216,7 @@ impl AbstractObject {
.inner
.memory
.iter()
.map(|(index, value)| (format!("{}", index), value.to_json_compact()));
.map(|(index, value)| (format!("{index}"), value.to_json_compact()));
elements.push((
"memory".to_string(),
serde_json::Value::Object(memory.collect()),
......
......@@ -174,7 +174,7 @@ impl AbstractObjectList {
use serde_json::*;
let mut object_map = Map::new();
for (id, object) in self.objects.iter() {
object_map.insert(format!("{}", id), object.to_json_compact());
object_map.insert(format!("{id}"), object.to_json_compact());
}
Value::Object(object_map)
}
......
use super::super::ValueDomain;
use super::*;
use crate::analysis::pointer_inference::object::*;
use Expression::*;
fn bv(value: i64) -> ValueDomain {
ValueDomain::from(Bitvector::from_i64(value))
}
fn new_id(time: &str, register: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new(time),
AbstractLocation::Register(Variable::mock(register, ByteSize::new(8))),
)
}
fn register(name: &str) -> Variable {
Variable {
name: name.into(),
size: ByteSize::new(8),
is_temp: false,
}
}
fn reg_add(name: &str, value: i64) -> Expression {
Expression::Var(register(name)).plus_const(value)
}
fn reg_sub(name: &str, value: i64) -> Expression {
Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(register(name))),
rhs: Box::new(Expression::Const(Bitvector::from_i64(value))),
}
}
#[test]
fn state() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(&register("RSP"), Tid::new("time0"), BTreeSet::new());
let stack_id = new_id("time0", "RSP");
let stack_addr = Data::from_target(stack_id.clone(), bv(8));
state
.store_value(&stack_addr, &bv(42).into(), &global_memory)
.unwrap();
state.register.insert(register("RSP"), stack_addr.clone());
assert_eq!(
state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap(),
bv(42).into()
);
let mut other_state = State::new(&register("RSP"), Tid::new("time0"), BTreeSet::new());
state.register.insert(register("RAX"), bv(42).into());
other_state
.register
.insert(register("RSP"), stack_addr.clone());
other_state.register.insert(register("RAX"), bv(42).into());
other_state.register.insert(register("RBX"), bv(35).into());
let merged_state = state.merge(&other_state);
assert_eq!(merged_state.register[&register("RAX")], bv(42).into());
assert_eq!(
merged_state
.get_register(&register("RBX"))
.get_absolute_value()
.unwrap(),
&bv(35).into()
);
assert!(merged_state.get_register(&register("RBX")).contains_top());
assert!(merged_state
.load_value(&Var(register("RSP")), ByteSize::new(8), &global_memory)
.unwrap()
.contains_top());
state.memory.add_abstract_object(
new_id("heap_time", "heap_obj"),
ByteSize::new(8),
Some(ObjectType::Heap),
);
assert_eq!(state.memory.get_num_objects(), 3);
state.remove_unreferenced_objects();
assert_eq!(state.memory.get_num_objects(), 2);
}
#[test]
fn handle_store() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(&register("RSP"), Tid::new("time0"), BTreeSet::new());
let stack_id = new_id("time0", "RSP");
assert_eq!(
state.eval(&Var(register("RSP"))),
Data::from_target(stack_id.clone(), bv(0))
);
state.handle_register_assign(&register("RSP"), &reg_sub("RSP", 32));
assert_eq!(
state.eval(&Var(register("RSP"))),
Data::from_target(stack_id.clone(), bv(-32))
);
state.handle_register_assign(&register("RSP"), &reg_add("RSP", -8));
assert_eq!(
state.eval(&Var(register("RSP"))),
Data::from_target(stack_id.clone(), bv(-40))
);
state
.handle_store(
&reg_add("RSP", 8),
&Const(Bitvector::from_i64(1)),
&global_memory,
)
.unwrap();
state
.handle_store(
&reg_sub("RSP", 8),
&Const(Bitvector::from_i64(2)),
&global_memory,
)
.unwrap();
state
.handle_store(
&reg_add("RSP", -16),
&Const(Bitvector::from_i64(3)),
&global_memory,
)
.unwrap();
state.handle_register_assign(&register("RSP"), &reg_sub("RSP", 4));
assert_eq!(
state
.load_value(&reg_add("RSP", 12), ByteSize::new(8), &global_memory)
.unwrap(),
bv(1).into()
);
assert_eq!(
state
.load_value(&reg_sub("RSP", 4), ByteSize::new(8), &global_memory)
.unwrap(),
bv(2).into()
);
assert_eq!(
state
.load_value(&reg_add("RSP", -12), ByteSize::new(8), &global_memory)
.unwrap(),
bv(3).into()
);
}
#[test]
fn clear_parameters_on_the_stack_on_extern_calls() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(&register("RSP"), Tid::new("time0"), BTreeSet::new());
state.register.insert(
register("RSP"),
Data::from_target(new_id("time0", "RSP"), bv(-20)),
);
// write something onto the stack
state
.handle_store(
&reg_add("RSP", 8),
&Const(Bitvector::from_i64(42)),
&global_memory,
)
.unwrap();
// create an extern symbol which uses the value on the stack as a parameter
let stack_param = Arg::Stack {
address: reg_add("RSP", 8),
size: ByteSize::new(8),
data_type: None,
};
let extern_symbol = ExternSymbol {
tid: Tid::new("symbol"),
addresses: vec![],
name: "my_extern_symbol".into(),
calling_convention: None,
parameters: vec![stack_param],
return_values: Vec::new(),
no_return: false,
has_var_args: false,
};
// check the value before
let pointer = Data::from_target(new_id("time0", "RSP"), bv(-12));
assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)),
bv(42).into()
);
// clear stack parameter
state
.clear_stack_parameter(&extern_symbol, &global_memory)
.unwrap();
// check the value after
assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)),
Data::new_top(ByteSize::new(8))
);
}
#[test]
fn reachable_ids_under_and_overapproximation() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
let stack_id = new_id("func_tid", "RSP");
let heap_id = new_id("heap_obj", "RAX");
let stack_address: Data = Data::from_target(stack_id.clone(), Bitvector::from_i64(-8).into());
let heap_address: Data = Data::from_target(heap_id.clone(), Bitvector::from_i64(0).into());
// Add the heap object to the state, so that it can be recursively searched.
state
.memory
.add_abstract_object(heap_id.clone(), ByteSize::new(8), Some(ObjectType::Heap));
state
.store_value(&stack_address, &heap_address, &global_memory)
.unwrap();
let reachable_ids: BTreeSet<AbstractIdentifier> = vec![stack_id.clone()].into_iter().collect();
assert_eq!(
state.add_directly_reachable_ids_to_id_set(reachable_ids.clone()),
vec![stack_id.clone(), heap_id.clone()]
.into_iter()
.collect()
);
assert_eq!(
state.add_recursively_referenced_ids_to_id_set(reachable_ids.clone()),
vec![stack_id.clone(), heap_id.clone()]
.into_iter()
.collect()
);
let _ = state.store_value(
&Data::from_target(stack_id.clone(), ValueDomain::new_top(ByteSize::new(8))),
&Bitvector::from_i64(42).into(),
&global_memory,
);
assert_eq!(
state.add_directly_reachable_ids_to_id_set(reachable_ids.clone()),
vec![stack_id.clone(), heap_id.clone()]
.into_iter()
.collect()
);
assert_eq!(
state.add_recursively_referenced_ids_to_id_set(reachable_ids.clone()),
vec![stack_id.clone(), heap_id.clone()]
.into_iter()
.collect()
);
}
#[test]
fn global_mem_access() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(
&register("RSP"),
Tid::new("func_tid"),
BTreeSet::from([0x2000]),
);
// global read-only address
let address_expr = Expression::Const(Bitvector::from_u64(0x1000));
assert_eq!(
state
.load_value(&address_expr, ByteSize::new(4), &global_memory)
.unwrap(),
Bitvector::from_u32(0xb3b2b1b0).into() // note that we read in little-endian byte order
);
assert!(state
.write_to_address(
&address_expr,
&DataDomain::new_top(ByteSize::new(4)),
&global_memory
)
.is_err());
// global writeable address
let address_expr = Expression::Const(Bitvector::from_u64(0x2000));
assert_eq!(
state
.load_value(&address_expr, ByteSize::new(4), &global_memory)
.unwrap(),
DataDomain::new_top(ByteSize::new(4))
);
assert!(state
.write_to_address(
&address_expr,
&Bitvector::from_u32(21).into(),
&global_memory
)
.is_ok());
assert_eq!(
state
.load_value(&address_expr, ByteSize::new(4), &global_memory)
.unwrap(),
Bitvector::from_u32(21).into()
);
// invalid global address
let address_expr = Expression::Const(Bitvector::from_u64(0x3456));
assert!(state
.load_value(&address_expr, ByteSize::new(4), &global_memory)
.is_err());
assert!(state
.write_to_address(
&address_expr,
&DataDomain::new_top(ByteSize::new(4)),
&global_memory
)
.is_err());
}
/// Test expression specialization except for binary operations.
#[test]
fn specialize_by_expression_results() {
let mut base_state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
base_state.set_register(
&register("RAX"),
IntervalDomain::new(Bitvector::from_i64(5), Bitvector::from_i64(10)).into(),
);
// Expr = Var(RAX)
let mut state = base_state.clone();
let x = state
.specialize_by_expression_result(&Expression::var("RAX", 8), Bitvector::from_i64(7).into());
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
Bitvector::from_i64(7).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::var("RAX", 8),
Bitvector::from_i64(-20).into(),
);
assert!(x.is_err());
let mut state = base_state.clone();
let abstract_id = AbstractIdentifier::new(
Tid::new("heap_obj"),
AbstractLocation::from_var(&register("RAX")).unwrap(),
);
state.set_register(
&register("RAX"),
Data::from_target(abstract_id.clone(), IntervalDomain::mock(0, 50)),
);
let x = state.specialize_by_expression_result(
&Expression::var("RAX", 8),
Data::from_target(abstract_id.clone(), IntervalDomain::mock(20, 70)),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
Data::from_target(abstract_id, IntervalDomain::mock(20, 50))
);
// Expr = Const
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::Const(Bitvector::from_i64(-20)),
Bitvector::from_i64(-20).into(),
);
assert!(x.is_ok());
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::Const(Bitvector::from_i64(5)),
Bitvector::from_i64(-20).into(),
);
assert!(x.is_err());
// Expr = -Var(RAX)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::var("RAX", 8).un_op(UnOpType::Int2Comp),
Bitvector::from_i64(-7).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
Bitvector::from_i64(7).into()
);
// Expr = IntSExt(Var(EAX))
let mut state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
let eax_register = Variable {
name: "EAX".to_string(),
size: ByteSize::new(4),
is_temp: false,
};
state.set_register(
&eax_register,
IntervalDomain::new(Bitvector::from_i32(-10), Bitvector::from_i32(-5)).into(),
);
let x = state.specialize_by_expression_result(
&Expression::Var(eax_register.clone()).cast(CastOpType::IntSExt),
Bitvector::from_i64(-7).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&eax_register),
Bitvector::from_i32(-7).into()
);
// Expr = Subpiece(Var(RAX))
let mut state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
let rax_register = Variable {
name: "RAX".to_string(),
size: ByteSize::new(8),
is_temp: false,
};
let x = state.specialize_by_expression_result(
&Expression::Var(rax_register.clone()).subpiece(ByteSize::new(0), ByteSize::new(1)),
Bitvector::from_i8(5).into(),
);
assert!(x.is_ok());
assert!(state.get_register(&rax_register).is_top());
state.set_register(&rax_register, IntervalDomain::mock(3, 10).into());
let x = state.specialize_by_expression_result(
&Expression::Var(rax_register.clone()).subpiece(ByteSize::new(0), ByteSize::new(1)),
Bitvector::from_i8(5).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&rax_register),
IntervalDomain::mock(5, 5).into()
);
}
/// Test expression specialization for binary operations
/// except equality and inequality operations
#[test]
fn specialize_by_binop() {
let base_state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
// Expr = RAX + Const
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::var("RAX", 8).plus_const(20),
IntervalDomain::mock(5, 7).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock(-15, -13).into()
);
// Expr = RAX - Const
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::var("RAX", 8).minus_const(20),
Bitvector::from_i64(5).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
Bitvector::from_i64(25).into()
);
// Expr = RAX xor Const
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::var("RAX", 8)),
op: BinOpType::IntXOr,
rhs: Box::new(Expression::const_from_i64(3)),
},
Bitvector::from_i64(-1).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
Bitvector::from_i64(-4).into()
);
// Expr = (RAX or RBX == 0)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::var("RAX", 8)),
op: BinOpType::IntOr,
rhs: Box::new(Expression::var("RBX", 8)),
},
Bitvector::from_i64(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
Bitvector::from_i64(0).into()
);
assert_eq!(
state.get_register(&register("RBX")),
Bitvector::from_i64(0).into()
);
// Expr = (RAX or 0 == Const)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::var("RAX", 8)),
op: BinOpType::IntOr,
rhs: Box::new(Expression::const_from_i64(0)),
},
Bitvector::from_i64(42).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
Bitvector::from_i64(42).into()
);
// Expr = (FLAG1 bool_and FLAG2 == 1)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::Var(Variable::mock("FLAG1", 1u64))),
op: BinOpType::BoolAnd,
rhs: Box::new(Expression::Var(Variable::mock("FLAG2", 1u64))),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&Variable::mock("FLAG1", 1u64)),
Bitvector::from_u8(1).into()
);
assert_eq!(
state.get_register(&Variable::mock("FLAG2", 1u64)),
Bitvector::from_u8(1).into()
);
// Expr = (FLAG bool_and 1 = Const)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::Const(Bitvector::from_u8(1))),
op: BinOpType::BoolAnd,
rhs: Box::new(Expression::Var(Variable::mock("FLAG", 1u64))),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&Variable::mock("FLAG", 1u64)),
Bitvector::from_u8(0).into()
);
}
/// Test expression specialization for comparison operations `==` and `!=`.
#[test]
fn specialize_by_equality_comparison() {
let mut base_state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
base_state.set_register(&register("RAX"), IntervalDomain::mock(0, 50).into());
// Expr = RAX == Const
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(23)),
op: BinOpType::IntEqual,
rhs: Box::new(Expression::var("RAX", 8)),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
Bitvector::from_i64(23).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(23)),
op: BinOpType::IntNotEqual,
rhs: Box::new(Expression::var("RAX", 8)),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
Bitvector::from_i64(23).into()
);
// Expr = RAX != Const
let mut state = base_state.clone();
state.set_register(&register("RAX"), Bitvector::from_i64(23).into());
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(23)),
op: BinOpType::IntNotEqual,
rhs: Box::new(Expression::var("RAX", 8)),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_err());
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(100)),
op: BinOpType::IntEqual,
rhs: Box::new(Expression::var("RAX", 8)),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock_with_bounds(None, 0, 50, Some(99)).into()
);
}
/// Test expression specialization for signed comparison operations `<` and `<=`.
#[test]
fn specialize_by_signed_comparison_op() {
let mut base_state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
let interval = IntervalDomain::mock(5, 10);
base_state.set_register(&register("RAX"), interval.into());
// Expr = RAX < Const (signed)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(7)),
op: BinOpType::IntSLess,
rhs: Box::new(Expression::Var(register("RAX"))),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock(8, 10).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(15)),
op: BinOpType::IntSLess,
rhs: Box::new(Expression::Var(register("RAX"))),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock_with_bounds(None, 5, 10, Some(15)).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::Var(register("RAX"))),
op: BinOpType::IntSLess,
rhs: Box::new(Expression::Const(Bitvector::signed_min_value(
ByteSize::new(8).into(),
))),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_err());
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::Var(register("RAX"))),
op: BinOpType::IntSLess,
rhs: Box::new(Expression::const_from_i64(7)),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock(7, 10).into()
);
// Expr = RAX <= Const (signed)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(7)),
op: BinOpType::IntSLessEqual,
rhs: Box::new(Expression::Var(register("RAX"))),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock(7, 10).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(15)),
op: BinOpType::IntSLessEqual,
rhs: Box::new(Expression::Var(register("RAX"))),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock_with_bounds(None, 5, 10, Some(14)).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::Var(register("RAX"))),
op: BinOpType::IntSLessEqual,
rhs: Box::new(Expression::Const(Bitvector::signed_min_value(
ByteSize::new(8).into(),
))),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_err());
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::Var(register("RAX"))),
op: BinOpType::IntSLessEqual,
rhs: Box::new(Expression::const_from_i64(7)),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock(8, 10).into()
);
}
/// Test expression specialization for unsigned comparison operations `<` and `<=`.
#[test]
fn specialize_by_unsigned_comparison_op() {
let mut base_state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
let interval = IntervalDomain::mock(-5, 10);
base_state.set_register(&register("RAX"), interval.into());
// Expr = RAX < Const (unsigned)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(7)),
op: BinOpType::IntLess,
rhs: Box::new(Expression::Var(register("RAX"))),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock(-5, 10).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(15)),
op: BinOpType::IntLess,
rhs: Box::new(Expression::Var(register("RAX"))),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock_with_bounds(None, 0, 10, Some(15)).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::Var(register("RAX"))),
op: BinOpType::IntLess,
rhs: Box::new(Expression::const_from_i64(0)),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_err());
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::Var(register("RAX"))),
op: BinOpType::IntLess,
rhs: Box::new(Expression::const_from_i64(-20)),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock_with_bounds(Some(-20), -5, -1, None).into()
);
// Expr = RAX <= Const (unsigned)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(7)),
op: BinOpType::IntLessEqual,
rhs: Box::new(Expression::Var(register("RAX"))),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock(-5, 10).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::const_from_i64(15)),
op: BinOpType::IntLessEqual,
rhs: Box::new(Expression::Var(register("RAX"))),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock_with_bounds(None, 0, 10, Some(14)).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::Var(register("RAX"))),
op: BinOpType::IntLessEqual,
rhs: Box::new(Expression::const_from_i64(0)),
},
Bitvector::from_u8(1).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
Bitvector::from_i64(0).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&Expression::BinOp {
lhs: Box::new(Expression::Var(register("RAX"))),
op: BinOpType::IntLessEqual,
rhs: Box::new(Expression::const_from_i64(-20)),
},
Bitvector::from_u8(0).into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&register("RAX")),
IntervalDomain::mock_with_bounds(Some(-19), -5, -1, None).into()
);
}
#[test]
fn specialize_pointer_comparison() {
let mut state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
let interval = IntervalDomain::mock(-5, 10);
state.set_register(
&register("RAX"),
Data::from_target(new_id("func_tid", "RSP"), interval.into()),
);
let interval = IntervalDomain::mock(20, 20);
state.set_register(
&register("RBX"),
Data::from_target(new_id("func_tid", "RSP"), interval.into()),
);
let expression = Expression::BinOp {
op: BinOpType::IntEqual,
lhs: Box::new(Expression::Var(register("RAX"))),
rhs: Box::new(Expression::Var(register("RBX"))),
};
assert!(state
.clone()
.specialize_by_expression_result(&expression, Bitvector::from_i8(1).into())
.is_err());
let specialized_interval = IntervalDomain::mock_with_bounds(None, -5, 10, Some(19));
let specialized_pointer =
Data::from_target(new_id("func_tid", "RSP"), specialized_interval.into());
assert!(state
.specialize_by_expression_result(&expression, Bitvector::from_i8(0).into())
.is_ok());
assert_eq!(state.get_register(&register("RAX")), specialized_pointer);
}
/// Test that value specialization does not introduce unintended widening hints.
/// This is a regression test for cases where pointer comparisons introduced two-sided bounds
/// (resulting in two-sided widenings) instead of one-sided bounds.
#[test]
fn test_widening_hints_after_pointer_specialization() {
let mut state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
state.set_register(
&register("RAX"),
Data::from_target(new_id("func_tid", "RSP"), Bitvector::from_i64(10).into()),
);
state.set_register(
&register("RBX"),
Data::from_target(new_id("func_tid", "RSP"), Bitvector::from_i64(10).into()),
);
let expression = Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(Variable::mock("RAX", 8))),
rhs: Box::new(Expression::Var(Variable::mock("RBX", 8))),
};
let neq_expression = Expression::BinOp {
op: BinOpType::IntNotEqual,
lhs: Box::new(Expression::Const(Bitvector::from_i64(5))),
rhs: Box::new(expression),
};
state
.specialize_by_expression_result(&neq_expression, Bitvector::from_i8(1).into())
.unwrap();
state
.specialize_by_expression_result(&neq_expression, Bitvector::from_i8(1).into())
.unwrap();
let offset_with_upper_bound: IntervalDomain = Bitvector::from_i64(10).into();
let offset_with_upper_bound = offset_with_upper_bound
.add_signed_less_equal_bound(&Bitvector::from_i64(14))
.unwrap();
let expected_val = Data::from_target(new_id("func_tid", "RSP"), offset_with_upper_bound);
assert_eq!(state.get_register(&Variable::mock("RAX", 8)), expected_val);
let offset_with_lower_bound: IntervalDomain = Bitvector::from_i64(10).into();
let offset_with_lower_bound = offset_with_lower_bound
.add_signed_greater_equal_bound(&Bitvector::from_i64(6))
.unwrap();
let expected_val = Data::from_target(new_id("func_tid", "RSP"), offset_with_lower_bound);
assert_eq!(state.get_register(&Variable::mock("RBX", 8)), expected_val);
}
#[test]
fn test_check_def_for_null_dereferences() {
let mut state = State::new(&register("RSP"), Tid::new("func_tid"), BTreeSet::new());
let var_rax = Variable::mock("RAX", 8);
let def = Def::load(
"load_def",
Variable::mock("RBX", 8),
Expression::Var(var_rax.clone()),
);
state.set_register(&var_rax, Bitvector::from_i64(0).into());
assert!(state.check_def_for_null_dereferences(&def).is_err());
state.set_register(&var_rax, Bitvector::from_i64(12345).into());
assert_eq!(
state.check_def_for_null_dereferences(&def).ok(),
Some(false)
);
state.set_register(&var_rax, IntervalDomain::mock(-2000, 5).into());
assert_eq!(state.check_def_for_null_dereferences(&def).ok(), Some(true));
assert_eq!(
state.get_register(&var_rax),
IntervalDomain::mock(-2000, -1024).into()
);
let mut address = state.get_register(&register("RSP"));
address.set_contains_top_flag();
address.set_absolute_value(Some(IntervalDomain::mock(0, 0xffff)));
state.set_register(&var_rax, address);
assert_eq!(state.check_def_for_null_dereferences(&def).ok(), Some(true));
}
#[test]
fn from_fn_sig() {
let fn_sig = FunctionSignature::mock_x64();
let state = State::from_fn_sig(&fn_sig, &Variable::mock("RSP", 8), Tid::new("func"));
assert_eq!(state.memory.get_num_objects(), 3);
assert_eq!(
*state.memory.get_object(&new_id("func", "RSI")).unwrap(),
AbstractObject::new(None, ByteSize::new(8))
);
assert_eq!(
state.get_register(&Variable::mock("RSP", 8)),
Data::from_target(new_id("func", "RSP"), bv(0).into())
);
assert_eq!(
state.get_register(&Variable::mock("RDI", 8)),
Data::from_target(new_id("func", "RDI"), bv(0).into())
);
assert_eq!(
state.get_register(&Variable::mock("RSI", 8)),
Data::from_target(new_id("func", "RSI"), bv(0).into())
);
}
#[test]
fn add_param_object_from_callee() {
let global_memory = RuntimeMemoryImage::empty(true);
let mut generic_state =
State::new(&Variable::mock("RSP", 8), Tid::new("func"), BTreeSet::new());
generic_state
.write_to_address(
&Expression::Var(Variable::mock("RSP", 8)).plus_const(-8),
&bv(1).into(),
&global_memory,
)
.unwrap();
let mut param_object = AbstractObject::new(None, ByteSize::new(8));
param_object.set_value(bv(2).into(), &bv(0).into()).unwrap();
let mut param_value = Data::from_target(new_id("func", "RSP"), bv(-16).into());
// Testcase 1: param object is unique
let mut state = generic_state.clone();
state
.add_param_object_from_callee(param_object.clone(), &param_value)
.unwrap();
let value = state
.load_value(
&Expression::Var(Variable::mock("RSP", 8)).plus_const(-8),
ByteSize::new(8),
&global_memory,
)
.unwrap();
assert_eq!(value.get_absolute_value().unwrap(), &bv(1).into());
assert!(value.contains_top());
let value = state
.load_value(
&Expression::Var(Variable::mock("RSP", 8)).plus_const(-16),
ByteSize::new(8),
&global_memory,
)
.unwrap();
assert_eq!(value.get_absolute_value().unwrap(), &bv(2).into());
assert!(!value.contains_top());
// Testcase 2: param object is not unique
let mut state = generic_state.clone();
param_value.set_contains_top_flag();
state
.add_param_object_from_callee(param_object.clone(), &param_value)
.unwrap();
let value = state
.load_value(
&Expression::Var(Variable::mock("RSP", 8)).plus_const(-16),
ByteSize::new(8),
&global_memory,
)
.unwrap();
assert_eq!(value.get_absolute_value().unwrap(), &bv(2).into());
assert!(value.contains_top());
}
use super::super::ValueDomain;
use super::*;
use crate::analysis::pointer_inference::object::*;
use crate::{bitvec, def, expr, variable};
mod specialized_expressions;
fn bv(value: i64) -> ValueDomain {
ValueDomain::from(bitvec!(format!("{}:8", value)))
}
fn new_id(time: &str, register: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new(time),
AbstractLocation::Register(variable!(format!("{}:8", register))),
)
}
fn expr_bi_op(lhs: Expression, op: BinOpType, rhs: Expression) -> Expression {
Expression::BinOp {
lhs: Box::new(lhs),
op: op,
rhs: Box::new(rhs),
}
}
#[test]
fn state() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(&variable!("RSP:8"), Tid::new("time0"), BTreeSet::new());
let stack_id = new_id("time0", "RSP");
let stack_addr = Data::from_target(stack_id.clone(), bv(8));
state
.store_value(&stack_addr, &bv(42).into(), &global_memory)
.unwrap();
state
.register
.insert(variable!("RSP:8"), stack_addr.clone());
assert_eq!(
state
.load_value(&expr!("RSP:8"), ByteSize::new(8), &global_memory)
.unwrap(),
bv(42).into()
);
let mut other_state = State::new(&variable!("RSP:8"), Tid::new("time0"), BTreeSet::new());
state.register.insert(variable!("RAX:8"), bv(42).into());
other_state
.register
.insert(variable!("RSP:8"), stack_addr.clone());
other_state
.register
.insert(variable!("RAX:8"), bv(42).into());
other_state
.register
.insert(variable!("RBX:8"), bv(35).into());
let merged_state = state.merge(&other_state);
assert_eq!(merged_state.register[&variable!("RAX:8")], bv(42).into());
assert_eq!(
merged_state
.get_register(&variable!("RBX:8"))
.get_absolute_value()
.unwrap(),
&bv(35).into()
);
assert!(merged_state
.get_register(&variable!("RBX:8"))
.contains_top());
assert!(merged_state
.load_value(&expr!("RSP:8"), ByteSize::new(8), &global_memory)
.unwrap()
.contains_top());
state.memory.add_abstract_object(
new_id("heap_time", "heap_obj"),
ByteSize::new(8),
Some(ObjectType::Heap),
);
assert_eq!(state.memory.get_num_objects(), 3);
state.remove_unreferenced_objects();
assert_eq!(state.memory.get_num_objects(), 2);
}
#[test]
fn handle_store() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(&variable!("RSP:8"), Tid::new("time0"), BTreeSet::new());
let stack_id = new_id("time0", "RSP");
assert_eq!(
state.eval(&expr!("RSP:8")),
Data::from_target(stack_id.clone(), bv(0))
);
state.handle_register_assign(&variable!("RSP:8"), &expr!("RSP:8 - 32:8"));
assert_eq!(
state.eval(&expr!("RSP:8")),
Data::from_target(stack_id.clone(), bv(-32))
);
state.handle_register_assign(&variable!("RSP:8"), &expr!("RSP:8 + -8:8"));
assert_eq!(
state.eval(&expr!("RSP:8")),
Data::from_target(stack_id.clone(), bv(-40))
);
state
.handle_store(&expr!("RSP:8 + 8:8"), &expr!("1:8"), &global_memory)
.unwrap();
state
.handle_store(&expr!("RSP:8 - 8:8"), &expr!("2:8"), &global_memory)
.unwrap();
state
.handle_store(&expr!("RSP:8 + -16:8"), &expr!("3:8"), &global_memory)
.unwrap();
state.handle_register_assign(&variable!("RSP:8"), &expr!("RSP:8 - 4:8"));
assert_eq!(
state
.load_value(&expr!("RSP:8 + 12:8"), ByteSize::new(8), &global_memory)
.unwrap(),
bv(1).into()
);
assert_eq!(
state
.load_value(&expr!("RSP:8 - 4:8"), ByteSize::new(8), &global_memory)
.unwrap(),
bv(2).into()
);
assert_eq!(
state
.load_value(&expr!("RSP:8 + -12:8"), ByteSize::new(8), &global_memory)
.unwrap(),
bv(3).into()
);
}
#[test]
fn clear_parameters_on_the_stack_on_extern_calls() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(&variable!("RSP:8"), Tid::new("time0"), BTreeSet::new());
state.register.insert(
variable!("RSP:8"),
Data::from_target(new_id("time0", "RSP"), bv(-20)),
);
// write something onto the stack
state
.handle_store(&expr!("RSP:8 + 8:8"), &expr!("42:8"), &global_memory)
.unwrap();
// create an extern symbol which uses the value on the stack as a parameter
let stack_param = Arg::Stack {
address: expr!("RSP:8 + 8:8"),
size: ByteSize::new(8),
data_type: None,
};
let extern_symbol = ExternSymbol {
tid: Tid::new("symbol"),
addresses: vec![],
name: "my_extern_symbol".into(),
calling_convention: None,
parameters: vec![stack_param],
return_values: Vec::new(),
no_return: false,
has_var_args: false,
};
// check the value before
let pointer = Data::from_target(new_id("time0", "RSP"), bv(-12));
assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)),
bv(42).into()
);
// clear stack parameter
state
.clear_stack_parameter(&extern_symbol, &global_memory)
.unwrap();
// check the value after
assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)),
Data::new_top(ByteSize::new(8))
);
}
#[test]
fn reachable_ids_under_and_overapproximation() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
let stack_id = new_id("func_tid", "RSP");
let heap_id = new_id("heap_obj", "RAX");
let stack_address: Data = Data::from_target(stack_id.clone(), bitvec!("-8:8").into());
let heap_address: Data = Data::from_target(heap_id.clone(), bitvec!("0:8").into());
// Add the heap object to the state, so that it can be recursively searched.
state
.memory
.add_abstract_object(heap_id.clone(), ByteSize::new(8), Some(ObjectType::Heap));
state
.store_value(&stack_address, &heap_address, &global_memory)
.unwrap();
let reachable_ids: BTreeSet<AbstractIdentifier> = vec![stack_id.clone()].into_iter().collect();
assert_eq!(
state.add_directly_reachable_ids_to_id_set(reachable_ids.clone()),
vec![stack_id.clone(), heap_id.clone()]
.into_iter()
.collect()
);
assert_eq!(
state.add_recursively_referenced_ids_to_id_set(reachable_ids.clone()),
vec![stack_id.clone(), heap_id.clone()]
.into_iter()
.collect()
);
let _ = state.store_value(
&Data::from_target(stack_id.clone(), ValueDomain::new_top(ByteSize::new(8))),
&bitvec!("42:8").into(),
&global_memory,
);
assert_eq!(
state.add_directly_reachable_ids_to_id_set(reachable_ids.clone()),
vec![stack_id.clone(), heap_id.clone()]
.into_iter()
.collect()
);
assert_eq!(
state.add_recursively_referenced_ids_to_id_set(reachable_ids.clone()),
vec![stack_id.clone(), heap_id.clone()]
.into_iter()
.collect()
);
}
#[test]
fn global_mem_access() {
let global_memory = RuntimeMemoryImage::mock();
let mut state = State::new(
&variable!("RSP:8"),
Tid::new("func_tid"),
BTreeSet::from([0x2000]),
);
// global read-only address
let address_expr = expr!("0x1000:8");
assert_eq!(
state
.load_value(&address_expr, ByteSize::new(4), &global_memory)
.unwrap(),
bitvec!("0xb3b2b1b0:4").into() // note that we read in little-endian byte order
);
assert!(state
.write_to_address(
&address_expr,
&DataDomain::new_top(ByteSize::new(4)),
&global_memory
)
.is_err());
// global writeable address
let address_expr = expr!("0x2000:8");
assert_eq!(
state
.load_value(&address_expr, ByteSize::new(4), &global_memory)
.unwrap(),
DataDomain::new_top(ByteSize::new(4))
);
assert!(state
.write_to_address(&address_expr, &bitvec!("21:4").into(), &global_memory)
.is_ok());
assert_eq!(
state
.load_value(&address_expr, ByteSize::new(4), &global_memory)
.unwrap(),
bitvec!("21:4").into()
);
// invalid global address
let address_expr = expr!("0x3456:8");
assert!(state
.load_value(&address_expr, ByteSize::new(4), &global_memory)
.is_err());
assert!(state
.write_to_address(
&address_expr,
&DataDomain::new_top(ByteSize::new(4)),
&global_memory
)
.is_err());
}
/// Test that value specialization does not introduce unintended widening hints.
/// This is a regression test for cases where pointer comparisons introduced two-sided bounds
/// (resulting in two-sided widenings) instead of one-sided bounds.
#[test]
fn test_widening_hints_after_pointer_specialization() {
let mut state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
state.set_register(
&variable!("RAX:8"),
Data::from_target(new_id("func_tid", "RSP"), bitvec!("10:8").into()),
);
state.set_register(
&variable!("RBX:8"),
Data::from_target(new_id("func_tid", "RSP"), bitvec!("10:8").into()),
);
let neq_expression = expr_bi_op(expr!("5:8"), BinOpType::IntNotEqual, expr!("RAX:8 - RBX:8"));
state
.specialize_by_expression_result(&neq_expression, bitvec!("1:1").into())
.unwrap();
state
.specialize_by_expression_result(&neq_expression, bitvec!("1:1").into())
.unwrap();
let offset_with_upper_bound: IntervalDomain = bitvec!("10:8").into();
let offset_with_upper_bound = offset_with_upper_bound
.add_signed_less_equal_bound(&bitvec!("14:8"))
.unwrap();
let expected_val = Data::from_target(new_id("func_tid", "RSP"), offset_with_upper_bound);
assert_eq!(state.get_register(&variable!("RAX:8")), expected_val);
let offset_with_lower_bound: IntervalDomain = bitvec!("10:8").into();
let offset_with_lower_bound = offset_with_lower_bound
.add_signed_greater_equal_bound(&Bitvector::from_i64(6))
.unwrap();
let expected_val = Data::from_target(new_id("func_tid", "RSP"), offset_with_lower_bound);
assert_eq!(state.get_register(&Variable::mock("RBX", 8)), expected_val);
}
#[test]
fn test_check_def_for_null_dereferences() {
let mut state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
let var_rax = variable!("RAX:8");
let def = def![format!("load_def: RBX:8 := Load from {}", var_rax)];
state.set_register(&var_rax, bitvec!("0:8").into());
assert!(state.check_def_for_null_dereferences(&def).is_err());
state.set_register(&var_rax, bitvec!("12345:8").into());
assert_eq!(
state.check_def_for_null_dereferences(&def).ok(),
Some(false)
);
state.set_register(&var_rax, IntervalDomain::mock(-2000, 5).into());
assert_eq!(state.check_def_for_null_dereferences(&def).ok(), Some(true));
assert_eq!(
state.get_register(&var_rax),
IntervalDomain::mock(-2000, -1024).into()
);
let mut address = state.get_register(&variable!("RSP:8"));
address.set_contains_top_flag();
address.set_absolute_value(Some(IntervalDomain::mock(0, 0xffff)));
state.set_register(&var_rax, address);
assert_eq!(state.check_def_for_null_dereferences(&def).ok(), Some(true));
}
#[test]
fn from_fn_sig() {
let fn_sig = FunctionSignature::mock_x64();
let state = State::from_fn_sig(&fn_sig, &variable!("RSP:8"), Tid::new("func"));
assert_eq!(state.memory.get_num_objects(), 3);
assert_eq!(
*state.memory.get_object(&new_id("func", "RSI")).unwrap(),
AbstractObject::new(None, ByteSize::new(8))
);
assert_eq!(
state.get_register(&variable!("RSP:8")),
Data::from_target(new_id("func", "RSP"), bv(0).into())
);
assert_eq!(
state.get_register(&Variable::mock("RDI", 8)),
Data::from_target(new_id("func", "RDI"), bv(0).into())
);
assert_eq!(
state.get_register(&Variable::mock("RSI", 8)),
Data::from_target(new_id("func", "RSI"), bv(0).into())
);
}
#[test]
fn add_param_object_from_callee() {
let global_memory = RuntimeMemoryImage::empty(true);
let mut generic_state = State::new(&variable!("RSP:8"), Tid::new("func"), BTreeSet::new());
generic_state
.write_to_address(&expr!("RSP:8 + -8:8"), &bv(1).into(), &global_memory)
.unwrap();
let mut param_object = AbstractObject::new(None, ByteSize::new(8));
param_object.set_value(bv(2).into(), &bv(0).into()).unwrap();
let mut param_value = Data::from_target(new_id("func", "RSP"), bv(-16).into());
// Testcase 1: param object is unique
let mut state = generic_state.clone();
state
.add_param_object_from_callee(param_object.clone(), &param_value)
.unwrap();
let value = state
.load_value(&expr!("RSP:8 + -8:8"), ByteSize::new(8), &global_memory)
.unwrap();
assert_eq!(value.get_absolute_value().unwrap(), &bv(1).into());
assert!(value.contains_top());
let value = state
.load_value(&expr!("RSP:8 + -16:8"), ByteSize::new(8), &global_memory)
.unwrap();
assert_eq!(value.get_absolute_value().unwrap(), &bv(2).into());
assert!(!value.contains_top());
// Testcase 2: param object is not unique
let mut state = generic_state.clone();
param_value.set_contains_top_flag();
state
.add_param_object_from_callee(param_object.clone(), &param_value)
.unwrap();
let value = state
.load_value(&expr!("RSP:8 + -16:8"), ByteSize::new(8), &global_memory)
.unwrap();
assert_eq!(value.get_absolute_value().unwrap(), &bv(2).into());
assert!(value.contains_top());
}
use super::*;
/// Test expression specialization except for binary operations.
#[test]
fn specialize_by_expression_results() {
let mut base_state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
base_state.set_register(
&variable!("RAX:8"),
IntervalDomain::new(bitvec!("5:8"), bitvec!("10:8")).into(),
);
// Expr = Var(RAX)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(&expr!("RAX:8"), bitvec!("7:8").into());
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
bitvec!("7:8").into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(&expr!("RAX:8"), bitvec!("-20:8").into());
assert!(x.is_err());
let mut state = base_state.clone();
let abstract_id = AbstractIdentifier::new(
Tid::new("heap_obj"),
AbstractLocation::from_var(&variable!("RAX:8")).unwrap(),
);
state.set_register(
&variable!("RAX:8"),
Data::from_target(abstract_id.clone(), IntervalDomain::mock(0, 50)),
);
let x = state.specialize_by_expression_result(
&expr!("RAX:8"),
Data::from_target(abstract_id.clone(), IntervalDomain::mock(20, 70)),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
Data::from_target(abstract_id, IntervalDomain::mock(20, 50))
);
// Expr = Const
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(&expr!("-20:8"), bitvec!("-20:8").into());
assert!(x.is_ok());
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(&expr!("5:8"), bitvec!("-20:8").into());
assert!(x.is_err());
// Expr = -Var(RAX)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr!("RAX:8").un_op(UnOpType::Int2Comp),
bitvec!("-7:8").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
bitvec!("7:8").into()
);
// Expr = IntSExt(Var(EAX))
let mut state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
let eax_register = variable!("EAX:4");
state.set_register(
&eax_register,
IntervalDomain::new(bitvec!("-7:4"), bitvec!("-5:4")).into(),
);
let x = state.specialize_by_expression_result(
&expr!("EAX:4").cast(CastOpType::IntSExt),
bitvec!("-7:8").into(),
);
assert!(x.is_ok());
assert_eq!(state.get_register(&eax_register), bitvec!("-7:4").into());
// Expr = Subpiece(Var(RAX))
let mut state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
let rax_register = variable!("RAX:8");
let x = state.specialize_by_expression_result(
&Expression::Var(rax_register.clone()).subpiece(ByteSize::new(0), ByteSize::new(1)),
bitvec!("5:1").into(),
);
assert!(x.is_ok());
assert!(state.get_register(&rax_register).is_top());
state.set_register(&rax_register, IntervalDomain::mock(3, 10).into());
let x = state.specialize_by_expression_result(
&Expression::Var(rax_register.clone()).subpiece(ByteSize::new(0), ByteSize::new(1)),
bitvec!("5:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&rax_register),
IntervalDomain::mock(5, 5).into()
);
}
/// Test expression specialization for binary operations
/// except equality and inequality operations
#[test]
fn specialize_by_binop() {
let base_state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
// Expr = RAX + Const
let mut state = base_state.clone();
let x = state
.specialize_by_expression_result(&expr!("RAX:8 + 20:8"), IntervalDomain::mock(5, 7).into());
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock(-15, -13).into()
);
// Expr = RAX - Const
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(&expr!("RAX:8 - 20:8"), bitvec!("5:8").into());
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
bitvec!("25:8").into()
);
// Expr = RAX xor Const
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("RAX:8"), BinOpType::IntXOr, expr!("3:8")),
bitvec!("-1:8").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
bitvec!("-4:8").into()
);
// Expr = (RAX or RBX == 0)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("RAX:8"), BinOpType::IntOr, expr!("RBX:8")),
bitvec!("0:8").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
bitvec!("0:8").into()
);
assert_eq!(
state.get_register(&variable!("RBX:8")),
bitvec!("0:8").into()
);
// Expr = (RAX or 0 == Const)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("RAX:8"), BinOpType::IntOr, expr!("0:8")),
bitvec!("42:8").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
bitvec!("42:8").into()
);
// Expr = (FLAG1 bool_and FLAG2 == 1)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("FLAG1:1"), BinOpType::BoolAnd, expr!("FLAG2:1")),
bitvec!("1:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&Variable::mock("FLAG1", 1u64)),
bitvec!("1:1").into()
);
assert_eq!(
state.get_register(&Variable::mock("FLAG2", 1u64)),
bitvec!("1:1").into()
);
// Expr = (FLAG bool_and 1 = Const)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("1:1"), BinOpType::BoolAnd, expr!("FLAG:1")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("FLAG:1")),
bitvec!("0:1").into()
);
}
/// Test expression specialization for comparison operations `==` and `!=`.
#[test]
fn specialize_by_equality_comparison() {
let mut base_state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
base_state.set_register(&variable!("RAX:8"), IntervalDomain::mock(0, 50).into());
// Expr = RAX == Const
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("23:8"), BinOpType::IntEqual, expr!("RAX:8")),
bitvec!("1:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
bitvec!("23:8").into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("23:8"), BinOpType::IntNotEqual, expr!("RAX:8")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
bitvec!("23:8").into()
);
// Expr = RAX != Const
let mut state = base_state.clone();
state.set_register(&variable!("RAX:8"), bitvec!("23:8").into());
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("23:8"), BinOpType::IntNotEqual, expr!("RAX:8")),
bitvec!("1:1").into(),
);
assert!(x.is_err());
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("100:8"), BinOpType::IntEqual, expr!("RAX:8")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock_with_bounds(None, 0, 50, Some(99)).into()
);
}
/// Test expression specialization for signed comparison operations `<` and `<=`.
#[test]
fn specialize_by_signed_comparison_op() {
let mut base_state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
let interval = IntervalDomain::mock(5, 10);
base_state.set_register(&variable!("RAX:8"), interval.into());
// Expr = RAX < Const (signed)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("7:8"), BinOpType::IntSLess, expr!("RAX:8")),
bitvec!("1:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock(8, 10).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("15:8"), BinOpType::IntSLess, expr!("RAX:8")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock_with_bounds(None, 5, 10, Some(15)).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(
expr!("RAX:8"),
BinOpType::IntSLess,
Expression::Const(Bitvector::signed_min_value(ByteSize::new(8).into())),
),
bitvec!("1:1").into(),
);
assert!(x.is_err());
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("RAX:8"), BinOpType::IntSLess, expr!("7:8")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock(7, 10).into()
);
// Expr = RAX <= Const (signed)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("7:8"), BinOpType::IntSLessEqual, expr!("RAX:8")),
bitvec!("1:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock(7, 10).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("15:8"), BinOpType::IntSLessEqual, expr!("RAX:8")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock_with_bounds(None, 5, 10, Some(14)).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(
expr!("RAX:8"),
BinOpType::IntSLessEqual,
Expression::Const(Bitvector::signed_min_value(ByteSize::new(8).into())),
),
bitvec!("1:1").into(),
);
assert!(x.is_err());
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("RAX:8"), BinOpType::IntSLessEqual, expr!("7:8")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock(8, 10).into()
);
}
/// Test expression specialization for unsigned comparison operations `<` and `<=`.
#[test]
fn specialize_by_unsigned_comparison_op() {
let mut base_state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
let interval = IntervalDomain::mock(-5, 10);
base_state.set_register(&variable!("RAX:8"), interval.into());
// Expr = RAX < Const (unsigned)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("7:8"), BinOpType::IntLess, expr!("RAX:8")),
bitvec!("1:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock(-5, 10).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("15:8"), BinOpType::IntLess, expr!("RAX:8")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock_with_bounds(None, 0, 10, Some(15)).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("RAX:8"), BinOpType::IntLess, expr!("0:8")),
bitvec!("1:1").into(),
);
assert!(x.is_err());
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("RAX:8"), BinOpType::IntLess, expr!("-20:8")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock_with_bounds(Some(-20), -5, -1, None).into()
);
// Expr = RAX <= Const (unsigned)
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("7:8"), BinOpType::IntLessEqual, expr!("RAX:8")),
bitvec!("1:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock(-5, 10).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("15:8"), BinOpType::IntLessEqual, expr!("RAX:8")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock_with_bounds(None, 0, 10, Some(14)).into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("RAX:8"), BinOpType::IntLessEqual, expr!("0:8")),
bitvec!("1:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
bitvec!("0:8").into()
);
let mut state = base_state.clone();
let x = state.specialize_by_expression_result(
&expr_bi_op(expr!("RAX:8"), BinOpType::IntLessEqual, expr!("-20:8")),
bitvec!("0:1").into(),
);
assert!(x.is_ok());
assert_eq!(
state.get_register(&variable!("RAX:8")),
IntervalDomain::mock_with_bounds(Some(-19), -5, -1, None).into()
);
}
#[test]
fn specialize_pointer_comparison() {
let mut state = State::new(&variable!("RSP:8"), Tid::new("func_tid"), BTreeSet::new());
let interval = IntervalDomain::mock(-5, 10);
state.set_register(
&variable!("RAX:8"),
Data::from_target(new_id("func_tid", "RSP"), interval.into()),
);
let interval = IntervalDomain::mock(20, 20);
state.set_register(
&variable!("RBX:8"),
Data::from_target(new_id("func_tid", "RSP"), interval.into()),
);
let expression = expr_bi_op(expr!("RAX:8"), BinOpType::IntEqual, expr!("RBX:8"));
assert!(state
.clone()
.specialize_by_expression_result(&expression, bitvec!("1:1").into())
.is_err());
let specialized_interval = IntervalDomain::mock_with_bounds(None, -5, 10, Some(19));
let specialized_pointer =
Data::from_target(new_id("func_tid", "RSP"), specialized_interval.into());
assert!(state
.specialize_by_expression_result(&expression, bitvec!("0:1").into())
.is_ok());
assert_eq!(state.get_register(&variable!("RAX:8")), specialized_pointer);
}
use super::*;
use crate::{def, defs, expr, variable};
use std::borrow::BorrowMut;
/// Creates a x64 or ARM32 Project for easy addidion of assignments.
......@@ -30,9 +31,7 @@ fn unexpected_alignment() {
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u32(
0xFFFFFFFF << i,
))),
rhs: Box::new(expr!(format!("{}:4", 0xFFFFFFFF_u32 << i))),
},
)];
let mut proj_x64 = setup(def_x64, true);
......@@ -55,9 +54,7 @@ fn unexpected_alignment() {
lhs: Box::new(Expression::Var(
Project::mock_arm32().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u32(
0xFFFFFFFF << i,
))),
rhs: Box::new(expr!(format!("{}:4", 0xFFFFFFFF_u32 << i))),
},
)];
let mut proj_arm = setup(def_arm, false);
......@@ -77,26 +74,15 @@ fn unexpected_alignment() {
/// Tests the substituted offset meets the alignment for x64. Tests only the logical AND case.
fn compute_correct_offset_x64() {
for i in 0..=33 {
let sub_from_sp = Def::assign(
"tid_alter_sp",
Project::mock_x64().stack_pointer_register.clone(),
Expression::minus(
Expression::Var(Project::mock_x64().stack_pointer_register.clone()),
Expression::const_from_apint(ApInt::from_u64(i)),
),
);
let sub_from_sp = def![format!("tid_alter_sp: RSP:8 = RSP:8 - {}:8", i)];
let byte_alignment_as_and = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
variable!("RSP:8"),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(
0xFFFFFFFF_FFFFFFFF << 4, // 16 Byte alignment
))),
lhs: Box::new(expr!("RSP:8")),
rhs: Box::new(expr!(format!("{}:8", 0xFFFFFFFF_FFFFFFFF_u64 << 4))),
},
);
let mut proj = setup(
......@@ -110,20 +96,15 @@ fn compute_correct_offset_x64() {
if def.tid == byte_alignment_as_and.tid.clone() {
let expected_offset: u64 = match i % 16 {
0 => 0,
_ => (16 - (i % 16)).into(),
_ => 16 - (i % 16),
};
// translated alignment as substraction
let expected_def = Def::Assign {
var: proj.stack_pointer_register.clone(),
value: Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(proj.stack_pointer_register.clone())),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(
expected_offset,
))),
},
};
assert_eq!(expected_def, def.term);
let expected_def = def![format!(
"{} = RSP:8 - {}:8",
proj.stack_pointer_register, expected_offset
)];
assert_eq!(expected_def.term, def.term);
assert!(log.is_none());
}
}
......@@ -136,25 +117,15 @@ fn compute_correct_offset_x64() {
/// Tests the substituted offset meets the alignment for arm32. Tests only the logical AND case.
fn compute_correct_offset_arm32() {
for i in 0..=33 {
let sub_from_sp = Def::assign(
"tid_alter_sp",
Project::mock_arm32().stack_pointer_register.clone(),
Expression::minus(
Expression::Var(Project::mock_arm32().stack_pointer_register.clone()),
Expression::const_from_apint(ApInt::from_u32(i)),
),
);
let sub_from_sp = def!(format!("tid_alter_sp: sp:4 = sp:4 - {}:4", i));
let byte_alignment_as_and = Def::assign(
"tid_to_be_substituted",
Project::mock_arm32().stack_pointer_register.clone(),
variable!("sp:4"),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::Var(
Project::mock_arm32().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u32(
0xFFFFFFFF << 2, // 4 Byte alignment
))),
lhs: Box::new(expr!("sp:4")),
rhs: Box::new(expr!(format!("{}:4", 0xFFFFFFFF_u32 << 2))), // 4 Byte alignment
},
);
let mut proj = setup(
......@@ -171,17 +142,8 @@ fn compute_correct_offset_arm32() {
_ => 4 - (i % 4),
};
// translated alignment as substraction
let expected_def = Def::Assign {
var: proj.stack_pointer_register.clone(),
value: Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(proj.stack_pointer_register.clone())),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u32(
expected_offset,
))),
},
};
assert_eq!(expected_def, def.term);
let expected_def = def!(format!("sp:4 = sp:4 - {}:4", expected_offset));
assert_eq!(expected_def.term, def.term);
assert!(log.is_none());
}
}
......@@ -202,24 +164,20 @@ fn check_bin_operations() {
] {
let unsupported_def_x64 = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
variable!("RSP:8"),
Expression::BinOp {
op: biopty,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_i32(0)),
lhs: Box::new(expr!("RSP:8")),
rhs: Box::new(expr!("0:4")),
},
);
let unsupported_def_arm32 = Def::assign(
"tid_to_be_substituted",
Project::mock_arm32().stack_pointer_register.clone(),
variable!("sp:4"),
Expression::BinOp {
op: biopty,
lhs: Box::new(Expression::Var(
Project::mock_arm32().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_i32(0)),
lhs: Box::new(expr!("sp:4")),
rhs: Box::new(expr!("0:4")),
},
);
let mut proj_x64 = setup(vec![unsupported_def_x64.clone()], true);
......@@ -267,9 +225,9 @@ fn substitution_ends_if_unsubstituable() {
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(
0xFFFFFFFF_FFFFFFFF << 4, // 16 Byte alignment
))),
rhs: Box::new(
expr!(format!("{}:8", 0xFFFFFFFF_FFFFFFFF_u64 << 4)), // 16 Byte alignment
),
},
);
......@@ -278,10 +236,8 @@ fn substitution_ends_if_unsubstituable() {
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_i64(0)),
lhs: Box::new(expr!("RSP:8")),
rhs: Box::new(expr!("0:8")),
},
);
let mut proj = setup(
......@@ -302,17 +258,9 @@ fn substitution_ends_if_unsubstituable() {
.text
.contains("Unsubstitutable Operation on SP"));
let exp_16_byte_alignment_substituted = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(0))),
},
);
let exp_16_byte_alignment_substituted = defs!["tid_to_be_substituted: RSP:8 = RSP:8 - 0x0:8"]
.pop()
.unwrap();
for sub in proj.program.term.subs.into_values() {
for blk in sub.term.blocks {
......@@ -333,12 +281,10 @@ fn substitution_ends_if_unsubstituable() {
fn supports_commutative_and() {
let var_and_bitmask = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
variable!("RSP:8"),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
lhs: Box::new(expr!("RSP:8")),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(
0xFFFFFFFF_FFFFFFFF << 4, // 16 Byte alignment
))),
......@@ -349,12 +295,8 @@ fn supports_commutative_and() {
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::const_from_apint(ApInt::from_u64(
0xFFFFFFFF_FFFFFFFF << 4, // 16 Byte alignment
))),
rhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
lhs: Box::new(expr!(format!("{}:8", 0xFFFFFFFF_FFFFFFFF_u64 << 4))),
rhs: Box::new(expr!("RSP:8")),
},
);
......@@ -362,17 +304,9 @@ fn supports_commutative_and() {
let log = substitute_and_on_stackpointer(proj.borrow_mut());
assert!(log.is_none());
let expected_def = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(0))),
},
);
let expected_def = defs!["tid_to_be_substituted: RSP:8 = RSP:8 - 0x0:8"]
.pop()
.unwrap();
for sub in proj.program.term.subs.into_values() {
for blk in sub.term.blocks {
......@@ -386,18 +320,11 @@ fn supports_commutative_and() {
/// Some functions have leading blocks without any defs. This might be due to `endbr`-like instructions.
/// We skip those empty blocks and start substituting for rhe first non-empty block.
fn skips_empty_blocks() {
let sub_from_sp = Def::assign(
"tid_alter_sp",
Project::mock_x64().stack_pointer_register.clone(),
Expression::minus(
Expression::Var(Project::mock_x64().stack_pointer_register.clone()),
Expression::const_from_apint(ApInt::from_u64(1)),
),
);
let sub_from_sp = def!["tid_alter_sp: RSP:8 = RSP:8 - 1:8"];
let byte_alignment_as_and = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
variable!("RSP:8"),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::Var(
......@@ -439,14 +366,7 @@ fn skips_empty_blocks() {
.blocks
.push(blk);
let expected_def = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
Expression::minus(
Expression::Var(Project::mock_x64().stack_pointer_register.clone()),
Expression::const_from_apint(ApInt::from_u64(15)),
),
);
let expected_def = def!["tid_to_be_substituted: RSP:8 = RSP:8 - 15:8"];
substitute_and_on_stackpointer(&mut proj);
......
......@@ -7,12 +7,11 @@ use crate::{
analysis::pointer_inference::PointerInference as PointerInferenceComputation,
analysis::{
forward_interprocedural_fixpoint::Context,
string_abstraction::{
context::symbol_calls::tests::Setup,
tests::mock_project_with_intraprocedural_control_flow, tests::Setup as ProjectSetup,
},
string_abstraction::{context::symbol_calls::tests::Setup, tests::*},
},
intermediate_representation::{Bitvector, Blk, ByteSize, ExternSymbol, Jmp, Tid, Variable},
bitvec, def,
intermediate_representation::*,
variable,
};
#[test]
......@@ -28,29 +27,28 @@ fn test_update_def() {
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
setup.context.block_first_def_set = HashSet::new();
let project_setup = ProjectSetup::new();
let assign_def = project_setup.string_input_constant("assign_def", "r1", 0x7000);
let load_def = project_setup.load_var_content_from_temp_var("load_def", "r5", "r2");
let store_def = project_setup.store_var_content_at_temp_var("store_def", "r0", "r5");
let assign_def = def!["assign_def: r1:4 = 0x7000:4"];
let load_def = load_var_content_from_temp_var("load_def", "r5", "r2");
let store_def = store_var_content_at_temp_var("store_def", "r0", "r5");
let new_state = setup
.context
.update_def(&setup.state_before_call, &assign_def)
.unwrap();
let absolute_target = DataDomain::from(Bitvector::from_i32(0x7000));
let absolute_target = DataDomain::from(bitvec!("0x7000:4"));
assert_eq!(
absolute_target,
*new_state
.get_variable_to_pointer_map()
.get(&Variable::mock("r1", 4))
.get(&variable!("r1:4"))
.unwrap()
);
let stack_id = AbstractIdentifier::new(
Tid::new("func"),
AbstractLocation::from_var(&Variable::mock("sp", 4)).unwrap(),
AbstractLocation::from_var(&variable!("sp:4")).unwrap(),
);
let loaded_pointer = DataDomain::from_target(stack_id.clone(), IntervalDomain::mock_i32(4, 4));
......@@ -64,7 +62,7 @@ fn test_update_def() {
);
let r2_reg = Variable {
name: String::from("r2"),
name: "r2".into(),
size: ByteSize::new(4),
is_temp: true,
};
......@@ -79,7 +77,7 @@ fn test_update_def() {
setup
.state_before_call
.add_new_variable_to_pointer_entry(Variable::mock("r3", 4), loaded_pointer.clone());
.add_new_variable_to_pointer_entry(variable!("r3:4"), loaded_pointer.clone());
let new_state = setup
.context
......@@ -90,14 +88,14 @@ fn test_update_def() {
loaded_pointer,
*new_state
.get_variable_to_pointer_map()
.get(&Variable::mock("r5", 4))
.get(&variable!("r5:4"))
.unwrap()
);
let store_target = DataDomain::from_target(stack_id, IntervalDomain::mock_i32(12, 12));
let r0_reg = Variable {
name: String::from("r0"),
name: "r0".into(),
size: ByteSize::new(4),
is_temp: true,
};
......@@ -108,7 +106,7 @@ fn test_update_def() {
setup
.pi_state_before_symbol_call
.set_register(&Variable::mock("r5", 4), absolute_target.clone());
.set_register(&variable!("r5:4"), absolute_target.clone());
setup
.state_before_call
......@@ -165,9 +163,9 @@ fn test_update_return() {
let mut setup: Setup<CharacterInclusionDomain> = Setup::new(&pi_results);
let pointer = DataDomain::from(Bitvector::from_i32(0x6000));
let callee_saved_reg = Variable::mock("r11", 4);
let non_callee_saved_reg = Variable::mock("r0", 4);
let pointer = DataDomain::from(bitvec!("0x6000:4"));
let callee_saved_reg = variable!("r11:4");
let non_callee_saved_reg = variable!("r0:4");
setup
.state_before_call
......
use crate::intermediate_representation::*;
pub struct Setup;
impl Setup {
pub fn new() -> Self {
Setup
}
pub fn format_string_constant(&self, tid: &str, register: &str) -> Term<Def> {
Def::assign(
tid,
Variable::mock(register, 4),
Expression::const_from_i32(0x6000),
)
}
pub fn string_input_constant(&self, tid: &str, register: &str, address: i32) -> Term<Def> {
Def::assign(
tid,
Variable::mock(register, 4),
Expression::const_from_i32(address),
)
}
// FIXME: Move this function to the intermediate_representation module
pub fn pointer_plus_offset(
&self,
tid: &str,
output: &str,
pointer: &str,
offset: i64,
) -> Term<Def> {
Def::assign(
tid,
Variable::mock(output, 4),
Expression::var(pointer, 4).plus_const(offset),
)
}
// FIXME: Move this function to the intermediate_representation module
pub fn pointer_minus_offset(
&self,
tid: &str,
output: &str,
pointer: &str,
offset: i64,
) -> Term<Def> {
Def::assign(
tid,
Variable::mock(output, 4),
Expression::var(pointer, 4).minus_const(offset),
)
}
// FIXME: Move this function to the intermediate_representation module
pub fn pointer_plus_offset_to_temp_var(
&self,
tid: &str,
tmp_name: &str,
pointer: &str,
offset: i64,
) -> Term<Def> {
Def::assign(
tid,
Variable {
name: String::from(tmp_name),
size: ByteSize::new(4),
is_temp: true,
},
Expression::var(pointer, 4).plus_const(offset),
)
}
use crate::{def, defs, expr, intermediate_representation::*, variable};
// FIXME: Move this function to the intermediate_representation module
pub fn pointer_plus_offset_to_temp_var(
tid: &str,
tmp_name: &str,
pointer: &str,
offset: i64,
) -> Term<Def> {
Def::assign(
tid,
Variable {
name: String::from(tmp_name),
size: ByteSize::new(4),
is_temp: true,
},
expr!(format!("{}:4 + {}:4", pointer, offset)),
)
}
// FIXME: Move this function to the intermediate_representation module
pub fn store_var_content_at_temp_var(&self, tid: &str, tmp_name: &str, var: &str) -> Term<Def> {
Def::store(
tid,
Expression::Var(Variable {
name: String::from(tmp_name),
size: ByteSize::new(4),
is_temp: true,
}),
Expression::var(var, 4),
)
}
// FIXME: Move this function to the intermediate_representation module
pub fn store_var_content_at_temp_var(tid: &str, tmp_name: &str, var: &str) -> Term<Def> {
Def::store(
tid,
Expression::Var(Variable {
name: String::from(tmp_name),
size: ByteSize::new(4),
is_temp: true,
}),
expr!(format!("{}:4", var)),
)
}
// FIXME: Move this function to the intermediate_representation module
pub fn load_var_content_from_temp_var(
&self,
tid: &str,
var: &str,
tmp_name: &str,
) -> Term<Def> {
Def::load(
tid,
Variable::mock(var, 4 as u64),
Expression::Var(Variable {
name: String::from(tmp_name),
size: ByteSize::new(4),
is_temp: true,
}),
)
}
// FIXME: Move this function to the intermediate_representation module
pub fn load_var_content_from_temp_var(tid: &str, var: &str, tmp_name: &str) -> Term<Def> {
Def::load(
tid,
variable!(format!("{}:4", var)),
Expression::Var(Variable {
name: String::from(tmp_name),
size: ByteSize::new(4),
is_temp: true,
}),
)
}
fn mock_defs_for_sprintf(format_known: bool, blk_num: usize) -> Vec<Term<Def>> {
let setup = Setup::new();
let mut defs: Vec<Term<Def>> = Vec::new();
/*
r11 = INT_ADD sp, 4:4
r12 = COPY 0x3002:4
r0 = INT_SUB r11, 0x58:4 // Destination string pointer
r1 = COPY 0x6000:4 // Constant format string
OR
r1 = INT_SUB r11, 0x62:4 // Variable format string
r2 = INT_ADD sp, 24:4 // Variable input in register
r3 = INT_ADD sp, 16:4 // Variable input in register
$U1050:4 = INT_ADD sp, 0:4 // Constant string input 'Hello World' on stack
STORE ram($U1050:4), r12
r12 = INT_ADD r11, 0x66:4
$U1050:4 = INT_ADD sp, 4:4 // Second variable input on stack
STORE ram($U1050:4), r12
*/
defs.push(setup.pointer_plus_offset(&format!("def_0_blk_{}", blk_num), "r11", "sp", 4));
defs.push(setup.string_input_constant(&format!("def_1_blk_{}", blk_num), "r12", 0x3002));
defs.push(setup.pointer_minus_offset(&format!("def_2_blk_{}", blk_num), "r0", "r11", 0x58));
let mut defs = defs![
format!("def_0_blk_{}: r11:4 = sp:4 + 0x4:4", blk_num),
format!("def_1_blk_{}: r12:4 = 0x3002:4", blk_num),
format!("def_2_blk_{}: r0:4 = r11:4 - 0x58:4", blk_num)
];
if format_known {
defs.push(setup.format_string_constant(&format!("def_3_blk_{}", blk_num), "r1"));
defs.append(defs![&format!("def_3_blk_{}: r1:4 = 0x6000:4", blk_num)].as_mut());
} else {
defs.push(setup.pointer_minus_offset(&format!("def_3_blk_{}", blk_num), "r1", "r11", 0x62));
defs.append(defs![&format!("def_3_blk_{}: r1:4 = r11:4 - 0x62:4", blk_num)].as_mut());
}
defs.push(setup.pointer_plus_offset(&format!("def_4_blk_{}", blk_num), "r2", "sp", 24));
defs.push(setup.pointer_plus_offset(&format!("def_5_blk_{}", blk_num), "r3", "sp", 16));
defs.append(
defs![
&format!("def_4_blk_{}: r2:4 = sp:4 + 0x18:4", blk_num),
&format!("def_5_blk_{}: r3:4 = sp:4 + 0x10:4", blk_num)
]
.as_mut(),
);
defs.push(setup.pointer_plus_offset_to_temp_var(
defs.push(pointer_plus_offset_to_temp_var(
&format!("def_6_blk_{}", blk_num),
"$U1050",
"sp",
0,
));
defs.push(setup.store_var_content_at_temp_var(
defs.push(store_var_content_at_temp_var(
&format!("def_7_blk_{}", blk_num),
"$U1050",
"r12",
));
defs.push(setup.pointer_plus_offset(&format!("def_8_blk_{}", blk_num), "r12", "r11", 0x66));
defs.push(def![&format!(
"def_8_blk_{}: r12:4 = r11:4 + 0x66:4",
blk_num
)]);
defs.push(setup.pointer_plus_offset_to_temp_var(
defs.push(pointer_plus_offset_to_temp_var(
&format!("def_9_blk_{}", blk_num),
"$U1050",
"sp",
4,
));
defs.push(setup.store_var_content_at_temp_var(
defs.push(store_var_content_at_temp_var(
&format!("def_10_blk_{}", blk_num),
"$U1050",
"r12",
......@@ -177,67 +114,60 @@ fn mock_defs_for_sprintf(format_known: bool, blk_num: usize) -> Vec<Term<Def>> {
}
fn mock_defs_for_scanf(format_known: bool, blk_num: usize) -> Vec<Term<Def>> {
let setup = Setup::new();
let mut defs: Vec<Term<Def>> = Vec::new();
/*
r11 = INT_ADD sp, 4:4
r0 = INT_SUB r11, 0x3c:4
$U1050 = INT_ADD sp, 0:4
STORE ram($U1050:4), r0 - variable output 4
r3 = INT_SUB r11, 0x50:4 - variable output 3
r2 = INT_SUB r11, 0x62:4 - variable output 2
r1 = INT_SUB r11, 0x78:4 - variable output 1
r0 = LOAD ram(0x6000) - constant format string
OR
r0 = INT_SUB r11, 0x82:4 - variable format string
*/
defs.push(setup.pointer_plus_offset(&format!("def_0_blk_{}", blk_num), "r11", "sp", 4));
defs.push(setup.pointer_minus_offset(&format!("def_1_blk_{}", blk_num), "r0", "r11", 0x3c));
let mut defs = defs![
format!("def_0_blk_{}: r11:4 = sp:4 + 4:4", blk_num),
format!("def_1_blk_{}: r0:4 = r11:4 - 0x3c:4", blk_num)
];
defs.push(setup.pointer_plus_offset_to_temp_var(
defs.push(pointer_plus_offset_to_temp_var(
&format!("def_2_blk_{}", blk_num),
"$U1050",
"sp",
0,
));
defs.push(setup.store_var_content_at_temp_var(
defs.push(store_var_content_at_temp_var(
&format!("def_3_blk_{}", blk_num),
"$U1050",
"r0",
));
defs.push(setup.pointer_minus_offset(&format!("def_4_blk_{}", blk_num), "r3", "r11", 0x50));
defs.push(setup.pointer_minus_offset(&format!("def_5_blk_{}", blk_num), "r2", "r11", 0x62));
defs.push(setup.pointer_minus_offset(&format!("def_6_blk_{}", blk_num), "r1", "r11", 0x78));
defs.append(
defs![
format!("def_4_blk_{}: r3:4 = r11:4 - 0x50:4", blk_num),
format!("def_5_blk_{}: r2:4 = r11:4 - 0x62:4", blk_num),
format!("def_6_blk_{}: r1:4 = r11:4 - 0x78:4", blk_num)
]
.as_mut(),
);
if format_known {
defs.push(setup.format_string_constant(&format!("def_7_blk_{}", blk_num), "r0"));
defs.push(def![format!("def_7_blk_{}: r0:4 = 0x6000:4", blk_num)]);
} else {
defs.push(setup.pointer_minus_offset(&format!("def_7_blk_{}", blk_num), "r0", "r11", 0x82));
defs.push(def![format!(
"def_7_blk_{}: r0:4 = r11:4 - 0x82:4",
blk_num
)]);
}
defs
}
fn mock_defs_for_sscanf(source_known: bool, format_known: bool, blk_num: usize) -> Vec<Term<Def>> {
let setup = Setup::new();
let mut defs: Vec<Term<Def>> = Vec::new();
/*
r11 = INT_ADD sp, 4:4
r3 = INT_SUB r11, 0x96:4
$U1050:4 = INT_ADD sp, 0:4
......@@ -261,134 +191,125 @@ fn mock_defs_for_sscanf(source_known: bool, format_known: bool, blk_num: usize)
r0 = INT_SUB r11, 048:4 - variable source string
*/
let mut defs = defs![
format!("def_0_blk_{}: r11:4 = sp:4 + 4:4", blk_num),
format!("def_1_blk_{}: r3:4 = r11:4 - 0x96:4", blk_num)
];
defs.push(setup.pointer_plus_offset(&format!("def_0_blk_{}", blk_num), "r11", "sp", 4));
defs.push(setup.pointer_minus_offset(&format!("def_1_blk_{}", blk_num), "r3", "r11", 0x96));
defs.push(setup.pointer_plus_offset_to_temp_var(
defs.push(pointer_plus_offset_to_temp_var(
&format!("def_2_blk_{}", blk_num),
"$U1050",
"sp",
0,
));
defs.push(setup.store_var_content_at_temp_var(
defs.push(store_var_content_at_temp_var(
&format!("def_3_blk_{}", blk_num),
"$U1050",
"r3",
));
defs.push(setup.pointer_minus_offset(&format!("def_4_blk_{}", blk_num), "r3", "r11", 0x88));
defs.push(def![format!(
"def_4_blk_{}: r3:4 = r11:4 - 0x88:4",
blk_num
)]);
defs.push(setup.pointer_plus_offset_to_temp_var(
defs.push(pointer_plus_offset_to_temp_var(
&format!("def_5_blk_{}", blk_num),
"$U1050",
"sp",
4,
));
defs.push(setup.store_var_content_at_temp_var(
defs.push(store_var_content_at_temp_var(
&format!("def_6_blk_{}", blk_num),
"$U1050",
"r3",
));
defs.push(setup.pointer_minus_offset(&format!("def_7_blk_{}", blk_num), "r3", "r11", 0x6c));
defs.push(setup.pointer_minus_offset(&format!("def_8_blk_{}", blk_num), "r2", "r11", 0x80));
defs.append(
defs![
format!("def_7_blk_{}: r3:4 = r11:4 - 0x6c:4", blk_num),
format!("def_8_blk_{}: r2:4 = r11:4 - 0x80:4", blk_num)
]
.as_mut(),
);
if format_known {
defs.push(setup.format_string_constant(&format!("def_9_blk_{}", blk_num), "r1"));
defs.push(def![format!("def_9_blk_{}: r1:4 = 0x6000:4", blk_num)]);
} else {
defs.push(setup.pointer_minus_offset(&format!("def_9_blk_{}", blk_num), "r1", "r11", 0x40));
defs.push(def![format!(
"def_9_blk_{}: r1:4 = r11:4 - 0x40:4",
blk_num
)]);
}
if source_known {
defs.push(setup.string_input_constant(&format!("def_10_blk_{}", blk_num), "r0", 0x7000));
defs.push(def![format!("def_10_blk_{}: r0:4 = 0x7000:4", blk_num)]);
} else {
defs.push(setup.pointer_minus_offset(
&format!("def_10_blk_{}", blk_num),
"r0",
"r11",
0x48,
));
defs.push(def![format!(
"def_10_blk_{}: r0:4 = r11:4 - 0x48:4",
blk_num
)]);
}
defs
}
fn mock_defs_for_strcat(second_input_known: bool, blk_num: usize) -> Vec<Term<Def>> {
let setup = Setup::new();
let mut defs: Vec<Term<Def>> = Vec::new();
/*
r11 = INT_ADD sp, 4:4
r0 = INT_SUB r11, 40:4,
r1 = LOAD ram(0x7000)
OR
r1 = INT_ADD r11, 0x24:4
*/
defs.push(setup.pointer_plus_offset(&format!("def_0_blk_{}", blk_num), "r11", "sp", 4));
defs.push(setup.pointer_minus_offset(&format!("def_1_blk_{}", blk_num), "r0", "r11", 0x40));
let mut def = defs![
format!("def_0_blk_{}: r11:4 = sp:4 + 4:4", blk_num),
format!("def_1_blk_{}: r0:4 = r11:4 - 0x40:4", blk_num)
];
if second_input_known {
defs.push(setup.string_input_constant(&format!("def_2_blk_{}", blk_num), "r1", 0x7000));
def.push(def![format!("def_2_blk_{}: r1:4 = 0x7000:4", blk_num)]);
} else {
defs.push(setup.pointer_plus_offset(&format!("def_3_blk_{}", blk_num), "r1", "r11", 0x24));
def.push(def![format!(
"def_3_blk_{}: r1:4 = r11:4 + 0x24:4",
blk_num
)]);
}
defs
}
fn mock_defs_for_free(_blk_num: usize) -> Vec<Term<Def>> {
vec![]
def
}
fn mock_defs_for_malloc(blk_num: usize) -> Vec<Term<Def>> {
let setup = Setup::new();
let mut defs: Vec<Term<Def>> = Vec::new();
/*
r0 = COPY 0xf
*/
defs.push(setup.string_input_constant(&format!("def_0_blk_{}", blk_num), "r0", 0xf));
defs
defs![format!("def_0_blk_{}: r0:4 = 0xf:4", blk_num)]
}
fn mock_defs_for_memcpy(copy_from_global: bool, blk_num: usize) -> Vec<Term<Def>> {
let setup = Setup::new();
let mut defs: Vec<Term<Def>> = Vec::new();
/*
r11 = INT_ADD sp, 4:4
r0 = INT_SUB r11, 0x40:4,
r1 = LOAD ram(0x7000)
OR
r1 = INT_ADD r11, 0x24:4
*/
defs.push(setup.pointer_plus_offset(&format!("def_0_blk_{}", blk_num), "r11", "sp", 4));
defs.push(setup.pointer_minus_offset(&format!("def_1_blk_{}", blk_num), "r0", "r11", 0x40));
let mut def = defs![
format!("def_0_blk_{}: r11:4 = sp:4 + 4:4", blk_num),
format!("def_1_blk_{}: r0:4 = r11:4 - 0x40:4", blk_num)
];
if copy_from_global {
defs.push(setup.string_input_constant(&format!("def_2_blk_{}", blk_num), "r1", 0x7000));
def.push(def![format!("def_2_blk_{}: r1:4 = 0x7000:4", blk_num)]);
} else {
defs.push(setup.pointer_plus_offset(&format!("def_3_blk_{}", blk_num), "r1", "r11", 0x24));
def.push(def![format!(
"def_3_blk_{}: r1:4 = r11:4 + 0x24:4",
blk_num
)])
}
defs
def
}
impl ExternSymbol {
......@@ -495,7 +416,7 @@ fn mock_block_with_function_call(
"scanf" => mock_defs_for_scanf(*config.get(0).unwrap(), blk_num),
"sscanf" => mock_defs_for_sscanf(*config.get(0).unwrap(), *config.get(1).unwrap(), blk_num),
"strcat" => mock_defs_for_strcat(*config.get(0).unwrap(), blk_num),
"free" => mock_defs_for_free(blk_num),
"free" => vec![],
"malloc" => mock_defs_for_malloc(blk_num),
"memcpy" => mock_defs_for_memcpy(*config.get(0).unwrap(), blk_num),
_ => panic!("Invalid symbol name for def mock"),
......
......@@ -171,6 +171,7 @@ impl<'a> Context<'a> {
pub mod tests {
use super::*;
use crate::analysis::pointer_inference::Data;
use crate::{bitvec, intermediate_representation::parsing};
use std::collections::{HashMap, HashSet};
#[test]
......@@ -183,9 +184,8 @@ pub mod tests {
let malloc_call_id = AbstractIdentifier::mock("malloc_call", "RAX", 8);
let main_stack_id = AbstractIdentifier::mock("main", "RSP", 8);
let param_value = Data::from_target(malloc_call_id.clone(), Bitvector::from_i64(2).into());
let param_value_2 =
Data::from_target(main_stack_id.clone(), Bitvector::from_i64(-10).into());
let param_value = Data::from_target(malloc_call_id.clone(), bitvec!("2:8").into());
let param_value_2 = Data::from_target(main_stack_id.clone(), bitvec!("-10:8").into());
let param_replacement_map = HashMap::from([
(callsite_id, param_value.clone()),
(callsite_id_2, param_value_2.clone()),
......@@ -196,7 +196,7 @@ pub mod tests {
context.callee_to_callsites_map = callee_to_callsites_map;
context
.malloc_tid_to_object_size_map
.insert(Tid::new("malloc_call"), Data::from(Bitvector::from_i64(42)));
.insert(Tid::new("malloc_call"), Data::from(bitvec!("42:8")));
context.call_to_caller_fn_map = HashMap::from([
(Tid::new("malloc_call"), Tid::new("main")),
(Tid::new("callsite_id"), Tid::new("main")),
......
......@@ -166,7 +166,7 @@ impl<'a> Context<'a> {
};
let mut cwe_warning =
CweWarning::new("CWE119", super::CWE_MODULE.version, description);
cwe_warning.tids = vec![format!("{}", call_tid)];
cwe_warning.tids = vec![format!("{call_tid}")];
cwe_warning.addresses = vec![call_tid.address.to_string()];
cwe_warning.other = vec![warnings];
self.log_collector.send(cwe_warning.into()).unwrap();
......
......@@ -218,6 +218,7 @@ fn add_param_replacements_for_call(
#[cfg(test)]
pub mod tests {
use super::*;
use crate::bitvec;
#[test]
fn test_substitute_param_values_context_sensitive() {
......@@ -228,9 +229,8 @@ pub mod tests {
let recursive_param_id = AbstractIdentifier::mock("main", "RSI", 8);
let recursive_callsite_id = AbstractIdentifier::mock("recursive_callsite_id", "RSI", 8);
let param_value =
Data::from_target(recursive_param_id.clone(), Bitvector::from_i64(1).into());
let recursive_param_value = Data::from(Bitvector::from_i64(41));
let param_value = Data::from_target(recursive_param_id.clone(), bitvec!("1:8").into());
let recursive_param_value = Data::from(bitvec!("41:8"));
let param_replacement_map = HashMap::from([
(callsite_id, param_value.clone()),
(recursive_callsite_id.clone(), recursive_param_value),
......@@ -254,7 +254,7 @@ pub mod tests {
context.call_to_caller_fn_map = call_to_caller_map;
// non-recursive substitution
let result = context.substitute_param_values_context_sensitive(
&Data::from_target(param_id.clone(), Bitvector::from_i64(5).into()),
&Data::from_target(param_id.clone(), bitvec!("5:8").into()),
&Tid::new("callsite_id"),
&Tid::new("func"),
);
......@@ -264,12 +264,12 @@ pub mod tests {
);
// recursive substitution
let result = context.recursively_substitute_param_values_context_sensitive(
&Data::from_target(param_id, Bitvector::from_i64(5).into()),
&Data::from_target(param_id, bitvec!("5:8").into()),
&Tid::new("func"),
&[Tid::new("callsite_id"), Tid::new("recursive_callsite_id")],
);
println!("{:#}", result.to_json_compact());
assert_eq!(result, Bitvector::from_i64(47).into());
assert_eq!(result, bitvec!("47:8").into());
}
#[test]
......@@ -281,9 +281,8 @@ pub mod tests {
let recursive_param_id = AbstractIdentifier::mock("main", "RSI", 8);
let recursive_callsite_id = AbstractIdentifier::mock("recursive_callsite_id", "RSI", 8);
let param_value =
Data::from_target(recursive_param_id.clone(), Bitvector::from_i64(1).into());
let recursive_param_value = Data::from(Bitvector::from_i64(39));
let param_value = Data::from_target(recursive_param_id.clone(), bitvec!("1:8").into());
let recursive_param_value = Data::from(bitvec!("39:8"));
let param_replacement_map = HashMap::from([
(callsite_id, param_value.clone()),
(recursive_callsite_id.clone(), recursive_param_value),
......@@ -304,8 +303,8 @@ pub mod tests {
// recursive substitution
let result = context.recursively_substitute_param_values(&Data::from_target(
param_id,
Bitvector::from_i64(5).into(),
bitvec!("5:8").into(),
));
assert_eq!(result, Bitvector::from_i64(45).into());
assert_eq!(result, bitvec!("45:8").into());
}
}
use super::*;
use crate::{bitvec, variable};
use std::collections::BTreeSet;
impl<'a> Context<'a> {
......@@ -28,9 +29,8 @@ fn test_compute_size_value_of_malloc_like_call() {
use crate::analysis::pointer_inference::State as PiState;
let project = Project::mock_x64();
let mut pi_results = PointerInference::mock(&project);
let mut malloc_state =
PiState::new(&Variable::mock("RSP", 8), Tid::new("func"), BTreeSet::new());
malloc_state.set_register(&Variable::mock("RDI", 8), Bitvector::from_i64(3).into());
let mut malloc_state = PiState::new(&variable!("RSP:8"), Tid::new("func"), BTreeSet::new());
malloc_state.set_register(&variable!("RDI:8"), bitvec!("3:8").into());
*pi_results.get_mut_states_at_tids() = HashMap::from([(Tid::new("malloc_call"), malloc_state)]);
let malloc_symbol = ExternSymbol::mock_x64("malloc");
......@@ -41,7 +41,7 @@ fn test_compute_size_value_of_malloc_like_call() {
&pi_results
)
.unwrap(),
Bitvector::from_i64(3).into()
bitvec!("3:8").into()
);
assert!(compute_size_value_of_malloc_like_call(
&Tid::new("other"),
......
......@@ -73,11 +73,7 @@ impl State {
if let Ok((lower_offset, upper_offset)) = offset.try_to_offset_interval() {
if let Ok(lower_bound) = self.object_lower_bounds.get(id).unwrap().try_to_offset() {
if lower_bound > lower_offset {
out_of_bounds_access_warnings.push(format!("For the object ID {} access to the offset {} may be smaller than the lower object bound of {}.",
id,
lower_offset,
lower_bound,
));
out_of_bounds_access_warnings.push(format!("For the object ID {id} access to the offset {lower_offset} may be smaller than the lower object bound of {lower_bound}."));
if let (
Some(BoundsMetadata {
source: Some(source),
......@@ -93,7 +89,7 @@ impl State {
context,
);
out_of_bounds_access_warnings
.push(format!("Relevant callgraph TIDs: [{}]", call_sequence_tids));
.push(format!("Relevant callgraph TIDs: [{call_sequence_tids}]"));
} else {
out_of_bounds_access_warnings.push(format!(
"Relevant callgraph TIDs: [{}]",
......@@ -128,7 +124,7 @@ impl State {
context,
);
out_of_bounds_access_warnings
.push(format!("Relevant callgraph TIDs: [{}]", call_sequence_tids));
.push(format!("Relevant callgraph TIDs: [{call_sequence_tids}]"));
} else {
out_of_bounds_access_warnings.push(format!(
"Relevant callgraph TIDs: [{}]",
......@@ -204,13 +200,13 @@ impl State {
let lower_bounds: Vec<_> = self
.object_lower_bounds
.iter()
.map(|(id, bound)| Value::String(format!("{}: {}", id, bound)))
.map(|(id, bound)| Value::String(format!("{id}: {bound}")))
.collect();
state_map.insert("lower_bounds".to_string(), Value::Array(lower_bounds));
let upper_bounds: Vec<_> = self
.object_upper_bounds
.iter()
.map(|(id, bound)| Value::String(format!("{}: {}", id, bound)))
.map(|(id, bound)| Value::String(format!("{id}: {bound}")))
.collect();
state_map.insert("upper_bounds".to_string(), Value::Array(upper_bounds));
......@@ -265,8 +261,8 @@ fn collect_tids_for_cwe_warning(
}
// Build a string out of the TID list
tids.iter()
.map(|tid| format!("{}", tid))
.reduce(|accum, elem| format!("{}, {}", accum, elem))
.map(|tid| format!("{tid}"))
.reduce(|accum, elem| format!("{accum}, {elem}"))
.unwrap()
}
......
......@@ -169,7 +169,7 @@ fn generate_cwe_warning(
_ => panic!("Invalid String Location."),
};
CweWarning::new(CWE_MODULE.name, CWE_MODULE.version, description)
.tids(vec![format!("{}", callsite)])
.tids(vec![format!("{callsite}")])
.addresses(vec![callsite.address.clone()])
.symbols(vec![called_symbol.name.clone()])
}
......@@ -179,7 +179,7 @@ pub mod tests {
use std::collections::HashSet;
use crate::analysis::pointer_inference::PointerInference as PointerInferenceComputation;
use crate::intermediate_representation::*;
use crate::{defs, intermediate_representation::*};
use super::*;
......@@ -189,21 +189,10 @@ pub mod tests {
let mut block1 = Blk::mock_with_tid("block1");
let block2 = Blk::mock_with_tid("block2");
let def1 = Def::assign(
"def2",
Variable::mock("RDI", 8 as u64),
Expression::var("RBP", 8).plus_const(8),
);
let def2 = Def::assign(
"def3",
Variable::mock("RSI", 8 as u64),
Expression::Const(Bitvector::from_str_radix(16, "3002").unwrap()),
);
let mut defs = defs!["def2: RDI:8 = RBP:8 + 8:8", "def3: RSI:8 = 0x3002:8"];
let jump = Jmp::call("call_string", "sprintf", Some("block2"));
block1.term.defs.push(def1);
block1.term.defs.push(def2);
block1.term.defs.append(&mut defs);
block1.term.jmps.push(jump);
sub.term.blocks.push(block1);
sub.term.blocks.push(block2);
......
......@@ -98,7 +98,7 @@ fn generate_cwe_warning(callsite: &Tid, called_symbol: &ExternSymbol) -> CweWarn
"(Integer Overflow or Wraparound) Potential overflow due to multiplication before call to {} at {}",
called_symbol.name, callsite.address
))
.tids(vec![format!("{}", callsite)])
.tids(vec![format!("{callsite}")])
.addresses(vec![callsite.address.clone()])
.symbols(vec![called_symbol.name.clone()])
}
......
......@@ -63,7 +63,7 @@ pub fn check_cwe(
(vec![info_log], Vec::new())
}
Err(err) => {
let err_log = LogMessage::new_error(format!("Error while parsing binary: {}", err))
let err_log = LogMessage::new_error(format!("Error while parsing binary: {err}"))
.source(CWE_MODULE.name);
(vec![err_log], Vec::new())
}
......
......@@ -99,7 +99,7 @@ fn generate_cwe_warning(sub: &Term<Sub>, callsite: &Tid) -> CweWarning {
"(The program utilizes chroot without dropping privileges and/or changing the directory) at {} ({})",
callsite.address, sub.term.name
))
.tids(vec![format!("{}", callsite)])
.tids(vec![format!("{callsite}")])
.addresses(vec![callsite.address.clone()])
.symbols(vec![sub.term.name.clone()])
}
......
......@@ -48,9 +48,7 @@ fn generate_cwe_warning(secure_initializer_func: &str, rand_func: &str) -> CweWa
CWE_MODULE.name,
CWE_MODULE.version,
format!(
"(Insufficient Entropy in PRNG) program uses {} without calling {} before",
rand_func, secure_initializer_func
),
"(Insufficient Entropy in PRNG) program uses {rand_func} without calling {secure_initializer_func} before"),
)
}
......
......@@ -61,7 +61,7 @@ fn generate_cwe_warning(
"(Time-of-check Time-of-use Race Condition) '{}' is reachable from '{}' at {} ({}). This could lead to a TOCTOU.",
sink, source, sink_callsite.address, sub_name
))
.tids(vec![format!("{}", source_callsite), format!("{}", sink_callsite)])
.tids(vec![format!("{source_callsite}"), format!("{sink_callsite}")])
.addresses(vec![source_callsite.address, sink_callsite.address])
.symbols(vec![source.into(), sink.into()])
}
......
......@@ -117,7 +117,7 @@ impl<'a> Context<'a> {
name: "CWE416".to_string(),
version: CWE_MODULE.version.to_string(),
addresses: vec![call_tid.address.clone()],
tids: vec![format!("{}", call_tid)],
tids: vec![format!("{call_tid}")],
symbols: Vec::new(),
other: vec![warnings],
description: format!(
......@@ -151,7 +151,7 @@ impl<'a> Context<'a> {
name: "CWE415".to_string(),
version: CWE_MODULE.version.to_string(),
addresses: vec![call_tid.address.clone()],
tids: vec![format!("{}", call_tid)],
tids: vec![format!("{call_tid}")],
symbols: Vec::new(),
other: vec![warning_causes],
description: format!(
......
......@@ -63,8 +63,7 @@ impl State {
for id in address.get_relative_values().keys() {
if let Some(ObjectState::Dangling(free_id)) = self.dangling_objects.get(id) {
free_ids_of_dangling_pointers.push(format!(
"Accessed ID {} may have been already freed at {}",
id, free_id
"Accessed ID {id} may have been already freed at {free_id}"
));
self.dangling_objects
......@@ -101,8 +100,7 @@ impl State {
.insert(id.clone(), ObjectState::Dangling(call_tid.clone()))
{
warnings.push(format!(
"Object {} may have been freed before at {}.",
id, old_free_id
"Object {id} may have been freed before at {old_free_id}."
));
}
}
......@@ -169,7 +167,7 @@ impl AbstractDomain for State {
#[cfg(test)]
pub mod tests {
use super::*;
use crate::intermediate_representation::Variable;
use crate::{bitvec, intermediate_representation::parsing, variable};
use std::collections::BTreeSet;
#[test]
......@@ -223,9 +221,9 @@ pub mod tests {
let mut state = State::new(Tid::new("current_fn"));
let param = Data::from_target(
AbstractIdentifier::mock("obj_id", "RAX", 8),
Bitvector::from_i64(0).into(),
bitvec!("0:8").into(),
);
let pi_state = PiState::new(&Variable::mock("RSP", 8), Tid::new("call"), BTreeSet::new());
let pi_state = PiState::new(&variable!("RSP:8"), Tid::new("call"), BTreeSet::new());
// Check that the parameter is correctly marked as freed in the state.
assert!(state
.handle_param_of_free_call(&Tid::new("free_call"), &param, &pi_state)
......@@ -251,12 +249,12 @@ pub mod tests {
AbstractIdentifier::mock("callee_obj_tid", "RAX", 8),
ObjectState::Dangling(Tid::new("free_tid")),
);
let pi_state = PiState::new(&Variable::mock("RSP", 8), Tid::new("call"), BTreeSet::new());
let pi_state = PiState::new(&variable!("RSP:8"), Tid::new("call"), BTreeSet::new());
let id_replacement_map = BTreeMap::from([(
AbstractIdentifier::mock("callee_obj_tid", "RAX", 8),
Data::from_target(
AbstractIdentifier::mock("caller_tid", "RBX", 8),
Bitvector::from_i64(42).into(),
bitvec!("42:8").into(),
),
)]);
// Check that the callee object ID is correctly translated to a caller object ID
......
......@@ -156,7 +156,7 @@ impl<'a> Context<'a> {
format!("(NULL Pointer Dereference) There is no check if the return value is NULL at {} ({}).",
taint_source.tid.address, taint_source_name))
.addresses(vec![taint_source.tid.address.clone(), taint_access_location.address.clone()])
.tids(vec![format!("{}", taint_source.tid), format!("{}", taint_access_location)])
.tids(vec![format!("{}", taint_source.tid), format!("{taint_access_location}")])
.symbols(vec![taint_source_name]);
let _ = self.cwe_collector.send(cwe_warning);
}
......@@ -412,6 +412,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
#[cfg(test)]
mod tests {
use super::*;
use crate::{def, expr, variable};
impl<'a> Context<'a> {
pub fn mock(
......@@ -451,10 +452,7 @@ mod tests {
false
);
state.set_register_taint(
&Variable::mock("RDI", ByteSize::new(8)),
Taint::Tainted(ByteSize::new(8)),
);
state.set_register_taint(&variable!("RDI:8"), Taint::Tainted(ByteSize::new(8)));
assert_eq!(
context.check_parameters_for_taint(
&state,
......@@ -476,10 +474,7 @@ mod tests {
.handle_generic_call(&state, &Tid::new("call_tid"))
.is_some());
state.set_register_taint(
&Variable::mock("RDX", 8u64),
Taint::Tainted(ByteSize::new(8)),
);
state.set_register_taint(&variable!("RDX:8"), Taint::Tainted(ByteSize::new(8)));
assert!(context
.handle_generic_call(&state, &Tid::new("call_tid"))
.is_none());
......@@ -493,48 +488,21 @@ mod tests {
let (mut state, pi_state) = State::mock_with_pi_state();
state.set_pointer_inference_state(Some(pi_state));
let assign_def = Term {
tid: Tid::new("def"),
term: Def::Assign {
var: Variable::mock("RCX", 8u64),
value: Expression::Var(Variable::mock("RAX", 8u64)),
},
};
let assign_def = def!["def: RCX:8 = RAX:8"];
let result = context.update_def(&state, &assign_def).unwrap();
assert!(result
.eval(&Expression::Var(Variable::mock("RCX", 8u64)))
.is_tainted());
assert!(result
.eval(&Expression::Var(Variable::mock("RSP", 8u64)))
.is_top());
let load_def = Term {
tid: Tid::new("def"),
term: Def::Load {
var: Variable::mock("RCX", 8u64),
address: Expression::Var(Variable::mock("RSP", 8u64)),
},
};
assert!(result.eval(&expr!("RCX:8")).is_tainted());
assert!(result.eval(&expr!("RSP:8")).is_top());
let load_def = def!["def: RCX:8 := Load from RSP:8"];
let result = context.update_def(&state, &load_def).unwrap();
assert!(result
.eval(&Expression::Var(Variable::mock("RCX", 8u64)))
.is_tainted());
assert!(result
.eval(&Expression::Var(Variable::mock("RSP", 8u64)))
.is_top());
let store_def = Term {
tid: Tid::new("def"),
term: Def::Store {
value: Expression::Var(Variable::mock("RCX", 8u64)),
address: Expression::Var(Variable::mock("RSP", 8u64)),
},
};
assert!(result.eval(&expr!("RCX:8")).is_tainted());
assert!(result.eval(&expr!("RSP:8")).is_top());
let store_def = def!["def: Store at RSP:8 := RCX:8"];
let result = context.update_def(&state, &store_def).unwrap();
let result = context.update_def(&result, &load_def).unwrap();
assert!(result
.eval(&Expression::Var(Variable::mock("RCX", 8u64)))
.is_top());
assert!(result.eval(&expr!("RCX:8")).is_top());
}
#[test]
......@@ -548,7 +516,7 @@ mod tests {
tid: Tid::new("jmp"),
term: Jmp::CBranch {
target: Tid::new("target"),
condition: Expression::Var(Variable::mock("RAX", 8u64)),
condition: expr!("RAX:8"),
},
};
assert!(context
......@@ -558,7 +526,7 @@ mod tests {
tid: Tid::new("jmp"),
term: Jmp::CBranch {
target: Tid::new("target"),
condition: Expression::Var(Variable::mock("RBX", 8u64)),
condition: expr!("RBX:8"),
},
};
assert!(context
......
......@@ -349,7 +349,7 @@ impl State {
let register: Vec<(String, Value)> = self
.register_taint
.iter()
.map(|(var, data)| (var.name.clone(), json!(format!("{}", data))))
.map(|(var, data)| (var.name.clone(), json!(format!("{data}"))))
.collect();
let mut memory = Vec::new();
for (tid, mem_region) in self.memory_taint.iter() {
......@@ -357,7 +357,7 @@ impl State {
for (offset, elem) in mem_region.iter() {
elements.push((offset.to_string(), json!(elem.to_string())));
}
memory.push((format!("{}", tid), Value::Object(Map::from_iter(elements))));
memory.push((format!("{tid}"), Value::Object(Map::from_iter(elements))));
}
let state_map = vec![
(
......@@ -374,8 +374,8 @@ impl State {
#[cfg(test)]
mod tests {
use super::*;
use crate::abstract_domain::*;
use crate::analysis::pointer_inference::ValueDomain;
use crate::{abstract_domain::*, expr, variable};
use std::collections::BTreeSet;
impl State {
......@@ -389,16 +389,16 @@ mod tests {
pub fn mock_with_pi_state() -> (State, PointerInferenceState) {
let arg1 = Arg::Register {
expr: Expression::Var(register("RAX")),
expr: expr!("RAX:8"),
data_type: None,
};
let arg2 = Arg::Stack {
address: Expression::Var(register("RSP")),
address: expr!("RSP:8"),
size: ByteSize::new(8),
data_type: None,
};
let pi_state =
PointerInferenceState::new(&register("RSP"), Tid::new("func"), BTreeSet::new());
PointerInferenceState::new(&variable!("RSP:8"), Tid::new("func"), BTreeSet::new());
let symbol = ExternSymbol {
tid: Tid::new("extern_symbol".to_string()),
addresses: vec![],
......@@ -414,14 +414,6 @@ mod tests {
}
}
fn register(name: &str) -> Variable {
Variable {
name: name.into(),
size: ByteSize::new(8),
is_temp: false,
}
}
fn bv(value: i64) -> ValueDomain {
ValueDomain::from(Bitvector::from_i64(value))
}
......@@ -429,7 +421,7 @@ mod tests {
fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new(
Tid::new("time0"),
AbstractLocation::Register(Variable::mock(name, ByteSize::new(8))),
AbstractLocation::Register(variable!(format!("{}:8", name))),
)
}
......@@ -444,7 +436,7 @@ mod tests {
let top = Taint::Top(ByteSize::new(8));
let mut state = State::mock();
state.set_register_taint(&register("RAX"), taint.clone());
state.set_register_taint(&variable!("RAX:8"), taint.clone());
let mut other_state = State::mock();
let address = new_pointer("mem", 10);
......@@ -452,10 +444,10 @@ mod tests {
let merged_state = state.merge(&other_state);
assert_eq!(
merged_state.register_taint.get(&register("RAX")),
merged_state.register_taint.get(&variable!("RAX:8")),
Some(&taint)
);
assert_eq!(merged_state.register_taint.get(&register("RBX")), None);
assert_eq!(merged_state.register_taint.get(&variable!("RBX:8")), None);
assert_eq!(
merged_state.load_taint_from_memory(&address, ByteSize::new(8)),
taint.clone()
......@@ -471,9 +463,9 @@ mod tests {
fn new_state() {
let (state, pi_state) = State::mock_with_pi_state();
let taint = Taint::Tainted(ByteSize::new(8));
assert_eq!(state.register_taint.get(&register("RAX")), Some(&taint));
assert_eq!(state.register_taint.get(&register("RSP")), None);
let address = Expression::Var(register("RSP"));
assert_eq!(state.register_taint.get(&variable!("RAX:8")), Some(&taint));
assert_eq!(state.register_taint.get(&variable!("RSP:8")), None);
let address = Expression::Var(variable!("RSP:8"));
assert_eq!(
state.load_taint_from_memory(&pi_state.eval(&address), ByteSize::new(8)),
taint
......@@ -484,12 +476,12 @@ mod tests {
fn eval_expression() {
let (state, _pi_state) = State::mock_with_pi_state();
let expr = Expression::Var(register("RAX")).plus(Expression::Var(register("RBX")));
let expr = expr!("RAX:8 + RBX:8");
assert!(state.eval(&expr).is_tainted());
let expr = Expression::UnOp {
op: UnOpType::Int2Comp,
arg: Box::new(Expression::Var(register("RSP"))),
arg: Box::new(Expression::Var(variable!("RSP:8"))),
};
assert!(state.eval(&expr).is_top());
}
......
......@@ -23,8 +23,8 @@ impl Display for Taint {
/// Print the value of a `Taint` object.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Tainted(size) => write!(f, "Tainted:{}", size),
Self::Top(size) => write!(f, "Top:{}", size),
Self::Tainted(size) => write!(f, "Tainted:{size}"),
Self::Top(size) => write!(f, "Top:{size}"),
}
}
}
......
......@@ -92,7 +92,7 @@ fn generate_cwe_warning(sub: &Term<Sub>, jmp: &Term<Jmp>, permission_const: u64)
.addresses(vec![jmp.tid.address.clone()])
.other(vec![vec![
"umask_arg".to_string(),
format!("{:#o}", permission_const),
format!("{permission_const:#o}"),
]])
}
......@@ -122,8 +122,7 @@ pub fn check_cwe(
}
Err(err) => {
let log = LogMessage::new_info(format!(
"Could not determine umask argument: {}",
err
"Could not determine umask argument: {err}"
))
.location(jmp.tid.clone())
.source(CWE_MODULE.name);
......
......@@ -66,8 +66,7 @@ pub fn generate_cwe_warnings<'a>(
for (sub_name, jmp_tid, target_name) in dangerous_calls.iter() {
let address: &String = &jmp_tid.address;
let description: String = format!(
"(Use of Potentially Dangerous Function) {} ({}) -> {}",
sub_name, address, target_name
"(Use of Potentially Dangerous Function) {sub_name} ({address}) -> {target_name}"
);
let cwe_warning = CweWarning::new(
String::from(CWE_MODULE.name),
......@@ -75,7 +74,7 @@ pub fn generate_cwe_warnings<'a>(
description,
)
.addresses(vec![address.clone()])
.tids(vec![format!("{}", jmp_tid)])
.tids(vec![format!("{jmp_tid}")])
.symbols(vec![String::from(*sub_name)])
.other(vec![vec![
String::from("dangerous_function"),
......
......@@ -247,7 +247,7 @@ pub fn generate_cwe_warning(sub_name: &str, jmp_tid: &Tid, symbol_name: &str) ->
description,
)
.addresses(vec![jmp_tid.address.clone()])
.tids(vec![format!("{}", jmp_tid)])
.tids(vec![format!("{jmp_tid}")])
.symbols(vec![String::from(sub_name)])
.other(vec![vec![
String::from("OS Command Injection"),
......
......@@ -50,16 +50,14 @@ pub fn generate_cwe_warning(calls: &[(&str, &Tid, &str)]) -> Vec<CweWarning> {
for (sub_name, jmp_tid, _) in calls.iter() {
let address: &String = &jmp_tid.address;
let description = format!(
"(Exposed IOCTL with Insufficient Access Control) Program uses ioctl at {} ({}). Be sure to double check the program and the corresponding driver.",
sub_name, address
);
"(Exposed IOCTL with Insufficient Access Control) Program uses ioctl at {sub_name} ({address}). Be sure to double check the program and the corresponding driver.");
let cwe_warning = CweWarning::new(
String::from(CWE_MODULE.name),
String::from(CWE_MODULE.version),
description,
)
.addresses(vec![address.clone()])
.tids(vec![format!("{}", jmp_tid)])
.tids(vec![format!("{jmp_tid}")])
.symbols(vec![String::from(*sub_name)]);
cwe_warnings.push(cwe_warning);
......
......@@ -120,7 +120,7 @@ fn generate_cwe_warning(allocation: &Tid, is_stack_allocation: bool) -> CweWarni
allocation.address
),
)
.tids(vec![format!("{}", allocation)])
.tids(vec![format!("{allocation}")])
.addresses(vec![allocation.address.clone()])
.symbols(vec![])
}
......
......@@ -91,9 +91,9 @@ impl Term<Def> {
impl fmt::Display for Def {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Def::Load { var, address } => write!(f, "{} := Load from {}", var, address),
Def::Store { address, value } => write!(f, "Store at {} := {}", address, value),
Def::Assign { var, value } => write!(f, "{} = {}", var, value),
Def::Load { var, address } => write!(f, "{var} := Load from {address}"),
Def::Store { address, value } => write!(f, "Store at {address} := {value}"),
Def::Assign { var, value } => write!(f, "{var} = {value}"),
}
}
}
......
......@@ -213,24 +213,24 @@ impl Expression {
impl fmt::Display for Expression {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expression::Var(var) => write!(f, "{}", var),
Expression::Var(var) => write!(f, "{var}"),
Expression::Const(c) => {
write!(f, "0x{:016x}:i{}", c, c.bytesize().as_bit_length())
write!(f, "0x{:016x}:{}", c, c.bytesize())
}
Expression::BinOp { op, lhs, rhs } => match op {
BinOpType::IntMult
| BinOpType::IntDiv
| BinOpType::IntRem
| BinOpType::FloatMult
| BinOpType::FloatDiv => write!(f, "{} {} {}", lhs, op, rhs),
_ => write!(f, "({} {} {})", lhs, op, rhs),
| BinOpType::FloatDiv => write!(f, "{lhs} {op} {rhs}"),
_ => write!(f, "({lhs} {op} {rhs})"),
},
Expression::UnOp { op, arg } => write!(f, "{}({})", op, arg),
Expression::Cast { op, size: _, arg } => write!(f, "{}({})", op, arg),
Expression::UnOp { op, arg } => write!(f, "{op}({arg})"),
Expression::Cast { op, size: _, arg } => write!(f, "{op}({arg})"),
Expression::Unknown {
description,
size: _,
} => write!(f, "{}", description),
} => write!(f, "{description}"),
Expression::Subpiece {
low_byte,
size,
......@@ -263,7 +263,7 @@ impl fmt::Display for BinOpType {
BinOpType::IntRem => write!(f, "%"),
BinOpType::BoolAnd => write!(f, "&&"),
BinOpType::BoolOr => write!(f, "||"),
_ => write!(f, "{:?}", self),
_ => write!(f, "{self:?}"),
}
}
}
......@@ -272,14 +272,15 @@ impl fmt::Display for UnOpType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UnOpType::BoolNegate => write!(f, "¬"),
_ => write!(f, "{:?}", self),
UnOpType::IntNegate => write!(f, "-"),
_ => write!(f, "{self:?}"),
}
}
}
impl fmt::Display for CastOpType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
write!(f, "{self:?}")
}
}
#[cfg(test)]
......
......@@ -232,7 +232,7 @@ fn display() {
.subpiece(ByteSize(0), ByteSize(20));
assert_eq!(
"(FloatCeil(IntSExt(IntNegate((0x2:i32 + RAX:64 * RBP:64)))))[0-19]",
"(FloatCeil(IntSExt(-((0x2:4 + RAX:8 * RBP:8)))))[0-19]",
format!("{}", expr)
);
}
......@@ -64,9 +64,9 @@ pub enum Jmp {
impl fmt::Display for Jmp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Jmp::Branch(tid) => write!(f, "Jump to {}", tid),
Jmp::BranchInd(expr) => write!(f, "Jump to {}", expr),
Jmp::CBranch { target, condition } => write!(f, "If {} jump to {}", condition, target),
Jmp::Branch(tid) => write!(f, "Jump to {tid}"),
Jmp::BranchInd(expr) => write!(f, "Jump to {expr}"),
Jmp::CBranch { target, condition } => write!(f, "If {condition} jump to {target}"),
Jmp::Call { target, return_ } => write!(
f,
"call {} ret {}",
......@@ -79,7 +79,7 @@ impl fmt::Display for Jmp {
target,
return_.as_ref().unwrap_or(&Tid::new("?"))
),
Jmp::Return(expr) => write!(f, "ret {}", expr),
Jmp::Return(expr) => write!(f, "ret {expr}"),
Jmp::CallOther {
description,
return_,
......
//! This module implements macros for an intuitive and condensed construction of intermediate representation elements.
//! [variable!] creates a Variable, [bitvec!] creates a Bitvector, [expr!] creates an Expression, [def!] and [defs!]
//! create `Term<Def>` and Vec<Term<Def>>.
/// Creates a `Variable` specified by the string slice of form `name:size`.
///
/// `size` determines the size in bytes. `is_temp` field is set to `false`.
///
///
/// ## Example
/// ```rust
/// use cwe_checker_lib::intermediate_representation::*;
/// use cwe_checker_lib::variable;
///
/// assert_eq!(variable!("RAX:8"), Variable{ name: "RAX".into(), size: ByteSize::new(8), is_temp: false });
/// ```
#[macro_export]
macro_rules! variable {
( $x:expr ) => {
parsing::parse_variable($x)
};
}
/// Creates a `Bitvector` specified by the string slice of form `0xvalue:size` or value:size.
///
/// `value` is either in hexadecimal representation with leading `0x` or in
/// decimal representation. `size` is in bytes.
/// If `value` does not fit in `size`, `value` is truncated.
/// ## Panics
///- string must contain `:`
///- `size` must be one of `1`, `2`, `4` or `8`
///
/// ## Example
/// ```rust
/// use cwe_checker_lib::intermediate_representation::*;
/// use cwe_checker_lib::bitvec;
///
/// assert_eq!(bitvec!("0xFF:4"), Bitvector::from_u32(0xFF));
/// assert_eq!(bitvec!("0x-A:8"), Bitvector::from_i64(-10));
/// assert_eq!(bitvec!("-5:1"), Bitvector::from_i8(-5));
/// ```
#[macro_export]
macro_rules! bitvec {
( $x:expr ) => {
parsing::parse_bitvec($x)
};
}
/// Creates an `Expression` specified by the string slice.
///
/// Currently supported are: `Var` and `Const` as well as `IntAdd` and `IntSub` of `BinOp`.
/// Supported unary operations are `IntNegate` and `BoolNegate`.
/// Does not support `(`, `)` nor chaining of `+`.
/// ## Panics
///- utilizes `variable!` and `bitvec!` macros and their constrains.
///
///
/// ## Example
/// ```rust
/// use cwe_checker_lib::intermediate_representation::*;
/// use cwe_checker_lib::expr;
///
/// assert_eq!(expr!("0xFF:32"), Expression::Const(Bitvector::from_u32(0xFF)));
/// assert_eq!(
/// expr!("RAX:8"),
/// Expression::Var(Variable {name: "RAX".into(), size: ByteSize::new(8),is_temp: false})
/// );
/// assert_eq!(expr!("¬(0xFF)"), Expression::UnOp { op: UnOpType::BoolNegate, arg: Box::new(Expression::Const(Bitvector::from_u32(0xFF)))});
/// assert_eq!(expr!("-(0xFF)"), Expression::UnOp { op: UnOpType::IntNegate, arg: Box::new(Expression::Const(Bitvector::from_u32(0xFF)))})
///
/// assert_eq!(
/// expr!("RAX:8 + 0x42:8"),
/// Expression::BinOp { op: BinOpType::IntAdd,
/// lhs: Box::new(Expression::Var(Variable { name: "RAX".into(), size: ByteSize::new(8), is_temp: false })),
/// rhs: Box::new(Expression::Const(Bitvector::from_u8(0x42)))}
/// );
/// ```
#[macro_export]
macro_rules! expr {
( $x:expr ) => {
parsing::parse_expr($x)
};
}
/// Creates a `Vec<Term<Def>>` specified by the string slices. Utilizes `variable!`, `bitvec!` and `expr!` macros and their constrains.
///
/// Tid IDs are optionally prefixed by `tid_name: `. If not, `tid_x` is set as Tid ID with incrementing `x` starting by `0`.
/// ## Syntax
/// Load: `var := Load from expr`, with a Variable `var` according to `variable!` macro and and expression `expr` according to `expr!` macro.
///
/// Store: `Store at expr_a := expr_b` with Expressions `expr_a` and `expr_b` according to `expr!` macro.
///
/// Assign: `var = expr` with a Variable `var` according to `var!` macro and an Expression `expr` according to `expr!` macro.
/// ## Example
/// ```rust
/// use cwe_checker_lib::intermediate_representation::*;
/// use cwe_checker_lib::def;
///
/// defs!["tid_x: Store at RSP:8 + 0x8:8 := RAX:8", "RSP:8 = RSP:8 + 0x8:8", "tid_z: RDI:8 := Load from RSP:8"];
/// ```
#[macro_export]
macro_rules! defs {
[$($x:expr),*] => {{
let mut vec = vec![];
let mut _tid_suffix: u8 = 0;
$(
vec.push(parsing::parse_def($x, _tid_suffix));
_tid_suffix += 1;
)*
vec}
};
}
/// Creates a `Term<Def>` specified by the string slices. Utilizes `variable!`, `bitvec!` and `expr!` macros and their constrains.
///
/// Tid ID is optionally prefixed by `tid_name: `. If not, Tid ID `tid_0` is set.
/// ## Syntax
/// Load: `var := Load from expr`, with a Variable `var` according to `variable!` macro and and expression `expr` according to `expr!` macro.
///
/// Store: `Store at expr_a := expr_b` with Expressions `expr_a` and `expr_b` according to `expr!` macro.
///
/// Assign: `var = expr` with a Variable `var` according to `var!` macro and an Expression `expr` according to `expr!` macro.
/// ## Example
/// ```rust
/// use cwe_checker_lib::intermediate_representation::*;
/// use cwe_checker_lib::def;
///
/// def!["tid_x: Store at RSP:8 + 0x8:8 := RAX:8"];
/// def!["RSP:8 = RSP:8 + 0x8:8"];
/// ```
#[macro_export]
macro_rules! def {
($x:expr) => {
parsing::parse_def($x, 0)
};
}
pub mod parsing {
//! Provides parsing functions for the macros defined in `macros.rs`.
//! This module hides the parsing functions and allows exposure of the macros only.
use crate::intermediate_representation::{
BinOpType, Bitvector, ByteSize, Def, Expression, Term, Tid, UnOpType, Variable,
};
use regex::RegexSet;
/// Parses a Variable defining string slice and returns its corresponding Variable.
///
/// This is used for the `var!` macro, consider the macro documentation for more details.
#[allow(dead_code)]
pub fn parse_variable<S: AsRef<str>>(str: S) -> Variable {
let args: Vec<&str> = str.as_ref().split(':').collect();
if args.len() != 2 {
panic!("Could not uniquely parse variable: {}", str.as_ref())
}
let (name, size) = (args[0], args[1]);
Variable {
name: name.to_string(),
size: ByteSize(size.parse().unwrap()),
is_temp: false,
}
}
/// Parses a Bitvector defining string slice and returns its corresponding Bitvector.
///
/// This is used for the `bitvec!` macro, consider the macro documentation for more details.
#[allow(dead_code)]
pub fn parse_bitvec<S: AsRef<str>>(str: S) -> Bitvector {
let args: Vec<&str> = str.as_ref().split(&['x', ':'][..]).collect();
let value: i128;
if args.len() == 3 {
// hex representation
value = i128::from_str_radix(args[1], 16).unwrap();
} else if args.len() == 2 {
// dec representation
value = args[0].parse().unwrap();
} else {
panic!("Could not uniquely parse bitvector: {}", str.as_ref())
}
Bitvector::from_i128(value)
.into_sign_resize(args[args.len() - 1].parse::<usize>().unwrap() * 8)
}
/// Parses a Expression defining string slice and returns its corresponding Expression.
///
/// This is used for the `expr!` macro, consider the macro documentation for more details.
/// Variable names must not start with a number.
#[allow(dead_code)]
pub fn parse_expr<S: AsRef<str>>(str: S) -> Expression {
let set = RegexSet::new([
r"^[[:alnum:]&&[^0-9]]{1}[[:alnum:]&&[^x]]?[[:alnum:]]*:[0-9]{1,2}$", // Variable
r"^((0x(-)?[[:alnum:]]+)|^(-)?([0-9])+)+:[0-9]+$", // Constant
r"^[^\+]*\+{1}[^\+]*$", // BinOp (IntAdd)
r"^[[:ascii:]]+ \-{1} [[:ascii:]]+$", // BinOp (IntSub)
r"^-\([[:ascii:]]*\)$", // UnOp (IntNegate)
r"^¬\([[:ascii:]]*\)$", // UnOp (BoolNegate)
])
.unwrap();
let result: Vec<usize> = set.matches(str.as_ref()).into_iter().collect();
if result.len() != 1 {
panic!("Expression: {} matched Regex: {:#?}", str.as_ref(), result)
}
match result[0] {
0 => Expression::Var(parse_variable(str)),
1 => Expression::Const(parse_bitvec(str)),
2 => {
let args: Vec<&str> = str.as_ref().split('+').collect();
Expression::BinOp {
op: BinOpType::IntAdd,
lhs: Box::new(parse_expr(args[0].trim())),
rhs: Box::new(parse_expr(args[1].trim())),
}
}
3 => {
let args: Vec<&str> = str.as_ref().split('-').collect();
Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(parse_expr(args[0].trim())),
rhs: Box::new(parse_expr(args[1].trim())),
}
}
4 => {
let arg: &str = str.as_ref().trim_matches(&['-', '(', ')'][..]);
Expression::UnOp {
op: UnOpType::IntNegate,
arg: Box::new(parse_expr(arg.trim())),
}
}
5 => {
let arg: &str = str.as_ref().trim_matches(&['¬', '(', ')'][..]);
Expression::UnOp {
op: UnOpType::BoolNegate,
arg: Box::new(parse_expr(arg.trim())),
}
}
_ => panic!(),
}
}
/// Parses a `Term<Def>` defining string slice and returns its corresponding `Term<Def>`.
///
/// This is used for the `def!` and `defs!` macro, consider the macro documentation for more details.
#[allow(dead_code)]
pub fn parse_def<S: AsRef<str>>(str: S, tid_suffix: u8) -> Term<Def> {
let set = RegexSet::new([
r"^[[:ascii:]]+: [[:alnum:]:]* = ", // Assign with tid
r"^[[:alnum:]:]* = ", // Assign without tid
r"^[[:ascii:]]+: [[:alnum:]:]* := Load from [[:ascii:]:]*$", // Load with tid
r"^[[:alnum:]:]* := Load from [[:ascii:]:]*$", // Load without tid
r"^[[:ascii:]]+: Store at [[:ascii:]:]* := ", // Store with tid
r"^Store at [[:ascii:]:]* := ", // Store without tid
])
.unwrap();
let result: Vec<usize> = set.matches(str.as_ref()).into_iter().collect();
if result.len() != 1 {
panic!("Def: {} matched Regex: {:#?}", str.as_ref(), result)
}
let (tid, def): (String, &str) = match result[0] {
0 | 2 | 4 => {
// tid is specified
let (tid, def) = str.as_ref().split_once(": ").unwrap();
(tid.into(), def)
}
_ => (format!("tid_{}", tid_suffix), str.as_ref()), // unspecified tid
};
match result[0] {
0 | 1 => {
// Assign
let args: Vec<&str> = def.split('=').collect();
Term {
tid: Tid::new(tid),
term: Def::Assign {
var: parse_variable(args[0].trim()),
value: parse_expr(args[1].trim()),
},
}
}
2 | 3 => {
// Load
let args: Vec<&str> = def.split(":= Load from").collect();
Term {
tid: Tid::new(tid),
term: Def::Load {
var: parse_variable(args[0].trim()),
address: parse_expr(args[1].trim()),
},
}
}
4 | 5 => {
// Store
let args: Vec<&str> = def.split(":=").collect();
Term {
tid: Tid::new(tid),
term: Def::Store {
address: parse_expr(args[0].trim_start_matches("Store at ").trim()),
value: parse_expr(args[1].trim()),
},
}
}
_ => panic!(),
}
}
}
#[cfg(test)]
mod tests;
use crate::intermediate_representation::*;
#[test]
fn test_var() {
assert_eq!(
variable!("RAX:8"),
Variable {
name: "RAX".to_string(),
size: ByteSize(8),
is_temp: false
}
);
}
#[test]
#[should_panic]
fn var_empty_panics() {
variable!("");
}
#[test]
#[should_panic]
fn var_no_colon_panics() {
variable!("RAX8");
}
#[test]
#[should_panic]
fn var_no_size_panics() {
variable!("RAX:");
}
#[test]
fn test_bitvec() {
assert_eq!(bitvec!("0x42:1"), Bitvector::from_u8(0x42));
assert_eq!(bitvec!("0xFF:2"), Bitvector::from_u16(0xFF));
assert_eq!(bitvec!("0xAAFF:1"), Bitvector::from_u8(0xFF));
assert_eq!(bitvec!("0x-01:1"), Bitvector::from_i8(-1));
assert_eq!(bitvec!("123:4"), Bitvector::from_u32(123));
assert_eq!(bitvec!("-42:8"), Bitvector::from_i64(-42));
}
#[test]
fn test_expr_var() {
assert_eq!(
expr!("RAX:8"),
Expression::Var(Variable {
name: "RAX".into(),
size: ByteSize(8),
is_temp: false
})
);
}
#[test]
fn test_expr_const() {
assert_eq!(
expr!("0x42:8"),
Expression::Const(Bitvector::from_u64(0x42))
);
assert_eq!(
expr!("0xFFFF:1"),
Expression::Const(Bitvector::from_u8(255))
);
assert_eq!(expr!("42:4"), Expression::Const(Bitvector::from_u32(42)));
}
#[test]
fn test_expr_plus() {
assert_eq!(
expr!("RAX:8 + 0x42:8"),
Expression::BinOp {
op: BinOpType::IntAdd,
lhs: Box::new(Expression::Var(Variable {
name: "RAX".into(),
size: ByteSize(8),
is_temp: false
})),
rhs: Box::new(Expression::Const(Bitvector::from_u64(0x42)))
}
);
}
#[test]
fn test_expr_minus() {
assert_eq!(
expr!("RAX:8 - 0x42:8"),
Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(Variable {
name: "RAX".into(),
size: ByteSize(8),
is_temp: false
})),
rhs: Box::new(Expression::Const(Bitvector::from_u64(0x42)))
}
);
}
#[test]
fn test_expr_int_negate() {
assert_eq!(
expr!("-(RAX:8)"),
Expression::UnOp {
op: UnOpType::IntNegate,
arg: Box::new(Expression::Var(Variable {
name: "RAX".into(),
size: ByteSize(8),
is_temp: false
}))
}
);
}
#[test]
fn test_expr_bool_negate() {
assert_eq!(
expr!("¬(RAX:8)"),
Expression::UnOp {
op: UnOpType::BoolNegate,
arg: Box::new(Expression::Var(Variable {
name: "RAX".into(),
size: ByteSize(8),
is_temp: false
}))
}
);
}
#[test]
fn test_def_tid() {
let defs = defs![
"RDI:8 = RAX:8 + RBP:8",
"A: RAX:8 = 0x42:1",
"RDX:8 = RAX:8 + RBP:8"
];
assert_eq!(
defs.into_iter()
.map(|x| x.tid.to_string())
.collect::<Vec<String>>(),
["tid_0", "A", "tid_2"]
)
}
#[test]
fn test_defs_assign() {
assert_eq!(
defs!["tid_0: RAX:8 = 0x42:1", "tid_1: RDI:8 = RAX:8 + RBP:8"],
vec![
Term {
tid: Tid::new("tid_0"),
term: Def::Assign {
var: Variable {
name: "RAX".into(),
size: ByteSize(8),
is_temp: false
},
value: Expression::Const(Bitvector::from_i8(0x42))
}
},
Term {
tid: Tid::new("tid_1"),
term: Def::Assign {
var: Variable {
name: "RDI".into(),
size: ByteSize(8),
is_temp: false
},
value: Expression::BinOp {
op: BinOpType::IntAdd,
lhs: Box::new(Expression::Var(Variable {
name: "RAX".into(),
size: ByteSize(8),
is_temp: false
})),
rhs: Box::new(Expression::Var(Variable {
name: "RBP".into(),
size: ByteSize(8),
is_temp: false
}))
}
}
}
]
);
}
#[test]
fn test_defs_store() {
assert_eq!(
defs!["tid: Store at RSP:8 - 0x8:1 := 0x42:1"],
vec![Term {
tid: Tid::new("tid"),
term: Def::Store {
address: Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(Variable {
name: "RSP".into(),
size: ByteSize(8),
is_temp: false
})),
rhs: Box::new(Expression::Const(Bitvector::from_u8(0x8)))
},
value: Expression::Const(Bitvector::from_u8(0x42))
}
}]
)
}
#[test]
fn test_defs_load() {
assert_eq!(
defs!["tid_a: RAX:8 := Load from 0xFF00:4 + 0x08:4"],
vec![Term {
tid: Tid::new("tid_a"),
term: Def::Load {
var: Variable {
name: "RAX".into(),
size: ByteSize(8),
is_temp: false
},
address: Expression::BinOp {
op: BinOpType::IntAdd,
lhs: Box::new(Expression::Const(Bitvector::from_u32(0xFF00))),
rhs: Box::new(Expression::Const(Bitvector::from_u32(0x08)))
}
}
}]
)
}
#[test]
fn test_defs_composition() {
assert_eq!(
defs![
"tid_a: Store at RSP:8 + -(0x8:1) := RAX:8",
"tid_b: RSP:8 = RSP:8 + ¬(0x8:1)",
"tid_c: RDI:8 := Load from RSP:8"
],
vec![
Term {
tid: Tid::new("tid_a"),
term: Def::Store {
address: Expression::BinOp {
op: BinOpType::IntAdd,
lhs: Box::new(Expression::Var(Variable {
name: "RSP".into(),
size: ByteSize(8),
is_temp: false
})),
rhs: Box::new(Expression::UnOp {
op: UnOpType::IntNegate,
arg: Box::new(Expression::Const(Bitvector::from_u8(0x08)))
})
},
value: Expression::Var(Variable {
name: "RAX".into(),
size: ByteSize(8),
is_temp: false
})
}
},
Term {
tid: Tid::new("tid_b"),
term: Def::Assign {
var: Variable {
name: "RSP".into(),
size: ByteSize(8),
is_temp: false
},
value: Expression::BinOp {
op: BinOpType::IntAdd,
lhs: Box::new(Expression::Var(Variable {
name: "RSP".into(),
size: ByteSize(8),
is_temp: false
})),
rhs: Box::new(Expression::UnOp {
op: UnOpType::BoolNegate,
arg: Box::new(Expression::Const(Bitvector::from_u8(0x08)))
})
}
}
},
Term {
tid: Tid::new("tid_c"),
term: Def::Load {
var: Variable {
name: "RDI".into(),
size: ByteSize(8),
is_temp: false
},
address: Expression::Var(Variable {
name: "RSP".into(),
size: ByteSize(8),
is_temp: false
})
}
}
]
)
}
......@@ -32,6 +32,11 @@ mod project;
pub use project::*;
mod runtime_memory_image;
pub use runtime_memory_image::*;
#[cfg(test)]
#[macro_use]
mod macros;
#[cfg(test)]
pub use macros::*;
/// An unsigned number of bytes.
///
......
......@@ -37,7 +37,7 @@ impl Tid {
/// the returned block ID is the one that would be executed first if a jump to the given address happened.
pub fn blk_id_at_address(address: &str) -> Tid {
Tid {
id: format!("blk_{}", address),
id: format!("blk_{address}"),
address: address.to_string(),
}
}
......
......@@ -22,7 +22,7 @@ pub struct Variable {
impl Display for Variable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.name, self.size.as_bit_length())?;
write!(f, "{}:{}", self.name, self.size)?;
if self.is_temp {
write!(f, "(temp)")?;
}
......
......@@ -42,7 +42,7 @@ impl From<Variable> for IrExpression {
match (&pcode_var.name, &pcode_var.value) {
(Some(_name), None) => IrExpression::Var(pcode_var.into()),
(None, Some(_hex_value)) => IrExpression::Const(pcode_var.parse_const_to_bitvector()),
_ => panic!("Conversion failed:\n{:?}", pcode_var),
_ => panic!("Conversion failed:\n{pcode_var:?}"),
}
}
}
......
......@@ -312,7 +312,7 @@ fn piecing_or_zero_extending() {
replace_subregister_in_block(&mut block, &register_map);
assert!(check_defs_of_block(
&block,
vec!["zext_eax_to_rax: RAX:64 = IntZExt(0x0:i32)"]
vec!["zext_eax_to_rax: RAX:8 = IntZExt(0x0:4)"]
));
// Test whether zero extension to base register is still recognized
......@@ -328,7 +328,7 @@ fn piecing_or_zero_extending() {
replace_subregister_in_block(&mut block, &register_map);
assert!(check_defs_of_block(
&block,
vec!["zext_ah_to_rax: RAX:64 = IntZExt(0x0:i8)"]
vec!["zext_ah_to_rax: RAX:8 = IntZExt(0x0:1)"]
));
// Test when the next register is a zero extension to a different register.
......@@ -344,8 +344,8 @@ fn piecing_or_zero_extending() {
assert!(check_defs_of_block(
&block,
vec![
"eax_assign: RAX:64 = ((RAX:64)[4-7] Piece 0x0:i32)",
"zext_eax_to_rcx: RCX:64 = IntZExt((RAX:64)[0-3])"
"eax_assign: RAX:8 = ((RAX:8)[4-7] Piece 0x0:4)",
"zext_eax_to_rcx: RCX:8 = IntZExt((RAX:8)[0-3])"
]
));
......@@ -362,8 +362,8 @@ fn piecing_or_zero_extending() {
assert!(check_defs_of_block(
&block,
vec![
"ah_assign: RAX:64 = (((RAX:64)[2-7] Piece 0x0:i8) Piece (RAX:64)[0-0])",
"zext_ah_to_eax: RAX:64 = ((RAX:64)[4-7] Piece IntZExt((RAX:64)[1-1]))",
"ah_assign: RAX:8 = (((RAX:8)[2-7] Piece 0x0:1) Piece (RAX:8)[0-0])",
"zext_ah_to_eax: RAX:8 = ((RAX:8)[4-7] Piece IntZExt((RAX:8)[1-1]))",
]
));
......@@ -380,8 +380,8 @@ fn piecing_or_zero_extending() {
assert!(check_defs_of_block(
&block,
vec![
"load_to_eax: loaded_value:32(temp) := Load from 0x0:i64",
"zext_eax_to_rax: RAX:64 = IntZExt(loaded_value:32(temp))",
"load_to_eax: loaded_value:4(temp) := Load from 0x0:8",
"zext_eax_to_rax: RAX:8 = IntZExt(loaded_value:4(temp))",
]
));
......@@ -398,9 +398,9 @@ fn piecing_or_zero_extending() {
assert!(check_defs_of_block(
&block,
vec![
"load_to_eax: loaded_value:32(temp) := Load from 0x0:i64",
"load_to_eax_cast_to_base: RAX:64 = ((RAX:64)[4-7] Piece loaded_value:32(temp))",
"zext_eax_to_rcx: RCX:64 = IntZExt((RAX:64)[0-3])"
"load_to_eax: loaded_value:4(temp) := Load from 0x0:8",
"load_to_eax_cast_to_base: RAX:8 = ((RAX:8)[4-7] Piece loaded_value:4(temp))",
"zext_eax_to_rcx: RCX:8 = IntZExt((RAX:8)[0-3])"
]
));
}
......@@ -293,7 +293,7 @@ impl Blk {
_ => panic!(),
};
if input.address.is_some() {
let temp_register_name = format!("$load_temp{}", index);
let temp_register_name = format!("$load_temp{index}");
let load_def = input.to_load_def(temp_register_name, generic_pointer_size);
*input = load_def.lhs.clone().unwrap();
refactored_defs.push(Term {
......
......@@ -792,28 +792,28 @@ fn from_project_to_ir_project() {
// Checks if the other definitions and the jump were correctly casted.
assert_eq!(
format!("{}", ir_block.defs[0].term),
"loaded_value:32(temp) := Load from (RDI:64)[0-3]".to_string()
"loaded_value:4(temp) := Load from (RDI:8)[0-3]".to_string()
);
assert_eq!(
format!("{}", ir_block.defs[1].term),
"RDI:64 = ((RDI:64)[4-7] Piece loaded_value:32(temp))".to_string()
"RDI:8 = ((RDI:8)[4-7] Piece loaded_value:4(temp))".to_string()
);
assert_eq!(
format!("{}", ir_block.defs[2].term),
"RAX:64 = (((RAX:64)[2-7] Piece ((RAX:64)[1-1] ^ (RAX:64)[1-1])) Piece (RAX:64)[0-0])"
"RAX:8 = (((RAX:8)[2-7] Piece ((RAX:8)[1-1] ^ (RAX:8)[1-1])) Piece (RAX:8)[0-0])"
.to_string()
);
assert_eq!(
format!("{}", ir_block.defs[3].term),
"RAX:64 = IntZExt((RDI:64)[0-3])".to_string()
"RAX:8 = IntZExt((RDI:8)[0-3])".to_string()
);
assert_eq!(
format!("{}", ir_block.defs[4].term),
"RAX:64 = ((RAX:64)[4-7] Piece (0x0:i16 Piece (RAX:64)[0-1]))".to_string()
"RAX:8 = ((RAX:8)[4-7] Piece (0x0:2 Piece (RAX:8)[0-1]))".to_string()
);
assert_eq!(
format!("{}", ir_block.defs[5].term),
"RAX:64 = ((RAX:64)[2-7] Piece ((RDI:64)[0-3])[1-2])".to_string()
"RAX:8 = ((RAX:8)[2-7] Piece ((RDI:8)[0-3])[1-2])".to_string()
);
assert_eq!(ir_block.jmps[0].term, expected_jmp);
}
......@@ -31,7 +31,7 @@ pub fn get_project_from_ghidra(
.as_millis()
);
// Create a unique name for the pipe
let fifo_path = tmp_folder.join(format!("pcode_{}.pipe", timestamp_suffix));
let fifo_path = tmp_folder.join(format!("pcode_{timestamp_suffix}.pipe"));
let ghidra_command = generate_ghidra_call_command(
file_path,
&fifo_path,
......@@ -93,7 +93,7 @@ fn execute_ghidra(
let output = match ghidra_command.output() {
Ok(output) => output,
Err(err) => {
eprintln!("Ghidra could not be executed: {}", err);
eprintln!("Ghidra could not be executed: {err}");
std::process::exit(101);
}
};
......@@ -107,7 +107,7 @@ fn execute_ghidra(
eprintln!("{}", String::from_utf8(output.stdout).unwrap());
eprintln!("{}", String::from_utf8(output.stderr).unwrap());
if let Some(code) = output.status.code() {
eprintln!("Ghidra plugin failed with exit code {}", code);
eprintln!("Ghidra plugin failed with exit code {code}");
}
eprintln!("Execution of Ghidra plugin failed.");
} else {
......@@ -150,7 +150,7 @@ fn generate_ghidra_call_command(
let mut ghidra_command = Command::new(headless_path);
ghidra_command
.arg(&tmp_folder) // The folder where temporary files should be stored
.arg(format!("PcodeExtractor_{}_{}", filename, timestamp_suffix)) // The name of the temporary Ghidra Project.
.arg(format!("PcodeExtractor_{filename}_{timestamp_suffix}")) // The name of the temporary Ghidra Project.
.arg("-import") // Import a file into the Ghidra project
.arg(file_path) // File import path
.arg("-postScript") // Execute a script after standard analysis by Ghidra finished
......
......@@ -155,9 +155,9 @@ impl std::fmt::Display for LogMessage {
LogLevel::Info => write!(formatter, "INFO: ")?,
};
match (&self.source, &self.location) {
(Some(source), Some(location)) => write!(formatter, "{} @ {}: ", source, location)?,
(Some(source), None) => write!(formatter, "{}: ", source)?,
(None, Some(location)) => write!(formatter, "{}: ", location)?,
(Some(source), Some(location)) => write!(formatter, "{source} @ {location}: ")?,
(Some(source), None) => write!(formatter, "{source}: ")?,
(None, Some(location)) => write!(formatter, "{location}: ")?,
(None, None) => (),
};
write!(formatter, "{}", self.text)
......@@ -177,23 +177,22 @@ pub fn print_all_messages(
emit_json: bool,
) {
for log in logs {
println!("{}", log);
println!("{log}");
}
let output: String = if emit_json {
serde_json::to_string_pretty(&cwes).unwrap()
} else {
cwes.iter()
.map(|cwe| format!("{}", cwe))
.map(|cwe| format!("{cwe}"))
.collect::<Vec<String>>()
.join("\n")
+ "\n"
};
if let Some(file_path) = out_path {
std::fs::write(file_path, output).unwrap_or_else(|error| {
panic!("Writing to output path {} failed: {}", file_path, error)
});
std::fs::write(file_path, output)
.unwrap_or_else(|error| panic!("Writing to output path {file_path} failed: {error}"));
} else {
print!("{}", output);
print!("{output}",);
}
}
......@@ -215,7 +214,7 @@ pub fn add_debug_log_statistics(all_logs: &mut Vec<LogMessage>) {
}
for (analysis, count) in analysis_debug_log_count {
all_logs.push(LogMessage {
text: format!("Logged {} debug log messages.", count),
text: format!("Logged {count} debug log messages."),
level: LogLevel::Info,
location: None,
source: Some(analysis),
......@@ -223,10 +222,7 @@ pub fn add_debug_log_statistics(all_logs: &mut Vec<LogMessage>) {
}
if general_debug_log_count > 0 {
all_logs.push(LogMessage {
text: format!(
"Logged {} general debug log messages.",
general_debug_log_count
),
text: format!("Logged {general_debug_log_count} general debug log messages."),
level: LogLevel::Info,
location: None,
source: None,
......
......@@ -70,8 +70,7 @@ impl CweTestCase {
} else {
println!("{} \t {}", filepath, "[FAILED]".red());
Err(format!(
"Expected occurrences: {}. Found: {}",
num_expected_occurences, num_cwes
"Expected occurrences: {num_expected_occurences}. Found: {num_cwes}"
))
}
} else {
......@@ -79,7 +78,7 @@ impl CweTestCase {
match output.status.code() {
Some(_code) => Err(String::from_utf8(output.stdout).unwrap()
+ &String::from_utf8(output.stderr).unwrap()),
None => Err(format!("Execution failed for file {}", filepath)),
None => Err(format!("Execution failed for file {filepath}")),
}
}
}
......@@ -160,8 +159,8 @@ pub fn all_test_cases(cwe: &'static str, check_name: &'static str) -> Vec<CweTes
/// The `error_log` tuples are of the form `(check_filename, error_message)`.
pub fn print_errors(error_log: Vec<(String, String)>) {
for (filepath, error) in error_log {
println!("{}", format!("+++ Error for {} +++", filepath).red());
println!("{}", error);
println!("{}", format!("+++ Error for {filepath} +++").red());
println!("{error}");
}
}
......
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