ref-filter: fix leak with unterminated %(if) atoms

When parsing `%(if)` atoms we expect a few other atoms to exist to complete it, like `%(then)` and `%(end)`. Whether or not we have seen these other atoms is tracked in an allocated `if_then_else` structure, which gets free'd by the `if_then_else_handler()` once we have parsed the complete conditional expression. This results in a memory leak when the `%(if)` atom is not terminated correctly and thus incomplete. We never end up executing its handler and thus don't end up freeing the structure. Plug this memory leak by introducing a new `at_end_data_free` callback function. If set, we'll execute it in `pop_stack_element()` and pass it the `at_end_data` variable with the intent to free its state. Wire it up for the `%(if)` atom accordingly. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Sep 10, 2024 at 08:57 UTC 04d9744f839dc90f27f08f94cc26f8bb33b3adfa
2 files changed +6 -3
ref-filter.c
+5 -3
@@ -1001,6 +1001,7 @@ struct ref_formatting_stack {
1001 struct ref_formatting_stack *prev;
1002 struct strbuf output;
1003 void (*at_end)(struct ref_formatting_stack **stack);
1004 + void (*at_end_data_free)(void *data);
1005 void *at_end_data;
1006 };
1007
@@ -1169,6 +1170,8 @@ static void pop_stack_element(struct ref_formatting_stack **stack)
1170 if (prev)
1171 strbuf_addbuf(&prev->output, &current->output);
1172 strbuf_release(&current->output);
1173 + if (current->at_end_data_free)
1174 + current->at_end_data_free(current->at_end_data);
1175 free(current);
1176 *stack = prev;
1177 }
@@ -1228,15 +1231,13 @@ static void if_then_else_handler(struct ref_formatting_stack **stack)
1231 }
1232
1233 *stack = cur;
1231 - free(if_then_else);
1234 }
1235
1236 static int if_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state,
1237 struct strbuf *err UNUSED)
1238 {
1239 struct ref_formatting_stack *new_stack;
1238 - struct if_then_else *if_then_else = xcalloc(1,
1239 - sizeof(struct if_then_else));
1240 + struct if_then_else *if_then_else = xcalloc(1, sizeof(*if_then_else));
1241
1242 if_then_else->str = atomv->atom->u.if_then_else.str;
1243 if_then_else->cmp_status = atomv->atom->u.if_then_else.cmp_status;
@@ -1245,6 +1246,7 @@ static int if_atom_handler(struct atom_value *atomv, struct ref_formatting_state
1246 new_stack = state->stack;
1247 new_stack->at_end = if_then_else_handler;
1248 new_stack->at_end_data = if_then_else;
1249 + new_stack->at_end_data_free = free;
1250 return 0;
1251 }
1252
t/t6302-for-each-ref-filter.sh
+1
@@ -2,6 +2,7 @@
2
3 test_description='test for-each-refs usage of ref-filter APIs'
4
5 +TEST_PASSES_SANITIZE_LEAK=true
6 . ./test-lib.sh
7 . "$TEST_DIRECTORY"/lib-gpg.sh
8