Skip to content

Commit c4e4a73

Browse files
committed
Merge branch 'jk/attr-macroexpand-wo-recursion' into next
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
2 parents c784b1e + 42ed046 commit c4e4a73

2 files changed

Lines changed: 54 additions & 16 deletions

File tree

attr.c

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,24 +1064,52 @@ static int path_matches(const char *pathname, int pathlen,
10641064
pattern, prefix, pat->patternlen);
10651065
}
10661066

1067-
static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem);
1067+
struct attr_state_queue {
1068+
const struct attr_state **items;
1069+
size_t alloc, nr;
1070+
};
1071+
1072+
static void attr_state_queue_push(struct attr_state_queue *t,
1073+
const struct match_attr *a)
1074+
{
1075+
for (size_t i = 0; i < a->num_attr; i++) {
1076+
ALLOC_GROW(t->items, t->nr + 1, t->alloc);
1077+
t->items[t->nr++] = &a->state[i];
1078+
}
1079+
}
1080+
1081+
static const struct attr_state *attr_state_queue_pop(struct attr_state_queue *t)
1082+
{
1083+
return t->nr ? t->items[--t->nr] : NULL;
1084+
}
1085+
1086+
static void attr_state_queue_release(struct attr_state_queue *t)
1087+
{
1088+
free(t->items);
1089+
}
10681090

10691091
static int fill_one(struct all_attrs_item *all_attrs,
10701092
const struct match_attr *a, int rem)
10711093
{
1072-
size_t i;
1094+
struct attr_state_queue todo = { 0 };
1095+
const struct attr_state *state;
10731096

1074-
for (i = a->num_attr; rem > 0 && i > 0; i--) {
1075-
const struct git_attr *attr = a->state[i - 1].attr;
1097+
attr_state_queue_push(&todo, a);
1098+
while (rem > 0 && (state = attr_state_queue_pop(&todo))) {
1099+
const struct git_attr *attr = state->attr;
10761100
const char **n = &(all_attrs[attr->attr_nr].value);
1077-
const char *v = a->state[i - 1].setto;
1101+
const char *v = state->setto;
10781102

10791103
if (*n == ATTR__UNKNOWN) {
1104+
const struct all_attrs_item *item =
1105+
&all_attrs[attr->attr_nr];
10801106
*n = v;
10811107
rem--;
1082-
rem = macroexpand_one(all_attrs, attr->attr_nr, rem);
1108+
if (item->macro && item->value == ATTR__TRUE)
1109+
attr_state_queue_push(&todo, item->macro);
10831110
}
10841111
}
1112+
attr_state_queue_release(&todo);
10851113
return rem;
10861114
}
10871115

@@ -1106,16 +1134,6 @@ static int fill(const char *path, int pathlen, int basename_offset,
11061134
return rem;
11071135
}
11081136

1109-
static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem)
1110-
{
1111-
const struct all_attrs_item *item = &all_attrs[nr];
1112-
1113-
if (item->macro && item->value == ATTR__TRUE)
1114-
return fill_one(all_attrs, item->macro, rem);
1115-
else
1116-
return rem;
1117-
}
1118-
11191137
/*
11201138
* Marks the attributes which are macros based on the attribute stack.
11211139
* This prevents having to search through the attribute stack each time

t/t0003-attributes.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,4 +664,24 @@ test_expect_success 'user defined builtin_objectmode values are ignored' '
664664
test_cmp expect err
665665
'
666666

667+
test_expect_success ULIMIT_STACK_SIZE 'deep macro recursion' '
668+
n=3000 &&
669+
{
670+
i=0 &&
671+
while test $i -lt $n; do
672+
echo "[attr]a$i a$((i+1))" &&
673+
i=$((i+1)) ||
674+
return 1
675+
done &&
676+
echo "[attr]a$n -text" &&
677+
echo "file a0"
678+
} >.gitattributes &&
679+
{
680+
echo "file: text: unset" &&
681+
test_seq -f "file: a%d: set" 0 $n
682+
} >expect &&
683+
run_with_limited_stack git check-attr -a file >actual &&
684+
test_cmp expect actual
685+
'
686+
667687
test_done

0 commit comments

Comments
 (0)