Skip to content

Commit 08d99d3

Browse files
author
Carolyn Zech
authored
Contracts/Stubs for multiple inherent impls: fix checking the generic args path for equality (#4051)
#3829 fixed an issue where we couldn't verify contracts or stubs for types with multiple inherent impls. However, the logic to check for path equality incorrectly assumed that there would only be one element in the path before the generic argument, when in fact the path can be arbitrarily long if there are modules involved. Change the logic to just look at the last two elements, since we can expect those will be the generic argument and the method name. Before this PR, the new test would fail with this error: ``` error: Failed to resolve checking function NegativeNumber::<i32>::unchecked_mul because the generic arguments ::<i32> are invalid. The available implementations are: num::negative::NegativeNumber::<i32>::unchecked_mul num::negative::NegativeNumber::<i16>::unchecked_mul --> /Users/cmzech/kani/tests/kani/FunctionContracts/multiple_inherent_impls.rs:57:5 | 57 | #[kani::proof_for_contract(NegativeNumber::<i32>::unchecked_mul)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the attribute macro `kani::proof_for_contract` (in Nightly builds, run with -Z macro-backtrace for more info) ``` because we'd try to compare `::negative::NegativeNumber::<i32>::unchecked_mul` to `::<i32>::unchecked_mul` and find they weren't equal. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent e229b69 commit 08d99d3

File tree

2 files changed

+86
-2
lines changed

2 files changed

+86
-2
lines changed

kani-compiler/src/kani_middle/resolve.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,13 +708,63 @@ fn is_item_name(tcx: TyCtxt, item: DefId, name: &str) -> bool {
708708
last == name
709709
}
710710

711+
/// Use this when we don't just care about the item name matching (c.f. is_item_name),
712+
/// but also if the generic arguments are the same, e.g. <u32>::unchecked_add.
711713
fn is_item_name_with_generic_args(
712714
tcx: TyCtxt,
713715
item: DefId,
714716
generic_args: &str,
715717
name: &str,
716718
) -> bool {
717719
let item_path = tcx.def_path_str(item);
718-
let all_but_base_type = item_path.find("::").map_or("", |idx| &item_path[idx..]);
719-
all_but_base_type == format!("{generic_args}::{name}")
720+
last_two_items_of_path_match(&item_path, generic_args, name)
721+
}
722+
723+
// This is just a helper function for is_item_name_with_generic_args.
724+
// It's in a separate function so we can unit-test it without a mock TyCtxt or DefIds.
725+
fn last_two_items_of_path_match(item_path: &str, generic_args: &str, name: &str) -> bool {
726+
let parts: Vec<&str> = item_path.split("::").collect();
727+
728+
if parts.len() < 2 {
729+
return false;
730+
}
731+
732+
let actual_last_two =
733+
format!("{}{}{}{}", "::", parts[parts.len() - 2], "::", parts[parts.len() - 1]);
734+
735+
let last_two = format!("{}{}{}", generic_args, "::", name);
736+
737+
// The last two components of the item_path should be the same as ::{generic_args}::{name}
738+
last_two == actual_last_two
739+
}
740+
741+
#[cfg(test)]
742+
mod tests {
743+
mod simple_last_two_items_of_path_match {
744+
use crate::kani_middle::resolve::last_two_items_of_path_match;
745+
746+
#[test]
747+
fn length_one_item_prefix() {
748+
let generic_args = "::<u32>";
749+
let name = "unchecked_add";
750+
let item_path = format!("NonZero{}::{}", generic_args, name);
751+
assert!(last_two_items_of_path_match(&item_path, generic_args, name))
752+
}
753+
754+
#[test]
755+
fn length_three_item_prefix() {
756+
let generic_args = "::<u32>";
757+
let name = "unchecked_add";
758+
let item_path = format!("core::num::NonZero{}::{}", generic_args, name);
759+
assert!(last_two_items_of_path_match(&item_path, generic_args, name))
760+
}
761+
762+
#[test]
763+
fn wrong_generic_arg() {
764+
let generic_args = "::<u64>";
765+
let name = "unchecked_add";
766+
let item_path = format!("core::num::NonZero{}::{}", "::<u32>", name);
767+
assert!(!last_two_items_of_path_match(&item_path, generic_args, name))
768+
}
769+
}
720770
}

tests/kani/FunctionContracts/multiple_inherent_impls.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,37 @@ fn verify_unchecked_mul_ambiguous_path() {
2626
let x: NonZero<i32> = NonZero(-1);
2727
x.unchecked_mul(-2);
2828
}
29+
30+
// Test that the resolution still works if the function in question is nested inside multiple modules,
31+
// i.e. the absolute path to the function can be arbitrarily long.
32+
// As long as the generic arguments and function name match, resolution should succeed.
33+
// This mimics the actual structure of NonZero relative to its harnesses in the standard library.
34+
pub mod num {
35+
pub mod negative {
36+
pub struct NegativeNumber<T>(pub T);
37+
38+
impl NegativeNumber<i32> {
39+
#[kani::requires(self.0.checked_mul(x).is_some())]
40+
pub fn unchecked_mul(self, x: i32) -> i32 {
41+
self.0 * x
42+
}
43+
}
44+
45+
impl NegativeNumber<i16> {
46+
#[kani::requires(self.0.checked_mul(x).is_some())]
47+
pub fn unchecked_mul(self, x: i16) -> i16 {
48+
self.0 * x
49+
}
50+
}
51+
}
52+
}
53+
54+
mod verify {
55+
use crate::num::negative::*;
56+
57+
#[kani::proof_for_contract(NegativeNumber::<i32>::unchecked_mul)]
58+
fn verify_unchecked_mul_ambiguous_path() {
59+
let x: NegativeNumber<i32> = NegativeNumber(-1);
60+
x.unchecked_mul(-2);
61+
}
62+
}

0 commit comments

Comments
 (0)