Coverage Report

Created: 2025-07-18 06:21

/src/njs/external/njs_shell.c
Line
Count
Source (jump to first uncovered line)
1
2
/*
3
 * Copyright (C) Dmitry Volyntsev
4
 * Copyright (C) NGINX, Inc.
5
 */
6
7
8
#include <njs.h>
9
#include <njs_unix.h>
10
#include <njs_arr.h>
11
#include <njs_queue.h>
12
#include <njs_rbtree.h>
13
14
#if (NJS_HAVE_QUICKJS)
15
#include <qjs.h>
16
#endif
17
18
#if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE)
19
20
#include <locale.h>
21
#include <signal.h>
22
#include <sys/select.h>
23
#if (NJS_HAVE_EDITLINE)
24
#include <editline/readline.h>
25
#elif (NJS_HAVE_EDIT_READLINE)
26
#include <edit/readline/readline.h>
27
#else
28
#include <readline/readline.h>
29
#endif
30
31
#endif
32
33
34
typedef struct {
35
    uint8_t                 disassemble;
36
    uint8_t                 denormals;
37
    uint8_t                 interactive;
38
    uint8_t                 module;
39
    uint8_t                 quiet;
40
    uint8_t                 sandbox;
41
    uint8_t                 safe;
42
    uint8_t                 version;
43
    uint8_t                 ast;
44
    uint8_t                 unhandled_rejection;
45
    uint8_t                 suppress_stdout;
46
    uint8_t                 opcode_debug;
47
    uint8_t                 generator_debug;
48
    uint8_t                 can_block;
49
    int                     exit_code;
50
    int                     stack_size;
51
52
    char                    *file;
53
    njs_str_t               command;
54
    size_t                  n_paths;
55
    njs_str_t               *paths;
56
    char                    **argv;
57
    njs_uint_t              argc;
58
59
    enum {
60
        NJS_ENGINE_NJS = 0,
61
        NJS_ENGINE_QUICKJS = 1,
62
    }                       engine;
63
} njs_opts_t;
64
65
66
typedef enum {
67
    NJS_LOG_ERROR = 4,
68
    NJS_LOG_WARN = 5,
69
    NJS_LOG_INFO = 7,
70
} njs_log_level_t;
71
72
73
typedef struct {
74
    size_t                  index;
75
    njs_arr_t               *suffix_completions;
76
} njs_completion_t;
77
78
79
typedef struct {
80
    NJS_RBTREE_NODE         (node);
81
    union {
82
        struct {
83
            njs_function_t      *function;
84
            njs_value_t         *args;
85
        }                       njs;
86
#if (NJS_HAVE_QUICKJS)
87
        struct {
88
            JSValue             function;
89
            JSValue             *args;
90
        }                       qjs;
91
#endif
92
    }                           u;
93
94
    njs_uint_t              nargs;
95
    uint32_t                id;
96
97
    njs_queue_link_t        link;
98
} njs_ev_t;
99
100
101
typedef struct {
102
    njs_str_t               name;
103
    uint64_t                time;
104
    njs_queue_link_t        link;
105
} njs_timelabel_t;
106
107
108
typedef struct {
109
    union {
110
        struct {
111
            njs_opaque_value_t  promise;
112
            njs_opaque_value_t  message;
113
        }                       njs;
114
#if (NJS_HAVE_QUICKJS)
115
        struct {
116
            JSValue             promise;
117
            JSValue             message;
118
        }                       qjs;
119
#endif
120
    }                           u;
121
} njs_rejected_promise_t;
122
123
124
typedef struct {
125
    int                 fd;
126
    njs_str_t           name;
127
    njs_str_t           file;
128
    char                path[NJS_MAX_PATH + 1];
129
} njs_module_info_t;
130
131
132
typedef struct  njs_engine_s     njs_engine_t;
133
134
135
struct njs_engine_s {
136
    union {
137
        struct {
138
            njs_vm_t            *vm;
139
140
            njs_opaque_value_t  value;
141
        }                       njs;
142
#if (NJS_HAVE_QUICKJS)
143
        struct {
144
            JSRuntime           *rt;
145
            JSContext           *ctx;
146
            JSValue             value;
147
        }                       qjs;
148
#endif
149
    }                           u;
150
151
    njs_int_t                 (*eval)(njs_engine_t *engine, njs_str_t *script);
152
    njs_int_t                 (*execute_pending_job)(njs_engine_t *engine);
153
    njs_int_t                 (*unhandled_rejection)(njs_engine_t *engine);
154
    njs_int_t                 (*process_events)(njs_engine_t *engine);
155
    njs_int_t                 (*destroy)(njs_engine_t *engine);
156
    njs_int_t                 (*output)(njs_engine_t *engine, njs_int_t ret);
157
    njs_arr_t                *(*complete)(njs_engine_t *engine, njs_str_t *ex);
158
159
    unsigned                    type;
160
    njs_mp_t                    *pool;
161
    njs_completion_t            completion;
162
};
163
164
typedef struct {
165
    njs_engine_t            *engine;
166
167
    uint32_t                event_id;
168
    njs_rbtree_t            events;  /* njs_ev_t * */
169
    njs_queue_t             posted_events;
170
171
    njs_queue_t             labels;
172
173
    njs_str_t               cwd;
174
175
    njs_arr_t               *rejected_promises;
176
177
    njs_bool_t              suppress_stdout;
178
    njs_bool_t              interactive;
179
    njs_bool_t              module;
180
    char                    **argv;
181
    njs_uint_t              argc;
182
183
#if (NJS_HAVE_QUICKJS)
184
    JSValue                 process;
185
186
    njs_queue_t             agents;
187
    njs_queue_t             reports;
188
    pthread_mutex_t         agent_mutex;
189
    pthread_cond_t          agent_cond;
190
    pthread_mutex_t         report_mutex;
191
#endif
192
} njs_console_t;
193
194
195
#if (NJS_HAVE_QUICKJS)
196
typedef struct {
197
    njs_queue_link_t        link;
198
    pthread_t               tid;
199
    njs_console_t           *console;
200
    char                    *script;
201
    JSValue                 broadcast_func;
202
    njs_bool_t              broadcast_pending;
203
    JSValue                 broadcast_sab;
204
    uint8_t                 *broadcast_sab_buf;
205
    size_t                  broadcast_sab_size;
206
    int32_t                 broadcast_val;
207
} njs_262agent_t;
208
209
210
typedef struct {
211
    njs_queue_link_t        link;
212
    char                    *str;
213
} njs_agent_report_t;
214
#endif
215
216
217
static njs_int_t njs_main(njs_opts_t *opts);
218
static njs_int_t njs_console_init(njs_opts_t *opts, njs_console_t *console);
219
static njs_int_t njs_externals_init(njs_vm_t *vm);
220
static njs_engine_t *njs_create_engine(njs_opts_t *opts);
221
static njs_int_t njs_process_file(njs_opts_t *opts);
222
static njs_int_t njs_process_script(njs_engine_t *engine,
223
    njs_console_t *console, njs_str_t *script);
