Commit 9710b2f3 authored by Kalesh Singh's avatar Kalesh Singh Committed by Steven Rostedt (VMware)

tracing: Fix operator precedence for hist triggers expression

The current histogram expression evaluation logic evaluates the
expression from right to left. This can lead to incorrect results
if the operations are not associative (as is the case for subtraction
and, the now added, division operators).
	e.g. 16-8-4-2 should be 2 not 10 --> 16-8-4-2 = ((16-8)-4)-2
	     64/8/4/2 should be 1 not 16 --> 64/8/4/2 = ((64/8)/4)/2

Division and multiplication are currently limited to single operation
expression due to operator precedence support not yet implemented.

Rework the expression parsing to support the correct evaluation of
expressions containing operators of different precedences; and fix
the associativity error by evaluating expressions with operators of
the same precedence from left to right.

Examples:
        (1) echo 'hist:keys=common_pid:a=8,b=4,c=2,d=1,w=$a-$b-$c-$d' \
                  >> event/trigger
        (2) echo 'hist:keys=common_pid:x=$a/$b/3/2' >> event/trigger
        (3) echo 'hist:keys=common_pid:y=$a+10/$c*1024' >> event/trigger
        (4) echo 'hist:keys=common_pid:z=$a/$b+$c*$d' >> event/trigger

