[njs] Implemented RegExp getters according to the specification.

Dmitry Volyntsev xeioex at nginx.com
Mon Jun 7 19:15:39 UTC 2021


details:   https://hg.nginx.org/njs/rev/02444445df29
branches:  
changeset: 1650:02444445df29
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Sat Jun 05 11:58:00 2021 +0000
description:
Implemented RegExp getters according to the specification.

diffstat:

 src/njs_regexp.c         |  269 ++++++++++++++++++++++++++++++++++++++--------
 src/njs_regexp_pattern.h |    5 +-
 src/njs_value.c          |    2 +
 src/njs_value.h          |    1 +
 src/test/njs_unit_test.c |   13 +-
 5 files changed, 235 insertions(+), 55 deletions(-)

diffs (460 lines):

diff -r 687d9eacbe33 -r 02444445df29 src/njs_regexp.c
--- a/src/njs_regexp.c	Wed Jun 02 14:10:03 2021 +0000
+++ b/src/njs_regexp.c	Sat Jun 05 11:58:00 2021 +0000
@@ -17,9 +17,8 @@ struct njs_regexp_group_s {
 
 static void *njs_regexp_malloc(size_t size, void *memory_data);
 static void njs_regexp_free(void *p, void *memory_data);
-static njs_int_t njs_regexp_prototype_source(njs_vm_t *vm,
-    njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
-    njs_value_t *retval);
+static njs_int_t njs_regexp_prototype_source(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused);
 static int njs_regexp_pattern_compile(njs_vm_t *vm, njs_regex_t *regex,
     u_char *source, int options);
 static u_char *njs_regexp_compile_trace_handler(njs_trace_t *trace,
@@ -108,11 +107,13 @@ njs_regexp_constructor(njs_vm_t *vm, njs
     pattern = njs_arg(args, nargs, 1);
 
     if (njs_is_regexp(pattern)) {
-        ret = njs_regexp_prototype_source(vm, NULL, pattern, NULL, &source);
+        ret = njs_regexp_prototype_source(vm, pattern, 1, 0);
         if (njs_slow_path(ret != NJS_OK)) {
             return ret;
         }
 
+        source = vm->retval;
+
         re_flags = njs_regexp_value_flags(vm, pattern);
 
         pattern = &source;
@@ -634,64 +635,141 @@ njs_regexp_prototype_last_index(njs_vm_t
 
 
 static njs_int_t
-njs_regexp_prototype_global(njs_vm_t *vm, njs_object_prop_t *prop,
-    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
+njs_regexp_prototype_flags(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused)
 {
-    njs_regexp_pattern_t  *pattern;
+    u_char       *p;
+    njs_int_t    ret;
+    njs_value_t  *this, value;
+    u_char       dst[3];
+
+    static const njs_value_t  string_global = njs_string("global");
+    static const njs_value_t  string_ignore_case = njs_string("ignoreCase");
+    static const njs_value_t  string_multiline = njs_string("multiline");
+
+    this = njs_argument(args, 0);
+    if (njs_slow_path(!njs_is_object(this))) {
+        njs_type_error(vm, "\"this\" argument is not an object");
+        return NJS_ERROR;
+    }
+
+    p = &dst[0];
+
+    ret = njs_value_property(vm, this, njs_value_arg(&string_global),
+                             &value);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NJS_ERROR;
+    }
 
-    pattern = njs_regexp_pattern(value);
-    *retval = pattern->global ? njs_value_true : njs_value_false;
-    njs_release(vm, value);
+    if (njs_bool(&value)) {
+        *p++ = 'g';
+    }
+
+    ret = njs_value_property(vm, this, njs_value_arg(&string_ignore_case),
+                             &value);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NJS_ERROR;
+    }
 
-    return NJS_OK;
+    if (njs_bool(&value)) {
+        *p++ = 'i';
+    }
+
+    ret = njs_value_property(vm, this, njs_value_arg(&string_multiline),
+                             &value);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NJS_ERROR;
+    }
+
+    if (njs_bool(&value)) {
+        *p++ = 'm';
+    }
+
+    return njs_string_new(vm, &vm->retval, dst, p - dst, p - dst);
 }
 
 
 static njs_int_t
-njs_regexp_prototype_ignore_case(njs_vm_t *vm, njs_object_prop_t *prop,
-    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
+njs_regexp_prototype_flag(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t flag)
 {
+    unsigned              yn;
+    njs_value_t           *this;
     njs_regexp_pattern_t  *pattern;
 
-    pattern = njs_regexp_pattern(value);
-    *retval = pattern->ignore_case ? njs_value_true : njs_value_false;
-    njs_release(vm, value);
+    this = njs_argument(args, 0);
+    if (njs_slow_path(!njs_is_object(this))) {
+        njs_type_error(vm, "\"this\" argument is not an object");
+        return NJS_ERROR;
+    }
+
+    if (njs_slow_path(!njs_is_regexp(this))) {
+        if (njs_object(this) == &vm->prototypes[NJS_OBJ_TYPE_REGEXP].object) {
+            njs_set_undefined(&vm->retval);
+            return NJS_OK;
+        }
+
+        njs_type_error(vm, "\"this\" argument is not a regexp");
+        return NJS_ERROR;
+    }
+
+    pattern = njs_regexp_pattern(this);
+
+    switch (flag) {
+    case NJS_REGEXP_GLOBAL:
+        yn = pattern->global;
+        break;
+
+    case NJS_REGEXP_IGNORE_CASE:
+        yn = pattern->ignore_case;
+        break;
+
+    case NJS_REGEXP_MULTILINE:
+    default:
+        yn = pattern->multiline;
+        break;
+    }
+
+    njs_set_boolean(&vm->retval, yn);
 
     return NJS_OK;
 }
 
 
 static njs_int_t
-njs_regexp_prototype_multiline(njs_vm_t *vm, njs_object_prop_t *prop,
-    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
-{
-    njs_regexp_pattern_t  *pattern;
-
-    pattern = njs_regexp_pattern(value);
-    *retval = pattern->multiline ? njs_value_true : njs_value_false;
-    njs_release(vm, value);
-
-    return NJS_OK;
-}
-
-
-static njs_int_t
-njs_regexp_prototype_source(njs_vm_t *vm, njs_object_prop_t *prop,
-    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
+njs_regexp_prototype_source(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused)
 {
     u_char                *source;
     int32_t               length;
     uint32_t              size;
+    njs_value_t           *this;
     njs_regexp_pattern_t  *pattern;
 
-    pattern = njs_regexp_pattern(value);
+    this = njs_argument(args, 0);
+    if (njs_slow_path(!njs_is_object(this))) {
+        njs_type_error(vm, "\"this\" argument is not an object");
+        return NJS_ERROR;
+    }
+
+    if (njs_slow_path(!njs_is_regexp(this))) {
+        if (njs_object(this) == &vm->prototypes[NJS_OBJ_TYPE_REGEXP].object) {
+            vm->retval = njs_string_empty_regexp;
+            return NJS_OK;
+        }
+
+        njs_type_error(vm, "\"this\" argument is not a regexp");
+        return NJS_ERROR;
+    }
+
+    pattern = njs_regexp_pattern(this);
     /* Skip starting "/". */
     source = pattern->source + 1;
 
     size = njs_strlen(source) - pattern->flags;
     length = njs_utf8_length(source, size);
 
-    return njs_regexp_string_create(vm, retval, source, size, length);
+    return njs_regexp_string_create(vm, &vm->retval, source, size, length);
 }
 
 
@@ -699,13 +777,67 @@ static njs_int_t
 njs_regexp_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
     njs_uint_t nargs, njs_index_t unused)
 {
-    if (njs_is_regexp(njs_arg(args, nargs, 0))) {
-        return njs_regexp_to_string(vm, &vm->retval, &args[0]);
+    u_char             *p;
+    size_t             size, length;
+    njs_int_t          ret;
+    njs_value_t        *r, source, flags;
+    njs_string_prop_t  source_string, flags_string;
+
+    static const njs_value_t  string_source = njs_string("source");
+    static const njs_value_t  string_flags = njs_string("flags");
+
+    r = njs_argument(args, 0);
+
+    if (njs_slow_path(!njs_is_object(r))) {
+        njs_type_error(vm, "\"this\" argument is not an object");
+        return NJS_ERROR;
+    }
+
+    ret = njs_value_property(vm, r, njs_value_arg(&string_source),
+                             &source);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NJS_ERROR;
+    }
+
+    ret = njs_value_to_string(vm, &source, &source);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
     }
 
-    njs_type_error(vm, "\"this\" argument is not a regexp");
+    ret = njs_value_property(vm, r, njs_value_arg(&string_flags),
+                             &flags);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NJS_ERROR;
+    }
+
+    ret = njs_value_to_string(vm, &flags, &flags);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return NJS_ERROR;
+    }
+
+    (void) njs_string_prop(&source_string, &source);
+    (void) njs_string_prop(&flags_string, &flags);
+
+    size = source_string.size + flags_string.size + njs_length("//");
+    length = source_string.length + flags_string.length + njs_length("//");
 
-    return NJS_ERROR;
+    if (njs_is_byte_string(&source_string)
+        || njs_is_byte_string(&flags_string))
+    {
+        length = 0;
+    }
+
+    p = njs_string_alloc(vm, &vm->retval, size, length);
+    if (njs_slow_path(p == NULL)) {
+        return NJS_ERROR;
+    }
+
+    *p++ = '/';
+    p = njs_cpymem(p, source_string.start, source_string.size);
+    *p++ = '/';
+    memcpy(p, flags_string.start, flags_string.size);
+
+    return NJS_OK;
 }
 
 
@@ -713,7 +845,7 @@ njs_int_t
 njs_regexp_to_string(njs_vm_t *vm, njs_value_t *retval,
     const njs_value_t *value)
 {
-    u_char                *source;
+    u_char                *p, *source;
     int32_t               length;
     uint32_t              size;
     njs_regexp_pattern_t  *pattern;
@@ -724,7 +856,16 @@ njs_regexp_to_string(njs_vm_t *vm, njs_v
     size = njs_strlen(source);
     length = njs_utf8_length(source, size);
 
-    return njs_regexp_string_create(vm, retval, source, size, length);
+    length = (length >= 0) ? length: 0;
+
+    p = njs_string_alloc(vm, retval, size, length);
+    if (njs_slow_path(p == NULL)) {
+        return NJS_ERROR;
+    }
+
+    p = njs_cpymem(p, source, size);
+
+    return NJS_OK;
 }
 
 
@@ -1522,31 +1663,61 @@ static const njs_object_prop_t  njs_rege
     },
 
     {
-        .type = NJS_PROPERTY_HANDLER,
+        .type = NJS_PROPERTY,
+        .name = njs_string("flags"),
+        .value = njs_value(NJS_INVALID, 1, NAN),
+        .getter = njs_native_function(njs_regexp_prototype_flags, 0),
+        .setter = njs_value(NJS_UNDEFINED, 0, NAN),
+        .writable = NJS_ATTRIBUTE_UNSET,
+        .configurable = 1,
+        .enumerable = 0,
+    },
+
+    {
+        .type = NJS_PROPERTY,
         .name = njs_string("global"),
-        .value = njs_prop_handler(njs_regexp_prototype_global),
+        .value = njs_value(NJS_INVALID, 1, NAN),
+        .getter = njs_native_function2(njs_regexp_prototype_flag, 0,
+                                       NJS_REGEXP_GLOBAL),
+        .setter = njs_value(NJS_UNDEFINED, 0, NAN),
+        .writable = NJS_ATTRIBUTE_UNSET,
         .configurable = 1,
+        .enumerable = 0,
     },
 
     {
-        .type = NJS_PROPERTY_HANDLER,
+        .type = NJS_PROPERTY,
         .name = njs_string("ignoreCase"),
-        .value = njs_prop_handler(njs_regexp_prototype_ignore_case),
+        .value = njs_value(NJS_INVALID, 1, NAN),
+        .getter = njs_native_function2(njs_regexp_prototype_flag, 0,
+                                       NJS_REGEXP_IGNORE_CASE),
+        .setter = njs_value(NJS_UNDEFINED, 0, NAN),
+        .writable = NJS_ATTRIBUTE_UNSET,
         .configurable = 1,
+        .enumerable = 0,
     },
 
     {
-        .type = NJS_PROPERTY_HANDLER,
+        .type = NJS_PROPERTY,
         .name = njs_string("multiline"),
-        .value = njs_prop_handler(njs_regexp_prototype_multiline),
+        .value = njs_value(NJS_INVALID, 1, NAN),
+        .getter = njs_native_function2(njs_regexp_prototype_flag, 0,
+                                       NJS_REGEXP_MULTILINE),
+        .setter = njs_value(NJS_UNDEFINED, 0, NAN),
+        .writable = NJS_ATTRIBUTE_UNSET,
         .configurable = 1,
+        .enumerable = 0,
     },
 
     {
-        .type = NJS_PROPERTY_HANDLER,
+        .type = NJS_PROPERTY,
         .name = njs_string("source"),
-        .value = njs_prop_handler(njs_regexp_prototype_source),
+        .value = njs_value(NJS_INVALID, 1, NAN),
+        .getter = njs_native_function(njs_regexp_prototype_source, 0),
+        .setter = njs_value(NJS_UNDEFINED, 0, NAN),
+        .writable = NJS_ATTRIBUTE_UNSET,
         .configurable = 1,
+        .enumerable = 0,
     },
 
     {
@@ -1610,5 +1781,5 @@ const njs_object_type_init_t  njs_regexp
    .constructor = njs_native_ctor(njs_regexp_constructor, 2, 0),
    .constructor_props = &njs_regexp_constructor_init,
    .prototype_props = &njs_regexp_prototype_init,
-   .prototype_value = { .object = { .type = NJS_REGEXP } },
+   .prototype_value = { .object = { .type = NJS_OBJECT } },
 };
diff -r 687d9eacbe33 -r 02444445df29 src/njs_regexp_pattern.h
--- a/src/njs_regexp_pattern.h	Wed Jun 02 14:10:03 2021 +0000
+++ b/src/njs_regexp_pattern.h	Sat Jun 05 11:58:00 2021 +0000
@@ -21,8 +21,9 @@ struct njs_regexp_pattern_s {
     njs_regex_t           regex[2];
 
     /*
-     * A pattern source is used by RegExp.toString() method and
-     * RegExp.source property.  So it is is stored in form "/pattern/flags"
+     * A pattern source is used by RegExp.prototype.toString() method and
+     * RegExp.prototype.source and RegExp.prototype.flags accessor properties.
+     * So it is is stored in form "/pattern/flags"
      * and as zero-terminated C string but not as value, because retrieving
      * it is very seldom operation.  To get just a pattern string for
      * RegExp.source property a length of flags part "/flags" is stored
diff -r 687d9eacbe33 -r 02444445df29 src/njs_value.c
--- a/src/njs_value.c	Wed Jun 02 14:10:03 2021 +0000
+++ b/src/njs_value.c	Sat Jun 05 11:58:00 2021 +0000
@@ -30,6 +30,8 @@ const njs_value_t  njs_value_nan =      
 const njs_value_t  njs_value_invalid =      njs_value(NJS_INVALID, 0, 0.0);
 
 const njs_value_t  njs_string_empty =       njs_string("");
+const njs_value_t  njs_string_empty_regexp =
+                                            njs_string("(?:)");
 const njs_value_t  njs_string_comma =       njs_string(",");
 const njs_value_t  njs_string_null =        njs_string("null");
 const njs_value_t  njs_string_undefined =   njs_string("undefined");
diff -r 687d9eacbe33 -r 02444445df29 src/njs_value.h
--- a/src/njs_value.h	Wed Jun 02 14:10:03 2021 +0000
+++ b/src/njs_value.h	Sat Jun 05 11:58:00 2021 +0000
@@ -802,6 +802,7 @@ extern const njs_value_t  njs_value_nan;
 extern const njs_value_t  njs_value_invalid;
 
 extern const njs_value_t  njs_string_empty;
+extern const njs_value_t  njs_string_empty_regexp;
 extern const njs_value_t  njs_string_comma;
 extern const njs_value_t  njs_string_null;
 extern const njs_value_t  njs_string_undefined;
diff -r 687d9eacbe33 -r 02444445df29 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Wed Jun 02 14:10:03 2021 +0000
+++ b/src/test/njs_unit_test.c	Sat Jun 05 11:58:00 2021 +0000
@@ -10846,6 +10846,11 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("var r = (/^.+$/mg); [r.global, r.multiline, r.ignoreCase]"),
       njs_str("true,true,false") },
 
+    { njs_str("['global', 'ignoreCase', 'multiline']"
+              ".map(v => Object.getOwnPropertyDescriptor(RegExp.prototype, v))"
+              ".every(desc => (typeof desc.get === 'function' && typeof desc.set === 'undefined'))"),
+      njs_str("true") },
+
     { njs_str("var r = /./; r"),
       njs_str("/./") },
 
@@ -10855,8 +10860,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("var r = new RegExp('.'); r"),
       njs_str("/./") },
 
-    { njs_str("var r = new RegExp('.', 'ig'); r"),
-      njs_str("/./gi") },
+    { njs_str("var r = new RegExp('.', 'igm'); r"),
+      njs_str("/./gim") },
 
     { njs_str("var r = new RegExp('abc'); r.test('00abc11')"),
       njs_str("true") },
@@ -10922,7 +10927,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("SyntaxError: pcre_compile(\"\\\") failed: \\ at end of pattern") },
 
     { njs_str("[0].map(RegExp().toString)"),
-      njs_str("TypeError: \"this\" argument is not a regexp") },
+      njs_str("TypeError: \"this\" argument is not an object") },
 
     /* Non-standard ECMA-262 features. */
 
@@ -12986,7 +12991,7 @@ static njs_unit_test_t  njs_test[] =
       njs_str("true") },
 
     { njs_str("Object.prototype.toString.call(RegExp.prototype)"),
-      njs_str("[object RegExp]") },
+      njs_str("[object Object]") },
 
     { njs_str("RegExp.prototype"),
       njs_str("/(?:)/") },


More information about the nginx-devel mailing list