224
225
#ifndef NJS_FUZZER_TARGET
226
227
static njs_int_t njs_options_parse(njs_opts_t *opts, int argc, char **argv);
228
static njs_int_t njs_options_parse_engine(njs_opts_t *opts, const char *engine);
229
static njs_int_t njs_options_add_path(njs_opts_t *opts, char *path, size_t len);
230
static void njs_options_free(njs_opts_t *opts);
231
232
#ifdef NJS_HAVE_READLINE
233
static njs_int_t njs_interactive_shell(njs_opts_t *opts);
234
static njs_int_t njs_editline_init(void);
235
static char *njs_completion_generator(const char *text, int state);
236
#endif
237
238
#endif
239
240
static njs_int_t njs_set_timeout(njs_vm_t *vm, njs_value_t *args,
241
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
242
static njs_int_t njs_set_immediate(njs_vm_t *vm, njs_value_t *args,
243
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
244
static njs_int_t njs_clear_timeout(njs_vm_t *vm, njs_value_t *args,
245
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
246
static njs_int_t njs_ext_console_log(njs_vm_t *vm, njs_value_t *args,
247
    njs_uint_t nargs, njs_index_t magic, njs_value_t *retval);
248
static njs_int_t njs_ext_console_time(njs_vm_t *vm, njs_value_t *args,
249
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
250
static njs_int_t njs_ext_console_time_end(njs_vm_t *vm, njs_value_t *args,
251
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
252
253
static void njs_console_log(njs_log_level_t level, const char *fmt, ...);
254
static void njs_console_logger(njs_log_level_t level, const u_char *start,
255
    size_t length);
256
257
static njs_int_t njs_console_time(njs_console_t *console, njs_str_t *name);
258
static void njs_console_time_end(njs_console_t *console, njs_str_t *name,
259
    uint64_t time);
260
static intptr_t njs_event_rbtree_compare(njs_rbtree_node_t *node1,
261
    njs_rbtree_node_t *node2);
262
static uint64_t njs_time(void);
263
264
njs_int_t njs_array_buffer_detach(njs_vm_t *vm, njs_value_t *args,
265
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
266
267
268
static njs_external_t  njs_ext_console[] = {
269
270
    {
271
        .flags = NJS_EXTERN_METHOD,
272
        .name.string = njs_str("dump"),
273
        .writable = 1,
274
        .configurable = 1,
275
        .enumerable = 1,
276
        .u.method = {
277
            .native = njs_ext_console_log,
278
55.0k
#define NJS_LOG_DUMP  16
279
18.3k
#define NJS_LOG_MASK  15
280
            .magic8 = NJS_LOG_INFO | NJS_LOG_DUMP,
281
        }
282
    },
283
284
    {
285
        .flags = NJS_EXTERN_METHOD,
286
        .name.string = njs_str("error"),
287
        .writable = 1,
288
        .configurable = 1,
289
        .enumerable = 1,
290
        .u.method = {
291
            .native = njs_ext_console_log,
292
            .magic8 = NJS_LOG_ERROR,
293
        }
294
    },
295
296
    {
297
        .flags = NJS_EXTERN_METHOD,
298
        .name.string = njs_str("info"),
299
        .writable = 1,
300
        .configurable = 1,
301
        .enumerable = 1,
302
        .u.method = {
303
            .native = njs_ext_console_log,
304
            .magic8 = NJS_LOG_INFO,
305
        }
306
    },
307
308
    {
309
        .flags = NJS_EXTERN_METHOD,
310
        .name.string = njs_str("log"),
311
        .writable = 1,
312
        .configurable = 1,
313
        .enumerable = 1,
314
        .u.method = {
315
            .native = njs_ext_console_log,
316
            .magic8 = NJS_LOG_INFO,
317
        }
318
    },
319
320
    {
321
        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
322
        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
323
        .u.property = {
324
            .value = "Console",
325
        }
326
    },
327
328
    {
329
        .flags = NJS_EXTERN_METHOD,
330
        .name.string = njs_str("time"),
331
        .writable = 1,
332
        .configurable = 1,
333
        .enumerable = 1,
334
        .u.method = {
335
            .native = njs_ext_console_time,
336
        }
337
    },
338
339
    {
340
        .flags = NJS_EXTERN_METHOD,
341
        .name.string = njs_str("timeEnd"),
342
        .writable = 1,
343
        .configurable = 1,
344
        .enumerable = 1,
345
        .u.method = {
346
            .native = njs_ext_console_time_end,
347
        }
348
    },
349
350
    {
351
        .flags = NJS_EXTERN_METHOD,
352
        .name.string = njs_str("warn"),
353
        .writable = 1,
354
        .configurable = 1,
355
        .enumerable = 1,
356
        .u.method = {
357
            .native = njs_ext_console_log,
358
            .magic8 = NJS_LOG_WARN,
359
        }
360
    },
361
362
};
363
364
365
static njs_external_t  njs_ext_262[] = {
366
367
    {
368
        .flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
369
        .name.symbol = NJS_SYMBOL_TO_STRING_TAG,
370
        .u.property = {
371
            .value = "$262",
372
        }
373
    },
374
375
    {
376
        .flags = NJS_EXTERN_METHOD,
377
        .name.string = njs_str("detachArrayBuffer"),
378
        .writable = 1,
379
        .configurable = 1,
380
        .enumerable = 1,
381
        .u.method = {
382
            .native = njs_array_buffer_detach,
383
        }
384
    },
385
386
};
387
388
389
njs_module_t  njs_console_module = {
390
    .name = njs_str("console"),
391
    .preinit = NULL,
392
    .init = njs_externals_init,
393
};
394
395
396
static njs_module_t *njs_console_addon_modules[] = {
397
    &njs_console_module,
398
    NULL,
399
};
400
401
402
static njs_int_t      njs_console_proto_id;
403
404
405
static njs_console_t  njs_console;
406
407
408
static njs_int_t
409
njs_main(njs_opts_t *opts)
410
11.6k
{
411
11.6k
    njs_int_t     ret;
412
11.6k
    njs_engine_t  *engine;
413
414
11.6k
    njs_mm_denormals(opts->denormals);
415
416
11.6k
    if (opts->file == NULL) {
417
0
        if (opts->command.length != 0) {
418
0
            opts->file = (char *) "string";
419
0
        }
420
421
#ifdef NJS_HAVE_READLINE
422
        else if (opts->interactive) {
423
            opts->file = (char *) "shell";
424
        }
425
#endif
426
427
0
        if (opts->file == NULL) {
428
0
            njs_stderror("file name is required in non-interactive mode\n");
429
0
            return NJS_ERROR;
430
0
        }
431
0
    }
432
433
11.6k
    ret = njs_console_init(opts, &njs_console);
434
11.6k
    if (njs_slow_path(ret != NJS_OK)) {
435
0
        njs_stderror("njs_console_init() failed\n");
436
0
        return NJS_ERROR;
437
0
    }
438
439
#if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE)
440
441
    if (opts->interactive) {
442
        ret = njs_interactive_shell(opts);
443
444
    } else
445
446
#endif
447
448
11.6k
    if (opts->command.length != 0) {
449
11.6k
        engine = njs_create_engine(opts);
450
11.6k
        if (engine == NULL) {
451
0
            return NJS_ERROR;
452
0
        }
453
454
11.6k
        ret = njs_process_script(engine, &njs_console, &opts->command);
455
11.6k
        engine->destroy(engine);
456
457
11.6k
    } else {
458
0
        ret = njs_process_file(opts);
459
0
    }
460
461
11.6k
    return ret;
462
11.6k
}
463
464
465
#ifndef NJS_FUZZER_TARGET
466
467
int
468
main(int argc, char **argv)
469
{
470
    njs_int_t   ret;
471
    njs_opts_t  opts;
472
473
    njs_memzero(&opts, sizeof(njs_opts_t));
474
    opts.interactive = 1;
475
476
    ret = njs_options_parse(&opts, argc, argv);
477
    if (ret != NJS_OK) {
478
        ret = (ret == NJS_DONE) ? NJS_OK : NJS_ERROR;
479
        goto done;
480
    }
481
482
    if (opts.version != 0) {
483
        njs_printf("%s\n", NJS_VERSION);
484
        ret = NJS_OK;
485
        goto done;
486
    }
487
488
    ret = njs_main(&opts);
489
490
done:
491
492
    njs_options_free(&opts);
493
494
    return (ret == NJS_OK) ? EXIT_SUCCESS : opts.exit_code;
495
}
496
497
498
static njs_int_t
499
njs_options_parse(njs_opts_t *opts, int argc, char **argv)
500
{
501
    char        *p, *start;
502
    size_t      len;
503
    njs_int_t   i, ret;
504
    njs_uint_t  n;
505
506
    static const char  help[] =
507
        "njs [options] [-c string | script.js | -] [script args]\n"
508
        "\n"
509
        "Interactive shell: "
510
#ifdef NJS_HAVE_READLINE
511
        "enabled\n"
512
#else
513
        "disabled\n"
514
#endif
515
        "\n"
516
        "Options:\n"
517
        "  -a                print AST.\n"
518
        "  -c                specify the command to execute.\n"
519
        "  -d                print disassembled code.\n"
520
        "  -e <code>         set failure exit code.\n"
521
        "  -f                disabled denormals mode.\n"
522
#ifdef NJS_DEBUG_GENERATOR
523
        "  -g                enable generator debug.\n"
524
#endif
525
        "  -j <size>         set the maximum stack size in bytes.\n"
526
        "  -m                load as ES6 module (script is default).\n"
527
#ifdef NJS_HAVE_QUICKJS
528
        "  -n njs|QuickJS    set JS engine (njs is default)\n"
529
#endif
530
#ifdef NJS_DEBUG_OPCODE
531
        "  -o                enable opcode debug.\n"
532
#endif
533
        "  -p <path>         set path prefix for modules.\n"
534
        "  -q                disable interactive introduction prompt.\n"
535
        "  -r                ignore unhandled promise rejection.\n"
536
        "  -s                sandbox mode.\n"
537
        "  -v                print njs version and exit.\n"
538
        "  -u                disable \"unsafe\" mode.\n"
539
        "  script.js | -     run code from a file or stdin.\n";
540
541
    opts->denormals = 1;
542
    opts->can_block = 1;
543
    opts->exit_code = EXIT_FAILURE;
544
    opts->engine = NJS_ENGINE_NJS;
545
    opts->unhandled_rejection = 1;
546
547
    p = getenv("NJS_EXIT_CODE");
548
    if (p != NULL) {
549
        opts->exit_code = atoi(p);
550
    }
551
552
    p = getenv("NJS_CAN_BLOCK");
553
    if (p != NULL) {
554
        opts->can_block = atoi(p);
555
    }
556
557
    p = getenv("NJS_LOAD_AS_MODULE");
558
    if (p != NULL) {
559
        opts->module = 1;
560
    }
561
562
    p = getenv("NJS_ENGINE");
563
    if (p != NULL) {
564
        ret = njs_options_parse_engine(opts, p);
565
        if (ret != NJS_OK) {
566
            return NJS_ERROR;
567
        }
568
    }
569
570
    start = getenv("NJS_PATH");
571
    if (start != NULL) {
572
        for ( ;; ) {
573
            p = (char *) njs_strchr(start, ':');
574
575
            len = (p != NULL) ? (size_t) (p - start) : njs_strlen(start);
576
577
            ret = njs_options_add_path(opts, start, len);
578
            if (ret != NJS_OK) {
579
                njs_stderror("failed to add path\n");
580
                return NJS_ERROR;
581
            }
582
583
            if (p == NULL) {
584
                break;
585
            }
586
587
            start = p + 1;
588
        }
589
    }
590
591
    for (i = 1; i < argc; i++) {
592
593
        p = argv[i];
594
595
        if (p[0] != '-' || (p[0] == '-' && p[1] == '\0')) {
596
            opts->interactive = 0;
597
            opts->file = argv[i];
598
            goto done;
599
        }
600
601
        p++;
602
603
        switch (*p) {
604
        case '?':
605
        case 'h':
606
            njs_printf("%*s", njs_length(help), help);
607
            return NJS_DONE;
608
609
        case 'a':
610
            opts->ast = 1;
611
            break;
612
613
        case 'c':
614
            opts->interactive = 0;
615
616
            if (++i < argc) {
617
                opts->command.start = (u_char *) argv[i];
618
                opts->command.length = njs_strlen(argv[i]);
619
                goto done;
620
            }
621
622
            njs_stderror("option \"-c\" requires argument\n");
623
            return NJS_ERROR;
624
625
        case 'd':
626
            opts->disassemble = 1;
627
            break;
628
629
        case 'e':
630
            if (++i < argc) {
631
                opts->exit_code = atoi(argv[i]);
632
                break;
633
            }
634
635
            njs_stderror("option \"-e\" requires argument\n");
636
            return NJS_ERROR;
637
638
        case 'f':
639
640
#if !(NJS_HAVE_DENORMALS_CONTROL)
641
            njs_stderror("option \"-f\" is not supported\n");
642
            return NJS_ERROR;
643
#endif
644
645
            opts->denormals = 0;
646
            break;
647
648
#ifdef NJS_DEBUG_GENERATOR
649
        case 'g':
650
            opts->generator_debug = 1;
651
            break;
652
#endif
653
        case 'j':
654
            if (++i < argc) {
655
                opts->stack_size = atoi(argv[i]);
656
                break;
657
            }
658
659
            njs_stderror("option \"-j\" requires argument\n");
660
            return NJS_ERROR;
661
662
        case 'm':
663
            opts->module = 1;
664
            break;
665
666
        case 'n':
667
            if (++i < argc) {
668
                ret = njs_options_parse_engine(opts, argv[i]);
669
                if (ret != NJS_OK) {
670
                    return NJS_ERROR;
671
                }
672
673
                break;
674
            }
675
676
            njs_stderror("option \"-n\" requires argument\n");
677
            return NJS_ERROR;
678
679
#ifdef NJS_DEBUG_OPCODE
680
        case 'o':
681
            opts->opcode_debug = 1;
682
            break;
683
#endif
684
685
        case 'p':
686
            if (++i < argc) {
687
                ret = njs_options_add_path(opts, argv[i], njs_strlen(argv[i]));
688
                if (ret != NJS_OK) {
689
                    njs_stderror("failed to add path\n");
690
                    return NJS_ERROR;
691
                }
692
693
                break;
694
            }
695
696
            njs_stderror("option \"-p\" requires directory name\n");
697
            return NJS_ERROR;
698
699
        case 'q':
700
            opts->quiet = 1;
701
            break;
702
703
        case 'r':
704
            opts->unhandled_rejection = 0;
705
            break;
706
707
        case 's':
708
            opts->sandbox = 1;
709
            break;
710
711
        case 't':
712
            if (++i < argc) {
713
                if (strcmp(argv[i], "module") == 0) {
714
                    opts->module = 1;
715
716
                } else if (strcmp(argv[i], "script") != 0) {
717
                    njs_stderror("option \"-t\" unexpected source type: %s\n",
718
                                 argv[i]);
719
                    return NJS_ERROR;
720
                }
721
722
                break;
723
            }
724
725
            njs_stderror("option \"-t\" requires source type\n");
726
            return NJS_ERROR;
727
        case 'v':
728
        case 'V':
729
            opts->version = 1;
730
            break;
731
732
        case 'u':
733
            opts->safe = 1;
734
            break;
735
736
        default:
737
            njs_stderror("Unknown argument: \"%s\" "
738
                         "try \"%s -h\" for available options\n", argv[i],
739
                         argv[0]);
740
            return NJS_ERROR;
741
        }
742
    }
743
744
done:
745
746
#ifdef NJS_HAVE_QUICKJS
747
    if (opts->engine == NJS_ENGINE_QUICKJS) {
748
        if (opts->ast) {
749
            njs_stderror("option \"-a\" is not supported for quickjs\n");
750
            return NJS_ERROR;
751
        }
752
753
        if (opts->disassemble) {
754
            njs_stderror("option \"-d\" is not supported for quickjs\n");
755
            return NJS_ERROR;
756
        }
757
758
        if (opts->generator_debug) {
759
            njs_stderror("option \"-g\" is not supported for quickjs\n");
760
            return NJS_ERROR;
761
        }
762
763
        if (opts->opcode_debug) {
764
            njs_stderror("option \"-o\" is not supported for quickjs\n");
765
            return NJS_ERROR;
766
        }
767
768
        if (opts->sandbox) {
769
            njs_stderror("option \"-s\" is not supported for quickjs\n");
770
            return NJS_ERROR;
771
        }
772
773
        if (opts->safe) {
774
            njs_stderror("option \"-u\" is not supported for quickjs\n");
775
            return NJS_ERROR;
776
        }
777
    }
778
#endif
779
780
    opts->argc = njs_max(argc - i + 1, 2);
781
    opts->argv = malloc(sizeof(char*) * opts->argc);
782
    if (opts->argv == NULL) {
783
        njs_stderror("failed to alloc argv\n");
784
        return NJS_ERROR;
785
    }
786
787
    opts->argv[0] = argv[0];
788
    opts->argv[1] = (opts->file != NULL) ? opts->file : (char *) "";
789
    for (n = 2; n < opts->argc; n++) {
790
        opts->argv[n] = argv[i + n - 1];
791
    }
792
793
    return NJS_OK;
794
}
795
796
797
static njs_int_t
798
njs_options_parse_engine(njs_opts_t *opts, const char *engine)
799
{
800
    if (strncasecmp(engine, "njs", 3) == 0) {
801
        opts->engine = NJS_ENGINE_NJS;
802
803
#ifdef NJS_HAVE_QUICKJS
804
    } else if (strncasecmp(engine, "QuickJS", 7) == 0) {
805
        opts->engine = NJS_ENGINE_QUICKJS;
806
#endif
807
808
    } else {
809
        njs_stderror("unknown engine \"%s\"\n", engine);
810
        return NJS_ERROR;
811
    }
812
813
    return NJS_OK;
814
}
815
816
817
static njs_int_t
818
njs_options_add_path(njs_opts_t *opts, char *path, size_t len)
819
{
820
    njs_str_t  *paths;
821
822
    opts->n_paths++;
823
824
    paths = realloc(opts->paths, opts->n_paths * sizeof(njs_str_t));
825
    if (paths == NULL) {
826
        njs_stderror("failed to add path\n");
827
        return NJS_ERROR;
828
    }
829
830
    opts->paths = paths;
831
    opts->paths[opts->n_paths - 1].start = (u_char *) path;
832
    opts->paths[opts->n_paths - 1].length = len;
833
834
    return NJS_OK;
835
}
836
837
838
static void
839
njs_options_free(njs_opts_t *opts)
840
{
841
    if (opts->paths != NULL) {
842
        free(opts->paths);
843
    }
844
845
    if (opts->argv != NULL) {
846
        free(opts->argv);
847
    }
848
}
849
850
851
#else
852
853
int
854
LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
855
11.6k
{
856
11.6k
    njs_opts_t  opts;
857
858
11.6k
    if (size == 0) {
859
0
        return 0;
860
0
    }
861
862
11.6k
    njs_memzero(&opts, sizeof(njs_opts_t));
863
864
11.6k
    opts.file = (char *) "fuzzer";
865
11.6k
    opts.command.start = (u_char *) data;
866
11.6k
    opts.command.length = size;
867
11.6k
    opts.suppress_stdout = 1;
868
869
11.6k
    return njs_main(&opts);
870
11.6k
}
871
872
#endif
873
874
static njs_int_t
875
njs_console_init(njs_opts_t *opts, njs_console_t *console)
876
11.6k
{
877
11.6k
    njs_memzero(console, sizeof(njs_console_t));
878
879
11.6k
    njs_rbtree_init(&console->events, njs_event_rbtree_compare);
880
11.6k
    njs_queue_init(&console->posted_events);
881
11.6k
    njs_queue_init(&console->labels);
882
883
11.6k
    console->interactive = opts->interactive;
884
11.6k
    console->suppress_stdout = opts->suppress_stdout;
885
11.6k
    console->module = opts->module;
886
11.6k
    console->argv = opts->argv;
887
11.6k
    console->argc = opts->argc;
888
889
#if (NJS_HAVE_QUICKJS)
890
    if (opts->engine == NJS_ENGINE_QUICKJS) {
891
        njs_queue_init(&console->agents);
892
        njs_queue_init(&console->reports);
893
        pthread_mutex_init(&console->report_mutex, NULL);
894
        pthread_mutex_init(&console->agent_mutex, NULL);
895
        pthread_cond_init(&console->agent_cond, NULL);
896
897
        console->process = JS_UNDEFINED;
898
    }
899
#endif
900
901
11.6k
    return NJS_OK;
902
11.6k
}
903
904
905
static njs_int_t
906
njs_function_bind(njs_vm_t *vm, const njs_str_t *name,
907
    njs_function_native_t native, njs_bool_t ctor)
908
34.8k
{
909
34.8k
    njs_function_t      *f;
910
34.8k
    njs_opaque_value_t   value;
911
912
34.8k
    f = njs_vm_function_alloc(vm, native, 1, ctor);
913
34.8k
    if (f == NULL) {
914
0
        return NJS_ERROR;
915
0
    }
916
917
34.8k
    njs_value_function_set(njs_value_arg(&value), f);
918
919
34.8k
    return njs_vm_bind(vm, name, njs_value_arg(&value), 1);
920
34.8k
}
921
922
923
static njs_int_t
924
njs_externals_init(njs_vm_t *vm)
925
11.6k
{
926
11.6k
    njs_int_t           ret, proto_id;
927
11.6k
    njs_console_t       *console;
928
11.6k
    njs_opaque_value_t  value, method;
929
930
11.6k
    static const njs_str_t  console_name = njs_str("console");
931
11.6k
    static const njs_str_t  dollar_262 = njs_str("$262");
932
11.6k
    static const njs_str_t  print_name = njs_str("print");
933
11.6k
    static const njs_str_t  console_log = njs_str("console.log");
934
11.6k
    static const njs_str_t  set_timeout = njs_str("setTimeout");
935
11.6k
    static const njs_str_t  set_immediate = njs_str("setImmediate");
936
11.6k
    static const njs_str_t  clear_timeout = njs_str("clearTimeout");
937
938
11.6k
    console = njs_vm_external_ptr(vm);
939
940
11.6k
    njs_console_proto_id = njs_vm_external_prototype(vm, njs_ext_console,
941
11.6k
                                         njs_nitems(njs_ext_console));
942
11.6k
    if (njs_slow_path(njs_console_proto_id < 0)) {
943
0
        njs_stderror("failed to add \"console\" proto\n");
944
0
        return NJS_ERROR;
945
0
    }
946
947
11.6k
    ret = njs_vm_external_create(vm, njs_value_arg(&value),
948
11.6k
                                 njs_console_proto_id, console, 0);
949
11.6k
    if (njs_slow_path(ret != NJS_OK)) {
950
0
        return NJS_ERROR;
951
0
    }
952
953
11.6k
    ret = njs_vm_bind(vm, &console_name, njs_value_arg(&value), 0);
954
11.6k
    if (njs_slow_path(ret != NJS_OK)) {
955
0
        return NJS_ERROR;
956
0
    }
957
958
11.6k
    ret = njs_vm_value(vm, &console_log, njs_value_arg(&method));
959
11.6k
    if (njs_slow_path(ret != NJS_OK)) {
960
0
        return NJS_ERROR;
961
0
    }
962
963
11.6k
    ret = njs_vm_bind(vm, &print_name, njs_value_arg(&method), 0);
964
11.6k
    if (njs_slow_path(ret != NJS_OK)) {
965
0
        return NJS_ERROR;
966
0
    }
967
968
11.6k
    ret = njs_function_bind(vm, &set_timeout, njs_set_timeout, 0);
969
11.6k
    if (ret != NJS_OK) {
970
0
        return NJS_ERROR;
971
0
    }
972
973
11.6k
    ret = njs_function_bind(vm, &set_immediate, njs_set_immediate, 0);
974
11.6k
    if (ret != NJS_OK) {
975
0
        return NJS_ERROR;
976
0
    }
977
978
11.6k
    ret = njs_function_bind(vm, &clear_timeout, njs_clear_timeout, 0);
979
11.6k
    if (ret != NJS_OK) {
980
0
        return NJS_ERROR;
981
0
    }
982
983
11.6k
    proto_id = njs_vm_external_prototype(vm, njs_ext_262,
984
11.6k
                                         njs_nitems(njs_ext_262));
985
11.6k
    if (njs_slow_path(proto_id < 0)) {
986
0
        njs_stderror("failed to add \"$262\" proto\n");
987
0
        return NJS_ERROR;
988
0
    }
989
990
11.6k
    ret = njs_vm_external_create(vm,  njs_value_arg(&value), proto_id, NULL, 1);
991
11.6k
    if (njs_slow_path(ret != NJS_OK)) {
992
0
        return NJS_ERROR;
993
0
    }
994
995
11.6k
    ret = njs_vm_bind(vm, &dollar_262, njs_value_arg(&value), 1);
996
11.6k
    if (njs_slow_path(ret != NJS_OK)) {
997
0
        return NJS_ERROR;
998
0
    }
999
1000
11.6k
    return NJS_OK;
1001
11.6k
}
1002
1003
1004
static void
1005
njs_rejection_tracker(njs_vm_t *vm, njs_external_ptr_t external,
1006
    njs_bool_t is_handled, njs_value_t *promise, njs_value_t *reason)
1007
0
{
1008
0
    void                    *promise_obj;
1009
0
    uint32_t                i, length;
1010
0
    njs_console_t           *console;
1011
0
    njs_rejected_promise_t  *rejected_promise;
1012
1013
0
    console = external;
1014
1015
0
    if (is_handled && console->rejected_promises != NULL) {
1016
0
        rejected_promise = console->rejected_promises->start;
1017
0
        length = console->rejected_promises->items;
1018
1019
0
        promise_obj = njs_value_ptr(promise);
1020
1021
0
        for (i = 0; i < length; i++) {
1022
0
            if (njs_value_ptr(njs_value_arg(&rejected_promise[i].u.njs.promise))
1023
0
                == promise_obj)
1024
0
            {
1025
0
                njs_arr_remove(console->rejected_promises,
1026
0
                               &rejected_promise[i]);
1027
1028
0
                break;
1029
0
            }
1030
0
        }
1031
1032
0
        return;
1033
0
    }
1034
1035
0
    if (console->rejected_promises == NULL) {
1036
0
        console->rejected_promises = njs_arr_create(console->engine->pool, 4,
1037
0
                                                sizeof(njs_rejected_promise_t));
1038
0
        if (njs_slow_path(console->rejected_promises == NULL)) {
1039
0
            return;
1040
0
        }
1041
0
    }
1042
1043
0
    rejected_promise = njs_arr_add(console->rejected_promises);
1044
0
    if (njs_slow_path(rejected_promise == NULL)) {
1045
0
        return;
1046
0
    }
1047
1048
0
    njs_value_assign(&rejected_promise->u.njs.promise, promise);
1049
0
    njs_value_assign(&rejected_promise->u.njs.message, reason);
1050
0
}
1051
1052
1053
static njs_int_t
1054
njs_module_path(const njs_str_t *dir, njs_module_info_t *info)
1055
0
{
1056
0
    char        *p;
1057
0
    size_t      length;
1058
0
    njs_bool_t  trail;
1059
0
    char        src[NJS_MAX_PATH + 1];
1060
1061
0
    trail = 0;
1062
0
    length = info->name.length;
1063
1064
0
    if (dir != NULL) {
1065
0
        length += dir->length;
1066
1067
0
        if (length == 0 || dir->length == 0) {
1068
0
            return NJS_DECLINED;
1069
0
        }
1070
1071
0
        trail = (dir->start[dir->length - 1] != '/');
1072
1073
0
        if (trail) {
1074
0
            length++;
1075
0
        }
1076
0
    }
1077
1078
0
    if (njs_slow_path(length > NJS_MAX_PATH)) {
1079
0
        return NJS_ERROR;
1080
0
    }
1081
1082
0
    p = &src[0];
1083
1084
0
    if (dir != NULL) {
1085
0
        p = (char *) njs_cpymem(p, dir->start, dir->length);
1086
1087
0
        if (trail) {
1088
0
            *p++ = '/';
1089
0
        }
1090
0
    }
1091
1092
0
    p = (char *) njs_cpymem(p, info->name.start, info->name.length);
1093
0
    *p = '\0';
1094
1095
0
    p = realpath(&src[0], &info->path[0]);
1096
0
    if (p == NULL) {
1097
0
        return NJS_DECLINED;
1098
0
    }
1099
1100
0
    info->fd = open(&info->path[0], O_RDONLY);
1101
0
    if (info->fd < 0) {
1102
0
        return NJS_DECLINED;
1103
0
    }
1104
1105
0
    info->file.start = (u_char *) &info->path[0];
1106
0
    info->file.length = njs_strlen(info->file.start);
1107
1108
0
    return NJS_OK;
1109
0
}
1110
1111
1112
static njs_int_t
1113
njs_module_lookup(njs_opts_t *opts, const njs_str_t *cwd,
1114
    njs_module_info_t *info)
1115
0
{
1116
0
    njs_int_t   ret;
1117
0
    njs_str_t   *path;
1118
0
    njs_uint_t  i;
1119
1120
0
    if (info->name.start[0] == '/') {
1121
0
        return njs_module_path(NULL, info);
1122
0
    }
1123
1124
0
    ret = njs_module_path(cwd, info);
1125
1126
0
    if (ret != NJS_DECLINED) {
1127
0
        return ret;
1128
0
    }
1129
1130
0
    path = opts->paths;
1131
1132
0
    for (i = 0; i < opts->n_paths; i++) {
1133
0
        ret = njs_module_path(&path[i], info);
1134
1135
0
        if (ret != NJS_DECLINED) {
1136
0
            return ret;
1137
0
        }
1138
0
    }
1139
1140
0
    return NJS_DECLINED;
1141
0
}
1142
1143
1144
static njs_int_t
1145
njs_module_read(njs_mp_t *mp, int fd, njs_str_t *text)
1146
0
{
1147
0
    ssize_t      n;
1148
0
    struct stat  sb;
1149
1150
0
    text->start = NULL;
1151
1152
0
    if (fstat(fd, &sb) == -1) {
1153
0
        goto fail;
1154
0
    }
1155
1156
0
    if (!S_ISREG(sb.st_mode)) {
1157
0
        goto fail;
1158
0
    }
1159
1160
0
    text->length = sb.st_size;
1161
1162
0
    text->start = njs_mp_alloc(mp, text->length + 1);
1163
0
    if (text->start == NULL) {
1164
0
        goto fail;
1165
0
    }
1166
1167
0
    n = read(fd, text->start, sb.st_size);
1168
1169
0
    if (n < 0 || n != sb.st_size) {
1170
0
        goto fail;
1171
0
    }
1172
1173
0
    text->start[text->length] = '\0';
1174
1175
0
    return NJS_OK;
1176
1177
0
fail:
1178
1179
0
    if (text->start != NULL) {
1180
0
        njs_mp_free(mp, text->start);
1181
0
    }
1182
1183
0
    return NJS_ERROR;
1184
0
}
1185
1186
1187
static void
1188
njs_file_dirname(const njs_str_t *path, njs_str_t *name)
1189
11.6k
{
1190
11.6k
    const u_char  *p, *end;
1191
1192
11.6k
    if (path->length == 0) {
1193
0
        goto current_dir;
1194
0
    }
1195
1196
11.6k
    p = path->start + path->length - 1;
1197
1198
    /* Stripping basename. */
1199
1200
81.2k
    while (p >= path->start && *p != '/') { p--; }
1201
1202
11.6k
    end = p + 1;
1203
1204
11.6k
    if (end == path->start) {
1205
11.6k
        goto current_dir;
1206
11.6k
    }
1207
1208
    /* Stripping trailing slashes. */
1209
1210
0
    while (p >= path->start && *p == '/') { p--; }
1211
1212
0
    p++;
1213
1214
0
    if (p == path->start) {
1215
0
        p = end;
1216
0
    }
1217
1218
0
    name->start = path->start;
1219
0
    name->length = p - path->start;
1220
1221
0
    return;
1222
1223
11.6k
current_dir:
1224
1225
11.6k
    *name = njs_str_value(".");
1226
11.6k
}
1227
1228
1229
static njs_int_t
1230
njs_console_set_cwd(njs_console_t *console, njs_str_t *file)
1231
11.6k
{
1232
11.6k
    njs_str_t  cwd;
1233
1234
11.6k
    njs_file_dirname(file, &cwd);
1235
1236
11.6k
    console->cwd.start = njs_mp_alloc(console->engine->pool, cwd.length);
1237
11.6k
    if (njs_slow_path(console->cwd.start == NULL)) {
1238
0
        return NJS_ERROR;
1239
0
    }
1240
1241
11.6k
    memcpy(console->cwd.start, cwd.start, cwd.length);
1242
11.6k
    console->cwd.length = cwd.length;
1243
1244
11.6k
    return NJS_OK;
1245
11.6k
}
1246
1247
1248
static njs_mod_t *
1249
njs_module_loader(njs_vm_t *vm, njs_external_ptr_t external, njs_str_t *name)
1250
0
{
1251
0
    u_char             *start;
1252
0
    njs_int_t          ret;
1253
0
    njs_str_t          text, prev_cwd;
1254
0
    njs_mod_t          *module;
1255
0
    njs_opts_t         *opts;
1256
0
    njs_console_t      *console;
1257
0
    njs_module_info_t  info;
1258
1259
0
    opts = external;
1260
0
    console = njs_vm_external_ptr(vm);
1261
1262
0
    njs_memzero(&info, sizeof(njs_module_info_t));
1263
1264
0
    info.name = *name;
1265
1266
0
    ret = njs_module_lookup(opts, &console->cwd, &info);
1267
0
    if (njs_slow_path(ret != NJS_OK)) {
1268
0
        return NULL;
1269
0
    }
1270
1271
0
    ret = njs_module_read(console->engine->pool, info.fd, &text);
1272
1273
0
    (void) close(info.fd);
1274
1275
0
    if (njs_slow_path(ret != NJS_OK)) {
1276
0
        njs_vm_internal_error(vm, "while reading \"%V\" module", &info.file);
1277
0
        return NULL;
1278
0
    }
1279
1280
0
    prev_cwd = console->cwd;
1281
1282
0
    ret = njs_console_set_cwd(console, &info.file);
1283
0
    if (njs_slow_path(ret != NJS_OK)) {
1284
0
        njs_vm_internal_error(vm, "while setting cwd for \"%V\" module",
1285
0
                              &info.file);
1286
0
        return NULL;
1287
0
    }
1288
1289
0
    start = text.start;
1290
1291
0
    module = njs_vm_compile_module(vm, &info.file, &start,
1292
0
                                   &text.start[text.length]);
1293
1294
0
    njs_mp_free(console->engine->pool, console->cwd.start);
1295
0
    console->cwd = prev_cwd;
1296
1297
0
    njs_mp_free(console->engine->pool, text.start);
1298
1299
0
    return module;
1300
0
}
1301
1302
1303
static njs_int_t
1304
njs_engine_njs_init(njs_engine_t *engine, njs_opts_t *opts)
1305
11.6k
{
1306
11.6k
    njs_vm_t      *vm;
1307
11.6k
    njs_int_t     ret;
1308
11.6k
    njs_vm_opt_t  vm_options;
1309
1310
11.6k
    njs_vm_opt_init(&vm_options);
1311
1312
11.6k
    vm_options.file.start = (u_char *) opts->file;
1313
11.6k
    vm_options.file.length = njs_strlen(opts->file);
1314
1315
11.6k
    vm_options.init = 1;
1316
11.6k
    vm_options.interactive = opts->interactive;
1317
11.6k
    vm_options.disassemble = opts->disassemble;
1318
11.6k
    vm_options.backtrace = 1;
1319
11.6k
    vm_options.quiet = opts->quiet;
1320
11.6k
    vm_options.sandbox = opts->sandbox;
1321
11.6k
    vm_options.unsafe = !opts->safe;
1322
11.6k
    vm_options.module = opts->module;
1323
#ifdef NJS_DEBUG_GENERATOR
1324
    vm_options.generator_debug = opts->generator_debug;
1325
#endif
1326
#ifdef NJS_DEBUG_OPCODE
1327
    vm_options.opcode_debug = opts->opcode_debug;
1328
#endif
1329
1330
11.6k
    vm_options.addons = njs_console_addon_modules;
1331
11.6k
    vm_options.external = &njs_console;
1332
11.6k
    vm_options.argv = opts->argv;
1333
11.6k
    vm_options.argc = opts->argc;
1334
11.6k
    vm_options.ast = opts->ast;
1335
1336
11.6k
    if (opts->stack_size != 0) {
1337
0
        vm_options.max_stack_size = opts->stack_size;
1338
0
    }
1339
1340
11.6k
    vm = njs_vm_create(&vm_options);
1341
11.6k
    if (vm == NULL) {
1342
0
        njs_stderror("failed to create vm\n");
1343
0
        return NJS_ERROR;
1344
0
    }
1345
1346
11.6k
    if (opts->unhandled_rejection) {
1347
0
        njs_vm_set_rejection_tracker(vm, njs_rejection_tracker,
1348
0
                                     njs_vm_external_ptr(vm));
1349
0
    }
1350
1351
11.6k
    ret = njs_console_set_cwd(njs_vm_external_ptr(vm), &vm_options.file);
1352
11.6k
    if (njs_slow_path(ret != NJS_OK)) {
1353
0
        njs_stderror("failed to set cwd\n");
1354
0
        return NJS_ERROR;
1355
0
    }
1356
1357
11.6k
    njs_vm_set_module_loader(vm, njs_module_loader, opts);
1358
1359
11.6k
    engine->u.njs.vm = vm;
1360
1361
11.6k
    return NJS_OK;
1362
11.6k
}
1363
1364
1365
static njs_int_t
1366
njs_engine_njs_destroy(njs_engine_t *engine)
1367
11.6k
{
1368
11.6k
    njs_vm_destroy(engine->u.njs.vm);
1369
11.6k
    njs_mp_destroy(engine->pool);
1370
1371
11.6k
    return NJS_OK;
1372
11.6k
}
1373
1374
1375
static njs_int_t
1376
njs_engine_njs_eval(njs_engine_t *engine, njs_str_t *script)
1377
11.6k
{
1378
11.6k
     u_char     *start, *end;
1379
11.6k
     njs_int_t  ret;
1380
1381
11.6k
     start = script->start;
1382
11.6k
     end = start + script->length;
1383
1384
11.6k
     ret = njs_vm_compile(engine->u.njs.vm, &start, end);
1385
1386
11.6k
     if (ret == NJS_OK && start == end) {
1387
11.5k
        return njs_vm_start(engine->u.njs.vm,
1388
11.5k
                           njs_value_arg(&engine->u.njs.value));
1389
11.5k
     }
1390
1391
90
     return NJS_ERROR;
1392
11.6k
}
1393
1394
1395
static njs_int_t
1396
njs_engine_njs_execute_pending_job(njs_engine_t *engine)
1397
63.4k
{
1398
63.4k
    return njs_vm_execute_pending_job(engine->u.njs.vm);
1399
63.4k
}
1400
1401
1402
static njs_int_t
1403
njs_engine_njs_output(njs_engine_t *engine, njs_int_t ret)
1404
0
{
1405
0
    njs_vm_t       *vm;
1406
0
    njs_str_t      out;
1407
0
    njs_console_t  *console;
1408
1409
0
    vm = engine->u.njs.vm;
1410
0
    console = njs_vm_external_ptr(vm);
1411
1412
0
    if (ret == NJS_OK) {
1413
0
        if (console->interactive) {
1414
0
            if (njs_vm_value_dump(vm, &out, njs_value_arg(&engine->u.njs.value),
1415
0
                                  0, 1)
1416
0
                != NJS_OK)
1417
0
            {
1418
0
                njs_stderror("Shell:failed to get retval from VM\n");
1419
0
                return NJS_ERROR;
1420
0
            }
1421
1422
0
            njs_print(out.start, out.length);
1423
0
            njs_print("\n", 1);
1424
0
        }
1425
1426
0
    } else {
1427
0
        njs_vm_exception_string(vm, &out);
1428
0
        njs_stderror("Thrown:\n%V\n", &out);
1429
0
    }
1430
1431
0
    return NJS_OK;
1432
0
}
1433
1434
1435
static njs_arr_t *
1436
njs_object_completions(njs_vm_t *vm, njs_value_t *object, njs_str_t *expression)
1437
0
{
1438
0
    u_char              *prefix;
1439
0
    size_t              len, prefix_len;
1440
0
    int64_t             k, n, length;
1441
0
    njs_int_t           ret;
1442
0
    njs_arr_t           *array;
1443
0
    njs_str_t           *completion, key;
1444
0
    njs_value_t         *keys;
1445
0
    njs_opaque_value_t  *start, retval, prototype;
1446
1447
0
    prefix = expression->start + expression->length;
1448
1449
0
    while (prefix > expression->start && *prefix != '.') {
1450
0
        prefix--;
1451
0
    }
1452
1453
0
    if (prefix != expression->start) {
1454
0
        prefix++;
1455
0
    }
1456
1457
0
    prefix_len = prefix - expression->start;
1458
0
    len = expression->length - prefix_len;
1459
1460
0
    array = njs_arr_create(njs_vm_memory_pool(vm), 8, sizeof(njs_str_t));
1461
0
    if (njs_slow_path(array == NULL)) {
1462
0
        goto fail;
1463
0
    }
1464
1465
0
    while (!njs_value_is_null(object)) {
1466
0
        keys = njs_vm_value_enumerate(vm, object, NJS_ENUM_KEYS
1467
0
                                      | NJS_ENUM_STRING,
1468
0
                                      njs_value_arg(&retval));
1469
0
        if (njs_slow_path(keys == NULL)) {
1470
0
            goto fail;
1471
0
        }
1472
1473
0
        (void) njs_vm_array_length(vm, keys, &length);
1474
1475
0
        start = (njs_opaque_value_t *) njs_vm_array_start(vm, keys);
1476
0
        if (start == NULL) {
1477
0
            goto fail;
1478
0
        }
1479
1480
1481
0
        for (n = 0; n < length; n++) {
1482
0
            ret = njs_vm_value_to_string(vm, &key, njs_value_arg(start));
1483
0
            if (njs_slow_path(ret != NJS_OK)) {
1484
0
                goto fail;
1485
0
            }
1486
1487
0
            start++;
1488
1489
0
            if (len > key.length || njs_strncmp(key.start, prefix, len) != 0) {
1490
0
                continue;
1491
0
            }
1492
1493
0
            for (k = 0; k < array->items; k++) {
1494
0
                completion = njs_arr_item(array, k);
1495
1496
0
                if ((completion->length - prefix_len - 1) == key.length
1497
0
                    && njs_strncmp(&completion->start[prefix_len],
1498
0
                                   key.start, key.length)
1499
0
                       == 0)
1500
0
                {
1501
0
                    break;
1502
0
                }
1503
0
            }
1504
1505
0
            if (k != array->items) {
1506
0
                continue;
1507
0
            }
1508
1509
0
            completion = njs_arr_add(array);
1510
0
            if (njs_slow_path(completion == NULL)) {
1511
0
                goto fail;
1512
0
            }
1513
1514
0
            completion->length = prefix_len + key.length + 1;
1515
0
            completion->start = njs_mp_alloc(njs_vm_memory_pool(vm),
1516
0
                                             completion->length);
1517
0
            if (njs_slow_path(completion->start == NULL)) {
1518
0
                goto fail;
1519
0
            }
1520
1521
1522
0
            njs_sprintf(completion->start,
1523
0
                        completion->start + completion->length,
1524
0
                        "%*s%V%Z", prefix_len, expression->start, &key);
1525
0
        }
1526
1527
0
        ret = njs_vm_prototype(vm, object, njs_value_arg(&prototype));
1528
0
        if (njs_slow_path(ret != NJS_OK)) {
1529
0
            goto fail;
1530
0
        }
1531
1532
0
        object = njs_value_arg(&prototype);
1533
0
    }
1534
1535
0
    return array;
1536
1537
0
fail:
1538
1539
0
    if (array != NULL) {
1540
0
        njs_arr_destroy(array);
1541
0
    }
1542
1543
0
    return NULL;
1544
0
}
1545
1546
1547
static njs_arr_t *
1548
njs_engine_njs_complete(njs_engine_t *engine, njs_str_t *expression)
1549
0
{
1550
0
    u_char              *p, *start, *end;
1551
0
    njs_vm_t            *vm;
1552
0
    njs_int_t           ret;
1553
0
    njs_bool_t          global;
1554
0
    njs_opaque_value_t  value, key, retval;
1555
1556
0
    vm = engine->u.njs.vm;
1557
1558
0
    p = expression->start;
1559
0
    end = p + expression->length;
1560
1561
0
    global = 1;
1562
0
    (void) njs_vm_global(vm, njs_value_arg(&value));
1563
1564
0
    while (p < end && *p != '.') { p++; }
1565
1566
0
    if (p == end) {
1567
0
        goto done;
1568
0
    }
1569
1570
0
    p = expression->start;
1571
1572
0
    for ( ;; ) {
1573
1574
0
        start = (*p == '.' && p < end) ? ++p: p;
1575
1576
0
        if (p == end) {
1577
0
            break;
1578
0
        }
1579
1580
0
        while (p < end && *p != '.') { p++; }
1581
1582
0
        ret = njs_vm_value_string_create(vm, njs_value_arg(&key), start,
1583
0
                                         p - start);
1584
0
        if (njs_slow_path(ret != NJS_OK)) {
1585
0
            return NULL;
1586
0
        }
1587
1588
0
        ret = njs_value_property_val(vm, njs_value_arg(&value),
1589
0
                                     njs_value_arg(&key),
1590
0
                                     njs_value_arg(&retval));
1591
0
        if (njs_slow_path(ret != NJS_OK)) {
1592
0
            if (ret == NJS_DECLINED && !global) {
1593
0
                goto done;
1594
0
            }
1595
1596
0
            return NULL;
1597
0
        }
1598
1599
0
        global = 0;
1600
0
        njs_value_assign(&value, &retval);
1601
0
    }
1602
1603
0
done:
1604
1605
0
    return njs_object_completions(vm, njs_value_arg(&value), expression);
1606
0
}
1607
1608
1609
static njs_int_t
1610
njs_engine_njs_process_events(njs_engine_t *engine)
1611
11.4k
{
1612
11.4k
    njs_ev_t            *ev;
1613
11.4k
    njs_vm_t            *vm;
1614
11.4k
    njs_int_t           ret;
1615
11.4k
    njs_queue_t         *events;
1616
11.4k
    njs_console_t       *console;
1617
11.4k
    njs_queue_link_t    *link;
1618
11.4k
    njs_opaque_value_t  retval;
1619
1620
11.4k
    vm = engine->u.njs.vm;
1621
11.4k
    console = njs_vm_external_ptr(vm);
1622
11.4k
    events = &console->posted_events;
1623
1624
11.4k
    for ( ;; ) {
1625
11.4k
        link = njs_queue_first(events);
1626
1627
11.4k
        if (link == njs_queue_tail(events)) {
1628
11.4k
            break;
1629
11.4k
        }
1630
1631
0
        ev = njs_queue_link_data(link, njs_ev_t, link);
1632
1633
0
        njs_queue_remove(&ev->link);
1634
0
        njs_rbtree_delete(&console->events, &ev->node);
1635
1636
0
        ret = njs_vm_invoke(vm, ev->u.njs.function, ev->u.njs.args, ev->nargs,
1637
0
                            njs_value_arg(&retval));
1638
0
        if (ret == NJS_ERROR) {
1639
0
            njs_engine_njs_output(engine, ret);
1640
1641
0
            if (!console->interactive) {
1642
0
                return NJS_ERROR;
1643
0
            }
1644
0
        }
1645
0
    }
1646
1647
11.4k
    if (!njs_rbtree_is_empty(&console->events)) {
1648
0
        return NJS_AGAIN;
1649
0
    }
1650
1651
11.4k
    return njs_vm_pending(vm) ? NJS_AGAIN: NJS_OK;
1652
11.4k
}
1653
1654
1655
static njs_int_t
1656
njs_engine_njs_unhandled_rejection(njs_engine_t *engine)
1657
11.4k
{
1658
11.4k
    njs_vm_t                *vm;
1659
11.4k
    njs_int_t               ret;
1660
11.4k
    njs_str_t               message;
1661
11.4k
    njs_console_t           *console;
1662
11.4k
    njs_rejected_promise_t  *rejected_promise;
1663
1664
11.4k
    vm = engine->u.njs.vm;
1665
11.4k
    console = njs_vm_external_ptr(vm);
1666
1667
11.4k
    if (console->rejected_promises == NULL
1668
11.4k
        || console->rejected_promises->items == 0)
1669
11.4k
    {
1670
11.4k
        return 0;
1671
11.4k
    }
1672
1673
0
    rejected_promise = console->rejected_promises->start;
1674
1675
0
    ret = njs_vm_value_to_string(vm, &message,
1676
0
                               njs_value_arg(&rejected_promise->u.njs.message));
1677
0
    if (njs_slow_path(ret != NJS_OK)) {
1678
0
        return -1;
1679
0
    }
1680
1681
0
    njs_vm_error(vm, "unhandled promise rejection: %V", &message);
1682
1683
0
    njs_arr_destroy(console->rejected_promises);
1684
0
    console->rejected_promises = NULL;
1685
1686
0
    return 1;
1687
0
}
1688
1689
#ifdef NJS_HAVE_QUICKJS
1690
1691
static JSValue
1692
njs_qjs_console_log(JSContext *ctx, JSValueConst this_val, int argc,
1693
    JSValueConst *argv, int magic)
1694
{
1695
    int         i;
1696
    size_t      len;
1697
    const char  *str;
1698
1699
    for (i = 0; i < argc; i++) {
1700
        str = JS_ToCStringLen(ctx, &len, argv[i]);
1701
        if (!str) {
1702
            return JS_EXCEPTION;
1703
        }
1704
1705
        njs_console_logger(magic, (const u_char*) str, len);
1706
        JS_FreeCString(ctx, str);
1707
    }
1708
1709
    return JS_UNDEFINED;
1710
}
1711
1712
1713
static JSValue
1714
njs_qjs_console_time(JSContext *ctx, JSValueConst this_val, int argc,
1715
    JSValueConst *argv)
1716
{
1717
    njs_str_t      name;
1718
    const char     *str;
1719
    njs_console_t  *console;
1720
1721
    static const njs_str_t  default_label = njs_str("default");
1722
1723
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
1724
1725
    name = default_label;
1726
1727
    if (argc > 0 && !JS_IsUndefined(argv[0])) {
1728
        str = JS_ToCStringLen(ctx, &name.length, argv[0]);
1729
        if (str == NULL) {
1730
            return JS_EXCEPTION;
1731
        }
1732
1733
        name.start = njs_mp_alloc(console->engine->pool, name.length);
1734
        if (njs_slow_path(name.start == NULL)) {
1735
            JS_ThrowOutOfMemory(ctx);
1736
            return JS_EXCEPTION;
1737
        }
1738
1739
        (void) memcpy(name.start, str, name.length);
1740
1741
        JS_FreeCString(ctx, str);
1742
    }
1743
1744
    if (njs_console_time(console, &name) != NJS_OK) {
1745
        return JS_EXCEPTION;
1746
    }
1747
1748
    return JS_UNDEFINED;
1749
}
1750
1751
1752
static JSValue
1753
njs_qjs_console_time_end(JSContext *ctx, JSValueConst this_val, int argc,
1754
    JSValueConst *argv)
1755
{
1756
    uint64_t       ns;
1757
    njs_str_t      name;
1758
    const char     *str;
1759
    njs_console_t  *console;
1760
1761
    static const njs_str_t  default_label = njs_str("default");
1762
1763
    ns = njs_time();
1764
1765
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
1766
1767
    name = default_label;
1768
1769
    if (argc > 0 && !JS_IsUndefined(argv[0])) {
1770
        str = JS_ToCStringLen(ctx, &name.length, argv[0]);
1771
        if (str == NULL) {
1772
            return JS_EXCEPTION;
1773
        }
1774
1775
        name.start = njs_mp_alloc(console->engine->pool, name.length);
1776
        if (njs_slow_path(name.start == NULL)) {
1777
            JS_ThrowOutOfMemory(ctx);
1778
            return JS_EXCEPTION;
1779
        }
1780
1781
        (void) memcpy(name.start, str, name.length);
1782
1783
        JS_FreeCString(ctx, str);
1784
    }
1785
1786
    njs_console_time_end(console, &name, ns);
1787
1788
    return JS_UNDEFINED;
1789
}
1790
1791
1792
static JSValue
1793
njs_qjs_set_timer(JSContext *ctx, JSValueConst this_val, int argc,
1794
    JSValueConst *argv, int immediate)
1795
{
1796
    int            n;
1797
    int64_t        delay;
1798
    njs_ev_t       *ev;
1799
    njs_uint_t     i;
1800
    njs_console_t  *console;
1801
1802
    if (njs_slow_path(argc < 1)) {
1803
        JS_ThrowTypeError(ctx, "too few arguments");
1804
        return JS_EXCEPTION;
1805
    }
1806
1807
    if (njs_slow_path(!JS_IsFunction(ctx, argv[0]))) {
1808
        JS_ThrowTypeError(ctx, "first arg must be a function");
1809
        return JS_EXCEPTION;
1810
    }
1811
1812
    delay = 0;
1813
1814
    if (!immediate && argc >= 2 && JS_IsNumber(argv[1])) {
1815
        JS_ToInt64(ctx, &delay, argv[1]);
1816
    }
1817
1818
    if (delay != 0) {
1819
        JS_ThrowInternalError(ctx, "njs_set_timer(): async timers unsupported");
1820
        return JS_EXCEPTION;
1821
    }
1822
1823
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
1824
1825
    n = immediate ? 1 : 2;
1826
    argc = (argc >= n) ? argc - n : 0;
1827
1828
    ev = njs_mp_alloc(console->engine->pool,
1829
                      sizeof(njs_ev_t) + sizeof(njs_opaque_value_t) * argc);
1830
    if (njs_slow_path(ev == NULL)) {
1831
        JS_ThrowOutOfMemory(ctx);
1832
        return JS_EXCEPTION;
1833
    }
1834
1835
    ev->u.qjs.function = JS_DupValue(ctx, argv[0]);
1836
    ev->u.qjs.args = (JSValue *) &ev[1];
1837
    ev->nargs = (njs_uint_t) argc;
1838
    ev->id = console->event_id++;
1839
1840
    if (ev->nargs != 0) {
1841
        for (i = 0; i < ev->nargs; i++) {
1842
            ev->u.qjs.args[i] = JS_DupValue(ctx, argv[i + n]);
1843
        }
1844
    }
1845
1846
    njs_rbtree_insert(&console->events, &ev->node);
1847
1848
    njs_queue_insert_tail(&console->posted_events, &ev->link);
1849
1850
    return JS_NewUint32(ctx, ev->id);
1851
}
1852
1853
1854
static void
1855
njs_qjs_destroy_event(JSContext *ctx, njs_console_t *console, njs_ev_t *ev)
1856
{
1857
    njs_uint_t  i;
1858
1859
    JS_FreeValue(ctx, ev->u.qjs.function);
1860
1861
    if (ev->nargs != 0) {
1862
        for (i = 0; i < ev->nargs; i++) {
1863
            JS_FreeValue(ctx, ev->u.qjs.args[i]);
1864
        }
1865
    }
1866
1867
    njs_mp_free(console->engine->pool, ev);
1868
}
1869
1870
1871
static JSValue
1872
njs_qjs_clear_timeout(JSContext *ctx, JSValueConst this_val, int argc,
1873
    JSValueConst *argv)
1874
{
1875
    njs_ev_t           ev_lookup, *ev;
1876
    njs_console_t      *console;
1877
    njs_rbtree_node_t  *rb;
1878
1879
    if (argc < 1 || !JS_IsNumber(argv[0])) {
1880
        return JS_UNDEFINED;
1881
    }
1882
1883
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
1884
1885
    if (JS_ToUint32(ctx, &ev_lookup.id, argv[0])) {
1886
        return JS_EXCEPTION;
1887
    }
1888
1889
    rb = njs_rbtree_find(&console->events, &ev_lookup.node);
1890
    if (njs_slow_path(rb == NULL)) {
1891
        JS_ThrowTypeError(ctx, "failed to find timer");
1892
        return JS_EXCEPTION;
1893
    }
1894
1895
    ev = (njs_ev_t *) rb;
1896
    njs_queue_remove(&ev->link);
1897
    njs_rbtree_delete(&console->events, (njs_rbtree_part_t *) rb);
1898
1899
    njs_qjs_destroy_event(ctx, console, ev);
1900
1901
    return JS_UNDEFINED;
1902
}
1903
1904
1905
static JSValue
1906
njs_qjs_process_getter(JSContext *ctx, JSValueConst this_val)
1907
{
1908
    JSValue         obj;
1909
    njs_console_t  *console;
1910
1911
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
1912
1913
    if (!JS_IsUndefined(console->process)) {
1914
        return JS_DupValue(ctx, console->process);
1915
    }
1916
1917
    obj = qjs_process_object(ctx, console->argc, (const char **) console->argv);
1918
    if (JS_IsException(obj)) {
1919
        return JS_EXCEPTION;
1920
    }
1921
1922
    console->process = JS_DupValue(ctx, obj);
1923
1924
    return obj;
1925
}
1926
1927
1928
static njs_int_t njs_qjs_global_init(JSContext *ctx, JSValue global_obj);
1929
static void njs_qjs_dump_error(JSContext *ctx);
1930
1931
1932
static void
1933
njs_qjs_dump_obj(JSContext *ctx, FILE *f, JSValueConst val, const char *prefix,
1934
    const char *quote)
1935
{
1936
    njs_bool_t  is_str;
1937
    const char  *str;
1938
1939
    is_str = JS_IsString(val);
1940
1941
    str = JS_ToCString(ctx, val);
1942
    if (str) {
1943
        fprintf(f, "%s%s%s%s\n", prefix, is_str ? quote : "",
1944
                str, is_str ? quote : "");
1945
        JS_FreeCString(ctx, str);
1946
1947
    } else {
1948
        njs_qjs_dump_error(ctx);
1949
    }
1950
}
1951
1952
1953
static void
1954
njs_qjs_dump_error2(JSContext *ctx, JSValueConst exception)
1955
{
1956
    _Bool    is_error;
1957
    JSValue  val;
1958
1959
    is_error = JS_IsError(ctx, exception);
1960
1961
    njs_qjs_dump_obj(ctx, stderr, exception, "Thrown:\n", "");
1962
1963
    if (is_error) {
1964
        val = JS_GetPropertyStr(ctx, exception, "stack");
1965
        if (!JS_IsUndefined(val)) {
1966
            njs_qjs_dump_obj(ctx, stderr, val, "", "");
1967
        }
1968
1969
        JS_FreeValue(ctx, val);
1970
    }
1971
}
1972
1973
1974
static void
1975
njs_qjs_dump_error(JSContext *ctx)
1976
{
1977
    JSValue  exception;
1978
1979
    exception = JS_GetException(ctx);
1980
    njs_qjs_dump_error2(ctx, exception);
1981
    JS_FreeValue(ctx, exception);
1982
}
1983
1984
1985
static void *
1986
njs_qjs_agent(void *arg)
1987
{
1988
    int            ret;
1989
    JSValue        ret_val, global_obj;
1990
    JSRuntime      *rt;
1991
    JSContext      *ctx, *ctx1;
1992
    njs_console_t  *console;
1993
    JSValue        args[2];
1994
1995
    njs_262agent_t *agent = arg;
1996
    console = agent->console;
1997
1998
    rt = JS_NewRuntime();
1999
    if (rt == NULL) {
2000
        njs_stderror("JS_NewRuntime failure\n");
2001
        exit(1);
2002
    }
2003
2004
    ctx = JS_NewContext(rt);
2005
    if (ctx == NULL) {
2006
        JS_FreeRuntime(rt);
2007
        njs_stderror("JS_NewContext failure\n");
2008
        exit(1);
2009
    }
2010
2011
    JS_SetContextOpaque(ctx, agent);
2012
    JS_SetRuntimeInfo(rt, "agent");
2013
    JS_SetCanBlock(rt, 1);
2014
2015
    global_obj = JS_GetGlobalObject(ctx);
2016
2017
    ret = njs_qjs_global_init(ctx, global_obj);
2018
    if (ret == -1) {
2019
        JS_FreeContext(ctx);
2020
        JS_FreeRuntime(rt);
2021
        njs_stderror("njs_qjs_global_init failure\n");
2022
        exit(1);
2023
    }
2024
2025
    JS_FreeValue(ctx, global_obj);
2026
2027
    ret_val = JS_Eval(ctx, agent->script, strlen(agent->script),
2028
                      "<evalScript>", JS_EVAL_TYPE_GLOBAL);
2029
2030
    free(agent->script);
2031
    agent->script = NULL;
2032
2033
    if (JS_IsException(ret_val)) {
2034
        njs_qjs_dump_error(ctx);
2035
    }
2036
2037
    JS_FreeValue(ctx, ret_val);
2038
2039
    for (;;) {
2040
        ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
2041
        if (ret < 0) {
2042
            njs_qjs_dump_error(ctx);
2043
            break;
2044
2045
        } else if (ret == 0) {
2046
            if (JS_IsUndefined(agent->broadcast_func)) {
2047
                break;
2048
2049
            } else {
2050
                pthread_mutex_lock(&console->agent_mutex);
2051
2052
                while (!agent->broadcast_pending) {
2053
                    pthread_cond_wait(&console->agent_cond,
2054
                                      &console->agent_mutex);
2055
                }
2056
2057
                agent->broadcast_pending = 0;
2058
                pthread_cond_signal(&console->agent_cond);
2059
                pthread_mutex_unlock(&console->agent_mutex);
2060
2061
                args[0] = JS_NewArrayBuffer(ctx, agent->broadcast_sab_buf,
2062
                                            agent->broadcast_sab_size,
2063
                                            NULL, NULL, 1);
2064
                args[1] = JS_NewInt32(ctx, agent->broadcast_val);
2065
2066
                ret_val = JS_Call(ctx, agent->broadcast_func, JS_UNDEFINED,
2067
                                  2, (JSValueConst *)args);
2068
2069
                JS_FreeValue(ctx, args[0]);
2070
                JS_FreeValue(ctx, args[1]);
2071
2072
                if (JS_IsException(ret_val)) {
2073
                    njs_qjs_dump_error(ctx);
2074
                }
2075
2076
                JS_FreeValue(ctx, ret_val);
2077
                JS_FreeValue(ctx, agent->broadcast_func);
2078
                agent->broadcast_func = JS_UNDEFINED;
2079
            }
2080
        }
2081
    }
2082
2083
    JS_FreeValue(ctx, agent->broadcast_func);
2084
2085
    JS_FreeContext(ctx);
2086
    JS_FreeRuntime(rt);
2087
2088
    return NULL;
2089
}
2090
2091
2092
static JSValue
2093
njs_qjs_agent_start(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2094
{
2095
    const char      *script;
2096
    njs_console_t   *console;
2097
    njs_262agent_t  *agent;
2098
2099
    if (JS_GetContextOpaque(ctx) != NULL) {
2100
        return JS_ThrowTypeError(ctx, "cannot be called inside an agent");
2101
    }
2102
2103
    script = JS_ToCString(ctx, argv[0]);
2104
    if (script == NULL) {
2105
        return JS_EXCEPTION;
2106
    }
2107
2108
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
2109
2110
    agent = malloc(sizeof(*agent));
2111
    if (agent == NULL) {
2112
        return JS_ThrowOutOfMemory(ctx);
2113
    }
2114
2115
    njs_memzero(agent, sizeof(*agent));
2116
2117
    agent->broadcast_func = JS_UNDEFINED;
2118
    agent->broadcast_sab = JS_UNDEFINED;
2119
    agent->script = strdup(script);
2120
    if (agent->script == NULL) {
2121
        return JS_ThrowOutOfMemory(ctx);
2122
    }
2123
2124
    JS_FreeCString(ctx, script);
2125
2126
    agent->console = console;
2127
    njs_queue_insert_tail(&console->agents, &agent->link);
2128
2129
    pthread_create(&agent->tid, NULL, njs_qjs_agent, agent);
2130
2131
    return JS_UNDEFINED;
2132
}
2133
2134
2135
static JSValue
2136
njs_qjsr_agent_get_report(JSContext *ctx, JSValue this_val, int argc,
2137
    JSValue *argv)
2138
{
2139
    JSValue             ret;
2140
    njs_console_t       *console;
2141
    njs_queue_link_t    *link;
2142
    njs_agent_report_t  *rep;
2143
2144
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
2145
2146
    pthread_mutex_lock(&console->report_mutex);
2147
2148
    rep = NULL;
2149
2150
    for ( ;; ) {
2151
        link = njs_queue_first(&console->reports);
2152
2153
        if (link == njs_queue_tail(&console->reports)) {
2154
            break;
2155
        }
2156
2157
        rep = njs_queue_link_data(link, njs_agent_report_t, link);
2158
2159
        njs_queue_remove(&rep->link);
2160
        break;
2161
    }
2162
2163
    pthread_mutex_unlock(&console->report_mutex);
2164
2165
    if (rep != NULL) {
2166
        ret = JS_NewString(ctx, rep->str);
2167
        free(rep->str);
2168
        free(rep);
2169
2170
    } else {
2171
        ret = JS_NULL;
2172
    }
2173
2174
    return ret;
2175
}
2176
2177
2178
static njs_bool_t
2179
njs_qjs_broadcast_pending(njs_console_t *console)
2180
{
2181
    njs_262agent_t    *agent;
2182
    njs_queue_link_t  *link;
2183
2184
    link = njs_queue_first(&console->agents);
2185
2186
    for ( ;; ) {
2187
        if (link == njs_queue_tail(&console->agents)) {
2188
            break;
2189
        }
2190
2191
        agent = njs_queue_link_data(link, njs_262agent_t, link);
2192
2193
        if (agent->broadcast_pending) {
2194
            return 1;
2195
        }
2196
2197
        link = njs_queue_next(link);
2198
    }
2199
2200
    return 0;
2201
}
2202
2203
static JSValue
2204
njs_qjs_agent_broadcast(JSContext *ctx, JSValue this_val, int argc,
2205
    JSValue *argv)
2206
{
2207
    uint8_t           *buf;
2208
    size_t            buf_size;
2209
    int32_t           val;
2210
    njs_console_t     *console;
2211
    njs_262agent_t    *agent;
2212
    njs_queue_link_t  *link;
2213
2214
    JSValueConst sab = argv[0];
2215
2216
    if (JS_GetContextOpaque(ctx) != NULL) {
2217
        return JS_ThrowTypeError(ctx, "cannot be called inside an agent");
2218
    }
2219
2220
    buf = JS_GetArrayBuffer(ctx, &buf_size, sab);
2221
    if (buf == NULL) {
2222
        return JS_EXCEPTION;
2223
    }
2224
2225
    if (JS_ToInt32(ctx, &val, argv[1])) {
2226
        return JS_EXCEPTION;
2227
    }
2228
2229
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
2230
2231
    pthread_mutex_lock(&console->agent_mutex);
2232
2233
    link = njs_queue_first(&console->agents);
2234
2235
    for ( ;; ) {
2236
        if (link == njs_queue_tail(&console->agents)) {
2237
            break;
2238
        }
2239
2240
        agent = njs_queue_link_data(link, njs_262agent_t, link);
2241
2242
        agent->broadcast_pending = 1;
2243
        agent->broadcast_sab = JS_DupValue(ctx, sab);
2244
        agent->broadcast_sab_buf = buf;
2245
        agent->broadcast_sab_size = buf_size;
2246
        agent->broadcast_val = val;
2247
2248
        link = njs_queue_next(link);
2249
    }
2250
2251
    pthread_cond_broadcast(&console->agent_cond);
2252
2253
    while (njs_qjs_broadcast_pending(console)) {
2254
        pthread_cond_wait(&console->agent_cond, &console->agent_mutex);
2255
    }
2256
2257
    pthread_mutex_unlock(&console->agent_mutex);
2258
2259
    return JS_UNDEFINED;
2260
}
2261
2262
2263
static JSValue
2264
njs_qjs_agent_receive_broadcast(JSContext *ctx, JSValue this_val, int argc,
2265
    JSValue *argv)
2266
{
2267
    njs_262agent_t *agent = JS_GetContextOpaque(ctx);
2268
    if (agent == NULL) {
2269
        return JS_ThrowTypeError(ctx, "must be called inside an agent");
2270
    }
2271
2272
    if (!JS_IsFunction(ctx, argv[0])) {
2273
        return JS_ThrowTypeError(ctx, "expecting function");
2274
    }
2275
2276
    JS_FreeValue(ctx, agent->broadcast_func);
2277
    agent->broadcast_func = JS_DupValue(ctx, argv[0]);
2278
2279
    return JS_UNDEFINED;
2280
}
2281
2282
2283
static JSValue
2284
njs_qjs_agent_report(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2285
{
2286
    const char          *str;
2287
    njs_console_t       *console;
2288
    njs_262agent_t      *agent;
2289
    njs_agent_report_t  *rep;
2290
2291
    str = JS_ToCString(ctx, argv[0]);
2292
    if (str == NULL) {
2293
        return JS_EXCEPTION;
2294
    }
2295
2296
    rep = malloc(sizeof(*rep));
2297
    rep->str = strdup(str);
2298
    JS_FreeCString(ctx, str);
2299
2300
    agent = JS_GetContextOpaque(ctx);
2301
    console = agent->console;
2302
2303
    pthread_mutex_lock(&console->report_mutex);
2304
    njs_queue_insert_tail(&console->reports, &rep->link);
2305
    pthread_mutex_unlock(&console->report_mutex);
2306
2307
    return JS_UNDEFINED;
2308
}
2309
2310
2311
static JSValue
2312
njs_qjs_agent_leaving(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2313
{
2314
    njs_262agent_t *agent = JS_GetContextOpaque(ctx);
2315
    if (agent == NULL) {
2316
        return JS_ThrowTypeError(ctx, "must be called inside an agent");
2317
    }
2318
2319
    return JS_UNDEFINED;
2320
}
2321
2322
2323
static JSValue
2324
njs_qjs_agent_sleep(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2325
{
2326
    uint32_t duration;
2327
2328
    if (JS_ToUint32(ctx, &duration, argv[0])) {
2329
        return JS_EXCEPTION;
2330
    }
2331
2332
    usleep(duration * 1000);
2333
2334
    return JS_UNDEFINED;
2335
}
2336
2337
2338
static JSValue
2339
njs_qjs_agent_monotonic_now(JSContext *ctx, JSValue this_val, int argc,
2340
    JSValue *argv)
2341
{
2342
    return JS_NewInt64(ctx, njs_time() / 1000000);
2343
}
2344
2345
2346
static const JSCFunctionListEntry njs_qjs_agent_proto[] = {
2347
    JS_CFUNC_DEF("start", 1, njs_qjs_agent_start),
2348
    JS_CFUNC_DEF("getReport", 0, njs_qjsr_agent_get_report),
2349
    JS_CFUNC_DEF("broadcast", 2, njs_qjs_agent_broadcast),
2350
    JS_CFUNC_DEF("report", 1, njs_qjs_agent_report),
2351
    JS_CFUNC_DEF("leaving", 0, njs_qjs_agent_leaving),
2352
    JS_CFUNC_DEF("receiveBroadcast", 1, njs_qjs_agent_receive_broadcast),
2353
    JS_CFUNC_DEF("sleep", 1, njs_qjs_agent_sleep),
2354
    JS_CFUNC_DEF("monotonicNow", 0, njs_qjs_agent_monotonic_now),
2355
};
2356
2357
2358
static JSValue
2359
njs_qjs_new_agent(JSContext *ctx)
2360
{
2361
    JSValue  agent;
2362
2363
    agent = JS_NewObject(ctx);
2364
    if (JS_IsException(agent)) {
2365
        return JS_EXCEPTION;
2366
    }
2367
2368
    JS_SetPropertyFunctionList(ctx, agent, njs_qjs_agent_proto,
2369
                               njs_nitems(njs_qjs_agent_proto));
2370
    return agent;
2371
}
2372
2373
2374
static JSValue
2375
njs_qjs_detach_array_buffer(JSContext *ctx, JSValueConst this_val, int argc,
2376
    JSValueConst *argv)
2377
{
2378
    JS_DetachArrayBuffer(ctx, argv[0]);
2379
2380
    return JS_NULL;
2381
}
2382
2383
static JSValue
2384
njs_qjs_eval_script(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2385
{
2386
    size_t     len;
2387
    JSValue    ret;
2388
    const char *str;
2389
2390
    str = JS_ToCStringLen(ctx, &len, argv[0]);
2391
    if (str == NULL) {
2392
        return JS_EXCEPTION;
2393
    }
2394
2395
    ret = JS_Eval(ctx, str, len, "<evalScript>", JS_EVAL_TYPE_GLOBAL);
2396
2397
    JS_FreeCString(ctx, str);
2398
2399
    return ret;
2400
}
2401
2402
2403
static JSValue
2404
njs_qjs_create_realm(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2405
{
2406
    JSValue    ret_val, global_obj;
2407
    njs_int_t  ret;
2408
    JSContext  *ctx1;
2409
2410
    ctx1 = JS_NewContext(JS_GetRuntime(ctx));
2411
    if (ctx1 == NULL) {
2412
        return JS_ThrowOutOfMemory(ctx);
2413
    }
2414
2415
    global_obj = JS_GetGlobalObject(ctx1);
2416
2417
    ret = njs_qjs_global_init(ctx1, global_obj);
2418
    if (ret == -1) {
2419
        JS_FreeContext(ctx1);
2420
        return JS_EXCEPTION;
2421
    }
2422
2423
    ret_val = JS_GetPropertyStr(ctx1, global_obj, "$262");
2424
2425
    JS_FreeValue(ctx1, global_obj);
2426
    JS_FreeContext(ctx1);
2427
2428
    return ret_val;
2429
}
2430
2431
2432
static JSValue
2433
njs_qjs_is_HTMLDDA(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2434
{
2435
    return JS_NULL;
2436
}
2437
2438
2439
static const JSCFunctionListEntry njs_qjs_262_proto[] = {
2440
    JS_CFUNC_DEF("detachArrayBuffer", 1, njs_qjs_detach_array_buffer),
2441
    JS_CFUNC_DEF("evalScript", 1, njs_qjs_eval_script),
2442
    JS_CFUNC_DEF("codePointRange", 2, js_string_codePointRange),
2443
    JS_CFUNC_DEF("createRealm", 0, njs_qjs_create_realm),
2444
};
2445
2446
2447
static JSValue
2448
njs_qjs_new_262(JSContext *ctx, JSValueConst this_val)
2449
{
2450
    JSValue  obj, obj262, global_obj;
2451
2452
    obj262 = JS_NewObject(ctx);
2453
    if (JS_IsException(obj262)) {
2454
        return JS_EXCEPTION;
2455
    }
2456
2457
    JS_SetPropertyFunctionList(ctx, obj262, njs_qjs_262_proto,
2458
                               njs_nitems(njs_qjs_262_proto));
2459
2460
    global_obj = JS_GetGlobalObject(ctx);
2461
    JS_SetPropertyStr(ctx, obj262, "global", JS_DupValue(ctx, global_obj));
2462
    JS_FreeValue(ctx, global_obj);
2463
2464
    obj = JS_NewCFunction(ctx, njs_qjs_is_HTMLDDA, "IsHTMLDDA", 0);
2465
    JS_SetIsHTMLDDA(ctx, obj);
2466
    JS_SetPropertyStr(ctx, obj262, "IsHTMLDDA", obj);
2467
2468
    JS_SetPropertyStr(ctx, obj262, "agent", njs_qjs_new_agent(ctx));
2469
2470
    return obj262;
2471
}
2472
2473
2474
static const JSCFunctionListEntry njs_qjs_global_proto[] = {
2475
    JS_CFUNC_DEF("clearTimeout", 1, njs_qjs_clear_timeout),
2476
    JS_CFUNC_MAGIC_DEF("print", 0, njs_qjs_console_log, NJS_LOG_INFO),
2477
    JS_CGETSET_DEF("process", njs_qjs_process_getter, NULL),
2478
    JS_CFUNC_MAGIC_DEF("setImmediate", 0, njs_qjs_set_timer, 1),
2479
    JS_CFUNC_MAGIC_DEF("setTimeout", 0, njs_qjs_set_timer, 0),
2480
};
2481
2482
2483
static const JSCFunctionListEntry njs_qjs_console_proto[] = {
2484
    JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Console",
2485
                       JS_PROP_CONFIGURABLE),
2486
    JS_CFUNC_MAGIC_DEF("error", 0, njs_qjs_console_log, NJS_LOG_ERROR),
2487
    JS_CFUNC_MAGIC_DEF("info", 0, njs_qjs_console_log, NJS_LOG_INFO),
2488
    JS_CFUNC_MAGIC_DEF("log", 0, njs_qjs_console_log, NJS_LOG_INFO),
2489
    JS_CFUNC_DEF("time", 0, njs_qjs_console_time),
2490
    JS_CFUNC_DEF("timeEnd", 0, njs_qjs_console_time_end),
2491
    JS_CFUNC_MAGIC_DEF("warn", 0, njs_qjs_console_log, NJS_LOG_WARN),
2492
};
2493
2494
2495
static njs_int_t
2496
njs_qjs_global_init(JSContext *ctx, JSValue global_obj)
2497
{
2498
    JS_SetPropertyFunctionList(ctx, global_obj, njs_qjs_global_proto,
2499
                               njs_nitems(njs_qjs_global_proto));
2500
2501
    return JS_SetPropertyStr(ctx, global_obj, "$262",
2502
                             njs_qjs_new_262(ctx, global_obj));
2503
}
2504
2505
2506
static void
2507
njs_qjs_rejection_tracker(JSContext *ctx, JSValueConst promise,
2508
    JSValueConst reason, JS_BOOL is_handled, void *opaque)
2509
{
2510
    void                    *promise_obj;
2511
    uint32_t                i, length;
2512
    njs_console_t           *console;
2513
    njs_rejected_promise_t  *rejected_promise;
2514
2515
    console = opaque;
2516
2517
    if (is_handled && console->rejected_promises != NULL) {
2518
        rejected_promise = console->rejected_promises->start;
2519
        length = console->rejected_promises->items;
2520
2521
        promise_obj = JS_VALUE_GET_PTR(promise);
2522
2523
        for (i = 0; i < length; i++) {
2524
            if (JS_VALUE_GET_PTR(rejected_promise[i].u.qjs.promise)
2525
                == promise_obj)
2526
            {
2527
                JS_FreeValue(ctx, rejected_promise[i].u.qjs.promise);
2528
                JS_FreeValue(ctx, rejected_promise[i].u.qjs.message);
2529
                njs_arr_remove(console->rejected_promises,
2530
                               &rejected_promise[i]);
2531
2532
                break;
2533
            }
2534
        }
2535
2536
        return;
2537
    }
2538
2539
    if (console->rejected_promises == NULL) {
2540
        console->rejected_promises = njs_arr_create(console->engine->pool, 4,
2541
                                                sizeof(njs_rejected_promise_t));
2542
        if (njs_slow_path(console->rejected_promises == NULL)) {
2543
            return;
2544
        }
2545
    }
2546
2547
    rejected_promise = njs_arr_add(console->rejected_promises);
2548
    if (njs_slow_path(rejected_promise == NULL)) {
2549
        return;
2550
    }
2551
2552
    rejected_promise->u.qjs.promise = JS_DupValue(ctx, promise);
2553
    rejected_promise->u.qjs.message = JS_DupValue(ctx, reason);
2554
}
2555
2556
2557
static JSModuleDef *
2558
njs_qjs_module_loader(JSContext *ctx, const char *module_name, void *opaque)
2559
{
2560
    JSValue            func_val;
2561
    njs_int_t          ret;
2562
    njs_str_t          text, prev_cwd;
2563
    njs_opts_t         *opts;
2564
    njs_console_t      *console;
2565
    JSModuleDef        *m;
2566
    njs_module_info_t  info;
2567
2568
    opts = opaque;
2569
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
2570
2571
    njs_memzero(&info, sizeof(njs_module_info_t));
2572
2573
    info.name.start = (u_char *) module_name;
2574
    info.name.length = njs_strlen(module_name);
2575
2576
    ret = njs_module_lookup(opts, &console->cwd, &info);
2577
    if (njs_slow_path(ret != NJS_OK)) {
2578
        JS_ThrowReferenceError(ctx, "could not load module filename '%s'",
2579
                               module_name);
2580
        return NULL;
2581
    }
2582
2583
    ret = njs_module_read(console->engine->pool, info.fd, &text);
2584
2585
    (void) close(info.fd);
2586
2587
    if (njs_slow_path(ret != NJS_OK)) {
2588
        JS_ThrowInternalError(ctx, "while reading \"%.*s\" module",
2589
                              (int) info.file.length, info.file.start);
2590
        return NULL;
2591
    }
2592
2593
    prev_cwd = console->cwd;
2594
2595
    ret = njs_console_set_cwd(console, &info.file);
2596
    if (njs_slow_path(ret != NJS_OK)) {
2597
        JS_ThrowInternalError(ctx, "while setting cwd for \"%.*s\" module",
2598
                              (int) info.file.length, info.file.start);
2599
        return NULL;
2600
    }
2601
2602
    func_val = JS_Eval(ctx, (char *) text.start, text.length, module_name,
2603
                       JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
2604
2605
    njs_mp_free(console->engine->pool, console->cwd.start);
2606
    console->cwd = prev_cwd;
2607
2608
    njs_mp_free(console->engine->pool, text.start);
2609
2610
    if (JS_IsException(func_val)) {
2611
        return NULL;
2612
    }
2613
2614
    m = JS_VALUE_GET_PTR(func_val);
2615
    JS_FreeValue(ctx, func_val);
2616
2617
    return m;
2618
}
2619
2620
2621
static njs_int_t
2622
njs_engine_qjs_init(njs_engine_t *engine, njs_opts_t *opts)
2623
{
2624
    JSValue    global_obj, obj;
2625
    njs_int_t  ret;
2626
    JSContext  *ctx;
2627
2628
    engine->u.qjs.rt = JS_NewRuntime();
2629
    if (engine->u.qjs.rt == NULL) {
2630
        njs_stderror("JS_NewRuntime() failed\n");
2631
        return NJS_ERROR;
2632
    }
2633
2634
    engine->u.qjs.ctx = qjs_new_context(engine->u.qjs.rt, NULL);
2635
    if (engine->u.qjs.ctx == NULL) {
2636
        njs_stderror("JS_NewContext() failed\n");
2637
        return NJS_ERROR;
2638
    }
2639
2640
    JS_SetRuntimeOpaque(engine->u.qjs.rt, &njs_console);
2641
2642
    engine->u.qjs.value = JS_UNDEFINED;
2643
2644
    ctx = engine->u.qjs.ctx;
2645
2646
    global_obj = JS_GetGlobalObject(ctx);
2647
2648
    ret = njs_qjs_global_init(ctx, global_obj);
2649
    if (ret == -1) {
2650
        njs_stderror("njs_qjs_global_init() failed\n");
2651
        ret = NJS_ERROR;
2652
        goto done;
2653
    }
2654
2655
    obj = JS_NewObject(ctx);
2656
    if (JS_IsException(obj)) {
2657
        njs_stderror("JS_NewObject() failed\n");
2658
        ret = NJS_ERROR;
2659
        goto done;
2660
    }
2661
2662
    JS_SetOpaque(obj, &njs_console);
2663
2664
    JS_SetPropertyFunctionList(ctx, obj, njs_qjs_console_proto,
2665
                               njs_nitems(njs_qjs_console_proto));
2666
2667
    ret = JS_SetPropertyStr(ctx, global_obj, "console", obj);
2668
    if (ret == -1) {
2669
        njs_stderror("JS_SetPropertyStr() failed\n");
2670
        ret = NJS_ERROR;
2671
        goto done;
2672
    }
2673
2674
    if (opts->unhandled_rejection) {
2675
        JS_SetHostPromiseRejectionTracker(engine->u.qjs.rt,
2676
                                         njs_qjs_rejection_tracker,
2677
                                         JS_GetRuntimeOpaque(engine->u.qjs.rt));
2678
    }
2679
2680
    JS_SetModuleLoaderFunc(engine->u.qjs.rt, NULL, njs_qjs_module_loader, opts);
2681
2682
    JS_SetCanBlock(engine->u.qjs.rt, opts->can_block);
2683
2684
    ret = NJS_OK;
2685
2686
done:
2687
2688
    JS_FreeValue(ctx, global_obj);
2689
2690
    return ret;
2691
}
2692
2693
2694
static njs_int_t
2695
njs_engine_qjs_destroy(njs_engine_t *engine)
2696
{
2697
    uint32_t                i;
2698
    njs_ev_t                *ev;
2699
    njs_queue_t             *events;
2700
    njs_console_t           *console;
2701
    njs_262agent_t          *agent;
2702
    njs_queue_link_t        *link;
2703
    njs_rejected_promise_t  *rejected_promise;
2704
2705
    console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
2706
2707
    if (console->rejected_promises != NULL) {
2708
        rejected_promise = console->rejected_promises->start;
2709
2710
        for (i = 0; i < console->rejected_promises->items; i++) {
2711
            JS_FreeValue(engine->u.qjs.ctx, rejected_promise[i].u.qjs.promise);
2712
            JS_FreeValue(engine->u.qjs.ctx, rejected_promise[i].u.qjs.message);
2713
        }
2714
    }
2715
2716
    events = &console->posted_events;
2717
2718
    for ( ;; ) {
2719
        link = njs_queue_first(events);
2720
2721
        if (link == njs_queue_tail(events)) {
2722
            break;
2723
        }
2724
2725
        ev = njs_queue_link_data(link, njs_ev_t, link);
2726
2727
        njs_queue_remove(&ev->link);
2728
        njs_rbtree_delete(&console->events, &ev->node);
2729
2730
        njs_qjs_destroy_event(engine->u.qjs.ctx, console, ev);
2731
    }
2732
2733
    for ( ;; ) {
2734
        link = njs_queue_first(&console->agents);
2735
2736
        if (link == njs_queue_tail(&console->agents)) {
2737
            break;
2738
        }
2739
2740
        agent = njs_queue_link_data(link, njs_262agent_t, link);
2741
2742
        njs_queue_remove(&agent->link);
2743
2744
        pthread_join(agent->tid, NULL);
2745
        JS_FreeValue(engine->u.qjs.ctx, agent->broadcast_sab);
2746
        free(agent->script);
2747
        free(agent);
2748
    }
2749
2750
    JS_FreeValue(engine->u.qjs.ctx, console->process);
2751
    JS_FreeValue(engine->u.qjs.ctx, engine->u.qjs.value);
2752
    JS_FreeContext(engine->u.qjs.ctx);
2753
    JS_FreeRuntime(engine->u.qjs.rt);
2754
2755
    return NJS_OK;
2756
}
2757
2758
2759
static njs_int_t
2760
njs_engine_qjs_eval(njs_engine_t *engine, njs_str_t *script)
2761
{
2762
    int            flags;
2763
    JSValue        code;
2764
    njs_console_t  *console;
2765
2766
    flags = JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT
2767
            | JS_EVAL_FLAG_COMPILE_ONLY;
2768
2769
    console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
2770
2771
    if (console->module) {
2772
        flags |= JS_EVAL_TYPE_MODULE;
2773
    }
2774
2775
    code = JS_Eval(engine->u.qjs.ctx, (char *) script->start,
2776
                   script->length, "<input>", flags);
2777
2778
    if (JS_IsException(code)) {
2779
        return NJS_ERROR;
2780
    }
2781
2782
    JS_FreeValue(engine->u.qjs.ctx, engine->u.qjs.value);
2783
2784
    engine->u.qjs.value = JS_EvalFunction(engine->u.qjs.ctx, code);
2785
2786
    return (JS_IsException(engine->u.qjs.value)) ? NJS_ERROR : NJS_OK;
2787
}
2788
2789
2790
static njs_int_t
2791
njs_engine_qjs_execute_pending_job(njs_engine_t *engine)
2792
{
2793
    JSContext  *ctx1;
2794
2795
    return JS_ExecutePendingJob(engine->u.qjs.rt, &ctx1);
2796
}
2797
2798
2799
static njs_int_t
2800
njs_engine_qjs_unhandled_rejection(njs_engine_t *engine)
2801
{
2802
    size_t                  len;
2803
    uint32_t                i;
2804
    JSContext               *ctx;
2805
    const char              *str;
2806
    njs_console_t           *console;
2807
    njs_rejected_promise_t  *rejected_promise;
2808
2809
    ctx = engine->u.qjs.ctx;
2810
    console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
2811
2812
    if (console->rejected_promises == NULL
2813
        || console->rejected_promises->items == 0)
2814
    {
2815
        return 0;
2816
    }
2817
2818
    rejected_promise = console->rejected_promises->start;
2819
2820
    str = JS_ToCStringLen(ctx, &len, rejected_promise->u.qjs.message);
2821
    if (njs_slow_path(str == NULL)) {
2822
        return -1;
2823
    }
2824
2825
    JS_ThrowTypeError(ctx, "unhandled promise rejection: %.*s", (int) len, str);
2826
    JS_FreeCString(ctx, str);
2827
2828
    for (i = 0; i < console->rejected_promises->items; i++) {
2829
        JS_FreeValue(ctx, rejected_promise[i].u.qjs.promise);
2830
        JS_FreeValue(ctx, rejected_promise[i].u.qjs.message);
2831
    }
2832
2833
    njs_arr_destroy(console->rejected_promises);
2834
    console->rejected_promises = NULL;
2835
2836
    return 1;
2837
}
2838
2839
2840
static njs_int_t
2841
njs_engine_qjs_process_events(njs_engine_t *engine)
2842
{
2843
    JSValue           ret;
2844
    njs_ev_t          *ev;
2845
    JSContext         *ctx;
2846
    njs_queue_t       *events;
2847
    njs_console_t     *console;
2848
    njs_queue_link_t  *link;
2849
2850
    ctx = engine->u.qjs.ctx;
2851
    console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
2852
    events = &console->posted_events;
2853
2854
    for ( ;; ) {
2855
        link = njs_queue_first(events);
2856
2857
        if (link == njs_queue_tail(events)) {
2858
            break;
2859
        }
2860
2861
        ev = njs_queue_link_data(link, njs_ev_t, link);
2862
2863
        njs_queue_remove(&ev->link);
2864
        njs_rbtree_delete(&console->events, &ev->node);
2865
2866
        ret = JS_Call(ctx, ev->u.qjs.function, JS_UNDEFINED, ev->nargs,
2867
                      ev->u.qjs.args);
2868
2869
        njs_qjs_destroy_event(ctx, console, ev);
2870
2871
        if (JS_IsException(ret)) {
2872
            engine->output(engine, NJS_ERROR);
2873
2874
            if (!console->interactive) {
2875
                return NJS_ERROR;
2876
            }
2877
        }
2878
2879
        JS_FreeValue(ctx, ret);
2880
    }
2881
2882
    if (!njs_rbtree_is_empty(&console->events)) {
2883
        return NJS_AGAIN;
2884
    }
2885
2886
    return JS_IsJobPending(engine->u.qjs.rt) ? NJS_AGAIN: NJS_OK;
2887
}
2888
2889
2890
static njs_int_t
2891
njs_engine_qjs_output(njs_engine_t *engine, njs_int_t ret)
2892
{
2893
    JSContext      *ctx;
2894
    njs_console_t  *console;
2895
2896
    ctx = engine->u.qjs.ctx;
2897
    console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
2898
2899
    if (ret == NJS_OK) {
2900
        if (console->interactive) {
2901
            njs_qjs_dump_obj(ctx, stdout, engine->u.qjs.value, "", "\'");
2902
        }
2903
2904
    } else {
2905
        njs_qjs_dump_error(ctx);
2906
    }
2907
2908
    return NJS_OK;
2909
}
2910
2911
2912
static njs_arr_t *
2913
njs_qjs_object_completions(njs_engine_t *engine, JSContext *ctx,
2914
    JSValueConst object, njs_str_t *expression)
2915
{
2916
    u_char          *prefix;
2917
    size_t          len, prefix_len;
2918
    JSValue         prototype;
2919
    uint32_t        k, n, length;
2920
    njs_int_t       ret;
2921
    njs_arr_t       *array;
2922
    njs_str_t       *completion, key;
2923
    JSPropertyEnum  *ptab;
2924
2925
    prefix = expression->start + expression->length;
2926
2927
    while (prefix > expression->start && *prefix != '.') {
2928
        prefix--;
2929
    }
2930
2931
    if (prefix != expression->start) {
2932
        prefix++;
2933
    }
2934
2935
    ptab = NULL;
2936
    key.start = NULL;
2937
    prefix_len = prefix - expression->start;
2938
    len = expression->length - prefix_len;
2939
2940
    array = njs_arr_create(engine->pool, 8, sizeof(njs_str_t));
2941
    if (njs_slow_path(array == NULL)) {
2942
        goto fail;
2943
    }
2944
2945
    while (!JS_IsNull(object)) {
2946
        ret = JS_GetOwnPropertyNames(ctx, &ptab, &length, object,
2947
                                     JS_GPN_STRING_MASK);
2948
        if (ret < 0) {
2949
            goto fail;
2950
        }
2951
2952
        for (n = 0; n < length; n++) {
2953
            key.start = (u_char *) JS_AtomToCString(ctx, ptab[n].atom);
2954
            if (njs_slow_path(key.start == NULL)) {
2955
                goto fail;
2956
            }
2957
2958
            key.length = njs_strlen(key.start);
2959
2960
            if (len > key.length || njs_strncmp(key.start, prefix, len) != 0) {
2961
                goto next;
2962
            }
2963
2964
            for (k = 0; k < array->items; k++) {
2965
                completion = njs_arr_item(array, k);
2966
2967
                if ((completion->length - prefix_len - 1) == key.length
2968
                    && njs_strncmp(&completion->start[prefix_len],
2969
                                   key.start, key.length)
2970
                       == 0)
2971
                {
2972
                    goto next;
2973
                }
2974
            }
2975
2976
            completion = njs_arr_add(array);
2977
            if (njs_slow_path(completion == NULL)) {
2978
                goto fail;
2979
            }
2980
2981
            completion->length = prefix_len + key.length + 1;
2982
            completion->start = njs_mp_alloc(engine->pool, completion->length);
2983
            if (njs_slow_path(completion->start == NULL)) {
2984
                goto fail;
2985
            }
2986
2987
            njs_sprintf(completion->start,
2988
                        completion->start + completion->length,
2989
                        "%*s%V%Z", prefix_len, expression->start, &key);
2990
2991
next:
2992
2993
            JS_FreeCString(ctx, (const char *) key.start);
2994
        }
2995
2996
        qjs_free_prop_enum(ctx, ptab, length);
2997
2998
        prototype = JS_GetPrototype(ctx, object);
2999
        if (JS_IsException(prototype)) {
3000
            goto fail;
3001
        }
3002
3003
        JS_FreeValue(ctx, object);
3004
        object = prototype;
3005
    }
3006
3007
    return array;
3008
3009
fail:
3010
3011
    if (array != NULL) {
3012
        njs_arr_destroy(array);
3013
    }
3014
3015
    if (key.start != NULL) {
3016
        JS_FreeCString(ctx, (const char *) key.start);
3017
    }
3018
3019
    if (ptab != NULL) {
3020
        qjs_free_prop_enum(ctx, ptab, length);
3021
    }
3022
3023
    JS_FreeValue(ctx, object);
3024
3025
    return NULL;
3026
}
3027
3028
3029
static njs_arr_t *
3030
njs_engine_qjs_complete(njs_engine_t *engine, njs_str_t *expression)
3031
{
3032
    u_char      *p, *start, *end;
3033
    JSAtom      key;
3034
    JSValue     value, retval;
3035
    njs_arr_t   *arr;
3036
    JSContext   *ctx;
3037
    njs_bool_t  global;
3038
3039
    ctx = engine->u.qjs.ctx;
3040
3041
    p = expression->start;
3042
    end = p + expression->length;
3043
3044
    global = 1;
3045
    value = JS_GetGlobalObject(ctx);
3046
3047
    while (p < end && *p != '.') { p++; }
3048
3049
    if (p == end) {
3050
        goto done;
3051
    }
3052
3053
    p = expression->start;
3054
3055
    for ( ;; ) {
3056
3057
        start = (*p == '.' && p < end) ? ++p: p;
3058
3059
        if (p == end) {
3060
            break;
3061
        }
3062
3063
        while (p < end && *p != '.') { p++; }
3064
3065
        key = JS_NewAtomLen(ctx, (char *) start, p - start);
3066
        if (key == JS_ATOM_NULL) {
3067
            goto fail;
3068
        }
3069
3070
        retval = JS_GetProperty(ctx, value, key);
3071
3072
        JS_FreeAtom(ctx, key);
3073
3074
        if (JS_IsUndefined(retval)) {
3075
            if (global) {
3076
                goto fail;
3077
            }
3078
3079
            goto done;
3080
        }
3081
3082
        if (JS_IsException(retval)) {
3083
            goto fail;
3084
        }
3085
3086
        JS_FreeValue(ctx, value);
3087
        value = retval;
3088
        global = 0;
3089
    }
3090
3091
done:
3092
3093
    arr = njs_qjs_object_completions(engine, ctx, JS_DupValue(ctx, value),
3094
                                     expression);
3095
3096
    JS_FreeValue(ctx, value);
3097
3098
    return arr;
3099
3100
fail:
3101
3102
    JS_FreeValue(ctx, value);
3103
3104
    return NULL;
3105
}
3106
3107
#endif
3108
3109
3110
static njs_engine_t *
3111
njs_create_engine(njs_opts_t *opts)
3112
11.6k
{
3113
11.6k
    njs_mp_t      *mp;
3114
11.6k
    njs_int_t     ret;
3115
11.6k
    njs_engine_t  *engine;
3116
3117
11.6k
    mp = njs_mp_fast_create(2 * njs_pagesize(), 128, 512, 16);
3118
11.6k
    if (njs_slow_path(mp == NULL)) {
3119
0
        return NULL;
3120
0
    }
3121
3122
11.6k
    engine = njs_mp_zalloc(mp, sizeof(njs_engine_t));
3123
11.6k
    if (njs_slow_path(engine == NULL)) {
3124
0
        return NULL;
3125
0
    }
3126
3127
11.6k
    engine->pool = mp;
3128
3129
11.6k
    njs_console.engine = engine;
3130
3131
11.6k
    switch (opts->engine) {
3132
11.6k
    case NJS_ENGINE_NJS:
3133
11.6k
        ret = njs_engine_njs_init(engine, opts);
3134
11.6k
        if (njs_slow_path(ret != NJS_OK)) {
3135
0
            njs_stderror("njs_engine_njs_init() failed\n");
3136
0
            return NULL;
3137
0
        }
3138
3139
11.6k
        engine->type = NJS_ENGINE_NJS;
3140
11.6k
        engine->eval = njs_engine_njs_eval;
3141
11.6k
        engine->execute_pending_job = njs_engine_njs_execute_pending_job;
3142
11.6k
        engine->unhandled_rejection = njs_engine_njs_unhandled_rejection;
3143
11.6k
        engine->process_events = njs_engine_njs_process_events;
3144
11.6k
        engine->destroy = njs_engine_njs_destroy;
3145
11.6k
        engine->output = njs_engine_njs_output;
3146
11.6k
        engine->complete = njs_engine_njs_complete;
3147
11.6k
        break;
3148
3149
#ifdef NJS_HAVE_QUICKJS
3150
    case NJS_ENGINE_QUICKJS:
3151
        ret = njs_engine_qjs_init(engine, opts);
3152
        if (njs_slow_path(ret != NJS_OK)) {
3153
            njs_stderror("njs_engine_qjs_init() failed\n");
3154
            return NULL;
3155
        }
3156
3157
        engine->type = NJS_ENGINE_QUICKJS;
3158
        engine->eval = njs_engine_qjs_eval;
3159
        engine->execute_pending_job = njs_engine_qjs_execute_pending_job;
3160
        engine->unhandled_rejection = njs_engine_qjs_unhandled_rejection;
3161
        engine->process_events = njs_engine_qjs_process_events;
3162
        engine->destroy = njs_engine_qjs_destroy;
3163
        engine->output = njs_engine_qjs_output;
3164
        engine->complete = njs_engine_qjs_complete;
3165
        break;
3166
#endif
3167
3168
0
    default:
3169
0
        njs_stderror("unknown engine type\n");
3170
0
        return NULL;
3171
11.6k
    }
3172
3173
11.6k
    return engine;
3174
11.6k
}
3175
3176
3177
static njs_int_t
3178
njs_read_file(njs_opts_t *opts, njs_str_t *content)
3179
0
{
3180
0
    int          fd;
3181
0
    char         *file;
3182
0
    u_char       *p, *end, *start;
3183
0
    size_t       size;
3184
0
    ssize_t      n;
3185
0
    njs_int_t    ret;
3186
0
    struct stat  sb;
3187
3188
0
    file = opts->file;
3189
3190
0
    if (file[0] == '-' && file[1] == '\0') {
3191
0
        fd = STDIN_FILENO;
3192
3193
0
    } else {
3194
0
        fd = open(file, O_RDONLY);
3195
0
        if (fd == -1) {
3196
0
            njs_stderror("failed to open file: '%s' (%s)\n",
3197
0
                         file, strerror(errno));
3198
0
            return NJS_ERROR;
3199
0
        }
3200
0
    }
3201
3202
0
    if (fstat(fd, &sb) == -1) {
3203
0
        njs_stderror("fstat(%d) failed while reading '%s' (%s)\n",
3204
0
                     fd, file, strerror(errno));
3205
0
        ret = NJS_ERROR;
3206
0
        goto close_fd;
3207
0
    }
3208
3209
0
    size = 4096;
3210
3211
0
    if (S_ISREG(sb.st_mode) && sb.st_size) {
3212
0
        size = sb.st_size;
3213
0
    }
3214
3215
0
    content->length = 0;
3216
0
    content->start = realloc(NULL, size + 1);
3217
0
    if (content->start == NULL) {
3218
0
        njs_stderror("alloc failed while reading '%s'\n", file);
3219
0
        ret = NJS_ERROR;
3220
0
        goto close_fd;
3221
0
    }
3222
3223
0
    p = content->start;
3224
0
    end = p + size;
3225
3226
0
    for ( ;; ) {
3227
0
        n = read(fd, p, end - p);
3228
3229
0
        if (n == 0) {
3230
0
            break;
3231
0
        }
3232
3233
0
        if (n < 0) {
3234
0
            njs_stderror("failed to read file: '%s' (%s)\n",
3235
0
                      file, strerror(errno));
3236
0
            ret = NJS_ERROR;
3237
0
            goto close_fd;
3238
0
        }
3239
3240
0
        if (p + n == end) {
3241
0
            size *= 2;
3242
3243
0
            start = realloc(content->start, size + 1);
3244
0
            if (start == NULL) {
3245
0
                njs_stderror("alloc failed while reading '%s'\n", file);
3246
0
                ret = NJS_ERROR;
3247
0
                goto close_fd;
3248
0
            }
3249
3250
0
            content->start = start;
3251
3252
0
            p = content->start + content->length;
3253
0
            end = content->start + size;
3254
0
        }
3255
3256
0
        p += n;
3257
0
        content->length += n;
3258
0
    }
3259
3260
0
    content->start[content->length] = '\0';
3261
3262
0
    ret = NJS_OK;
3263
3264
0
close_fd:
3265
3266
0
    if (fd != STDIN_FILENO) {
3267
0
        (void) close(fd);
3268
0
    }
3269
3270
0
    return ret;
3271
0
}
3272
3273
3274
static njs_int_t
3275
njs_process_file(njs_opts_t *opts)
3276
0
{
3277
0
    u_char        *p;
3278
0
    njs_int_t     ret;
3279
0
    njs_str_t     source, script;
3280
0
    njs_engine_t  *engine;
3281
3282
0
    engine = NULL;
3283
0
    source.start = NULL;
3284
3285
0
    ret = njs_read_file(opts, &source);
3286
0
    if (ret != NJS_OK) {
3287
0
        goto done;
3288
0
    }
3289
3290
0
    script = source;
3291
3292
    /* shebang */
3293
3294
0
    if (script.length > 2 && memcmp(script.start, "#!", 2) == 0) {
3295
0
        p = njs_strlchr(script.start, script.start + script.length, '\n');
3296
3297
0
        if (p != NULL) {
3298
0
            script.length -= (p + 1 - script.start);
3299
0
            script.start = p + 1;
3300
3301
0
        } else {
3302
0
            script.length = 0;
3303
0
        }
3304
0
    }
3305
3306
0
    engine = njs_create_engine(opts);
3307
0
    if (engine == NULL) {
3308
0
        ret = NJS_ERROR;
3309
0
        goto done;
3310
0
    }
3311
3312
0
    ret = njs_process_script(engine, &njs_console, &script);
3313
0
    if (ret != NJS_OK) {
3314
0
        ret = NJS_ERROR;
3315
0
        goto done;
3316
0
    }
3317
3318
0
    ret = NJS_OK;
3319
3320
0
done:
3321
3322
0
    if (engine != NULL) {
3323
0
        engine->destroy(engine);
3324
0
    }
3325
3326
0
    if (source.start != NULL) {
3327
0
        free(source.start);
3328
0
    }
3329
3330
0
    return ret;
3331
0
}
3332
3333
3334
static njs_int_t
3335
njs_process_script(njs_engine_t *engine, njs_console_t *console,
3336
    njs_str_t *script)
3337
11.6k
{
3338
11.6k
    njs_int_t   ret;
3339
3340
11.6k
    ret = engine->eval(engine, script);
3341
3342
11.6k
    if (!console->suppress_stdout) {
3343
0
        engine->output(engine, ret);
3344
0
    }
3345
3346
11.6k
    if (!console->interactive && ret == NJS_ERROR) {
3347
157
        return NJS_ERROR;
3348
157
    }
3349
3350
11.4k
    for ( ;; ) {
3351
63.4k
        for ( ;; ) {
3352
63.4k
            ret = engine->execute_pending_job(engine);
3353
63.4k
            if (ret <= NJS_OK) {
3354
11.4k
                if (ret == NJS_ERROR) {
3355
0
                    if (!console->suppress_stdout) {
3356
0
                        engine->output(engine, ret);
3357
0
                    }
3358
3359
0
                    if (!console->interactive) {
3360
0
                         return NJS_ERROR;
3361
0
                    }
3362
0
                }
3363
3364
11.4k
                break;
3365
11.4k
            }
3366
63.4k
        }
3367
3368
11.4k
        ret = engine->process_events(engine);
3369
11.4k
        if (njs_slow_path(ret == NJS_ERROR)) {
3370
0
            break;
3371
0
        }
3372
3373
11.4k
        if (engine->unhandled_rejection(engine)) {
3374
0
            if (!console->suppress_stdout) {
3375
0
                engine->output(engine, NJS_ERROR);
3376
0
            }
3377
3378
0
            if (!console->interactive) {
3379
0
                return NJS_ERROR;
3380
0
            }
3381
0
        }
3382
3383
11.4k
        if (ret == NJS_OK) {
3384
11.4k
            break;
3385
11.4k
        }
3386
11.4k
    }
3387
3388
11.4k
    return ret;
3389
11.4k
}
3390
3391
3392
#if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE)
3393
3394
3395
volatile sig_atomic_t njs_running;
3396
volatile sig_atomic_t njs_sigint_count;
3397
volatile sig_atomic_t njs_sigint_received;
3398
3399
3400
static void
3401
njs_cb_line_handler(char *line_in)
3402
{
3403
    njs_int_t  ret;
3404
    njs_str_t  line;
3405
3406
    if (line_in == NULL) {
3407
        njs_running = NJS_DONE;
3408
        return;
3409
    }
3410
3411
    line.start = (u_char *) line_in;
3412
    line.length = njs_strlen(line.start);
3413
3414
    if (strcmp(line_in, ".exit") == 0) {
3415
        njs_running = NJS_DONE;
3416
        goto free_line;
3417
    }
3418
3419
    njs_sigint_count = 0;
3420
3421
    if (line.length == 0) {
3422
        rl_callback_handler_install(">> ", njs_cb_line_handler);
3423
        goto free_line;
3424
    }
3425
3426
    add_history((char *) line.start);
3427
3428
    ret = njs_process_script(njs_console.engine, &njs_console, &line);
3429
    if (ret == NJS_ERROR) {
3430
        njs_running = NJS_ERROR;
3431
    }
3432
3433
    if (ret == NJS_OK) {
3434
        rl_callback_handler_install(">> ", njs_cb_line_handler);
3435
    }
3436
3437
free_line:
3438
3439
    free(line.start);
3440
}
3441
3442
3443
static njs_int_t
3444
njs_interactive_shell(njs_opts_t *opts)
3445
{
3446
    int             flags;
3447
    fd_set          fds;
3448
    njs_int_t       ret;
3449
    njs_engine_t    *engine;
3450
    struct timeval  timeout;
3451
3452
    if (njs_editline_init() != NJS_OK) {
3453
        njs_stderror("failed to init completions\n");
3454
        return NJS_ERROR;
3455
    }
3456
3457
    engine = njs_create_engine(opts);
3458
    if (engine == NULL) {
3459
        njs_stderror("njs_create_engine() failed\n");
3460
        return NJS_ERROR;
3461
    }
3462
3463
    if (!opts->quiet) {
3464
        if (engine->type == NJS_ENGINE_NJS) {
3465
            njs_printf("interactive njs (njs:%s)\n\n", NJS_VERSION);
3466
3467
#if (NJS_HAVE_QUICKJS)
3468
        } else {
3469
            njs_printf("interactive njs (QuickJS:%s)\n\n", NJS_QUICKJS_VERSION);
3470
#endif
3471
        }
3472
    }
3473
3474
    rl_callback_handler_install(">> ", njs_cb_line_handler);
3475
3476
    flags = fcntl(STDIN_FILENO, F_GETFL, 0);
3477
    fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
3478
3479
    njs_running = NJS_OK;
3480
3481
    while (njs_running == NJS_OK) {
3482
        FD_ZERO(&fds);
3483
        FD_SET(STDIN_FILENO, &fds);
3484
        timeout = (struct timeval) {1, 0};
3485
3486
        ret = select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
3487
        if (ret < 0 && errno != EINTR) {
3488
            njs_stderror("select() failed\n");
3489
            njs_running = NJS_ERROR;
3490
            break;
3491
        }
3492
3493
        if (njs_sigint_received) {
3494
            if (njs_sigint_count > 1) {
3495
                njs_running = NJS_DONE;
3496
                break;
3497
            }
3498
3499
            if (rl_end != 0) {
3500
                njs_printf("\n");
3501
3502
                njs_sigint_count = 0;
3503
3504
            } else {
3505
                njs_printf("(To exit, press Ctrl+C again or Ctrl+D "
3506
                           "or type .exit)\n");
3507
3508
                njs_sigint_count = 1;
3509
            }
3510
3511
            rl_point = rl_end = 0;
3512
            rl_on_new_line();
3513
            rl_redisplay();
3514
3515
            njs_sigint_received = 0;
3516
        }
3517
3518
        if (ret < 0) {
3519
            continue;
3520
        }
3521
3522
        if (FD_ISSET(fileno(rl_instream), &fds)) {
3523
            rl_callback_read_char();
3524
        }
3525
    }
3526
3527
    rl_callback_handler_remove();
3528
3529
    if (njs_running == NJS_DONE) {
3530
        njs_printf("exiting\n");
3531
    }
3532
3533
    engine->destroy(engine);
3534
3535
    return njs_running == NJS_DONE ? NJS_OK : njs_running;
3536
}
3537
3538
3539
static char **
3540
njs_completion_handler(const char *text, int start, int end)
3541
{
3542
    rl_attempted_completion_over = 1;
3543
3544
    return rl_completion_matches(text, njs_completion_generator);
3545
}
3546
3547
3548
static void
3549
njs_signal_handler(int signal)
3550
{
3551
    switch (signal) {
3552
    case SIGINT:
3553
        njs_sigint_received = 1;
3554
        njs_sigint_count += 1;
3555
        break;
3556
    default:
3557
        break;
3558
    }
3559
}
3560
3561
3562
static njs_int_t
3563
njs_editline_init(void)
3564
{
3565
    rl_completion_append_character = '\0';
3566
    rl_attempted_completion_function = njs_completion_handler;
3567
    rl_basic_word_break_characters = (char *) " \t\n\"\\'`@$><=;,|&{(";
3568
3569
    setlocale(LC_ALL, "");
3570
3571
    signal(SIGINT, njs_signal_handler);
3572
3573
    return NJS_OK;
3574
}
3575
3576
3577
static char *
3578
njs_completion_generator(const char *text, int state)
3579
{
3580
    njs_str_t         expression, *suffix;
3581
    njs_engine_t      *engine;
3582
    njs_completion_t  *cmpl;
3583
3584
    engine = njs_console.engine;
3585
    cmpl = &engine->completion;
3586
3587
    if (state == 0) {
3588
        cmpl->index = 0;
3589
        expression.start = (u_char *) text;
3590
        expression.length = njs_strlen(text);
3591
3592
        cmpl->suffix_completions = engine->complete(engine, &expression);
3593
        if (cmpl->suffix_completions == NULL) {
3594
            return NULL;
3595
        }
3596
    }
3597
3598
    if (cmpl->index == cmpl->suffix_completions->items) {
3599
        return NULL;
3600
    }
3601
3602
    suffix = njs_arr_item(cmpl->suffix_completions, cmpl->index++);
3603
3604
    return strndup((char *) suffix->start, suffix->length);
3605
}
3606
3607
#endif
3608
3609
3610
static njs_int_t
3611
njs_ext_console_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3612
    njs_index_t magic, njs_value_t *retval)
3613
18.3k
{
3614
18.3k
    njs_str_t        msg;
3615
18.3k
    njs_uint_t       n;
3616
18.3k
    njs_log_level_t  level;
3617
3618
18.3k
    n = 1;
3619
18.3k
    level = (njs_log_level_t) magic & NJS_LOG_MASK;
3620
3621
73.4k
    while (n < nargs) {
3622
55.0k
        if (njs_vm_value_dump(vm, &msg, njs_argument(args, n), 1,
3623
55.0k
                              !!(magic & NJS_LOG_DUMP))
3624
55.0k
            == NJS_ERROR)
3625
0
        {
3626
0
            return NJS_ERROR;
3627
0
        }
3628
3629
55.0k
        njs_console_logger(level, msg.start, msg.length);
3630
3631
55.0k
        n++;
3632
55.0k
    }
3633
3634
18.3k
    njs_value_undefined_set(retval);
3635
3636
18.3k
    return NJS_OK;
3637
18.3k
}
3638
3639
3640
static njs_int_t
3641
njs_ext_console_time(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3642
    njs_index_t unused, njs_value_t *retval)
3643
0
{
3644
0
    njs_int_t      ret;
3645
0
    njs_str_t      name;
3646
0
    njs_value_t    *value;
3647
0
    njs_console_t  *console;
3648
3649
0
    static const njs_str_t  default_label = njs_str("default");
3650
3651
0
    console = njs_vm_external(vm, njs_console_proto_id, njs_argument(args, 0));
3652
0
    if (njs_slow_path(console == NULL)) {
3653
0
        njs_vm_error(vm, "external value is expected");
3654
0
        return NJS_ERROR;
3655
0
    }
3656
3657
0
    name = default_label;
3658
3659
0
    value = njs_arg(args, nargs, 1);
3660
3661
0
    if (njs_slow_path(!njs_value_is_string(value))) {
3662
0
        if (!njs_value_is_undefined(value)) {
3663
0
            ret = njs_value_to_string(vm, value, value);
3664
0
            if (njs_slow_path(ret != NJS_OK)) {
3665
0
                return ret;
3666
0
            }
3667
3668
0
            njs_value_string_get(vm, value, &name);
3669
0
        }
3670
3671
0
    } else {
3672
0
        njs_value_string_get(vm, value, &name);
3673
0
    }
3674
3675
0
    if (njs_console_time(console, &name) != NJS_OK) {
3676
0
        njs_vm_error(vm, "failed to add timer");
3677
0
        return NJS_ERROR;
3678
0
    }
3679
3680
0
    njs_value_undefined_set(retval);
3681
3682
0
    return NJS_OK;
3683
0
}
3684
3685
3686
static njs_int_t
3687
njs_ext_console_time_end(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3688
    njs_index_t unused, njs_value_t *retval)
3689
0
{
3690
0
    uint64_t       ns;
3691
0
    njs_int_t      ret;
3692
0
    njs_str_t      name;
3693
0
    njs_value_t    *value;
3694
0
    njs_console_t  *console;
3695
3696
0
    static const njs_str_t  default_label = njs_str("default");
3697
3698
0
    ns = njs_time();
3699
3700
0
    console = njs_vm_external(vm, njs_console_proto_id, njs_argument(args, 0));
3701
0
    if (njs_slow_path(console == NULL)) {
3702
0
        njs_vm_error(vm, "external value is expected");
3703
0
        return NJS_ERROR;
3704
0
    }
3705
3706
0
    name = default_label;
3707
3708
0
    value = njs_arg(args, nargs, 1);
3709
3710
0
    if (njs_slow_path(!njs_value_is_string(value))) {
3711
0
        if (!njs_value_is_undefined(value)) {
3712
0
            ret = njs_value_to_string(vm, value, value);
3713
0
            if (njs_slow_path(ret != NJS_OK)) {
3714
0
                return ret;
3715
0
            }
3716
3717
0
            njs_value_string_get(vm, value, &name);
3718
0
        }
3719
3720
0
    } else {
3721
0
        njs_value_string_get(vm, value, &name);
3722
0
    }
3723
3724
0
    njs_console_time_end(console, &name, ns);
3725
3726
0
    njs_value_undefined_set(retval);
3727
3728
0
    return NJS_OK;
3729
0
}
3730
3731
3732
static njs_int_t
3733
njs_set_timer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3734
    njs_index_t unused, njs_bool_t immediate, njs_value_t *retval)
3735
0
{
3736
0
    njs_ev_t       *ev;
3737
0
    uint64_t       delay;
3738
0
    njs_uint_t     n;
3739
0
    njs_console_t  *console;
3740
3741
0
    console = njs_vm_external_ptr(vm);
3742
3743
0
    if (njs_slow_path(nargs < 2)) {
3744
0
        njs_vm_type_error(vm, "too few arguments");
3745
0
        return NJS_ERROR;
3746
0
    }
3747
3748
0
    if (njs_slow_path(!njs_value_is_function(njs_argument(args, 1)))) {
3749
0
        njs_vm_type_error(vm, "first arg must be a function");
3750
0
        return NJS_ERROR;
3751
0
    }
3752
3753
0
    delay = 0;
3754
3755
0
    if (!immediate && nargs >= 3
3756
0
        && njs_value_is_number(njs_argument(args, 2)))
3757
0
    {
3758
0
        delay = njs_value_number(njs_argument(args, 2));
3759
0
    }
3760
3761
0
    if (delay != 0) {
3762
0
        njs_vm_internal_error(vm, "njs_set_timer(): async timers unsupported");
3763
0
        return NJS_ERROR;
3764
0
    }
3765
3766
0
    n = immediate ? 2 : 3;
3767
0
    nargs = (nargs >= n) ? nargs - n : 0;
3768
3769
0
    ev = njs_mp_alloc(njs_vm_memory_pool(vm),
3770
0
                      sizeof(njs_ev_t) + sizeof(njs_opaque_value_t) * nargs);
3771
0
    if (njs_slow_path(ev == NULL)) {
3772
0
        njs_vm_memory_error(vm);
3773
0
        return NJS_ERROR;
3774
0
    }
3775
3776
0
    ev->u.njs.function = njs_value_function(njs_argument(args, 1));
3777
0
    ev->u.njs.args = (njs_value_t *) ((u_char *) ev + sizeof(njs_ev_t));
3778
0
    ev->nargs = nargs;
3779
0
    ev->id = console->event_id++;
3780
3781
0
    if (ev->nargs != 0) {
3782
0
        memcpy(ev->u.njs.args, njs_argument(args, n),
3783
0
               sizeof(njs_opaque_value_t) * ev->nargs);
3784
0
    }
3785
3786
0
    njs_rbtree_insert(&console->events, &ev->node);
3787
3788
0
    njs_queue_insert_tail(&console->posted_events, &ev->link);
3789
3790
0
    njs_value_number_set(retval, ev->id);
3791
3792
0
    return NJS_OK;
3793
0
}
3794
3795
3796
static njs_int_t
3797
njs_set_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3798
    njs_index_t unused, njs_value_t *retval)
3799
0
{
3800
0
    return njs_set_timer(vm, args, nargs, unused, 0, retval);
3801
0
}
3802
3803
3804
static njs_int_t
3805
njs_set_immediate(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3806
    njs_index_t unused, njs_value_t *retval)
3807
0
{
3808
0
    return njs_set_timer(vm, args, nargs, unused, 1, retval);
3809
0
}
3810
3811
3812
static njs_int_t
3813
njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3814
    njs_index_t unused, njs_value_t *retval)
3815
0
{
3816
0
    njs_ev_t           ev_lookup, *ev;
3817
0
    njs_console_t      *console;
3818
0
    njs_rbtree_node_t  *rb;
3819
3820
0
    if (nargs < 2 || !njs_value_is_number(njs_argument(args, 1))) {
3821
0
        njs_value_undefined_set(retval);
3822
0
        return NJS_OK;
3823
0
    }
3824
3825
0
    console = njs_vm_external_ptr(vm);
3826
3827
0
    ev_lookup.id = njs_value_number(njs_argument(args, 1));
3828
3829
0
    rb = njs_rbtree_find(&console->events, &ev_lookup.node);
3830
0
    if (njs_slow_path(rb == NULL)) {
3831
0
        njs_vm_internal_error(vm, "failed to find timer");
3832
0
        return NJS_ERROR;
3833
0
    }
3834
3835
0
    ev = (njs_ev_t *) rb;
3836
0
    njs_queue_remove(&ev->link);
3837
0
    njs_rbtree_delete(&console->events, (njs_rbtree_part_t *) rb);
3838
3839
0
    njs_mp_free(njs_vm_memory_pool(vm), ev);
3840
3841
0
    njs_value_undefined_set(retval);
3842
3843
0
    return NJS_OK;
3844
0
}
3845
3846
3847
static void
3848
njs_console_log(njs_log_level_t level, const char *fmt, ...)
3849
0
{
3850
0
    u_char   *p;
3851
0
    va_list  args;
3852
0
    u_char   buf[2048];
3853
3854
0
    va_start(args, fmt);
3855
0
    p = njs_vsprintf(buf, buf + sizeof(buf), fmt, args);
3856
0
    va_end(args);
3857
3858
0
    njs_console_logger(level, buf, p - buf);
3859
0
}
3860
3861
3862
static void
3863
njs_console_logger(njs_log_level_t level, const u_char *start, size_t length)
3864
55.0k
{
3865
55.0k
    switch (level) {
3866
0
    case NJS_LOG_WARN:
3867
0
        njs_printf("W: ");
3868
0
        break;
3869
0
    case NJS_LOG_ERROR:
3870
0
        njs_printf("E: ");
3871
0
        break;
3872
55.0k
    case NJS_LOG_INFO:
3873
55.0k
        break;
3874
55.0k
    }
3875
3876
55.0k
    njs_print(start, length);
3877
55.0k
    njs_print("\n", 1);
3878
55.0k
}
3879
3880
3881
static njs_int_t
3882
njs_console_time(njs_console_t *console, njs_str_t *name)
3883
0
{
3884
0
    njs_queue_t       *labels;
3885
0
    njs_timelabel_t   *label;
3886
0
    njs_queue_link_t  *link;
3887
3888
0
    labels = &console->labels;
3889
0
    link = njs_queue_first(labels);
3890
3891
0
    while (link != njs_queue_tail(labels)) {
3892
0
        label = njs_queue_link_data(link, njs_timelabel_t, link);
3893
3894
0
        if (njs_strstr_eq(name, &label->name)) {
3895
0
            njs_console_log(NJS_LOG_INFO, "Timer \"%V\" already exists.",
3896
0
                            name);
3897
0
            return NJS_OK;
3898
0
        }
3899
3900
0
        link = njs_queue_next(link);
3901
0
    }
3902
3903
0
    label = njs_mp_alloc(console->engine->pool,
3904
0
                         sizeof(njs_timelabel_t) + name->length);
3905
0
    if (njs_slow_path(label == NULL)) {
3906
0
        return NJS_ERROR;
3907
0
    }
3908
3909
0
    label->name.start = (u_char *) label + sizeof(njs_timelabel_t);
3910
0
    memcpy(label->name.start, name->start, name->length);
3911
0
    label->name.length = name->length;
3912
0
    label->time = njs_time();
3913
3914
0
    njs_queue_insert_tail(&console->labels, &label->link);
3915
3916
0
    return NJS_OK;
3917
0
}
3918
3919
3920
static void
3921
njs_console_time_end(njs_console_t *console, njs_str_t *name, uint64_t ns)
3922
0
{
3923
0
    uint64_t          ms;
3924
0
    njs_queue_t       *labels;
3925
0
    njs_timelabel_t   *label;
3926
0
    njs_queue_link_t  *link;
3927
3928
0
    labels = &console->labels;
3929
0
    link = njs_queue_first(labels);
3930
3931
0
    for ( ;; ) {
3932
0
        if (link == njs_queue_tail(labels)) {
3933
0
            njs_console_log(NJS_LOG_INFO, "Timer \"%V\" doesn’t exist.",
3934
0
                            name);
3935
0
            return;
3936
0
        }
3937
3938
0
        label = njs_queue_link_data(link, njs_timelabel_t, link);
3939
3940
0
        if (njs_strstr_eq(name, &label->name)) {
3941
0
            njs_queue_remove(&label->link);
3942
0
            break;
3943
0
        }
3944
3945
0
        link = njs_queue_next(link);
3946
0
    }
3947
3948
0
    ns = ns - label->time;
3949
3950
0
    ms = ns / 1000000;
3951
0
    ns = ns % 1000000;
3952
3953
0
    njs_console_log(NJS_LOG_INFO, "%V: %uL.%06uLms", name, ms, ns);
3954
3955
0
    njs_mp_free(console->engine->pool, label);
3956
0
}
3957
3958
3959
static intptr_t
3960
njs_event_rbtree_compare(njs_rbtree_node_t *node1, njs_rbtree_node_t *node2)
3961
0
{
3962
0
    njs_ev_t  *ev1, *ev2;
3963
3964
0
    ev1 = (njs_ev_t *) node1;
3965
0
    ev2 = (njs_ev_t *) node2;
3966
3967
0
    if (ev1->id < ev2->id) {
3968
0
        return -1;
3969
0
    }
3970
3971
0
    if (ev1->id > ev2->id) {
3972
0
        return 1;
3973
0
    }
3974
3975
0
    return 0;
3976
0
}
3977
3978
3979
static uint64_t
3980
njs_time(void)
3981
0
{
3982
0
#if (NJS_HAVE_CLOCK_MONOTONIC)
3983
0
    struct timespec ts;
3984
3985
0
    clock_gettime(CLOCK_MONOTONIC, &ts);
3986
3987
0
    return (uint64_t) ts.tv_sec * 1000000000 + ts.tv_nsec;
3988
#else
3989
    struct timeval tv;
3990
3991
    gettimeofday(&tv, NULL);
3992
3993
    return (uint64_t) tv.tv_sec * 1000000000 + tv.tv_usec * 1000;
3994
#endif
3995
0
}