dirstate: better error messages when dirstate is corrupted
authorArseniy Alekseyev <aalekseyev@janestreet.com>
Wed, 31 May 2023 19:00:11 +0100
changeset 50536 475c170bb815
parent 50535 a8531bd9210b
child 50537 6a019a037085
dirstate: better error messages when dirstate is corrupted The current error message "Overflow in dirstate" sounds confusing because it suggests either a certain size limit that's being exceeded, or integer arithmetic overflowing. The reality is just a file being shorter than expected.
rust/hg-core/src/dirstate/parsers.rs
--- a/rust/hg-core/src/dirstate/parsers.rs	Wed May 31 18:18:52 2023 +0100
+++ b/rust/hg-core/src/dirstate/parsers.rs	Wed May 31 19:00:11 2023 +0100
@@ -61,12 +61,21 @@
         Option<&'a HgPath>,
     ) -> Result<(), HgError>,
 ) -> Result<&'a DirstateParents, HgError> {
-    let (parents, rest) = DirstateParents::from_bytes(contents)
-        .map_err(|_| HgError::corrupted("Too little data for dirstate."))?;
+    let mut entry_idx = 0;
+    let original_len = contents.len();
+    let (parents, rest) =
+        DirstateParents::from_bytes(contents).map_err(|_| {
+            HgError::corrupted(format!(
+                "Too little data for dirstate: {} bytes.",
+                original_len
+            ))
+        })?;
     contents = rest;
     while !contents.is_empty() {
         let (raw_entry, rest) = RawEntry::from_bytes(contents)
-            .map_err(|_| HgError::corrupted("Overflow in dirstate."))?;
+            .map_err(|_| HgError::corrupted(format!(
+            "dirstate corrupted: ran out of bytes at entry header {}, offset {}.",
+            entry_idx, original_len-contents.len())))?;
 
         let entry = DirstateEntry::from_v1_data(
             EntryState::try_from(raw_entry.state)?,
@@ -74,9 +83,14 @@
             raw_entry.size.get(),
             raw_entry.mtime.get(),
         );
+        let filename_len = raw_entry.length.get() as usize;
         let (paths, rest) =
-            u8::slice_from_bytes(rest, raw_entry.length.get() as usize)
-                .map_err(|_| HgError::corrupted("Overflow in dirstate."))?;
+            u8::slice_from_bytes(rest, filename_len)
+                .map_err(|_|
+                HgError::corrupted(format!(
+         "dirstate corrupted: ran out of bytes at entry {}, offset {} (expected {} bytes).",
+              entry_idx, original_len-contents.len(), filename_len))
+                )?;
 
         // `paths` is either a single path, or two paths separated by a NULL
         // byte
@@ -87,6 +101,7 @@
         let copy_source = iter.next().map(HgPath::new);
         each_entry(path, &entry, copy_source)?;
 
+        entry_idx += 1;
         contents = rest;
     }
     Ok(parents)