]> git.kaiwu.me - njs.git/commitdiff
Fixed exit instructions inside try blocks.
authorDmitry Volyntsev <xeioex@nginx.com>
Wed, 26 Dec 2018 10:23:16 +0000 (13:23 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Wed, 26 Dec 2018 10:23:16 +0000 (13:23 +0300)
Fixed instructions: continue, break, return.

njs/njs_disassembler.c
njs/njs_generator.c
njs/njs_vm.c
njs/njs_vm.h
njs/test/njs_expect_test.exp
njs/test/njs_unit_test.c

index 2097bdd9feda2eb564db185595f57972a8d3bc51..da16443a34b2bce225e4153e6f38e089ff0d6552 100644 (file)
@@ -125,8 +125,6 @@ static njs_code_name_t  code_names[] = {
 
     { njs_vmcode_throw, sizeof(njs_vmcode_throw_t),
           nxt_string("THROW           ") },
-    { njs_vmcode_finally, sizeof(njs_vmcode_finally_t),
-          nxt_string("FINALLY         ") },
 
 };
 
@@ -162,15 +160,18 @@ njs_disassemble(u_char *start, u_char *end)
     njs_vmcode_3addr_t           *code3;
     njs_vmcode_array_t           *array;
     njs_vmcode_catch_t           *catch;
+    njs_vmcode_finally_t         *finally;
     njs_vmcode_try_end_t         *try_end;
     njs_vmcode_try_start_t       *try_start;
     njs_vmcode_operation_t       operation;
     njs_vmcode_cond_jump_t       *cond_jump;
     njs_vmcode_test_jump_t       *test_jump;
     njs_vmcode_prop_next_t       *prop_next;
+    njs_vmcode_try_return_t      *try_return;
     njs_vmcode_equal_jump_t      *equal;
     njs_vmcode_prop_foreach_t    *prop_foreach;
     njs_vmcode_method_frame_t    *method;
+    njs_vmcode_try_trampoline_t  *try_tramp;
     njs_vmcode_function_frame_t  *function;
 
     p = start;
@@ -323,8 +324,9 @@ njs_disassemble(u_char *start, u_char *end)
         if (operation == njs_vmcode_try_start) {
             try_start = (njs_vmcode_try_start_t *) p;
 
-            printf("%05zd TRY START         %04zX +%zd\n",
-                   p - start, (size_t) try_start->value,
+            printf("%05zd TRY START         %04zX %04zX +%zd\n",
+                   p - start, (size_t) try_start->exception_value,
+                   (size_t) try_start->exit_value,
                    (size_t) try_start->offset);
 
             p += sizeof(njs_vmcode_try_start_t);
@@ -332,6 +334,43 @@ njs_disassemble(u_char *start, u_char *end)
             continue;
         }
 
+        if (operation == njs_vmcode_try_break) {
+            try_tramp = (njs_vmcode_try_trampoline_t *) p;
+
+            printf("%05zd TRY BREAK         %04zX %zd\n",
+                   p - start, (size_t) try_tramp->exit_value,
+                   (size_t) try_tramp->offset);
+
+            p += sizeof(njs_vmcode_try_trampoline_t);
+
+            continue;
+        }
+
+        if (operation == njs_vmcode_try_continue) {
+            try_tramp = (njs_vmcode_try_trampoline_t *) p;
+
+            printf("%05zd TRY CONTINUE      %04zX %zd\n",
+                   p - start, (size_t) try_tramp->exit_value,
+                   (size_t) try_tramp->offset);
+
+            p += sizeof(njs_vmcode_try_trampoline_t);
+
+            continue;
+        }
+
+        if (operation == njs_vmcode_try_return) {
+            try_return = (njs_vmcode_try_return_t *) p;
+
+            printf("%05zd TRY RETURN        %04zX %04zX +%zd\n",
+                   p - start, (size_t) try_return->save,
+                   (size_t) try_return->retval,
+                   (size_t) try_return->offset);
+
+            p += sizeof(njs_vmcode_try_return_t);
+
+            continue;
+        }
+
         if (operation == njs_vmcode_catch) {
             catch = (njs_vmcode_catch_t *) p;
 
@@ -355,6 +394,20 @@ njs_disassemble(u_char *start, u_char *end)
             continue;
         }
 
+        if (operation == njs_vmcode_finally) {
+            finally = (njs_vmcode_finally_t *) p;
+
+            printf("%05zd TRY FINALLY       %04zX %04zX +%zd +%zd\n",
+                   p - start, (size_t) finally->retval,
+                   (size_t) finally->exit_value,
+                   (size_t) finally->continue_offset,
+                   (size_t) finally->break_offset);
+
+            p += sizeof(njs_vmcode_finally_t);
+
+            continue;
+        }
+
         code_name = code_names;
         n = nxt_nitems(code_names);
 
index f2678f244e8d2c856237c518a3602603b0f8eaa1..6ca1e1166a474b1de45c959812243d41476da7ab 100644 (file)
@@ -20,13 +20,24 @@ struct njs_generator_patch_s {
      */
     njs_ret_t                       jump_offset;
     njs_generator_patch_t           *next;
+    /*
+     * index_offset is used for patching vmcode_try_return instruction
+     * inside try blocks.
+     */
+    njs_index_t                     index_offset;
 };
 
 
 typedef enum {
-    NJS_GENERATOR_BLOCK = 0,
-    NJS_GENERATOR_LOOP,
-    NJS_GENERATOR_SWITCH,
+    NJS_GENERATOR_BLOCK = 1,
+    NJS_GENERATOR_LOOP = 2,
+    NJS_GENERATOR_SWITCH = 4,
+    NJS_GENERATOR_TRY = 8,
+
+#define NJS_GENERATOR_ALL          (NJS_GENERATOR_BLOCK                      \
+                                    | NJS_GENERATOR_LOOP                     \
+                                    | NJS_GENERATOR_SWITCH                   \
+                                    | NJS_GENERATOR_TRY)
 } njs_generator_block_type_t;
 
 
@@ -70,8 +81,16 @@ static nxt_int_t njs_generate_for_in_statement(njs_vm_t *vm,
 static nxt_noinline nxt_int_t njs_generate_start_block(njs_vm_t *vm,
     njs_generator_t *generator, njs_generator_block_type_t type,
     const nxt_str_t *label);
-static nxt_noinline void njs_generate_patch_loop_continuation(njs_vm_t *vm,
-    njs_generator_t *generator);
+static njs_generator_block_t *njs_generate_find_block(
+    njs_generator_block_t *block, uint32_t mask);
+static nxt_int_t njs_generate_make_continuation_patch(njs_vm_t *vm,
+    njs_generator_t *generator, njs_generator_block_t *block, njs_ret_t offset);
+static nxt_noinline void njs_generate_patch_block(njs_vm_t *vm,
+    njs_generator_t *generator, njs_generator_patch_t *list);
+static nxt_noinline void njs_generate_patch_try_exit_block(njs_vm_t *vm,
+    njs_generator_t *generator, njs_generator_patch_t *list, njs_index_t dest);
+static nxt_int_t njs_generate_make_exit_patch(njs_vm_t *vm,
+    njs_generator_t *generator, njs_generator_block_t *block, njs_ret_t offset);
 static nxt_noinline void njs_generate_patch_block_exit(njs_vm_t *vm,
     njs_generator_t *generator);
 static nxt_int_t njs_generate_continue_statement(njs_vm_t *vm,
@@ -866,6 +885,7 @@ njs_generate_switch_statement(njs_vm_t *vm, njs_generator_t *generator,
                 return NXT_ERROR;
             }
 
+            patch->index_offset = 0;
             patch->jump_offset = njs_code_offset(generator, equal)
                                  + offsetof(njs_vmcode_equal_jump_t, offset);
 
@@ -966,7 +986,7 @@ njs_generate_while_statement(njs_vm_t *vm, njs_generator_t *generator,
 
     /* The loop condition. */
 
-    njs_generate_patch_loop_continuation(vm, generator);
+    njs_generate_patch_block(vm, generator, generator->block->continuation);
 
     njs_code_set_jump_offset(generator, njs_vmcode_jump_t, jump_offset);
 
@@ -1016,7 +1036,7 @@ njs_generate_do_while_statement(njs_vm_t *vm, njs_generator_t *generator,
 
     /* The loop condition. */
 
-    njs_generate_patch_loop_continuation(vm, generator);
+    njs_generate_patch_block(vm, generator, generator->block->continuation);
 
     condition = node->right;
 
@@ -1100,7 +1120,7 @@ njs_generate_for_statement(njs_vm_t *vm, njs_generator_t *generator,
 
     /* The loop update. */
 
-    njs_generate_patch_loop_continuation(vm, generator);
+    njs_generate_patch_block(vm, generator, generator->block->continuation);
 
     update = node->right;
 
@@ -1199,7 +1219,7 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator,
 
     /* The loop iterator. */
 
-    njs_generate_patch_loop_continuation(vm, generator);
+    njs_generate_patch_block(vm, generator, generator->block->continuation);
 
     njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t, prop_offset);
 
@@ -1258,15 +1278,51 @@ njs_generate_start_block(njs_vm_t *vm, njs_generator_t *generator,
 }
 
 
+static njs_generator_block_t *
+njs_generate_find_block(njs_generator_block_t *block, uint32_t mask)
+{
+    while (block != NULL) {
+        if (block->type & mask) {
+            return block;
+        }
+
+        block = block->next;
+    }
+
+    return NULL;
+}
+
+
+static nxt_int_t
+njs_generate_make_continuation_patch(njs_vm_t *vm, njs_generator_t *generator,
+    njs_generator_block_t *block, njs_ret_t offset)
+{
+    njs_generator_patch_t  *patch;
+
+    patch = nxt_mem_cache_alloc(vm->mem_cache_pool,
+                                sizeof(njs_generator_patch_t));
+    if (nxt_slow_path(patch == NULL)) {
+        njs_memory_error(vm);
+        return NXT_ERROR;
+    }
+
+    patch->next = block->continuation;
+    block->continuation = patch;
+
+    patch->index_offset = 0;
+    patch->jump_offset = offset;
+
+    return NXT_OK;
+}
+
+
 static nxt_noinline void
-njs_generate_patch_loop_continuation(njs_vm_t *vm, njs_generator_t *generator)
+njs_generate_patch_block(njs_vm_t *vm, njs_generator_t *generator,
+    njs_generator_patch_t *list)
 {
-    njs_generator_block_t  *block;
     njs_generator_patch_t  *patch, *next;
 
-    block = generator->block;
-
-    for (patch = block->continuation; patch != NULL; patch = next) {
+    for (patch = list; patch != NULL; patch = next) {
         njs_code_update_offset(generator, patch);
         next = patch->next;
 
@@ -1276,20 +1332,56 @@ njs_generate_patch_loop_continuation(njs_vm_t *vm, njs_generator_t *generator)
 
 
 static nxt_noinline void
-njs_generate_patch_block_exit(njs_vm_t *vm, njs_generator_t *generator)
+njs_generate_patch_try_exit_block(njs_vm_t *vm, njs_generator_t *generator,
+    njs_generator_patch_t *list, njs_index_t dest)
 {
-    njs_generator_block_t  *block;
     njs_generator_patch_t  *patch, *next;
 
-    block = generator->block;
-    generator->block = block->next;
-
-    for (patch = block->exit; patch != NULL; patch = next) {
+    for (patch = list; patch != NULL; patch = next) {
         njs_code_update_offset(generator, patch);
         next = patch->next;
 
+        if (patch->index_offset != 0) {
+            *(njs_code_ptr(generator, njs_index_t, patch->index_offset)) = dest;
+        }
+
         nxt_mem_cache_free(vm->mem_cache_pool, patch);
     }
+}
+
+
+static nxt_int_t
+njs_generate_make_exit_patch(njs_vm_t *vm, njs_generator_t *generator,
+    njs_generator_block_t *block, njs_ret_t offset)
+{
+    njs_generator_patch_t  *patch;
+
+    patch = nxt_mem_cache_alloc(vm->mem_cache_pool,
+                                sizeof(njs_generator_patch_t));
+    if (nxt_slow_path(patch == NULL)) {
+        njs_memory_error(vm);
+        return NXT_ERROR;
+    }
+
+    patch->next = block->exit;
+    block->exit = patch;
+
+    patch->index_offset = 0;
+    patch->jump_offset = offset;
+
+    return NXT_OK;
+}
+
+
+static nxt_noinline void
+njs_generate_patch_block_exit(njs_vm_t *vm, njs_generator_t *generator)
+{
+    njs_generator_block_t  *block;
+
+    block = generator->block;
+    generator->block = block->next;
+
+    njs_generate_patch_block(vm, generator, block->exit);
 
     nxt_mem_cache_free(vm->mem_cache_pool, block);
 }
@@ -1299,22 +1391,22 @@ static nxt_int_t
 njs_generate_continue_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    njs_vmcode_jump_t   *jump;
+    njs_vmcode_jump_t      *jump;
     njs_generator_patch_t  *patch;
     njs_generator_block_t  *block;
 
-    for (block = generator->block; block != NULL; block = block->next) {
-        if (block->type == NJS_GENERATOR_LOOP) {
-            goto found;
-        }
-    }
-
-    njs_generate_syntax_error(vm, node->token_line,
-                              "Illegal continue statement");
+    block = njs_generate_find_block(generator->block,
+                                    NJS_GENERATOR_LOOP | NJS_GENERATOR_TRY);
 
-    return NXT_ERROR;
+    if (nxt_slow_path(block == NULL)) {
+        goto syntax_error;
+    }
 
-found:
+    if (block->type == NJS_GENERATOR_TRY
+        && njs_generate_find_block(block->next, NJS_GENERATOR_LOOP) == NULL)
+    {
+        goto syntax_error;
+    }
 
     /* TODO: LABEL */
 
@@ -1331,11 +1423,19 @@ found:
         jump->code.retval = NJS_VMCODE_NO_RETVAL;
         jump->offset = offsetof(njs_vmcode_jump_t, offset);
 
+        patch->index_offset = 0;
         patch->jump_offset = njs_code_offset(generator, jump)
                              + offsetof(njs_vmcode_jump_t, offset);
     }
 
     return NXT_OK;
+
+syntax_error:
+
+    njs_generate_syntax_error(vm, node->token_line,
+                              "Illegal continue statement");
+
+    return NXT_ERROR;
 }
 
 
@@ -1343,23 +1443,21 @@ static nxt_int_t
 njs_generate_break_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    njs_vmcode_jump_t   *jump;
+    njs_vmcode_jump_t      *jump;
     njs_generator_patch_t  *patch;
     njs_generator_block_t  *block;
 
-    for (block = generator->block; block != NULL; block = block->next) {
-        if (block->type == NJS_GENERATOR_LOOP
-            || block->type == NJS_GENERATOR_SWITCH)
-        {
-            goto found;
-        }
-    }
-
-    njs_generate_syntax_error(vm, node->token_line, "Illegal break statement");
+    block = njs_generate_find_block(generator->block, NJS_GENERATOR_ALL);
 
-    return NXT_ERROR;
+    if (nxt_slow_path(block == NULL)) {
+        goto syntax_error;
+     }
 
-found:
+    if (block->type == NJS_GENERATOR_TRY
+        && njs_generate_find_block(block->next, NJS_GENERATOR_ALL) == NULL)
+    {
+        goto syntax_error;
+    }
 
     /* TODO: LABEL: loop and switch may have label, block must have label. */
 
@@ -1376,11 +1474,18 @@ found:
         jump->code.retval = NJS_VMCODE_NO_RETVAL;
         jump->offset = offsetof(njs_vmcode_jump_t, offset);
 
+        patch->index_offset = 0;
         patch->jump_offset = njs_code_offset(generator, jump)
                              + offsetof(njs_vmcode_jump_t, offset);
     }
 
     return NXT_OK;
+
+syntax_error:
+
+    njs_generate_syntax_error(vm, node->token_line, "Illegal break statement");
+
+    return NXT_ERROR;
 }
 
 
@@ -2356,30 +2461,65 @@ static nxt_int_t
 njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    nxt_int_t            ret;
-    njs_index_t          index;
-    njs_vmcode_return_t  *code;
+    nxt_int_t                ret;
+    njs_index_t              index;
+    njs_vmcode_return_t      *code;
+    njs_generator_patch_t    *patch;
+    njs_generator_block_t    *block;
+    njs_vmcode_try_return_t  *try_return;
 
     ret = njs_generator(vm, generator, node->right);
 
-    if (nxt_fast_path(ret == NXT_OK)) {
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    if (node->right != NULL) {
+        index = node->right->index;
+
+    } else {
+        index = njs_value_index(vm, &njs_value_void,
+                                generator->runtime);
+    }
+
+    block = njs_generate_find_block(generator->block, NJS_GENERATOR_TRY);
+
+    if (nxt_fast_path(block == NULL)) {
         njs_generate_code(generator, njs_vmcode_return_t, code);
         code->code.operation = njs_vmcode_return;
         code->code.operands = NJS_VMCODE_1OPERAND;
         code->code.retval = NJS_VMCODE_NO_RETVAL;
 
-        if (node->right != NULL) {
-            index = node->right->index;
-
-        } else {
-            index = njs_value_index(vm, &njs_value_void, generator->runtime);
-        }
-
         code->retval = index;
         node->index = index;
+
+        return NXT_OK;
     }
 
-    return ret;
+    patch = nxt_mem_cache_alloc(vm->mem_cache_pool,
+                                sizeof(njs_generator_patch_t));
+    if (nxt_slow_path(patch == NULL)) {
+        return NXT_ERROR;
+    }
+
+    patch->next = block->exit;
+    block->exit = patch;
+
+    njs_generate_code(generator, njs_vmcode_try_return_t, try_return);
+    try_return->code.operation = njs_vmcode_try_return;
+    try_return->code.operands = NJS_VMCODE_2OPERANDS;
+    try_return->code.retval = NJS_VMCODE_RETVAL;
+    try_return->retval = index;
+
+    try_return->save = index;
+    patch->index_offset = njs_code_offset(generator, try_return)
+                          + offsetof(njs_vmcode_try_return_t, save);
+
+    try_return->offset = offsetof(njs_vmcode_try_return_t, offset);
+    patch->jump_offset = njs_code_offset(generator, try_return)
+                         + offsetof(njs_vmcode_try_return_t, offset);
+
+    return NXT_OK;
 }
 
 
@@ -2534,13 +2674,16 @@ static nxt_int_t
 njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    njs_ret_t               try_offset, catch_offset;
-    nxt_int_t               ret;
-    njs_index_t             index, catch_index;
-    njs_vmcode_catch_t      *catch;
-    njs_vmcode_finally_t    *finally;
-    njs_vmcode_try_end_t    *try_end, *catch_end;
-    njs_vmcode_try_start_t  *try_start;
+    njs_ret_t                    try_offset, try_end_offset, catch_offset,
+                                 catch_end_offset;
+    nxt_int_t                    ret;
+    njs_index_t                  exception_index, exit_index, catch_index;
+    njs_vmcode_catch_t           *catch;
+    njs_vmcode_finally_t         *finally;
+    njs_vmcode_try_end_t         *try_end, *catch_end;
+    njs_generator_block_t        *block, *try_block, *catch_block;
+    njs_vmcode_try_start_t       *try_start;
+    njs_vmcode_try_trampoline_t  *try_break, *try_continue;
 
     njs_generate_code(generator, njs_vmcode_try_start_t, try_start);
     try_offset = njs_code_offset(generator, try_start);
@@ -2548,25 +2691,82 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
     try_start->code.operands = NJS_VMCODE_2OPERANDS;
     try_start->code.retval = NJS_VMCODE_NO_RETVAL;
 
-    index = njs_generate_temp_index_get(vm, generator, node);
-    if (nxt_slow_path(index == NJS_INDEX_ERROR)) {
+    exception_index = njs_generate_temp_index_get(vm, generator, node);
+    if (nxt_slow_path(exception_index == NJS_INDEX_ERROR)) {
         return NXT_ERROR;
     }
 
-    try_start->value = index;
+    try_start->exception_value = exception_index;
+
+    /*
+     * exit_value is used in njs_vmcode_finally to make a decision
+     * which way to go after "break", "continue" and "return" instruction
+     * inside "try" or "catch" blocks.
+     */
+
+    exit_index = njs_generate_temp_index_get(vm, generator, node);
+    if (nxt_slow_path(exit_index == NJS_INDEX_ERROR)) {
+        return NXT_ERROR;
+    }
+
+    try_start->exit_value = exit_index;
+
+    ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_TRY, &no_label);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
 
     ret = njs_generator(vm, generator, node->left);
     if (nxt_slow_path(ret != NXT_OK)) {
         return ret;
     }
 
+    try_block = generator->block;
+
     njs_generate_code(generator, njs_vmcode_try_end_t, try_end);
+    try_end_offset = njs_code_offset(generator, try_end);
     try_end->code.operation = njs_vmcode_try_end;
     try_end->code.operands = NJS_VMCODE_NO_OPERAND;
     try_end->code.retval = NJS_VMCODE_NO_RETVAL;
 
+    if (try_block->exit != NULL) {
+        njs_generate_patch_try_exit_block(vm, generator, try_block->exit,
+                                          exit_index);
+
+        njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_break);
+        try_break->code.operation = njs_vmcode_try_break;
+        try_break->code.operands = NJS_VMCODE_2OPERANDS;
+        try_break->code.retval = NJS_VMCODE_NO_RETVAL;
+
+        try_break->exit_value = exit_index;
+
+        try_break->offset = -sizeof(njs_vmcode_try_end_t);
+
+    } else {
+        try_break = NULL;
+    }
+
+    if (try_block->continuation != NULL) {
+        njs_generate_patch_block(vm, generator, try_block->continuation);
+
+        njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_continue);
+        try_continue->code.operation = njs_vmcode_try_continue;
+        try_continue->code.operands = NJS_VMCODE_2OPERANDS;
+        try_continue->code.retval = NJS_VMCODE_NO_RETVAL;
+
+        try_continue->exit_value = exit_index;
+
+        try_continue->offset = -sizeof(njs_vmcode_try_end_t);
+
+        if (try_break != NULL) {
+            try_continue->offset -= sizeof(njs_vmcode_try_trampoline_t);
+        }
+    }
+
+    generator->block = try_block->next;
+
     njs_code_set_jump_offset(generator, njs_vmcode_try_start_t, try_offset);
-    try_offset = njs_code_offset(generator, try_end);
+    try_offset = try_end_offset;
 
     node = node->right;
 
@@ -2592,6 +2792,43 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
 
         njs_code_set_jump_offset(generator, njs_vmcode_try_end_t, try_offset);
 
+        if (try_block->continuation != NULL || try_block->exit != NULL) {
+            njs_generate_code(generator, njs_vmcode_finally_t, finally);
+            finally->code.operation = njs_vmcode_finally;
+            finally->code.operands = NJS_VMCODE_2OPERANDS;
+            finally->code.retval = NJS_VMCODE_NO_RETVAL;
+            finally->retval = exception_index;
+            finally->exit_value = exit_index;
+            finally->continue_offset = offsetof(njs_vmcode_finally_t,
+                                                continue_offset);
+            finally->break_offset = offsetof(njs_vmcode_finally_t,
+                                             break_offset);
+
+            if (try_block->continuation != NULL) {
+                /*
+                 * block != NULL is checked
+                 * by njs_generate_continue_statement()
+                 */
+                block = njs_generate_find_block(generator->block,
+                                                NJS_GENERATOR_LOOP);
+
+                njs_generate_make_continuation_patch(vm, generator, block,
+                            njs_code_offset(generator, finally)
+                             + offsetof(njs_vmcode_finally_t, continue_offset));
+            }
+
+            if (try_block->exit != NULL) {
+                block = njs_generate_find_block(generator->block,
+                                                NJS_GENERATOR_ALL);
+
+                if (block != NULL) {
+                    njs_generate_make_exit_patch(vm, generator, block,
+                                njs_code_offset(generator, finally)
+                                + offsetof(njs_vmcode_finally_t, break_offset));
+                }
+            }
+        }
+
         /* TODO: release exception variable index. */
 
     } else {
@@ -2610,19 +2847,67 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
             catch->code.retval = NJS_VMCODE_NO_RETVAL;
             catch->exception = catch_index;
 
+            ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_TRY,
+                                           &no_label);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+
             ret = njs_generator(vm, generator, node->left->right);
             if (nxt_slow_path(ret != NXT_OK)) {
                 return ret;
             }
 
+            catch_block = generator->block;
+
             njs_generate_code(generator, njs_vmcode_try_end_t, catch_end);
+            catch_end_offset = njs_code_offset(generator, catch_end);
             catch_end->code.operation = njs_vmcode_try_end;
             catch_end->code.operands = NJS_VMCODE_NO_OPERAND;
             catch_end->code.retval = NJS_VMCODE_NO_RETVAL;
 
+            if (catch_block->exit != NULL) {
+                njs_generate_patch_try_exit_block(vm, generator,
+                                                  catch_block->exit,
+                                                  exit_index);
+
+                njs_generate_code(generator, njs_vmcode_try_trampoline_t,
+                                  try_break);
+                try_break->code.operation = njs_vmcode_try_break;
+                try_break->code.operands = NJS_VMCODE_2OPERANDS;
+                try_break->code.retval = NJS_VMCODE_NO_RETVAL;
+
+                try_break->exit_value = exit_index;
+
+                try_break->offset = -sizeof(njs_vmcode_try_end_t);
+
+            } else {
+                try_break = NULL;
+            }
+
+            if (catch_block->continuation != NULL) {
+                njs_generate_patch_block(vm, generator,
+                                         catch_block->continuation);
+
+                njs_generate_code(generator, njs_vmcode_try_trampoline_t,
+                                  try_continue);
+                try_continue->code.operation = njs_vmcode_try_continue;
+                try_continue->code.operands = NJS_VMCODE_2OPERANDS;
+                try_continue->code.retval = NJS_VMCODE_NO_RETVAL;
+
+                try_continue->exit_value = exit_index;
+
+                try_continue->offset = -sizeof(njs_vmcode_try_end_t);
+
+                if (try_break != NULL) {
+                    try_continue->offset -= sizeof(njs_vmcode_try_trampoline_t);
+                }
+            }
+
+            generator->block = catch_block->next;
+
             njs_code_set_jump_offset(generator, njs_vmcode_catch_t,
                                      catch_offset);
-            catch_offset = njs_code_offset(generator, catch_end);
 
             /* TODO: release exception variable index. */
 
@@ -2631,10 +2916,10 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
             catch->code.operands = NJS_VMCODE_2OPERANDS;
             catch->code.retval = NJS_VMCODE_NO_RETVAL;
             catch->offset = sizeof(njs_vmcode_catch_t);
-            catch->exception = index;
+            catch->exception = exception_index;
 
             njs_code_set_jump_offset(generator, njs_vmcode_try_end_t,
-                                     catch_offset);
+                                     catch_end_offset);
 
         } else {
             /* A try/finally case. */
@@ -2644,7 +2929,9 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
             catch->code.operands = NJS_VMCODE_2OPERANDS;
             catch->code.retval = NJS_VMCODE_NO_RETVAL;
             catch->offset = sizeof(njs_vmcode_catch_t);
-            catch->exception = index;
+            catch->exception = exception_index;
+
+            catch_block = NULL;
         }
 
         njs_code_set_jump_offset(generator, njs_vmcode_try_end_t, try_offset);
