Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/operators/bitcoin_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ impl OperatorSet for BitcoinCore {
("std::max", "std::min"),
("std::begin", "std::end"),
("std::end", "std::begin"),
// Block-time source swap: `GetBlockTime()` -> `GetMedianTimePast()`.
// Both live on CBlockIndex and return int64_t, but MTP lags the
// header timestamp, so locktime/timeout checks that pick the
// wrong clock only get caught by tests around that boundary.
(r"\bGetBlockTime\(\)", "GetMedianTimePast()"),
("true", "false"),
("false", "true"),
// Designated-initializer / member-assignment value mutation: for
Expand All @@ -37,6 +42,12 @@ impl OperatorSet for BitcoinCore {
// `true`/`false`.
(r"(\.\w+\s*=\s*)([^,;=][^,;]*?)(\s*[,;])", r"${1}true${3}"),
(r"(\.\w+\s*=\s*)([^,;=][^,;]*?)(\s*[,;])", r"${1}false${3}"),
// Integer type narrowing: `int64_t` -> `uint32_t`. Silently
// truncates values above 2^32 and turns negatives into large
// positives, so a mutant only dies when a test actually exercises
// the wide or signed range. Word boundaries keep `uint64_t`
// untouched. Mirrors the entry in `common`.
(r"\bint64_t\b", "uint32_t"),
(r" / ", " * "),
// Boundary (off-by-one) mutations first — hardest to kill
(r" >= ", " > "),
Expand Down Expand Up @@ -290,4 +301,48 @@ mod tests {
// comparison into an assignment (`state.m_count =true;`).
assert!(!op.pattern.is_match("bool ok = state.m_count == 5;"));
}

fn find_op(needle: &str) -> MutationOperator {
BitcoinCore
.regex_operators()
.unwrap()
.into_iter()
.find(|op| op.pattern.as_str().contains(needle))
.unwrap_or_else(|| panic!("operator containing {needle:?} present"))
}

#[test]
fn test_get_block_time_becomes_median_time_past() {
let op = find_op("GetBlockTime");
let line = " if (pindex->GetBlockTime() < nLockTime) return false;";
assert!(op.pattern.is_match(line));
assert_eq!(
op.pattern.replace(line, &op.replacement),
" if (pindex->GetMedianTimePast() < nLockTime) return false;"
);
assert_eq!(
op.pattern
.replace("int64_t t = tip.GetBlockTime();", &op.replacement),
"int64_t t = tip.GetMedianTimePast();"
);
}

#[test]
fn test_get_block_time_swap_ignores_lookalikes() {
let op = find_op("GetBlockTime");
assert!(!op.pattern.is_match(" pindex->GetBlockTimeMax();"));
assert!(!op.pattern.is_match(" block.SetBlockTime(now);"));
assert!(!op.pattern.is_match(" int64_t GetBlockTime(int height);"));
}

#[test]
fn test_int64_narrowing_is_present_in_bitcoin_core_set() {
let op = find_op("int64_t");
assert_eq!(
op.pattern
.replace(" int64_t nTime = GetTime();", &op.replacement),
" uint32_t nTime = GetTime();"
);
assert!(!op.pattern.is_match(" uint64_t x = 0;"));
}
}
39 changes: 39 additions & 0 deletions src/operators/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ pub(crate) fn regex_operators() -> Vec<(&'static str, &'static str)> {
// RHS isn't itself the literal `true`/`false`.
(r"(\.\w+\s*=\s*)([^,;=][^,;]*?)(\s*[,;])", r"${1}true${3}"),
(r"(\.\w+\s*=\s*)([^,;=][^,;]*?)(\s*[,;])", r"${1}false${3}"),
// Integer type narrowing: `int64_t` -> `uint32_t`. Silently truncates
// values above 2^32 and turns negatives into large positives, so a
// mutant only dies when a test actually exercises the wide or signed
// range. Word boundaries keep `uint64_t` untouched.
(r"\bint64_t\b", "uint32_t"),
(r" / ", " * "),
// Boundary (off-by-one) mutations first — hardest to kill
(r" >= ", " > "),
Expand Down Expand Up @@ -131,4 +136,38 @@ mod tests {
assert!(!re.is_match(" returned = 1;"));
assert!(!re.is_match(" return_code = f();"));
}

fn int64_narrowing_op() -> (regex::Regex, &'static str) {
let (pattern, replacement) = regex_operators()
.into_iter()
.find(|(pattern, _)| pattern.contains("int64_t"))
.expect("int64_t narrowing operator present");
(regex::Regex::new(pattern).unwrap(), replacement)
}

#[test]
fn int64_narrowing_rewrites_declarations_and_casts() {
let (re, rep) = int64_narrowing_op();
assert_eq!(
re.replace(" int64_t nTime = GetTime();", rep),
" uint32_t nTime = GetTime();"
);
assert_eq!(
re.replace(" return static_cast<int64_t>(x);", rep),
" return static_cast<uint32_t>(x);"
);
assert_eq!(
re.replace("std::numeric_limits<int64_t>::max()", rep),
"std::numeric_limits<uint32_t>::max()"
);
}

#[test]
fn int64_narrowing_ignores_other_integer_types() {
let (re, _) = int64_narrowing_op();
assert!(!re.is_match(" uint64_t x = 0;"));
assert!(!re.is_match(" int32_t x = 0;"));
assert!(!re.is_match(" int64_tt x = 0;"));
assert!(!re.is_match(" my_int64_t x = 0;"));
}
}
Loading