Re: [PATCH 1/2] perf c2c: Fix error masking, OOM, and unchecked caller errors in hpp_list__parse()
Ian Rogers <[email protected]> Wed, 5 Aug 2026 17:00:40 -0700
| Newsgroups | org.kernel.vger.linux-perf-users,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CAP-5=fXKQAxrukBh2CazptHvoZdFhaOhLkZC6fXmgPf2fLD9Sw@mail.gmail.com> |
On Wed, Aug 5, 2026 at 8:11 AM Arnaldo Carvalho de Melo <[email protected]> wrote: > > From: Arnaldo Carvalho de Melo <[email protected]> > > hpp_list__parse() has three bugs: > > 1. The PARSE_LIST macro resets ret = 0 at the start of each invocation, > so an error from output parsing is silently overwritten when the sort > parsing block runs. The function returns success with partially > initialized state. > > 2. When the caller passes a non-NULL output_ or sort_ string, but > strdup() returns NULL due to OOM, NULL is passed to PARSE_LIST which > treats it as empty input (the "if (!_list) break" branch). No error > is returned. > > 3. When the called _fn function fails and returns something other than > -ESRCH or -EINVAL (-ENOMEM, for instance) it was not bailing out of > the strtok loop. > > Fix them by checking strdup() return values before proceeding and adding > a cleanup label so that ret from each PARSE_LIST call is checked before > the next runs, preserving the first error. > > The early exits now skip perf_hpp__setup_output_field(), which means > c2c_hists__reinit() can return a non-zero value in cases that previously > always succeeded silently. Both callers discarded its return: > resort_cl_cb() continued into hists__collapse_resort() on a broken list, > and perf_c2c__report() proceeded with uninitialised hists. Fix the full > chain: check and propagate the error in resort_cl_cb() -- hists__iterate_cb() > already stops iteration and returns the callback error -- and check both > c2c_hists__reinit() and hists__iterate_cb() in perf_c2c__report(). > > Also turn PARSE_LIST into a function, using a switch to catch other > errors, converting the called functions to return an appropriate errno > instead of -1 on failure. > > Also make the two callers that iterate sort_dimension__add() and > output_field_add() handle the newly propagated errors: setup_sort_list() > and setup_output_list() only checked for -EINVAL and -ESRCH, so an > -ENOMEM from a failed allocation was silently overwritten by the next > loop iteration. Break out of the loop and propagate any other error. > > The hpp_list__parse() fixes were developed with AI assistance from > Claude:claude-sonnet-4.6, and the setup_sort_list()/setup_output_list() > caller fixes with AI assistance from Opencode:mimo-v2.5-free and > Opencode:DeepSeek-V4-Flash-free. > > Fixes: 2d388bd0c9d3 ("perf c2c report: Add stdio output support") > Reported-by: sashiko-bot <[email protected]> > Cc: Jiri Olsa <[email protected]> > Assisted-by: Claude:claude-sonnet-4.6 > Assisted-by: Opencode:mimo-v2.5-free > Assisted-by: Opencode:DeepSeek-V4-Flash-free > Signed-off-by: Arnaldo Carvalho de Melo <[email protected]> Reviewed-by: Ian Rogers <[email protected]> Thanks, Ian > --- > tools/perf/builtin-c2c.c | 85 ++++++++++++++++++++++++++++------------ > tools/perf/util/sort.c | 76 +++++++++++++++++++++++------------ > 2 files changed, 112 insertions(+), 49 deletions(-) > > diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c > index c9584dbedf77afe8..160b82694d391c50 100644 > --- a/tools/perf/builtin-c2c.c > +++ b/tools/perf/builtin-c2c.c > @@ -12,11 +12,14 @@ > */ > #include <errno.h> > #include <inttypes.h> > +#include <stdlib.h> > +#include <string.h> > > #include <asm/bug.h> > #include <linux/compiler.h> > #include <linux/err.h> > #include <linux/kernel.h> > +#include <linux/string.h> > #include <linux/stringify.h> > #include <linux/zalloc.h> > #include <sys/param.h> > @@ -2063,26 +2066,38 @@ static int c2c_hists__init_sort(struct perf_hpp_list *hpp_list, char *name, stru > return 0; > } > > -#define PARSE_LIST(_list, _fn) \ > - do { \ > - char *tmp, *tok; \ > - ret = 0; \ > - \ > - if (!_list) \ > - break; \ > - \ > - for (tok = strtok_r((char *)_list, ", ", &tmp); \ > - tok; tok = strtok_r(NULL, ", ", &tmp)) { \ > - ret = _fn(hpp_list, tok, env); \ > - if (ret == -EINVAL) { \ > - pr_err("Invalid --fields key: `%s'", tok); \ > - break; \ > - } else if (ret == -ESRCH) { \ > - pr_err("Unknown --fields key: `%s'", tok); \ > - break; \ > - } \ > - } \ > - } while (0) > +static int __hpp_list__parse(struct perf_hpp_list *hpp_list, char *_list, struct perf_env *env, > + int (*_fn)(struct perf_hpp_list *hpp_list, char *name, struct perf_env *env)) > +{ > + char *tmp, *tok; > + int ret = 0; > + > + if (!_list) > + return 0; > + > + for (tok = strtok_r(_list, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) { > + ret = _fn(hpp_list, tok, env); > + switch (ret) { > + case 0: > + continue; > + case -EINVAL: > + pr_err("Invalid --fields key: `%s'", tok); > + goto out; > + case -ESRCH: > + pr_err("Unknown --fields key: `%s'", tok); > + goto out; > + default: { > + char buf[STRERR_BUFSIZE]; > + > + pr_err("%s for --fields key: `%s'", > + str_error_r(-ret, buf, sizeof(buf)), tok); > + goto out; > + } > + } > + } > +out: > + return ret; > +} > > static int hpp_list__parse(struct perf_hpp_list *hpp_list, > const char *output_, > @@ -2093,8 +2108,18 @@ static int hpp_list__parse(struct perf_hpp_list *hpp_list, > char *sort = sort_ ? strdup(sort_) : NULL; > int ret; > > - PARSE_LIST(output, c2c_hists__init_output); > - PARSE_LIST(sort, c2c_hists__init_sort); > + /* strdup() returns NULL on OOM, don't silently treat as empty */ > + if ((output_ && !output) || (sort_ && !sort)) { > + ret = -ENOMEM; > + goto out; > + } > + > + ret = __hpp_list__parse(hpp_list, output, env, c2c_hists__init_output); > + if (ret) > + goto out; > + ret = __hpp_list__parse(hpp_list, sort, env, c2c_hists__init_sort); > + if (ret) > + goto out; > > /* copy sort keys to output fields */ > perf_hpp__setup_output_field(hpp_list); > @@ -2111,6 +2136,7 @@ static int hpp_list__parse(struct perf_hpp_list *hpp_list, > perf_hpp__append_sort_keys(&hists->list); > #endif > > +out: > free(output); > free(sort); > return ret; > @@ -2281,6 +2307,7 @@ static int resort_cl_cb(struct hist_entry *he, void *arg) > struct c2c_hist_entry *c2c_he; > struct c2c_hists *c2c_hists; > bool display = he__display(he, &c2c.shared_clines_stats); > + int ret; > > c2c_he = container_of(he, struct c2c_hist_entry, he); > c2c_hists = c2c_he->hists; > @@ -2291,7 +2318,9 @@ static int resort_cl_cb(struct hist_entry *he, void *arg) > c2c_he->cacheline_idx = idx++; > calc_width(c2c_he); > > - c2c_hists__reinit(c2c_hists, c2c.cl_output, c2c.cl_resort, env); > + ret = c2c_hists__reinit(c2c_hists, c2c.cl_output, c2c.cl_resort, env); > + if (ret) > + return ret; > > hists__collapse_resort(&c2c_hists->hists, NULL); > hists__output_resort_cb(&c2c_hists->hists, NULL, filter_cb); > @@ -3356,13 +3385,19 @@ static int perf_c2c__report(int argc, const char **argv) > else if (c2c.display == DISPLAY_SNP_PEER) > sort_str = "tot_peer"; > > - c2c_hists__reinit(&c2c.hists, output_str, sort_str, perf_session__env(session)); > + err = c2c_hists__reinit(&c2c.hists, output_str, sort_str, perf_session__env(session)); > + if (err) { > + pr_err("Failed to reinitialize hists\n"); > + goto out_mem2node; > + } > > ui_progress__init(&prog, c2c.hists.hists.nr_entries, "Sorting..."); > > hists__collapse_resort(&c2c.hists.hists, NULL); > hists__output_resort_cb(&c2c.hists.hists, &prog, resort_shared_cl_cb); > - hists__iterate_cb(&c2c.hists.hists, resort_cl_cb, perf_session__env(session)); > + err = hists__iterate_cb(&c2c.hists.hists, resort_cl_cb, perf_session__env(session)); > + if (err) > + goto out_mem2node; > > ui_progress__finish(); > > diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c > index dcf9189786f8aeba..58638ec9ae0ede7f 100644 > --- a/tools/perf/util/sort.c > +++ b/tools/perf/util/sort.c > @@ -3105,7 +3105,7 @@ static int __sort_dimension__add_hpp_sort(struct sort_dimension *sd, > struct hpp_sort_entry *hse = __sort_dimension__alloc_hpp(sd, level); > > if (hse == NULL) > - return -1; > + return -ENOMEM; > > perf_hpp_list__register_sort_field(list, &hse->hpp); > return 0; > @@ -3118,7 +3118,7 @@ static int __sort_dimension__add_hpp_output(struct sort_dimension *sd, > struct hpp_sort_entry *hse = __sort_dimension__alloc_hpp(sd, level); > > if (hse == NULL) > - return -1; > + return -ENOMEM; > > perf_hpp_list__column_register(list, &hse->hpp); > return 0; > @@ -3742,14 +3742,18 @@ static int __sort_dimension__add(struct sort_dimension *sd, > struct perf_hpp_list *list, > int level) > { > + int ret; > + > if (sd->taken) > return 0; > > - if (__sort_dimension__add_hpp_sort(sd, list, level) < 0) > - return -1; > + ret = __sort_dimension__add_hpp_sort(sd, list, level); > + if (ret < 0) > + return ret; > > - if (__sort_dimension__update(sd, list) < 0) > - return -1; > + ret = __sort_dimension__update(sd, list); > + if (ret < 0) > + return ret; > > sd->taken = 1; > > @@ -3767,7 +3771,7 @@ static int __hpp_dimension__add(struct hpp_dimension *hd, > > fmt = __hpp_dimension__alloc_hpp(hd, level); > if (!fmt) > - return -1; > + return -ENOMEM; > > hd->taken = 1; > hd->was_taken = 1; > @@ -3779,14 +3783,18 @@ static int __sort_dimension__add_output(struct perf_hpp_list *list, > struct sort_dimension *sd, > int level) > { > + int ret; > + > if (sd->taken) > return 0; > > - if (__sort_dimension__add_hpp_output(sd, list, level) < 0) > - return -1; > + ret = __sort_dimension__add_hpp_output(sd, list, level); > + if (ret < 0) > + return ret; > > - if (__sort_dimension__update(sd, list) < 0) > - return -1; > + ret = __sort_dimension__update(sd, list); > + if (ret < 0) > + return ret; > > sd->taken = 1; > return 0; > @@ -3803,7 +3811,7 @@ static int __hpp_dimension__add_output(struct perf_hpp_list *list, > > fmt = __hpp_dimension__alloc_hpp(hd, level); > if (!fmt) > - return -1; > + return -ENOMEM; > > hd->taken = 1; > perf_hpp_list__column_register(list, fmt); > @@ -3869,8 +3877,7 @@ int sort_dimension__add(struct perf_hpp_list *list, const char *tok, > strlen(tok))) > return -EINVAL; > > - __sort_dimension__add(sd, list, level); > - return 0; > + return __sort_dimension__add(sd, list, level); > } > > for (i = 0; i < ARRAY_SIZE(memory_sort_dimensions); i++) { > @@ -3882,8 +3889,7 @@ int sort_dimension__add(struct perf_hpp_list *list, const char *tok, > if (sort__mode != SORT_MODE__MEMORY) > return -EINVAL; > > - __sort_dimension__add(sd, list, level); > - return 0; > + return __sort_dimension__add(sd, list, level); > } > > for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++) { > @@ -3973,15 +3979,25 @@ static int setup_sort_list(struct perf_hpp_list *list, char *str, > } > > ret = sort_dimension__add(list, tok, evlist, env, level); > - if (ret == -EINVAL) { > + switch (ret) { > + case 0: > + break; > + case -EINVAL: > if (!cacheline_size() && !strncasecmp(tok, "dcacheline", strlen(tok))) > ui__error("The \"dcacheline\" --sort key needs to know the cacheline size and it couldn't be determined on this system"); > else > ui__error("Invalid --sort key: `%s'", tok); > - break; > - } else if (ret == -ESRCH) { > + goto out; > + case -ESRCH: > ui__error("Unknown --sort key: `%s'", tok); > - break; > + goto out; > + default: { > + char buf[STRERR_BUFSIZE]; > + > + ui__error("%s for --sort key: `%s'", > + str_error_r(-ret, buf, sizeof(buf)), tok); > + goto out; > + } > } > prev_level = level; > } > @@ -3989,6 +4005,7 @@ static int setup_sort_list(struct perf_hpp_list *list, char *str, > level = next_level; > } while (tmp); > > +out: > return ret; > } > > @@ -4315,15 +4332,26 @@ static int setup_output_list(struct perf_hpp_list *list, char *str) > for (tok = strtok_r(str, ", ", &tmp); > tok; tok = strtok_r(NULL, ", ", &tmp)) { > ret = output_field_add(list, tok, &level); > - if (ret == -EINVAL) { > - ui__error("Invalid --fields key: `%s'", tok); > + switch (ret) { > + case 0: > break; > - } else if (ret == -ESRCH) { > + case -EINVAL: > + ui__error("Invalid --fields key: `%s'", tok); > + goto out; > + case -ESRCH: > ui__error("Unknown --fields key: `%s'", tok); > - break; > + goto out; > + default: { > + char buf[STRERR_BUFSIZE]; > + > + ui__error("%s for --fields key: `%s'", > + str_error_r(-ret, buf, sizeof(buf)), tok); > + goto out; > + } > } > } > > +out: > return ret; > } > > -- > 2.55.0 >