Skip to content

Commit b32a847

Browse files
committed
fix!: don't panic when unknown entry types are encountered.
Related to helix-editor/helix#10660 which runs into object types that are unknown. I have looked into this and [couldn't find evidence of a new pack-entry type](https://github.com/git/git/blob/0f3415f1f8478b05e64db11eb8aaa2915e48fef6/packfile.c#L1303-L1358) in the Git codebase. It also looks like that Git [will never write packs that aren't V2](https://github.com/git/git/blob/0f3415f1f8478b05e64db11eb8aaa2915e48fef6/pack-write.c#L352) - initially I thought it might write V3 based on some other criteria. For now, the thesis is that one would have to be able to mark bad objects to be able to handle this more gracefully, but all we can do is try to fail.
1 parent addf446 commit b32a847

File tree

12 files changed

+52
-35
lines changed

12 files changed

+52
-35
lines changed

gix-pack/src/bundle/find.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,19 @@ impl crate::Bundle {
3737
cache: &mut dyn crate::cache::DecodeEntry,
3838
) -> Result<(gix_object::Data<'a>, crate::data::entry::Location), crate::data::decode::Error> {
3939
let ofs = self.index.pack_offset_at_index(idx);
40-
let pack_entry = self.pack.entry(ofs);
40+
let pack_entry = self.pack.entry(ofs)?;
4141
let header_size = pack_entry.header_size();
4242
self.pack
4343
.decode_entry(
4444
pack_entry,
4545
out,
4646
inflate,
4747
&|id, _out| {
48-
self.index.lookup(id).map(|idx| {
49-
crate::data::decode::entry::ResolvedBase::InPack(
50-
self.pack.entry(self.index.pack_offset_at_index(idx)),
51-
)
52-
})
48+
let idx = self.index.lookup(id)?;
49+
self.pack
50+
.entry(self.index.pack_offset_at_index(idx))
51+
.ok()
52+
.map(crate::data::decode::entry::ResolvedBase::InPack)
5353
},
5454
cache,
5555
)

gix-pack/src/cache/delta/traverse/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub enum Error {
2626
},
2727
#[error("The resolver failed to obtain the pack entry bytes for the entry at {pack_offset}")]
2828
ResolveFailed { pack_offset: u64 },
29+
#[error(transparent)]
30+
EntryType(#[from] crate::data::entry::decode::Error),
2931
#[error("One of the object inspectors failed")]
3032
Inspect(#[from] Box<dyn std::error::Error + Send + Sync>),
3133
#[error("Interrupted")]

gix-pack/src/cache/delta/traverse/resolve.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ where
128128
let bytes = resolve(slice.clone(), resolve_data).ok_or(Error::ResolveFailed {
129129
pack_offset: slice.start,
130130
})?;
131-
let entry = data::Entry::from_bytes(bytes, slice.start, hash_len);
131+
let entry = data::Entry::from_bytes(bytes, slice.start, hash_len)?;
132132
let compressed = &bytes[entry.header_size()..];
133133
let decompressed_len = entry.decompressed_size as usize;
134134
decompress_all_at_once_with(&mut inflate, compressed, decompressed_len, out)?;
@@ -304,7 +304,7 @@ where
304304
let bytes = resolve(slice.clone(), resolve_data).ok_or(Error::ResolveFailed {
305305
pack_offset: slice.start,
306306
})?;
307-
let entry = data::Entry::from_bytes(bytes, slice.start, hash_len);
307+
let entry = data::Entry::from_bytes(bytes, slice.start, hash_len)?;
308308
let compressed = &bytes[entry.header_size()..];
309309
let decompressed_len = entry.decompressed_size as usize;
310310
decompress_all_at_once_with(&mut inflate, compressed, decompressed_len, out)?;

gix-pack/src/data/entry/decode.rs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,22 @@ use gix_features::decode::{leb64, leb64_from_read};
55
use super::{BLOB, COMMIT, OFS_DELTA, REF_DELTA, TAG, TREE};
66
use crate::data;
77

8+
/// The error returned by [data::Entry::from_bytes()].
9+
#[derive(Debug, thiserror::Error)]
10+
#[allow(missing_docs)]
11+
#[error("Object type {type_id} is unsupported")]
12+
pub struct Error {
13+
pub type_id: u8,
14+
}
15+
816
/// Decoding
917
impl data::Entry {
1018
/// Decode an entry from the given entry data `d`, providing the `pack_offset` to allow tracking the start of the entry data section.
1119
///
1220
/// # Panics
1321
///
1422
/// If we cannot understand the header, garbage data is likely to trigger this.
15-
pub fn from_bytes(d: &[u8], pack_offset: data::Offset, hash_len: usize) -> data::Entry {
23+
pub fn from_bytes(d: &[u8], pack_offset: data::Offset, hash_len: usize) -> Result<data::Entry, Error> {
1624
let (type_id, size, mut consumed) = parse_header_info(d);
1725

1826
use crate::data::entry::Header::*;
@@ -36,21 +44,17 @@ impl data::Entry {
3644
TREE => Tree,
3745
COMMIT => Commit,
3846
TAG => Tag,
39-
_ => panic!("We currently don't support any V3 features or extensions"),
47+
other => return Err(Error { type_id: other }),
4048
};
41-
data::Entry {
49+
Ok(data::Entry {
4250
header: object,
4351
decompressed_size: size,
4452
data_offset: pack_offset + consumed as u64,
45-
}
53+
})
4654
}
4755

4856
/// Instantiate an `Entry` from the reader `r`, providing the `pack_offset` to allow tracking the start of the entry data section.
49-
pub fn from_read(
50-
r: &mut dyn io::Read,
51-
pack_offset: data::Offset,
52-
hash_len: usize,
53-
) -> Result<data::Entry, io::Error> {
57+
pub fn from_read(r: &mut dyn io::Read, pack_offset: data::Offset, hash_len: usize) -> io::Result<data::Entry> {
5458
let (type_id, size, mut consumed) = streaming_parse_header_info(r)?;
5559

5660
use crate::data::entry::Header::*;
@@ -78,7 +82,12 @@ impl data::Entry {
7882
TREE => Tree,
7983
COMMIT => Commit,
8084
TAG => Tag,
81-
_ => panic!("We currently don't support any V3 features or extensions"),
85+
other => {
86+
return Err(io::Error::new(
87+
io::ErrorKind::Other,
88+
format!("Object type {other} is unsupported"),
89+
))
90+
}
8291
};
8392
Ok(data::Entry {
8493
header: object,

gix-pack/src/data/entry/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ impl Entry {
4545
}
4646
}
4747

48-
mod decode;
48+
///
49+
#[allow(clippy::empty_docs)]
50+
pub mod decode;
4951

5052
mod header;
5153
pub use header::Header;

gix-pack/src/data/file/decode/entry.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,18 +100,10 @@ impl File {
100100
.map_err(Into::into)
101101
}
102102

103-
fn assure_v2(&self) {
104-
assert!(
105-
matches!(self.version, crate::data::Version::V2),
106-
"Only V2 is implemented"
107-
);
108-
}
109-
110103
/// Obtain the [`Entry`][crate::data::Entry] at the given `offset` into the pack.
111104
///
112105
/// The `offset` is typically obtained from the pack index file.
113-
pub fn entry(&self, offset: data::Offset) -> data::Entry {
114-
self.assure_v2();
106+
pub fn entry(&self, offset: data::Offset) -> Result<data::Entry, data::entry::decode::Error> {
115107
let pack_offset: usize = offset.try_into().expect("offset representable by machine");
116108
assert!(pack_offset <= self.data.len(), "offset out of bounds");
117109

@@ -246,7 +238,7 @@ impl File {
246238
});
247239
use crate::data::entry::Header;
248240
cursor = match cursor.header {
249-
Header::OfsDelta { base_distance } => self.entry(cursor.base_pack_offset(base_distance)),
241+
Header::OfsDelta { base_distance } => self.entry(cursor.base_pack_offset(base_distance))?,
250242
Header::RefDelta { base_id } => match resolve(base_id.as_ref(), out) {
251243
Some(ResolvedBase::InPack(entry)) => entry,
252244
Some(ResolvedBase::OutOfPack { end, kind }) => {

gix-pack/src/data/file/decode/header.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ impl File {
6666
if first_delta_decompressed_size.is_none() {
6767
first_delta_decompressed_size = Some(self.decode_delta_object_size(inflate, &entry)?);
6868
}
69-
entry = self.entry(entry.base_pack_offset(base_distance))
69+
entry = self.entry(entry.base_pack_offset(base_distance))?
7070
}
7171
RefDelta { base_id } => {
7272
num_deltas += 1;

gix-pack/src/data/file/decode/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub enum Error {
1717
ZlibInflate(#[from] gix_features::zlib::inflate::Error),
1818
#[error("A delta chain could not be followed as the ref base with id {0} could not be found")]
1919
DeltaBaseUnresolved(gix_hash::ObjectId),
20+
#[error(transparent)]
21+
EntryType(#[from] crate::data::entry::decode::Error),
2022
#[error("Entry too large to fit in memory")]
2123
OutOfMemory,
2224
}

gix-pack/src/data/output/entry/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pub enum Kind {
3838
pub enum Error {
3939
#[error("{0}")]
4040
ZlibDeflate(#[from] std::io::Error),
41+
#[error(transparent)]
42+
EntryType(#[from] crate::data::entry::decode::Error),
4143
}
4244

4345
impl output::Entry {
@@ -74,7 +76,11 @@ impl output::Entry {
7476
};
7577

7678
let pack_offset_must_be_zero = 0;
77-
let pack_entry = data::Entry::from_bytes(&entry.data, pack_offset_must_be_zero, count.id.as_slice().len());
79+
let pack_entry = match data::Entry::from_bytes(&entry.data, pack_offset_must_be_zero, count.id.as_slice().len())
80+
{
81+
Ok(e) => e,
82+
Err(err) => return Some(Err(err.into())),
83+
};
7884

7985
use crate::data::entry::Header::*;
8086
match pack_entry.header {

gix-pack/src/index/traverse/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub enum Error<E: std::error::Error + Send + Sync + 'static> {
1212
Tree(#[from] crate::cache::delta::from_offsets::Error),
1313
#[error("The tree traversal failed")]
1414
TreeTraversal(#[from] crate::cache::delta::traverse::Error),
15+
#[error(transparent)]
16+
EntryType(#[from] crate::data::entry::decode::Error),
1517
#[error("Object {id} at offset {offset} could not be decoded")]
1618
PackDecode {
1719
id: gix_hash::ObjectId,

gix-pack/src/index/traverse/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,18 @@ impl index::File {
161161
C: crate::cache::DecodeEntry,
162162
E: std::error::Error + Send + Sync + 'static,
163163
{
164-
let pack_entry = pack.entry(index_entry.pack_offset);
164+
let pack_entry = pack.entry(index_entry.pack_offset)?;
165165
let pack_entry_data_offset = pack_entry.data_offset;
166166
let entry_stats = pack
167167
.decode_entry(
168168
pack_entry,
169169
buf,
170170
inflate,
171171
&|id, _| {
172-
self.lookup(id).map(|index| {
173-
crate::data::decode::entry::ResolvedBase::InPack(pack.entry(self.pack_offset_at_index(index)))
174-
})
172+
let index = self.lookup(id)?;
173+
pack.entry(self.pack_offset_at_index(index))
174+
.ok()
175+
.map(crate::data::decode::entry::ResolvedBase::InPack)
175176
},
176177
cache,
177178
)

gix-pack/src/multi_index/verify.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ impl File {
286286
TreeTraversal(err) => TreeTraversal(err),
287287
PackDecode { id, offset, source } => PackDecode { id, offset, source },
288288
PackMismatch { expected, actual } => PackMismatch { expected, actual },
289+
EntryType(err) => EntryType(err),
289290
PackObjectMismatch {
290291
expected,
291292
actual,

0 commit comments

Comments
 (0)