changeset 47334:18b3060fe598

dirstate-v2: Add a zero-size error type for dirstate v2 parse errors This error should only happen if Mercurial is buggy or the file is corrupted. It indicates for example that: * A part of the file refers to another part, and the byte offset or item count would cause reading out of bounds, beyond the end of the file. * The byte for an entry state has an invalid value When parsing becomes lazy, many more functions will return a `Result` with this error. Making it zero-size reduces the work that the `?` operator needs to do to pass around the error value. Differential Revision: https://phab.mercurial-scm.org/D10748
author Simon Sapin <simon.sapin@octobus.net>
date Wed, 19 May 2021 13:15:00 +0200
parents 69530e5d4fe5
children ed1583a845d2
files rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/dirstate_tree/on_disk.rs
diffstat 2 files changed, 45 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs	Wed May 19 13:15:00 2021 +0200
+++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs	Wed May 19 13:15:00 2021 +0200
@@ -228,7 +228,7 @@
     pub fn new_v2(
         on_disk: &'on_disk [u8],
     ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
-        on_disk::read(on_disk)
+        Ok(on_disk::read(on_disk)?)
     }
 
     #[timed]
--- a/rust/hg-core/src/dirstate_tree/on_disk.rs	Wed May 19 13:15:00 2021 +0200
+++ b/rust/hg-core/src/dirstate_tree/on_disk.rs	Wed May 19 13:15:00 2021 +0200
@@ -108,14 +108,32 @@
     let _ = std::mem::transmute::<Node, [u8; 57]>;
 }
 
+/// Unexpected file format found in `.hg/dirstate` with the "v2" format.
+pub(crate) struct DirstateV2ParseError;
+
+impl From<DirstateV2ParseError> for HgError {
+    fn from(_: DirstateV2ParseError) -> Self {
+        HgError::corrupted("dirstate-v2 parse error")
+    }
+}
+
+impl From<DirstateV2ParseError> for crate::DirstateError {
+    fn from(error: DirstateV2ParseError) -> Self {
+        HgError::from(error).into()
+    }
+}
+
 pub(super) fn read<'on_disk>(
     on_disk: &'on_disk [u8],
-) -> Result<(DirstateMap<'on_disk>, Option<DirstateParents>), DirstateError> {
+) -> Result<
+    (DirstateMap<'on_disk>, Option<DirstateParents>),
+    DirstateV2ParseError,
+> {
     if on_disk.is_empty() {
         return Ok((DirstateMap::empty(on_disk), None));
     }
-    let (header, _) = Header::from_bytes(on_disk)
-        .map_err(|_| HgError::corrupted("truncated dirstate-v2"))?;
+    let (header, _) =
+        Header::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
     let Header {
         marker,
         parents,
@@ -124,7 +142,7 @@
         nodes_with_copy_source_count,
     } = header;
     if marker != V2_FORMAT_MARKER {
-        return Err(HgError::corrupted("missing dirstated-v2 marker").into());
+        return Err(DirstateV2ParseError);
     }
     let dirstate_map = DirstateMap {
         on_disk,
@@ -140,7 +158,7 @@
     pub(super) fn path<'on_disk>(
         &self,
         on_disk: &'on_disk [u8],
-    ) -> Result<dirstate_map::NodeKey<'on_disk>, HgError> {
+    ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
         let full_path = read_hg_path(on_disk, self.full_path)?;
         let base_name_start = usize::try_from(self.base_name_start.get())
             // u32 -> usize, could only panic on a 16-bit CPU
@@ -148,16 +166,14 @@
         if base_name_start < full_path.len() {
             Ok(WithBasename::from_raw_parts(full_path, base_name_start))
         } else {
-            Err(HgError::corrupted(
-                "dirstate-v2 base_name_start out of bounds",
-            ))
+            Err(DirstateV2ParseError)
         }
     }
 
     pub(super) fn copy_source<'on_disk>(
         &self,
         on_disk: &'on_disk [u8],
-    ) -> Result<Option<Cow<'on_disk, HgPath>>, HgError> {
+    ) -> Result<Option<Cow<'on_disk, HgPath>>, DirstateV2ParseError> {
         Ok(if self.copy_source.start.get() != 0 {
             Some(read_hg_path(on_disk, self.copy_source)?)
         } else {
@@ -165,10 +181,16 @@
         })
     }
 
-    pub(super) fn entry(&self) -> Result<Option<DirstateEntry>, HgError> {
+    pub(super) fn entry(
+        &self,
+    ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
         Ok(if self.entry.state != b'\0' {
             Some(DirstateEntry {
-                state: self.entry.state.try_into()?,
+                state: self
+                    .entry
+                    .state
+                    .try_into()
+                    .map_err(|_| DirstateV2ParseError)?,
                 mode: self.entry.mode.get(),
                 mtime: self.entry.mtime.get(),
                 size: self.entry.size.get(),
@@ -181,7 +203,7 @@
     pub(super) fn to_in_memory_node<'on_disk>(
         &self,
         on_disk: &'on_disk [u8],
-    ) -> Result<dirstate_map::Node<'on_disk>, HgError> {
+    ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
         Ok(dirstate_map::Node {
             children: read_nodes(on_disk, self.children)?,
             copy_source: self.copy_source(on_disk)?,
@@ -194,7 +216,7 @@
 fn read_nodes(
     on_disk: &[u8],
     slice: ChildNodes,
-) -> Result<dirstate_map::ChildNodes, HgError> {
+) -> Result<dirstate_map::ChildNodes, DirstateV2ParseError> {
     read_slice::<Node>(on_disk, slice)?
         .iter()
         .map(|node| {
@@ -204,12 +226,18 @@
         .map(dirstate_map::ChildNodes::InMemory)
 }
 
-fn read_hg_path(on_disk: &[u8], slice: Slice) -> Result<Cow<HgPath>, HgError> {
+fn read_hg_path(
+    on_disk: &[u8],
+    slice: Slice,
+) -> Result<Cow<HgPath>, DirstateV2ParseError> {
     let bytes = read_slice::<u8>(on_disk, slice)?;
     Ok(Cow::Borrowed(HgPath::new(bytes)))
 }
 
-fn read_slice<T>(on_disk: &[u8], slice: Slice) -> Result<&[T], HgError>
+fn read_slice<T>(
+    on_disk: &[u8],
+    slice: Slice,
+) -> Result<&[T], DirstateV2ParseError>
 where
     T: BytesCast,
 {
@@ -221,9 +249,7 @@
         .get(start..)
         .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
         .map(|(slice, _rest)| slice)
-        .ok_or_else(|| {
-            HgError::corrupted("dirstate v2 slice is out of bounds")
-        })
+        .ok_or_else(|| DirstateV2ParseError)
 }
 
 pub(super) fn write(