Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libr/include/r_util/r_strpool.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ typedef struct {
ut32 *idxs; // indexes
ut32 *sidx; // sorted index -- not yet used
RBloom *bloom;
ut32 last; // position returned by the previous add, UT32_MAX if none
} RStrpool;

R_API RStrpool* r_strpool_new(void);
Expand Down
17 changes: 11 additions & 6 deletions libr/util/strpool.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ R_API RStrpool * R_NULLABLE r_strpool_new(void) {
p->idxs = calloc (sizeof (p->idxs[0]), p->isize);
if (p->str && p->idxs) {
p->str[0] = 0;
p->last = UT32_MAX;
p->bloom = r_bloom_new (1024, 2, NULL);
if (!p->bloom) {
free (p->str);
Expand Down Expand Up @@ -80,6 +81,7 @@ R_API void r_strpool_empty(RStrpool *p) {
p->isize = 128;
p->count = 0;
p->len = 0;
p->last = UT32_MAX;
free (p->str);
p->str = malloc (p->size);
free (p->idxs);
Expand All @@ -91,6 +93,7 @@ R_API void r_strpool_slice(RStrpool *p, int index) {
char *pos = r_strpool_get_nth (p, index);
if (pos) {
p->count = index; // or index -1 ?
p->last = UT32_MAX;
// TODO: shrink string allocation too?
}
}
Expand All @@ -103,6 +106,7 @@ R_API void r_strpool_slice_range(RStrpool *p, int idx_from, int idx_to) {
if (idx_to > p->count || idx_to < idx_from) {
idx_to = p->count;
}
p->last = UT32_MAX;
if (idx_from == 0) {
// Just truncate from idx_to onwards
p->count = idx_to;
Expand All @@ -118,14 +122,15 @@ R_API void r_strpool_slice_range(RStrpool *p, int idx_from, int idx_to) {

R_API int r_strpool_add(RStrpool *p, const char *s) {
R_RETURN_VAL_IF_FAIL (p && s, -1);
if (!r_bloom_check (p->bloom, s, strlen (s))) {
return r_strpool_append (p, s);
if (p->last != UT32_MAX && !strcmp (p->str + p->idxs[p->last], s)) {
return p->last;
}
const int pos = r_strpool_get (p, s);
if (pos >= 0) {
return pos;
int pos = r_bloom_check (p->bloom, s, strlen (s))? r_strpool_get (p, s): -1;
if (pos < 0) {
pos = r_strpool_append (p, s);
}
return r_strpool_append (p, s);
p->last = (pos < 0)? UT32_MAX: (ut32)pos;
return pos;
}

R_API int r_strpool_append(RStrpool *p, const char *s) {
Expand Down
1 change: 1 addition & 0 deletions test/unit/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ if get_option('enable_tests')
'stack',
'str',
'strbuf',
'strpool',
'scanf',
'printf',
'table',
Expand Down
204 changes: 204 additions & 0 deletions test/unit/test_strpool.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
#include <r_util.h>
#include "minunit.h"

static int add_nth(RStrpool *p, int n) {
char *s = r_str_newf ("/usr/src/lib/file-%d.c", n);
const int pos = r_strpool_add (p, s);
free (s);
return pos;
}

static int get_nth(RStrpool *p, int n) {
char *s = r_str_newf ("/usr/src/lib/file-%d.c", n);
const int pos = r_strpool_get (p, s);
free (s);
return pos;
}

bool test_r_strpool_add_dedups(void) {
RStrpool *p = r_strpool_new ();
mu_assert_eq (r_strpool_add (p, "alpha"), 0, "first string");
mu_assert_eq (r_strpool_add (p, "beta"), 1, "second string");
mu_assert_eq (r_strpool_add (p, "alpha"), 0, "repeated string returns its first position");
mu_assert_eq (r_strpool_get (p, "gamma"), -1, "missing string");
mu_assert_eq (p->count, 2, "no duplicate stored");
mu_assert_streq (r_strpool_get_nth (p, 1), "beta", "nth lookup");
r_strpool_free (p);
mu_end;
}

bool test_r_strpool_empty_string_first(void) {
RStrpool *p = r_strpool_new ();
mu_assert_eq (r_strpool_add (p, ""), 0, "empty string first");
mu_assert_eq (p->count, 1, "and it is stored");
r_strpool_free (p);
mu_end;
}

bool test_r_strpool_append_keeps_first(void) {
RStrpool *p = r_strpool_new ();
int i;
for (i = 0; i < 100; i++) {
add_nth (p, i);
}
mu_assert_eq (r_strpool_append (p, "/usr/src/lib/file-5.c"), 100, "append does not dedup");
mu_assert_eq (get_nth (p, 5), 5, "get finds the first copy");
mu_assert_eq (add_nth (p, 5), 5, "add finds the first copy");
mu_assert_eq (r_strpool_append (p, "y"), 101, "append a new string");
mu_assert_eq (r_strpool_get (p, "y"), 101, "appended string is found");
mu_assert_eq (r_strpool_add (p, "y"), 101, "appended string is deduplicated");
r_strpool_free (p);
mu_end;
}

bool test_r_strpool_add_returns_first(void) {
RStrpool *p = r_strpool_new ();
int first[500];
int i, next = 0;
for (i = 0; i < 500; i++) {
first[i] = -1;
}
for (i = 0; i < 3000; i++) {
const int n = (i * 37) % 500;
if (first[n] < 0) {
first[n] = next++;
}
mu_assert_eq (add_nth (p, n), first[n], "add returns the first position");
mu_assert_eq (add_nth (p, n), first[n], "repeated add returns it again");
}
mu_assert_eq (p->count, 500, "each string stored once");
r_strpool_free (p);
mu_end;
}

bool test_r_strpool_append_duplicates(void) {
RStrpool *p = r_strpool_new ();
int i;
for (i = 0; i < 3000; i++) {
add_nth (p, i);
r_strpool_append (p, "/usr/src/lib/file-7.c");
}
mu_assert_eq (get_nth (p, 7), 1, "get finds the first copy among appended duplicates");
r_strpool_free (p);
mu_end;
}

bool test_r_strpool_many(void) {
RStrpool *p = r_strpool_new ();
int i;
for (i = 0; i < 5000; i++) {
mu_assert_eq (add_nth (p, i), i, "distinct strings get fresh positions");
}
for (i = 0; i < 5000; i += 7) {
mu_assert_eq (add_nth (p, i), i, "strings survive the pool reallocating");
}
mu_assert_eq (p->count, 5000, "nothing duplicated");
r_strpool_free (p);
mu_end;
}

bool test_r_strpool_slice_and_empty(void) {
RStrpool *p = r_strpool_new ();
int i;
for (i = 0; i < 100; i++) {
add_nth (p, i);
}
r_strpool_slice_range (p, 50, 100);
mu_assert_eq (p->count, 50, "fifty strings left");
mu_assert_eq (get_nth (p, 60), 10, "positions shift with the slice");
mu_assert_eq (get_nth (p, 10), -1, "sliced-out string is gone");
mu_assert_eq (add_nth (p, 10), 50, "re-added after the slice");
r_strpool_slice (p, 20);
mu_assert_eq (get_nth (p, 75), -1, "string past the slice is gone");
mu_assert_eq (add_nth (p, 75), 20, "re-added after the slice");
r_strpool_empty (p);
mu_assert_eq (get_nth (p, 60), -1, "empty pool finds nothing");
mu_assert_eq (add_nth (p, 60), 0, "add after empty");
mu_assert_eq (add_nth (p, 61), 1, "second add after empty");
mu_assert_eq (add_nth (p, 60), 0, "nothing from before the empty is found");
r_strpool_free (p);
mu_end;
}

bool test_r_strpool_truncate(void) {
RStrpool *p = r_strpool_new ();
int i;
for (i = 0; i < 100; i++) {
add_nth (p, i);
}
r_strpool_slice_range (p, 0, 10);
mu_assert_eq (p->count, 10, "ten strings left");
mu_assert_eq (get_nth (p, 50), -1, "truncated string is gone");
mu_assert_eq (add_nth (p, 50), 10, "re-added after the truncate");
r_strpool_free (p);
mu_end;
}

// append can store duplicates, so a stale previous hit could name a later copy
bool test_r_strpool_last_reset(void) {
RStrpool *p = r_strpool_new ();
r_strpool_add (p, "a");
r_strpool_add (p, "b");
r_strpool_empty (p);
r_strpool_append (p, "a");
r_strpool_append (p, "a");
mu_assert_eq (r_strpool_add (p, "a"), 0, "first copy after an empty");
r_strpool_free (p);
p = r_strpool_new ();
r_strpool_append (p, "a");
r_strpool_append (p, "x");
r_strpool_add (p, "x");
r_strpool_slice (p, 1);
r_strpool_append (p, "a");
mu_assert_eq (r_strpool_add (p, "a"), 0, "first copy after a slice");
r_strpool_free (p);
p = r_strpool_new ();
r_strpool_add (p, "a");
r_strpool_add (p, "b");
r_strpool_add (p, "c");
r_strpool_slice_range (p, 0, 1);
r_strpool_append (p, "b");
r_strpool_append (p, "a");
mu_assert_eq (r_strpool_add (p, "a"), 0, "first copy after a truncate");
r_strpool_free (p);
p = r_strpool_new ();
r_strpool_add (p, "z");
r_strpool_add (p, "a");
r_strpool_add (p, "q");
r_strpool_append (p, "a");
r_strpool_add (p, "q");
r_strpool_slice_range (p, 1, 4);
mu_assert_eq (r_strpool_add (p, "a"), 0, "first copy after a shifting slice");
r_strpool_free (p);
mu_end;
}

bool test_r_strpool_last_after_slice(void) {
RStrpool *p = r_strpool_new ();
r_strpool_add (p, "a");
r_strpool_add (p, "b");
r_strpool_add (p, "c");
r_strpool_slice_range (p, 1, 3);
mu_assert_eq (r_strpool_add (p, "c"), 1, "previous hit does not survive a slice");
mu_assert_eq (r_strpool_add (p, "b"), 0, "previous hit miss falls back to lookup");
r_strpool_free (p);
mu_end;
}

int all_tests(void) {
mu_run_test (test_r_strpool_add_dedups);
mu_run_test (test_r_strpool_empty_string_first);
mu_run_test (test_r_strpool_append_keeps_first);
mu_run_test (test_r_strpool_add_returns_first);
mu_run_test (test_r_strpool_append_duplicates);
mu_run_test (test_r_strpool_many);
mu_run_test (test_r_strpool_slice_and_empty);
mu_run_test (test_r_strpool_truncate);
mu_run_test (test_r_strpool_last_reset);
mu_run_test (test_r_strpool_last_after_slice);
return tests_passed != tests_run;
}

int main(int argc, char **argv) {
return all_tests ();
}
Loading