diff --git a/librz/core/project_migrate.c b/librz/core/project_migrate.c index 3a61bf94c11..579a2ae0588 100644 --- a/librz/core/project_migrate.c +++ b/librz/core/project_migrate.c @@ -533,7 +533,7 @@ RZ_API bool rz_project_migrate_v12_v13(RzProject *prj, RzSerializeResultInfo *re // -- // Migration 13 -> 14 // -// Changes from : +// Changes from 8e29b959b86a35bbbfed599989f077dba6e0ebd5: // Removed {stack,reg} from "/core/analysis/functions/vars" // and converted into storage object { ..., storage: { type: ... } } @@ -592,6 +592,21 @@ RZ_API bool rz_project_migrate_v13_v14(RzProject *prj, RzSerializeResultInfo *re return true; } +// -- +// Migration 14 -> 15 +// +// Changes from : +// Added serialization functionality for seek history +// New namespace: /core/seek + +RZ_API bool rz_project_migrate_v14_v15(RzProject *prj, RzSerializeResultInfo *res) { + Sdb *core_db; + RZ_SERIALIZE_SUB(prj, core_db, res, "core", return false;); + sdb_ns(core_db, "seek", true); + + return true; +} + static bool (*const migrations[])(RzProject *prj, RzSerializeResultInfo *res) = { rz_project_migrate_v1_v2, rz_project_migrate_v2_v3, @@ -606,6 +621,7 @@ static bool (*const migrations[])(RzProject *prj, RzSerializeResultInfo *res) = rz_project_migrate_v11_v12, rz_project_migrate_v12_v13, rz_project_migrate_v13_v14, + rz_project_migrate_v14_v15, }; /// Migrate the given project to the current version in-place diff --git a/librz/core/serialize_core.c b/librz/core/serialize_core.c index 754fab99e20..cc0e5293bcd 100644 --- a/librz/core/serialize_core.c +++ b/librz/core/serialize_core.c @@ -12,6 +12,7 @@ * /flags => see flag.c * /analysis => see analysis.c * /file => see below + * /seek => see serialize_core_seek.c * offset= * blocksize= */ @@ -26,6 +27,7 @@ RZ_API void rz_serialize_core_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, rz_serialize_flag_save(sdb_ns(db, "flags", true), core->flags); rz_serialize_analysis_save(sdb_ns(db, "analysis", true), core->analysis); rz_serialize_debug_save(sdb_ns(db, "debug", true), core->dbg); + rz_serialize_core_seek_save(sdb_ns(db, "seek", true), core); char buf[0x20]; if (snprintf(buf, sizeof(buf), "0x%" PFMT64x, core->offset) < 0) { @@ -79,6 +81,7 @@ RZ_API bool rz_serialize_core_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, SUB("flags", rz_serialize_flag_load(subdb, core->flags, res)); SUB("analysis", rz_serialize_analysis_load(subdb, core->analysis, res)); SUB("debug", rz_serialize_debug_load(subdb, core->dbg, res)); + SUB("seek", rz_serialize_core_seek_load(subdb, core, res)); const char *str = sdb_const_get(db, "offset", 0); if (!str || !*str) { @@ -256,3 +259,220 @@ static bool file_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, RZ_NULLABLE c RZ_SERIALIZE_ERR(res, "failed to re-locate file referenced by project"); return false; } + +/** + * \brief Serialize seek history state and save to a sdb + * + * \param db sdb to save the state + * \param core RzCore instance to save from + */ +RZ_API void rz_serialize_core_seek_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core) { + rz_return_if_fail(db && core); + + RzList *list = rz_core_seek_list(core); + if (!list) { + return; + } + + RzListIter *iter; + RzCoreSeekItem *undo; + rz_list_foreach (list, iter, undo) { + PJ *j = pj_new(); + if (!j) { + goto err; + } + pj_o(j); + pj_kn(j, "offset", undo->offset); + pj_kn(j, "cursor", undo->cursor); + pj_kb(j, "current", undo->is_current); + pj_end(j); + + char key[12]; + sdb_set(db, rz_strf(key, "%" PFMT32d, undo->idx), pj_string(j), 0); + pj_free(j); + } + +err: + rz_list_free(list); +} + +enum { + SEEK_FIELD_OFFSET, + SEEK_FIELD_CURSOR, + SEEK_FIELD_CURRENT +}; + +/** + * \brief Create and initialize a JSON parser for seek history items + * \return a RzKeyParser* for seek history items + **/ +static RzKeyParser *seek_parser_new(void) { + RzKeyParser *parser = rz_key_parser_new(); + if (!parser) { + return NULL; + } + + rz_key_parser_add(parser, "offset", SEEK_FIELD_OFFSET); + rz_key_parser_add(parser, "cursor", SEEK_FIELD_CURSOR); + rz_key_parser_add(parser, "current", SEEK_FIELD_CURRENT); + + return parser; +} + +typedef struct { + RzCore *core; + RzKeyParser *parser; + char *current_key; + RzVector /**/ *vec; +} SeekLoadCtx; + +/** + * \brief Load a single seek history item + * \param ctx context for loading the item + * \param k sdb item key + * \param v sdb item value (expected to be JSON) + **/ +static bool seek_load_item(SeekLoadCtx *ctx, const char *k, const char *v) { + bool ret = false; + char *json_str = strdup(v); + if (!json_str) { + return true; + } + RzJson *json = rz_json_parse(json_str); + if (!json || json->type != RZ_JSON_OBJECT) { + goto out_free_str; + } + RzCoreSeekItem seek_item = { 0 }; + + RZ_KEY_PARSER_JSON(ctx->parser, json, child, { + case SEEK_FIELD_OFFSET: + if (child->type != RZ_JSON_INTEGER) { + break; + } + seek_item.offset = child->num.u_value; + break; + case SEEK_FIELD_CURSOR: + if (child->type != RZ_JSON_INTEGER) { + break; + } + seek_item.cursor = child->num.s_value; + break; + case SEEK_FIELD_CURRENT: + if (child->type != RZ_JSON_BOOLEAN) { + break; + } + seek_item.is_current = child->num.u_value; + break; + }) + + if (seek_item.is_current && !ctx->current_key) { + // The offset is serialized by the core, so ignore the information from the seek history + // But the cursor position isn't serialized otherwise + ctx->core->print->cur = seek_item.cursor; + // Switch to the vector of redos + ctx->vec = &ctx->core->seek_history.redos; + // Remember we've found the current seek + ctx->current_key = strdup(k); + } else { + if (seek_item.is_current) { + // Warn about this additional "current" seek + RZ_LOG_WARN("core: Seek history item \"%s\" marked as current, but current already found at \"%s\"!", k, ctx->current_key); + } + rz_vector_push(ctx->vec, &seek_item); + } + ret = true; + + rz_json_free(json); +out_free_str: + free(json_str); + return ret; +} + +static int __cmp_num_asc(const void *a, const void *b) { + const SdbKv *ka = a, *kb = b; + // Parse as signed ints but don't bother witb error detection, it'll sort bad and that's it + long ia = strtol(sdbkv_key(ka), NULL, 10); + long ib = strtol(sdbkv_key(kb), NULL, 10); + return ia > ib; +} + +/** + * \brief Deserialize seek history state from an sdb + * + * \param db sdb to load state from + * \param core RzCore instance to load into + * \param res RzSerializeResultInfo to store info/errors/warnings + * \return true if successful, false otherwise + */ +RZ_API bool rz_serialize_core_seek_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, RZ_NULLABLE RzSerializeResultInfo *res) { + rz_return_val_if_fail(db && core, false); + + bool ret = true; + RzKeyParser *seek_parser = seek_parser_new(); + if (!seek_parser) { + return false; + } + + // Sort by (numeric) key + SdbList *db_list = sdb_foreach_list(db, false); + if (!db_list) { + ret = false; + goto out_free_parser; + } + ls_sort(db_list, __cmp_num_asc); + + // Clear the current history + rz_core_seek_reset(core); + core->seek_history.saved_set = false; + + SdbKv *kv; + SdbListIter *it; + SeekLoadCtx ctx = { + .core = core, + .parser = seek_parser, + .current_key = NULL, + .vec = &core->seek_history.undos, + }; + bool parsed = true; + ls_foreach (db_list, it, kv) { + parsed &= seek_load_item(&ctx, sdbkv_key(kv), sdbkv_value(kv)); + } + ret &= parsed; + if (!parsed) { + RZ_SERIALIZE_ERR(res, "failed to parse seek history offset from json"); + } + + // Reverse the redo vector, which has been deserialized from oldest to youngest entry + // but should be ordered from youngest to oldest + // (so the entry closest to the current seek can be pushed/popped) + bool reversed = true; + size_t rlen = rz_vector_len(&core->seek_history.redos); + for (size_t i = 0; i < rlen / 2; i++) { + // Swap with the mirror item from the end of the vector + reversed &= rz_vector_swap(&core->seek_history.redos, i, rlen - 1 - i); + } + ret &= reversed; + if (!reversed) { + RZ_SERIALIZE_ERR(res, "failed to reorder seek history redo items"); + } + + // Increase cfg.seek.histsize as needed + size_t ulen = rz_vector_len(&core->seek_history.undos); + if (SZT_ADD_OVFCHK(ulen, rlen)) { + ret = false; + RZ_SERIALIZE_ERR(res, "failed to adjust cfg.seek.histsize"); + rz_goto_if_reached(out_free_list); + } + ut64 histsize = rz_config_get_i(core->config, "cfg.seek.histsize"); + if (histsize != 0 && histsize < ulen + rlen) { + RZ_LOG_WARN("core: Loaded project seek history exceeds cfg.seek.histsize, increasing that limit."); + rz_config_set_i(core->config, "cfg.seek.histsize", ulen + rlen); + } + +out_free_list: + free(ctx.current_key); + ls_free(db_list); +out_free_parser: + rz_key_parser_free(seek_parser); + return ret; +} diff --git a/librz/include/rz_core.h b/librz/include/rz_core.h index 11880fb54ec..fa6a5a90374 100644 --- a/librz/include/rz_core.h +++ b/librz/include/rz_core.h @@ -1279,6 +1279,10 @@ RZ_API void rz_serialize_core_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, RZ_API bool rz_serialize_core_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, bool load_bin_io, RZ_NULLABLE const char *prj_file, RZ_NULLABLE RzSerializeResultInfo *res); +RZ_API void rz_serialize_core_seek_save(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core); + +RZ_API bool rz_serialize_core_seek_load(RZ_NONNULL Sdb *db, RZ_NONNULL RzCore *core, RZ_NULLABLE RzSerializeResultInfo *res); + /** * \brief Load a project and print info and errors */ diff --git a/librz/include/rz_project.h b/librz/include/rz_project.h index 412834257bb..59ad94642cb 100644 --- a/librz/include/rz_project.h +++ b/librz/include/rz_project.h @@ -12,7 +12,7 @@ extern "C" { #endif -#define RZ_PROJECT_VERSION 14 +#define RZ_PROJECT_VERSION 15 typedef Sdb RzProject; @@ -57,6 +57,9 @@ RZ_API bool rz_project_migrate_v8_v9(RzProject *prj, RzSerializeResultInfo *res) RZ_API bool rz_project_migrate_v9_v10(RzProject *prj, RzSerializeResultInfo *res); RZ_API bool rz_project_migrate_v10_v11(RzProject *prj, RzSerializeResultInfo *res); RZ_API bool rz_project_migrate_v11_v12(RzProject *prj, RzSerializeResultInfo *res); +RZ_API bool rz_project_migrate_v12_v13(RzProject *prj, RzSerializeResultInfo *res); +RZ_API bool rz_project_migrate_v13_v14(RzProject *prj, RzSerializeResultInfo *res); +RZ_API bool rz_project_migrate_v14_v15(RzProject *prj, RzSerializeResultInfo *res); RZ_API bool rz_project_migrate(RzProject *prj, unsigned long version, RzSerializeResultInfo *res); #ifdef __cplusplus diff --git a/librz/include/rz_vector.h b/librz/include/rz_vector.h index 9d4c62e2e55..6e7384b91aa 100644 --- a/librz/include/rz_vector.h +++ b/librz/include/rz_vector.h @@ -142,6 +142,14 @@ RZ_API void *rz_vector_push(RzVector *vec, void *x); // like rz_vector_insert for the beginning of vec RZ_API void *rz_vector_push_front(RzVector *vec, void *x); +/** + * \brief Swap two elements of the vector + * \param index_a index of the first element to swap + * \param index_b index of the second element to swap + * \return true if the swap succeeded + **/ +RZ_API bool rz_vector_swap(RzVector *vec, size_t index_a, size_t index_b); + // make sure the capacity is at least capacity. RZ_API void *rz_vector_reserve(RzVector *vec, size_t capacity); diff --git a/librz/util/vector.c b/librz/util/vector.c index f5609866aaf..ea14a647138 100644 --- a/librz/util/vector.c +++ b/librz/util/vector.c @@ -226,6 +226,21 @@ RZ_API void *rz_vector_push_front(RzVector *vec, void *x) { return rz_vector_insert(vec, 0, x); } +RZ_API bool rz_vector_swap(RzVector *vec, size_t index_a, size_t index_b) { + rz_return_val_if_fail(vec && index_a < vec->len && index_b < vec->len, false); + ut8 *tmp = malloc(vec->elem_size); + if (!tmp) { + return false; + } + void *elem_a = rz_vector_index_ptr(vec, index_a); + void *elem_b = rz_vector_index_ptr(vec, index_b); + memcpy(tmp, elem_a, vec->elem_size); + memcpy(elem_a, elem_b, vec->elem_size); + memcpy(elem_b, tmp, vec->elem_size); + free(tmp); + return true; +} + RZ_API void *rz_vector_reserve(RzVector *vec, size_t capacity) { rz_return_val_if_fail(vec, NULL); if (vec->capacity < capacity) { diff --git a/test/db/cmd/project b/test/db/cmd/project index 27f865d5ae2..f2e21eec02c 100644 --- a/test/db/cmd/project +++ b/test/db/cmd/project @@ -372,6 +372,7 @@ Detailed project load info: project migrated from version 11 to 12. project migrated from version 12 to 13. project migrated from version 13 to 14. + project migrated from version 14 to 15. EOF RUN diff --git a/test/integration/test_project_migrate.c b/test/integration/test_project_migrate.c index ab21cffb0f3..8dcb41a252e 100644 --- a/test/integration/test_project_migrate.c +++ b/test/integration/test_project_migrate.c @@ -535,6 +535,23 @@ static bool test_migrate_v9_v10_v11_stack_vars_sp() { mu_end; } +static bool test_migrate_v14_v15() { + RzProject *prj = rz_project_load_file_raw("prj/v14-float_ex1_hightec.rzdb.gz"); + mu_assert_notnull(prj, "load raw project"); + RzSerializeResultInfo *res = rz_serialize_result_info_new(); + bool s = rz_project_migrate_v14_v15(prj, res); + mu_assert_true(s, "migrate success"); + + Sdb *core_db = sdb_ns(prj, "core", false); + mu_assert_notnull(core_db, "core ns"); + Sdb *seek_db = sdb_ns(core_db, "seek", false); + mu_assert_notnull(seek_db, "seek ns"); + + rz_serialize_result_info_free(res); + rz_project_free(prj); + mu_end; +} + /// Load project of given version from file into core and check the log for migration success messages #define BEGIN_LOAD_TEST(core, version, file) \ do { \ @@ -845,7 +862,6 @@ static bool test_load_v9_v10_stack_vars_sp(int version, const char *prj_file) { rz_core_free(core); mu_end; - mu_end; } static bool test_load_v12() { @@ -877,6 +893,34 @@ static bool test_load_v14() { mu_end; } +static bool test_load_v15_seek_history() { + RzCore *core = rz_core_new(); + + // enable the cursor so we can check the deserialized value + rz_print_set_cursor(core->print, true, 0, 0); + + BEGIN_LOAD_TEST(core, 15, "prj/v15-seek-history.rzdb"); + + mu_assert_eq(rz_vector_len(&core->seek_history.undos), 1, "bad number of undos"); + RzCoreSeekItem *item = rz_vector_index_ptr(&core->seek_history.undos, 0); + mu_assert_eq(item->offset, 0x5ae0, "bad undo offset"); + mu_assert_eq(item->cursor, 1, "bad undo cursor"); + + mu_assert_eq(rz_vector_len(&core->seek_history.redos), 2, "bad number of redos"); + item = rz_vector_index_ptr(&core->seek_history.redos, 1); + mu_assert_eq(item->offset, 0x5b00, "bad first redo offset"); + mu_assert_eq(item->cursor, 3, "bad first redo cursor"); + item = rz_vector_index_ptr(&core->seek_history.redos, 0); + mu_assert_eq(item->offset, 0x5b10, "bad second redo offset"); + mu_assert_eq(item->cursor, 4, "bad second redo cursor"); + + // core offset not restored from current seek history item, so not checked + mu_assert_eq(rz_print_get_cursor(core->print), 2, "bad current cursor"); + + rz_core_free(core); + mu_end; +} + int all_tests() { mu_run_test(test_migrate_v1_v2_noreturn); mu_run_test(test_migrate_v1_v2_noreturn_empty); @@ -894,6 +938,7 @@ int all_tests() { mu_run_test(test_migrate_v9_v10_v11_stack_vars_bp); mu_run_test(test_migrate_v9_v10_v11_stack_vars_sp); mu_run_test(test_migrate_v2_v12); + mu_run_test(test_migrate_v14_v15); mu_run_test(test_load_v1_noreturn); mu_run_test(test_load_v1_noreturn_empty); mu_run_test(test_load_v1_unknown_type); @@ -913,6 +958,7 @@ int all_tests() { mu_run_test(test_load_v9_v10_stack_vars_sp, 10, "prj/v10-sp-vars.rzdb"); mu_run_test(test_load_v12); mu_run_test(test_load_v14); + mu_run_test(test_load_v15_seek_history); return tests_passed != tests_run; } diff --git a/test/prj/v15-seek-history.rzdb b/test/prj/v15-seek-history.rzdb new file mode 100644 index 00000000000..71fbd8c21f7 --- /dev/null +++ b/test/prj/v15-seek-history.rzdb @@ -0,0 +1,86 @@ +/ +type=rizin rz-db project +version=15 + +/core +blocksize=0x100 +offset=0x5af0 + +/core/analysis + +/core/analysis/blocks + +/core/analysis/callables + +/core/analysis/cc + +/core/analysis/classes + +/core/analysis/classes/attrs + +/core/analysis/functions + +/core/analysis/hints + +/core/analysis/imports + +/core/analysis/meta + +/core/analysis/meta/spaces +name=CS +spacestack=["*"] + +/core/analysis/meta/spaces/spaces +bin=s + +/core/analysis/noreturn + +/core/analysis/types + +/core/analysis/vars + +/core/analysis/xrefs + +/core/config +asm.arch=x86 +asm.bits=64 +cfg.seek.histsize=63 +cfg.seek.silent=false +cfg.wseek=false +prj.compress=false +prj.file=./test/prj/v15-seek-history.rzdb + +/core/debug + +/core/debug/breakpoints + +/core/file +relative=../bins/elf/ls + +/core/flags +base=0 +realnames=0 + +/core/flags/flags + +/core/flags/spaces +name=fs +spacestack=["*"] + +/core/flags/spaces/spaces +classes=s +relocs=s +sections=s +segments=s +strings=s +symbols=s + +/core/flags/tags + +/core/flags/zones + +/core/seek +-1={"offset":23264,"cursor":1,"current":false} +0={"offset":23280,"cursor":2,"current":true} +1={"offset":23296,"cursor":3,"current":false} +2={"offset":23312,"cursor":4,"current":false} diff --git a/test/unit/meson.build b/test/unit/meson.build index a0d559d2b89..9fdadc09098 100644 --- a/test/unit/meson.build +++ b/test/unit/meson.build @@ -87,6 +87,7 @@ if get_option('enable_tests') 'serialize_config', 'serialize_debug', 'serialize_flag', + 'serialize_seek', 'serialize_spaces', 'serialize_types', 'skiplist', diff --git a/test/unit/test_serialize_seek.c b/test/unit/test_serialize_seek.c new file mode 100644 index 00000000000..fb12c85408d --- /dev/null +++ b/test/unit/test_serialize_seek.c @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2023 Quentin Minster +// SPDX-License-Identifier: LGPL-3.0-only + +#include +#include "minunit.h" +#include "test_sdb.h" + +Sdb *get_ref_sdb() { + Sdb *ref_sdb = sdb_new0(); + + sdb_set(ref_sdb, "-1", "{\"offset\":16,\"cursor\":1,\"current\":false}", 0); + sdb_set(ref_sdb, "0", "{\"offset\":32,\"cursor\":2,\"current\":true}", 0); + sdb_set(ref_sdb, "1", "{\"offset\":48,\"cursor\":3,\"current\":false}", 0); + sdb_set(ref_sdb, "2", "{\"offset\":64,\"cursor\":4,\"current\":false}", 0); + + return ref_sdb; +} + +bool test_seek_serialize_save() { + RzCore *core = rz_core_new(); + mu_assert_notnull(core, "core null"); + rz_core_file_open(core, "malloc://0x3000", RZ_PERM_R, 0); + + bool sought = rz_core_seek(core, 0x10, false); + rz_print_set_cursor(core->print, true, 0, 1); + mu_assert_true(sought, "failed to seek 0x10"); + sought = rz_core_seek_and_save(core, 0x20, false); + rz_print_set_cursor(core->print, true, 0, 2); + mu_assert_true(sought, "failed to seek 0x20"); + sought = rz_core_seek_and_save(core, 0x30, false); + rz_print_set_cursor(core->print, true, 0, 3); + mu_assert_true(sought, "failed to seek 0x30"); + sought = rz_core_seek_and_save(core, 0x40, false); + rz_print_set_cursor(core->print, true, 0, 4); + mu_assert_true(sought, "failed to seek 0x40"); + sought = rz_core_seek_undo(core); + mu_assert_true(sought, "failed first undo seek"); + sought = rz_core_seek_undo(core); + mu_assert_true(sought, "failed second undo seek"); + + Sdb *save_sdb = sdb_new0(); + mu_assert_notnull(save_sdb, "sdb null"); + rz_serialize_core_seek_save(save_sdb, core); + Sdb *ref = get_ref_sdb(); + mu_assert_notnull(ref, "ref sdb null"); + assert_sdb_eq(save_sdb, ref, "saved sdb not same"); + + rz_core_file_close(core->file); + rz_core_free(core); + sdb_free(save_sdb); + sdb_free(ref); + + mu_end; +} + +bool test_seek_serialize_load() { + RzCore *core = rz_core_new(); + mu_assert_notnull(core, "core null"); + rz_core_file_open(core, "malloc://0x3000", RZ_PERM_R, 0); + + // enable the cursor so we can check the deserialized value + rz_print_set_cursor(core->print, true, 0, 0); + + Sdb *ref = get_ref_sdb(); + Sdb *load_sdb = sdb_new0(); + rz_serialize_core_seek_load(ref, core, NULL); + + mu_assert_eq(rz_vector_len(&core->seek_history.undos), 1, "bad number of undos"); + RzCoreSeekItem *item = rz_vector_index_ptr(&core->seek_history.undos, 0); + mu_assert_eq(item->offset, 0x10, "bad undo offset"); + mu_assert_eq(item->cursor, 1, "bad undo cursor"); + + mu_assert_eq(rz_vector_len(&core->seek_history.redos), 2, "bad number of redos"); + item = rz_vector_index_ptr(&core->seek_history.redos, 1); + mu_assert_eq(item->offset, 0x30, "bad first redo offset"); + mu_assert_eq(item->cursor, 3, "bad first redo cursor"); + item = rz_vector_index_ptr(&core->seek_history.redos, 0); + mu_assert_eq(item->offset, 0x40, "bad second redo offset"); + mu_assert_eq(item->cursor, 4, "bad second redo cursor"); + + // core offset not restored from current seek history item, so not checked + mu_assert_eq(rz_print_get_cursor(core->print), 2, "bad current cursor"); + + rz_core_file_close(core->file); + rz_core_free(core); + sdb_free(load_sdb); + sdb_free(ref); + + mu_end; +} + +int all_tests() { + mu_run_test(test_seek_serialize_save); + mu_run_test(test_seek_serialize_load); + return tests_passed != tests_run; +} + +mu_main(all_tests) diff --git a/test/unit/test_vector.c b/test/unit/test_vector.c index edda8b29547..0d27ce5fed0 100644 --- a/test/unit/test_vector.c +++ b/test/unit/test_vector.c @@ -629,6 +629,42 @@ static bool test_vector_push_front(void) { mu_end; } +static bool test_vector_swap(void) { + RzVector v; + init_test_vector(&v, 3, 0, NULL, NULL); + + rz_vector_swap(&v, 0, 2); + mu_assert_eq(v.len, 3UL, "rz_vector_swap (valid indexes) => len == 3"); + ut32 e = *((ut32 *)rz_vector_index_ptr(&v, 0)); + mu_assert_eq(e, 2, "rz_vector_swap (valid indexes) => content"); + e = *((ut32 *)rz_vector_index_ptr(&v, 1)); + mu_assert_eq(e, 1, "rz_vector_swap (valid indexes) => old content"); + e = *((ut32 *)rz_vector_index_ptr(&v, 2)); + mu_assert_eq(e, 0, "rz_vector_swap (valid indexes) => content"); + + rz_vector_swap(&v, 2, 2); + mu_assert_eq(v.len, 3UL, "rz_vector_swap (same index) => len == 3"); + e = *((ut32 *)rz_vector_index_ptr(&v, 0)); + mu_assert_eq(e, 2, "rz_vector_swap (same index) => old content"); + e = *((ut32 *)rz_vector_index_ptr(&v, 1)); + mu_assert_eq(e, 1, "rz_vector_swap (same index) => old content"); + e = *((ut32 *)rz_vector_index_ptr(&v, 2)); + mu_assert_eq(e, 0, "rz_vector_swap (same index) => content"); + + rz_vector_swap(&v, 3, 2); + mu_assert_eq(v.len, 3UL, "rz_vector_swap (bad index) => len == 3"); + e = *((ut32 *)rz_vector_index_ptr(&v, 0)); + mu_assert_eq(e, 2, "rz_vector_swap (bad index) => old content"); + e = *((ut32 *)rz_vector_index_ptr(&v, 1)); + mu_assert_eq(e, 1, "rz_vector_swap (bad index) => old content"); + e = *((ut32 *)rz_vector_index_ptr(&v, 2)); + mu_assert_eq(e, 0, "rz_vector_swap (bad index) => old content"); + + rz_vector_clear(&v); + + mu_end; +} + static bool test_vector_reserve(void) { RzVector v; rz_vector_init(&v, 4, NULL, NULL); @@ -1343,6 +1379,7 @@ static int all_tests(void) { mu_run_test(test_vector_pop_front); mu_run_test(test_vector_push); mu_run_test(test_vector_push_front); + mu_run_test(test_vector_swap); mu_run_test(test_vector_reserve); mu_run_test(test_vector_shrink); mu_run_test(test_vector_flush);