Link: https://lkml.kernel.org/r/20211025200852.3002369-4-kaleshsingh@google.comSigned-off-by: default avatarKalesh Singh <kaleshsingh@google.com>
Reviewed-by: default avatarNamhyung Kim <namhyung@kernel.org>
Signed-off-by: default avatarSteven Rostedt (VMware) <rostedt@goodmis.org>
parent bcef0441
...@@ -67,7 +67,9 @@ ...@@ -67,7 +67,9 @@
C(TOO_MANY_SORT_FIELDS, "Too many sort fields (Max = 2)"), \ C(TOO_MANY_SORT_FIELDS, "Too many sort fields (Max = 2)"), \
C(INVALID_SORT_FIELD, "Sort field must be a key or a val"), \ C(INVALID_SORT_FIELD, "Sort field must be a key or a val"), \
C(INVALID_STR_OPERAND, "String type can not be an operand in expression"), \ C(INVALID_STR_OPERAND, "String type can not be an operand in expression"), \
C(EXPECT_NUMBER, "Expecting numeric literal"), C(EXPECT_NUMBER, "Expecting numeric literal"), \
C(UNARY_MINUS_SUBEXPR, "Unary minus not supported in sub-expressions"), \
C(SYM_OFFSET_SUBEXPR, ".sym-offset not supported in sub-expressions"),
#undef C #undef C
#define C(a, b) HIST_ERR_##a #define C(a, b) HIST_ERR_##a
...@@ -1644,40 +1646,96 @@ static char *expr_str(struct hist_field *field, unsigned int level) ...@@ -1644,40 +1646,96 @@ static char *expr_str(struct hist_field *field, unsigned int level)
return expr; return expr;
} }
static int contains_operator(char *str) /*
* If field_op != FIELD_OP_NONE, *sep points to the root operator
* of the expression tree to be evaluated.
*/
static int contains_operator(char *str, char **sep)
{ {
enum field_op_id field_op = FIELD_OP_NONE; enum field_op_id field_op = FIELD_OP_NONE;
char *op; char *minus_op, *plus_op, *div_op, *mult_op;
/*
* Report the last occurrence of the operators first, so that the
* expression is evaluated left to right. This is important since
* subtraction and division are not associative.
*
* e.g
* 64/8/4/2 is 1, i.e 64/8/4/2 = ((64/8)/4)/2
* 14-7-5-2 is 0, i.e 14-7-5-2 = ((14-7)-5)-2
*/
op = strpbrk(str, "+-/*"); /*
if (!op) * First, find lower precedence addition and subtraction
return FIELD_OP_NONE; * since the expression will be evaluated recursively.
*/
minus_op = strrchr(str, '-');
if (minus_op) {
/* Unfortunately, the modifier ".sym-offset" can confuse things. */
if (minus_op - str >= 4 && !strncmp(minus_op - 4, ".sym-offset", 11))
goto out;
switch (*op) {
case '-':
/* /*
* Unfortunately, the modifier ".sym-offset" * Unary minus is not supported in sub-expressions. If
* can confuse things. * present, it is always the next root operator.
*/ */
if (op - str >= 4 && !strncmp(op - 4, ".sym-offset", 11)) if (minus_op == str) {
return FIELD_OP_NONE;
if (*str == '-')
field_op = FIELD_OP_UNARY_MINUS; field_op = FIELD_OP_UNARY_MINUS;
else goto out;
field_op = FIELD_OP_MINUS; }
break;
case '+': field_op = FIELD_OP_MINUS;
field_op = FIELD_OP_PLUS; }
break;
case '/': plus_op = strrchr(str, '+');
if (plus_op || minus_op) {
/*
* For operators of the same precedence use to rightmost as the
* root, so that the expression is evaluated left to right.
*/
if (plus_op > minus_op)
field_op = FIELD_OP_PLUS;
goto out;
}
/*
* Multiplication and division have higher precedence than addition and
* subtraction.
*/
div_op = strrchr(str, '/');
if (div_op)
field_op = FIELD_OP_DIV; field_op = FIELD_OP_DIV;
break;
case '*': mult_op = strrchr(str, '*');
/*
* For operators of the same precedence use to rightmost as the
* root, so that the expression is evaluated left to right.
*/
if (mult_op > div_op)
field_op = FIELD_OP_MULT; field_op = FIELD_OP_MULT;
break;
default: out:
break; if (sep) {
switch (field_op) {
case FIELD_OP_UNARY_MINUS:
case FIELD_OP_MINUS:
*sep = minus_op;
break;
case FIELD_OP_PLUS:
*sep = plus_op;
break;
case FIELD_OP_DIV:
*sep = div_op;
break;
case FIELD_OP_MULT:
*sep = mult_op;
break;
case FIELD_OP_NONE:
default:
*sep = NULL;
break;
}
} }
return field_op; return field_op;
...@@ -2003,7 +2061,7 @@ static char *field_name_from_var(struct hist_trigger_data *hist_data, ...@@ -2003,7 +2061,7 @@ static char *field_name_from_var(struct hist_trigger_data *hist_data,
if (strcmp(var_name, name) == 0) { if (strcmp(var_name, name) == 0) {
field = hist_data->attrs->var_defs.expr[i]; field = hist_data->attrs->var_defs.expr[i];
if (contains_operator(field) || is_var_ref(field)) if (contains_operator(field, NULL) || is_var_ref(field))
continue; continue;
return field; return field;
} }
...@@ -2266,21 +2324,24 @@ static struct hist_field *parse_atom(struct hist_trigger_data *hist_data, ...@@ -2266,21 +2324,24 @@ static struct hist_field *parse_atom(struct hist_trigger_data *hist_data,
static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
struct trace_event_file *file, struct trace_event_file *file,
char *str, unsigned long flags, char *str, unsigned long flags,
char *var_name, unsigned int level); char *var_name, unsigned int *n_subexprs);
static struct hist_field *parse_unary(struct hist_trigger_data *hist_data, static struct hist_field *parse_unary(struct hist_trigger_data *hist_data,
struct trace_event_file *file, struct trace_event_file *file,
char *str, unsigned long flags, char *str, unsigned long flags,
char *var_name, unsigned int level) char *var_name, unsigned int *n_subexprs)
{ {
struct hist_field *operand1, *expr = NULL; struct hist_field *operand1, *expr = NULL;
unsigned long operand_flags; unsigned long operand_flags;
int ret = 0; int ret = 0;
char *s; char *s;
/* Unary minus operator, increment n_subexprs */
++*n_subexprs;
/* we support only -(xxx) i.e. explicit parens required */ /* we support only -(xxx) i.e. explicit parens required */
if (level > 3) { if (*n_subexprs > 3) {
hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str)); hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str));
ret = -EINVAL; ret = -EINVAL;
goto free; goto free;
...@@ -2297,8 +2358,16 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data, ...@@ -2297,8 +2358,16 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data,
} }
s = strrchr(str, ')'); s = strrchr(str, ')');
if (s) if (s) {
/* unary minus not supported in sub-expressions */
if (*(s+1) != '\0') {
hist_err(file->tr, HIST_ERR_UNARY_MINUS_SUBEXPR,
errpos(str));
ret = -EINVAL;
goto free;
}
*s = '\0'; *s = '\0';
}
else { else {
ret = -EINVAL; /* no closing ')' */ ret = -EINVAL; /* no closing ')' */
goto free; goto free;
...@@ -2312,7 +2381,7 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data, ...@@ -2312,7 +2381,7 @@ static struct hist_field *parse_unary(struct hist_trigger_data *hist_data,
} }
operand_flags = 0; operand_flags = 0;
operand1 = parse_expr(hist_data, file, str, operand_flags, NULL, ++level); operand1 = parse_expr(hist_data, file, str, operand_flags, NULL, n_subexprs);
if (IS_ERR(operand1)) { if (IS_ERR(operand1)) {
ret = PTR_ERR(operand1); ret = PTR_ERR(operand1);
goto free; goto free;
...@@ -2382,60 +2451,61 @@ static int check_expr_operands(struct trace_array *tr, ...@@ -2382,60 +2451,61 @@ static int check_expr_operands(struct trace_array *tr,
static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
struct trace_event_file *file, struct trace_event_file *file,
char *str, unsigned long flags, char *str, unsigned long flags,
char *var_name, unsigned int level) char *var_name, unsigned int *n_subexprs)
{ {
struct hist_field *operand1 = NULL, *operand2 = NULL, *expr = NULL; struct hist_field *operand1 = NULL, *operand2 = NULL, *expr = NULL;
unsigned long operand_flags; unsigned long operand_flags;
int field_op, ret = -EINVAL; int field_op, ret = -EINVAL;
char *sep, *operand1_str; char *sep, *operand1_str;
if (level > 3) { if (*n_subexprs > 3) {
hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str)); hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str));
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
} }
field_op = contains_operator(str); /*
* ".sym-offset" in expressions has no effect on their evaluation,
* but can confuse operator parsing.
*/
if (*n_subexprs == 0) {
sep = strstr(str, ".sym-offset");
if (sep) {
*sep = '\0';
if (strpbrk(str, "+-/*") || strpbrk(sep + 11, "+-/*")) {
*sep = '.';
hist_err(file->tr, HIST_ERR_SYM_OFFSET_SUBEXPR,
errpos(sep));
return ERR_PTR(-EINVAL);
}
*sep = '.';
}
}
field_op = contains_operator(str, &sep);
if (field_op == FIELD_OP_NONE) if (field_op == FIELD_OP_NONE)
return parse_atom(hist_data, file, str, &flags, var_name); return parse_atom(hist_data, file, str, &flags, var_name);
if (field_op == FIELD_OP_UNARY_MINUS) if (field_op == FIELD_OP_UNARY_MINUS)
return parse_unary(hist_data, file, str, flags, var_name, ++level); return parse_unary(hist_data, file, str, flags, var_name, n_subexprs);
switch (field_op) { /* Binary operator found, increment n_subexprs */
case FIELD_OP_MINUS: ++*n_subexprs;
sep = "-";
break;
case FIELD_OP_PLUS:
sep = "+";
break;
case FIELD_OP_DIV:
sep = "/";
break;
case FIELD_OP_MULT:
sep = "*";
break;
default:
goto free;
}
/* /* Split the expression string at the root operator */
* Multiplication and division are only supported in single operator if (!sep)
* expressions, since the expression is always evaluated from right goto free;
* to left. *sep = '\0';
*/ operand1_str = str;
if ((field_op == FIELD_OP_DIV || field_op == FIELD_OP_MULT) && level > 0) { str = sep+1;
hist_err(file->tr, HIST_ERR_TOO_MANY_SUBEXPR, errpos(str));
return ERR_PTR(-EINVAL);
}
operand1_str = strsep(&str, sep);
if (!operand1_str || !str) if (!operand1_str || !str)
goto free; goto free;
operand_flags = 0; operand_flags = 0;
operand1 = parse_atom(hist_data, file, operand1_str,
&operand_flags, NULL); /* LHS of string is an expression e.g. a+b in a+b+c */
operand1 = parse_expr(hist_data, file, operand1_str, operand_flags, NULL, n_subexprs);
if (IS_ERR(operand1)) { if (IS_ERR(operand1)) {
ret = PTR_ERR(operand1); ret = PTR_ERR(operand1);
operand1 = NULL; operand1 = NULL;
...@@ -2447,9 +2517,9 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data, ...@@ -2447,9 +2517,9 @@ static struct hist_field *parse_expr(struct hist_trigger_data *hist_data,
goto free; goto free;
} }
/* rest of string could be another expression e.g. b+c in a+b+c */ /* RHS of string is another expression e.g. c in a+b+c */
operand_flags = 0; operand_flags = 0;
operand2 = parse_expr(hist_data, file, str, operand_flags, NULL, ++level); operand2 = parse_expr(hist_data, file, str, operand_flags, NULL, n_subexprs);
if (IS_ERR(operand2)) { if (IS_ERR(operand2)) {
ret = PTR_ERR(operand2); ret = PTR_ERR(operand2);
operand2 = NULL; operand2 = NULL;
...@@ -3883,9 +3953,9 @@ static int __create_val_field(struct hist_trigger_data *hist_data, ...@@ -3883,9 +3953,9 @@ static int __create_val_field(struct hist_trigger_data *hist_data,
unsigned long flags) unsigned long flags)
{ {
struct hist_field *hist_field; struct hist_field *hist_field;
int ret = 0; int ret = 0, n_subexprs = 0;
hist_field = parse_expr(hist_data, file, field_str, flags, var_name, 0); hist_field = parse_expr(hist_data, file, field_str, flags, var_name, &n_subexprs);
if (IS_ERR(hist_field)) { if (IS_ERR(hist_field)) {
ret = PTR_ERR(hist_field); ret = PTR_ERR(hist_field);
goto out; goto out;
...@@ -4026,7 +4096,7 @@ static int create_key_field(struct hist_trigger_data *hist_data, ...@@ -4026,7 +4096,7 @@ static int create_key_field(struct hist_trigger_data *hist_data,
struct hist_field *hist_field = NULL; struct hist_field *hist_field = NULL;
unsigned long flags = 0; unsigned long flags = 0;
unsigned int key_size; unsigned int key_size;
int ret = 0; int ret = 0, n_subexprs = 0;
if (WARN_ON(key_idx >= HIST_FIELDS_MAX)) if (WARN_ON(key_idx >= HIST_FIELDS_MAX))
return -EINVAL; return -EINVAL;
...@@ -4039,7 +4109,7 @@ static int create_key_field(struct hist_trigger_data *hist_data, ...@@ -4039,7 +4109,7 @@ static int create_key_field(struct hist_trigger_data *hist_data,
hist_field = create_hist_field(hist_data, NULL, flags, NULL); hist_field = create_hist_field(hist_data, NULL, flags, NULL);
} else { } else {
hist_field = parse_expr(hist_data, file, field_str, flags, hist_field = parse_expr(hist_data, file, field_str, flags,
NULL, 0); NULL, &n_subexprs);
if (IS_ERR(hist_field)) { if (IS_ERR(hist_field)) {
ret = PTR_ERR(hist_field); ret = PTR_ERR(hist_field);
goto out; goto out;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment