Use chunks_exact where possible#1277
Conversation
Byron
left a comment
There was a problem hiding this comment.
Thanks a lot for contributing!
I will definitely keep chunks_exact() in mind as it's certainly less of a footgun than chunks(), even though both have their strength.
Regarding these, I think there are occasions where this is an improvement. In the other cases, I think it's better to go back to chunks() - the idea is that it should panic if there is unexpected input, which typically is checked beforehand.
| fn read_fan(d: &[u8]) -> ([u32; FAN_LEN], usize) { | ||
| let mut fan = [0; FAN_LEN]; | ||
| for (c, f) in d.chunks(4).zip(fan.iter_mut()) { | ||
| for (c, f) in d.chunks_exact(4).zip(fan.iter_mut()) { |
There was a problem hiding this comment.
Let's keep using chunks here, as that will cause a panic which is better than silently working with a fan that isn't fully initialised. Please apply this to all other instances of this function as well.
There was a problem hiding this comment.
.chunks() would not catch cases of broken input when when it's multiple of 4, e.g. 8-byte long d initializes 2 bytes, and keeps 254 bytes zeroed. I've added an assert instead, which catches all length mismatches.
There was a problem hiding this comment.
That's a good point! The assert is doing a much better job. I also checked the implementation and think that chunks_exact has the potential to be faster, so probably good to use it even if it otherwise wouldn't make a difference. (It does use more state on the stack as well, in case that ever matters).
| let pack64_offset = self.offset_pack_offset64_v2(); | ||
| match self.version { | ||
| index::Version::V2 => izip!( | ||
| self.data[V2_HEADER_SIZE..].chunks(self.hash_len), |
There was a problem hiding this comment.
Let's leave this at chunks as well, it should panic if the count is off (which it will).
There was a problem hiding this comment.
This could truncate izip! too, so maybe an explicit check of all 3 lengths would be better?.
There was a problem hiding this comment.
I've added length check, which helps with zip too.
There was a problem hiding this comment.
Thank you, very readable and a much stronger assertion.
gix-pack/src/index/access.rs
Outdated
| let pack_offset_64_start = self.offset_pack_offset64_v2(); | ||
| offset32_start | ||
| .chunks(N32_SIZE) | ||
| .chunks_exact(N32_SIZE) |
There was a problem hiding this comment.
Let's leave this at chunks as well, it should panic if the count is off (which it will).
There was a problem hiding this comment.
I've added an explicit length check instead. I think that makes intention clearer. It lowers risk of performing some work with side effects before panicking, and may be easier to diagnose bad inputs than panicking somewhere down the line.
4842419 to
2482023
Compare
|
gix-pack uses offsets into Is this necessary? Looking at implementation of offset_crc32_v2, offset_pack_offset_v2, offset_pack_offset64_v2 I can see they're one after another, but this relationship is unclear elsewhere that just uses the offset. The offsets are also used with an open range Instead of providing offsets, could these functions return right-sized slices of |
Byron
left a comment
There was a problem hiding this comment.
Great! Thanks a lot! I will definitely be more careful about the usage of chunks and chunks_exact() from now on.
| fn read_fan(d: &[u8]) -> ([u32; FAN_LEN], usize) { | ||
| let mut fan = [0; FAN_LEN]; | ||
| for (c, f) in d.chunks(4).zip(fan.iter_mut()) { | ||
| for (c, f) in d.chunks_exact(4).zip(fan.iter_mut()) { |
There was a problem hiding this comment.
That's a good point! The assert is doing a much better job. I also checked the implementation and think that chunks_exact has the potential to be faster, so probably good to use it even if it otherwise wouldn't make a difference. (It does use more state on the stack as well, in case that ever matters).
| let chunk_size = (entry_offsets.len() as f32 / num_threads as f32).ceil() as usize; | ||
| let num_chunks = entry_offsets.chunks(chunk_size).count(); | ||
| let entry_offsets_chunked = entry_offsets.chunks(chunk_size); | ||
| let num_chunks = entry_offsets_chunked.len(); |
There was a problem hiding this comment.
Using .len() is much better than count(), thanks for catching this!
| let pack64_offset = self.offset_pack_offset64_v2(); | ||
| match self.version { | ||
| index::Version::V2 => izip!( | ||
| self.data[V2_HEADER_SIZE..].chunks(self.hash_len), |
There was a problem hiding this comment.
Thank you, very readable and a much stronger assertion.
|
Apologies, I forgot to reply.
I don't now what you are alluding to. Please feel free to submit a PR with improvements if this can clear this up.
In the index, locations of all portions of data can be known in advance, I think, which should make it possible to determine the exact range a slice should occupy. A PR for this would definitely be welcome, maybe this could help avoid running into 'traps' laid by malicious actors. Note though that such an actor would need to be able to distribute full repos, with packs and indices, as typically all indices are build from scratch when cloning, a vital part of validating the received pack. From that point of view, there might be little benefit to further restricting the regions. Definitely your call though, as I wouldn't expect right-sizing to greatly increase the code's complexity. |
|
What I've meant was about Rust API/design pattern, instead of: let slice_of_crcs_and_more = &self.data[self.offset_crc32_v2()..];refactoring it to make the offset an implementation detail, and have: let slice_of_crcs_only = self.crc32_v2();where |
|
Thanks for clarifying. I think there shouldn't be any issue with refactoring, particularly if it allows to deduplicate some of the logic to make it less error prone. |
I've noticed a few functions use
chunks(len), but assume they will actually getlen-long chunks, even though there could be a shorter remainder chunk.I suspect there should be more strict length checks for the inputs to
chunks_(exact), andzipiterators. For exampleread_fanwould tolerate inputs shorter than the number of bytes read it returns.