]> git.kaiwu.me - njs.git/commitdiff
Added labels support.
authorDmitry Volyntsev <xeioex@nginx.com>
Mon, 25 Feb 2019 16:00:55 +0000 (19:00 +0300)
committerDmitry Volyntsev <xeioex@nginx.com>
Mon, 25 Feb 2019 16:00:55 +0000 (19:00 +0300)
njs/njs_generator.c
njs/njs_lexer.c
njs/njs_parser.c
njs/njs_parser.h
njs/njs_variable.c
njs/njs_variable.h
njs/test/njs_unit_test.c

index a5496e9c701756b14632b713724eaef013960e9c..ed74e6758aa0aab4a535ce10f7d83d0fa6e0006a 100644 (file)
@@ -20,29 +20,35 @@ struct njs_generator_patch_s {
      */
     njs_ret_t                       jump_offset;
     njs_generator_patch_t           *next;
+
+    nxt_str_t                       label;
 };
 
 
 typedef enum {
-    NJS_GENERATOR_BLOCK = 1,
-    NJS_GENERATOR_LOOP = 2,
-    NJS_GENERATOR_SWITCH = 4,
+    NJS_GENERATOR_LOOP = 1,
+    NJS_GENERATOR_SWITCH = 2,
+    NJS_GENERATOR_BLOCK = 4,
     NJS_GENERATOR_TRY = 8,
-
-#define NJS_GENERATOR_ALL          (NJS_GENERATOR_BLOCK                      \
-                                    | NJS_GENERATOR_LOOP                     \
-                                    | NJS_GENERATOR_SWITCH                   \
-                                    | NJS_GENERATOR_TRY)
+#define NJS_GENERATOR_ALL          (NJS_GENERATOR_LOOP | NJS_GENERATOR_SWITCH)
 } njs_generator_block_type_t;
 
 
 struct njs_generator_block_s {
-    njs_generator_block_type_t      type;    /* 2 bits */
+    njs_generator_block_type_t      type;    /* 4 bits */
     nxt_str_t                       label;
+
+    /* list of "continue" instruction offsets to be patched. */
     njs_generator_patch_t           *continuation;
+    /*
+     * list of "return" from try-catch block and "break"
+     * instruction offsets to be patched.
+     */
     njs_generator_patch_t           *exit;
+
     njs_generator_block_t           *next;
 
+    /* exit value index, used only for NJS_GENERATOR_TRY blocks. */
     njs_index_t                     index;
 };
 
@@ -78,16 +84,21 @@ 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 njs_generator_block_t *njs_generate_lookup_block(
+    njs_generator_block_t *block, uint32_t mask, const nxt_str_t *label);
 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);
+    njs_generator_block_t *block, uint32_t mask, const nxt_str_t *label);
+static njs_generator_patch_t *njs_generate_make_continuation_patch(njs_vm_t *vm,
+    njs_generator_block_t *block, const nxt_str_t *label, 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_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 njs_generator_patch_t *njs_generate_make_exit_patch(njs_vm_t *vm,
+    njs_generator_block_t *block, const nxt_str_t *label, njs_ret_t offset);
 static nxt_noinline void njs_generate_patch_block_exit(njs_vm_t *vm,
     njs_generator_t *generator);
+static const nxt_str_t *njs_generate_jump_destination(njs_vm_t *vm,
+    njs_generator_block_t *block, const char *inst_type, uint32_t mask,
+    const nxt_str_t *label1, const nxt_str_t *label2);
 static nxt_int_t njs_generate_continue_statement(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static nxt_int_t njs_generate_break_statement(njs_vm_t *vm,
@@ -208,7 +219,10 @@ static nxt_int_t njs_generate_function_debug(njs_vm_t *vm, nxt_str_t *name,
     njs_parser_node_error(vm, node, NJS_OBJECT_SYNTAX_ERROR, fmt, ##__VA_ARGS__)
 
 
-static const nxt_str_t  no_label = { 0, NULL };
+static const nxt_str_t  no_label     = nxt_string("");
+static const nxt_str_t  return_label = nxt_string("@return");
+/* GCC and Clang complain about NULL argument passed to memcmp(). */
+static const nxt_str_t  undef_label  = { 0xffffffff, (u_char *) "" };
 
 
 static nxt_int_t
@@ -432,7 +446,7 @@ njs_generator(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node)
 
     default:
         nxt_thread_log_debug("unknown token: %d", node->token);
-        njs_syntax_error(vm, "unknown token");
+        njs_internal_error(vm, "Generator failed: unknown token");
 
         return NXT_ERROR;
     }
@@ -845,7 +859,7 @@ njs_generate_switch_statement(njs_vm_t *vm, njs_generator_t *generator,
     }
 
     ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_SWITCH,
-                                   &no_label);
+                                   &swtch->label);
     if (nxt_slow_path(ret != NXT_OK)) {
         return ret;
     }
@@ -886,6 +900,7 @@ njs_generate_switch_statement(njs_vm_t *vm, njs_generator_t *generator,
 
             patch->jump_offset = njs_code_offset(generator, equal)
                                  + offsetof(njs_vmcode_equal_jump_t, offset);
+            patch->label = no_label;
 
             *last = patch;
             last = &patch->next;
@@ -970,7 +985,7 @@ njs_generate_while_statement(njs_vm_t *vm, njs_generator_t *generator,
     /* The loop body. */
 
     ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
-                                   &no_label);
+                                   &node->label);
     if (nxt_slow_path(ret != NXT_OK)) {
         return ret;
     }
@@ -1020,7 +1035,7 @@ njs_generate_do_while_statement(njs_vm_t *vm, njs_generator_t *generator,
     /* The loop body. */
 
     ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
-                                   &no_label);
+                                   &node->label);
     if (nxt_slow_path(ret != NXT_OK)) {
         return ret;
     }
@@ -1067,7 +1082,7 @@ njs_generate_for_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_vmcode_cond_jump_t  *cond_jump;
 
     ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
-                                   &no_label);
+                                   &node->label);
     if (nxt_slow_path(ret != NXT_OK)) {
         return ret;
     }
