Add a new function "free_object_buffer", which marks the object as
un-parsed and frees the buffer. Only trees and commits have buffers;
other types are not affected. If the tree or commit buffer is already
NULL, the "parsed" flag is still cleared so callers can control the free
themselves (index-pack.c uses this).

Several areas of code would free buffers for object structs that
contained them ("struct tree" and "struct commit"), but without clearing
the "parsed" flag. parse_object would clear the flag for "struct tree",
but commits would remain in an invalid state (marked as parsed but with
a NULL buffer). Because the objects are uniqued (ccdc6037fee), the
invalid objects stay around and can lead to bad behavior.

In particular, running pickaxe on all refs in a repository with a cached
textconv could segfault. If the textconv cache ref was evaluated first
by cmd_log_walk, a subsequent notes_cache_match_validity call would
dereference NULL.

Signed-off-by: Jonathon Mah <m...@jonathonmah.com>
---

I found an old email where Jeff noted that this would be bad (yet the buffer 
manipulation remained).
<http://permalink.gmane.org/gmane.comp.version-control.git/188000>

 builtin/fsck.c                   | 17 ++---------------
 builtin/index-pack.c             |  3 +++
 builtin/log.c                    |  9 +++------
 builtin/reflog.c                 |  3 +--
 builtin/rev-list.c               |  3 +--
 http-push.c                      |  3 +--
 list-objects.c                   |  3 +--
 object.c                         | 25 +++++++++++++++++++++++--
 object.h                         |  3 +++
 reachable.c                      |  3 +--
 revision.c                       |  3 +--
 t/t4042-diff-textconv-caching.sh | 11 +++++++++++
 upload-pack.c                    |  3 +--
 walker.c                         |  5 +----
 14 files changed, 53 insertions(+), 41 deletions(-)

diff --git a/builtin/fsck.c b/builtin/fsck.c
index bb9a2cd..82b3612 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -133,10 +133,7 @@ static int traverse_one_object(struct object *obj)
                        return 1; /* error already displayed */
        }
        result = fsck_walk(obj, mark_object, obj);
-       if (tree) {
-               free(tree->buffer);
-               tree->buffer = NULL;
-       }
+       free_object_buffer(obj);
        return result;
 }
 
@@ -303,26 +300,16 @@ static int fsck_obj(struct object *obj)
        if (fsck_object(obj, check_strict, fsck_error_func))
                return -1;
 
