Skip to content

Commit 19948b7

Browse files
authored
Merge pull request #1254 from DanielEScherzer/blame-hunk-null-sig
Avoid null ptr dereferences for `BlameHunk` missing `Signature`s
2 parents f63d8f5 + 2eafc45 commit 19948b7

2 files changed

Lines changed: 132 additions & 21 deletions

File tree

examples/blame.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ fn run(args: &Args) -> Result<(), git2::Error> {
8181

8282
for (i, line) in reader.lines().enumerate() {
8383
if let (Ok(line), Some(hunk)) = (line, blame.get_line(i + 1)) {
84-
let sig = hunk.final_signature();
84+
let sig = hunk.final_signature().expect("Should have a signature");
8585
println!(
8686
"{} {} <{}> {}",
8787
hunk.final_commit_id(),

src/blame.rs

Lines changed: 131 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -106,18 +106,28 @@ impl<'blame> BlameHunk<'blame> {
106106
unsafe { Oid::from_raw(&(*self.raw).final_commit_id) }
107107
}
108108

109-
/// Returns signature for the author of the final commit.
109+
/// Returns signature for the author of the final commit, if present.
110110
///
111111
/// The final commit is the one identified by [Self::final_commit_id()].
112-
pub fn final_signature(&self) -> Signature<'_> {
113-
unsafe { signature::from_raw_const(self, (*self.raw).final_signature) }
112+
pub fn final_signature(&self) -> Option<Signature<'_>> {
113+
let ptr = unsafe { (*self.raw).final_signature };
114+
if ptr.is_null() {
115+
None
116+
} else {
117+
Some(unsafe { signature::from_raw_const(self, ptr) })
118+
}
114119
}
115120

116-
/// Returns signature for the committer of the final commit.
121+
/// Returns signature for the committer of the final commit, if present.
117122
///
118123
/// The final commit is the one identified by [Self::final_commit_id()].
119-
pub fn final_committer(&self) -> Signature<'_> {
120-
unsafe { signature::from_raw_const(self, (*self.raw).final_committer) }
124+
pub fn final_committer(&self) -> Option<Signature<'_>> {
125+
let ptr = unsafe { (*self.raw).final_committer };
126+
if ptr.is_null() {
127+
None
128+
} else {
129+
Some(unsafe { signature::from_raw_const(self, ptr) })
130+
}
121131
}
122132

123133
/// Returns line number where this hunk begins.
@@ -136,18 +146,28 @@ impl<'blame> BlameHunk<'blame> {
136146
unsafe { Oid::from_raw(&(*self.raw).orig_commit_id) }
137147
}
138148

139-
/// Returns signature of the author of the original commit.
149+
/// Returns signature of the author of the original commit, if present.
140150
///
141151
/// The original commit is the one identified by [Self::orig_commit_id()].
142-
pub fn orig_signature(&self) -> Signature<'_> {
143-
unsafe { signature::from_raw_const(self, (*self.raw).orig_signature) }
152+
pub fn orig_signature(&self) -> Option<Signature<'_>> {
153+
let ptr = unsafe { (*self.raw).orig_signature };
154+
if ptr.is_null() {
155+
None
156+
} else {
157+
Some(unsafe { signature::from_raw_const(self, ptr) })
158+
}
144159
}
145160

146-
/// Returns signature of the committer of the original commit.
161+
/// Returns signature of the committer of the original commit, if present.
147162
///
148163
/// The original commit is the one identified by [Self::orig_commit_id()].
149-
pub fn orig_committer(&self) -> Signature<'_> {
150-
unsafe { signature::from_raw_const(self, (*self.raw).orig_committer) }
164+
pub fn orig_committer(&self) -> Option<Signature<'_>> {
165+
let ptr = unsafe { (*self.raw).orig_committer };
166+
if ptr.is_null() {
167+
None
168+
} else {
169+
Some(unsafe { signature::from_raw_const(self, ptr) })
170+
}
151171
}
152172