@@ -2656,12 +2943,43 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
 
         njs_generate_code(generator, njs_vmcode_finally_t, finally);
         finally->code.operation = njs_vmcode_finally;
-        finally->code.operands = NJS_VMCODE_1OPERAND;
+        finally->code.operands = NJS_VMCODE_2OPERANDS;
         finally->code.retval = NJS_VMCODE_NO_RETVAL;
-        finally->retval = index;
+        finally->retval = exception_index;
+        finally->exit_value = exit_index;
+        finally->continue_offset = offsetof(njs_vmcode_finally_t,
+                                            continue_offset);
+        finally->break_offset = offsetof(njs_vmcode_finally_t, break_offset);
+
+        if (try_block->continuation != NULL
+            || (catch_block && catch_block->continuation != NULL))
+        {
+            /*
+             * block != NULL is checked
+             * by njs_generate_continue_statement()
+             */
+            block = njs_generate_find_block(generator->block,
+                                            NJS_GENERATOR_LOOP);
+
+            njs_generate_make_continuation_patch(vm, generator, block,
+                         njs_code_offset(generator, finally)
+                         + offsetof(njs_vmcode_finally_t, continue_offset));
+        }
+
+        if (try_block->exit != NULL
+            || (catch_block != NULL && catch_block->exit != NULL))
+        {
+            block = njs_generate_find_block(generator->block,
+                                            NJS_GENERATOR_ALL);
+            if (block != NULL) {
+                njs_generate_make_exit_patch(vm, generator, block,
+                                njs_code_offset(generator, finally)
+                                + offsetof(njs_vmcode_finally_t, break_offset));
+            }
+        }
     }
 
