Repository: nginx/njs (c)

Issue:
Failed to run v8-v7 test
### Describe the bug

[run.zip](https://github.com/user-attachments/files/20789860/run.zip)

One of the regular expressions in the v8-v7 tests doesn't seem to parse correctly

```
  var re37 = /\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g;
```

```


Thrown:
SyntaxError: pcre_compile2("\s*([+>~\s])\s*([a-zA-Z#.*:\(?!))") failed: missing terminating ] for character class at "" in ./a.js:1
```


- [x] The bug is reproducible with the latest version of njs.
- [x] I minimized the code and NGINX configuration to the smallest
possible to reproduce the issue.

### To reproduce

Steps to reproduce the behavior:

- JS script
```js
  var re37 = /\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g;
```
 
### Expected behavior

A clear and concise description of what you expected to happen.

### Your environment

- Version of njs or specific commit
```
 ./build/njs -v
0.9.1
```
- OS: [e.g. Ubuntu 20.04]
Ubuntu 2504
 



Discussion:

Patch:
diff --git a/external/njs_regex.c b/external/njs_regex.c
--- a/external/njs_regex.c
+++ b/external/njs_regex.c
@@ -114,6 +114,11 @@ njs_regex_escape(njs_mp_t *mp, njs_str_t *text)
 
     for (p = start; p < end; p++) {
         switch (*p) {
+        case '\\':
+            p += 1;
+
+            break;
+
         case '[':
             if (p + 1 < end && p[1] == ']') {
                 p += 1;
@@ -122,6 +127,11 @@ njs_regex_escape(njs_mp_t *mp, njs_str_t *text)
             } else if (p + 2 < end && p[1] == '^' && p[2] == ']') {
                 p += 2;
                 anychars += 1;
+
+            } else {
+                while (p < end && *p != ']') {
+                    p += 1;
+                }
             }
 
             break;
@@ -146,6 +156,15 @@ njs_regex_escape(njs_mp_t *mp, njs_str_t *text)
     for (p = start; p < end; p++) {
 
         switch (*p) {
+        case '\\':
+            *dst++ = *p;
+            if (p + 1 < end) {
+                p += 1;
+                *dst++ = *p;
+            }
+
+            continue;
+
         case '[':
             if (p + 1 < end && p[1] == ']') {
                 p += 1;
@@ -156,6 +175,14 @@ njs_regex_escape(njs_mp_t *mp, njs_str_t *text)
                 p += 2;
                 dst = njs_cpymem(dst, "[\\s\\S]", 6);
                 continue;
+
+            } else {
+                *dst++ = *p;
+                while (p < end && *p != ']') {
+                    *dst++ = *p++;
+                }
+
+                continue;
             }
         }
 
diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c
--- a/nginx/ngx_js.c
+++ b/nginx/ngx_js.c
@@ -978,6 +978,11 @@ ngx_qjs_clone(ngx_js_ctx_t *ctx, ngx_js_loc_conf_t *cf, void *external)
                           "js load module exception: %V", &exception);
             goto destroy;
         }
+
+        if (i != length - 1) {
+            /* JS_EvalFunction() does JS_FreeValue(cx, rv) for the last rv. */
+            JS_FreeValue(cx, rv);
+        }
     }
 
     if (JS_ResolveModule(cx, rv) < 0) {


Test patch:
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -11969,6 +11969,48 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("/[]a/.test('a')"),
       njs_str("false") },
 
+    { njs_str("/[#[]/.test('[')"),
+      njs_str("true") },
+
+    { njs_str("/[\\s[]/.test('[')"),
+      njs_str("true") },
+
+    { njs_str("/[#[^]/.test('[')"),
+      njs_str("true") },
+
+    { njs_str("/[#\\[]/.test('[')"),
+      njs_str("true") },
+
+    { njs_str("/[\\[^]/.test('[')"),
+      njs_str("true") },
+
+    { njs_str("/[^]abc]/.test('#abc]')"),
+      njs_str("true") },
+
+    { njs_str("/[[^]abc]/.test('[abc]')"),
+      njs_str("true") },
+
+    { njs_str("/[[^]abc]/.test('^abc]')"),
+      njs_str("true") },
+
+    { njs_str("/[]/.test('[]')"),
+      njs_str("false") },
+
+    { njs_str("/[[]/.test('[')"),
+      njs_str("true") },
+
+    { njs_str("/\\[]/.test('[]')"),
+      njs_str("true") },
+
+    { njs_str("/[]abc]/.test('abc]')"),
+      njs_str("false") },
+
+    { njs_str("/abc]/.test('abc]')"),
+      njs_str("true") },
+
+    { njs_str("/\\\\\\[]/.test('\\\\[]')"),
+      njs_str("true") },
+
 #ifdef NJS_HAVE_PCRE2
     { njs_str("/[]*a/.test('a')"),
       njs_str("true") },