diff --git a/rust/hg-core/src/dirstate/dirs_multiset.rs b/rust/hg-core/src/dirstate/dirs_multiset.rs --- a/rust/hg-core/src/dirstate/dirs_multiset.rs +++ b/rust/hg-core/src/dirstate/dirs_multiset.rs @@ -8,6 +8,8 @@ //! A multiset of directory names. //! //! Used to counts the references to directories in a manifest or dirstate. +use crate::utils::hg_path::HgPathError; +use crate::utils::subslice_offset; use crate::{ dirstate::EntryState, utils::{ @@ -78,7 +80,18 @@ if subpath.as_bytes().last() == Some(&b'/') { // TODO Remove this once PathAuditor is certified // as the only entrypoint for path data - return Err(DirstateMapError::ConsecutiveSlashes); + let offset = subslice_offset( + path.as_ref().as_bytes(), + subpath.as_bytes(), + ); + let second_slash_index = offset.unwrap() + subpath.len() - 1; + + return Err(DirstateMapError::InvalidPath( + HgPathError::ConsecutiveSlashes { + bytes: path.as_ref().as_bytes().to_owned(), + second_slash_index, + }, + )); } if let Some(val) = self.inner.get_mut(subpath) { *val += 1; diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs +++ b/rust/hg-core/src/lib.rs @@ -82,18 +82,17 @@ pub enum DirstateMapError { PathNotFound(HgPathBuf), EmptyPath, - ConsecutiveSlashes, + InvalidPath(HgPathError), } impl ToString for DirstateMapError { fn to_string(&self) -> String { - use crate::DirstateMapError::*; match self { - PathNotFound(_) => "expected a value, found none".to_string(), - EmptyPath => "Overflow in dirstate.".to_string(), - ConsecutiveSlashes => { - "found invalid consecutive slashes in path".to_string() + DirstateMapError::PathNotFound(_) => { + "expected a value, found none".to_string() } + DirstateMapError::EmptyPath => "Overflow in dirstate.".to_string(), + DirstateMapError::InvalidPath(e) => e.to_string(), } } } diff --git a/rust/hg-core/src/utils.rs b/rust/hg-core/src/utils.rs --- a/rust/hg-core/src/utils.rs +++ b/rust/hg-core/src/utils.rs @@ -61,6 +61,36 @@ } } +/// Find the offset of the subslice relative to the original collection +/// +/// This function panics for zero-sized types. +/// # Examples: +/// +/// ``` +/// use crate::hg::utils::subslice_offset; +/// let mut line = b"Subslice me!".to_vec(); +/// assert_eq!(subslice_offset(&line, &line[8..]), Some(8)); +/// +/// assert_eq!(subslice_offset(&line, b"hahaha"), None); +/// +/// // Empty array +/// let v: [u8; 0] = []; +/// assert_eq!(subslice_offset(&v, &v), Some(0)); +/// assert_eq!(subslice_offset(&v, b"hehe"), None); +/// ``` +pub fn subslice_offset(outer: &[T], inner: &[T]) -> Option { + let pointee_size = std::mem::size_of::(); + assert!(0 < pointee_size && pointee_size <= isize::max_value() as usize); + + let outer_start = outer.as_ptr() as usize; + let inner = inner.as_ptr() as usize; + if inner < outer_start || inner > outer_start.wrapping_add(outer.len()) { + None + } else { + Some(inner.wrapping_sub(outer_start)) + } +} + pub trait SliceExt { fn trim_end(&self) -> &Self; fn trim_start(&self) -> &Self;