From 59866881977802c43981b2ca9bbb26945373d02b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= <jdavid.ibp@gmail.com>
Date: Mon, 19 Dec 2011 23:40:27 +0100
Subject: [PATCH] refs: fix segfault when using a deleted reference

Before this patch the code below produced a segfault:

  >>> reference.delete()
  >>> reference.name

Now an exception is raised.
---
 TODO.txt          |  6 +++
 pygit2.c          | 98 ++++++++++++++++++++++++++++-------------------
 test/test_refs.py | 14 ++++++-
 3 files changed, 77 insertions(+), 41 deletions(-)

diff --git a/TODO.txt b/TODO.txt
index d878858..7b33ba4 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -3,6 +3,12 @@ Signature
 - Implement equality interface
 - In Repository's create_commit/create_tag check signatures encoding is right
 
+References
+==========
+- Free the git_reference struct.
+- Wrap missing functions: git_reference_foreach, git_reference_is_packed,
+  git_reference_reload
+
 Other
 =========
 - Make the Py_LOCAL_INLINE macro to work with Python 2.6, 2.7 and 3.1
diff --git a/pygit2.c b/pygit2.c
index a5c537f..c9b41d0 100644
--- a/pygit2.c
+++ b/pygit2.c
@@ -67,6 +67,17 @@ to_bytes(const char * value)
   #define to_encoding(x) PyUnicode_DecodeASCII(x, strlen(x), "strict")
 #endif
 
+#define CHECK_REFERENCE(self)\
+    if (self->reference == NULL) {\
+        PyErr_SetString(GitError, "deleted reference");\
+        return NULL;\
+    }
+
+#define CHECK_REFERENCE_INT(self)\
+    if (self->reference == NULL) {\
+        PyErr_SetString(GitError, "deleted reference");\
+        return -1;\
+    }
 
 /* Python objects */
 typedef struct {
@@ -773,26 +784,21 @@ Repository_listall_references(Repository *self, PyObject *args)
 
     /* 3- Create a new PyTuple */
     py_result = PyTuple_New(c_result.count);
-    if (py_result == NULL) {
-        git_strarray_free(&c_result);
-        return NULL;
-    }
+    if (py_result == NULL)
+        goto out;
 
     /* 4- Fill it */
     for (index=0; index < c_result.count; index++) {
         py_string = to_path((c_result.strings)[index]);
         if (py_string == NULL) {
-            Py_XDECREF(py_result);
-            git_strarray_free(&c_result);
-            return NULL;
+            Py_CLEAR(py_result);
+            goto out;
         }
         PyTuple_SET_ITEM(py_result, index, py_string);
     }
 
-    /* 5- Destroy the c_result */
+out:
     git_strarray_free(&c_result);
-
-    /* 6- And return the py_result */
     return py_result;
 }
 
@@ -811,7 +817,7 @@ Repository_lookup_reference(Repository *self, PyObject *py_name)
     /* 2- Lookup */
     err = git_reference_lookup(&c_reference, self->repo, c_name);
     if (err < 0)
-      return Error_set(err);
+        return Error_set_str(err, c_name);
 
     /* 3- Make an instance of Reference and return it */
     return wrap_reference(c_reference);
@@ -860,7 +866,7 @@ Repository_create_symbolic_reference(Repository *self,  PyObject *args)
     err = git_reference_create_symbolic(&c_reference, self->repo, c_name,
                                         c_target, 0);
     if (err < 0)
-      return Error_set(err);
+        return Error_set(err);
 
     /* 3- Make an instance of Reference and return it */
     return wrap_reference(c_reference);
@@ -2363,16 +2369,15 @@ Reference_delete(Reference *self, PyObject *args)
 {
     int err;
 
-    /* 1- Delete the reference */
+    CHECK_REFERENCE(self);
+
+    /* Delete the reference */
     err = git_reference_delete(self->reference);
     if (err < 0)
-      return Error_set(err);
+        return Error_set(err);
 
-    /* 2- Invalidate the pointer */
-    self->reference = NULL;
-
-    /* 3- Return None */
-    Py_RETURN_NONE;
+    self->reference = NULL; /* Invalidate the pointer */
+    Py_RETURN_NONE;         /* Return None */
 }
 
 static PyObject *
@@ -2381,18 +2386,19 @@ Reference_rename(Reference *self, PyObject *py_name)
     char *c_name;
     int err;
 
-    /* 1- Get the C name */
+    CHECK_REFERENCE(self);
+
+    /* Get the C name */
     c_name = py_path_to_c_str(py_name);
     if (c_name == NULL)
         return NULL;
 
-    /* 2- Rename */
+    /* Rename */
     err = git_reference_rename(self->reference, c_name, 0);
     if (err < 0)
-      return Error_set(err);
+        return Error_set(err);
 
-    /* 3- Return None */
-    Py_RETURN_NONE;
+    Py_RETURN_NONE; /* Return None */
 }
 
 static PyObject *
@@ -2401,12 +2407,14 @@ Reference_resolve(Reference *self, PyObject *args)
     git_reference *c_reference;
     int err;
 
-    /* 1- Resolve */
+    CHECK_REFERENCE(self);
+
+    /* Resolve */
     err = git_reference_resolve(&c_reference, self->reference);
     if (err < 0)
