From 8f8de764cb2f891fd704330e920cefdcdf54d63d Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Thu, 22 Aug 2019 18:27:34 +0300 Subject: [PATCH] Making "prototype" property of function instances writable. This closes #40 issue on Github. --- src/njs_function.c | 89 +++++++++++++++++++++++++++------------- src/njs_function.h | 2 - src/njs_vmcode.c | 60 ++++++++++++--------------- src/test/njs_unit_test.c | 39 +++++++++++++++++- 4 files changed, 123 insertions(+), 67 deletions(-) diff --git a/src/njs_function.c b/src/njs_function.c index 5a3af531..8a1f97da 100644 --- a/src/njs_function.c +++ b/src/njs_function.c @@ -780,6 +780,42 @@ njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *native) } +static njs_value_t * +njs_function_property_prototype_create(njs_vm_t *vm, njs_lvlhsh_t *hash, + njs_value_t *prototype) +{ + njs_int_t ret; + njs_object_prop_t *prop; + njs_lvlhsh_query_t lhq; + + const njs_value_t proto_string = njs_string("prototype"); + + prop = njs_object_prop_alloc(vm, &proto_string, prototype, 0); + if (njs_slow_path(prop == NULL)) { + return NULL; + } + + prop->writable = 1; + + lhq.value = prop; + lhq.key_hash = NJS_PROTOTYPE_HASH; + lhq.key = njs_str_value("prototype"); + lhq.replace = 1; + lhq.pool = vm->mem_pool; + lhq.proto = &njs_object_hash_proto; + + ret = njs_lvlhsh_insert(hash, &lhq); + + if (njs_fast_path(ret == NJS_OK)) { + return &prop->value; + } + + njs_internal_error(vm, "lvlhsh insert failed"); + + return NULL; +} + + /* * The "prototype" property of user defined functions is created on * demand in private hash of the functions by the "prototype" getter. @@ -794,49 +830,43 @@ njs_int_t njs_function_prototype_create(njs_vm_t *vm, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) { - njs_value_t *proto; - - proto = njs_function_property_prototype_create(vm, value); - - if (njs_fast_path(proto != NULL)) { - *retval = *proto; - return NJS_OK; - } - - return NJS_ERROR; -} - - -njs_value_t * -njs_function_property_prototype_create(njs_vm_t *vm, njs_value_t *value) -{ - njs_value_t *proto, *cons; + njs_value_t *proto, proto_value, *cons; njs_object_t *prototype; njs_function_t *function; - prototype = njs_object_alloc(vm); - if (njs_slow_path(prototype == NULL)) { - return NULL; + if (setval == NULL) { + prototype = njs_object_alloc(vm); + if (njs_slow_path(prototype == NULL)) { + return NJS_ERROR; + } + + njs_set_object(&proto_value, prototype); + + setval = &proto_value; } function = njs_function_value_copy(vm, value); if (njs_slow_path(function == NULL)) { - return NULL; + return NJS_ERROR; } - proto = njs_property_prototype_create(vm, &function->object.hash, - prototype); + proto = njs_function_property_prototype_create(vm, &function->object.hash, + setval); if (njs_slow_path(proto == NULL)) { - return NULL; + return NJS_ERROR; } - cons = njs_property_constructor_create(vm, &prototype->hash, value); - - if (njs_fast_path(cons != NULL)) { - return proto; + if (njs_is_object(proto)) { + cons = njs_property_constructor_create(vm, njs_object_hash(proto), + value); + if (njs_slow_path(cons == NULL)) { + return NJS_ERROR; + } } - return NULL; + *retval = *proto; + + return NJS_OK; } @@ -1197,6 +1227,7 @@ const njs_object_prop_t njs_function_instance_properties[] = .type = NJS_PROPERTY_HANDLER, .name = njs_string("prototype"), .value = njs_prop_handler(njs_function_prototype_create), + .writable = 1 }, }; diff --git a/src/njs_function.h b/src/njs_function.h index 983d220b..29decd01 100644 --- a/src/njs_function.h +++ b/src/njs_function.h @@ -118,8 +118,6 @@ njs_int_t njs_function_rest_parameters_init(njs_vm_t *vm, njs_native_frame_t *frame); njs_int_t njs_function_prototype_create(njs_vm_t *vm, njs_value_t *value, njs_value_t *setval, njs_value_t *retval); -njs_value_t *njs_function_property_prototype_create(njs_vm_t *vm, - njs_value_t *value); njs_int_t njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused); njs_int_t njs_function_native_frame(njs_vm_t *vm, njs_function_t *function, diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 7c86ba0e..5aaf925f 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -70,7 +70,8 @@ static njs_jump_off_t njs_primitive_values_compare(njs_vm_t *vm, static njs_jump_off_t njs_function_frame_create(njs_vm_t *vm, njs_value_t *value, const njs_value_t *this, uintptr_t nargs, njs_bool_t ctor); -static njs_object_t *njs_function_new_object(njs_vm_t *vm, njs_value_t *value); +static njs_object_t *njs_function_new_object(njs_vm_t *vm, + njs_value_t *constructor); /* * The nJSVM is optimized for an ABIs where the first several arguments @@ -1458,10 +1459,10 @@ njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object, njs_jump_off_t ret; const njs_value_t *retval; - static njs_value_t prototype_string = njs_string("prototype"); + const njs_value_t prototype_string = njs_string("prototype"); if (!njs_is_function(constructor)) { - njs_type_error(vm, "right argument is not a function"); + njs_type_error(vm, "right argument is not callable"); return NJS_ERROR; } @@ -1469,16 +1470,17 @@ njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object, if (njs_is_object(object)) { njs_set_undefined(&value); - ret = njs_value_property(vm, constructor, &prototype_string, &value); + ret = njs_value_property(vm, constructor, + njs_value_arg(&prototype_string), &value); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } if (njs_fast_path(ret == NJS_OK)) { - if (njs_slow_path(!njs_is_object(&value))) { - njs_internal_error(vm, "prototype is not an object"); + njs_type_error(vm, "Function has non-object prototype " + "in instanceof"); return NJS_ERROR; } @@ -1737,41 +1739,31 @@ njs_function_frame_create(njs_vm_t *vm, njs_value_t *value, static njs_object_t * -njs_function_new_object(njs_vm_t *vm, njs_value_t *value) +njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor) { - njs_value_t *proto; - njs_object_t *object; - njs_jump_off_t ret; - njs_function_t *function; - njs_object_prop_t *prop; - njs_lvlhsh_query_t lhq; - - object = njs_object_alloc(vm); + njs_value_t proto; + njs_object_t *object; + njs_jump_off_t ret; - if (njs_fast_path(object != NULL)) { + const njs_value_t prototype_string = njs_string("prototype"); - lhq.key_hash = NJS_PROTOTYPE_HASH; - lhq.key = njs_str_value("prototype"); - lhq.proto = &njs_object_hash_proto; - function = njs_function(value); - - ret = njs_lvlhsh_find(&function->object.hash, &lhq); + object = njs_object_alloc(vm); + if (njs_slow_path(object == NULL)) { + return NULL; + } - if (ret == NJS_OK) { - prop = lhq.value; - proto = &prop->value; + ret = njs_value_property(vm, constructor, njs_value_arg(&prototype_string), + &proto); - } else { - proto = njs_function_property_prototype_create(vm, value); - } + if (njs_slow_path(ret == NJS_ERROR)) { + return NULL; + } - if (njs_fast_path(proto != NULL)) { - object->__proto__ = njs_object(proto); - return object; - } - } + if (njs_fast_path(njs_is_object(&proto))) { + object->__proto__ = njs_object(&proto); + } - return NULL; + return object; } diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 43ec8640..5206092b 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -7233,6 +7233,41 @@ static njs_unit_test_t njs_test[] = { njs_str("var F = function (){}; typeof F.prototype"), njs_str("object") }, + { njs_str("var F = function (){}; F.prototype = NaN; ({}) instanceof F"), + njs_str("TypeError: Function has non-object prototype in instanceof") }, + + { njs_str("var F = function() {};" + "[F, F].map((x)=>Object.getOwnPropertyDescriptor(x, 'prototype').writable)" + ".every((x)=> x === true)"), + njs_str("true") }, + + { njs_str("var F = function() {}, a = {t: 1}, b = {t: 2}, x, y; " + "F.prototype = a; x = new F();" + "F.prototype = b; y = new F();" + "x.t == 1 && y.t == 2"), + njs_str("true") }, + + { njs_str("var x = {}, y = function() {}, z; y.prototype = x; z = new y();" + "(z instanceof y) && (z.__proto__ == y.prototype) && (x.isPrototypeOf(z))"), + njs_str("true") }, + + { njs_str("var x = {}, y = function() {}, z; y.prototype = x; z = new y();" + "(z instanceof y) && (z.__proto__ == y.prototype) && (x.isPrototypeOf(z))"), + njs_str("true") }, + + { njs_str("[undefined, null, false, NaN, '']" + ".map((x) => { var f = function() {}; f.prototype = x; " + " return Object.getPrototypeOf(new f()); })" + ".every((x) => x == Object.prototype)"), + njs_str("true") }, + + { njs_str("[undefined, null, false, NaN, '']" + ".map((x) => { var f = function() {}; f.prototype = x; return f; })" + ".map((x) => { try { return ({} instanceof x) ? 1 : 2; } " + " catch (e) { return (e instanceof TypeError) ? 3 : 4; } })" + ".every((x) => x == 3)"), + njs_str("true")}, + { njs_str("new decodeURI('%00')"), njs_str("TypeError: function is not a constructor")}, @@ -8579,7 +8614,7 @@ static njs_unit_test_t njs_test[] = njs_str("true") }, { njs_str("[] instanceof []"), - njs_str("TypeError: right argument is not a function") }, + njs_str("TypeError: right argument is not callable") }, { njs_str("[] instanceof Array"), njs_str("true") }, @@ -8651,7 +8686,7 @@ static njs_unit_test_t njs_test[] = njs_str("TypeError: object is not a function") }, { njs_str("var ex; try {({}) instanceof this} catch (e) {ex = e}; ex"), - njs_str("TypeError: right argument is not a function") }, + njs_str("TypeError: right argument is not callable") }, { njs_str("Function.call(this, 'var x / = 1;')"), njs_str("InternalError: Not implemented") }, -- 2.47.3