Skip to content
Open
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
5 changes: 5 additions & 0 deletions library/core/src/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@

#![stable(feature = "rust1", since = "1.0.0")]

use safety::requires;

#[cfg(kani)]
use crate::kani;
use crate::marker::{Destruct, PointeeSized};

mod uninit;
Expand Down Expand Up @@ -544,6 +548,7 @@ unsafe impl CloneToUninit for str {
#[unstable(feature = "clone_to_uninit", issue = "126799")]
unsafe impl CloneToUninit for crate::ffi::CStr {
#[cfg_attr(debug_assertions, track_caller)]
#[requires(!dest.is_null())]
unsafe fn clone_to_uninit(&self, dest: *mut u8) {
// SAFETY: For now, CStr is just a #[repr(trasnsparent)] [c_char] with some invariants.
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: #[repr(trasnsparent)] should be #[repr(transparent)].

Suggested change
// SAFETY: For now, CStr is just a #[repr(trasnsparent)] [c_char] with some invariants.
// SAFETY: For now, CStr is just a #[repr(transparent)] [c_char] with some invariants.

Copilot uses AI. Check for mistakes.
// And we can cast [c_char] to [u8] on all supported platforms (see: to_bytes_with_nul).
Comment on lines 550 to 554
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new #[requires(!dest.is_null())] precondition is weaker than the documented safety requirements for CloneToUninit::clone_to_uninit (destination must be valid for size_of_val(self) writes and properly aligned). For consistency with other contracts in core (e.g. pointer APIs using ub_checks::can_write), consider strengthening this contract to reflect the full safety requirements, not just non-nullness.

Copilot uses AI. Check for mistakes.
Expand Down
50 changes: 50 additions & 0 deletions library/core/src/ffi/c_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,7 @@ impl FusedIterator for Bytes<'_> {}
#[unstable(feature = "kani", issue = "none")]
mod verify {
use super::*;
use crate::clone::CloneToUninit;

// Helper function
fn arbitrary_cstr(slice: &[u8]) -> &CStr {
Expand Down Expand Up @@ -1096,4 +1097,53 @@ mod verify {
assert_eq!(expected_is_empty, c_str.is_empty());
assert!(c_str.is_safe());
}

// ops::Index<ops::RangeFrom<usize>> for CStr
#[kani::proof]
#[kani::unwind(33)]
fn check_index_from() {
const MAX_SIZE: usize = 32;
let string: [u8; MAX_SIZE] = kani::any();
let slice = kani::slice::any_slice_of_array(&string);
let c_str = arbitrary_cstr(slice);

let bytes_with_nul = c_str.to_bytes_with_nul();
let idx: usize = kani::any();
kani::assume(idx < bytes_with_nul.len());

let indexed = &c_str[idx..];
assert!(indexed.is_safe());
// The indexed result should correspond to the tail of the original bytes
assert_eq!(indexed.to_bytes_with_nul(), &bytes_with_nul[idx..]);
}

// CloneToUninit for CStr
// MAX_SIZE is kept small to avoid CBMC timeout: the symbolic pointer
// arithmetic in clone_to_uninit is expensive; 8 bytes is sufficient to
// cover empty, single-char, and multi-char C strings.
#[kani::proof]
#[kani::unwind(9)]
fn check_clone_to_uninit() {
const MAX_SIZE: usize = 8;
let string: [u8; MAX_SIZE] = kani::any();
let slice = kani::slice::any_slice_of_array(&string);
let c_str = arbitrary_cstr(slice);

let len = c_str.to_bytes_with_nul().len();

// Allocate destination buffer (len <= MAX_SIZE since slice.len() <= MAX_SIZE)
let mut buf = [core::mem::MaybeUninit::<u8>::uninit(); MAX_SIZE];
let dest = buf.as_mut_ptr() as *mut u8;

Comment on lines +1132 to +1137
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check_clone_to_uninit only checks that the cloned buffer parses as a valid CStr, but it doesn’t verify that clone_to_uninit copied the exact bytes (including the NUL terminator) from the source. This could pass even if the implementation writes a different (but still valid) C string. Consider asserting that the destination bytes match c_str.to_bytes_with_nul().

Copilot uses AI. Check for mistakes.
// Safety: dest is non-null (stack allocation), valid for len writes
unsafe {
c_str.clone_to_uninit(dest);
}

// Verify the cloned bytes form a valid CStr
let cloned_slice = unsafe { core::slice::from_raw_parts(dest, len) };
let cloned_cstr = CStr::from_bytes_with_nul(cloned_slice);
assert!(cloned_cstr.is_ok());
Comment on lines +1134 to +1146
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The harness creates a &[u8] over MaybeUninit<u8> memory via from_raw_parts(dest, len) and then reads it in CStr::from_bytes_with_nul. If clone_to_uninit were buggy and failed to initialize all len bytes, the harness itself would perform an uninitialized read (UB), making the proof unsound. Consider using an already-initialized u8 buffer (still valid to pass to clone_to_uninit) so the harness remains defined even under partial writes.

Copilot uses AI. Check for mistakes.
assert!(cloned_cstr.unwrap().is_safe());
}
}
Loading