aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2025-11-24 15:46:39 -0800
committerJunio C Hamano <gitster@pobox.com>2025-11-24 15:46:39 -0800
commit54f7817456940bb1f72cb747328e5fde75307dc3 (patch)
treeb046035205941760cf53b526320fc146fa161359
parentdebbc87557487aa9a8ed8a35367d17f8b4081c76 (diff)
parent42ed0468663dd493c0a0e00edc83b668369157d6 (diff)
downloadgit-54f7817456940bb1f72cb747328e5fde75307dc3.tar.xz
Merge branch 'jk/attr-macroexpand-wo-recursion'
The code to expand attribute macros has been rewritten to avoid recursion to avoid running out of stack space in an uncontrolled way. * jk/attr-macroexpand-wo-recursion: attr: avoid recursion when expanding attribute macros
-rw-r--r--attr.c50
-rwxr-xr-xt/t0003-attributes.sh20
2 files changed, 54 insertions, 16 deletions
diff --git a/attr.c b/attr.c
index d1daeb0b4d..4999b7e09d 100644
--- a/attr.c
+++ b/attr.c
@@ -1064,24 +1064,52 @@ static int path_matches(const char *pathname, int pathlen,
pattern, prefix, pat->patternlen);
}
-static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem);
+struct attr_state_queue {
+ const struct attr_state **items;
+ size_t alloc, nr;
+};
+
+static void attr_state_queue_push(struct attr_state_queue *t,
+ const struct match_attr *a)
+{
+ for (size_t i = 0; i < a->num_attr; i++) {
+ ALLOC_GROW(t->items, t->nr + 1, t->alloc);
+ t->items[t->nr++] = &a->state[i];
+ }
+}
+
+static const struct attr_state *attr_state_queue_pop(struct attr_state_queue *t)
+{
+ return t->nr ? t->items[--t->nr] : NULL;
+}
+
+static void attr_state_queue_release(struct attr_state_queue *t)
+{
+ free(t->items);
+}
static int fill_one(struct all_attrs_item *all_attrs,
const struct match_attr *a, int rem)
{
- size_t i;
+ struct attr_state_queue todo = { 0 };
+ const struct attr_state *state;
- for (i = a->num_attr; rem > 0 && i > 0; i--) {
- const struct git_attr *attr = a->state[i - 1].attr;
+ attr_state_queue_push(&todo, a);
+ while (rem > 0 && (state = attr_state_queue_pop(&todo))) {
+ const struct git_attr *attr = state->attr;
const char **n = &(all_attrs[attr->attr_nr].value);
- const char *v = a->state[i - 1].setto;
+ const char *v = state->setto;
if (*n == ATTR__UNKNOWN) {
+ const struct all_attrs_item *item =
+ &all_attrs[attr->attr_nr];
*n = v;
rem--;
- rem = macroexpand_one(all_attrs, attr->attr_nr, rem);
+ if (item->macro && item->value == ATTR__TRUE)
+ attr_state_queue_push(&todo, item->macro);
}
}
+ attr_state_queue_release(&todo);
return rem;
}
@@ -1106,16 +1134,6 @@ static int fill(const char *path, int pathlen, int basename_offset,
return rem;
}
-static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem)
-{
- const struct all_attrs_item *item = &all_attrs[nr];
-
- if (item->macro && item->value == ATTR__TRUE)
- return fill_one(all_attrs, item->macro, rem);
- else
- return rem;
-}
-
/*
* Marks the attributes which are macros based on the attribute stack.
* This prevents having to search through the attribute stack each time
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index 3c98b622f2..582e207aa1 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -664,4 +664,24 @@ test_expect_success 'user defined builtin_objectmode values are ignored' '
test_cmp expect err
'
+test_expect_success ULIMIT_STACK_SIZE 'deep macro recursion' '
+ n=3000 &&
+ {
+ i=0 &&
+ while test $i -lt $n; do
+ echo "[attr]a$i a$((i+1))" &&
+ i=$((i+1)) ||
+ return 1
+ done &&
+ echo "[attr]a$n -text" &&
+ echo "file a0"
+ } >.gitattributes &&
+ {
+ echo "file: text: unset" &&
+ test_seq -f "file: a%d: set" 0 $n
+ } >expect &&
+ run_with_limited_stack git check-attr -a file >actual &&
+ test_cmp expect actual
+'
+
test_done