-      return Error_set(err);
+        return Error_set(err);
 
-    /* 2- Make an instance of Reference and return it */
+    /* Make an instance of Reference and return it */
     return wrap_reference(c_reference);
 }
 
@@ -2415,14 +2423,16 @@ Reference_get_target(Reference *self)
 {
     const char * c_name;
 
-    /* 1- Get the target */
+    CHECK_REFERENCE(self);
+
+    /* Get the target */
     c_name = git_reference_target(self->reference);
     if (c_name == NULL) {
-        PyErr_SetString(PyExc_ValueError, "Not target available");
+        PyErr_SetString(PyExc_ValueError, "no target available");
         return NULL;
     }
 
-    /* 2- Make a PyString and return it */
+    /* Make a PyString and return it */
     return to_path(c_name);
 }
 
@@ -2432,25 +2442,27 @@ Reference_set_target(Reference *self, PyObject *py_name)
     char *c_name;
     int err;
 
-    /* 1- Get the C name */
+    CHECK_REFERENCE_INT(self);
+
+    /* Get the C name */
     c_name = py_path_to_c_str(py_name);
     if (c_name == NULL)
         return -1;
 
-    /* 2- Set the new target */
+    /* Set the new target */
     err = git_reference_set_target(self->reference, c_name);
     if (err < 0) {
         Error_set(err);
         return -1;
     }
 
-    /* 3- All OK */
     return 0;
 }
 
 static PyObject *
 Reference_get_name(Reference *self)
 {
+    CHECK_REFERENCE(self);
     return to_path(git_reference_name(self->reference));
 }
 
@@ -2459,7 +2471,9 @@ Reference_get_oid(Reference *self)
 {
     const git_oid *oid;
 
-    /* 1- Get the oid (only for "direct" references) */
+    CHECK_REFERENCE(self);
+
+    /* Get the oid (only for "direct" references) */
     oid = git_reference_oid(self->reference);
     if (oid == NULL) {
         PyErr_SetString(PyExc_ValueError,
@@ -2468,7 +2482,7 @@ Reference_get_oid(Reference *self)
         return NULL;
     }
 
-    /* 2- Convert and return it */
+    /* Convert and return it */
     return git_oid_to_python(oid->id);
 }
 
@@ -2479,20 +2493,21 @@ Reference_set_oid(Reference *self, PyObject *py_hex)
     int err;
     size_t len;
 
-    /* 1- Get the oid */
+    CHECK_REFERENCE_INT(self);
+
+    /* Get the oid */
     len = py_str_to_git_oid(py_hex, &oid);
     TODO_SUPPORT_SHORT_HEXS(len)
     if (len == 0)
         return -1;
 
-    /* 2- Set the oid */
+    /* Set the oid */
     err = git_reference_set_oid(self->reference, &oid);
     if (err < 0) {
         Error_set(err);
         return -1;
     }
 
-    /* 3- All OK */
     return 0;
 }
 
@@ -2501,7 +2516,9 @@ Reference_get_hex(Reference *self)
 {
     const git_oid *oid;
 
-    /* 1- Get the oid (only for "direct" references) */
+    CHECK_REFERENCE(self);
+
+    /* Get the oid (only for "direct" references) */
     oid = git_reference_oid(self->reference);
     if (oid == NULL) {
         PyErr_SetString(PyExc_ValueError,
@@ -2510,7 +2527,7 @@ Reference_get_hex(Reference *self)
         return NULL;
     }
 
-    /* 2- Convert and return it */
+    /* Convert and return it */
     return git_oid_to_py_str(oid);
 }
 
@@ -2519,6 +2536,7 @@ Reference_get_type(Reference *self)
 {
     git_rtype c_type;
 
+    CHECK_REFERENCE(self);
     c_type = git_reference_type(self->reference);
     return PyInt_FromLong(c_type);
 }
diff --git a/test/test_refs.py b/test/test_refs.py
index e457d82..de4c054 100644
--- a/test/test_refs.py
+++ b/test/test_refs.py
@@ -31,7 +31,7 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 import unittest
 
-from pygit2 import GIT_REF_OID, GIT_REF_SYMBOLIC
+from pygit2 import GitError, GIT_REF_OID, GIT_REF_SYMBOLIC
 from . import utils
 
 
@@ -113,6 +113,18 @@ class ReferencesTest(utils.RepoTestCase):
         reference.delete()
         self.assertFalse('refs/tags/version1' in repo.listall_references())
 
+        # Access the deleted reference
+        self.assertRaises(GitError, getattr, reference, 'name')
+        self.assertRaises(GitError, getattr, reference, 'type')
+        self.assertRaises(GitError, getattr, reference, 'oid')
+        self.assertRaises(GitError, setattr, reference, 'oid', LAST_COMMIT)
+        self.assertRaises(GitError, getattr, reference, 'hex')
+        self.assertRaises(GitError, getattr, reference, 'target')
+        self.assertRaises(GitError, setattr, reference, 'target', "a/b/c")
+        self.assertRaises(GitError, reference.delete)
+        self.assertRaises(GitError, reference.resolve)
+        self.assertRaises(GitError, reference.rename, "refs/tags/version2")
+
 
     def test_rename(self):
         # We add a tag as a new reference that points to "origin/master"