From 8a5a88e4636e93fc9bedeb8685f6bc942d5a582e Mon Sep 17 00:00:00 2001 From: Cole Munz <145523609+munzzyy@users.noreply.github.com> Date: Mon, 6 Jul 2026 02:43:47 -0500 Subject: [PATCH] fix string leak in cli args_free (#2220) args_parse zeroed every option's value pointer right before returning, so args_free's NULL check always skipped the free. Even without that, args_free was freeing the address of the caller's static variable (e.g. &ext_vars), not the string it points to. On Windows (_UNICODE builds) each ARGS_OPT_STRING value is a heap copy from unicode_to_ansi(), so -d, -x, -i, atom-quality-table and module-data args leaked on every run. On non-Windows the value just aliases argv, nothing to free. Removed the value-clearing loop in args_parse (nothing reads .value after parsing except args_free) and rewrote args_free to walk options->count entries and free the actual string pointers, gated on _UNICODE so it's a no-op elsewhere. --- cli/args.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/cli/args.c b/cli/args.c index 939f3e8c..1ae6a128 100644 --- a/cli/args.c +++ b/cli/args.c @@ -257,9 +257,6 @@ int args_parse(args_option_t* options, int argc, const char_t** argv) i++; } - // Initialize to NULL the value pointers for all options. - for (; options->type != ARGS_OPT_END; options++) options->value = NULL; - return o; } @@ -298,11 +295,21 @@ void args_print_usage(args_option_t* options, int help_alignment) void args_free(args_option_t* options) { +#ifdef _UNICODE + // On Windows the string values stored by args_parse_option are ANSI + // copies allocated by unicode_to_ansi(), so they must be released here. + // On non-Windows builds the string values point directly into argv, + // there's nothing to free. for (; options->type != ARGS_OPT_END; options++) { - if (options->type == ARGS_OPT_STRING && options->value != NULL) - { - free(options->value); - } + if (options->type != ARGS_OPT_STRING || options->value == NULL) + continue; + + char** strings = (char**) options->value; + + for (int i = 0; i < options->count; i++) free(strings[i]); } +#else + (void) options; +#endif }