-
-
Notifications
You must be signed in to change notification settings - Fork 176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Map item iterators #1143
base: master
Are you sure you want to change the base?
feat: Map item iterators #1143
Changes from all commits
cd8fbd6
bf04599
2b7c381
d1f42e4
0572237
338913c
1ee402d
eb2bbb1
5254d25
c0974e3
7512069
1a1cb69
dcc5f20
959c375
1accd89
05b907e
96a344c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -778,6 +778,75 @@ sentry_value_get_by_key_owned(sentry_value_t value, const char *k) | |
return rv; | ||
} | ||
|
||
struct sentry_item_iter_s { | ||
size_t *len; // Pointer to length! | ||
obj_pair_t *pairs; | ||
size_t index; | ||
int frozen; | ||
}; | ||
|
||
sentry_item_iter_t * | ||
sentry_value_new_item_iter(sentry_value_t value) | ||
{ | ||
const thing_t *thing = value_as_thing(value); | ||
if (thing && thing_get_type(thing) == THING_TYPE_OBJECT) { | ||
obj_t *o = thing->payload._ptr; | ||
sentry_item_iter_t *item_iter = SENTRY_MAKE(sentry_item_iter_t); | ||
item_iter->len = &o->len; | ||
item_iter->pairs = o->pairs; | ||
item_iter->index = 0; | ||
item_iter->frozen = thing_is_frozen(thing); | ||
return item_iter; | ||
} | ||
return NULL; | ||
} | ||
|
||
void | ||
sentry_value_item_iter_next(sentry_item_iter_t *item_iter) | ||
{ | ||
item_iter->index++; | ||
} | ||
|
||
const char * | ||
sentry_value_item_iter_get_key(sentry_item_iter_t *item_iter) | ||
{ | ||
if (item_iter->index >= *item_iter->len) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If |
||
return NULL; | ||
} | ||
return item_iter->pairs[item_iter->index].k; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If |
||
} | ||
|
||
sentry_value_t | ||
sentry_value_item_iter_get_value(sentry_item_iter_t *item_iter) | ||
{ | ||
if (item_iter->index >= *item_iter->len) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
return sentry_value_new_null(); | ||
} | ||
return item_iter->pairs[item_iter->index].v; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
} | ||
|
||
int | ||
sentry_value_item_iter_valid(sentry_item_iter_t *item_iter) | ||
{ | ||
return item_iter->index < *item_iter->len && item_iter->pairs != NULL; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we check |
||
} | ||
|
||
int | ||
sentry_value_item_iter_erase(sentry_item_iter_t *item_iter) | ||
{ | ||
if (item_iter->frozen || item_iter->index >= *item_iter->len) { | ||
return 1; | ||
} | ||
obj_pair_t *pair = &item_iter->pairs[item_iter->index]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, do we assume that If so, why? Because Whichever way we choose, we should be consistent regarding the guarantees of calling This demonstrates how easily checks (and thus guarantees) can become inconsistent quickly when an in-place erasure is introduced. If you introduce the guarantees for each getter and mutator (making |
||
sentry_free(pair->k); | ||
sentry_value_decref(pair->v); | ||
memmove(item_iter->pairs + item_iter->index, | ||
item_iter->pairs + item_iter->index + 1, | ||
(*item_iter->len - item_iter->index - 1) * sizeof(item_iter->pairs[0])); | ||
(*item_iter->len)--; | ||
return 0; | ||
} | ||
|
||
sentry_value_t | ||
sentry_value_get_by_index(sentry_value_t value, size_t index) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -290,6 +290,94 @@ SENTRY_TEST(value_object) | |
sentry_value_decref(val); | ||
} | ||
|
||
SENTRY_TEST(value_object_iteration) | ||
{ | ||
sentry_value_t obj = sentry_value_new_object(); | ||
|
||
// Populate. | ||
for (size_t i = 0; i < 10; i++) { | ||
char key[100]; | ||
sprintf(key, "key%d", (int)i); | ||
sentry_value_set_by_key(obj, key, sentry_value_new_int32((int32_t)i)); | ||
} | ||
|
||
// Iterate over items. | ||
{ | ||
sentry_item_iter_t *it = sentry_value_new_item_iter(obj); | ||
size_t count = 0; | ||
TEST_CHECK(it != NULL); | ||
for (; sentry_value_item_iter_valid(it); | ||
sentry_value_item_iter_next(it)) { | ||
const char *key = sentry_value_item_iter_get_key(it); | ||
sentry_value_t value = sentry_value_item_iter_get_value(it); | ||
|
||
TEST_CHECK(key != NULL); | ||
TEST_CHECK(sentry_value_get_type(value) == SENTRY_VALUE_TYPE_INT32); | ||
|
||
int32_t key_idx; | ||
sscanf(key, "key%d", &key_idx); | ||
TEST_CHECK_INT_EQUAL(key_idx, sentry_value_as_int32(value)); | ||
|
||
count++; | ||
} | ||
TEST_CHECK_INT_EQUAL(count, 10); | ||
sentry_free(it); | ||
} | ||
|
||
// Erase even-numbered items. | ||
{ | ||
sentry_item_iter_t *it = sentry_value_new_item_iter(obj); | ||
TEST_CHECK(it != NULL); | ||
size_t count = 0; | ||
const char *prev_key = ""; | ||
size_t i = 0; | ||
while (sentry_value_item_iter_valid(it)) { | ||
TEST_CHECK( | ||
strcmp(prev_key, sentry_value_item_iter_get_key(it)) != 0); | ||
prev_key = sentry_value_item_iter_get_key(it); | ||
if (i % 2 == 0) { | ||
int err = sentry_value_item_iter_erase(it); | ||
TEST_CHECK_INT_EQUAL(err, 0); | ||
} else { | ||
sentry_value_item_iter_next(it); | ||
count++; | ||
} | ||
i++; | ||
} | ||
TEST_CHECK_INT_EQUAL(sentry_value_get_length(obj), 5); | ||
TEST_CHECK_INT_EQUAL(count, 5); | ||
sentry_free(it); | ||
} | ||
|
||
// Verify if the right items were removed. | ||
{ | ||
sentry_item_iter_t *it = sentry_value_new_item_iter(obj); | ||
for (; sentry_value_item_iter_valid(it); | ||
sentry_value_item_iter_next(it)) { | ||
const char *key = sentry_value_item_iter_get_key(it); | ||
int32_t key_idx; | ||
sscanf(key, "key%d", &key_idx); | ||
TEST_CHECK(key_idx % 2 != 0); | ||
} | ||
sentry_free(it); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we keep the mutating iterator, please also add a loop that adds keys or documents that this is forbidden. |
||
// Erase the rest of the items. | ||
{ | ||
sentry_item_iter_t *it = sentry_value_new_item_iter(obj); | ||
size_t count = 0; | ||
TEST_CHECK(it != NULL); | ||
while (sentry_value_item_iter_erase(it) == 0) { | ||
count++; | ||
} | ||
TEST_CHECK_INT_EQUAL(sentry_value_get_length(obj), 0); | ||
TEST_CHECK_INT_EQUAL(count, 5); | ||
sentry_free(it); | ||
} | ||
|
||
sentry_value_decref(obj); | ||
} | ||
|
||
SENTRY_TEST(value_object_merge) | ||
{ | ||
sentry_value_t dst = sentry_value_new_object(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having
len
in the iterator and the actual data structure be independent was a choice since it clarifies the immutability of the iterated object.If we introduce a pointer here, its validity becomes part of the iterator invariant and must be checked accordingly.
I am not against introducing in-place erasure, but as you can see, it complicates the user guarantees, which you can't change without breaking the code, even if you keep the interfaces the same.
To be clear, you can achieve the same filtering by populating a new object with only the unfiltered items. The cost will be negligible in most cases (considering that practically anything you interact with is a pointer or a thing the size of a pointer), and you will have to make fewer decisions regarding the API and its guarantees.
We can still introduce in-place erasure later if someone finds "copy filtering" too costly (either due to required memory size or allocation cost). I also wonder if most scenarios want to remove items of the original object they are iterating. Would you happen to have an example?
Again, I am not entirely against introducing this; we should just re-evaluate the need vs the cost.