/* * Testcase for object finalizer manipulation. */ /*=== *** test_basic (duk_safe_call) top: 1 finalizer name: basic_finalizer top: 1 before set top 0 basic_finalizer, arg: target object after set top 0 ==> rc=0, result='undefined' *** test_recursive_finalizer (duk_safe_call) top: 1 finalizer.bar=321 c_finalizer.quux=234 before set top 0 finalizing obj, obj.foo: 123 c_finalizer, argument bar: 321 after set top 0 before explicit gc after explicit gc ==> rc=0, result='undefined' *** test_get_nonobject (duk_safe_call) read finalizer: undefined ==> rc=0, result='undefined' *** test_set_nonobject (duk_safe_call) ==> rc=1, result='TypeError: invalid base value' *** test_finalizer_loop (duk_safe_call) before pop after pop before forced gc finalizer called after forced gc ==> rc=0, result='undefined' ===*/ static duk_ret_t basic_finalizer(duk_context *ctx) { printf("basic_finalizer, arg: %s\n", duk_safe_to_string(ctx, -1)); return 0; } static duk_ret_t test_basic(duk_context *ctx) { /* Object to be finalized, special toString() */ duk_push_object(ctx); duk_eval_string(ctx, "(function() { return 'target object'; })"); duk_put_prop_string(ctx, -2, "toString"); /* [ target ] */ /* Set finalizer */ duk_push_c_function(ctx, basic_finalizer, 1); duk_push_string(ctx, "basic_finalizer"); duk_put_prop_string(ctx, -2, "myName"); duk_set_finalizer(ctx, -2); /* [ target ] */ printf("top: %ld\n", (long) duk_get_top(ctx)); /* Get finalizer and check it is correct */ duk_get_finalizer(ctx, -1); duk_get_prop_string(ctx, -1, "myName"); printf("finalizer name: %s\n", duk_to_string(ctx, -1)); duk_pop_2(ctx); printf("top: %ld\n", (long) duk_get_top(ctx)); /* [ target ] */ printf("before set top 0\n"); duk_set_top(ctx, 0); printf("after set top 0\n"); /* [ ] */ return 0; } static duk_ret_t c_finalizer(duk_context *ctx) { duk_get_prop_string(ctx, 0, "bar"); printf("c_finalizer, argument bar: %s\n", duk_safe_to_string(ctx, -1)); return 0; } static duk_ret_t test_recursive_finalizer(duk_context *ctx) { /* Object to be finalized */ duk_push_object(ctx); duk_push_int(ctx, 123); duk_put_prop_string(ctx, -2, "foo"); /* Ecmascript finalizer */ duk_eval_string(ctx, "(function (obj) { print('finalizing obj, obj.foo:', obj.foo); })"); duk_push_int(ctx, 321); duk_put_prop_string(ctx, -2, "bar"); /* [ target finalizer ] */ /* Break the function <-> prototype reference loop so that the * Ecmascript finalizer is not in a reference loop and gets * collected by refcounting. * * (Note that 'prototype' is not configurable so we can't * delete it.) */ duk_push_string(ctx, "dummy"); duk_put_prop_string(ctx, -2, "prototype"); /* Add a Duktape/C finalizer for the Ecmascript finalizer to * exercise both Duktape/C finalizers and recursive finalization */ duk_push_c_function(ctx, c_finalizer, 1); duk_push_int(ctx, 234); duk_put_prop_string(ctx, -2, "quux"); duk_set_finalizer(ctx, -2); /* [ target(foo:123) finalizer(bar:321) ] */ /* Set Ecmascript finalizer to original object */ duk_set_finalizer(ctx, -2); /* [ target(foo:123) ] */ printf("top: %ld\n", (long) duk_get_top(ctx)); /* Read back the finalizer and the finalizer's finalizer */ duk_get_finalizer(ctx, -1); /* target's finalizer */ duk_get_finalizer(ctx, -1); /* finalizer's finalizer */ /* [ target(foo:123) finalizer(bar:321) c_finalizer(quux:234) ] */ duk_get_prop_string(ctx, -2, "bar"); printf("finalizer.bar=%s\n", duk_safe_to_string(ctx, -1)); duk_pop(ctx); duk_get_prop_string(ctx, -1, "quux"); printf("c_finalizer.quux=%s\n", duk_safe_to_string(ctx, -1)); duk_pop(ctx); /* [ target(foo:123) finalizer(bar:321) c_finalizer(quux:234) ] */ printf("before set top 0\n"); duk_set_top(ctx, 0); /* causes finalization */ printf("after set top 0\n"); /* [ ] */ /* Explicit GC (just in case e.g. a reference loop prevented collection) */ printf("before explicit gc\n"); duk_gc(ctx, 0); printf("after explicit gc\n"); return 0; } static duk_ret_t test_get_nonobject(duk_context *ctx) { duk_push_int(ctx, 123); duk_get_finalizer(ctx, -1); printf("read finalizer: %s\n", duk_safe_to_string(ctx, -1)); return 0; } static duk_ret_t test_set_nonobject(duk_context *ctx) { duk_push_int(ctx, 123); duk_push_int(ctx, 321); duk_set_finalizer(ctx, -2); printf("never here\n"); return 0; } static duk_ret_t test_finalizer_loop(duk_context *ctx) { /* Setup a finalizer loop: the finalizer of a finalizer is the * finalizer itself. The finalizer won't be called recursively. */ duk_eval_string(ctx, "(function (obj) { print('finalizer called'); })"); duk_dup(ctx, -1); duk_set_finalizer(ctx, -2); printf("before pop\n"); duk_pop(ctx); printf("after pop\n"); /* The finalizer participates in two circular references so it won't * be collected until mark-and-sweep happens. The first circular * reference is the function<->prototype loop. The second circular * reference is the finalizer reference which points to the object * itself. */ printf("before forced gc\n"); duk_gc(ctx, 0); printf("after forced gc\n"); return 0; } void test(duk_context *ctx) { TEST_SAFE_CALL(test_basic); TEST_SAFE_CALL(test_recursive_finalizer); TEST_SAFE_CALL(test_get_nonobject); TEST_SAFE_CALL(test_set_nonobject); TEST_SAFE_CALL(test_finalizer_loop); }