mod support;
use crate::support::analyse_fixture;
const MUST_PANIC: &[(&str, &str)] = &[
("must_index", "index"),
("must_divide", "divide-by-zero"),
("must_remainder", "remainder-by-zero"),
("must_unwrap", "unwrap"),
("must_assert", "explicit"),
("must_slice_tail", "index"),
("must_copy", "explicit"),
("must_assert_generic", "explicit"),
("must_assert_false", "explicit"),
("must_divide_misguarded", "divide-by-zero"),
("must_divide_inverted_guard", "divide-by-zero"),
("must_divide_once_of_two", "divide-by-zero"),
("must_divide_narrowed", "divide-by-zero"),
("must_push", "capacity-overflow"),
("must_push", "alloc-failure"),
("must_insert_into_map", "capacity-overflow"),
("must_index_through_oom", "index"),
("must_index_through_precondition_check", "index"),
("must_index_under_raw_length", "index"),
("must_index_after_write_under_pointer", "index"),
("must_rethrow", "explicit"),
("must_lock", "poison"),
("must_write", "fmt"),
("must_unwrap_poisoned", "poison"),
("must_unwrap_result_of_format", "unwrap"),
("must_unwrap_option_of_poison", "unwrap"),
("must_rc_clone", "refcount-overflow"),
("must_rc_clone_through_helper", "refcount-overflow"),
("must_slice_str", "str-boundary"),
("must_borrow", "borrow"),
("must_dyn", "dyn-call"),
("must_foreign", "foreign"),
("must_catch_abort", "alloc-failure"),
("must_catch_across_ffi", "explicit"),
("must_index_off_by_one", "index"),
("must_index_wrong_slice", "index"),
("must_modulo_signed", "index"),
("must_fn_ptr", "fn-pointer"),
("must_call_closure_pointer", "fn-pointer"),
("must_generic", "generic-bound"),
("must_pick_through_generic", "explicit"),
("must_pick_any", "generic-bound"),
("must_dyn_speak", "dyn-call"),
("must_drop_object", "dyn-call"),
("must_zeroed_ref", "explicit"),
("must_panic_literal", "explicit"),
("must_zeroed_chain", "generic-bound"),
("must_divide_by_max_zero", "divide-by-zero"),
("must_divide_by_min", "divide-by-zero"),
("must_index_past_length_guard", "index"),
("must_modulo_length", "remainder-by-zero"),
("must_masked_index_offset", "index"),
("must_be_below", "explicit"),
("must_pass_unchecked_limit", "explicit"),
("Cursor::must_field_written", "index"),
("Cursor::must_field_after_call", "index"),
("Cursor::must_field_of_other", "index"),
("must_wide_index", "index"),
("must_unwrap_argument", "unwrap"),
("must_two_lengths", "index"),
("must_unwrap_wrong_arm", "unwrap"),
("must_match_panic", "explicit"),
("must_nonnull_of_argument", "unwrap"),
("must_nonnull_of_raw_field", "unwrap"),
("must_generic_size_divide", "divide-by-zero"),
("must_take_indexed", "index"),
("must_pass_unguarded", "index"),
("Window::must_window_of_other", "index"),
("must_shifted_index", "index"),
("must_shift_by_runtime", "index"),
("must_signed_shift_index", "index"),
("must_shifted_left_index", "index"),
("must_or_divide", "divide-by-zero"),
("must_or_index", "index"),
("must_xor_index", "index"),
("must_divided_index", "index"),
("must_remainder_by_bounded", "index"),
("must_leading_zeros_index", "index"),
("must_range_loop_of_other", "index"),
("must_option_carries_index", "index"),
("must_copy_two_slices", "index"),
("must_second_last_of_guarded", "index"),
("must_take_four", "explicit"),
("must_index_after_shrink", "index"),
("must_loop_past_the_end", "index"),
("must_chunks_of_a_size", "explicit"),
("must_step_by_a_size", "explicit"),
("must_split_unguarded", "explicit"),
("must_convert_runtime", "remainder-by-zero"),
("must_copy_into_prefix", "index"),
("must_copy_guarded_on_other", "explicit"),
("must_index_of_equal_lengths", "index"),
("must_nonzero_of_anything", "unwrap"),
("must_clamped_to_larger", "index"),
("must_prefix_unguarded", "index"),
("must_guard_within_a_loose_guard", "index"),
("must_guard_within_a_guard_of_other", "index"),
("must_countdown_from_the_length", "index"),
("must_fill_past_the_end", "index"),
("must_middle_of_one", "index"),
("must_offset_at_the_end", "index"),
("must_prefix_of_the_longer", "index"),
("must_split_two_past", "index"),
("must_shift_from_the_length", "index"),
("must_inner_of_other", "index"),
("must_inner_after_move", "index"),
("must_char_high_bits", "index"),
("must_copy_prefix_of_other", "explicit"),
("must_copy_two_lengths", "explicit"),
("must_modulo_at_most_guard", "remainder-by-zero"),
("must_table_of_threes", "index"),
("must_scan_by_two", "index"),
("must_drop_beside_a_guard", "index"),
("must_deque_range", "index"),
("must_atomic_load", "explicit"),
("must_masked_switch", "explicit"),
("must_vector_index", "index"),
("must_index_after_le", "index"),
("must_short_constant_table", "index"),
("sixteenth_of", "index"),
("must_reach_under_wrapping_guard", "index"),
("must_reach_past_guard", "index"),
("must_window_past_sixteen", "index"),
("must_guard_across_borrow", "index"),
("must_range_without_order", "index"),
("must_index_under_loose_chain", "index"),
("must_after_call_that_cannot_return", "explicit"),
("at_most_ten", "explicit"),
("must_always_stub", "explicit"),
("must_panic_unless_quitting", "explicit"),
];
const MUST_NOT_PANIC: &[(&str, &str)] = &[
("must_divide_once_of_two", "remainder-by-zero"),
("must_lock", "unwrap"),
("must_write", "unwrap"),
("must_not_catch_explicit", "explicit"),
("must_dyn_speak", "explicit"),
("must_modulo_length", "index"),
("must_copy_into_prefix", "explicit"),
("must_after_call_that_cannot_return", "index"),
("must_insert_into_map", "explicit"),
("must_index_through_oom", "alloc-failure"),
("must_index_through_precondition_check", "ub-check"),
("must_unwrap_poisoned", "unwrap"),
("must_unwrap_result_of_format", "fmt"),
("must_unwrap_option_of_poison", "poison"),
];
const MUST_BE_CLEAN_IN_RELEASE: &[&str] = &[
"clean_fold",
"clean_count_zeros",
"clean_sum_by_get",
"clean_assert_true",
"clean_guarded_widening",
"clean_zeroed_int",
"clean_divide_by_min_plus_one",
"clean_index_after_empty_guard",
"clean_index_after_length_guard",
"clean_masked_index_offset",
"clean_nonnull_of_place",
"clean_guard_before_call",
"Window::clean_window_read",
"clean_range_loop",
"clean_option_carries_index",
"clean_copy_same_length",
"clean_copy_two_arrays",
"clean_last_of_guarded",
"clean_pass_array_of_four",
"clean_chunks_of_a_constant",
"clean_step_by_a_constant",
"clean_split_after_guard",
"clean_convert_constant",
"clean_chunk_count",
"clean_copy_into_array",
"clean_copy_after_guard",
"clean_nonzero_of_set_bit",
"clean_split_in_half",
"clean_clamped_to_last",
"clean_prefix_under_guard",
"clean_guard_within_a_guard",
"clean_countdown_loop",
"clean_fill_bounded",
"clean_middle_of_guarded",
"clean_offset_below_the_end",
"clean_prefix_of_both",
"clean_split_at_a_byte",
"clean_first_half",
"clean_shift_along",
"clean_inner_of_guarded",
"clean_char_high_bits",
"clean_bool_index",
"clean_copy_prefix_under_guard",
"clean_copy_the_shorter",
"clean_modulo_above_guard",
"clean_table_of_threes",
"clean_drop_beside_a_guard",
"clean_deque_walk",
"clean_atomic_load",
"clean_atomic_fence",
"clean_atomic_compare_exchange",
"clean_masked_switch",
"clean_remainder_switch",
"clean_vector_index_guard",
"clean_deque_index_guard",
"clean_string_index_guard",
"clean_scan_until",
"clean_scan_break",
"clean_index_after_scan",
"clean_constant_table",
"clean_reach_within_guard",
"clean_window_of_sixteen",
"clean_guard_before_borrow",
"clean_range_between_guards",
"clean_index_under_chained_bound",
"clean_index_of_paired_slice",
"clean_index_under_referenced_length",
"clean_nonnull_of_referenced_field",
];
const NOT_SETTLED: &[&str] = &[];
const MUST_BE_CLEAN_IN_DEBUG: &[&str] = &[
"clean_divide_by_constant",
"clean_guarded_divide",
"clean_guarded_divide_ne",
"clean_guarded_remainder",
"clean_modulo_index",
"clean_masked_index",
"clean_guarded_index",
"clean_guarded_index_flipped",
"clean_while_index",
"clean_nonzero_divide",
"clean_divide_by_max",
"clean_remainder_by_max",
"clean_divide_by_clamp",
"clean_divide_by_helper",
"clean_divide_by_either_arm",
"clean_precondition_met",
"Cursor::clean_field_index",
"Cursor::clean_field_divide",
"clean_byte_index",
"clean_unwrap_built",
"clean_unwrap_matched",
"clean_unwrap_ok",
"clean_match_panic",
"clean_generic_guard",
"clean_shifted_index",
"clean_shifted_left_index",
"clean_or_divide",
"clean_or_index",
"clean_xor_index",
"clean_divided_index",
"clean_remainder_by_bounded",
"clean_leading_zeros_index",
"clean_trailing_zeros_index",
"clean_count_ones_index",
];
fn found(
reported: &[(String, Vec<String>)],
name: &str,
) -> Option<Vec<String>> {
reported
.iter()
.find(|(function, _)| function == name)
.map(|(_, categories)| categories.clone())
}
#[test]
fn a_known_crate_reports_exactly_its_panics() {
let reported = analyse_fixture("release", &[]);
for (function, category) in MUST_PANIC {
let categories = found(&reported, function).unwrap_or_else(|| {
panic!("{function} can panic with {category} and was not reported")
});
assert!(
categories.iter().any(|c| c == category),
"{function} can panic with {category}, but was reported with \
{categories:?}"
);
}
for (function, category) in MUST_NOT_PANIC {
let categories = found(&reported, function).unwrap_or_default();
assert!(
!categories.iter().any(|c| c == category),
"{function} cannot panic with {category}, but was reported with \
{categories:?}"
);
}
for function in MUST_BE_CLEAN_IN_DEBUG
.iter()
.chain(MUST_BE_CLEAN_IN_RELEASE)
{
assert!(
found(&reported, function).is_none(),
"{function} cannot panic, but was reported with {:?}",
found(&reported, function)
);
}
for function in NOT_SETTLED {
assert!(
found(&reported, function).is_some(),
"{function} is recorded as one the analysis cannot settle, but \
it is now clean; move it to one of the clean lists"
);
}
}
#[test]
fn a_debug_build_still_folds_the_guards() {
let reported = analyse_fixture("debug", &[]);
for function in MUST_BE_CLEAN_IN_DEBUG {
assert!(
found(&reported, function).is_none(),
"{function} cannot panic in a debug build either, but was \
reported with {:?}",
found(&reported, function)
);
}
for (function, category) in MUST_PANIC {
let categories = found(&reported, function).unwrap_or_else(|| {
panic!(
"{function} can panic with {category} in a debug build and \
was not reported"
)
});
assert!(
categories.iter().any(|c| c == category),
"{function} can panic with {category}, but a debug build \
reported {categories:?}"
);
}
}
#[test]
fn a_build_whose_panics_abort_catches_nothing() {
let reported = analyse_fixture("abort", &[]);
for (function, category) in [
("must_not_catch_explicit", "explicit"),
("must_catch_across_ffi", "explicit"),
("must_catch_abort", "alloc-failure"),
] {
let categories = found(&reported, function).unwrap_or_default();
assert!(
categories.iter().any(|c| c == category),
"{function} reaches {category} when panics abort, but was \
reported with {categories:?}"
);
}
}
#[test]
fn an_unwrap_is_named_by_the_error_it_discards_inlined_or_not() {
for profile in ["release", "debug"] {
let reported = analyse_fixture(profile, &[]);
for (function, category, not) in [
("must_lock", "poison", "unwrap"),
("must_unwrap_poisoned", "poison", "unwrap"),
("must_unwrap_result_of_format", "unwrap", "fmt"),
("must_unwrap_option_of_poison", "unwrap", "poison"),
] {
let categories = found(&reported, function).unwrap_or_default();
assert!(
categories.iter().any(|c| c == category)
&& !categories.iter().any(|c| c == not),
"{function} discards what makes it {category} rather than \
{not}, but a {profile} build reported {categories:?}"
);
}
}
}
#[test]
fn reading_the_tests_keeps_the_generic_functions() {
let reported = analyse_fixture("release", &["--with-tests"]);
for (function, category) in [
("must_assert_generic", "explicit"),
("must_generic_size_divide", "divide-by-zero"),
("must_generic", "generic-bound"),
("must_pick_any", "generic-bound"),
] {
let categories = found(&reported, function).unwrap_or_else(|| {
panic!(
"{function} can panic with {category} and was not reported \
once the tests were read"
)
});
assert!(
categories.iter().any(|c| c == category),
"{function} can panic with {category}, but reading the tests \
reported {categories:?}"
);
}
assert!(
reported
.iter()
.all(|(function, _)| !function.contains("tests::")),
"the tests themselves are not reported, got {reported:?}"
);
}
#[test]
fn a_panic_every_call_reaches_is_reported_as_always() {
let doc = support::analyse_fixture_json("release", &[]);
let findings = doc["findings"].as_array().cloned().unwrap_or_default();
let always_of = |name: &str| -> Vec<String> {
findings
.iter()
.find(|f| f["function"] == name)
.and_then(|f| f["always"].as_array())
.map(|list| {
list.iter()
.filter_map(|v| v.as_str().map(str::to_owned))
.collect()
})
.unwrap_or_default()
};
assert_eq!(
always_of("must_always_stub"),
vec!["explicit".to_owned()],
"a stub that panics on every call is always a panic"
);
assert!(
always_of("must_divide").is_empty(),
"a check that depends on the argument is not always a panic"
);
assert!(
always_of("must_after_call_that_cannot_return").is_empty(),
"a panic reached through a call is the callee's to call always"
);
assert!(
always_of("must_panic_unless_quitting").is_empty(),
"an argument that ends the process first avoids the panic"
);
}
#[test]
fn candidates_expand_dyn_and_pointer_calls() {
let reported = analyse_fixture("release", &["--candidates"]);
let dyn_call = found(&reported, "must_dyn_speak").unwrap_or_default();
assert!(
dyn_call.iter().any(|c| c == "explicit"),
"one implementation panics, so following candidates must surface \
it, got {dyn_call:?}"
);
assert!(
dyn_call.iter().any(|c| c == "dyn-call"),
"candidates narrow the unknown, they do not close it, got \
{dyn_call:?}"
);
let dropped = found(&reported, "must_drop_object").unwrap_or_default();
assert!(
dropped.iter().any(|c| c == "explicit"),
"one type made into an object panics in its drop, so following \
candidates must surface it, got {dropped:?}"
);
let closure =
found(&reported, "must_call_closure_pointer").unwrap_or_default();
assert!(
closure.iter().any(|c| c == "explicit"),
"a closure made into a pointer of this signature panics, got \
{closure:?}"
);
let pointer = found(&reported, "must_fn_ptr").unwrap_or_default();
assert!(
pointer.iter().any(|c| c == "explicit"),
"a reified function of this signature panics, got {pointer:?}"
);
assert!(
pointer.iter().any(|c| c == "fn-pointer"),
"the pointer could still hold something unseen, got {pointer:?}"
);
}
#[test]
fn a_static_panic_message_is_quoted_in_the_reason() {
let output = support::run_on_fixture(&["--suppress", ""]);
let text = String::from_utf8_lossy(&output.stdout);
assert!(
text.contains("panics with \"assertion failed: "),
"the report should quote the message a panic carries:\n{text}"
);
}
#[test]
fn closures_fold_into_their_parent_on_request() {
let reported = analyse_fixture("release", &["--closures", "parent"]);
assert!(
reported.iter().all(|(f, _)| !f.contains("{closure")),
"the parent view must not name closures on their own"
);
let folded = reported
.iter()
.find(|(f, _)| f == "must_not_catch_explicit")
.map(|(_, c)| c.clone())
.unwrap_or_default();
assert!(
folded.iter().any(|c| c == "explicit"),
"the compact view attributes a closure's panics to the function it \
is written in, even the contained ones; got {folded:?}"
);
}