Serialize seek history (#3838)

* Save/load seek history ##projects

* Add unit test for seek history serialization

* Bump project version to 15 for seek history serialization
This commit is contained in:
Quentin Minster 2023-09-10 13:30:30 +00:00 committed by GitHub
parent 9872eacdb5
commit 700c7d898b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 538 additions and 3 deletions

View file

@ -533,7 +533,7 @@ RZ_API bool rz_project_migrate_v12_v13(RzProject *prj, RzSerializeResultInfo *re
// --
// Migration 13 -> 14
//
// Changes from <commit hash not yet known>:
// 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 <commit hash not yet known>:
// 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

View file

@ -12,6 +12,7 @@
* /flags => see flag.c
* /analysis => see analysis.c
* /file => see below
* /seek => see serialize_core_seek.c
* offset=<offset>
* blocksize=<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 /*<RzCoreSeekItem>*/ *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;
}

View file

@ -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
*/

View file

@ -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

View file

@ -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);

View file

@ -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) {

View file

@ -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

View file

@ -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;
}

View file

@ -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}

View file

@ -87,6 +87,7 @@ if get_option('enable_tests')
'serialize_config',
'serialize_debug',
'serialize_flag',
'serialize_seek',
'serialize_spaces',
'serialize_types',
'skiplist',

View file

@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: 2023 Quentin Minster <quentin@minster.io>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_core.h>
#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)

View file

@ -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);