Browse Source

Add testcase for string intern side effect risk

pull/884/head
Sami Vaarala 8 years ago
parent
commit
4f1e53faa1
  1. 131
      tests/api/test-dev-string-intern-side-effect.c

131
tests/api/test-dev-string-intern-side-effect.c

@ -0,0 +1,131 @@
/*
* Illustrate corner case string intern side effect behavior: when a finalizer
* is triggered during string interning it can potentially invalidate the
* string data pointer/length provided before the data has been copied.
*
* This would be quite difficult to trigger in practice. This tests case
* relies on voluntary mark-and-sweep to trigger when the new duk_hstring
* is allocated. To achieve that a lot of tries are needed.
*
* https://github.com/svaarala/duktape/pull/884
*
* Run with valgrind to catch memory unsafe behavior.
*/
/* When testing manually, increase to e.g. 100. */
#define TEST_COUNT 3
/*===
*** test_side_effect (duk_safe_call)
...0
finalizer
resized
...1
finalizer
resized
...2
finalizer
resized
final top: 0
==> rc=0, result='undefined'
===*/
static int finalizer_ran = 0;
static duk_ret_t my_finalizer(duk_context *ctx) {
finalizer_ran = 1;
#if 1
printf("finalizer\n"); fflush(stdout);
#endif
duk_get_global_string(ctx, "currentBuffer");
duk_resize_buffer(ctx, -1, 0);
#if 1
printf("resized\n"); fflush(stdout);
#endif
return 0;
}
static duk_ret_t test_side_effect(duk_context *ctx, void *udata) {
int i, count_intern;
void *ptr;
(void) udata;
for (i = 0; i < TEST_COUNT; i++) {
printf("...%d\n", i); fflush(stdout);
/* Create a dynamic buffer we tweak in the side effect.
* Buffer is not yet registered to global.currentBuffer
* so any previous finalizers (which might still exist)
* can't reach it.
*/
ptr = duk_push_dynamic_buffer(ctx, 1024 * 1024);
/* Create cyclical garbage which won't get collected by
* refcounting. This is essential so that there can be
* something with a finalizer when mark-and-sweep gets
* triggered.
*/
duk_push_object(ctx);
duk_push_object(ctx);
duk_dup(ctx, -2);
duk_put_prop_string(ctx, -2, "ref");
duk_dup(ctx, -1);
duk_put_prop_string(ctx, -3, "ref");
duk_push_c_function(ctx, my_finalizer, 0);
duk_set_finalizer(ctx, -2);
/* Fill the buffer and make it reachable for the finalizer. */
finalizer_ran = 0;
memset(ptr, 0, 1024 * 1024);
*((int *) ptr) = i; /* Create a new string on each round. */
duk_dup(ctx, -3);
duk_put_global_string(ctx, "currentBuffer");
duk_remove(ctx, -3);
/* We're now ready: cause the two objects to become not yet
* collected garbage. The buffer may be resized before we
* reach the string test, so don't reference 'ptr' anymore.
*/
duk_pop_2(ctx);
/* Finally, push a new string and hope we trigger the
* finalizer. Repeat the operation until the finalizer
* runs (it may already have run), and hope it gets triggered
* in the right spot.
*/
count_intern = 0;
while (!finalizer_ran) {
count_intern++;
duk_push_lstring(ctx, ptr, 1024 * 1024);
duk_pop(ctx);
/* Because the fix in https://github.com/svaarala/duktape/pull/884
* prevent side effects in string interning, we need to trigger
* them explicitly at some pointer to avoid looping forever.
* The limit here allows a failure to be detected in e.g. 1.5.0.
*/
if (count_intern >= 50000) {
duk_gc(ctx, 0);
}
}
#if 0
printf("Finalizer run took %d tries \n", count_intern); fflush(stdout);
#endif
}
duk_gc(ctx, 0);
duk_gc(ctx, 0);
printf("final top: %ld\n", (long) duk_get_top(ctx));
return 0;
}
void test(duk_context *ctx) {
TEST_SAFE_CALL(test_side_effect);
}
Loading…
Cancel
Save