You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

296 lines
8.1 KiB

/*
* 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);
}