-    return njs_generate_index_release(vm, generator, index);
+    return njs_generate_index_release(vm, generator, exception_index);
 }
 
 
index 05c13b118bc2932c1a325589930d3d8c407a8d82..4d8e46bd5c6279afed55bc24f83d988196006c53 100644 (file)
@@ -132,7 +132,8 @@ start:
          *   njs_vmcode_function_call(),
          *   njs_vmcode_return(),
          *   njs_vmcode_try_start(),
-         *   njs_vmcode_try_next(),
+         *   njs_vmcode_try_continue(),
+         *   njs_vmcode_try_break(),
          *   njs_vmcode_try_end(),
          *   njs_vmcode_catch().
          *   njs_vmcode_throw().
@@ -2580,9 +2581,12 @@ njs_vmcode_stop(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
  */
 
 njs_ret_t
-njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value, njs_value_t *offset)
+njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *exception_value,
+    njs_value_t *offset)
 {
-    njs_exception_t  *e;
+    njs_value_t             *exit_value;
+    njs_exception_t         *e;
+    njs_vmcode_try_start_t  *try_start;
 
     if (vm->top_frame->exception.catch != NULL) {
         e = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_exception_t));
@@ -2597,12 +2601,67 @@ njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value, njs_value_t *offset)
 
     vm->top_frame->exception.catch = vm->current + (njs_ret_t) offset;
 