@@ -1178,7 +1193,7 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_vmcode_prop_foreach_t  *prop_foreach;
 
     ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP,
-                                   &no_label);
+                                   &node->label);
     if (nxt_slow_path(ret != NXT_OK)) {
         return ret;
     }
@@ -1278,10 +1293,18 @@ 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)
+njs_generate_lookup_block(njs_generator_block_t *block, uint32_t mask,
+    const nxt_str_t *label)
 {
+    if (nxt_strstr_eq(label, &return_label)) {
+        mask = NJS_GENERATOR_TRY;
+        label = &no_label;
+    }
+
     while (block != NULL) {
-        if (block->type & mask) {
+        if ((block->type & mask) != 0
+            && (label->length == 0 || nxt_strstr_eq(&block->label, label)))
+        {
             return block;
         }
 
@@ -1292,16 +1315,59 @@ 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 njs_generator_block_t *
+njs_generate_find_block(njs_generator_block_t *block, uint32_t mask,
+    const nxt_str_t *label)
+{
+    njs_generator_block_t  *dest_block;
+
+    /*
+     * ES5.1: 12.8 The break Statement
+     * "break" without a label is valid only from within
+     * loop or switch statement.
+     */
+    if ((mask & NJS_GENERATOR_ALL) == NJS_GENERATOR_ALL
+        && !nxt_strstr_eq(label, &no_label))
+    {
+        mask |= NJS_GENERATOR_BLOCK;
+    }
+
+    dest_block = njs_generate_lookup_block(block, mask, label);
+
+    if (dest_block != NULL) {
+
+        /*
+         * Looking for intermediate try-catch blocks. Before jumping to
+         * the destination finally blocks have to be executed.
+         */
+
+        while (block != NULL) {
+            if (block->type & NJS_GENERATOR_TRY) {
+                return block;
+            }
+
+            if (block == dest_block) {
+                return block;
+            }
+
+            block = block->next;
+        }
+    }
+
+    return dest_block;
+}
+
+
+static njs_generator_patch_t *
+njs_generate_make_continuation_patch(njs_vm_t *vm, njs_generator_block_t *block,
+    const nxt_str_t *label, njs_ret_t offset)
 {
     njs_generator_patch_t  *patch;
 
     patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
     if (nxt_slow_path(patch == NULL)) {
         njs_memory_error(vm);
-        return NXT_ERROR;
+        return NULL;
     }
 
     patch->next = block->continuation;
@@ -1309,7 +1375,9 @@ njs_generate_make_continuation_patch(njs_vm_t *vm, njs_generator_t *generator,
 
     patch->jump_offset = offset;
 
-    return NXT_OK;
+    patch->label = *label;
+
+    return patch;
 }
 
 
@@ -1328,16 +1396,16 @@ njs_generate_patch_block(njs_vm_t *vm, njs_generator_t *generator,
 }
 
 
-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 njs_generator_patch_t *
+njs_generate_make_exit_patch(njs_vm_t *vm, njs_generator_block_t *block,
+    const nxt_str_t *label, njs_ret_t offset)
 {
     njs_generator_patch_t  *patch;
 
     patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
     if (nxt_slow_path(patch == NULL)) {
         njs_memory_error(vm);
-        return NXT_ERROR;
+        return NULL;
     }
 
     patch->next = block->exit;
@@ -1345,7 +1413,9 @@ njs_generate_make_exit_patch(njs_vm_t *vm, njs_generator_t *generator,
 
     patch->jump_offset = offset;
 
-    return NXT_OK;
+    patch->label = *label;
+
+    return patch;
 }
 
 
@@ -1363,43 +1433,79 @@ njs_generate_patch_block_exit(njs_vm_t *vm, njs_generator_t *generator)
 }
 
 
+/*
+ * TODO: support multiple destination points from within try-catch block.
+ */
+static const nxt_str_t *
+njs_generate_jump_destination(njs_vm_t *vm, njs_generator_block_t *block,
+    const char *inst_type, uint32_t mask, const nxt_str_t *label1,
+    const nxt_str_t *label2)
+{
+    njs_generator_block_t  *block1, *block2;
+
+    if (label1->length == undef_label.length) {
+        return label2;
+    }
+
+    if (label2->length == undef_label.length) {
+        return label1;
+    }
+
+    block1 = njs_generate_lookup_block(block, mask, label1);
+    block2 = njs_generate_lookup_block(block, mask, label2);
+
+    if (block1 != block2) {
+        njs_internal_error(vm, "%s instructions with different labels "
+                           "(\"%V\" vs \"%V\") "
+                           "from try-catch block are not supported", inst_type,
+                            label1, label2);
+
+        return NULL;
+    }
+
+    return label1;
+}
+
+
 static nxt_int_t
 njs_generate_continue_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
+    const nxt_str_t        *label, *dest;
     njs_vmcode_jump_t      *jump;
     njs_generator_patch_t  *patch;
     njs_generator_block_t  *block;
 
-    block = njs_generate_find_block(generator->block,
-                                    NJS_GENERATOR_LOOP | NJS_GENERATOR_TRY);
+    label = &node->label;
+
+    block = njs_generate_find_block(generator->block, NJS_GENERATOR_LOOP,
+                                    label);
 
     if (nxt_slow_path(block == NULL)) {
         goto syntax_error;
     }
 
-    if (block->type == NJS_GENERATOR_TRY
-        && njs_generate_find_block(block->next, NJS_GENERATOR_LOOP) == NULL)
-    {
-        goto syntax_error;
+    if (block->type == NJS_GENERATOR_TRY && block->continuation != NULL) {
+        dest = njs_generate_jump_destination(vm, block->next, "continue",
+                                             NJS_GENERATOR_LOOP,
+                                             &block->continuation->label,
+                                             label);
+        if (nxt_slow_path(dest == NULL)) {
+            return NXT_ERROR;
+        }
     }
 
-    /* TODO: LABEL */
-
-    patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
-
-    if (nxt_fast_path(patch != NULL)) {
-        patch->next = block->continuation;
-        block->continuation = patch;
-
-        njs_generate_code(generator, njs_vmcode_jump_t, jump);
-        jump->code.operation = njs_vmcode_jump;
-        jump->code.operands = NJS_VMCODE_NO_OPERAND;
-        jump->code.retval = NJS_VMCODE_NO_RETVAL;
-        jump->offset = offsetof(njs_vmcode_jump_t, offset);
+    njs_generate_code(generator, njs_vmcode_jump_t, jump);
+    jump->code.operation = njs_vmcode_jump;
+    jump->code.operands = NJS_VMCODE_NO_OPERAND;
+    jump->code.retval = NJS_VMCODE_NO_RETVAL;
+    jump->offset = offsetof(njs_vmcode_jump_t, offset);
 
-        patch->jump_offset = njs_code_offset(generator, jump)
-                             + offsetof(njs_vmcode_jump_t, offset);
+    patch = njs_generate_make_continuation_patch(vm, block, label,
+                                         njs_code_offset(generator, jump)
+                                         + offsetof(njs_vmcode_jump_t, offset));
+    if (nxt_slow_path(patch == NULL)) {
+        return NXT_ERROR;
     }
 
     return NXT_OK;
@@ -1416,38 +1522,38 @@ static nxt_int_t
 njs_generate_break_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
+    const nxt_str_t        *label, *dest;
     njs_vmcode_jump_t      *jump;
     njs_generator_patch_t  *patch;
     njs_generator_block_t  *block;
 
-    block = njs_generate_find_block(generator->block, NJS_GENERATOR_ALL);
+    label = &node->label;
 
+    block = njs_generate_find_block(generator->block, NJS_GENERATOR_ALL, label);
     if (nxt_slow_path(block == NULL)) {
         goto syntax_error;
      }
 
-    if (block->type == NJS_GENERATOR_TRY
-        && njs_generate_find_block(block->next, NJS_GENERATOR_ALL) == NULL)
-    {
-        goto syntax_error;
+    if (block->type == NJS_GENERATOR_TRY && block->exit != NULL) {
+        dest = njs_generate_jump_destination(vm, block->next, "break/return",
+                                             NJS_GENERATOR_ALL,
+                                             &block->exit->label, label);
+        if (nxt_slow_path(dest == NULL)) {
+            return NXT_ERROR;
+        }
     }
 
-    /* TODO: LABEL: loop and switch may have label, block must have label. */
-
-    patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
-
-    if (nxt_fast_path(patch != NULL)) {
-        patch->next = block->exit;
-        block->exit = patch;
-
-        njs_generate_code(generator, njs_vmcode_jump_t, jump);
-        jump->code.operation = njs_vmcode_jump;
-        jump->code.operands = NJS_VMCODE_NO_OPERAND;
-        jump->code.retval = NJS_VMCODE_NO_RETVAL;
-        jump->offset = offsetof(njs_vmcode_jump_t, offset);
+    njs_generate_code(generator, njs_vmcode_jump_t, jump);
+    jump->code.operation = njs_vmcode_jump;
+    jump->code.operands = NJS_VMCODE_NO_OPERAND;
+    jump->code.retval = NJS_VMCODE_NO_RETVAL;
+    jump->offset = offsetof(njs_vmcode_jump_t, offset);
 
-        patch->jump_offset = njs_code_offset(generator, jump)
-                             + offsetof(njs_vmcode_jump_t, offset);
+    patch = njs_generate_make_exit_patch(vm, block, label,
+                                         njs_code_offset(generator, jump)
+                                         + offsetof(njs_vmcode_jump_t, offset));
+    if (nxt_slow_path(patch == NULL)) {
+        return NXT_ERROR;
     }
 
     return NXT_OK;
@@ -1483,7 +1589,7 @@ njs_generate_block_statement(njs_vm_t *vm, njs_generator_t *generator,
     nxt_int_t  ret;
 
     ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_BLOCK,
-                                   &no_label);
+                                   &node->label);
     if (nxt_slow_path(ret != NXT_OK)) {
         return ret;
     }
@@ -2437,9 +2543,10 @@ njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator,
 {
     nxt_int_t                ret;
     njs_index_t              index;
+    const nxt_str_t          *dest;
     njs_vmcode_return_t      *code;
     njs_generator_patch_t    *patch;
-    njs_generator_block_t    *block;
+    njs_generator_block_t    *block, *immediate, *top;
     njs_vmcode_try_return_t  *try_return;
 
     ret = njs_generator(vm, generator, node->right);
@@ -2452,13 +2559,13 @@ njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator,
         index = node->right->index;
 
     } else {
-        index = njs_value_index(vm, &njs_value_void,
-                                generator->runtime);
+        index = njs_value_index(vm, &njs_value_void, generator->runtime);
     }
 
-    block = njs_generate_find_block(generator->block, NJS_GENERATOR_TRY);
+    immediate = njs_generate_lookup_block(generator->block, NJS_GENERATOR_TRY,
+                                          &no_label);
 
-    if (nxt_fast_path(block == NULL)) {
+    if (nxt_fast_path(immediate == NULL)) {
         njs_generate_code(generator, njs_vmcode_return_t, code);
         code->code.operation = njs_vmcode_return;
         code->code.operands = NJS_VMCODE_1OPERAND;
@@ -2470,24 +2577,43 @@ njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator,
         return NXT_OK;
     }
 
-    patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t));
-    if (nxt_slow_path(patch == NULL)) {
-        return NXT_ERROR;
+    if (immediate->type == NJS_GENERATOR_TRY && immediate->exit != NULL) {
+        dest = njs_generate_jump_destination(vm, immediate->next,
+                                             "break/return",
+                                             NJS_GENERATOR_ALL,
+                                             &immediate->exit->label,
+                                             &return_label);
+        if (nxt_slow_path(dest == NULL)) {
+            return NXT_ERROR;
+        }
     }
 
-    patch->next = block->exit;
-    block->exit = patch;
+    top = immediate;
+    block = immediate->next;
+
+    while (block != NULL) {
+        if (block->type & NJS_GENERATOR_TRY) {
+            top = block;
+        }
+
+        block = block->next;
+    }
 
     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 = block->index;
-
+    try_return->save = top->index;
     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);
+
+    patch = njs_generate_make_exit_patch(vm, immediate, &return_label,
+                                         njs_code_offset(generator, try_return)
+                                         + offsetof(njs_vmcode_try_return_t,
+                                                    offset));
+    if (nxt_slow_path(patch == NULL)) {
+        return NXT_ERROR;
+    }
 
     return NXT_OK;
 }
@@ -2648,9 +2774,13 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
                                  catch_end_offset;
     nxt_int_t                    ret;
     njs_index_t                  exception_index, exit_index, catch_index;
+    nxt_str_t                    try_cont_label, try_exit_label,
+                                 catch_cont_label, catch_exit_label;
+    const nxt_str_t              *dest_label;
     njs_vmcode_catch_t           *catch;
     njs_vmcode_finally_t         *finally;
     njs_vmcode_try_end_t         *try_end, *catch_end;
+    njs_generator_patch_t        *patch;
     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;
@@ -2694,6 +2824,9 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
         return ret;
     }
 
+    try_exit_label = undef_label;
+    try_cont_label = undef_label;
+
     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;
@@ -2701,6 +2834,8 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
     try_end->code.retval = NJS_VMCODE_NO_RETVAL;
 
     if (try_block->exit != NULL) {
+        try_exit_label = try_block->exit->label;
+
         njs_generate_patch_block(vm, generator, try_block->exit);
 
         njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_break);
@@ -2717,6 +2852,8 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
     }
 
     if (try_block->continuation != NULL) {
+        try_cont_label = try_block->continuation->label;
+
         njs_generate_patch_block(vm, generator, try_block->continuation);
 
         njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_continue);
@@ -2740,6 +2877,9 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
 
     node = node->right;
 
+    catch_exit_label = undef_label;
+    catch_cont_label = undef_label;
+
     if (node->token == NJS_TOKEN_CATCH) {
         /* A "try/catch" case. */
 
@@ -2780,21 +2920,31 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
                  * by njs_generate_continue_statement()
                  */
                 block = njs_generate_find_block(generator->block,
-                                                NJS_GENERATOR_LOOP);
+                                                NJS_GENERATOR_LOOP,
+                                                &try_cont_label);
 
-                njs_generate_make_continuation_patch(vm, generator, block,
+                patch = njs_generate_make_continuation_patch(vm, block,
+                                                             &try_cont_label,
                             njs_code_offset(generator, finally)
                              + offsetof(njs_vmcode_finally_t, continue_offset));
+                if (nxt_slow_path(patch == NULL)) {
+                    return NXT_ERROR;
+                }
             }
 
             if (try_block->exit != NULL) {
                 block = njs_generate_find_block(generator->block,
-                                                NJS_GENERATOR_ALL);
+                                                NJS_GENERATOR_ALL,
+                                                &try_exit_label);
 
                 if (block != NULL) {
-                    njs_generate_make_exit_patch(vm, generator, block,
+                    patch = njs_generate_make_exit_patch(vm, block,
+                                                         &try_exit_label,
                                 njs_code_offset(generator, finally)
                                 + offsetof(njs_vmcode_finally_t, break_offset));
+                    if (nxt_slow_path(patch == NULL)) {
+                        return NXT_ERROR;
+                    }
                 }
             }
         }
@@ -2838,6 +2988,8 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
             catch_end->code.retval = NJS_VMCODE_NO_RETVAL;
 
             if (catch_block->exit != NULL) {
+                catch_exit_label = catch_block->exit->label;
+
                 njs_generate_patch_block(vm, generator, catch_block->exit);
 
                 njs_generate_code(generator, njs_vmcode_try_trampoline_t,
@@ -2855,6 +3007,8 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
             }
 
             if (catch_block->continuation != NULL) {
+                catch_cont_label = catch_block->continuation->label;
+
                 njs_generate_patch_block(vm, generator,
                                          catch_block->continuation);
 
@@ -2923,27 +3077,57 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator,
         if (try_block->continuation != NULL
             || (catch_block && catch_block->continuation != NULL))
         {
+            dest_label = njs_generate_jump_destination(vm, generator->block,
+                                                       "try continue",
+                                                       NJS_GENERATOR_LOOP,
+                                                       &try_cont_label,
+                                                       &catch_cont_label);
+            if (nxt_slow_path(dest_label == NULL)) {
+                return NXT_ERROR;
+            }
+
             /*
              * block != NULL is checked
              * by njs_generate_continue_statement()
              */
             block = njs_generate_find_block(generator->block,
-                                            NJS_GENERATOR_LOOP);
+                                            NJS_GENERATOR_LOOP, dest_label);
 
-            njs_generate_make_continuation_patch(vm, generator, block,
-                         njs_code_offset(generator, finally)
-                         + offsetof(njs_vmcode_finally_t, continue_offset));
+            patch = njs_generate_make_continuation_patch(vm, block, dest_label,
+                             njs_code_offset(generator, finally)
+                             + offsetof(njs_vmcode_finally_t, continue_offset));
+            if (nxt_slow_path(patch == NULL)) {
+                return NXT_ERROR;
+            }
         }
 
         if (try_block->exit != NULL
             || (catch_block != NULL && catch_block->exit != NULL))
         {
+            dest_label = njs_generate_jump_destination(vm, generator->block,
+                                                       "try break/return",
+                                                       NJS_GENERATOR_ALL
+                                                       | NJS_GENERATOR_TRY,
+                                                       &try_exit_label,
+                                                       &catch_exit_label);
+            if (nxt_slow_path(dest_label == NULL)) {
+                return NXT_ERROR;
+            }
+
+            /*
+             * block can be NULL for "return" instruction in
+             * outermost try-catch block.
+             */
             block = njs_generate_find_block(generator->block,
-                                            NJS_GENERATOR_ALL);
+                                            NJS_GENERATOR_ALL
+                                            | NJS_GENERATOR_TRY, dest_label);
             if (block != NULL) {
-                njs_generate_make_exit_patch(vm, generator, block,
+                patch = njs_generate_make_exit_patch(vm, block, dest_label,
                                 njs_code_offset(generator, finally)
                                 + offsetof(njs_vmcode_finally_t, break_offset));
+                if (nxt_slow_path(patch == NULL)) {
+                    return NXT_ERROR;
+                }
             }
         }
     }
index 388535156462d6faac342079a6c33345789390e5..338304d0026f4f67d7cd340a607c3b602dc5adca 100644 (file)
@@ -292,6 +292,32 @@ void
 njs_lexer_rollback(njs_lexer_t *lexer)
 {
     lexer->start = lexer->prev_start;
+    lexer->token = lexer->prev_token;
+}
+
+
+njs_token_t
+njs_lexer_peek_token(njs_lexer_t *lexer)
+{
+    u_char       *start;
+    njs_token_t  token;
+
+    start = lexer->start;
+
+    while (start < lexer->end) {
+        token = njs_tokens[*start++];
+
+        switch (token) {
+        case NJS_TOKEN_SPACE:
+        case NJS_TOKEN_LINE_END:
+            continue;
+
+        default:
+            return token;
+        }
+    }
+
+    return NJS_TOKEN_END;
 }
 
 
index 8f6a8ee67285275cd1c388c6486b3db053320e10..25080c4caa31ae98cc1453bd6ff989d759f4099d 100644 (file)
@@ -21,6 +21,8 @@ static njs_token_t njs_parser_block_statement(njs_vm_t *vm,
     njs_parser_t *parser);
 static njs_token_t njs_parser_block(njs_vm_t *vm,
     njs_parser_t *parser, njs_token_t token);
+static njs_token_t njs_parser_labelled_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
 static njs_token_t njs_parser_function_declaration(njs_vm_t *vm,
     njs_parser_t *parser);
 static njs_token_t njs_parser_function_lambda(njs_vm_t *vm,
@@ -42,10 +44,8 @@ static njs_token_t njs_parser_for_var_in_statement(njs_vm_t *vm,
     njs_parser_t *parser, njs_parser_node_t *name);
 static njs_token_t njs_parser_for_in_statement(njs_vm_t *vm,
     njs_parser_t *parser, nxt_str_t *name, njs_token_t token);
-static njs_token_t njs_parser_continue_statement(njs_vm_t *vm,
-    njs_parser_t *parser);
-static njs_token_t njs_parser_break_statement(njs_vm_t *vm,
-    njs_parser_t *parser);
+static njs_token_t njs_parser_brk_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token);
 static njs_token_t njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser);
 static njs_token_t njs_parser_try_block(njs_vm_t *vm, njs_parser_t *parser);
 static njs_token_t njs_parser_throw_statement(njs_vm_t *vm,
@@ -216,6 +216,7 @@ njs_parser_scope_begin(njs_vm_t *vm, njs_parser_t *parser, njs_scope_t type)
     scope->argument_closures = 0;
 
     nxt_queue_init(&scope->nested);
+    nxt_lvlhsh_init(&scope->labels);
     nxt_lvlhsh_init(&scope->variables);
     nxt_lvlhsh_init(&scope->references);
 
@@ -354,19 +355,17 @@ njs_parser_statement(njs_vm_t *vm, njs_parser_t *parser,
 
     default:
 
+        if (token == NJS_TOKEN_NAME
+            && njs_lexer_peek_token(parser->lexer) == NJS_TOKEN_COLON)
+        {
+            return njs_parser_labelled_statement(vm, parser);
+        }
+
         switch (token) {
         case NJS_TOKEN_VAR:
             token = njs_parser_var_statement(vm, parser);
             break;
 
-        case NJS_TOKEN_CONTINUE:
-            token = njs_parser_continue_statement(vm, parser);
-            break;
-
-        case NJS_TOKEN_BREAK:
-            token = njs_parser_break_statement(vm, parser);
-            break;
-
         case NJS_TOKEN_RETURN:
             token = njs_parser_return_statement(vm, parser);
             break;
@@ -375,6 +374,11 @@ njs_parser_statement(njs_vm_t *vm, njs_parser_t *parser,
             token = njs_parser_throw_statement(vm, parser);
             break;
 
+        case NJS_TOKEN_CONTINUE:
+        case NJS_TOKEN_BREAK:
+            token = njs_parser_brk_statement(vm, parser, token);
+            break;
+
         default:
             token = njs_parser_expression(vm, parser, token);
             break;
@@ -505,6 +509,63 @@ njs_parser_variable_reference(njs_vm_t *vm, njs_parser_t *parser,
 }
 
 
+static njs_token_t
+njs_parser_labelled_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    uint32_t        hash;
+    njs_ret_t       ret;
+    nxt_str_t       name;
+    njs_token_t     token;
+    njs_variable_t  *label;
+
+    name = parser->lexer->text;
+    hash = parser->lexer->key_hash;
+
+    label = njs_label_find(vm, parser->scope, &name, hash);
+    if (nxt_slow_path(label != NULL)) {
+        njs_parser_syntax_error(vm, parser, "Label \"%V\" "
+                                "has already been declared", &name);
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    label = njs_label_add(vm, parser->scope, &name, hash);
+    if (nxt_slow_path(label == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_match(vm, parser, token, NJS_TOKEN_COLON);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_statement(vm, parser, token);
+
+    if (nxt_fast_path(token > NJS_TOKEN_ILLEGAL)) {
+
+        if (parser->node != NULL) {
+            /* The statement is not empty block or just semicolon. */
+
+            ret = njs_name_copy(vm, &parser->node->label, &name);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            ret = njs_label_remove(vm, parser->scope, &name, hash);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return NJS_TOKEN_ERROR;
+            }
+        }
+    }
+
+    return token;
+}
+
+
 static njs_token_t
 njs_parser_function_declaration(njs_vm_t *vm, njs_parser_t *parser)
 {
@@ -1483,12 +1544,15 @@ njs_parser_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, nxt_str_t *name,
 
 
 static njs_token_t
-njs_parser_continue_statement(njs_vm_t *vm, njs_parser_t *parser)
+njs_parser_brk_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
 {
-    njs_token_t        token;
+    uint32_t           hash;
+    njs_ret_t          ret;
+    nxt_str_t          name;
     njs_parser_node_t  *node;
 
-    node = njs_parser_node_new(vm, parser, NJS_TOKEN_CONTINUE);
+    node = njs_parser_node_new(vm, parser, token);
     if (nxt_slow_path(node == NULL)) {
         return NJS_TOKEN_ERROR;
     }
@@ -1503,37 +1567,21 @@ njs_parser_continue_statement(njs_vm_t *vm, njs_parser_t *parser)
     case NJS_TOKEN_LINE_END:
         return njs_parser_token(parser);
 
-    case NJS_TOKEN_SEMICOLON:
-    case NJS_TOKEN_CLOSE_BRACE:
-    case NJS_TOKEN_END:
-        return token;
-
-    default:
-        /* TODO: LABEL */
-        return NJS_TOKEN_ILLEGAL;
-    }
-}
-
-
-static njs_token_t
-njs_parser_break_statement(njs_vm_t *vm, njs_parser_t *parser)
-{
-    njs_token_t        token;
-    njs_parser_node_t  *node;
-
-    node = njs_parser_node_new(vm, parser, NJS_TOKEN_BREAK);
-    if (nxt_slow_path(node == NULL)) {
-        return NJS_TOKEN_ERROR;
-    }
-
-    node->token_line = parser->lexer->token_line;
-    parser->node = node;
+    case NJS_TOKEN_NAME:
+        name = parser->lexer->text;
+        hash = parser->lexer->key_hash;
 
-    token = njs_lexer_token(parser->lexer);
+        if (njs_label_find(vm, parser->scope, &name, hash) == NULL) {
+            njs_parser_syntax_error(vm, parser, "Undefined label \"%V\"",
+                                    &name);
+            return NJS_TOKEN_ILLEGAL;
+        }
 
-    switch (token) {
+        ret = njs_name_copy(vm, &parser->node->label, &parser->lexer->text);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NJS_TOKEN_ERROR;
+        }
 
-    case NJS_TOKEN_LINE_END:
         return njs_parser_token(parser);
 
     case NJS_TOKEN_SEMICOLON:
index 4b8f68bd9f300e391c2ea0f2814150830cec66e4..501e806e771d874dbb9bbae07f6ef5fb2f4c2eaa 100644 (file)
@@ -243,6 +243,7 @@ struct njs_parser_scope_s {
     nxt_queue_t                     nested;
 
     njs_parser_scope_t              *parent;
+    nxt_lvlhsh_t                    labels;
     nxt_lvlhsh_t                    variables;
     nxt_lvlhsh_t                    references;
 
@@ -274,6 +275,8 @@ struct njs_parser_node_s {
         njs_parser_node_t           *object;
     } u;
 
+    nxt_str_t                       label;
+
     njs_index_t                     index;
 
     /*
@@ -306,6 +309,7 @@ typedef struct {
 
 njs_token_t njs_lexer_token(njs_lexer_t *lexer);
 void njs_lexer_rollback(njs_lexer_t *lexer);
+njs_token_t njs_lexer_peek_token(njs_lexer_t *lexer);
 nxt_int_t njs_lexer_keywords_init(nxt_mp_t *mcp, nxt_lvlhsh_t *hash);
 njs_token_t njs_lexer_keyword(njs_lexer_t *lexer);
 
index d3b8e2b4fa3ff59baafb8974cf97f0bbf166191d..f543552d934de4a93514d58c5567729311d62052 100644 (file)
@@ -92,6 +92,74 @@ njs_variable_add(njs_vm_t *vm, njs_parser_scope_t *scope, nxt_str_t *name,
 }
 
 
+njs_variable_t *
+njs_label_add(njs_vm_t *vm, njs_parser_scope_t *scope, nxt_str_t *name,
+    uint32_t hash)
+{
+    nxt_int_t           ret;
+    njs_variable_t      *label;
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key_hash = hash;
+    lhq.key = *name;
+    lhq.proto = &njs_variables_hash_proto;
+
+    if (nxt_lvlhsh_find(&scope->labels, &lhq) == NXT_OK) {
+        return lhq.value;
+    }
+
+    label = njs_variable_alloc(vm, &lhq.key, NJS_VARIABLE_CONST);
+    if (nxt_slow_path(label == NULL)) {
+        return label;
+    }
+
+    lhq.replace = 0;
+    lhq.value = label;
+    lhq.pool = vm->mem_pool;
+
+    ret = nxt_lvlhsh_insert(&scope->labels, &lhq);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        return label;
+    }
+
+    nxt_mp_free(vm->mem_pool, label->name.start);
+    nxt_mp_free(vm->mem_pool, label);
+
+    njs_internal_error(vm, "lvlhsh insert failed");
+
+    return NULL;
+}
+
+
+njs_ret_t
+njs_label_remove(njs_vm_t *vm, njs_parser_scope_t *scope, nxt_str_t *name,
+    uint32_t hash)
+{
+    nxt_int_t           ret;
+    njs_variable_t      *label;
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key_hash = hash;
+    lhq.key = *name;
+    lhq.proto = &njs_variables_hash_proto;
+    lhq.pool = vm->mem_pool;
+
+    ret = nxt_lvlhsh_delete(&scope->labels, &lhq);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        label = lhq.value;
+        nxt_mp_free(vm->mem_pool, label->name.start);
+        nxt_mp_free(vm->mem_pool, label);
+
+    } else {
+        njs_internal_error(vm, "lvlhsh delete failed");
+    }
+
+    return ret;
+}
+
+
 static nxt_int_t
 njs_reference_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
 {
@@ -351,6 +419,30 @@ not_found:
 }
 
 
+njs_variable_t *
+njs_label_find(njs_vm_t *vm, njs_parser_scope_t *scope, nxt_str_t *name,
+    uint32_t hash)
+{
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key_hash = hash;
+    lhq.key = *name;
+    lhq.proto = &njs_variables_hash_proto;
+
+    for ( ;; ) {
+        if (nxt_lvlhsh_find(&scope->labels, &lhq) == NXT_OK) {
+            return lhq.value;
+        }
+
+        scope = scope->parent;
+
+        if (scope == NULL) {
+            return NULL;
+        }
+    }
+}
+
+
 static njs_ret_t
 njs_variable_reference_resolve(njs_vm_t *vm, njs_variable_reference_t *vr,
     njs_parser_scope_t *node_scope)
index a812b9d50cf81817fefd1d2e211250b7c3620156..3af51872d621065fbd43c09db282104621a02cb0 100644 (file)
@@ -54,6 +54,12 @@ typedef struct {
 
 njs_variable_t *njs_variable_add(njs_vm_t *vm, njs_parser_scope_t *scope,
     nxt_str_t *name, uint32_t hash, njs_variable_type_t type);
+njs_variable_t * njs_label_add(njs_vm_t *vm, njs_parser_scope_t *scope,
+    nxt_str_t *name, uint32_t hash);
+njs_variable_t *njs_label_find(njs_vm_t *vm, njs_parser_scope_t *scope,
+    nxt_str_t *name, uint32_t hash);
+njs_ret_t njs_label_remove(njs_vm_t *vm, njs_parser_scope_t *scope,
+    nxt_str_t *name, uint32_t hash);
 njs_ret_t njs_variable_reference(njs_vm_t *vm, njs_parser_scope_t *scope,
     njs_parser_node_t *node, nxt_str_t *name, uint32_t hash,
     njs_reference_type_t type);
index cc733d59d902eaa46e72f704153ea887efc9cf83..c31ef3e4a631715f34794949160b6a3c80de91cc 100644 (file)
@@ -2434,6 +2434,9 @@ static njs_unit_test_t  njs_test[] =
     { nxt_string("break"),
       nxt_string("SyntaxError: Illegal break statement in 1") },
 
+    { nxt_string("{break}"),
+      nxt_string("SyntaxError: Illegal break statement in 1") },
+
     { nxt_string("\nbreak"),
       nxt_string("SyntaxError: Illegal break statement in 2") },
 
@@ -2494,6 +2497,268 @@ static njs_unit_test_t  njs_test[] =
                  "for (i in a) if (a[i] > 4) break; s += a[i]; s"),
       nxt_string("5") },
 
+    /* Labels. */
+
+    { nxt_string("var n = 0; a:{n++}; a:{n++}; n"),
+      nxt_string("2") },
+
+    { nxt_string("a: throw 'a'"),
+      nxt_string("a") },
+
+    { nxt_string("a : var n = 0; b :++n"),
+      nxt_string("1") },
+
+    { nxt_string("a:{a:1}"),
+      nxt_string("SyntaxError: Label \"a\" has already been declared in 1") },
+
+    { nxt_string("for (var i in [1]) {break b}"),
+      nxt_string("SyntaxError: Undefined label \"b\" in 1") },
+
+    { nxt_string("for (var i in [1]) {continue b}"),
+      nxt_string("SyntaxError: Undefined label \"b\" in 1") },
+
+    { nxt_string("a:{break b}"),
+      nxt_string("SyntaxError: Undefined label \"b\" in 1") },
+
+    { nxt_string("a:{continue b}"),
+      nxt_string("SyntaxError: Undefined label \"b\" in 1") },
+
+#if 0 /* TODO */
+    { nxt_string("a:{1; break a}"),
+      nxt_string("1") },
+#endif
+
+    { nxt_string("var a = 0; a:{a++}; a"),
+      nxt_string("1") },
+
+    { nxt_string("var a = 0; a:{break a; a++}; a"),
+      nxt_string("0") },
+
+    { nxt_string("var r = 0; "
+                 "out: for (var i in [1,2,3]) { if (i == 2) {break out;}; r++}; r"),
+      nxt_string("2") },
+
+    { nxt_string("var r = 0; "
+                 "out: for (var i = 0; i < 5; i++) { if (i == 2) {break out;}; r++}; r"),
+      nxt_string("2") },
+
+    { nxt_string("var l1 = 0, l2 = 0; "
+                 "out: "
+                 "for (var i in [1,2,3]) { "
+                 "  for (var j in [1,2,3]) { "
+                 "    if (i == 1 && j == 1) {break;}"
+                 "    l2++;"
+                 "  }"
+                 "  l1++;"
+                 "}; [l1, l2]"),
+      nxt_string("3,7") },
+
+    { nxt_string("var l1 = 0, l2 = 0; "
+                 "out: "
+                 "for (var i in [1,2,3]) { "
+                 "  for (var j in [1,2,3]) { "
+                 "    if (i == 1 && j == 1) {break out;}"
+                 "    l2++;"
+                 "  }"
+                 "  l1++;"
+                 "}; [l1, l2]"),
+      nxt_string("1,4") },
+
+    { nxt_string("var l1 = 0, l2 = 0; "
+                 "out: "
+                 "for (var i in [1,2,3]) { "
+                 "  for (var j in [1,2,3]) { "
+                 "    if (i == 1 && j == 1) {continue out;}"
+                 "    l2++;"
+                 "  }"
+                 "  l1++;"
+                 "}; [l1, l2]"),
+      nxt_string("2,7") },
+
+    { nxt_string("var l1 = 0, l2 = 0; "
+                 "out: "
+                 "for (var i in [1,2,3]) { "
+                 "  l1++;"
+                 "  switch (i) { "
+                 "    case '1':"
+                 "      break out;"
+                 "    default:"
+                 "  }"
+                 "  l2++;"
+                 "}; [l1, l2]"),
+      nxt_string("2,1") },
+
+    { nxt_string("var l1 = 0, l2 = 0; "
+                 "out: "
+                 "for (var i in [1,2,3]) { "
+                 "  l1++;"
+                 "  switch (i) { "
+                 "    case '1':"
+                 "      continue out;"
+                 "    default:"
+                 "  }"
+                 "  l2++;"
+                 "}; [l1, l2]"),
+      nxt_string("3,2") },
+
+    { nxt_string("var l1 = 0, l2 = 0, i = 0, j; "
+                 "out: "
+                 "while (i < 3) { "
+                 "  j = 0;"
+                 "  while (j < 3) { "
+                 "    if (i == 1 && j == 1) {break out;}"
+                 "    l2++;"
+                 "    j++;"
+                 "  }"
+                 "  l1++;"
+                 "  i++;"
+                 "}; [l1, l2]"),
+      nxt_string("1,4") },
+
+    { nxt_string("var l1 = 0, l2 = 0, i = 0, j; "
+                 "out: "
+                 "while (i < 3) { "
+                 "  j = 0;"
+                 "  while (j < 3) { "
+                 "    if (i == 1 && j == 1) {i++; continue out;}"
+                 "    l2++;"
+                 "    j++;"
+                 "  }"
+                 "  l1++;"
+                 "  i++;"
+                 "}; [l1, l2]"),
+      nxt_string("2,7") },
+
+    { nxt_string("var l1 = 0, l2 = 0, i = 0, j; "
+                 "out: "
+                 "do { "
+                 "  j = 0;"
+                 "  do { "
+                 "    if (i == 1 && j == 1) {break out;}"
+                 "    l2++;"
+                 "    j++;"
+                 "  } while (j < 3)"
+                 "  l1++;"
+                 "  i++;"
+                 "} while (i < 3); [l1, l2]"),
+      nxt_string("1,4") },
+
+    { nxt_string("var l1 = 0, l2 = 0, i = 0, j; "
+                 "out: "
+                 "do { "
+                 "  j = 0;"
+                 "  do { "
+                 "    if (i == 1 && j == 1) {i++; continue out;}"
+                 "    l2++;"
+                 "    j++;"
+                 "  } while (j < 3)"
+                 "  l1++;"
+                 "  i++;"
+                 "} while (i < 3); [l1, l2]"),
+      nxt_string("2,7") },
+
+    { nxt_string("out1: while (1) { out2: while (1) { "
+                 "  try { break out1; break out2; } catch (e) {}"
+                 "}}"),
+      nxt_string("InternalError: break/return instructions with different labels "
+                 "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+    { nxt_string("out1: while (1) { out2: while (1) { "
+                 "  try { } catch (e) {break out1; break out2;} finally {}"
+                 "}}"),
+      nxt_string("InternalError: break/return instructions with different labels "
+                 "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+    { nxt_string("out1: while (1) { out2: while (1) { "
+                 "  try { break out1; } catch (e) {break out2;} finally {}"
+                 "}}"),
+      nxt_string("InternalError: try break/return instructions with different labels "
+                 "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+    { nxt_string("out1: while (1) { out2: while (1) { "
+                 "  try { break out1; break out2; } finally {}"
+                 "}}"),
+      nxt_string("InternalError: break/return instructions with different labels "
+                 "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+    { nxt_string("out1: while (1) { out2: while (1) { "
+                 "  try { continue out1; continue out2; } catch (e) {}"
+                 "}}"),
+      nxt_string("InternalError: continue instructions with different labels "
+                 "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+    { nxt_string("out1: while (1) { out2: while (1) { "
+                 "  try { continue out1; } catch (e) {continue out2;} finally {}"
+                 "}}"),
+      nxt_string("InternalError: try continue instructions with different labels "
+                 "(\"out1\" vs \"out2\") from try-catch block are not supported") },
+
+    { nxt_string("function f() {"
+                 "  a:{ try { try { return 'a'; } catch (e) {break a;} finally {} } "
+                 "      catch (e) {} finally {}; }"
+                 "}"),
+      nxt_string("InternalError: try break/return instructions with different labels "
+                 "(\"@return\" vs \"a\") from try-catch block are not supported") },
+
+    { nxt_string("a:{ try { try { continue a; } catch (e) {} finally {} } "
+                 "    catch (e) {} finally {}; "
+                 "}"),
+      nxt_string("SyntaxError: Illegal continue statement in 1") },
+
+    { nxt_string("var i = 0, j = 0, r = 0;"
+                 "out1: while (i < 3) "
+                 "{ "
+                 "  i++;"
+                 "  out2: while (j < 3) { "
+                 "      j++; try { break out1; } catch (e) {} finally {r++}"
+                 "  }"
+                 "}; [i, j, r]"),
+      nxt_string("1,1,1") },
+
+    { nxt_string("var i = 0, j = 0, r = 0;"
+                 "out1: while (i < 3) "
+                 "{ "
+                 "  i++;"
+                 "  out2: while (j < 3) { "
+                 "      j++; try { continue out1; } catch (e) {} finally {r++}"
+                 "  }"
+                 "}; [i, j, r]"),
+      nxt_string("3,3,3") },
+
+    { nxt_string("var c=0,fin=0;"
+                 "try {"
+                 " while (c < 2) {"
+                 "    try { c += 1; throw 'e';}"
+                 "    finally { fin = 1; break;}"
+                 "    fin = -1;"
+                 "    c += 2;"
+                 " }"
+                 "} catch(e) {c = 10;}; [c, fin]"),
+      nxt_string("1,1") },
+
+    /* jumping out of a nested try-catch block. */
+
+    { nxt_string("var r = 0; "
+                 "function f () { try { try {return 'a';} finally { r++; }} "
+                 "                finally { r++; } }; "
+                 "[f(), r]"),
+      nxt_string("a,2") },
+
+    { nxt_string("function f(n) { "
+                 "  var r1 = 0, r2 = 0, r3 = 0;"
+                 "  a:{ try { try { "
+                 "              if (n == 0) { break a; } "
+                 "              if (n == 1) { throw 'a'; } "
+                 "            } "
+                 "            catch (e) { break a; } finally { r1++; } } "
+                 "      catch (e) {} "
+                 "      finally { r2++; } "
+                 "      r3++;  "
+                 "  }; "
+                 "return [r1, r2, r3]"
+                 "}; njs.dump([f(0), f(1), f(3)])"),
+      nxt_string("[[1,1,0],[1,1,0],[1,1,1]]") },
+
     /**/
 
     { nxt_string("var i; for (i = 0; i < 10; i++) { i += 1 } i"),