Repository: nginx/njs (c)

Issue:
SEGV in njs_function_lambda_call
### Environment

```
OS      : Linux ubuntu 5.11.10 #1 SMP Sat Oct 30 23:40:08 CST 2021 x86_64 x86_64 x86_64 GNU/Linux
Commit  : c62a9fb92b102c90a66aa724cb9054183a33a68c
Version : 0.7.4
Build   : 
          NJS_CFLAGS="$NJS_CFLAGS -fsanitize=address"
          NJS_CFLAGS="$NJS_CFLAGS -fno-omit-frame-pointer"
```

### Proof of concept

```js
async function f() {
    await 1;
    var v = 2;

    function g() {
      v + 1;
    }

    function s() {
      g + 1;
    }

    g();
}

f();
```

### Stack dump

```
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==3050875==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x00000043a7c6 bp 0x7ffcd6f10b00 sp 0x7ffcd6f10920 T3050875)
==3050875==The signal is caused by a READ memory access.
==3050875==Hint: address points to the zero page.
    #0 0x43a7c6 in njs_scope_valid_value /njs/src/njs_scope.h:86:10
    #1 0x43a7c6 in njs_vmcode_await /njs/src/njs_vmcode.c:1924:13
    #2 0x43a7c6 in njs_vmcode_interpreter /njs/src/njs_vmcode.c:861:24
    #3 0x468070 in njs_function_lambda_call /njs/src/njs_function.c:693:11
    #4 0x4bd170 in njs_async_function_frame_invoke /njs/src/njs_async.c:32:11
    #5 0x4364d7 in njs_vmcode_interpreter /njs/src/njs_vmcode.c:799:23
    #6 0x4bd35d in njs_await_fulfilled /njs/src/njs_async.c:91:11
    #7 0x468574 in njs_function_native_call /njs/src/njs_function.c:728:11
    #8 0x467941 in njs_function_frame_invoke /njs/src/njs_function.c:766:16
    #9 0x467941 in njs_function_call2 /njs/src/njs_function.c:592:11
    #10 0x4b6938 in njs_function_call /njs/src/njs_function.h:178:12
    #11 0x4b6938 in njs_promise_reaction_job /njs/src/njs_promise.c:1171:15
    #12 0x468574 in njs_function_native_call /njs/src/njs_function.c:728:11
    #13 0x433a27 in njs_vm_invoke /njs/src/njs_vm.c:428:12
    #14 0x433a27 in njs_vm_call /njs/src/njs_vm.c:412:12
    #15 0x433a27 in njs_vm_handle_events /njs/src/njs_vm.c:572:19
    #16 0x433a27 in njs_vm_run /njs/src/njs_vm.c:532:12
    #17 0x428d13 in njs_process_script /njs/src/njs_shell.c:1059:15
    #18 0x428763 in njs_process_file /njs/src/njs_shell.c:754:11
    #19 0x428763 in main /njs/src/njs_shell.c:435:15
    #20 0x7f8424948082 in __libc_start_main /build/glibc-KZwQYS/glibc-2.31/csu/../csu/libc-start.c:308:16
    #21 0x406d3d in _start (/home/q1iq/Documents/njs-dump/njs/build/njs+0x406d3d)

UndefinedBehaviorSanitizer can not provide additional info.
SUMMARY: UndefinedBehaviorSanitizer: SEGV /njs/src/njs_scope.h:86:10 in njs_scope_valid_value
==3050875==ABORTING
```

### Credit

Q1IQ(@Q1IQ)


Discussion:

Patch:
diff --git a/src/njs_builtin.c b/src/njs_builtin.c
--- a/src/njs_builtin.c
+++ b/src/njs_builtin.c
@@ -759,7 +759,6 @@ njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop,
 {
     njs_value_t          *value;
     njs_variable_t       *var;
-    njs_function_t       *function;
     njs_rbtree_node_t    *rb_node;
     njs_variable_node_t  *node, var_node;
 
@@ -788,15 +787,6 @@ njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop,
 
     value = njs_scope_valid_value(vm, var->index);
 
-    if (var->type == NJS_VARIABLE_FUNCTION && njs_is_undefined(value)) {
-        njs_value_assign(value, &var->value);
-
-        function = njs_function_value_copy(vm, value);
-        if (njs_slow_path(function == NULL)) {
-            return NJS_ERROR;
-        }
-    }
-
     if (setval != NULL) {
         njs_value_assign(value, setval);
     }
diff --git a/src/njs_disassembler.c b/src/njs_disassembler.c
--- a/src/njs_disassembler.c
+++ b/src/njs_disassembler.c
@@ -30,9 +30,6 @@ static njs_code_name_t  code_names[] = {
     { NJS_VMCODE_TEMPLATE_LITERAL, sizeof(njs_vmcode_template_literal_t),
           njs_str("TEMPLATE LITERAL") },
 
-    { NJS_VMCODE_FUNCTION_COPY, sizeof(njs_vmcode_function_copy_t),
-          njs_str("FUNCTION COPY   ") },
-
     { NJS_VMCODE_PROPERTY_GET, sizeof(njs_vmcode_prop_get_t),
           njs_str("PROP GET        ") },
     { NJS_VMCODE_PROPERTY_ATOM_GET, sizeof(njs_vmcode_prop_get_t),
diff --git a/src/njs_function.c b/src/njs_function.c
--- a/src/njs_function.c
+++ b/src/njs_function.c
@@ -394,6 +394,19 @@ njs_function_lambda_frame(njs_vm_t *vm, njs_function_t *function,
 
     lambda = function->u.lambda;
 
+    /*
+     * Lambda frame has the following layout:
+     *  njs_frame_t | p0 , p2, ..., pn | v0, v1, ..., vn
+     *  where:
+     *  p0, p1, ..., pn - pointers to arguments and locals,
+     *  v0, v1, ..., vn - values of arguments and locals.
+     *  n - number of arguments + locals.
+     *
+     *  Normally, the pointers point to the values directly after them,
+     *  but if a value was captured as a closure by an inner function,
+     *  pn points to a value allocated from the heap.
+     */
+
     args_count = njs_max(nargs, lambda->nargs);
     value_count = args_count + lambda->nlocal;
 
@@ -523,7 +536,6 @@ njs_function_lambda_call(njs_vm_t *vm, njs_value_t *retval, void *promise_cap)
     njs_value_t            *args, **local, *value;
     njs_value_t            **cur_local, **cur_closures;
     njs_function_t         *function;
-    njs_declaration_t      *declr;
     njs_function_lambda_t  *lambda;
 
     frame = (njs_frame_t *) vm->top_frame;
@@ -582,29 +594,6 @@ njs_function_lambda_call(njs_vm_t *vm, njs_value_t *retval, void *promise_cap)
 
     vm->active_frame = frame;
 
-    /* Closures */
-
-    n = lambda->ndeclarations;
-
-    while (n != 0) {
-        n--;
-
-        declr = &lambda->declarations[n];
-        value = njs_scope_value(vm, declr->index);
-
-        *value = *declr->value;
-
-        function = njs_function_value_copy(vm, value);
-        if (njs_slow_path(function == NULL)) {
-            return NJS_ERROR;
-        }
-
-        ret = njs_function_capture_closure(vm, function, function->u.lambda);
-        if (njs_slow_path(ret != NJS_OK)) {
-            return ret;
-        }
-    }
-
     ret = njs_vmcode_interpreter(vm, lambda->start, retval, promise_cap, NULL);
 
     /* Restore current level. */
@@ -699,11 +688,11 @@ njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *native)
 njs_int_t
 njs_function_frame_save(njs_vm_t *vm, njs_frame_t *frame, u_char *pc)
 {
-    size_t              args_count, value_count, n;
-    njs_value_t         *start, *end, *p, **new, *value, **local;
-    njs_function_t      *function;
+    size_t                 args_count, value_count, n;
+    njs_value_t            **map, *value, **current_map;
+    njs_function_t         *function;
+    njs_native_frame_t     *active, *native;
     njs_function_lambda_t  *lambda;
-    njs_native_frame_t  *active, *native;
 
     *frame = *vm->active_frame;
 
@@ -721,34 +710,37 @@ njs_function_frame_save(njs_vm_t *vm, njs_frame_t *frame, u_char *pc)
     args_count = njs_max(native->nargs, lambda->nargs);
     value_count = args_count + lambda->nlocal;
 
-    new = (njs_value_t **) ((u_char *) native + NJS_FRAME_SIZE);
-    value = (njs_value_t *) (new + value_count);
-
-    native->arguments = value;
-    native->local = new + njs_function_frame_args_count(active);
-    native->pc = pc;
-
-    start = njs_function_frame_values(active, &end);
-    p = native->arguments;
+    /*
+     * We need to save the current frame state because it will be freed
+     * when the function returns.
+     *
+     *  To detect whether a value is captured as a closure,
+     *  we check whether the pointer is within the frame. In this case
+     *  the pointer is copied as is because the value it points to
+     *  is already allocated in the heap and will not be freed.
+     *  See njs_function_capture_closure() and njs_function_lambda_frame()
+     *  for details.
+     */
 
-    while (start < end) {
-        njs_value_assign(p, start++);
-        *new++ = p++;
-    }
+    map = (njs_value_t **) ((u_char *) native + NJS_FRAME_SIZE);
+    value = (njs_value_t *) (map + value_count);
 
-    /* Move all arguments. */
+    current_map = (njs_value_t **) ((u_char *) active + NJS_FRAME_SIZE);
 
-    p = native->arguments;
-    local = native->local + 1 /* this */;
+    for (n = 0; n < value_count; n++) {
+        if (njs_is_value_allocated_on_frame(active, current_map[n])) {
+            map[n] = &value[n];
+            njs_value_assign(&value[n], current_map[n]);
 
-    for (n = 0; n < function->args_count; n++) {
-        if (!njs_is_valid(p)) {
-            njs_set_undefined(p);
+        } else {
+            map[n] = current_map[n];
         }
-
-        *local++ = p++;
     }
 
+    native->arguments = value;
+    native->local = map + args_count;
+    native->pc = pc;
+
     return NJS_OK;
 }
 
@@ -770,7 +762,6 @@ njs_int_t
 njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function,
     njs_function_lambda_t *lambda)
 {
-    void                *start, *end;
     uint32_t            n;
     njs_value_t         *value, **closure;
     njs_native_frame_t  *frame;
@@ -785,9 +776,6 @@ njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function,
         frame = frame->previous;
     }
 
-    start = frame;
-    end = frame->free;
-
     closure = njs_function_closures(function);
     n = lambda->nclosures;
 
@@ -796,7 +784,7 @@ njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function,
 
         value = njs_scope_value(vm, lambda->closures[n]);
 
-        if (start <= (void *) value && (void *) value < end) {
+        if (njs_is_value_allocated_on_frame(frame, value)) {
             value = njs_scope_value_clone(vm, lambda->closures[n], value);
             if (njs_slow_path(value == NULL)) {
                 return NJS_ERROR;
@@ -812,14 +800,14 @@ njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function,
 
 
 njs_inline njs_value_t *
-njs_function_closure_value(njs_vm_t *vm, njs_value_t **scope, njs_index_t index,
-    void *start, void *end)
+njs_function_closure_value(njs_vm_t *vm, njs_native_frame_t *frame,
+    njs_value_t **scope, njs_index_t index)
 {
     njs_value_t  *value, *newval;
 
     value = scope[njs_scope_index_value(index)];
 
-    if (start <= (void *) value && end > (void *) value) {
+    if (njs_is_value_allocated_on_frame(frame, value)) {
         newval = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t));
         if (njs_slow_path(newval == NULL)) {
             njs_memory_error(vm);
@@ -839,7 +827,6 @@ njs_function_closure_value(njs_vm_t *vm, njs_value_t **scope, njs_index_t index,
 njs_int_t
 njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function)
 {
-    void                   *start, *end;
     uint32_t               n;
     njs_value_t            *value, **refs, **global;
     njs_index_t            *indexes, index;
@@ -858,9 +845,6 @@ njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function)
         native = native->previous;
     }
 
-    start = native;
-    end = native->free;
-
     indexes = lambda->closures;
     refs = njs_function_closures(function);
 
@@ -875,12 +859,12 @@ njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function)
 
         switch (njs_scope_index_type(index)) {
         case NJS_LEVEL_LOCAL:
-            value = njs_function_closure_value(vm, native->local, index,
-                                               start, end);
+            value = njs_function_closure_value(vm, native, native->local,
+                                               index);
             break;
 
         case NJS_LEVEL_GLOBAL:
-            value = njs_function_closure_value(vm, global, index, start, end);
+            value = njs_function_closure_value(vm, native, global, index);
             break;
 
         default:
@@ -950,9 +934,8 @@ njs_function_prototype_create(njs_vm_t *vm, njs_object_prop_t *prop,
     uint32_t unused, njs_value_t *value, njs_value_t *setval,
     njs_value_t *retval)
 {
-    njs_value_t     *proto, proto_value, *cons;
-    njs_object_t    *prototype;
-    njs_function_t  *function;
+    njs_value_t   *proto, proto_value, *cons;
+    njs_object_t  *prototype;
 
     if (setval == NULL) {
         prototype = njs_object_alloc(vm);
@@ -965,11 +948,6 @@ njs_function_prototype_create(njs_vm_t *vm, njs_object_prop_t *prop,
         setval = &proto_value;
     }
 
-    function = njs_function_value_copy(vm, value);
-    if (njs_slow_path(function == NULL)) {
-        return NJS_ERROR;
-    }
-
     proto = njs_function_property_prototype_set(vm, njs_object_hash(value),
                                                 setval);
     if (njs_slow_path(proto == NULL)) {
diff --git a/src/njs_function.h b/src/njs_function.h
--- a/src/njs_function.h
+++ b/src/njs_function.h
@@ -13,9 +13,6 @@ struct njs_function_lambda_s {
     uint32_t                       nclosures;
     uint32_t                       nlocal;
 
-    njs_declaration_t              *declarations;
-    uint32_t                       ndeclarations;
-
     njs_index_t                    self;
 
     uint32_t                       nargs;
@@ -205,29 +202,15 @@ njs_function_frame_size(njs_native_frame_t *frame)
 }
 
 
-njs_inline size_t
-njs_function_frame_args_count(njs_native_frame_t *frame)
-{
-    uintptr_t  start;
-
-    start = (uintptr_t) ((u_char *) frame + NJS_FRAME_SIZE);
-
-    return ((uintptr_t) frame->local - start) / sizeof(njs_value_t *);
-}
-
-
-njs_inline njs_value_t *
-njs_function_frame_values(njs_native_frame_t *frame, njs_value_t **end)
+njs_inline njs_bool_t
+njs_is_value_allocated_on_frame(njs_native_frame_t *frame, njs_value_t *value)
 {
-    size_t     count;
-    uintptr_t  start;
-
-    start = (uintptr_t) ((u_char *) frame + NJS_FRAME_SIZE);
-    count = ((uintptr_t) frame->arguments - start) / sizeof(njs_value_t *);
+    void  *start, *end;
 
-    *end = frame->arguments + count;
+    start = frame;
+    end = frame->free;
 
-    return frame->arguments;
+    return start <= (void *) value && (void *) value < end;
 }
 
 
diff --git a/src/njs_generator.c b/src/njs_generator.c
--- a/src/njs_generator.c
+++ b/src/njs_generator.c
@@ -890,11 +890,10 @@ static njs_int_t
 njs_generate_name(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    njs_int_t                   ret;
-    njs_variable_t              *var;
-    njs_parser_scope_t          *scope;
-    njs_vmcode_variable_t       *variable;
-    njs_vmcode_function_copy_t  *copy;
+    njs_int_t              ret;
+    njs_variable_t         *var;
+    njs_parser_scope_t     *scope;
+    njs_vmcode_variable_t  *variable;
 
     var = njs_variable_reference(vm, node);
     if (njs_slow_path(var == NULL)) {
@@ -906,13 +905,6 @@ njs_generate_name(njs_vm_t *vm, njs_generator_t *generator,
         return njs_generator_stack_pop(vm, generator, NULL);
     }
 
-    if (var->function && var->type == NJS_VARIABLE_FUNCTION) {
-        njs_generate_code(generator, njs_vmcode_function_copy_t, copy,
-                          NJS_VMCODE_FUNCTION_COPY, node);
-        copy->function = &var->value;
-        copy->retval = node->index;
-    }
-
     if (var->init) {
         return njs_generator_stack_pop(vm, generator, NULL);
     }
@@ -935,10 +927,9 @@ static njs_int_t
 njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar)
 {
-    njs_variable_t              *var;
-    njs_parser_scope_t          *scope;
-    njs_vmcode_variable_t       *variable;
-    njs_vmcode_function_copy_t  *copy;
+    njs_variable_t         *var;
+    njs_parser_scope_t     *scope;
+    njs_vmcode_variable_t  *variable;
 
     var = njs_variable_reference(vm, node);
 
@@ -958,13 +949,6 @@ njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator,
         }
     }
 
-    if (var->function && var->type == NJS_VARIABLE_FUNCTION) {
-        njs_generate_code(generator, njs_vmcode_function_copy_t, copy,
-                          NJS_VMCODE_FUNCTION_COPY, node);
-        copy->function = &var->value;
-        copy->retval = node->index;
-    }
-
     if (var->init) {
         return NJS_OK;
     }
@@ -4293,7 +4277,6 @@ njs_generate_function_scope(njs_vm_t *vm, njs_generator_t *prev,
     const njs_str_t *name)
 {
     njs_int_t        ret;
-    njs_arr_t        *arr;
     njs_uint_t       depth;
     njs_vm_code_t    *code;
     njs_generator_t  generator;
@@ -4327,10 +4310,6 @@ njs_generate_function_scope(njs_vm_t *vm, njs_generator_t *prev,
     lambda->nclosures = generator.closures->items;
     lambda->nlocal = node->scope->items;
 
-    arr = node->scope->declarations;
-    lambda->declarations = (arr != NULL) ? arr->start : NULL;
-    lambda->ndeclarations = (arr != NULL) ? arr->items : 0;
-
     return NJS_OK;
 }
 
@@ -4339,11 +4318,15 @@ njs_vm_code_t *
 njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_scope_t *scope, const njs_str_t *name)
 {
-    u_char         *p;
-    int64_t        nargs;
-    njs_int_t      ret;
-    njs_uint_t     index;
-    njs_vm_code_t  *code;
+    u_char                 *p, *code_start;
+    size_t                 code_size, prelude;
+    int64_t                nargs;
+    njs_int_t              ret;
+    njs_arr_t              *arr;
+    njs_uint_t             index, n;
+    njs_vm_code_t          *code;
+    njs_declaration_t      *declr;
+    njs_vmcode_function_t  *fun;
 
     generator->code_size = 128;
 
@@ -4414,6 +4397,45 @@ njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator,
 
     } while (generator->state != NULL);
 
+    arr = scope->declarations;
+    scope->declarations = NULL;
+
+    if (arr != NULL && arr->items > 0) {
+        declr = arr->start;
+        prelude = sizeof(njs_vmcode_function_t) * arr->items;
+        code_size = generator->code_end - generator->code_start;
+
+        for (n = 0; n < arr->items; n++) {
+            fun = (njs_vmcode_function_t *) njs_generate_reserve(vm,
+                                     generator, sizeof(njs_vmcode_function_t));
+            if (njs_slow_path(fun == NULL)) {
+                njs_memory_error(vm);
+                return NULL;
+            }
+
+            generator->code_end += sizeof(njs_vmcode_function_t);
+            fun->code = NJS_VMCODE_FUNCTION;
+
+            fun->lambda = declr[n].lambda;
+            fun->async = declr[n].async;
+            fun->retval = declr[n].index;
+        }
+
+        code_start = njs_mp_alloc(vm->mem_pool, code_size + prelude);
+        if (njs_slow_path(code_start == NULL)) {
+            njs_memory_error(vm);
+            return NULL;
+        }
+
+        memcpy(code_start, &generator->code_start[code_size], prelude);
+        memcpy(&code_start[prelude], generator->code_start, code_size);
+
+        njs_mp_free(vm->mem_pool, generator->code_start);
+
+        generator->code_start = code_start;
+        generator->code_end = code_start + code_size + prelude;
+    }
+
     code = njs_arr_item(vm->codes, index);
     code->start = generator->code_start;
     code->end = generator->code_end;
diff --git a/src/njs_parser.c b/src/njs_parser.c
--- a/src/njs_parser.c
+++ b/src/njs_parser.c
@@ -839,10 +839,9 @@ njs_inline njs_int_t
 njs_parser_expect_semicolon(njs_parser_t *parser, njs_lexer_token_t *token)
 {
     if (token->type != NJS_TOKEN_SEMICOLON) {
-        if (parser->strict_semicolon
-            || (token->type != NJS_TOKEN_END
-                && token->type != NJS_TOKEN_CLOSE_BRACE
-                && parser->lexer->prev_type != NJS_TOKEN_LINE_END))
+        if (token->type != NJS_TOKEN_END
+            && token->type != NJS_TOKEN_CLOSE_BRACE
+            && parser->lexer->prev_type != NJS_TOKEN_LINE_END)
         {
             return NJS_DECLINED;
         }
@@ -5408,10 +5407,6 @@ static njs_int_t
 njs_parser_do_while_semicolon(njs_parser_t *parser, njs_lexer_token_t *token,
     njs_queue_link_t *current)
 {
-    if (parser->strict_semicolon) {
-        return njs_parser_failed(parser);
-    }
-
     parser->target->right = parser->node;
     parser->node = parser->target;
 
@@ -6257,10 +6252,9 @@ njs_parser_break_continue(njs_parser_t *parser, njs_lexer_token_t *token,
             break;
         }
 
-        if (parser->strict_semicolon
-            || (token->type != NJS_TOKEN_END
-                && token->type != NJS_TOKEN_CLOSE_BRACE
-                && parser->lexer->prev_type != NJS_TOKEN_LINE_END))
+        if (token->type != NJS_TOKEN_END
+            && token->type != NJS_TOKEN_CLOSE_BRACE
+            && parser->lexer->prev_type != NJS_TOKEN_LINE_END)
         {
             return njs_parser_failed(parser);
         }
@@ -6314,9 +6308,7 @@ njs_parser_return_statement(njs_parser_t *parser, njs_lexer_token_t *token,
         return njs_parser_failed(parser);
 
     default:
-        if (!parser->strict_semicolon
-            && parser->lexer->prev_type == NJS_TOKEN_LINE_END)
-        {
+        if (parser->lexer->prev_type == NJS_TOKEN_LINE_END) {
             break;
         }
 
@@ -7091,8 +7083,7 @@ njs_parser_function_declaration(njs_parser_t *parser, njs_lexer_token_t *token,
 
     njs_lexer_consume_token(parser->lexer, 1);
 
-    var = njs_variable_function_add(parser, parser->scope, atom_id,
-                                    NJS_VARIABLE_FUNCTION);
+    var = njs_variable_function_add(parser, parser->scope, atom_id);
     if (var == NULL) {
         return NJS_ERROR;
     }
diff --git a/src/njs_parser.h b/src/njs_parser.h
--- a/src/njs_parser.h
+++ b/src/njs_parser.h
@@ -84,7 +84,6 @@ struct njs_parser_s {
     uint8_t                         use_lhs;
 
     uint8_t                         module;
-    njs_bool_t                      strict_semicolon;
 
     njs_str_t                       file;
     uint32_t                        line;
@@ -109,8 +108,9 @@ typedef struct {
 
 
 typedef struct {
-    njs_value_t                     *value;
+    njs_function_lambda_t           *lambda;
     njs_index_t                     index;
+    njs_bool_t                      async;
 } njs_declaration_t;
 
 
diff --git a/src/njs_value.c b/src/njs_value.c
--- a/src/njs_value.c
+++ b/src/njs_value.c
@@ -562,7 +562,6 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value,
     uint32_t        index;
     njs_int_t       ret;
     njs_object_t    *obj;
-    njs_function_t  *function;
 
     njs_assert(atom_id != NJS_ATOM_STRING_unknown);
 
@@ -585,6 +584,7 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value,
 
     case NJS_OBJECT:
     case NJS_ARRAY:
+    case NJS_FUNCTION:
     case NJS_ARRAY_BUFFER:
     case NJS_DATA_VIEW:
     case NJS_TYPED_ARRAY:
@@ -595,15 +595,6 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value,
         obj = njs_object(value);
         break;
 
-    case NJS_FUNCTION:
-        function = njs_function_value_copy(vm, value);
-        if (njs_slow_path(function == NULL)) {
-            return NJS_ERROR;
-        }
-
-        obj = &function->object;
-        break;
-
     case NJS_UNDEFINED:
     case NJS_NULL:
     default:
diff --git a/src/njs_variable.c b/src/njs_variable.c
--- a/src/njs_variable.c
+++ b/src/njs_variable.c
@@ -36,7 +36,7 @@ njs_variable_add(njs_parser_t *parser, njs_parser_scope_t *scope,
 
 njs_variable_t *
 njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope,
-    uintptr_t atom_id, njs_variable_type_t type)
+    uintptr_t atom_id)
 {
     njs_bool_t             ctor;
     njs_variable_t         *var;
@@ -44,14 +44,15 @@ njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope,
     njs_parser_scope_t     *root;
     njs_function_lambda_t  *lambda;
 
-    root = njs_variable_scope_find(parser, scope, atom_id, type);
+    root = njs_variable_scope_find(parser, scope, atom_id,
+                                   NJS_VARIABLE_FUNCTION);
     if (njs_slow_path(root == NULL)) {
         njs_parser_ref_error(parser, "scope not found");
         return NULL;
     }
 
-    var = njs_variable_scope_add(parser, root, scope, atom_id, type,
-                                 NJS_INDEX_ERROR);
+    var = njs_variable_scope_add(parser, root, scope, atom_id,
+                                 NJS_VARIABLE_FUNCTION, NJS_INDEX_ERROR);
     if (njs_slow_path(var == NULL)) {
         return NULL;
     }
@@ -77,15 +78,15 @@ njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope,
     }
 
     var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL,
-                                 type);
+                                 NJS_VARIABLE_FUNCTION);
 
-    declr->value = &var->value;
+    declr->lambda = lambda;
+    declr->async = !ctor;
     declr->index = var->index;
 
     root->items++;
 
     var->type = NJS_VARIABLE_FUNCTION;
-    var->function = 1;
 
     return var;
 }
@@ -173,7 +174,6 @@ njs_variable_scope_find(njs_parser_t *parser, njs_parser_scope_t *scope,
 
         if (var != NULL && var->scope == root) {
             if (var->self) {
-                var->function = 0;
                 return scope;
             }
 
diff --git a/src/njs_variable.h b/src/njs_variable.h
--- a/src/njs_variable.h
+++ b/src/njs_variable.h
@@ -26,7 +26,6 @@ typedef struct {
     njs_bool_t            self;
     njs_bool_t            init;
     njs_bool_t            closure;
-    njs_bool_t            function;
 
     njs_parser_scope_t    *scope;
     njs_parser_scope_t    *original;
@@ -62,7 +61,7 @@ typedef struct {
 njs_variable_t *njs_variable_add(njs_parser_t *parser,
     njs_parser_scope_t *scope, uintptr_t atom_id, njs_variable_type_t type);
 njs_variable_t *njs_variable_function_add(njs_parser_t *parser,
-    njs_parser_scope_t *scope, uintptr_t atom_id, njs_variable_type_t type);
+    njs_parser_scope_t *scope, uintptr_t atom_id);
 njs_variable_t * njs_label_add(njs_vm_t *vm, njs_parser_scope_t *scope,
     uintptr_t atom_id);
 njs_variable_t *njs_label_find(njs_vm_t *vm, njs_parser_scope_t *scope,
diff --git a/src/njs_vm.c b/src/njs_vm.c
--- a/src/njs_vm.c
+++ b/src/njs_vm.c
@@ -310,7 +310,6 @@ njs_vm_compile_module(njs_vm_t *vm, njs_str_t *name, u_char **start,
     u_char *end)
 {
     njs_int_t              ret;
-    njs_arr_t              *arr;
     njs_mod_t              *module;
     njs_parser_t           parser;
     njs_vm_code_t          *code;
@@ -366,10 +365,6 @@ njs_vm_compile_module(njs_vm_t *vm, njs_str_t *name, u_char **start,
     lambda->start = generator.code_start;
     lambda->nlocal = scope->items;
 
-    arr = scope->declarations;
-    lambda->declarations = (arr != NULL) ? arr->start : NULL;
-    lambda->ndeclarations = (arr != NULL) ? arr->items : 0;
-
     module->function.u.lambda = lambda;
 
     return module;
diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c
--- a/src/njs_vmcode.c
+++ b/src/njs_vmcode.c
@@ -16,15 +16,12 @@ struct njs_property_next_s {
 static njs_jump_off_t njs_vmcode_object(njs_vm_t *vm, njs_value_t *retval);
 static njs_jump_off_t njs_vmcode_array(njs_vm_t *vm, u_char *pc,
     njs_value_t *retval);
-static njs_jump_off_t njs_vmcode_function(njs_vm_t *vm, u_char *pc,
-    njs_value_t *retval);
+static njs_jump_off_t njs_vmcode_function(njs_vm_t *vm, u_char *pc);
 static njs_jump_off_t njs_vmcode_arguments(njs_vm_t *vm, u_char *pc);
 static njs_jump_off_t njs_vmcode_regexp(njs_vm_t *vm, u_char *pc,
     njs_value_t *retval);
 static njs_jump_off_t njs_vmcode_template_literal(njs_vm_t *vm,
     njs_value_t *retval);
-static njs_jump_off_t njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value,
-    njs_index_t retval);
 
 static njs_jump_off_t njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value,
     njs_value_t *key, njs_value_t *retval);
@@ -113,7 +110,6 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval,
     njs_vmcode_equal_jump_t      *equal;
     njs_vmcode_try_return_t      *try_return;
     njs_vmcode_method_frame_t    *method_frame;
-    njs_vmcode_function_copy_t   *fcopy;
     njs_vmcode_prop_accessor_t   *accessor;
     njs_vmcode_try_trampoline_t  *try_trampoline;
     njs_vmcode_function_frame_t  *function_frame;
@@ -157,7 +153,6 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval,
         NJS_GOTO_ROW(NJS_VMCODE_IF_EQUAL_JUMP),
         NJS_GOTO_ROW(NJS_VMCODE_PROPERTY_INIT),
         NJS_GOTO_ROW(NJS_VMCODE_RETURN),
-        NJS_GOTO_ROW(NJS_VMCODE_FUNCTION_COPY),
         NJS_GOTO_ROW(NJS_VMCODE_FUNCTION_FRAME),
         NJS_GOTO_ROW(NJS_VMCODE_METHOD_FRAME),
         NJS_GOTO_ROW(NJS_VMCODE_FUNCTION_CALL),
@@ -1155,9 +1150,7 @@ NEXT_LBL;
     CASE (NJS_VMCODE_FUNCTION):
         njs_vmcode_debug_opcode();
 
-        njs_vmcode_operand(vm, vmcode->operand1, retval);
-
-        ret = njs_vmcode_function(vm, pc, retval);
+        ret = njs_vmcode_function(vm, pc);
         if (njs_slow_path(ret < 0 && ret >= NJS_PREEMPT)) {
             goto error;
         }
@@ -1422,17 +1415,6 @@ NEXT_LBL;
 
         return NJS_OK;
 
-    CASE (NJS_VMCODE_FUNCTION_COPY):
-        njs_vmcode_debug_opcode();
-
-        fcopy = (njs_vmcode_function_copy_t *) pc;
-        ret = njs_vmcode_function_copy(vm, fcopy->function, fcopy->retval);
-        if (njs_slow_path(ret == NJS_ERROR)) {
-            goto error;
-        }
-
-        BREAK;
-
     CASE (NJS_VMCODE_FUNCTION_FRAME):
         njs_vmcode_debug_opcode();
 
@@ -1928,7 +1910,7 @@ njs_vmcode_array(njs_vm_t *vm, u_char *pc, njs_value_t *retval)
 
 
 static njs_jump_off_t
-njs_vmcode_function(njs_vm_t *vm, u_char *pc, njs_value_t *retval)
+njs_vmcode_function(njs_vm_t *vm, u_char *pc)
 {
     njs_function_t         *function;
     njs_vmcode_function_t  *code;
@@ -1948,7 +1930,7 @@ njs_vmcode_function(njs_vm_t *vm, u_char *pc, njs_value_t *retval)
 
     function->args_count = lambda->nargs - lambda->rest_parameters;
 
-    njs_set_function(retval, function);
+    njs_set_function(njs_scope_value(vm, code->retval), function);
 
     return sizeof(njs_vmcode_function_t);
 }
@@ -2032,27 +2014,6 @@ njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *retval)
 }
 
 
-static njs_jump_off_t
-njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value, njs_index_t retidx)
-{
-    njs_value_t     *retval;
-    njs_function_t  *function;
-
-    retval = njs_scope_value(vm, retidx);
-
-    if (!njs_is_valid(retval)) {
-        *retval = *value;
-
-        function = njs_function_value_copy(vm, retval);
-        if (njs_slow_path(function == NULL)) {
-            return NJS_ERROR;
-        }
-    }
-
-    return sizeof(njs_vmcode_function_copy_t);
-}
-
-
 static njs_jump_off_t
 njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *key,
     njs_value_t *init)
diff --git a/src/njs_vmcode.h b/src/njs_vmcode.h
--- a/src/njs_vmcode.h
+++ b/src/njs_vmcode.h
@@ -38,7 +38,6 @@ enum {
     NJS_VMCODE_IF_EQUAL_JUMP,
     NJS_VMCODE_PROPERTY_INIT,
     NJS_VMCODE_RETURN,
-    NJS_VMCODE_FUNCTION_COPY,
     NJS_VMCODE_FUNCTION_FRAME,
     NJS_VMCODE_METHOD_FRAME,
     NJS_VMCODE_FUNCTION_CALL,
@@ -381,13 +380,6 @@ typedef struct {
 } njs_vmcode_error_t;
 
 
-typedef struct {
-    njs_vmcode_t               code;
-    njs_value_t                *function;
-    njs_index_t                retval;
-} njs_vmcode_function_copy_t;
-
-
 typedef struct {
     njs_vmcode_t               code;
     njs_index_t                retval;


Test patch:
diff --git a/test/js/async_closure.t.js b/test/js/async_closure.t.js
new file mode 100644
--- /dev/null
+++ b/test/js/async_closure.t.js
@@ -0,0 +1,24 @@
+/*---
+includes: []
+flags: [async]
+---*/
+
+async function f() {
+    await 1;
+    var v = 2;
+
+    function g() {
+      return v + 1;
+    }
+
+    function s() {
+      g + 1;
+    }
+
+    return g();
+}
+
+f().then(v => {
+    assert.sameValue(v, 3)
+})
+.then($DONE, $DONE);
diff --git a/test/js/async_closure_arg.t.js b/test/js/async_closure_arg.t.js
new file mode 100644
--- /dev/null
+++ b/test/js/async_closure_arg.t.js
@@ -0,0 +1,24 @@
+/*---
+includes: []
+flags: [async]
+---*/
+
+async function f(v) {
+    await 1;
+    v = 2;
+
+    function g() {
+      return v + 1;
+    }
+
+    function s() {
+      g + 1;
+    }
+
+    return g();
+}
+
+f(42).then(v => {
+    assert.sameValue(v, 3)
+})
+.then($DONE, $DONE);
diff --git a/test/js/async_closure_share.t.js b/test/js/async_closure_share.t.js
new file mode 100644
--- /dev/null
+++ b/test/js/async_closure_share.t.js
@@ -0,0 +1,28 @@
+/*---
+includes: []
+flags: [async]
+---*/
+
+async function f() {
+    await 1;
+    var v = 'f';
+
+    function g() {
+        v += ':g';
+        return v;
+    }
+
+    function s() {
+        v += ':s';
+        return v;
+    }
+
+    return [g, s];
+}
+
+f().then(pair => {
+    pair[0]();
+    var v = pair[1]();
+    assert.sameValue(v, 'f:g:s');
+})
+.then($DONE, $DONE);