-    njs_set_invalid(value);
+    njs_set_invalid(exception_value);
+
+    try_start = (njs_vmcode_try_start_t *) vm->current;
+    exit_value = njs_vmcode_operand(vm, try_start->exit_value);
+
+    njs_set_invalid(exit_value);
+    exit_value->data.u.number = 0;
 
     return sizeof(njs_vmcode_try_start_t);
 }
 
 
+/*
+ * njs_vmcode_try_break() sets exit_value to INVALID 1, and jumps to
+ * the nearest try_end block. The exit_value is checked by njs_vmcode_finally().
+ */
+
+nxt_noinline njs_ret_t
+njs_vmcode_try_break(njs_vm_t *vm, njs_value_t *exit_value,
+    njs_value_t *offset)
+{
+    exit_value->data.u.number = 1;
+
+    return (njs_ret_t) offset;
+}
+
+
+/*
+ * njs_vmcode_try_break() sets exit_value to INVALID -1, and jumps to
+ * the nearest try_end block. The exit_value is checked by njs_vmcode_finally().
+ */
+
+nxt_noinline njs_ret_t
+njs_vmcode_try_continue(njs_vm_t *vm, njs_value_t *exit_value,
+    njs_value_t *offset)
+{
+    exit_value->data.u.number = -1;
+
+    return (njs_ret_t) offset;
+}
+
+/*
+ * njs_vmcode_try_return() saves a return value to use it later by
+ * njs_vmcode_finally(), and jumps to the nearest try_end block.
+ */
+
+nxt_noinline njs_ret_t
+njs_vmcode_try_return(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
+{
+    njs_vmcode_try_return_t  *try_return;
+
+    vm->retval = *value;
+
+    njs_retain(value);
+
+    try_return = (njs_vmcode_try_return_t *) vm->current;
+
+    return try_return->offset;
+}
+
+
 /*
  * njs_vmcode_try_end() is set on the end of a "try" block to remove the block.
  * It is also set on the end of a "catch" block followed by a "finally" block.
@@ -2664,24 +2723,48 @@ njs_vmcode_catch(njs_vm_t *vm, njs_value_t *exception, njs_value_t *offset)
 
 
 /*
- * njs_vmcode_finally() is set on the end of a "finally" block to throw
- * uncaught exception.
+ * njs_vmcode_finally() is set on the end of a "finally" or a "catch" block.
+ *   1) to throw uncaught exception.
+ *   2) to make a jump to an enslosing loop exit if "continue" or "break"
+ *      statement was used inside try block.
+ *   3) to finalize "return" instruction from "try" block.
  */
 
 njs_ret_t
 njs_vmcode_finally(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
 {
-    njs_value_t  *value;
+    njs_value_t           *exception_value, *exit_value;
+    njs_vmcode_finally_t  *finally;
 
-    value = njs_vmcode_operand(vm, retval);
+    exception_value = njs_vmcode_operand(vm, retval);
+
+    if (njs_is_valid(exception_value)) {
+        vm->retval = *exception_value;
 
-    if (!njs_is_valid(value)) {
-        return sizeof(njs_vmcode_finally_t);
+        return NXT_ERROR;
     }
 
-    vm->retval = *value;
+    finally = (njs_vmcode_finally_t *) vm->current;
+    exit_value = njs_vmcode_operand(vm, finally->exit_value);
 
-    return NXT_ERROR;
+    /*
+     * exit_value is set by:
+     *   vmcode_try_start to INVALID 0
+     *   vmcode_try_break to INVALID 1
+     *   vmcode_try_continue to INVALID -1
+     *   vmcode_try_return to a valid return value
+     */
+
+    if (njs_is_valid(exit_value)) {
+        return njs_vmcode_return(vm, NULL, exit_value);
+
+    } else if (exit_value->data.u.number != 0) {
+        return (njs_ret_t) (exit_value->data.u.number > 0)
+                                ? finally->break_offset
+                                : finally->continue_offset;
+    }
+
+    return sizeof(njs_vmcode_finally_t);
 }
 
 
index 631b451f725bf9f533ee1e03ec945c02a1ed7bbf..cb353db5b6fa8354d4dbea9d3053cd6a922abdf2 100644 (file)
@@ -774,10 +774,18 @@ typedef struct {
 typedef struct {
     njs_vmcode_t               code;
     njs_ret_t                  offset;
-    njs_index_t                value;
+    njs_index_t                exception_value;
+    njs_index_t                exit_value;
 } njs_vmcode_try_start_t;
 
 
+typedef struct {
+    njs_vmcode_t               code;
+    njs_ret_t                  offset;
+    njs_index_t                exit_value;
+} njs_vmcode_try_trampoline_t;
+
+
 typedef struct {
     njs_vmcode_t               code;
     njs_ret_t                  offset;
@@ -799,7 +807,18 @@ typedef struct {
 
 typedef struct {
     njs_vmcode_t               code;
+    njs_index_t                save;
     njs_index_t                retval;
+    njs_ret_t                  offset;
+} njs_vmcode_try_return_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+    njs_index_t                exit_value;
+    njs_ret_t                  continue_offset;
+    njs_ret_t                  break_offset;
 } njs_vmcode_finally_t;
 
 
@@ -1223,6 +1242,12 @@ njs_ret_t njs_vmcode_stop(njs_vm_t *vm, njs_value_t *invld,
 
 njs_ret_t njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value,
     njs_value_t *offset);
+njs_ret_t njs_vmcode_try_break(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *offset);
+njs_ret_t njs_vmcode_try_continue(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *offset);
+njs_ret_t njs_vmcode_try_return(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *offset);
 njs_ret_t njs_vmcode_try_end(njs_vm_t *vm, njs_value_t *invld,
     njs_value_t *offset);
 njs_ret_t njs_vmcode_throw(njs_vm_t *vm, njs_value_t *invld,
index 5ca680291ad82d7c4c1e419c4717d56e0f2ee6a3..18d83e4228867870455fd9b56d3a5ef793639f08 100644 (file)
@@ -535,6 +535,10 @@ njs_run "-v" "\\d+\.\\d+\.\\d+"
 njs_test {
     {"1+1\r\n"
      "00000 ADD*\r\n00040 STOP*\r\n\r\n2"}
+    {"for (var n in [1]) {try {break} finally{}}\r\n"
+     "00000 ARRAY*\r\n*TRY BREAK*STOP*\r\n\r\nundefined"}
+    {"(function() {try {return} finally{}})()\r\n"
+     "00000 TRY START*\r\n*TRY RETURN*STOP*\r\n\r\nundefined"}
 } "-d"
 
 # sandboxing
index e2445b00d9d63537448c7eeb25ad2d42fc29a12a..c5340f6d17b80814c16fcc397a9e4c910dc919fa 100644 (file)
@@ -6619,6 +6619,286 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("throw\nnull"),
       nxt_string("SyntaxError: Illegal newline after throw in 2") },
 
+    { nxt_string("for (var x in [1,2]) { try{ continue; } catch(e) {} } throw 1"),
+      nxt_string("1") },
+
+    { nxt_string("for (var x in [1,2]) { try{ break; } catch(e) {} } throw 1"),
+      nxt_string("1") },
+
+    { nxt_string("try\n {\n continue; } catch(e) {}"),
+      nxt_string("SyntaxError: Illegal continue statement in 3") },
+
+    { nxt_string("var a = 1; "
+                 "switch (a) {"
+                 "default:"
+                 "  try\n {\n continue; } "
+                 "  catch(e) {}"
+                 "}"),
+      nxt_string("SyntaxError: Illegal continue statement in 3") },
+
+    { nxt_string("try\n {\n break; } catch(e) {}"),
+      nxt_string("SyntaxError: Illegal break statement in 3") },
+
+    { nxt_string("try\n { }\n catch(e) {continue;}"),
+      nxt_string("SyntaxError: Illegal continue statement in 3") },
+
+    { nxt_string("try { } catch(e) {break;}"),
+      nxt_string("SyntaxError: Illegal break statement in 1") },
+
+    { nxt_string("try { continue; } finally {}"),
+      nxt_string("SyntaxError: Illegal continue statement in 1") },
+
+    { nxt_string("try { break; } finally {}"),
+      nxt_string("SyntaxError: Illegal break statement in 1") },
+
+    { nxt_string("try\n {\n try\n {\n continue; } finally {} } finally {}"),
+      nxt_string("SyntaxError: Illegal continue statement in 5") },
+
+    /* break from try in try/catch. */
+
+    { nxt_string("function f(n) {"
+                 "    var pre = 0; var post = 0;"
+                 "    for (var x in [1, 2, 3]) {"
+                 "        pre++;"
+                 "        try { "
+                 "            if (n == 'b') {break;}"
+                 "        }"
+                 "        catch (e) {};"
+                 "        post++"
+                 "    }"
+                 "    return [pre, post];"
+                 "}; njs.dump([f(),f('b')])"),
+      nxt_string("[[3,3],"
+                  "[1,0]]") },
+
+    { nxt_string("function f(v, n) {"
+                 "    var pre = 0; var post = 0; var case2 = 0;"
+                 "    switch (v) {"
+                 "    case 1: "
+                 "        pre++;"
+                 "        try { "
+                 "            if (n == 'b') {break;}"
+                 "        }"
+                 "        catch (e) {};"
+                 "        post++;"
+                 "        break;"
+                 "    default:"
+                 "        case2++;"
+                 "    }"
+                 "    return [pre, post, case2];"
+                 "}; njs.dump([f(),f(1)])"),
+      nxt_string("[[0,0,1],"
+                  "[1,1,0]]") },
+
+    /* continue from try in try/catch. */
+
+    { nxt_string("function f(n) {"
+                 "    var pre = 0; var post = 0;"
+                 "    for (var x in [1, 2, 3]) {"
+                 "        pre++;"
+                 "        try { "
+                 "            if (n == 'c') {continue;}"
+                 "        }"
+                 "        catch (e) {};"
+                 "        post++"
+                 "    }"
+                 "    return [pre, post];"
+                 "}; njs.dump([f(),f('c')])"),
+      nxt_string("[[3,3],"
+                  "[3,0]]") },
+
+    /* Multiple break/continue from try in try/catch. */
+
+    { nxt_string("function f(n) {"
+                 "    var pre = 0; var mid = 0; var post = 0;"
+                 "    for (var x in [1, 2, 3]) {"
+                 "        pre++;"
+                 "        try { "
+                 "            if (n == 'c') {continue;}"
+                 "            if (n == 'b') {break;}"
+                 "            mid++;"
+                 "            if (n == 'c2') {continue;}"
+                 "            if (n == 'b2') {break;}"
+                 "        }"
+                 "        catch (e) {};"
+                 "        post++"
+                 "    }"
+                 "    return [pre, mid, post];"
+                 "}; njs.dump([f(),f('c'),f('b'),f('c2'),f('b2')])"),
+      nxt_string("[[3,3,3],"
+                  "[3,0,0],"
+                  "[1,0,0],"
+                  "[3,3,0],"
+                  "[1,1,0]]") },
+
+    /* Multiple break/continue from catch in try/catch. */
+
+    { nxt_string("function f(t, n) {"
+                 "    var pre = 0; var mid = 0; var post = 0;"
+                 "    for (var x in [1, 2, 3]) {"
+                 "        pre++;"
+                 "        try { "
+                 "            if (t) {throw 'a'}"
+                 "        }"
+                 "        catch (e) {"
+                 "            if (n == 'c') {continue;}"
+                 "            if (n == 'b') {break;}"
+                 "            mid++;"
+                 "            if (n == 'c2') {continue;}"
+                 "            if (n == 'b2') {break;}"
+                 "        };"
+                 "        post++"
+                 "    }"
+                 "    return [pre, mid, post];"
+                 "}; njs.dump([f(), f(1), f(1, 'c'), f(1, 'b'), f(1, 'c2'), f(1, 'b2')])"),
+      nxt_string("[[3,0,3],"
+                  "[3,3,3],"
+                  "[3,0,0],"
+                  "[1,0,0],"
+                  "[3,3,0],"
+                  "[1,1,0]]") },
+
+    /* break from try in try/finally. */
+
+    { nxt_string("function f(n) {"
+                 "    var pre = 0; var mid = 0; var fin = 0; var post = 0;"
+                 "    for (var x in [1, 2, 3]) {"
+                 "        pre++;"
+                 "        try { "
+                 "            if (n == 'c') {continue;}"
+                 "            if (n == 'b') {break;}"
+                 "            mid++;"
+                 "            if (n == 'c2') {continue;}"
+                 "            if (n == 'b2') {break;}"
+                 "        }"
+                 "        finally {fin++};"
+                 "        post++"
+                 "    }"
+                 "    return [pre, mid, fin, post];"
+                 "}; njs.dump([f(),f('c'),f('b'),f('c2'),f('b2')])"),
+      nxt_string("[[3,3,3,3],"
+                  "[3,0,3,0],"
+                  "[1,0,1,0],"
+                  "[3,3,3,0],"
+                  "[1,1,1,0]]") },
+
+    /* Multiple break/continue from try in try/catch/finally. */
+
+    { nxt_string("function f(n) {"
+                 "    var pre = 0; var mid = 0; var fin = 0; var post = 0;"
+                 "    for (var x in [1, 2, 3]) {"
+                 "        pre++;"
+                 "        try { "
+                 "            if (n == 'c') {continue;}"
+                 "            if (n == 'b') {break;}"
+                 "            mid++;"
+                 "            if (n == 'c2') {continue;}"
+                 "            if (n == 'b2') {break;}"
+                 "        }"
+                 "        catch (e) {}"
+                 "        finally {fin++};"
+                 "        post++"
+                 "    }"
+                 "    return [pre, mid, fin, post];"
+                 "}; njs.dump([f(),f('c'),f('b'),f('c2'),f('b2')])"),
+      nxt_string("[[3,3,3,3],"
+                  "[3,0,3,0],"
+                  "[1,0,1,0],"
+                  "[3,3,3,0],"
+                  "[1,1,1,0]]") },
+
+    /* Multiple break/continue from catch in try/catch/finally. */
+
+    { nxt_string("function f(t, n) {"
+                 "    var pre = 0; var mid = 0; var fin = 0; var post = 0;"
+                 "    for (var x in [1, 2, 3]) {"
+                 "        pre++;"
+                 "        try {if (t) {throw 'a'}}"
+                 "        catch (e) { "
+                 "            if (n == 'c') {continue;}"
+                 "            if (n == 'b') {break;}"
+                 "            mid++;"
+                 "            if (n == 'c2') {continue;}"
+                 "            if (n == 'b2') {break;}"
+                 "        }"
+                 "        finally {fin++};"
+                 "        post++"
+                 "    }"
+                 "    return [pre, mid, fin, post];"
+                 "}; njs.dump([f(), f(1), f(1, 'c'), f(1, 'b'), f(1, 'c2'), f(1, 'b2')])"),
+      nxt_string("[[3,0,3,3],"
+                  "[3,3,3,3],"
+                  "[3,0,3,0],"
+                  "[1,0,1,0],"
+                  "[3,3,3,0],"
+                  "[1,1,1,0]]") },
+
+    /* Multiple return from try. */
+
+    { nxt_string("var r = 0; "
+                 "function f(i, n) {"
+                 "   try { "
+                 "      var a = 'x'; "
+                 "      if (i != 0) {"
+                 "         return a.repeat(n);"
+                 "      } else {"
+                 "         return;"
+                 "      }"
+                 "   }"
+                 "   catch (e) {  } "
+                 "   finally { r++; }};"
+                 "[f(1,1), f(1,2), f(0), r]"),
+      nxt_string("x,xx,,3") },
+
+    { nxt_string("var r = 0; "
+                 "function f(i) {"
+                 "   try { "
+                 "      return i;"
+                 "   }"
+                 "   catch (e) {  } "
+                 "   finally { r++; }};"
+                 "[f(true), f(false), r]"),
+      nxt_string("true,false,2") },
+
+    /* Multiple return from catch. */
+
+    { nxt_string("var r = 0; "
+                 "function f(i, n) {"
+                 "   try { "
+                 "      throw 1;"
+                 "   }"
+                 "   catch (e) {  "
+                 "      var a = 'x'; "
+                 "      if (i != 0) {"
+                 "         return a.repeat(n);"
+                 "      } else {"
+                 "         return;"
+                 "      }"
+                 "   } "
+                 "   finally { r++; }};"
+                 "[f(1,1), f(1,2), f(0), r]"),
+      nxt_string("x,xx,,3") },
+
+    /* return overrun by finally. */
+
+    { nxt_string("function f() {"
+                 "   try { "
+                 "      return 'a';"
+                 "   }"
+                 "   catch (e) {  "
+                 "   } "
+                 "   finally { "
+                 "      return 'b'; "
+                 "   }}; "
+                 "f()"),
+      nxt_string("b") },
+
+    { nxt_string("(function (f, val) { "
+                 "  try { return f(val); } "
+                 "  finally { return val; }"
+                 "})(function () {throw 'a'}, 'v')"),
+      nxt_string("v") },
+
     { nxt_string("var o = { valueOf: function() { return '3' } }; --o"),
       nxt_string("2") },