[njs] Making "prototype" property of function instances writable.

Dmitry Volyntsev xeioex at nginx.com
Thu Aug 22 15:34:36 UTC 2019


details:   https://hg.nginx.org/njs/rev/6923d5ac84bc
branches:  
changeset: 1140:6923d5ac84bc
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Thu Aug 22 18:27:34 2019 +0300
description:
Making "prototype" property of function instances writable.

This closes #40 issue on Github.

diffstat:

 src/njs_function.c       |  93 ++++++++++++++++++++++++++++++++----------------
 src/njs_function.h       |   2 -
 src/njs_vmcode.c         |  60 +++++++++++++-----------------
 src/test/njs_unit_test.c |  39 +++++++++++++++++++-
 4 files changed, 125 insertions(+), 69 deletions(-)

diffs (311 lines):

diff -r 91e3f7345e47 -r 6923d5ac84bc src/njs_function.c
--- a/src/njs_function.c	Tue Aug 20 23:03:26 2019 -0400
+++ b/src/njs_function.c	Thu Aug 22 18:27:34 2019 +0300
@@ -780,6 +780,42 @@ njs_function_frame_free(njs_vm_t *vm, nj
 }
 
 
+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_function_property_prototype_create(vm, &function->object.hash,
+                                                   setval);
+    if (njs_slow_path(proto == NULL)) {
+        return NJS_ERROR;
     }
 
-    proto = njs_property_prototype_create(vm, &function->object.hash,
-                                          prototype);
-    if (njs_slow_path(proto == NULL)) {
-        return NULL;
+    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;
+        }
     }
 
-    cons = njs_property_constructor_create(vm, &prototype->hash, value);
+    *retval = *proto;
 
-    if (njs_fast_path(cons != NULL)) {
-        return proto;
-    }
-
-    return NULL;
+    return NJS_OK;
 }
 
 
@@ -1197,6 +1227,7 @@ const njs_object_prop_t  njs_function_in
         .type = NJS_PROPERTY_HANDLER,
         .name = njs_string("prototype"),
         .value = njs_prop_handler(njs_function_prototype_create),
+        .writable = 1
     },
 };
 
diff -r 91e3f7345e47 -r 6923d5ac84bc src/njs_function.h
--- a/src/njs_function.h	Tue Aug 20 23:03:26 2019 -0400
+++ b/src/njs_function.h	Thu Aug 22 18:27:34 2019 +0300
@@ -118,8 +118,6 @@ njs_int_t njs_function_rest_parameters_i
     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 -r 91e3f7345e47 -r 6923d5ac84bc src/njs_vmcode.c
--- a/src/njs_vmcode.c	Tue Aug 20 23:03:26 2019 -0400
+++ b/src/njs_vmcode.c	Thu Aug 22 18:27:34 2019 +0300
@@ -70,7 +70,8 @@ static njs_jump_off_t njs_primitive_valu
 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
     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
 
     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, 
 
 
 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;
+    njs_value_t     proto;
+    njs_object_t    *object;
+    njs_jump_off_t  ret;
+
+    const njs_value_t prototype_string = njs_string("prototype");
 
     object = njs_object_alloc(vm);
-
-    if (njs_fast_path(object != NULL)) {
+    if (njs_slow_path(object == NULL)) {
+        return NULL;
+    }
 
-        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);
+    ret = njs_value_property(vm, constructor, njs_value_arg(&prototype_string),
+                             &proto);
 
-        if (ret == NJS_OK) {
-            prop = lhq.value;
-            proto = &prop->value;
-
-        } 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 -r 91e3f7345e47 -r 6923d5ac84bc src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Tue Aug 20 23:03:26 2019 -0400
+++ b/src/test/njs_unit_test.c	Thu Aug 22 18:27:34 2019 +0300
@@ -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") },


More information about the nginx-devel mailing list