153173
/// Returns line number where this hunk begins.
@@ -411,14 +431,20 @@ mod tests {
411431

412432
let hunk = blame.get_index(0).unwrap();
413433
assert_eq!(hunk.final_commit_id(), commit);
414-
assert_eq!(hunk.final_signature().name(), sig.name());
415-
assert_eq!(hunk.final_signature().email(), sig.email());
416-
assert_eq!(hunk.orig_signature().name(), sig.name());
417-
assert_eq!(hunk.orig_signature().email(), sig.email());
418-
assert_eq!(hunk.final_committer().name(), committer_sig.name());
419-
assert_eq!(hunk.final_committer().email(), committer_sig.email());
420-
assert_eq!(hunk.orig_committer().name(), committer_sig.name());
421-
assert_eq!(hunk.orig_committer().email(), committer_sig.email());
434+
assert_eq!(hunk.final_signature().unwrap().name(), sig.name());
435+
assert_eq!(hunk.final_signature().unwrap().email(), sig.email());
436+
assert_eq!(hunk.orig_signature().unwrap().name(), sig.name());
437+
assert_eq!(hunk.orig_signature().unwrap().email(), sig.email());
438+
assert_eq!(hunk.final_committer().unwrap().name(), committer_sig.name());
439+
assert_eq!(
440+
hunk.final_committer().unwrap().email(),
441+
committer_sig.email()
442+
);
443+
assert_eq!(hunk.orig_committer().unwrap().name(), committer_sig.name());
444+
assert_eq!(
445+
hunk.orig_committer().unwrap().email(),
446+
committer_sig.email()
447+
);
422448
assert_eq!(hunk.final_start_line(), 1);
423449
assert_eq!(hunk.path(), Some(Path::new("foo/bar")));
424450
assert_eq!(hunk.lines_in_hunk(), 0);
@@ -432,4 +458,89 @@ mod tests {
432458
assert_eq!(blame_buffer.iter().count(), 2);
433459
assert!(line.final_commit_id().is_zero());
434460
}
461+
462+
#[test]
463+
fn buffer_signatures() {
464+
// Regression tests for #1253
465+
let td = tempfile::TempDir::new().unwrap();
466+
let path = td.path();
467+
468+
let repo = crate::Repository::init(path).unwrap();
469+
470+
{
471+
let mut config = repo.config().unwrap();
472+
config.set_str("user.name", "name").unwrap();
473+
config.set_str("user.email", "email").unwrap();
474+
475+
fs::write(&path.join("README.md"), "Testing").unwrap();
476+
477+
let mut index = repo.index().unwrap();
478+
index.add_path(&Path::new("README.md")).unwrap();
479+
index.write().unwrap();
480+
481+
let id = index.write_tree().unwrap();
482+
let tree = repo.find_tree(id).unwrap();
483+
let sig = repo.signature().unwrap();
484+
repo.commit(Some("HEAD"), &sig, &sig, "Add README.md", &tree, &[])
485+
.unwrap();
486+
}
487+
488+
let blame = repo.blame_file(&Path::new("README.md"), None).unwrap();
489+
// This hunk is safe to use
490+
let hunk = blame.get_index(0).unwrap();
491+
492+
{
493+
let final_author = hunk.final_signature().unwrap();
494+
assert_eq!(Some("name"), final_author.name());
495+
assert_eq!(Some("email"), final_author.email());
496+
497+
let final_committer = hunk.final_committer().unwrap();
498+
assert_eq!(Some("name"), final_committer.name());
499+
assert_eq!(Some("email"), final_committer.email());
500+
501+
let original_author = hunk.orig_signature().unwrap();
502+
assert_eq!(Some("name"), original_author.name());
503+
assert_eq!(Some("email"), original_author.email());
504+
505+
let original_committer = hunk.orig_committer().unwrap();
506+
assert_eq!(Some("name"), original_committer.name());
507+
assert_eq!(Some("email"), original_committer.email());
508+
}
509+
510+
let arbitrary = blame.blame_buffer(b"abc123").unwrap();
511+
let hunk = arbitrary.get_index(0).unwrap();
512+
// This hunk is NOT safe to use
513+
// the final_signature, final_committer, orig_signature, and
514+
// orig_committer pointers are all NULL
515+
// But the other methods still work
516+
{
517+
let final_commit_id = hunk.final_commit_id();
518+
assert!(final_commit_id.is_zero());
519+
520+
let original_commit_id = hunk.orig_commit_id();
521+
assert!(original_commit_id.is_zero());
522+
523+
assert_eq!(1, hunk.final_start_line());
524+
assert_eq!(0, hunk.orig_start_line());
525+
assert_eq!(Some(Path::new("README.md")), hunk.path());
526+
assert_eq!(false, hunk.is_boundary());
527+
assert_eq!(1, hunk.lines_in_hunk());
528+
assert_eq!(None, hunk.summary());
529+
assert_eq!(None, hunk.summary_bytes());
530+
}
531+
532+
{
533+
let final_author = hunk.final_signature();
534+
assert!(final_author.is_none());
535+
536+
let final_committer = hunk.final_committer();
537+
assert!(final_committer.is_none());
538+
539+
let original_author = hunk.orig_signature();
540+
assert!(original_author.is_none());
541+
542+
let original_committer = hunk.orig_committer();
543+
assert!(original_committer.is_none());
544+
}
545+
}
435546
}

0 commit comments

Comments
 (0)