From 352cfb3ea3c8d15c64707b045f251d1f4f3ae5da Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Sat, 5 Jun 2021 11:58:00 +0000 Subject: [PATCH] Implemented RegExp getters according to the specification. --- src/njs_regexp.c | 261 ++++++++++++++++++++++++++++++++------- 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, 231 insertions(+), 51 deletions(-) diff --git a/src/njs_regexp.c b/src/njs_regexp.c index c96a53ac..9143ac6f 100644 --- a/src/njs_regexp.c +++ b/src/njs_regexp.c @@ -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_value_t *args, njs_uint_t nargs, 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 *vm, njs_object_prop_t *unused, 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]; - pattern = njs_regexp_pattern(value); - *retval = pattern->global ? njs_value_true : njs_value_false; - njs_release(vm, value); + 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"); - return NJS_OK; -} + 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]; -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_pattern_t *pattern; + 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->ignore_case ? njs_value_true : njs_value_false; - njs_release(vm, value); + if (njs_bool(&value)) { + *p++ = 'g'; + } - return NJS_OK; + ret = njs_value_property(vm, this, njs_value_arg(&string_ignore_case), + &value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + + 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_multiline(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->multiline ? 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_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; } - njs_type_error(vm, "\"this\" argument is not a regexp"); + ret = njs_value_property(vm, r, njs_value_arg(&string_source), + &source); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } - return NJS_ERROR; + ret = njs_value_to_string(vm, &source, &source); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + 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("//"); + + 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_value_t *retval, 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_regexp_prototype_properties[] = }, { - .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_type_init = { .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 --git a/src/njs_regexp_pattern.h b/src/njs_regexp_pattern.h index 95716555..5681453b 100644 --- a/src/njs_regexp_pattern.h +++ b/src/njs_regexp_pattern.h @@ -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 --git a/src/njs_value.c b/src/njs_value.c index 62cd7320..9afd9442 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -30,6 +30,8 @@ const njs_value_t njs_value_nan = njs_value(NJS_NUMBER, 0, 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 --git a/src/njs_value.h b/src/njs_value.h index b3049d2f..0cf793df 100644 --- a/src/njs_value.h +++ b/src/njs_value.h @@ -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 --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index f788fd43..2afd5db1 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -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("/(?:)/") }, -- 2.47.3