-       if (obj->type == OBJ_TREE) {
-               struct tree *item = (struct tree *) obj;
-
-               free(item->buffer);
-               item->buffer = NULL;
-       }
+       free_object_buffer(obj);
 
        if (obj->type == OBJ_COMMIT) {
                struct commit *commit = (struct commit *) obj;
-
-               free(commit->buffer);
-               commit->buffer = NULL;
-
                if (!commit->parents && show_root)
                        printf("root %s\n", sha1_to_hex(commit->object.sha1));
        }
 
        if (obj->type == OBJ_TAG) {
                struct tag *tag = (struct tag *) obj;
-
                if (show_tags && tag->tagged) {
                        printf("tagged %s %s", typename(tag->tagged->type), 
sha1_to_hex(tag->tagged->sha1));
                        printf(" (%s) in %s\n", tag->tag, 
sha1_to_hex(tag->object.sha1));
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 43d364b..0eb39ae 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -750,13 +750,16 @@ static void sha1_object(const void *data, struct 
object_entry *obj_entry,
                        if (fsck_walk(obj, mark_link, NULL))
                                die(_("Not all child objects of %s are 
reachable"), sha1_to_hex(obj->sha1));
 
+                       /* set buffer to NULL so it isn't freed */
                        if (obj->type == OBJ_TREE) {
                                struct tree *item = (struct tree *) obj;
                                item->buffer = NULL;
+                               free_object_buffer(obj);
                        }
                        if (obj->type == OBJ_COMMIT) {
                                struct commit *commit = (struct commit *) obj;
                                commit->buffer = NULL;
+                               free_object_buffer(obj);
                        }
                        obj->flags |= FLAG_CHECKED;
                }
diff --git a/builtin/log.c b/builtin/log.c
index e7b7db1..433b874 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -305,11 +305,9 @@ static int cmd_log_walk(struct rev_info *rev)
                         * but we didn't actually show the commit.
                         */
                        rev->max_count++;
-               if (!rev->reflog_info) {
+               if (!rev->reflog_info)
                        /* we allow cycles in reflog ancestry */
-                       free(commit->buffer);
-                       commit->buffer = NULL;
-               }
+                       free_object_buffer(&commit->object);
                free_commit_list(commit->parents);
                commit->parents = NULL;
                if (saved_nrl < rev->diffopt.needed_rename_limit)
@@ -1399,8 +1397,7 @@ int cmd_format_patch(int argc, const char **argv, const 
char *prefix)
                    reopen_stdout(numbered_files ? NULL : commit, NULL, &rev, 
quiet))
                        die(_("Failed to create output files"));
                shown = log_tree_commit(&rev, commit);
-               free(commit->buffer);
-               commit->buffer = NULL;
+               free_object_buffer(&commit->object);
 
                /* We put one extra blank line between formatted
                 * patches and this flag is used by log-tree code
diff --git a/builtin/reflog.c b/builtin/reflog.c
index b3c9e27..c9a660f 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -94,8 +94,7 @@ static int tree_is_complete(const unsigned char *sha1)
                        complete = 0;
                }
        }
-       free(tree->buffer);
-       tree->buffer = NULL;
+       free_object_buffer(tree->buffer);
 
        if (complete)
                tree->object.flags |= SEEN;
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 67701be..6855892 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -170,8 +170,7 @@ static void finish_commit(struct commit *commit, void *data)
                free_commit_list(commit->parents);
                commit->parents = NULL;
        }
-       free(commit->buffer);
-       commit->buffer = NULL;
+       free_object_buffer(&commit->object);
 }
 
 static void finish_object(struct object *obj,
diff --git a/http-push.c b/http-push.c
index 8701c12..042a410 100644
--- a/http-push.c
+++ b/http-push.c
@@ -1347,8 +1347,7 @@ static struct object_list **process_tree(struct tree 
*tree,
                        break;
                }
 
-       free(tree->buffer);
-       tree->buffer = NULL;
+       free_object_buffer(obj);
        return p;
 }
 
diff --git a/list-objects.c b/list-objects.c
index 3dd4a96..fb4b531 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -123,8 +123,7 @@ static void process_tree(struct rev_info *revs,
                                     cb_data);
        }
        strbuf_setlen(base, baselen);
-       free(tree->buffer);
-       tree->buffer = NULL;
+       free_object_buffer(obj);
 }
 
 static void mark_edge_parents_uninteresting(struct commit *commit,
diff --git a/object.c b/object.c
index 4af3451..8e161f8 100644
--- a/object.c
+++ b/object.c
@@ -149,8 +149,6 @@ struct object *parse_object_buffer(const unsigned char 
*sha1, enum object_type t
                struct tree *tree = lookup_tree(sha1);
                if (tree) {
                        obj = &tree->object;
-                       if (!tree->buffer)
-                               tree->object.parsed = 0;
                        if (!tree->object.parsed) {
                                if (parse_tree_buffer(tree, buffer, size))
                                        return NULL;
@@ -225,6 +223,29 @@ struct object *parse_object(const unsigned char *sha1)
        return NULL;
 }
 
+void free_object_buffer(struct object *item)
+{
+       if (!item)
+               return;
+
+       if (item->type == OBJ_TREE) {
+               struct tree *tree = (struct tree *)item;
+               tree->object.parsed = 0;
+               tree->size = 0;
+               if (tree->buffer) {
+                       free(tree->buffer);
+                       tree->buffer = NULL;
+               }
+       } else if (item->type == OBJ_COMMIT) {
+               struct commit *commit = (struct commit *)item;
+               commit->object.parsed = 0;
+               if (commit->buffer) {
+                       free(commit->buffer);
+                       commit->buffer = NULL;
+               }
+       }
+}
+
 struct object_list *object_list_insert(struct object *item,
                                       struct object_list **list_p)
 {
diff --git a/object.h b/object.h
index 6a97b6b..cbc730c 100644
--- a/object.h
+++ b/object.h
@@ -63,6 +63,9 @@ struct object *parse_object(const unsigned char *sha1);
  */
 struct object *parse_object_buffer(const unsigned char *sha1, enum object_type 
type, unsigned long size, void *buffer, int *eaten_p);
 
+/** If the object's type has a buffer, it's freed and marked as un-parsed. */
+void free_object_buffer(struct object *item);
+
 /** Returns the object, with potentially excess memory allocated. **/
 struct object *lookup_unknown_object(const unsigned  char *sha1);
 
diff --git a/reachable.c b/reachable.c
index bf79706..c29d3e0 100644
--- a/reachable.c
+++ b/reachable.c
@@ -80,8 +80,7 @@ static void process_tree(struct tree *tree,
                else
                        process_blob(lookup_blob(entry.sha1), p, &me, 
entry.path, cp);
        }
-       free(tree->buffer);
-       tree->buffer = NULL;
+       free_object_buffer(obj);
 }
 
 static void process_tag(struct tag *tag, struct object_array *p,
diff --git a/revision.c b/revision.c
index 95d21e6..43c5eec 100644
--- a/revision.c
+++ b/revision.c
@@ -133,8 +133,7 @@ void mark_tree_uninteresting(struct tree *tree)
         * We don't care about the tree any more
         * after it has been marked uninteresting.
         */
-       free(tree->buffer);
-       tree->buffer = NULL;
+       free_object_buffer(obj);
 }
 
 void mark_parents_uninteresting(struct commit *commit)
diff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh
index 91f8198..d7e26ca 100755
--- a/t/t4042-diff-textconv-caching.sh
+++ b/t/t4042-diff-textconv-caching.sh
@@ -106,4 +106,15 @@ test_expect_success 'switching diff driver produces 
correct results' '
        test_cmp expect actual
 '
 
+cat >expect <<EOF
+./helper other (refs/notes/textconv/magic)
+one
+EOF
+# add empty commit on master to make bug more reproducible
+test_expect_success 'pickaxe with cached textconv' '
+       git commit --allow-empty -m another &&
+       git log -S other --pretty=tformat:%s%d --all >actual &&
+       test_cmp expect actual
+'
+
 test_done
diff --git a/upload-pack.c b/upload-pack.c
index 6142421..1feacbc 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -81,8 +81,7 @@ static void show_commit(struct commit *commit, void *data)
                die("broken output pipe");
        fputc('\n', pack_pipe);
        fflush(pack_pipe);
-       free(commit->buffer);
-       commit->buffer = NULL;
+       free_object_buffer(&commit->object);
 }
 
 static void show_object(struct object *obj,
diff --git a/walker.c b/walker.c
index be389dc..bae4876 100644
--- a/walker.c
+++ b/walker.c
@@ -56,10 +56,7 @@ static int process_tree(struct walker *walker, struct tree 
*tree)
                if (!obj || process(walker, obj))
                        return -1;
        }
-       free(tree->buffer);
-       tree->buffer = NULL;
-       tree->size = 0;
-       tree->object.parsed = 0;
+       free_object_buffer(&tree->object);
        return 0;
 }
 
-- 
1.8.1.1





Jonathon Mah
m...@jonathonmah.com


--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to