mirror of https://github.com/svaarala/duktape.git
Sami Vaarala
8 years ago
1 changed files with 131 additions and 0 deletions
@ -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…
Reference in new issue