/* * Prototype loop is tricky to handle internally and must not cause e.g. * GC failures. Exercise all prototype walk sites, and a few other * specific cases. * * At the moment prototype loops cause an error to be thrown so that * they are easily noticed (used code should never intentionally create * one). Internal stuff, like GC handling, must -avoid- throwing errors * in critical situations so that e.g. GC doesn't terminate with an * uncaught error. This testcase documents the current behavior. * * Sites can be found with DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY and then * looking at possible callers: * * - duk_hobject_prototype_chain_contains(): see below * * - duk__get_property_desc: can be exercised through duk_hobject_hasprop * * - duk_hobject_getprop * * - duk_hobject_putprop * * - duk_js_instanceof * * - duk__get_identifier_reference: walks environments chained using the * prototype reference; these cannot be in a loop and user code cannot * access these * * duk_hobject_prototype_chain_contains() call sites: * * - Object.prototype.isPrototypeOf() * * - duk_err_augment_error_create(): can be exercised by throwing an * object with a prototype loop */ /*=== *** test_gc (duk_safe_call) first gc make unreachable second gc ==> rc=0, result='undefined' *** test_is_prototype_of (duk_safe_call) Object.prototype.isPrototypeOf result: false Object.prototype.isPrototypeOf result: true ==> rc=1, result='Error: prototype chain limit' *** test_error_augment (duk_safe_call) ret=1 throw value .foo=123 ==> rc=0, result='undefined' *** test_hasprop (duk_safe_call) hasprop foo: 1 hasprop bar: 1 ==> rc=1, result='Error: prototype chain limit' *** test_getprop (duk_safe_call) getprop foo: 123 getprop bar: 321 ==> rc=1, result='Error: prototype chain limit' *** test_putprop (duk_safe_call) putprop foo done putprop bar done ==> rc=1, result='Error: prototype chain limit' *** test_instanceof (duk_safe_call) object function true object function true object function ==> rc=1, result='Error: prototype chain limit' still here ===*/ static void prep(duk_context *ctx) { duk_set_top(ctx, 0); duk_push_object(ctx); duk_push_object(ctx); duk_push_int(ctx, 123); duk_put_prop_string(ctx, 0, "foo"); duk_push_int(ctx, 321); duk_put_prop_string(ctx, 1, "bar"); duk_dup(ctx, 0); duk_set_prototype(ctx, 1); duk_dup(ctx, 1); duk_set_prototype(ctx, 0); /* Two objects on stack, in a prototype loop with each other. * One object has a 'foo' property, the other a 'bar' property. */ } static duk_ret_t test_gc(duk_context *ctx) { prep(ctx); /* Both objects are now in a prototype loop. Force garbage * collection to ensure nothing breaks. */ printf("first gc\n"); fflush(stdout); duk_gc(ctx, 0); /* Make the objects unreachable and re-run GC. This triggers * e.g. finalizer checks. */ printf("make unreachable\n"); fflush(stdout); duk_set_top(ctx, 0); printf("second gc\n"); fflush(stdout); duk_gc(ctx, 0); return 0; } static duk_ret_t test_is_prototype_of(duk_context *ctx) { prep(ctx); /* obj0.isPrototypeOf(dummy) -> false, traverses prototype chain of dummy */ duk_eval_string(ctx, "Object.prototype.isPrototypeOf"); duk_dup(ctx, 0); duk_push_object(ctx); duk_call_method(ctx, 1); printf("Object.prototype.isPrototypeOf result: %s\n", duk_safe_to_string(ctx, -1)); duk_pop(ctx); /* obj0.isPrototypeOf(obj1) -> true, traverses prototype chain of obj1 */ duk_eval_string(ctx, "Object.prototype.isPrototypeOf"); duk_dup(ctx, 0); duk_dup(ctx, 1); duk_call_method(ctx, 1); printf("Object.prototype.isPrototypeOf result: %s\n", duk_safe_to_string(ctx, -1)); duk_pop(ctx); /* dummy.isPrototypeOf(obj0) -> traverses prototype chain of obj0 and throws */ duk_eval_string(ctx, "Object.prototype.isPrototypeOf"); duk_push_object(ctx); duk_dup(ctx, 0); duk_call_method(ctx, 1); printf("Object.prototype.isPrototypeOf result: %s\n", duk_safe_to_string(ctx, -1)); duk_pop(ctx); return 0; } static duk_ret_t augment_raw(duk_context *ctx) { duk_throw(ctx); return 0; } static duk_ret_t test_error_augment(duk_context *ctx) { duk_int_t ret; prep(ctx); /* This case is a bit tricky. There used to be a problem where the * error augmentation process itself failed when checking whether or * not the throw value inherited from Error. This has now been fixed * and the error value no longer gets augmented and is thrown correctly. * * The TEST_SAFE_CALL() wrapper uses duk_safe_to_string() to coerce * the throw result. Since the object doesn't have a toString() * function, this coercion will fail and generate a prototype loop * error! * * So, use a separate duk_safe_call() wrapping here to ensure we treat * the final result value carefully. We print out 'foo' to be sure * the correct value was thrown. */ duk_dup(ctx, 0); ret = duk_safe_call(ctx, augment_raw, 0 /*nargs*/, 1 /*nrets*/); printf("ret=%d\n", (int) ret); duk_get_prop_string(ctx, -1, "foo"); printf("throw value .foo=%d\n", duk_get_int(ctx, -1)); duk_pop_2(ctx); return 0; } static duk_ret_t test_hasprop(duk_context *ctx) { duk_bool_t ret; prep(ctx); /* Property exists, own property */ ret = duk_has_prop_string(ctx, 0, "foo"); printf("hasprop foo: %d\n", (int) ret); /* Property exists, inherited property */ ret = duk_has_prop_string(ctx, 0, "bar"); printf("hasprop bar: %d\n", (int) ret); /* Property doesn't exist, terminate with error */ ret = duk_has_prop_string(ctx, 0, "quux"); printf("hasprop quux: %d\n", (int) ret); return 0; } static duk_ret_t test_getprop(duk_context *ctx) { prep(ctx); /* Property exists, own property */ duk_get_prop_string(ctx, 0, "foo"); printf("getprop foo: %s\n", duk_safe_to_string(ctx, -1)); duk_pop(ctx); /* Property exists, inherited property */ duk_get_prop_string(ctx, 0, "bar"); printf("getprop bar: %s\n", duk_safe_to_string(ctx, -1)); duk_pop(ctx); /* Property doesn't exist, terminate with error */ duk_get_prop_string(ctx, 0, "quux"); printf("getprop quux: %s\n", duk_safe_to_string(ctx, -1)); duk_pop(ctx); return 0; } static duk_ret_t test_putprop(duk_context *ctx) { prep(ctx); /* Property exists, own property */ duk_push_int(ctx, 1001); duk_put_prop_string(ctx, 0, "foo"); printf("putprop foo done\n"); /* Property exists, inherited property */ duk_push_int(ctx, 1002); duk_put_prop_string(ctx, 0, "bar"); printf("putprop bar done\n"); /* Property doesn't exist, terminate with error */ duk_push_int(ctx, 1003); duk_put_prop_string(ctx, 0, "quux"); printf("putprop quux done\n"); return 0; } static duk_ret_t test_instanceof(duk_context *ctx) { prep(ctx); /* For 'a instanceof b', the [[HasInstance]] algorithm looks up * b.prototype and then walks the internal prototype chain of 'a' * looking for b.prototype. The rvalue must also be a Function. * So we need a temporary object wrapping one of the objects * created by prep(). */ /* obj0 instanceof { prototype: obj0 } -> true, found */ duk_eval_string(ctx, "(function (a,b) { print(typeof a, typeof b); print(a instanceof b); })"); duk_dup(ctx, 0); duk_eval_string(ctx, "(function() {})"); duk_dup(ctx, 0); duk_put_prop_string(ctx, -2, "prototype"); duk_call(ctx, 2); duk_pop(ctx); /* obj0 instanceof { prototype: obj1 } -> true, found */ duk_eval_string(ctx, "(function (a,b) { print(typeof a, typeof b); print(a instanceof b); })"); duk_dup(ctx, 0); duk_eval_string(ctx, "(function() {})"); duk_dup(ctx, 0); duk_put_prop_string(ctx, -2, "prototype"); duk_call(ctx, 2); duk_pop(ctx); /* obj0 instanceof { prototype: dummy } -> error, loop */ duk_eval_string(ctx, "(function (a,b) { print(typeof a, typeof b); print(a instanceof b); })"); duk_dup(ctx, 0); duk_eval_string(ctx, "(function() {})"); duk_push_object(ctx); duk_put_prop_string(ctx, -2, "prototype"); duk_call(ctx, 2); duk_pop(ctx); return 0; } void test(duk_context *ctx) { TEST_SAFE_CALL(test_gc); TEST_SAFE_CALL(test_is_prototype_of); TEST_SAFE_CALL(test_error_augment); TEST_SAFE_CALL(test_hasprop); TEST_SAFE_CALL(test_getprop); TEST_SAFE_CALL(test_putprop); TEST_SAFE_CALL(test_instanceof); printf("still here\n"); fflush(stdout); }