Skip to contentSkip to conversation

type_from_string_gently: make sure length matches

PATCH In review Jeff King 2 participants 4 messages Jun 15, 2016

lore
Share & tools
lore

Jeff King

author
#0 lore raw

PATCH 1/1 type_from_string_gently: make sure length matches

When commit fe8e3b7 refactored type_from_string to allow input that was not NUL-terminated, it switched to using strncmp instead of strcmp. But this means we check only the first "len" bytes of the strings, and ignore any remaining bytes in the object_type_string. We should make sure that it is also "len" bytes, or else we would accept "comm" as "commit", and so forth.

---
Since the strings we are matching are literals, we could also record their sizes in the object_type_strings array and check the length first before even calling strncmp. I doubt this is a performance hot-spot, though.

You could also potentially just use strlen(object_type_strings[i]), but I'm not sure if compilers will optimize out the strlen in this case, since it is in a loop.

 object.c               | 3 ++-
 t/t1007-hash-object.sh | 8 ++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)
object.c +2 -1
index 23d6c96..980ac5f 100644
@@ -41,7 +41,8 @@ int type_from_string_gently(const char *str, ssize_t len, int gentle)
 		len = strlen(str);
 
 	for (i = 1; i < ARRAY_SIZE(object_type_strings); i++)
-		if (!strncmp(str, object_type_strings[i], len))
+		if (!strncmp(str, object_type_strings[i], len) &&
+		    object_type_strings[i][len] == '\0')
 			return i;
 
 	if (gentle)
t/t1007-hash-object.sh +8 -0
index f83df8e..ebb3a69 100755
@@ -201,4 +201,12 @@ test_expect_success 'corrupt tag' '
 	test_must_fail git hash-object -t tag --stdin </dev/null
 '
 
+test_expect_success 'hash-object complains about bogus type name' '
+	test_must_fail git hash-object -t bogus --stdin </dev/null
+'
+
+test_expect_success 'hash-object complains about truncated type name' '
+	test_must_fail git hash-object -t bl --stdin </dev/null
+'
+
 test_done
Signature · 1 line
2.4.0.rc2.384.g7297a4a
#1 lore raw
Quoted 15 lines · Jeff King

When commit fe8e3b7 refactored type_from_string to allow input that was not NUL-terminated, it switched to using strncmp instead of strcmp. But this means we check only the first "len" bytes of the strings, and ignore any remaining bytes in the object_type_string. We should make sure that it is also "len" bytes, or else we would accept "comm" as "commit", and so forth.

---
Since the strings we are matching are literals, we could also record their sizes in the object_type_strings array and check the length first before even calling strncmp. I doubt this is a performance hot-spot, though.

Quoted 3 lines

You could also potentially just use strlen(object_type_strings[i]), but I'm not sure if compilers will optimize out the strlen in this case, since it is in a loop.

That thought crossed my mind while reading your patch. It could even make it go faster if we made object_type_strings into an array of counted strings (i.e. "struct { const char *str; int len; }") and then took advantage of the fact that we have lengths of both.

 object.c | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)
object.c +14 -10
index aedac24..51584ea 100644
@@ -18,19 +18,22 @@ struct object *get_indexed_object(unsigned int idx)
 	return obj_hash[idx];
 }
 
-static const char *object_type_strings[] = {
-	NULL,		/* OBJ_NONE = 0 */
-	"commit",	/* OBJ_COMMIT = 1 */
-	"tree",		/* OBJ_TREE = 2 */
-	"blob",		/* OBJ_BLOB = 3 */
-	"tag",		/* OBJ_TAG = 4 */
+static struct {
+	const char *str;
+	int len;
+} object_type_name[] = {
+	{ NULL, 0 },      /* OBJ_NONE = 0 */
+	{ "commit", 6 },  /* OBJ_COMMIT = 1 */
+	{ "tree", 4 },	  /* OBJ_TREE = 2 */
+	{ "blob", 4 },    /* OBJ_BLOB = 3 */
+	{ "tag", 3 },     /* OBJ_TAG = 4 */
 };
 
 const char *typename(unsigned int type)
 {
-	if (type >= ARRAY_SIZE(object_type_strings))
+	if (type >= ARRAY_SIZE(object_type_name))
 		return NULL;
-	return object_type_strings[type];
+	return object_type_name[type].str;
 }
 
 int type_from_string_gently(const char *str, ssize_t len, int gentle)
@@ -40,8 +43,9 @@ int type_from_string_gently(const char *str, ssize_t len, int gentle)
 	if (len < 0)
 		len = strlen(str);
 
-	for (i = 1; i < ARRAY_SIZE(object_type_strings); i++)
-		if (!strncmp(str, object_type_strings[i], len))
+	for (i = 1; i < ARRAY_SIZE(object_type_name); i++)
+		if (object_type_name[i].len == len &&
+			    !strncmp(str, object_type_name[i].str, len))
 			return i;
 
 	if (gentle)
author
#2 lore raw
Quoted 5 lines · Junio C Hamano
Quoted 5 lines

Since the strings we are matching are literals, we could also record their sizes in the object_type_strings array and check the length first before even calling strncmp. I doubt this is a performance hot-spot, though.

Quoted 8 lines
Quoted 3 lines

You could also potentially just use strlen(object_type_strings[i]), but I'm not sure if compilers will optimize out the strlen in this case, since it is in a loop.

That thought crossed my mind while reading your patch. It could even make it go faster if we made object_type_strings into an array of counted strings (i.e. "struct { const char *str; int len; }") and then took advantage of the fact that we have lengths of both.

Right, that was what I meant.

I'd be surprised if it appreciably speeds things up, but I guess it is not too complicated to do.

object.c struct object *get_indexed_object(unsigned int idx) · line 30

Quoted 10 lines
object.c +9 -0
+static struct {
+	const char *str;
+	int len;
+} object_type_name[] = {
+	{ NULL, 0 },      /* OBJ_NONE = 0 */
+	{ "commit", 6 },  /* OBJ_COMMIT = 1 */
+	{ "tree", 4 },	  /* OBJ_TREE = 2 */
+	{ "blob", 4 },    /* OBJ_BLOB = 3 */
+	{ "tag", 3 },     /* OBJ_TAG = 4 */
 };

I had envisioned a macro like:

  #define SIZED_STRING(x) { (x), (sizeof(x) - 1) }

though perhaps that is overkill for such a short list (that we don't even expect to change).

-Peff

#3 lore raw
Quoted 16 lines · Jeff King

I'd be surprised if it appreciably speeds things up, but I guess it is not too complicated to do.

object.c struct object *get_indexed_object(unsigned int idx) · line 30

Quoted 10 lines
object.c +9 -0
+static struct {
+	const char *str;
+	int len;
+} object_type_name[] = {
+	{ NULL, 0 },      /* OBJ_NONE = 0 */
+	{ "commit", 6 },  /* OBJ_COMMIT = 1 */
+	{ "tree", 4 },	  /* OBJ_TREE = 2 */
+	{ "blob", 4 },    /* OBJ_BLOB = 3 */
+	{ "tag", 3 },     /* OBJ_TAG = 4 */
 };

I had envisioned a macro like:

Quoted 4 lines
  #define SIZED_STRING(x) { (x), (sizeof(x) - 1) }

though perhaps that is overkill for such a short list (that we don't even expect to change).

Sounds good (either way ;-)