Commit f8d16175 authored by Eduard Zingerman's avatar Eduard Zingerman Committed by Alexei Starovoitov

selftests/bpf: replace __regex macro with "{{...}}" patterns

Upcoming changes require a notation to specify regular expression
matches for regular verifier log messages, disassembly of BPF
instructions, disassembly of jited instructions.

Neither basic nor extended POSIX regular expressions w/o additional
escaping are good for this role because of wide use of special
characters in disassembly, for example:

    movq -0x10(%rbp), %rax  ;; () are special characters
    cmpq $0x21, %rax        ;; $ is a special character

    *(u64 *)(r10 -16) = r1  ;; * and () are special characters

This commit borrows syntax from LLVM's FileCheck utility.
It replaces __regex macro with ability to embed regular expressions
in __msg patters using "{{" "}}" pairs for escaping.
Syntax for __msg patterns:

    pattern := (<verbatim text> | regex)*
    regex := "{{" <posix extended regular expression> "}}"

For example, pattern "foo{{[0-9]+}}" matches strings like
"foo0", "foo007", etc.
Signed-off-by: default avatarEduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20240820102357.3372779-5-eddyz87@gmail.comSigned-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parent f00bb757
...@@ -25,12 +25,15 @@ ...@@ -25,12 +25,15 @@
* *
* __msg Message expected to be found in the verifier log. * __msg Message expected to be found in the verifier log.
* Multiple __msg attributes could be specified. * Multiple __msg attributes could be specified.
* To match a regular expression use "{{" "}}" brackets,
* e.g. "foo{{[0-9]+}}" matches strings like "foo007".
* Extended POSIX regular expression syntax is allowed
* inside the brackets.
* __msg_unpriv Same as __msg but for unprivileged mode. * __msg_unpriv Same as __msg but for unprivileged mode.
* *
* __regex Same as __msg, but using a regular expression.
* __regex_unpriv Same as __msg_unpriv but using a regular expression.
* __xlated Expect a line in a disassembly log after verifier applies rewrites. * __xlated Expect a line in a disassembly log after verifier applies rewrites.
* Multiple __xlated attributes could be specified. * Multiple __xlated attributes could be specified.
* Regular expressions could be specified same way as in __msg.
* __xlated_unpriv Same as __xlated but for unprivileged mode. * __xlated_unpriv Same as __xlated but for unprivileged mode.
* *
* __success Expect program load success in privileged mode. * __success Expect program load success in privileged mode.
...@@ -72,13 +75,11 @@ ...@@ -72,13 +75,11 @@
* When test case is not run on current arch it is marked as skipped. * When test case is not run on current arch it is marked as skipped.
*/ */
#define __msg(msg) __attribute__((btf_decl_tag("comment:test_expect_msg=" XSTR(__COUNTER__) "=" msg))) #define __msg(msg) __attribute__((btf_decl_tag("comment:test_expect_msg=" XSTR(__COUNTER__) "=" msg)))
#define __regex(regex) __attribute__((btf_decl_tag("comment:test_expect_regex=" XSTR(__COUNTER__) "=" regex)))
#define __xlated(msg) __attribute__((btf_decl_tag("comment:test_expect_xlated=" XSTR(__COUNTER__) "=" msg))) #define __xlated(msg) __attribute__((btf_decl_tag("comment:test_expect_xlated=" XSTR(__COUNTER__) "=" msg)))
#define __failure __attribute__((btf_decl_tag("comment:test_expect_failure"))) #define __failure __attribute__((btf_decl_tag("comment:test_expect_failure")))
#define __success __attribute__((btf_decl_tag("comment:test_expect_success"))) #define __success __attribute__((btf_decl_tag("comment:test_expect_success")))
#define __description(desc) __attribute__((btf_decl_tag("comment:test_description=" desc))) #define __description(desc) __attribute__((btf_decl_tag("comment:test_description=" desc)))
#define __msg_unpriv(msg) __attribute__((btf_decl_tag("comment:test_expect_msg_unpriv=" XSTR(__COUNTER__) "=" msg))) #define __msg_unpriv(msg) __attribute__((btf_decl_tag("comment:test_expect_msg_unpriv=" XSTR(__COUNTER__) "=" msg)))
#define __regex_unpriv(regex) __attribute__((btf_decl_tag("comment:test_expect_regex_unpriv=" XSTR(__COUNTER__) "=" regex)))
#define __xlated_unpriv(msg) __attribute__((btf_decl_tag("comment:test_expect_xlated_unpriv=" XSTR(__COUNTER__) "=" msg))) #define __xlated_unpriv(msg) __attribute__((btf_decl_tag("comment:test_expect_xlated_unpriv=" XSTR(__COUNTER__) "=" msg)))
#define __failure_unpriv __attribute__((btf_decl_tag("comment:test_expect_failure_unpriv"))) #define __failure_unpriv __attribute__((btf_decl_tag("comment:test_expect_failure_unpriv")))
#define __success_unpriv __attribute__((btf_decl_tag("comment:test_expect_success_unpriv"))) #define __success_unpriv __attribute__((btf_decl_tag("comment:test_expect_success_unpriv")))
......
...@@ -964,7 +964,7 @@ int dynptr_invalidate_slice_reinit(void *ctx) ...@@ -964,7 +964,7 @@ int dynptr_invalidate_slice_reinit(void *ctx)
* mem_or_null pointers. * mem_or_null pointers.
*/ */
SEC("?raw_tp") SEC("?raw_tp")
__failure __regex("R[0-9]+ type=scalar expected=percpu_ptr_") __failure __msg("R{{[0-9]+}} type=scalar expected=percpu_ptr_")
int dynptr_invalidate_slice_or_null(void *ctx) int dynptr_invalidate_slice_or_null(void *ctx)
{ {
struct bpf_dynptr ptr; struct bpf_dynptr ptr;
...@@ -982,7 +982,7 @@ int dynptr_invalidate_slice_or_null(void *ctx) ...@@ -982,7 +982,7 @@ int dynptr_invalidate_slice_or_null(void *ctx)
/* Destruction of dynptr should also any slices obtained from it */ /* Destruction of dynptr should also any slices obtained from it */
SEC("?raw_tp") SEC("?raw_tp")
__failure __regex("R[0-9]+ invalid mem access 'scalar'") __failure __msg("R{{[0-9]+}} invalid mem access 'scalar'")
int dynptr_invalidate_slice_failure(void *ctx) int dynptr_invalidate_slice_failure(void *ctx)
{ {
struct bpf_dynptr ptr1; struct bpf_dynptr ptr1;
...@@ -1069,7 +1069,7 @@ int dynptr_read_into_slot(void *ctx) ...@@ -1069,7 +1069,7 @@ int dynptr_read_into_slot(void *ctx)
/* bpf_dynptr_slice()s are read-only and cannot be written to */ /* bpf_dynptr_slice()s are read-only and cannot be written to */
SEC("?tc") SEC("?tc")
__failure __regex("R[0-9]+ cannot write into rdonly_mem") __failure __msg("R{{[0-9]+}} cannot write into rdonly_mem")
int skb_invalid_slice_write(struct __sk_buff *skb) int skb_invalid_slice_write(struct __sk_buff *skb)
{ {
struct bpf_dynptr ptr; struct bpf_dynptr ptr;
......
...@@ -105,7 +105,7 @@ long rbtree_api_remove_unadded_node(void *ctx) ...@@ -105,7 +105,7 @@ long rbtree_api_remove_unadded_node(void *ctx)
} }
SEC("?tc") SEC("?tc")
__failure __regex("Unreleased reference id=3 alloc_insn=[0-9]+") __failure __msg("Unreleased reference id=3 alloc_insn={{[0-9]+}}")
long rbtree_api_remove_no_drop(void *ctx) long rbtree_api_remove_no_drop(void *ctx)
{ {
struct bpf_rb_node *res; struct bpf_rb_node *res;
......
...@@ -32,7 +32,7 @@ static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b) ...@@ -32,7 +32,7 @@ static bool less(struct bpf_rb_node *a, const struct bpf_rb_node *b)
} }
SEC("?tc") SEC("?tc")
__failure __regex("Unreleased reference id=4 alloc_insn=[0-9]+") __failure __msg("Unreleased reference id=4 alloc_insn={{[0-9]+}}")
long rbtree_refcounted_node_ref_escapes(void *ctx) long rbtree_refcounted_node_ref_escapes(void *ctx)
{ {
struct node_acquire *n, *m; struct node_acquire *n, *m;
...@@ -73,7 +73,7 @@ long refcount_acquire_maybe_null(void *ctx) ...@@ -73,7 +73,7 @@ long refcount_acquire_maybe_null(void *ctx)
} }
SEC("?tc") SEC("?tc")
__failure __regex("Unreleased reference id=3 alloc_insn=[0-9]+") __failure __msg("Unreleased reference id=3 alloc_insn={{[0-9]+}}")
long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx) long rbtree_refcounted_node_ref_escapes_owning_input(void *ctx)
{ {
struct node_acquire *n, *m; struct node_acquire *n, *m;
......
...@@ -19,12 +19,10 @@ ...@@ -19,12 +19,10 @@
#define TEST_TAG_EXPECT_FAILURE "comment:test_expect_failure" #define TEST_TAG_EXPECT_FAILURE "comment:test_expect_failure"
#define TEST_TAG_EXPECT_SUCCESS "comment:test_expect_success" #define TEST_TAG_EXPECT_SUCCESS "comment:test_expect_success"
#define TEST_TAG_EXPECT_MSG_PFX "comment:test_expect_msg=" #define TEST_TAG_EXPECT_MSG_PFX "comment:test_expect_msg="
#define TEST_TAG_EXPECT_REGEX_PFX "comment:test_expect_regex="
#define TEST_TAG_EXPECT_XLATED_PFX "comment:test_expect_xlated=" #define TEST_TAG_EXPECT_XLATED_PFX "comment:test_expect_xlated="
#define TEST_TAG_EXPECT_FAILURE_UNPRIV "comment:test_expect_failure_unpriv" #define TEST_TAG_EXPECT_FAILURE_UNPRIV "comment:test_expect_failure_unpriv"
#define TEST_TAG_EXPECT_SUCCESS_UNPRIV "comment:test_expect_success_unpriv" #define TEST_TAG_EXPECT_SUCCESS_UNPRIV "comment:test_expect_success_unpriv"
#define TEST_TAG_EXPECT_MSG_PFX_UNPRIV "comment:test_expect_msg_unpriv=" #define TEST_TAG_EXPECT_MSG_PFX_UNPRIV "comment:test_expect_msg_unpriv="
#define TEST_TAG_EXPECT_REGEX_PFX_UNPRIV "comment:test_expect_regex_unpriv="
#define TEST_TAG_EXPECT_XLATED_PFX_UNPRIV "comment:test_expect_xlated_unpriv=" #define TEST_TAG_EXPECT_XLATED_PFX_UNPRIV "comment:test_expect_xlated_unpriv="
#define TEST_TAG_LOG_LEVEL_PFX "comment:test_log_level=" #define TEST_TAG_LOG_LEVEL_PFX "comment:test_log_level="
#define TEST_TAG_PROG_FLAGS_PFX "comment:test_prog_flags=" #define TEST_TAG_PROG_FLAGS_PFX "comment:test_prog_flags="
...@@ -55,8 +53,9 @@ enum mode { ...@@ -55,8 +53,9 @@ enum mode {
struct expect_msg { struct expect_msg {
const char *substr; /* substring match */ const char *substr; /* substring match */
const char *regex_str; /* regex-based match */
regex_t regex; regex_t regex;
bool is_regex;
bool on_next_line;
}; };
struct expected_msgs { struct expected_msgs {
...@@ -111,7 +110,7 @@ static void free_msgs(struct expected_msgs *msgs) ...@@ -111,7 +110,7 @@ static void free_msgs(struct expected_msgs *msgs)
int i; int i;
for (i = 0; i < msgs->cnt; i++) for (i = 0; i < msgs->cnt; i++)
if (msgs->patterns[i].regex_str) if (msgs->patterns[i].is_regex)
regfree(&msgs->patterns[i].regex); regfree(&msgs->patterns[i].regex);
free(msgs->patterns); free(msgs->patterns);
msgs->patterns = NULL; msgs->patterns = NULL;
...@@ -132,12 +131,71 @@ static void free_test_spec(struct test_spec *spec) ...@@ -132,12 +131,71 @@ static void free_test_spec(struct test_spec *spec)
spec->unpriv.name = NULL; spec->unpriv.name = NULL;
} }
static int push_msg(const char *substr, const char *regex_str, struct expected_msgs *msgs) /* Compiles regular expression matching pattern.
* Pattern has a special syntax:
*
* pattern := (<verbatim text> | regex)*
* regex := "{{" <posix extended regular expression> "}}"
*
* In other words, pattern is a verbatim text with inclusion
* of regular expressions enclosed in "{{" "}}" pairs.
* For example, pattern "foo{{[0-9]+}}" matches strings like
* "foo0", "foo007", etc.
*/
static int compile_regex(const char *pattern, regex_t *regex)
{
char err_buf[256], buf[256] = {}, *ptr, *buf_end;
const char *original_pattern = pattern;
bool in_regex = false;
int err;
buf_end = buf + sizeof(buf);
ptr = buf;
while (*pattern && ptr < buf_end - 2) {
if (!in_regex && str_has_pfx(pattern, "{{")) {
in_regex = true;
pattern += 2;
continue;
}
if (in_regex && str_has_pfx(pattern, "}}")) {
in_regex = false;
pattern += 2;
continue;
}
if (in_regex) {
*ptr++ = *pattern++;
continue;
}
/* list of characters that need escaping for extended posix regex */
if (strchr(".[]\\()*+?{}|^$", *pattern)) {
*ptr++ = '\\';
*ptr++ = *pattern++;
continue;
}
*ptr++ = *pattern++;
}
if (*pattern) {
PRINT_FAIL("Regexp too long: '%s'\n", original_pattern);
return -EINVAL;
}
if (in_regex) {
PRINT_FAIL("Regexp has open '{{' but no closing '}}': '%s'\n", original_pattern);
return -EINVAL;
}
err = regcomp(regex, buf, REG_EXTENDED | REG_NEWLINE);
if (err != 0) {
regerror(err, regex, err_buf, sizeof(err_buf));
PRINT_FAIL("Regexp compilation error in '%s': '%s'\n", buf, err_buf);
return -EINVAL;
}
return 0;
}
static int __push_msg(const char *pattern, bool on_next_line, struct expected_msgs *msgs)
{ {
void *tmp;
int regcomp_res;
char error_msg[100];
struct expect_msg *msg; struct expect_msg *msg;
void *tmp;
int err;
tmp = realloc(msgs->patterns, tmp = realloc(msgs->patterns,
(1 + msgs->cnt) * sizeof(struct expect_msg)); (1 + msgs->cnt) * sizeof(struct expect_msg));
...@@ -147,26 +205,38 @@ static int push_msg(const char *substr, const char *regex_str, struct expected_m ...@@ -147,26 +205,38 @@ static int push_msg(const char *substr, const char *regex_str, struct expected_m
} }
msgs->patterns = tmp; msgs->patterns = tmp;
msg = &msgs->patterns[msgs->cnt]; msg = &msgs->patterns[msgs->cnt];
msg->on_next_line = on_next_line;
if (substr) { msg->substr = pattern;
msg->substr = substr; msg->is_regex = false;
msg->regex_str = NULL; if (strstr(pattern, "{{")) {
} else { err = compile_regex(pattern, &msg->regex);
msg->regex_str = regex_str; if (err)
msg->substr = NULL; return err;
regcomp_res = regcomp(&msg->regex, regex_str, REG_EXTENDED|REG_NEWLINE); msg->is_regex = true;
if (regcomp_res != 0) {
regerror(regcomp_res, &msg->regex, error_msg, sizeof(error_msg));
PRINT_FAIL("Regexp compilation error in '%s': '%s'\n",
regex_str, error_msg);
return -EINVAL;
}
} }
msgs->cnt += 1; msgs->cnt += 1;
return 0; return 0;
} }
static int clone_msgs(struct expected_msgs *from, struct expected_msgs *to)
{
struct expect_msg *msg;
int i, err;
for (i = 0; i < from->cnt; i++) {
msg = &from->patterns[i];
err = __push_msg(msg->substr, msg->on_next_line, to);
if (err)
return err;
}
return 0;
}
static int push_msg(const char *substr, struct expected_msgs *msgs)
{
return __push_msg(substr, false, msgs);
}
static int parse_int(const char *str, int *val, const char *name) static int parse_int(const char *str, int *val, const char *name)
{ {
char *end; char *end;
...@@ -320,32 +390,22 @@ static int parse_test_spec(struct test_loader *tester, ...@@ -320,32 +390,22 @@ static int parse_test_spec(struct test_loader *tester,
spec->auxiliary = true; spec->auxiliary = true;
spec->mode_mask |= UNPRIV; spec->mode_mask |= UNPRIV;
} else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_MSG_PFX))) { } else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_MSG_PFX))) {
err = push_msg(msg, NULL, &spec->priv.expect_msgs); err = push_msg(msg, &spec->priv.expect_msgs);
if (err) if (err)
goto cleanup; goto cleanup;
spec->mode_mask |= PRIV; spec->mode_mask |= PRIV;
} else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_MSG_PFX_UNPRIV))) { } else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_MSG_PFX_UNPRIV))) {
err = push_msg(msg, NULL, &spec->unpriv.expect_msgs); err = push_msg(msg, &spec->unpriv.expect_msgs);
if (err)
goto cleanup;
spec->mode_mask |= UNPRIV;
} else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_REGEX_PFX))) {
err = push_msg(NULL, msg, &spec->priv.expect_msgs);
if (err)
goto cleanup;
spec->mode_mask |= PRIV;
} else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_REGEX_PFX_UNPRIV))) {
err = push_msg(NULL, msg, &spec->unpriv.expect_msgs);
if (err) if (err)
goto cleanup; goto cleanup;
spec->mode_mask |= UNPRIV; spec->mode_mask |= UNPRIV;
} else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_XLATED_PFX))) { } else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_XLATED_PFX))) {
err = push_msg(msg, NULL, &spec->priv.expect_xlated); err = push_msg(msg, &spec->priv.expect_xlated);
if (err) if (err)
goto cleanup; goto cleanup;
spec->mode_mask |= PRIV; spec->mode_mask |= PRIV;
} else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_XLATED_PFX_UNPRIV))) { } else if ((msg = skip_dynamic_pfx(s, TEST_TAG_EXPECT_XLATED_PFX_UNPRIV))) {
err = push_msg(msg, NULL, &spec->unpriv.expect_xlated); err = push_msg(msg, &spec->unpriv.expect_xlated);
if (err) if (err)
goto cleanup; goto cleanup;
spec->mode_mask |= UNPRIV; spec->mode_mask |= UNPRIV;
...@@ -457,26 +517,10 @@ static int parse_test_spec(struct test_loader *tester, ...@@ -457,26 +517,10 @@ static int parse_test_spec(struct test_loader *tester,
spec->unpriv.execute = spec->priv.execute; spec->unpriv.execute = spec->priv.execute;
} }
if (spec->unpriv.expect_msgs.cnt == 0) { if (spec->unpriv.expect_msgs.cnt == 0)
for (i = 0; i < spec->priv.expect_msgs.cnt; i++) { clone_msgs(&spec->priv.expect_msgs, &spec->unpriv.expect_msgs);
struct expect_msg *msg = &spec->priv.expect_msgs.patterns[i]; if (spec->unpriv.expect_xlated.cnt == 0)
clone_msgs(&spec->priv.expect_xlated, &spec->unpriv.expect_xlated);
err = push_msg(msg->substr, msg->regex_str,
&spec->unpriv.expect_msgs);
if (err)
goto cleanup;
}
}
if (spec->unpriv.expect_xlated.cnt == 0) {
for (i = 0; i < spec->priv.expect_xlated.cnt; i++) {
struct expect_msg *msg = &spec->priv.expect_xlated.patterns[i];
err = push_msg(msg->substr, msg->regex_str,
&spec->unpriv.expect_xlated);
if (err)
goto cleanup;
}
}
} }
spec->valid = true; spec->valid = true;
...@@ -542,7 +586,7 @@ static void validate_msgs(char *log_buf, struct expected_msgs *msgs, ...@@ -542,7 +586,7 @@ static void validate_msgs(char *log_buf, struct expected_msgs *msgs,
struct expect_msg *msg = &msgs->patterns[i]; struct expect_msg *msg = &msgs->patterns[i];
const char *match = NULL; const char *match = NULL;
if (msg->substr) { if (!msg->is_regex) {
match = strstr(log, msg->substr); match = strstr(log, msg->substr);
if (match) if (match)
log = match + strlen(msg->substr); log = match + strlen(msg->substr);
...@@ -562,8 +606,8 @@ static void validate_msgs(char *log_buf, struct expected_msgs *msgs, ...@@ -562,8 +606,8 @@ static void validate_msgs(char *log_buf, struct expected_msgs *msgs,
msg = &msgs->patterns[j]; msg = &msgs->patterns[j];
fprintf(stderr, "%s %s: '%s'\n", fprintf(stderr, "%s %s: '%s'\n",
j < i ? "MATCHED " : "EXPECTED", j < i ? "MATCHED " : "EXPECTED",
msg->substr ? "SUBSTR" : " REGEX", msg->is_regex ? " REGEX" : "SUBSTR",
msg->substr ?: msg->regex_str); msg->substr);
} }
return; return;
} }
......
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