786 lines
21 KiB
C
786 lines
21 KiB
C
// SPDX-FileCopyrightText: 2020 thestr4ng3r <info@florianmaerkl.de>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include "rz_test.h"
|
|
#include <rz_util/rz_str.h>
|
|
#include <rz_util/rz_regex.h>
|
|
#include <rz_cons.h>
|
|
|
|
#if __WINDOWS__
|
|
static ut8 *crlf2lf(ut8 *str) {
|
|
char *src = (char *)str;
|
|
char *dest = src;
|
|
while (*src) {
|
|
*dest = *src;
|
|
if (src[0] == '\r' && src[1] == '\n') {
|
|
// dest does not move
|
|
} else {
|
|
dest++;
|
|
}
|
|
src++;
|
|
}
|
|
*dest = '\0';
|
|
return str;
|
|
}
|
|
#else
|
|
#define crlf2lf(x) (x)
|
|
#endif
|
|
|
|
static RzSubprocessOutput *subprocess_runner(const char *file, const char *args[], size_t args_size,
|
|
const char *envvars[], const char *envvals[], size_t env_size, ut64 timeout_ms, void *user) {
|
|
RzSubprocess *proc = rz_subprocess_start(file, args, args_size, envvars, envvals, env_size);
|
|
if (!proc) {
|
|
return NULL;
|
|
}
|
|
RzSubprocessWaitReason r = rz_subprocess_wait(proc, timeout_ms);
|
|
if (r == RZ_SUBPROCESS_TIMEDOUT) {
|
|
rz_subprocess_kill(proc);
|
|
}
|
|
RzSubprocessOutput *out = rz_subprocess_drain(proc);
|
|
if (out) {
|
|
out->timeout = r == RZ_SUBPROCESS_TIMEDOUT;
|
|
out->out = crlf2lf(out->out);
|
|
out->err = crlf2lf(out->err);
|
|
}
|
|
rz_subprocess_free(proc);
|
|
return out;
|
|
}
|
|
|
|
#if __WINDOWS__
|
|
static char *convert_win_cmds(const char *cmds) {
|
|
if (RZ_STR_ISEMPTY(cmds)) {
|
|
return NULL;
|
|
}
|
|
char *r = malloc(strlen(cmds) + 1);
|
|
if (!r) {
|
|
return NULL;
|
|
}
|
|
char *p = r;
|
|
while (*cmds) {
|
|
if (*cmds == '!' || (*cmds == '\"' && cmds[1] == '!')) {
|
|
// Adjust shell syntax for Windows,
|
|
// only for lines starting with ! or "!
|
|
char c;
|
|
for (; c = *cmds, c; cmds++) {
|
|
if (c == '\\') {
|
|
// replace \$ by $
|
|
c = *++cmds;
|
|
if (c == '$') {
|
|
*p++ = '$';
|
|
} else {
|
|
*p++ = '\\';
|
|
*p++ = c;
|
|
}
|
|
} else if (c == '$') {
|
|
// replace ${VARNAME} by %VARNAME%
|
|
c = *++cmds;
|
|
if (c == '{') {
|
|
*p++ = '%';
|
|
cmds++;
|
|
for (; c = *cmds, c && c != '}'; cmds++) {
|
|
*p++ = c;
|
|
}
|
|
if (c) { // must check c to prevent overflow
|
|
*p++ = '%';
|
|
}
|
|
} else {
|
|
*p++ = '$';
|
|
*p++ = c;
|
|
}
|
|
} else {
|
|
*p++ = c;
|
|
if (c == '\n') {
|
|
cmds++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Nothing to do, just copy the line
|
|
char *lend = strchr(cmds, '\n');
|
|
size_t llen;
|
|
if (lend) {
|
|
llen = lend - cmds + 1;
|
|
} else {
|
|
llen = strlen(cmds);
|
|
}
|
|
memcpy(p, cmds, llen);
|
|
cmds += llen;
|
|
p += llen;
|
|
}
|
|
*p = '\0';
|
|
return rz_str_replace(r, "/dev/null", "nul", true);
|
|
}
|
|
#endif
|
|
|
|
typedef struct run_rz_test_s {
|
|
RzTestRunConfig *config; ///< Global configuration
|
|
ut64 timeout_ms; ///< Test timeout in millisec
|
|
const char *bin_path; ///< Path of the bin directory (can be null)
|
|
const char *tool; ///< When set executes a different tool rather than the default exe
|
|
const char *cmds; ///< Rizin command line passed as value of `-c` (-q will be added unless TOOL= is defined)
|
|
RzList /*<char *>*/ *envs; ///< Additional environment variables
|
|
RzList /*<char *>*/ *files; ///< Additional files or tool inputs
|
|
RzList /*<char *>*/ *extra_args; ///< Additional arguments
|
|
bool load_plugins; ///< When true, allows to load external plugins (RZ_NOPLUGINS=0)
|
|
bool color; ///< When true sets RZ_COLOR=1, otherwise RZ_COLOR=0 (default)
|
|
bool utf8; ///< When true sets RZ_UTF8=1, otherwise RZ_UTF8=0 (default)
|
|
RzTestCmdRunner runner; ///< Function to call to execute the test
|
|
void *user; ///< Additional user data passed to `runner`.
|
|
} RunRzTest;
|
|
|
|
static inline bool run_rz_test_is_custom(const RunRzTest *rrt) {
|
|
return RZ_STR_ISNOTEMPTY(rrt->tool);
|
|
}
|
|
|
|
static void run_rz_test_add_args_from_list(RzPVector /*<const char *>*/ *args, RzList /*<char *>*/ *list) {
|
|
RzListIter *it = NULL;
|
|
char *arg = NULL;
|
|
rz_list_foreach (list, it, arg) {
|
|
if (RZ_STR_ISEMPTY(arg)) {
|
|
continue;
|
|
}
|
|
rz_pvector_push(args, arg);
|
|
}
|
|
}
|
|
|
|
static void run_rz_test_init_args(const RunRzTest *rrt, const char *rizin_cmd, RzPVector /*<const char *>*/ *args) {
|
|
rz_pvector_init(args, NULL);
|
|
|
|
if (run_rz_test_is_custom(rrt)) {
|
|
// the test runs with custom args and may exec a different tool.
|
|
run_rz_test_add_args_from_list(args, rrt->extra_args);
|
|
if (rizin_cmd) {
|
|
rz_pvector_push(args, "-c");
|
|
rz_pvector_push(args, (void *)rizin_cmd);
|
|
}
|
|
run_rz_test_add_args_from_list(args, rrt->files);
|
|
return;
|
|
}
|
|
|
|
// the test runs in a normal rizin test environment.
|
|
rz_pvector_push(args, "-escr.utf8=0");
|
|
rz_pvector_push(args, "-escr.color=0");
|
|
rz_pvector_push(args, "-escr.interactive=0");
|
|
rz_pvector_push(args, "-eflirt.sigdb.load.system=false");
|
|
rz_pvector_push(args, "-esearch.show_progress=false");
|
|
rz_pvector_push(args, "-eflirt.sigdb.load.home=false");
|
|
rz_pvector_push(args, "-N");
|
|
run_rz_test_add_args_from_list(args, rrt->extra_args);
|
|
rz_pvector_push(args, "-qc");
|
|
rz_pvector_push(args, (void *)rizin_cmd);
|
|
run_rz_test_add_args_from_list(args, rrt->files);
|
|
}
|
|
|
|
static void run_rz_test_init_envs(const RunRzTest *rrt, const char ***envvars_o, const char ***envvals_o, size_t *env_size_o) {
|
|
RzListIter *it = NULL;
|
|
char *env = NULL;
|
|
size_t env_size = 0;
|
|
const size_t reserve = 4 + rz_list_length(rrt->envs);
|
|
const char **envvars = RZ_NEWS0(const char *, reserve);
|
|
const char **envvals = RZ_NEWS0(const char *, reserve);
|
|
|
|
#define RUN_RZ_TEST_ENV_SET(k, v) \
|
|
do { \
|
|
envvars[env_size] = k; \
|
|
envvals[env_size] = v; \
|
|
env_size++; \
|
|
} while (0)
|
|
|
|
#if __WINDOWS__
|
|
RUN_RZ_TEST_ENV_SET("ANSICON", "1");
|
|
#endif
|
|
RUN_RZ_TEST_ENV_SET("RZ_COLOR", rrt->color ? "1" : "0");
|
|
RUN_RZ_TEST_ENV_SET("RZ_UTF8", rrt->utf8 ? "1" : "0");
|
|
if (!rrt->load_plugins) {
|
|
RUN_RZ_TEST_ENV_SET("RZ_NOPLUGINS", "1");
|
|
}
|
|
|
|
rz_list_foreach (rrt->envs, it, env) {
|
|
if (RZ_STR_ISEMPTY(env)) {
|
|
continue;
|
|
}
|
|
|
|
const char *key = env;
|
|
char *value = strchr(env, '=');
|
|
if (value) {
|
|
*value = 0;
|
|
RUN_RZ_TEST_ENV_SET(key, value + 1);
|
|
} else {
|
|
RUN_RZ_TEST_ENV_SET(key, "");
|
|
}
|
|
}
|
|
#undef RUN_RZ_TEST_ENV_SET
|
|
|
|
*envvars_o = envvars;
|
|
*envvals_o = envvals;
|
|
*env_size_o = env_size;
|
|
}
|
|
|
|
RZ_API RZ_OWN char *rz_test_find_executable(RZ_NULLABLE const char *exec, RZ_NULLABLE const char *bin_path) {
|
|
if (RZ_STR_ISEMPTY(exec)) {
|
|
return NULL;
|
|
} else if (RZ_STR_ISEMPTY(bin_path)) {
|
|
return rz_file_path(exec);
|
|
}
|
|
|
|
#if __WINDOWS__
|
|
char *exe_path = rz_str_newf(RZ_JOIN_2_PATHS("%s", "%s.exe"), bin_path, exec);
|
|
#else
|
|
char *exe_path = rz_str_newf(RZ_JOIN_2_PATHS("%s", "%s"), bin_path, exec);
|
|
#endif
|
|
|
|
if (rz_file_exists(exe_path)) {
|
|
return exe_path;
|
|
}
|
|
|
|
free(exe_path);
|
|
return rz_str_dup(exec);
|
|
}
|
|
|
|
static char *run_rz_test_find_executable(const RunRzTest *rrt, const char *default_exe) {
|
|
const char *exec = default_exe;
|
|
if (RZ_STR_ISNOTEMPTY(rrt->tool)) {
|
|
exec = rrt->tool;
|
|
}
|
|
|
|
return rz_test_find_executable(exec, rrt->bin_path);
|
|
}
|
|
|
|
static RzSubprocessOutput *run_rz_test(const RunRzTest *rrt, const char *default_exe) {
|
|
const char **envvars = NULL;
|
|
const char **envvals = NULL;
|
|
size_t env_size = 0;
|
|
RzPVector args;
|
|
#if __WINDOWS__
|
|
char *wcmds = convert_win_cmds(rrt->cmds);
|
|
run_rz_test_init_args(rrt, wcmds, &args);
|
|
#else
|
|
run_rz_test_init_args(rrt, rrt->cmds, &args);
|
|
#endif
|
|
|
|
run_rz_test_init_envs(rrt, &envvars, &envvals, &env_size);
|
|
char *executable = run_rz_test_find_executable(rrt, default_exe);
|
|
|
|
RzSubprocessOutput *out = rrt->runner(executable, args.v.a, rz_pvector_len(&args), envvars, envvals, env_size, rrt->timeout_ms, rrt->user);
|
|
rz_pvector_clear(&args);
|
|
|
|
free(envvals);
|
|
free(envvars);
|
|
|
|
free(executable);
|
|
#if __WINDOWS__
|
|
free(wcmds);
|
|
#endif
|
|
return out;
|
|
}
|
|
|
|
RZ_API RzSubprocessOutput *rz_test_run_cmd_test(RzTestRunConfig *config, RzCmdTest *test, RzTestCmdRunner runner, void *user) {
|
|
RzList *extra_args = test->args.value ? rz_str_split_duplist(test->args.value, " ", true) : NULL;
|
|
RzList *files = test->file.value ? rz_str_split_duplist(test->file.value, "\n", true) : NULL;
|
|
RzList *envs = test->envs.value ? rz_str_split_duplist(test->envs.value, "\n", true) : NULL;
|
|
RzListIter *it;
|
|
RzListIter *tmpit;
|
|
char *token;
|
|
rz_list_foreach_safe (extra_args, it, tmpit, token) {
|
|
if (!*token) {
|
|
rz_list_delete(extra_args, it);
|
|
}
|
|
}
|
|
rz_list_foreach_safe (files, it, tmpit, token) {
|
|
if (!*token) {
|
|
rz_list_delete(files, it);
|
|
}
|
|
}
|
|
if (rz_list_empty(files) && RZ_STR_ISEMPTY(test->tool.value)) {
|
|
if (!files) {
|
|
files = rz_list_new();
|
|
} else {
|
|
files->free = NULL;
|
|
}
|
|
rz_list_push(files, "=");
|
|
}
|
|
ut64 timeout_ms = test->timeout.set ? test->timeout.value * 1000 : config->timeout_ms;
|
|
|
|
RunRzTest normal_rrt = {
|
|
.config = config,
|
|
.timeout_ms = timeout_ms,
|
|
.bin_path = config->bin_path,
|
|
.tool = test->tool.value,
|
|
.cmds = test->cmds.value,
|
|
.envs = envs,
|
|
.files = files,
|
|
.extra_args = extra_args,
|
|
.load_plugins = test->load_plugins,
|
|
.color = test->color.value,
|
|
.utf8 = test->utf8.value,
|
|
.runner = runner,
|
|
.user = user,
|
|
};
|
|
|
|
RzSubprocessOutput *out = run_rz_test(&normal_rrt, "rizin");
|
|
rz_list_free(extra_args);
|
|
rz_list_free(files);
|
|
rz_list_free(envs);
|
|
return out;
|
|
}
|
|
|
|
RZ_API RZ_OWN RzStrBuf *rz_test_regex_full_match_str(RZ_NONNULL const char *pattern, RZ_NONNULL const char *text) {
|
|
return rz_regex_full_match_str(pattern, text, RZ_REGEX_ZERO_TERMINATED, RZ_REGEX_EXTENDED, RZ_REGEX_DEFAULT,
|
|
"\n");
|
|
}
|
|
|
|
RZ_API bool rz_test_cmp_cmd_output(const char *output, const char *expect, const char *regexp) {
|
|
if (regexp) {
|
|
RzStrBuf *match_str = rz_test_regex_full_match_str(regexp, output);
|
|
bool equal = false;
|
|
size_t expect_len = strlen(expect);
|
|
if (expect_len && expect[expect_len - 1] == '\n') {
|
|
// Ignore newline in expect
|
|
expect_len--;
|
|
size_t match_str_len = rz_strbuf_length(match_str);
|
|
if (match_str_len && rz_strbuf_get(match_str)[match_str_len - 1] == '\n') {
|
|
// Ignore newline in match_str
|
|
match_str_len--;
|
|
}
|
|
if (expect_len != match_str_len) {
|
|
rz_strbuf_free(match_str);
|
|
return false;
|
|
}
|
|
equal = !rz_str_cmp(expect, rz_strbuf_get(match_str), expect_len);
|
|
} else {
|
|
equal = RZ_STR_EQ(expect, rz_strbuf_get(match_str));
|
|
}
|
|
rz_strbuf_free(match_str);
|
|
return equal;
|
|
}
|
|
return !strcmp(expect, output);
|
|
}
|
|
|
|
RZ_API bool rz_test_check_cmd_test(RzSubprocessOutput *out, RzCmdTest *test) {
|
|
if (!out || out->ret != test->exit_status.value || !out->out || !out->err || out->timeout) {
|
|
return false;
|
|
}
|
|
const char *expect_out = test->expect.value;
|
|
const char *regexp_out = test->regexp_out.value;
|
|
if (expect_out && !rz_test_cmp_cmd_output((char *)out->out, expect_out, regexp_out)) {
|
|
return false;
|
|
}
|
|
const char *expect_err = test->expect_err.value;
|
|
const char *regexp_err = test->regexp_err.value;
|
|
if (expect_err && !rz_test_cmp_cmd_output((char *)out->err, expect_err, regexp_err)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define JQ_CMD "jq"
|
|
|
|
RZ_API bool rz_test_check_jq_available(void) {
|
|
const char *args[] = { "." };
|
|
const char *invalid_json = "this is not json lol";
|
|
char *jq_path = rz_file_path(JQ_CMD);
|
|
RzSubprocess *proc = rz_subprocess_start(jq_path, args, 1, NULL, NULL, 0);
|
|
if (proc) {
|
|
rz_subprocess_stdin_write(proc, (const ut8 *)invalid_json, strlen(invalid_json));
|
|
rz_subprocess_wait(proc, UT64_MAX);
|
|
}
|
|
bool invalid_detected = proc && rz_subprocess_ret(proc) != 0;
|
|
rz_subprocess_free(proc);
|
|
|
|
const char *valid_json = "{\"this is\":\"valid json\",\"lol\":true}";
|
|
proc = rz_subprocess_start(jq_path, args, 1, NULL, NULL, 0);
|
|
if (proc) {
|
|
rz_subprocess_stdin_write(proc, (const ut8 *)valid_json, strlen(valid_json));
|
|
rz_subprocess_wait(proc, UT64_MAX);
|
|
}
|
|
bool valid_detected = proc && rz_subprocess_ret(proc) == 0;
|
|
rz_subprocess_free(proc);
|
|
free(jq_path);
|
|
return invalid_detected && valid_detected;
|
|
}
|
|
|
|
RZ_API bool rz_test_check_tool_available(RZ_NULLABLE const char *exec) {
|
|
if (RZ_STR_ISEMPTY(exec)) {
|
|
return false;
|
|
}
|
|
|
|
const char *args[] = { "-v" };
|
|
RzSubprocess *proc = rz_subprocess_start(exec, args, 1, NULL, NULL, 0);
|
|
if (!proc) {
|
|
return false;
|
|
}
|
|
rz_subprocess_wait(proc, UT64_MAX);
|
|
bool return_zero = rz_subprocess_ret(proc) == 0;
|
|
rz_subprocess_free(proc);
|
|
|
|
return return_zero;
|
|
}
|
|
|
|
RZ_API RzSubprocessOutput *rz_test_run_json_test(RzTestRunConfig *config, RzJsonTest *test, RzTestCmdRunner runner, void *user) {
|
|
RzList *files = rz_list_new();
|
|
rz_list_push(files, (void *)config->json_test_file);
|
|
|
|
RunRzTest json_rrt = {
|
|
.config = config,
|
|
.timeout_ms = config->timeout_ms,
|
|
.bin_path = config->bin_path,
|
|
.tool = NULL,
|
|
.cmds = test->cmd,
|
|
.files = files,
|
|
.extra_args = NULL,
|
|
.load_plugins = test->load_plugins,
|
|
.color = false,
|
|
.utf8 = false,
|
|
.runner = runner,
|
|
.user = user,
|
|
};
|
|
|
|
RzSubprocessOutput *ret = run_rz_test(&json_rrt, "rizin");
|
|
rz_list_free(files);
|
|
return ret;
|
|
}
|
|
|
|
RZ_API bool rz_test_check_json_test(RzSubprocessOutput *out, RzJsonTest *test) {
|
|
if (!out || out->ret != 0 || !out->out || !out->err || out->timeout) {
|
|
return false;
|
|
}
|
|
const char *args[] = { "." };
|
|
char *jq_path = rz_file_path(JQ_CMD);
|
|
RzSubprocess *proc = rz_subprocess_start(jq_path, args, 1, NULL, NULL, 0);
|
|
rz_subprocess_stdin_write(proc, (const ut8 *)out->out, strlen((char *)out->out));
|
|
rz_subprocess_wait(proc, UT64_MAX);
|
|
bool ret = rz_subprocess_ret(proc) == 0;
|
|
rz_subprocess_free(proc);
|
|
free(jq_path);
|
|
return ret;
|
|
}
|
|
|
|
RZ_API RzAsmTestOutput *rz_test_run_asm_test(RzTestRunConfig *config, RzAsmTest *test) {
|
|
RzAsmTestOutput *out = RZ_NEW0(RzAsmTestOutput);
|
|
if (!out) {
|
|
return NULL;
|
|
}
|
|
out->as_ret = out->disas_ret = out->il_ret = INT_MAX;
|
|
char *rz_asm_exe = rz_file_path("rz-asm");
|
|
|
|
RzPVector args;
|
|
rz_pvector_init(&args, NULL);
|
|
|
|
if (test->arch) {
|
|
rz_pvector_push(&args, "-a");
|
|
rz_pvector_push(&args, (void *)test->arch);
|
|
}
|
|
|
|
if (test->cpu) {
|
|
rz_pvector_push(&args, "-c");
|
|
rz_pvector_push(&args, (void *)test->cpu);
|
|
}
|
|
|
|
char bits[0x20];
|
|
if (test->bits) {
|
|
snprintf(bits, sizeof(bits), "%d", test->bits);
|
|
rz_pvector_push(&args, "-b");
|
|
rz_pvector_push(&args, bits);
|
|
}
|
|
|
|
if (test->mode & RZ_ASM_TEST_MODE_BIG_ENDIAN) {
|
|
rz_pvector_push(&args, "-e");
|
|
}
|
|
|
|
char offset[0x20];
|
|
if (test->offset) {
|
|
rz_snprintf(offset, sizeof(offset), "0x%" PFMT64x, test->offset);
|
|
rz_pvector_push(&args, "-o");
|
|
rz_pvector_push(&args, offset);
|
|
}
|
|
|
|
if (test->mode & RZ_ASM_TEST_MODE_ASSEMBLE) {
|
|
rz_pvector_push(&args, test->disasm);
|
|
RzSubprocess *proc = rz_subprocess_start(rz_asm_exe, args.v.a, rz_pvector_len(&args), NULL, NULL, 0);
|
|
if (rz_subprocess_wait(proc, config->timeout_ms) == RZ_SUBPROCESS_TIMEDOUT) {
|
|
rz_subprocess_kill(proc);
|
|
out->as_timeout = true;
|
|
goto rip;
|
|
}
|
|
char *as_err = (char *)crlf2lf(rz_subprocess_err(proc, NULL));
|
|
rz_str_trim(as_err);
|
|
out->as_err = as_err;
|
|
out->as_ret = rz_subprocess_ret(proc);
|
|
if (out->as_ret != 0) {
|
|
goto rip;
|
|
}
|
|
char *hex = (char *)crlf2lf(rz_subprocess_out(proc, NULL));
|
|
size_t hexlen = strlen(hex);
|
|
if (!hexlen) {
|
|
goto rip;
|
|
}
|
|
ut8 *bytes = malloc(hexlen);
|
|
int byteslen = rz_hex_str2bin(hex, bytes);
|
|
free(hex);
|
|
if (byteslen <= 0) {
|
|
free(bytes);
|
|
goto rip;
|
|
}
|
|
out->bytes = bytes;
|
|
out->bytes_size = (size_t)byteslen;
|
|
rip:
|
|
rz_pvector_pop(&args);
|
|
rz_subprocess_free(proc);
|
|
}
|
|
if (test->mode & RZ_ASM_TEST_MODE_DISASSEMBLE) {
|
|
char *hex = rz_hex_bin2strdup(test->bytes, test->bytes_size);
|
|
if (!hex) {
|
|
goto beach;
|
|
}
|
|
rz_pvector_push(&args, "-d");
|
|
rz_pvector_push(&args, hex);
|
|
RzSubprocess *proc = rz_subprocess_start(rz_asm_exe, args.v.a, rz_pvector_len(&args), NULL, NULL, 0);
|
|
if (rz_subprocess_wait(proc, config->timeout_ms) == RZ_SUBPROCESS_TIMEDOUT) {
|
|
rz_subprocess_kill(proc);
|
|
out->disas_timeout = true;
|
|
goto ship;
|
|
}
|
|
char *disas_err = (char *)crlf2lf(rz_subprocess_err(proc, NULL));
|
|
rz_str_trim(disas_err);
|
|
out->disas_err = disas_err;
|
|
out->disas_ret = rz_subprocess_ret(proc);
|
|
if (out->disas_ret != 0) {
|
|
goto ship;
|
|
}
|
|
char *disasm = (char *)crlf2lf(rz_subprocess_out(proc, NULL));
|
|
rz_str_trim(disasm);
|
|
rz_str_replace_char(disasm, '\n', ';');
|
|
out->disasm = disasm;
|
|
ship:
|
|
free(hex);
|
|
rz_pvector_pop(&args);
|
|
rz_pvector_pop(&args);
|
|
rz_subprocess_free(proc);
|
|
}
|
|
if (test->il) {
|
|
char *hex = rz_hex_bin2strdup(test->bytes, test->bytes_size);
|
|
if (!hex) {
|
|
goto beach;
|
|
}
|
|
rz_pvector_push(&args, "-I");
|
|
rz_pvector_push(&args, hex);
|
|
RzSubprocess *proc = rz_subprocess_start(rz_asm_exe, args.v.a, rz_pvector_len(&args), NULL, NULL, 0);
|
|
if (rz_subprocess_wait(proc, config->timeout_ms) == RZ_SUBPROCESS_TIMEDOUT) {
|
|
rz_subprocess_kill(proc);
|
|
out->il_timeout = true;
|
|
} else {
|
|
char *il = (char *)crlf2lf(rz_subprocess_out(proc, NULL));
|
|
rz_str_trim(il);
|
|
rz_str_replace_char(il, '\n', ';');
|
|
char *il_err = (char *)crlf2lf(rz_subprocess_err(proc, NULL));
|
|
rz_str_trim(il_err);
|
|
out->il = il;
|
|
out->il_err = il_err;
|
|
out->il_ret = rz_subprocess_ret(proc);
|
|
out->il_failed = out->il_ret != 0;
|
|
}
|
|
free(hex);
|
|
rz_pvector_pop(&args);
|
|
rz_pvector_pop(&args);
|
|
rz_subprocess_free(proc);
|
|
}
|
|
|
|
beach:
|
|
free(rz_asm_exe);
|
|
rz_pvector_clear(&args);
|
|
return out;
|
|
}
|
|
|
|
RZ_API bool rz_test_check_asm_test(RzAsmTestOutput *out, RzAsmTest *test) {
|
|
if (!out) {
|
|
return false;
|
|
}
|
|
if (test->mode & RZ_ASM_TEST_MODE_ASSEMBLE) {
|
|
if (!out->bytes || !test->bytes || out->bytes_size != test->bytes_size || out->as_timeout) {
|
|
return false;
|
|
}
|
|
if (memcmp(out->bytes, test->bytes, test->bytes_size) != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
if (test->mode & RZ_ASM_TEST_MODE_DISASSEMBLE) {
|
|
if (!out->disasm || !test->disasm || out->as_timeout) {
|
|
return false;
|
|
}
|
|
if (strcmp(out->disasm, test->disasm) != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
if (test->il) {
|
|
// expect some IL, no failure, no report and no timeout
|
|
if (!out->il || out->il_failed || RZ_STR_ISNOTEMPTY(out->il_err) || out->il_timeout) {
|
|
return false;
|
|
}
|
|
// IL must also be correct
|
|
if (strcmp(out->il, test->il) != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
RZ_API void rz_test_asm_test_output_free(RzAsmTestOutput *out) {
|
|
if (!out) {
|
|
return;
|
|
}
|
|
free(out->disasm);
|
|
free(out->bytes);
|
|
free(out->il);
|
|
free(out->as_err);
|
|
free(out->disas_err);
|
|
free(out->il_err);
|
|
free(out);
|
|
}
|
|
|
|
RZ_API RzSubprocessOutput *rz_test_run_fuzz_test(RzTestRunConfig *config, RzFuzzTest *test, RzTestCmdRunner runner, void *user) {
|
|
const char *cmd = "e analysis.nopskip=0; aaa";
|
|
RzList *files = rz_list_new();
|
|
rz_list_push(files, test->file);
|
|
#if ASAN
|
|
if (rz_str_endswith(test->file, "/swift_read")) {
|
|
cmd = "?F";
|
|
}
|
|
#endif
|
|
|
|
RunRzTest fuzzer_rrt = {
|
|
.config = config,
|
|
.timeout_ms = config->timeout_ms,
|
|
.bin_path = config->bin_path,
|
|
.tool = NULL,
|
|
.cmds = cmd,
|
|
.files = files,
|
|
.extra_args = NULL,
|
|
.load_plugins = false,
|
|
.color = false,
|
|
.utf8 = false,
|
|
.runner = runner,
|
|
.user = user,
|
|
};
|
|
|
|
RzSubprocessOutput *ret = run_rz_test(&fuzzer_rrt, "rizin");
|
|
rz_list_free(files);
|
|
return ret;
|
|
}
|
|
|
|
RZ_API bool rz_test_check_fuzz_test(RzSubprocessOutput *out) {
|
|
return out && out->ret == 0 && out->out && out->err && !out->timeout;
|
|
}
|
|
|
|
RZ_API char *rz_test_test_name(RzTest *test) {
|
|
switch (test->type) {
|
|
case RZ_TEST_TYPE_CMD:
|
|
if (test->cmd_test->name.value) {
|
|
return strdup(test->cmd_test->name.value);
|
|
}
|
|
return strdup("<unnamed>");
|
|
case RZ_TEST_TYPE_ASM:
|
|
return rz_str_newf("<asm> %s", test->asm_test->disasm ? test->asm_test->disasm : "");
|
|
case RZ_TEST_TYPE_JSON:
|
|
return rz_str_newf("<json> %s", test->json_test->cmd ? test->json_test->cmd : "");
|
|
case RZ_TEST_TYPE_FUZZ:
|
|
return rz_str_newf("<fuzz> %s", test->fuzz_test->file);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
RZ_API bool rz_test_broken(RzTest *test) {
|
|
switch (test->type) {
|
|
case RZ_TEST_TYPE_CMD:
|
|
return test->cmd_test->broken.value;
|
|
case RZ_TEST_TYPE_ASM:
|
|
return test->asm_test->mode & RZ_ASM_TEST_MODE_BROKEN ? true : false;
|
|
case RZ_TEST_TYPE_JSON:
|
|
return test->json_test->broken;
|
|
case RZ_TEST_TYPE_FUZZ:
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
RZ_API RzTestResultInfo *rz_test_run_test(RzTestRunConfig *config, RzTest *test) {
|
|
RzTestResultInfo *ret = RZ_NEW0(RzTestResultInfo);
|
|
if (!ret) {
|
|
return NULL;
|
|
}
|
|
ret->test = test;
|
|
bool success = false;
|
|
ut64 start_time = rz_time_now_mono();
|
|
switch (test->type) {
|
|
case RZ_TEST_TYPE_CMD: {
|
|
RzCmdTest *cmd_test = test->cmd_test;
|
|
RzSubprocessOutput *out = rz_test_run_cmd_test(config, cmd_test, subprocess_runner, NULL);
|
|
success = rz_test_check_cmd_test(out, cmd_test);
|
|
ret->proc_out = out;
|
|
ret->timeout = out && out->timeout;
|
|
ret->run_failed = !out;
|
|
break;
|
|
}
|
|
case RZ_TEST_TYPE_ASM: {
|
|
RzAsmTest *asm_test = test->asm_test;
|
|
RzAsmTestOutput *out = rz_test_run_asm_test(config, asm_test);
|
|
success = rz_test_check_asm_test(out, asm_test);
|
|
ret->asm_out = out;
|
|
if (out) {
|
|
ret->timeout = out->as_timeout || out->disas_timeout || out->il_timeout;
|
|
}
|
|
ret->run_failed = !out;
|
|
break;
|
|
}
|
|
case RZ_TEST_TYPE_JSON: {
|
|
RzJsonTest *json_test = test->json_test;
|
|
RzSubprocessOutput *out = rz_test_run_json_test(config, json_test, subprocess_runner, NULL);
|
|
success = rz_test_check_json_test(out, json_test);
|
|
ret->proc_out = out;
|
|
if (out) {
|
|
ret->timeout = out->timeout;
|
|
}
|
|
ret->run_failed = !out;
|
|
break;
|
|
}
|
|
case RZ_TEST_TYPE_FUZZ: {
|
|
RzFuzzTest *fuzz_test = test->fuzz_test;
|
|
RzSubprocessOutput *out = rz_test_run_fuzz_test(config, fuzz_test, subprocess_runner, NULL);
|
|
success = rz_test_check_fuzz_test(out);
|
|
ret->proc_out = out;
|
|
if (out) {
|
|
ret->timeout = out->timeout;
|
|
}
|
|
ret->run_failed = !out;
|
|
}
|
|
}
|
|
ret->time_elapsed = rz_time_now_mono() - start_time;
|
|
bool broken = rz_test_broken(test);
|
|
if (!success) {
|
|
ret->result = broken ? RZ_TEST_RESULT_BROKEN : RZ_TEST_RESULT_FAILED;
|
|
} else {
|
|
ret->result = broken ? RZ_TEST_RESULT_FIXED : RZ_TEST_RESULT_OK;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
RZ_API void rz_test_test_result_info_free(RzTestResultInfo *result) {
|
|
if (!result) {
|
|
return;
|
|
}
|
|
if (result->test) {
|
|
switch (result->test->type) {
|
|
case RZ_TEST_TYPE_CMD:
|
|
case RZ_TEST_TYPE_JSON:
|
|
case RZ_TEST_TYPE_FUZZ:
|
|
rz_subprocess_output_free(result->proc_out);
|
|
break;
|
|
case RZ_TEST_TYPE_ASM:
|
|
rz_test_asm_test_output_free(result->asm_out);
|
|
break;
|
|
}
|
|
}
|
|
free(result);
|
|
}
|