Coverage Report

Created: 2025-02-03 06:25

/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
115k
#define NJS_LOG_DUMP  16
279
53.8k
#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
36.2k
{
411
36.2k
    njs_int_t     ret;
412
36.2k
    njs_engine_t  *engine;
413
414
36.2k
    njs_mm_denormals(opts->denormals);
415
416
36.2k
    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
36.2k
    ret = njs_console_init(opts, &njs_console);
434
36.2k
    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
36.2k
    if (opts->command.length != 0) {
449
36.2k
        engine = njs_create_engine(opts);
450
36.2k
        if (engine == NULL) {
451
0
            return NJS_ERROR;
452
0
        }
453
454
36.2k
        ret = njs_process_script(engine, &njs_console, &opts->command);
455
36.2k
        engine->destroy(engine);
456
457
36.2k
    } else {
458
0
        ret = njs_process_file(opts);
459
0
    }
460
461
36.2k
    return ret;
462
36.2k
}
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
36.2k
{
856
36.2k
    njs_opts_t  opts;
857
858
36.2k
    if (size == 0) {
859
0
        return 0;
860
0
    }
861
862
36.2k
    njs_memzero(&opts, sizeof(njs_opts_t));
863
864
36.2k
    opts.file = (char *) "fuzzer";
865
36.2k
    opts.command.start = (u_char *) data;
866
36.2k
    opts.command.length = size;
867
36.2k
    opts.suppress_stdout = 1;
868
869
36.2k
    return njs_main(&opts);
870
36.2k
}
871
872
#endif
873
874
static njs_int_t
875
njs_console_init(njs_opts_t *opts, njs_console_t *console)
876
36.2k
{
877
36.2k
    njs_memzero(console, sizeof(njs_console_t));
878
879
36.2k
    njs_rbtree_init(&console->events, njs_event_rbtree_compare);
880
36.2k
    njs_queue_init(&console->posted_events);
881
36.2k
    njs_queue_init(&console->labels);
882
883
36.2k
    console->interactive = opts->interactive;
884
36.2k
    console->suppress_stdout = opts->suppress_stdout;
885
36.2k
    console->module = opts->module;
886
36.2k
    console->argv = opts->argv;
887
36.2k
    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
36.2k
    return NJS_OK;
902
36.2k
}
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
108k
{
909
108k
    njs_function_t      *f;
910
108k
    njs_opaque_value_t   value;
911
912
108k
    f = njs_vm_function_alloc(vm, native, 1, ctor);
913
108k
    if (f == NULL) {
914
0
        return NJS_ERROR;
915
0
    }
916
917
108k
    njs_value_function_set(njs_value_arg(&value), f);
918
919
108k
    return njs_vm_bind(vm, name, njs_value_arg(&value), 1);
920
108k
}
921
922
923
static njs_int_t
924
njs_externals_init(njs_vm_t *vm)
925
36.2k
{
926
36.2k
    njs_int_t           ret, proto_id;
927
36.2k
    njs_console_t       *console;
928
36.2k
    njs_opaque_value_t  value, method;
929
930
36.2k
    static const njs_str_t  console_name = njs_str("console");
931
36.2k
    static const njs_str_t  dollar_262 = njs_str("$262");
932
36.2k
    static const njs_str_t  print_name = njs_str("print");
933
36.2k
    static const njs_str_t  console_log = njs_str("console.log");
934
36.2k
    static const njs_str_t  set_timeout = njs_str("setTimeout");
935
36.2k
    static const njs_str_t  set_immediate = njs_str("setImmediate");
936
36.2k
    static const njs_str_t  clear_timeout = njs_str("clearTimeout");
937
938
36.2k
    console = njs_vm_external_ptr(vm);
939
940
36.2k
    njs_console_proto_id = njs_vm_external_prototype(vm, njs_ext_console,
941
36.2k
                                         njs_nitems(njs_ext_console));
942
36.2k
    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
36.2k
    ret = njs_vm_external_create(vm, njs_value_arg(&value),
948
36.2k
                                 njs_console_proto_id, console, 0);
949
36.2k
    if (njs_slow_path(ret != NJS_OK)) {
950
0
        return NJS_ERROR;
951
0
    }
952
953
36.2k
    ret = njs_vm_bind(vm, &console_name, njs_value_arg(&value), 0);
954
36.2k
    if (njs_slow_path(ret != NJS_OK)) {
955
0
        return NJS_ERROR;
956
0
    }
957
958
36.2k
    ret = njs_vm_value(vm, &console_log, njs_value_arg(&method));
959
36.2k
    if (njs_slow_path(ret != NJS_OK)) {
960
0
        return NJS_ERROR;
961
0
    }
962
963
36.2k
    ret = njs_vm_bind(vm, &print_name, njs_value_arg(&method), 0);
964
36.2k
    if (njs_slow_path(ret != NJS_OK)) {
965
0
        return NJS_ERROR;
966
0
    }
967
968
36.2k
    ret = njs_function_bind(vm, &set_timeout, njs_set_timeout, 0);
969
36.2k
    if (ret != NJS_OK) {
970
0
        return NJS_ERROR;
971
0
    }
972
973
36.2k
    ret = njs_function_bind(vm, &set_immediate, njs_set_immediate, 0);
974
36.2k
    if (ret != NJS_OK) {
975
0
        return NJS_ERROR;
976
0
    }
977
978
36.2k
    ret = njs_function_bind(vm, &clear_timeout, njs_clear_timeout, 0);
979
36.2k
    if (ret != NJS_OK) {
980
0
        return NJS_ERROR;
981
0
    }
982
983
36.2k
    proto_id = njs_vm_external_prototype(vm, njs_ext_262,
984
36.2k
                                         njs_nitems(njs_ext_262));
985
36.2k
    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
36.2k
    ret = njs_vm_external_create(vm,  njs_value_arg(&value), proto_id, NULL, 1);
991
36.2k
    if (njs_slow_path(ret != NJS_OK)) {
992
0
        return NJS_ERROR;
993
0
    }
994
995
36.2k
    ret = njs_vm_bind(vm, &dollar_262, njs_value_arg(&value), 1);
996
36.2k
    if (njs_slow_path(ret != NJS_OK)) {
997
0
        return NJS_ERROR;
998
0
    }
999
1000
36.2k
    return NJS_OK;
1001
36.2k
}
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
36.2k
{
1190
36.2k
    const u_char  *p, *end;
1191
1192
36.2k
    if (path->length == 0) {
1193
0
        goto current_dir;
1194
0
    }
1195
1196
36.2k
    p = path->start + path->length - 1;
1197
1198
    /* Stripping basename. */
1199
1200
253k
    while (p >= path->start && *p != '/') { p--; }
1201
1202
36.2k
    end = p + 1;
1203
1204
36.2k
    if (end == path->start) {
1205
36.2k
        goto current_dir;
1206
36.2k
    }
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
36.2k
current_dir:
1224
1225
36.2k
    *name = njs_str_value(".");
1226
36.2k
}
1227
1228
1229
static njs_int_t
1230
njs_console_set_cwd(njs_console_t *console, njs_str_t *file)
1231
36.2k
{
1232
36.2k
    njs_str_t  cwd;
1233
1234
36.2k
    njs_file_dirname(file, &cwd);
1235
1236
36.2k
    console->cwd.start = njs_mp_alloc(console->engine->pool, cwd.length);
1237
36.2k
    if (njs_slow_path(console->cwd.start == NULL)) {
1238
0
        return NJS_ERROR;
1239
0
    }
1240
1241
36.2k
    memcpy(console->cwd.start, cwd.start, cwd.length);
1242
36.2k
    console->cwd.length = cwd.length;
1243
1244
36.2k
    return NJS_OK;
1245
36.2k
}
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
36.2k
{
1306
36.2k
    njs_vm_t      *vm;
1307
36.2k
    njs_int_t     ret;
1308
36.2k
    njs_vm_opt_t  vm_options;
1309
1310
36.2k
    njs_vm_opt_init(&vm_options);
1311
1312
36.2k
    vm_options.file.start = (u_char *) opts->file;
1313
36.2k
    vm_options.file.length = njs_strlen(opts->file);
1314
1315
36.2k
    vm_options.init = 1;
1316
36.2k
    vm_options.interactive = opts->interactive;
1317
36.2k
    vm_options.disassemble = opts->disassemble;
1318
36.2k
    vm_options.backtrace = 1;
1319
36.2k
    vm_options.quiet = opts->quiet;
1320
36.2k
    vm_options.sandbox = opts->sandbox;
1321
36.2k
    vm_options.unsafe = !opts->safe;
1322
36.2k
    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
36.2k
    vm_options.addons = njs_console_addon_modules;
1331
36.2k
    vm_options.external = &njs_console;
1332
36.2k
    vm_options.argv = opts->argv;
1333
36.2k
    vm_options.argc = opts->argc;
1334
36.2k
    vm_options.ast = opts->ast;
1335
1336
36.2k
    if (opts->stack_size != 0) {
1337
0
        vm_options.max_stack_size = opts->stack_size;
1338
0
    }
1339
1340
36.2k
    vm = njs_vm_create(&vm_options);
1341
36.2k
    if (vm == NULL) {
1342
0
        njs_stderror("failed to create vm\n");
1343
0
        return NJS_ERROR;
1344
0
    }
1345
1346
36.2k
    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
36.2k
    ret = njs_console_set_cwd(njs_vm_external_ptr(vm), &vm_options.file);
1352
36.2k
    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
36.2k
    njs_vm_set_module_loader(vm, njs_module_loader, opts);
1358
1359
36.2k
    engine->u.njs.vm = vm;
1360
1361
36.2k
    return NJS_OK;
1362
36.2k
}
1363
1364
1365
static njs_int_t
1366
njs_engine_njs_destroy(njs_engine_t *engine)
1367
36.2k
{
1368
36.2k
    njs_vm_destroy(engine->u.njs.vm);
1369
36.2k
    njs_mp_destroy(engine->pool);
1370
1371
36.2k
    return NJS_OK;
1372
36.2k
}
1373
1374
1375
static njs_int_t
1376
njs_engine_njs_eval(njs_engine_t *engine, njs_str_t *script)
1377
36.2k
{
1378
36.2k
     u_char     *start, *end;
1379
36.2k
     njs_int_t  ret;
1380
1381
36.2k
     start = script->start;
1382
36.2k
     end = start + script->length;
1383
1384
36.2k
     ret = njs_vm_compile(engine->u.njs.vm, &start, end);
1385
1386
36.2k
     if (ret == NJS_OK && start == end) {
1387
34.6k
        return njs_vm_start(engine->u.njs.vm,
1388
34.6k
                           njs_value_arg(&engine->u.njs.value));
1389
34.6k
     }
1390
1391
1.60k
     return NJS_ERROR;
1392
36.2k
}
1393
1394
1395
static njs_int_t
1396
njs_engine_njs_execute_pending_job(njs_engine_t *engine)
1397
290k
{
1398
290k
    return njs_vm_execute_pending_job(engine->u.njs.vm);
1399
290k
}
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(vm, njs_value_arg(&value), njs_value_arg(&key),
1589
0
                                 njs_value_arg(&retval));
1590
0
        if (njs_slow_path(ret != NJS_OK)) {
1591
0
            if (ret == NJS_DECLINED && !global) {
1592
0
                goto done;
1593
0
            }
1594
1595
0
            return NULL;
1596
0
        }
1597
1598
0
        global = 0;
1599
0
        njs_value_assign(&value, &retval);
1600
0
    }
1601
1602
0
done:
1603
1604
0
    return njs_object_completions(vm, njs_value_arg(&value), expression);
1605
0
}
1606
1607
1608
static njs_int_t
1609
njs_engine_njs_process_events(njs_engine_t *engine)
1610
33.8k
{
1611
33.8k
    njs_ev_t            *ev;
1612
33.8k
    njs_vm_t            *vm;
1613
33.8k
    njs_int_t           ret;
1614
33.8k
    njs_queue_t         *events;
1615
33.8k
    njs_console_t       *console;
1616
33.8k
    njs_queue_link_t    *link;
1617
33.8k
    njs_opaque_value_t  retval;
1618
1619
33.8k
    vm = engine->u.njs.vm;
1620
33.8k
    console = njs_vm_external_ptr(vm);
1621
33.8k
    events = &console->posted_events;
1622
1623
33.8k
    for ( ;; ) {
1624
33.8k
        link = njs_queue_first(events);
1625
1626
33.8k
        if (link == njs_queue_tail(events)) {
1627
33.8k
            break;
1628
33.8k
        }
1629
1630
0
        ev = njs_queue_link_data(link, njs_ev_t, link);
1631
1632
0
        njs_queue_remove(&ev->link);
1633
0
        njs_rbtree_delete(&console->events, &ev->node);
1634
1635
0
        ret = njs_vm_invoke(vm, ev->u.njs.function, ev->u.njs.args, ev->nargs,
1636
0
                            njs_value_arg(&retval));
1637
0
        if (ret == NJS_ERROR) {
1638
0
            njs_engine_njs_output(engine, ret);
1639
1640
0
            if (!console->interactive) {
1641
0
                return NJS_ERROR;
1642
0
            }
1643
0
        }
1644
0
    }
1645
1646
33.8k
    if (!njs_rbtree_is_empty(&console->events)) {
1647
0
        return NJS_AGAIN;
1648
0
    }
1649
1650
33.8k
    return njs_vm_pending(vm) ? NJS_AGAIN: NJS_OK;
1651
33.8k
}
1652
1653
1654
static njs_int_t
1655
njs_engine_njs_unhandled_rejection(njs_engine_t *engine)
1656
33.8k
{
1657
33.8k
    njs_vm_t                *vm;
1658
33.8k
    njs_int_t               ret;
1659
33.8k
    njs_str_t               message;
1660
33.8k
    njs_console_t           *console;
1661
33.8k
    njs_rejected_promise_t  *rejected_promise;
1662
1663
33.8k
    vm = engine->u.njs.vm;
1664
33.8k
    console = njs_vm_external_ptr(vm);
1665
1666
33.8k
    if (console->rejected_promises == NULL
1667
33.8k
        || console->rejected_promises->items == 0)
1668
33.8k
    {
1669
33.8k
        return 0;
1670
33.8k
    }
1671
1672
0
    rejected_promise = console->rejected_promises->start;
1673
1674
0
    ret = njs_vm_value_to_string(vm, &message,
1675
0
                               njs_value_arg(&rejected_promise->u.njs.message));
1676
0
    if (njs_slow_path(ret != NJS_OK)) {
1677
0
        return -1;
1678
0
    }
1679
1680
0
    njs_vm_error(vm, "unhandled promise rejection: %V", &message);
1681
1682
0
    njs_arr_destroy(console->rejected_promises);
1683
0
    console->rejected_promises = NULL;
1684
1685
0
    return 1;
1686
0
}
1687
1688
#ifdef NJS_HAVE_QUICKJS
1689
1690
static JSValue
1691
njs_qjs_console_log(JSContext *ctx, JSValueConst this_val, int argc,
1692
    JSValueConst *argv, int magic)
1693
{
1694
    int         i;
1695
    size_t      len;
1696
    const char  *str;
1697
1698
    for (i = 0; i < argc; i++) {
1699
        str = JS_ToCStringLen(ctx, &len, argv[i]);
1700
        if (!str) {
1701
            return JS_EXCEPTION;
1702
        }
1703
1704
        njs_console_logger(magic, (const u_char*) str, len);
1705
        JS_FreeCString(ctx, str);
1706
    }
1707
1708
    return JS_UNDEFINED;
1709
}
1710
1711
1712
static JSValue
1713
njs_qjs_console_time(JSContext *ctx, JSValueConst this_val, int argc,
1714
    JSValueConst *argv)
1715
{
1716
    njs_str_t      name;
1717
    const char     *str;
1718
    njs_console_t  *console;
1719
1720
    static const njs_str_t  default_label = njs_str("default");
1721
1722
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
1723
1724
    name = default_label;
1725
1726
    if (argc > 0 && !JS_IsUndefined(argv[0])) {
1727
        str = JS_ToCStringLen(ctx, &name.length, argv[0]);
1728
        if (str == NULL) {
1729
            return JS_EXCEPTION;
1730
        }
1731
1732
        name.start = njs_mp_alloc(console->engine->pool, name.length);
1733
        if (njs_slow_path(name.start == NULL)) {
1734
            JS_ThrowOutOfMemory(ctx);
1735
            return JS_EXCEPTION;
1736
        }
1737
1738
        (void) memcpy(name.start, str, name.length);
1739
1740
        JS_FreeCString(ctx, str);
1741
    }
1742
1743
    if (njs_console_time(console, &name) != NJS_OK) {
1744
        return JS_EXCEPTION;
1745
    }
1746
1747
    return JS_UNDEFINED;
1748
}
1749
1750
1751
static JSValue
1752
njs_qjs_console_time_end(JSContext *ctx, JSValueConst this_val, int argc,
1753
    JSValueConst *argv)
1754
{
1755
    uint64_t       ns;
1756
    njs_str_t      name;
1757
    const char     *str;
1758
    njs_console_t  *console;
1759
1760
    static const njs_str_t  default_label = njs_str("default");
1761
1762
    ns = njs_time();
1763
1764
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
1765
1766
    name = default_label;
1767
1768
    if (argc > 0 && !JS_IsUndefined(argv[0])) {
1769
        str = JS_ToCStringLen(ctx, &name.length, argv[0]);
1770
        if (str == NULL) {
1771
            return JS_EXCEPTION;
1772
        }
1773
1774
        name.start = njs_mp_alloc(console->engine->pool, name.length);
1775
        if (njs_slow_path(name.start == NULL)) {
1776
            JS_ThrowOutOfMemory(ctx);
1777
            return JS_EXCEPTION;
1778
        }
1779
1780
        (void) memcpy(name.start, str, name.length);
1781
1782
        JS_FreeCString(ctx, str);
1783
    }
1784
1785
    njs_console_time_end(console, &name, ns);
1786
1787
    return JS_UNDEFINED;
1788
}
1789
1790
1791
static JSValue
1792
njs_qjs_set_timer(JSContext *ctx, JSValueConst this_val, int argc,
1793
    JSValueConst *argv, int immediate)
1794
{
1795
    int            n;
1796
    int64_t        delay;
1797
    njs_ev_t       *ev;
1798
    njs_uint_t     i;
1799
    njs_console_t  *console;
1800
1801
    if (njs_slow_path(argc < 1)) {
1802
        JS_ThrowTypeError(ctx, "too few arguments");
1803
        return JS_EXCEPTION;
1804
    }
1805
1806
    if (njs_slow_path(!JS_IsFunction(ctx, argv[0]))) {
1807
        JS_ThrowTypeError(ctx, "first arg must be a function");
1808
        return JS_EXCEPTION;
1809
    }
1810
1811
    delay = 0;
1812
1813
    if (!immediate && argc >= 2 && JS_IsNumber(argv[1])) {
1814
        JS_ToInt64(ctx, &delay, argv[1]);
1815
    }
1816
1817
    if (delay != 0) {
1818
        JS_ThrowInternalError(ctx, "njs_set_timer(): async timers unsupported");
1819
        return JS_EXCEPTION;
1820
    }
1821
1822
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
1823
1824
    n = immediate ? 1 : 2;
1825
    argc = (argc >= n) ? argc - n : 0;
1826
1827
    ev = njs_mp_alloc(console->engine->pool,
1828
                      sizeof(njs_ev_t) + sizeof(njs_opaque_value_t) * argc);
1829
    if (njs_slow_path(ev == NULL)) {
1830
        JS_ThrowOutOfMemory(ctx);
1831
        return JS_EXCEPTION;
1832
    }
1833
1834
    ev->u.qjs.function = JS_DupValue(ctx, argv[0]);
1835
    ev->u.qjs.args = (JSValue *) &ev[1];
1836
    ev->nargs = (njs_uint_t) argc;
1837
    ev->id = console->event_id++;
1838
1839
    if (ev->nargs != 0) {
1840
        for (i = 0; i < ev->nargs; i++) {
1841
            ev->u.qjs.args[i] = JS_DupValue(ctx, argv[i + n]);
1842
        }
1843
    }
1844
1845
    njs_rbtree_insert(&console->events, &ev->node);
1846
1847
    njs_queue_insert_tail(&console->posted_events, &ev->link);
1848
1849
    return JS_NewUint32(ctx, ev->id);
1850
}
1851
1852
1853
static void
1854
njs_qjs_destroy_event(JSContext *ctx, njs_console_t *console, njs_ev_t *ev)
1855
{
1856
    njs_uint_t  i;
1857
1858
    JS_FreeValue(ctx, ev->u.qjs.function);
1859
1860
    if (ev->nargs != 0) {
1861
        for (i = 0; i < ev->nargs; i++) {
1862
            JS_FreeValue(ctx, ev->u.qjs.args[i]);
1863
        }
1864
    }
1865
1866
    njs_mp_free(console->engine->pool, ev);
1867
}
1868
1869
1870
static JSValue
1871
njs_qjs_clear_timeout(JSContext *ctx, JSValueConst this_val, int argc,
1872
    JSValueConst *argv)
1873
{
1874
    njs_ev_t           ev_lookup, *ev;
1875
    njs_console_t      *console;
1876
    njs_rbtree_node_t  *rb;
1877
1878
    if (argc < 1 || !JS_IsNumber(argv[0])) {
1879
        return JS_UNDEFINED;
1880
    }
1881
1882
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
1883
1884
    if (JS_ToUint32(ctx, &ev_lookup.id, argv[0])) {
1885
        return JS_EXCEPTION;
1886
    }
1887
1888
    rb = njs_rbtree_find(&console->events, &ev_lookup.node);
1889
    if (njs_slow_path(rb == NULL)) {
1890
        JS_ThrowTypeError(ctx, "failed to find timer");
1891
        return JS_EXCEPTION;
1892
    }
1893
1894
    ev = (njs_ev_t *) rb;
1895
    njs_queue_remove(&ev->link);
1896
    njs_rbtree_delete(&console->events, (njs_rbtree_part_t *) rb);
1897
1898
    njs_qjs_destroy_event(ctx, console, ev);
1899
1900
    return JS_UNDEFINED;
1901
}
1902
1903
1904
static JSValue
1905
njs_qjs_console_to_string_tag(JSContext *ctx, JSValueConst this_val)
1906
{
1907
    return JS_NewString(ctx, "Console");
1908
}
1909
1910
1911
static JSValue
1912
njs_qjs_process_getter(JSContext *ctx, JSValueConst this_val)
1913
{
1914
    JSValue         obj;
1915
    njs_console_t  *console;
1916
1917
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
1918
1919
    if (!JS_IsUndefined(console->process)) {
1920
        return JS_DupValue(ctx, console->process);
1921
    }
1922
1923
    obj = qjs_process_object(ctx, console->argc, (const char **) console->argv);
1924
    if (JS_IsException(obj)) {
1925
        return JS_EXCEPTION;
1926
    }
1927
1928
    console->process = JS_DupValue(ctx, obj);
1929
1930
    return obj;
1931
}
1932
1933
1934
static njs_int_t njs_qjs_global_init(JSContext *ctx, JSValue global_obj);
1935
static void njs_qjs_dump_error(JSContext *ctx);
1936
1937
1938
static void
1939
njs_qjs_dump_obj(JSContext *ctx, FILE *f, JSValueConst val, const char *prefix,
1940
    const char *quote)
1941
{
1942
    njs_bool_t  is_str;
1943
    const char  *str;
1944
1945
    is_str = JS_IsString(val);
1946
1947
    str = JS_ToCString(ctx, val);
1948
    if (str) {
1949
        fprintf(f, "%s%s%s%s\n", prefix, is_str ? quote : "",
1950
                str, is_str ? quote : "");
1951
        JS_FreeCString(ctx, str);
1952
1953
    } else {
1954
        njs_qjs_dump_error(ctx);
1955
    }
1956
}
1957
1958
1959
static void
1960
njs_qjs_dump_error2(JSContext *ctx, JSValueConst exception)
1961
{
1962
    _Bool    is_error;
1963
    JSValue  val;
1964
1965
    is_error = JS_IsError(ctx, exception);
1966
1967
    njs_qjs_dump_obj(ctx, stderr, exception, "Thrown:\n", "");
1968
1969
    if (is_error) {
1970
        val = JS_GetPropertyStr(ctx, exception, "stack");
1971
        if (!JS_IsUndefined(val)) {
1972
            njs_qjs_dump_obj(ctx, stderr, val, "", "");
1973
        }
1974
1975
        JS_FreeValue(ctx, val);
1976
    }
1977
}
1978
1979
1980
static void
1981
njs_qjs_dump_error(JSContext *ctx)
1982
{
1983
    JSValue  exception;
1984
1985
    exception = JS_GetException(ctx);
1986
    njs_qjs_dump_error2(ctx, exception);
1987
    JS_FreeValue(ctx, exception);
1988
}
1989
1990
1991
static void *
1992
njs_qjs_agent(void *arg)
1993
{
1994
    int            ret;
1995
    JSValue        ret_val, global_obj;
1996
    JSRuntime      *rt;
1997
    JSContext      *ctx, *ctx1;
1998
    njs_console_t  *console;
1999
    JSValue        args[2];
2000
2001
    njs_262agent_t *agent = arg;
2002
    console = agent->console;
2003
2004
    rt = JS_NewRuntime();
2005
    if (rt == NULL) {
2006
        njs_stderror("JS_NewRuntime failure\n");
2007
        exit(1);
2008
    }
2009
2010
    ctx = JS_NewContext(rt);
2011
    if (ctx == NULL) {
2012
        JS_FreeRuntime(rt);
2013
        njs_stderror("JS_NewContext failure\n");
2014
        exit(1);
2015
    }
2016
2017
    JS_SetContextOpaque(ctx, agent);
2018
    JS_SetRuntimeInfo(rt, "agent");
2019
    JS_SetCanBlock(rt, 1);
2020
2021
    global_obj = JS_GetGlobalObject(ctx);
2022
2023
    ret = njs_qjs_global_init(ctx, global_obj);
2024
    if (ret == -1) {
2025
        JS_FreeContext(ctx);
2026
        JS_FreeRuntime(rt);
2027
        njs_stderror("njs_qjs_global_init failure\n");
2028
        exit(1);
2029
    }
2030
2031
    JS_FreeValue(ctx, global_obj);
2032
2033
    ret_val = JS_Eval(ctx, agent->script, strlen(agent->script),
2034
                      "<evalScript>", JS_EVAL_TYPE_GLOBAL);
2035
2036
    free(agent->script);
2037
    agent->script = NULL;
2038
2039
    if (JS_IsException(ret_val)) {
2040
        njs_qjs_dump_error(ctx);
2041
    }
2042
2043
    JS_FreeValue(ctx, ret_val);
2044
2045
    for (;;) {
2046
        ret = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
2047
        if (ret < 0) {
2048
            njs_qjs_dump_error(ctx);
2049
            break;
2050
2051
        } else if (ret == 0) {
2052
            if (JS_IsUndefined(agent->broadcast_func)) {
2053
                break;
2054
2055
            } else {
2056
                pthread_mutex_lock(&console->agent_mutex);
2057
2058
                while (!agent->broadcast_pending) {
2059
                    pthread_cond_wait(&console->agent_cond,
2060
                                      &console->agent_mutex);
2061
                }
2062
2063
                agent->broadcast_pending = 0;
2064
                pthread_cond_signal(&console->agent_cond);
2065
                pthread_mutex_unlock(&console->agent_mutex);
2066
2067
                args[0] = JS_NewArrayBuffer(ctx, agent->broadcast_sab_buf,
2068
                                            agent->broadcast_sab_size,
2069
                                            NULL, NULL, 1);
2070
                args[1] = JS_NewInt32(ctx, agent->broadcast_val);
2071
2072
                ret_val = JS_Call(ctx, agent->broadcast_func, JS_UNDEFINED,
2073
                                  2, (JSValueConst *)args);
2074
2075
                JS_FreeValue(ctx, args[0]);
2076
                JS_FreeValue(ctx, args[1]);
2077
2078
                if (JS_IsException(ret_val)) {
2079
                    njs_qjs_dump_error(ctx);
2080
                }
2081
2082
                JS_FreeValue(ctx, ret_val);
2083
                JS_FreeValue(ctx, agent->broadcast_func);
2084
                agent->broadcast_func = JS_UNDEFINED;
2085
            }
2086
        }
2087
    }
2088
2089
    JS_FreeValue(ctx, agent->broadcast_func);
2090
2091
    JS_FreeContext(ctx);
2092
    JS_FreeRuntime(rt);
2093
2094
    return NULL;
2095
}
2096
2097
2098
static JSValue
2099
njs_qjs_agent_start(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2100
{
2101
    const char      *script;
2102
    njs_console_t   *console;
2103
    njs_262agent_t  *agent;
2104
2105
    if (JS_GetContextOpaque(ctx) != NULL) {
2106
        return JS_ThrowTypeError(ctx, "cannot be called inside an agent");
2107
    }
2108
2109
    script = JS_ToCString(ctx, argv[0]);
2110
    if (script == NULL) {
2111
        return JS_EXCEPTION;
2112
    }
2113
2114
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
2115
2116
    agent = malloc(sizeof(*agent));
2117
    if (agent == NULL) {
2118
        return JS_ThrowOutOfMemory(ctx);
2119
    }
2120
2121
    njs_memzero(agent, sizeof(*agent));
2122
2123
    agent->broadcast_func = JS_UNDEFINED;
2124
    agent->broadcast_sab = JS_UNDEFINED;
2125
    agent->script = strdup(script);
2126
    if (agent->script == NULL) {
2127
        return JS_ThrowOutOfMemory(ctx);
2128
    }
2129
2130
    JS_FreeCString(ctx, script);
2131
2132
    agent->console = console;
2133
    njs_queue_insert_tail(&console->agents, &agent->link);
2134
2135
    pthread_create(&agent->tid, NULL, njs_qjs_agent, agent);
2136
2137
    return JS_UNDEFINED;
2138
}
2139
2140
2141
static JSValue
2142
njs_qjsr_agent_get_report(JSContext *ctx, JSValue this_val, int argc,
2143
    JSValue *argv)
2144
{
2145
    JSValue             ret;
2146
    njs_console_t       *console;
2147
    njs_queue_link_t    *link;
2148
    njs_agent_report_t  *rep;
2149
2150
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
2151
2152
    pthread_mutex_lock(&console->report_mutex);
2153
2154
    rep = NULL;
2155
2156
    for ( ;; ) {
2157
        link = njs_queue_first(&console->reports);
2158
2159
        if (link == njs_queue_tail(&console->reports)) {
2160
            break;
2161
        }
2162
2163
        rep = njs_queue_link_data(link, njs_agent_report_t, link);
2164
2165
        njs_queue_remove(&rep->link);
2166
        break;
2167
    }
2168
2169
    pthread_mutex_unlock(&console->report_mutex);
2170
2171
    if (rep != NULL) {
2172
        ret = JS_NewString(ctx, rep->str);
2173
        free(rep->str);
2174
        free(rep);
2175
2176
    } else {
2177
        ret = JS_NULL;
2178
    }
2179
2180
    return ret;
2181
}
2182
2183
2184
static njs_bool_t
2185
njs_qjs_broadcast_pending(njs_console_t *console)
2186
{
2187
    njs_262agent_t    *agent;
2188
    njs_queue_link_t  *link;
2189
2190
    link = njs_queue_first(&console->agents);
2191
2192
    for ( ;; ) {
2193
        if (link == njs_queue_tail(&console->agents)) {
2194
            break;
2195
        }
2196
2197
        agent = njs_queue_link_data(link, njs_262agent_t, link);
2198
2199
        if (agent->broadcast_pending) {
2200
            return 1;
2201
        }
2202
2203
        link = njs_queue_next(link);
2204
    }
2205
2206
    return 0;
2207
}
2208
2209
static JSValue
2210
njs_qjs_agent_broadcast(JSContext *ctx, JSValue this_val, int argc,
2211
    JSValue *argv)
2212
{
2213
    uint8_t           *buf;
2214
    size_t            buf_size;
2215
    int32_t           val;
2216
    njs_console_t     *console;
2217
    njs_262agent_t    *agent;
2218
    njs_queue_link_t  *link;
2219
2220
    JSValueConst sab = argv[0];
2221
2222
    if (JS_GetContextOpaque(ctx) != NULL) {
2223
        return JS_ThrowTypeError(ctx, "cannot be called inside an agent");
2224
    }
2225
2226
    buf = JS_GetArrayBuffer(ctx, &buf_size, sab);
2227
    if (buf == NULL) {
2228
        return JS_EXCEPTION;
2229
    }
2230
2231
    if (JS_ToInt32(ctx, &val, argv[1])) {
2232
        return JS_EXCEPTION;
2233
    }
2234
2235
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
2236
2237
    pthread_mutex_lock(&console->agent_mutex);
2238
2239
    link = njs_queue_first(&console->agents);
2240
2241
    for ( ;; ) {
2242
        if (link == njs_queue_tail(&console->agents)) {
2243
            break;
2244
        }
2245
2246
        agent = njs_queue_link_data(link, njs_262agent_t, link);
2247
2248
        agent->broadcast_pending = 1;
2249
        agent->broadcast_sab = JS_DupValue(ctx, sab);
2250
        agent->broadcast_sab_buf = buf;
2251
        agent->broadcast_sab_size = buf_size;
2252
        agent->broadcast_val = val;
2253
2254
        link = njs_queue_next(link);
2255
    }
2256
2257
    pthread_cond_broadcast(&console->agent_cond);
2258
2259
    while (njs_qjs_broadcast_pending(console)) {
2260
        pthread_cond_wait(&console->agent_cond, &console->agent_mutex);
2261
    }
2262
2263
    pthread_mutex_unlock(&console->agent_mutex);
2264
2265
    return JS_UNDEFINED;
2266
}
2267
2268
2269
static JSValue
2270
njs_qjs_agent_receive_broadcast(JSContext *ctx, JSValue this_val, int argc,
2271
    JSValue *argv)
2272
{
2273
    njs_262agent_t *agent = JS_GetContextOpaque(ctx);
2274
    if (agent == NULL) {
2275
        return JS_ThrowTypeError(ctx, "must be called inside an agent");
2276
    }
2277
2278
    if (!JS_IsFunction(ctx, argv[0])) {
2279
        return JS_ThrowTypeError(ctx, "expecting function");
2280
    }
2281
2282
    JS_FreeValue(ctx, agent->broadcast_func);
2283
    agent->broadcast_func = JS_DupValue(ctx, argv[0]);
2284
2285
    return JS_UNDEFINED;
2286
}
2287
2288
2289
static JSValue
2290
njs_qjs_agent_report(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2291
{
2292
    const char          *str;
2293
    njs_console_t       *console;
2294
    njs_262agent_t      *agent;
2295
    njs_agent_report_t  *rep;
2296
2297
    str = JS_ToCString(ctx, argv[0]);
2298
    if (str == NULL) {
2299
        return JS_EXCEPTION;
2300
    }
2301
2302
    rep = malloc(sizeof(*rep));
2303
    rep->str = strdup(str);
2304
    JS_FreeCString(ctx, str);
2305
2306
    agent = JS_GetContextOpaque(ctx);
2307
    console = agent->console;
2308
2309
    pthread_mutex_lock(&console->report_mutex);
2310
    njs_queue_insert_tail(&console->reports, &rep->link);
2311
    pthread_mutex_unlock(&console->report_mutex);
2312
2313
    return JS_UNDEFINED;
2314
}
2315
2316
2317
static JSValue
2318
njs_qjs_agent_leaving(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2319
{
2320
    njs_262agent_t *agent = JS_GetContextOpaque(ctx);
2321
    if (agent == NULL) {
2322
        return JS_ThrowTypeError(ctx, "must be called inside an agent");
2323
    }
2324
2325
    return JS_UNDEFINED;
2326
}
2327
2328
2329
static JSValue
2330
njs_qjs_agent_sleep(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2331
{
2332
    uint32_t duration;
2333
2334
    if (JS_ToUint32(ctx, &duration, argv[0])) {
2335
        return JS_EXCEPTION;
2336
    }
2337
2338
    usleep(duration * 1000);
2339
2340
    return JS_UNDEFINED;
2341
}
2342
2343
2344
static JSValue
2345
njs_qjs_agent_monotonic_now(JSContext *ctx, JSValue this_val, int argc,
2346
    JSValue *argv)
2347
{
2348
    return JS_NewInt64(ctx, njs_time() / 1000000);
2349
}
2350
2351
2352
static const JSCFunctionListEntry njs_qjs_agent_proto[] = {
2353
    JS_CFUNC_DEF("start", 1, njs_qjs_agent_start),
2354
    JS_CFUNC_DEF("getReport", 0, njs_qjsr_agent_get_report),
2355
    JS_CFUNC_DEF("broadcast", 2, njs_qjs_agent_broadcast),
2356
    JS_CFUNC_DEF("report", 1, njs_qjs_agent_report),
2357
    JS_CFUNC_DEF("leaving", 0, njs_qjs_agent_leaving),
2358
    JS_CFUNC_DEF("receiveBroadcast", 1, njs_qjs_agent_receive_broadcast),
2359
    JS_CFUNC_DEF("sleep", 1, njs_qjs_agent_sleep),
2360
    JS_CFUNC_DEF("monotonicNow", 0, njs_qjs_agent_monotonic_now),
2361
};
2362
2363
2364
static JSValue
2365
njs_qjs_new_agent(JSContext *ctx)
2366
{
2367
    JSValue  agent;
2368
2369
    agent = JS_NewObject(ctx);
2370
    if (JS_IsException(agent)) {
2371
        return JS_EXCEPTION;
2372
    }
2373
2374
    JS_SetPropertyFunctionList(ctx, agent, njs_qjs_agent_proto,
2375
                               njs_nitems(njs_qjs_agent_proto));
2376
    return agent;
2377
}
2378
2379
2380
static JSValue
2381
njs_qjs_detach_array_buffer(JSContext *ctx, JSValueConst this_val, int argc,
2382
    JSValueConst *argv)
2383
{
2384
    JS_DetachArrayBuffer(ctx, argv[0]);
2385
2386
    return JS_NULL;
2387
}
2388
2389
static JSValue
2390
njs_qjs_eval_script(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2391
{
2392
    size_t     len;
2393
    JSValue    ret;
2394
    const char *str;
2395
2396
    str = JS_ToCStringLen(ctx, &len, argv[0]);
2397
    if (str == NULL) {
2398
        return JS_EXCEPTION;
2399
    }
2400
2401
    ret = JS_Eval(ctx, str, len, "<evalScript>", JS_EVAL_TYPE_GLOBAL);
2402
2403
    JS_FreeCString(ctx, str);
2404
2405
    return ret;
2406
}
2407
2408
2409
static JSValue
2410
njs_qjs_create_realm(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2411
{
2412
    JSValue    ret_val, global_obj;
2413
    njs_int_t  ret;
2414
    JSContext  *ctx1;
2415
2416
    ctx1 = JS_NewContext(JS_GetRuntime(ctx));
2417
    if (ctx1 == NULL) {
2418
        return JS_ThrowOutOfMemory(ctx);
2419
    }
2420
2421
    global_obj = JS_GetGlobalObject(ctx1);
2422
2423
    ret = njs_qjs_global_init(ctx1, global_obj);
2424
    if (ret == -1) {
2425
        JS_FreeContext(ctx1);
2426
        return JS_EXCEPTION;
2427
    }
2428
2429
    ret_val = JS_GetPropertyStr(ctx1, global_obj, "$262");
2430
2431
    JS_FreeValue(ctx1, global_obj);
2432
    JS_FreeContext(ctx1);
2433
2434
    return ret_val;
2435
}
2436
2437
2438
static JSValue
2439
njs_qjs_is_HTMLDDA(JSContext *ctx, JSValue this_val, int argc, JSValue *argv)
2440
{
2441
    return JS_NULL;
2442
}
2443
2444
2445
static const JSCFunctionListEntry njs_qjs_262_proto[] = {
2446
    JS_CFUNC_DEF("detachArrayBuffer", 1, njs_qjs_detach_array_buffer),
2447
    JS_CFUNC_DEF("evalScript", 1, njs_qjs_eval_script),
2448
    JS_CFUNC_DEF("codePointRange", 2, js_string_codePointRange),
2449
    JS_CFUNC_DEF("createRealm", 0, njs_qjs_create_realm),
2450
};
2451
2452
2453
static JSValue
2454
njs_qjs_new_262(JSContext *ctx, JSValueConst this_val)
2455
{
2456
    JSValue  obj, obj262, global_obj;
2457
2458
    obj262 = JS_NewObject(ctx);
2459
    if (JS_IsException(obj262)) {
2460
        return JS_EXCEPTION;
2461
    }
2462
2463
    JS_SetPropertyFunctionList(ctx, obj262, njs_qjs_262_proto,
2464
                               njs_nitems(njs_qjs_262_proto));
2465
2466
    global_obj = JS_GetGlobalObject(ctx);
2467
    JS_SetPropertyStr(ctx, obj262, "global", JS_DupValue(ctx, global_obj));
2468
    JS_FreeValue(ctx, global_obj);
2469
2470
    obj = JS_NewCFunction(ctx, njs_qjs_is_HTMLDDA, "IsHTMLDDA", 0);
2471
    JS_SetIsHTMLDDA(ctx, obj);
2472
    JS_SetPropertyStr(ctx, obj262, "IsHTMLDDA", obj);
2473
2474
    JS_SetPropertyStr(ctx, obj262, "agent", njs_qjs_new_agent(ctx));
2475
2476
    return obj262;
2477
}
2478
2479
2480
static const JSCFunctionListEntry njs_qjs_global_proto[] = {
2481
    JS_CFUNC_DEF("clearTimeout", 1, njs_qjs_clear_timeout),
2482
    JS_CFUNC_MAGIC_DEF("print", 0, njs_qjs_console_log, NJS_LOG_INFO),
2483
    JS_CGETSET_DEF("process", njs_qjs_process_getter, NULL),
2484
    JS_CFUNC_MAGIC_DEF("setImmediate", 0, njs_qjs_set_timer, 1),
2485
    JS_CFUNC_MAGIC_DEF("setTimeout", 0, njs_qjs_set_timer, 0),
2486
};
2487
2488
2489
static const JSCFunctionListEntry njs_qjs_console_proto[] = {
2490
    JS_CGETSET_DEF("[Symbol.toStringTag]", njs_qjs_console_to_string_tag, NULL),
2491
    JS_CFUNC_MAGIC_DEF("error", 0, njs_qjs_console_log, NJS_LOG_ERROR),
2492
    JS_CFUNC_MAGIC_DEF("info", 0, njs_qjs_console_log, NJS_LOG_INFO),
2493
    JS_CFUNC_MAGIC_DEF("log", 0, njs_qjs_console_log, NJS_LOG_INFO),
2494
    JS_CFUNC_DEF("time", 0, njs_qjs_console_time),
2495
    JS_CFUNC_DEF("timeEnd", 0, njs_qjs_console_time_end),
2496
    JS_CFUNC_MAGIC_DEF("warn", 0, njs_qjs_console_log, NJS_LOG_WARN),
2497
};
2498
2499
2500
static njs_int_t
2501
njs_qjs_global_init(JSContext *ctx, JSValue global_obj)
2502
{
2503
    JS_SetPropertyFunctionList(ctx, global_obj, njs_qjs_global_proto,
2504
                               njs_nitems(njs_qjs_global_proto));
2505
2506
    return JS_SetPropertyStr(ctx, global_obj, "$262",
2507
                             njs_qjs_new_262(ctx, global_obj));
2508
}
2509
2510
2511
static void
2512
njs_qjs_rejection_tracker(JSContext *ctx, JSValueConst promise,
2513
    JSValueConst reason, JS_BOOL is_handled, void *opaque)
2514
{
2515
    void                    *promise_obj;
2516
    uint32_t                i, length;
2517
    njs_console_t           *console;
2518
    njs_rejected_promise_t  *rejected_promise;
2519
2520
    console = opaque;
2521
2522
    if (is_handled && console->rejected_promises != NULL) {
2523
        rejected_promise = console->rejected_promises->start;
2524
        length = console->rejected_promises->items;
2525
2526
        promise_obj = JS_VALUE_GET_PTR(promise);
2527
2528
        for (i = 0; i < length; i++) {
2529
            if (JS_VALUE_GET_PTR(rejected_promise[i].u.qjs.promise)
2530
                == promise_obj)
2531
            {
2532
                JS_FreeValue(ctx, rejected_promise[i].u.qjs.promise);
2533
                JS_FreeValue(ctx, rejected_promise[i].u.qjs.message);
2534
                njs_arr_remove(console->rejected_promises,
2535
                               &rejected_promise[i]);
2536
2537
                break;
2538
            }
2539
        }
2540
2541
        return;
2542
    }
2543
2544
    if (console->rejected_promises == NULL) {
2545
        console->rejected_promises = njs_arr_create(console->engine->pool, 4,
2546
                                                sizeof(njs_rejected_promise_t));
2547
        if (njs_slow_path(console->rejected_promises == NULL)) {
2548
            return;
2549
        }
2550
    }
2551
2552
    rejected_promise = njs_arr_add(console->rejected_promises);
2553
    if (njs_slow_path(rejected_promise == NULL)) {
2554
        return;
2555
    }
2556
2557
    rejected_promise->u.qjs.promise = JS_DupValue(ctx, promise);
2558
    rejected_promise->u.qjs.message = JS_DupValue(ctx, reason);
2559
}
2560
2561
2562
static JSModuleDef *
2563
njs_qjs_module_loader(JSContext *ctx, const char *module_name, void *opaque)
2564
{
2565
    JSValue            func_val;
2566
    njs_int_t          ret;
2567
    njs_str_t          text, prev_cwd;
2568
    njs_opts_t         *opts;
2569
    njs_console_t      *console;
2570
    JSModuleDef        *m;
2571
    njs_module_info_t  info;
2572
2573
    opts = opaque;
2574
    console = JS_GetRuntimeOpaque(JS_GetRuntime(ctx));
2575
2576
    njs_memzero(&info, sizeof(njs_module_info_t));
2577
2578
    info.name.start = (u_char *) module_name;
2579
    info.name.length = njs_strlen(module_name);
2580
2581
    ret = njs_module_lookup(opts, &console->cwd, &info);
2582
    if (njs_slow_path(ret != NJS_OK)) {
2583
        JS_ThrowReferenceError(ctx, "could not load module filename '%s'",
2584
                               module_name);
2585
        return NULL;
2586
    }
2587
2588
    ret = njs_module_read(console->engine->pool, info.fd, &text);
2589
2590
    (void) close(info.fd);
2591
2592
    if (njs_slow_path(ret != NJS_OK)) {
2593
        JS_ThrowInternalError(ctx, "while reading \"%*s\" module",
2594
                              (int) info.file.length, info.file.start);
2595
        return NULL;
2596
    }
2597
2598
    prev_cwd = console->cwd;
2599
2600
    ret = njs_console_set_cwd(console, &info.file);
2601
    if (njs_slow_path(ret != NJS_OK)) {
2602
        JS_ThrowInternalError(ctx, "while setting cwd for \"%*s\" module",
2603
                              (int) info.file.length, info.file.start);
2604
        return NULL;
2605
    }
2606
2607
    func_val = JS_Eval(ctx, (char *) text.start, text.length, module_name,
2608
                       JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
2609
2610
    njs_mp_free(console->engine->pool, console->cwd.start);
2611
    console->cwd = prev_cwd;
2612
2613
    njs_mp_free(console->engine->pool, text.start);
2614
2615
    if (JS_IsException(func_val)) {
2616
        return NULL;
2617
    }
2618
2619
    m = JS_VALUE_GET_PTR(func_val);
2620
    JS_FreeValue(ctx, func_val);
2621
2622
    return m;
2623
}
2624
2625
2626
static njs_int_t
2627
njs_engine_qjs_init(njs_engine_t *engine, njs_opts_t *opts)
2628
{
2629
    JSValue    global_obj, obj;
2630
    njs_int_t  ret;
2631
    JSContext  *ctx;
2632
2633
    engine->u.qjs.rt = JS_NewRuntime();
2634
    if (engine->u.qjs.rt == NULL) {
2635
        njs_stderror("JS_NewRuntime() failed\n");
2636
        return NJS_ERROR;
2637
    }
2638
2639
    engine->u.qjs.ctx = qjs_new_context(engine->u.qjs.rt, NULL);
2640
    if (engine->u.qjs.ctx == NULL) {
2641
        njs_stderror("JS_NewContext() failed\n");
2642
        return NJS_ERROR;
2643
    }
2644
2645
    JS_SetRuntimeOpaque(engine->u.qjs.rt, &njs_console);
2646
2647
    engine->u.qjs.value = JS_UNDEFINED;
2648
2649
    ctx = engine->u.qjs.ctx;
2650
2651
    global_obj = JS_GetGlobalObject(ctx);
2652
2653
    ret = njs_qjs_global_init(ctx, global_obj);
2654
    if (ret == -1) {
2655
        njs_stderror("njs_qjs_global_init() failed\n");
2656
        ret = NJS_ERROR;
2657
        goto done;
2658
    }
2659
2660
    obj = JS_NewObject(ctx);
2661
    if (JS_IsException(obj)) {
2662
        njs_stderror("JS_NewObject() failed\n");
2663
        ret = NJS_ERROR;
2664
        goto done;
2665
    }
2666
2667
    JS_SetOpaque(obj, &njs_console);
2668
2669
    JS_SetPropertyFunctionList(ctx, obj, njs_qjs_console_proto,
2670
                               njs_nitems(njs_qjs_console_proto));
2671
2672
    ret = JS_SetPropertyStr(ctx, global_obj, "console", obj);
2673
    if (ret == -1) {
2674
        njs_stderror("JS_SetPropertyStr() failed\n");
2675
        ret = NJS_ERROR;
2676
        goto done;
2677
    }
2678
2679
    if (opts->unhandled_rejection) {
2680
        JS_SetHostPromiseRejectionTracker(engine->u.qjs.rt,
2681
                                         njs_qjs_rejection_tracker,
2682
                                         JS_GetRuntimeOpaque(engine->u.qjs.rt));
2683
    }
2684
2685
    JS_SetModuleLoaderFunc(engine->u.qjs.rt, NULL, njs_qjs_module_loader, opts);
2686
2687
    JS_SetCanBlock(engine->u.qjs.rt, opts->can_block);
2688
2689
    ret = NJS_OK;
2690
2691
done:
2692
2693
    JS_FreeValue(ctx, global_obj);
2694
2695
    return ret;
2696
}
2697
2698
2699
static njs_int_t
2700
njs_engine_qjs_destroy(njs_engine_t *engine)
2701
{
2702
    uint32_t                i;
2703
    njs_ev_t                *ev;
2704
    njs_queue_t             *events;
2705
    njs_console_t           *console;
2706
    njs_262agent_t          *agent;
2707
    njs_queue_link_t        *link;
2708
    njs_rejected_promise_t  *rejected_promise;
2709
2710
    console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
2711
2712
    if (console->rejected_promises != NULL) {
2713
        rejected_promise = console->rejected_promises->start;
2714
2715
        for (i = 0; i < console->rejected_promises->items; i++) {
2716
            JS_FreeValue(engine->u.qjs.ctx, rejected_promise[i].u.qjs.promise);
2717
            JS_FreeValue(engine->u.qjs.ctx, rejected_promise[i].u.qjs.message);
2718
        }
2719
    }
2720
2721
    events = &console->posted_events;
2722
2723
    for ( ;; ) {
2724
        link = njs_queue_first(events);
2725
2726
        if (link == njs_queue_tail(events)) {
2727
            break;
2728
        }
2729
2730
        ev = njs_queue_link_data(link, njs_ev_t, link);
2731
2732
        njs_queue_remove(&ev->link);
2733
        njs_rbtree_delete(&console->events, &ev->node);
2734
2735
        njs_qjs_destroy_event(engine->u.qjs.ctx, console, ev);
2736
    }
2737
2738
    for ( ;; ) {
2739
        link = njs_queue_first(&console->agents);
2740
2741
        if (link == njs_queue_tail(&console->agents)) {
2742
            break;
2743
        }
2744
2745
        agent = njs_queue_link_data(link, njs_262agent_t, link);
2746
2747
        njs_queue_remove(&agent->link);
2748
2749
        pthread_join(agent->tid, NULL);
2750
        JS_FreeValue(engine->u.qjs.ctx, agent->broadcast_sab);
2751
        free(agent->script);
2752
        free(agent);
2753
    }
2754
2755
    JS_FreeValue(engine->u.qjs.ctx, console->process);
2756
    JS_FreeValue(engine->u.qjs.ctx, engine->u.qjs.value);
2757
    JS_FreeContext(engine->u.qjs.ctx);
2758
    JS_FreeRuntime(engine->u.qjs.rt);
2759
2760
    return NJS_OK;
2761
}
2762
2763
2764
static njs_int_t
2765
njs_engine_qjs_eval(njs_engine_t *engine, njs_str_t *script)
2766
{
2767
    int            flags;
2768
    JSValue        code;
2769
    njs_console_t  *console;
2770
2771
    flags = JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT
2772
            | JS_EVAL_FLAG_COMPILE_ONLY;
2773
2774
    console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
2775
2776
    if (console->module) {
2777
        flags |= JS_EVAL_TYPE_MODULE;
2778
    }
2779
2780
    code = JS_Eval(engine->u.qjs.ctx, (char *) script->start,
2781
                   script->length, "<input>", flags);
2782
2783
    if (JS_IsException(code)) {
2784
        return NJS_ERROR;
2785
    }
2786
2787
    JS_FreeValue(engine->u.qjs.ctx, engine->u.qjs.value);
2788
2789
    engine->u.qjs.value = JS_EvalFunction(engine->u.qjs.ctx, code);
2790
2791
    return (JS_IsException(engine->u.qjs.value)) ? NJS_ERROR : NJS_OK;
2792
}
2793
2794
2795
static njs_int_t
2796
njs_engine_qjs_execute_pending_job(njs_engine_t *engine)
2797
{
2798
    JSContext  *ctx1;
2799
2800
    return JS_ExecutePendingJob(engine->u.qjs.rt, &ctx1);
2801
}
2802
2803
2804
static njs_int_t
2805
njs_engine_qjs_unhandled_rejection(njs_engine_t *engine)
2806
{
2807
    size_t                  len;
2808
    uint32_t                i;
2809
    JSContext               *ctx;
2810
    const char              *str;
2811
    njs_console_t           *console;
2812
    njs_rejected_promise_t  *rejected_promise;
2813
2814
    ctx = engine->u.qjs.ctx;
2815
    console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
2816
2817
    if (console->rejected_promises == NULL
2818
        || console->rejected_promises->items == 0)
2819
    {
2820
        return 0;
2821
    }
2822
2823
    rejected_promise = console->rejected_promises->start;
2824
2825
    str = JS_ToCStringLen(ctx, &len, rejected_promise->u.qjs.message);
2826
    if (njs_slow_path(str == NULL)) {
2827
        return -1;
2828
    }
2829
2830
    JS_ThrowTypeError(ctx, "unhandled promise rejection: %*s", (int) len, str);
2831
    JS_FreeCString(ctx, str);
2832
2833
    for (i = 0; i < console->rejected_promises->items; i++) {
2834
        JS_FreeValue(ctx, rejected_promise[i].u.qjs.promise);
2835
        JS_FreeValue(ctx, rejected_promise[i].u.qjs.message);
2836
    }
2837
2838
    njs_arr_destroy(console->rejected_promises);
2839
    console->rejected_promises = NULL;
2840
2841
    return 1;
2842
}
2843
2844
2845
static njs_int_t
2846
njs_engine_qjs_process_events(njs_engine_t *engine)
2847
{
2848
    JSValue           ret;
2849
    njs_ev_t          *ev;
2850
    JSContext         *ctx;
2851
    njs_queue_t       *events;
2852
    njs_console_t     *console;
2853
    njs_queue_link_t  *link;
2854
2855
    ctx = engine->u.qjs.ctx;
2856
    console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
2857
    events = &console->posted_events;
2858
2859
    for ( ;; ) {
2860
        link = njs_queue_first(events);
2861
2862
        if (link == njs_queue_tail(events)) {
2863
            break;
2864
        }
2865
2866
        ev = njs_queue_link_data(link, njs_ev_t, link);
2867
2868
        njs_queue_remove(&ev->link);
2869
        njs_rbtree_delete(&console->events, &ev->node);
2870
2871
        ret = JS_Call(ctx, ev->u.qjs.function, JS_UNDEFINED, ev->nargs,
2872
                      ev->u.qjs.args);
2873
2874
        njs_qjs_destroy_event(ctx, console, ev);
2875
2876
        if (JS_IsException(ret)) {
2877
            engine->output(engine, NJS_ERROR);
2878
2879
            if (!console->interactive) {
2880
                return NJS_ERROR;
2881
            }
2882
        }
2883
2884
        JS_FreeValue(ctx, ret);
2885
    }
2886
2887
    if (!njs_rbtree_is_empty(&console->events)) {
2888
        return NJS_AGAIN;
2889
    }
2890
2891
    return JS_IsJobPending(engine->u.qjs.rt) ? NJS_AGAIN: NJS_OK;
2892
}
2893
2894
2895
static njs_int_t
2896
njs_engine_qjs_output(njs_engine_t *engine, njs_int_t ret)
2897
{
2898
    JSContext      *ctx;
2899
    njs_console_t  *console;
2900
2901
    ctx = engine->u.qjs.ctx;
2902
    console = JS_GetRuntimeOpaque(engine->u.qjs.rt);
2903
2904
    if (ret == NJS_OK) {
2905
        if (console->interactive) {
2906
            njs_qjs_dump_obj(ctx, stdout, engine->u.qjs.value, "", "\'");
2907
        }
2908
2909
    } else {
2910
        njs_qjs_dump_error(ctx);
2911
    }
2912
2913
    return NJS_OK;
2914
}
2915
2916
2917
static njs_arr_t *
2918
njs_qjs_object_completions(njs_engine_t *engine, JSContext *ctx,
2919
    JSValueConst object, njs_str_t *expression)
2920
{
2921
    u_char          *prefix;
2922
    size_t          len, prefix_len;
2923
    JSValue         prototype;
2924
    uint32_t        k, n, length;
2925
    njs_int_t       ret;
2926
    njs_arr_t       *array;
2927
    njs_str_t       *completion, key;
2928
    JSPropertyEnum  *ptab;
2929
2930
    prefix = expression->start + expression->length;
2931
2932
    while (prefix > expression->start && *prefix != '.') {
2933
        prefix--;
2934
    }
2935
2936
    if (prefix != expression->start) {
2937
        prefix++;
2938
    }
2939
2940
    ptab = NULL;
2941
    key.start = NULL;
2942
    prefix_len = prefix - expression->start;
2943
    len = expression->length - prefix_len;
2944
2945
    array = njs_arr_create(engine->pool, 8, sizeof(njs_str_t));
2946
    if (njs_slow_path(array == NULL)) {
2947
        goto fail;
2948
    }
2949
2950
    while (!JS_IsNull(object)) {
2951
        ret = JS_GetOwnPropertyNames(ctx, &ptab, &length, object,
2952
                                     JS_GPN_STRING_MASK);
2953
        if (ret < 0) {
2954
            goto fail;
2955
        }
2956
2957
        for (n = 0; n < length; n++) {
2958
            key.start = (u_char *) JS_AtomToCString(ctx, ptab[n].atom);
2959
            JS_FreeAtom(ctx, ptab[n].atom);
2960
            if (njs_slow_path(key.start == NULL)) {
2961
                goto fail;
2962
            }
2963
2964
            key.length = njs_strlen(key.start);
2965
2966
            if (len > key.length || njs_strncmp(key.start, prefix, len) != 0) {
2967
                goto next;
2968
            }
2969
2970
            for (k = 0; k < array->items; k++) {
2971
                completion = njs_arr_item(array, k);
2972
2973
                if ((completion->length - prefix_len - 1) == key.length
2974
                    && njs_strncmp(&completion->start[prefix_len],
2975
                                   key.start, key.length)
2976
                       == 0)
2977
                {
2978
                    goto next;
2979
                }
2980
            }
2981
2982
            completion = njs_arr_add(array);
2983
            if (njs_slow_path(completion == NULL)) {
2984
                goto fail;
2985
            }
2986
2987
            completion->length = prefix_len + key.length + 1;
2988
            completion->start = njs_mp_alloc(engine->pool, completion->length);
2989
            if (njs_slow_path(completion->start == NULL)) {
2990
                goto fail;
2991
            }
2992
2993
            njs_sprintf(completion->start,
2994
                        completion->start + completion->length,
2995
                        "%*s%V%Z", prefix_len, expression->start, &key);
2996
2997
next:
2998
2999
            JS_FreeCString(ctx, (const char *) key.start);
3000
        }
3001
3002
        js_free_rt(JS_GetRuntime(ctx), ptab);
3003
3004
        prototype = JS_GetPrototype(ctx, object);
3005
        if (JS_IsException(prototype)) {
3006
            goto fail;
3007
        }
3008
3009
        JS_FreeValue(ctx, object);
3010
        object = prototype;
3011
    }
3012
3013
    return array;
3014
3015
fail:
3016
3017
    if (array != NULL) {
3018
        njs_arr_destroy(array);
3019
    }
3020
3021
    if (key.start != NULL) {
3022
        JS_FreeCString(ctx, (const char *) key.start);
3023
    }
3024
3025
    if (ptab != NULL) {
3026
        js_free_rt(JS_GetRuntime(ctx), ptab);
3027
    }
3028
3029
    JS_FreeValue(ctx, object);
3030
3031
    return NULL;
3032
}
3033
3034
3035
static njs_arr_t *
3036
njs_engine_qjs_complete(njs_engine_t *engine, njs_str_t *expression)
3037
{
3038
    u_char      *p, *start, *end;
3039
    JSAtom      key;
3040
    JSValue     value, retval;
3041
    njs_arr_t   *arr;
3042
    JSContext   *ctx;
3043
    njs_bool_t  global;
3044
3045
    ctx = engine->u.qjs.ctx;
3046
3047
    p = expression->start;
3048
    end = p + expression->length;
3049
3050
    global = 1;
3051
    value = JS_GetGlobalObject(ctx);
3052
3053
    while (p < end && *p != '.') { p++; }
3054
3055
    if (p == end) {
3056
        goto done;
3057
    }
3058
3059
    p = expression->start;
3060
3061
    for ( ;; ) {
3062
3063
        start = (*p == '.' && p < end) ? ++p: p;
3064
3065
        if (p == end) {
3066
            break;
3067
        }
3068
3069
        while (p < end && *p != '.') { p++; }
3070
3071
        key = JS_NewAtomLen(ctx, (char *) start, p - start);
3072
        if (key == JS_ATOM_NULL) {
3073
            goto fail;
3074
        }
3075
3076
        retval = JS_GetProperty(ctx, value, key);
3077
3078
        JS_FreeAtom(ctx, key);
3079
3080
        if (JS_IsUndefined(retval)) {
3081
            if (global) {
3082
                goto fail;
3083
            }
3084
3085
            goto done;
3086
        }
3087
3088
        if (JS_IsException(retval)) {
3089
            goto fail;
3090
        }
3091
3092
        JS_FreeValue(ctx, value);
3093
        value = retval;
3094
        global = 0;
3095
    }
3096
3097
done:
3098
3099
    arr = njs_qjs_object_completions(engine, ctx, JS_DupValue(ctx, value),
3100
                                     expression);
3101
3102
    JS_FreeValue(ctx, value);
3103
3104
    return arr;
3105
3106
fail:
3107
3108
    JS_FreeValue(ctx, value);
3109
3110
    return NULL;
3111
}
3112
3113
#endif
3114
3115
3116
static njs_engine_t *
3117
njs_create_engine(njs_opts_t *opts)
3118
36.2k
{
3119
36.2k
    njs_mp_t      *mp;
3120
36.2k
    njs_int_t     ret;
3121
36.2k
    njs_engine_t  *engine;
3122
3123
36.2k
    mp = njs_mp_fast_create(2 * njs_pagesize(), 128, 512, 16);
3124
36.2k
    if (njs_slow_path(mp == NULL)) {
3125
0
        return NULL;
3126
0
    }
3127
3128
36.2k
    engine = njs_mp_zalloc(mp, sizeof(njs_engine_t));
3129
36.2k
    if (njs_slow_path(engine == NULL)) {
3130
0
        return NULL;
3131
0
    }
3132
3133
36.2k
    engine->pool = mp;
3134
3135
36.2k
    njs_console.engine = engine;
3136
3137
36.2k
    switch (opts->engine) {
3138
36.2k
    case NJS_ENGINE_NJS:
3139
36.2k
        ret = njs_engine_njs_init(engine, opts);
3140
36.2k
        if (njs_slow_path(ret != NJS_OK)) {
3141
0
            njs_stderror("njs_engine_njs_init() failed\n");
3142
0
            return NULL;
3143
0
        }
3144
3145
36.2k
        engine->type = NJS_ENGINE_NJS;
3146
36.2k
        engine->eval = njs_engine_njs_eval;
3147
36.2k
        engine->execute_pending_job = njs_engine_njs_execute_pending_job;
3148
36.2k
        engine->unhandled_rejection = njs_engine_njs_unhandled_rejection;
3149
36.2k
        engine->process_events = njs_engine_njs_process_events;
3150
36.2k
        engine->destroy = njs_engine_njs_destroy;
3151
36.2k
        engine->output = njs_engine_njs_output;
3152
36.2k
        engine->complete = njs_engine_njs_complete;
3153
36.2k
        break;
3154
3155
#ifdef NJS_HAVE_QUICKJS
3156
    case NJS_ENGINE_QUICKJS:
3157
        ret = njs_engine_qjs_init(engine, opts);
3158
        if (njs_slow_path(ret != NJS_OK)) {
3159
            njs_stderror("njs_engine_qjs_init() failed\n");
3160
            return NULL;
3161
        }
3162
3163
        engine->type = NJS_ENGINE_QUICKJS;
3164
        engine->eval = njs_engine_qjs_eval;
3165
        engine->execute_pending_job = njs_engine_qjs_execute_pending_job;
3166
        engine->unhandled_rejection = njs_engine_qjs_unhandled_rejection;
3167
        engine->process_events = njs_engine_qjs_process_events;
3168
        engine->destroy = njs_engine_qjs_destroy;
3169
        engine->output = njs_engine_qjs_output;
3170
        engine->complete = njs_engine_qjs_complete;
3171
        break;
3172
#endif
3173
3174
0
    default:
3175
0
        njs_stderror("unknown engine type\n");
3176
0
        return NULL;
3177
36.2k
    }
3178
3179
36.2k
    return engine;
3180
36.2k
}
3181
3182
3183
static njs_int_t
3184
njs_read_file(njs_opts_t *opts, njs_str_t *content)
3185
0
{
3186
0
    int          fd;
3187
0
    char         *file;
3188
0
    u_char       *p, *end, *start;
3189
0
    size_t       size;
3190
0
    ssize_t      n;
3191
0
    njs_int_t    ret;
3192
0
    struct stat  sb;
3193
3194
0
    file = opts->file;
3195
3196
0
    if (file[0] == '-' && file[1] == '\0') {
3197
0
        fd = STDIN_FILENO;
3198
3199
0
    } else {
3200
0
        fd = open(file, O_RDONLY);
3201
0
        if (fd == -1) {
3202
0
            njs_stderror("failed to open file: '%s' (%s)\n",
3203
0
                         file, strerror(errno));
3204
0
            return NJS_ERROR;
3205
0
        }
3206
0
    }
3207
3208
0
    if (fstat(fd, &sb) == -1) {
3209
0
        njs_stderror("fstat(%d) failed while reading '%s' (%s)\n",
3210
0
                     fd, file, strerror(errno));
3211
0
        ret = NJS_ERROR;
3212
0
        goto close_fd;
3213
0
    }
3214
3215
0
    size = 4096;
3216
3217
0
    if (S_ISREG(sb.st_mode) && sb.st_size) {
3218
0
        size = sb.st_size;
3219
0
    }
3220
3221
0
    content->length = 0;
3222
0
    content->start = realloc(NULL, size + 1);
3223
0
    if (content->start == NULL) {
3224
0
        njs_stderror("alloc failed while reading '%s'\n", file);
3225
0
        ret = NJS_ERROR;
3226
0
        goto close_fd;
3227
0
    }
3228
3229
0
    p = content->start;
3230
0
    end = p + size;
3231
3232
0
    for ( ;; ) {
3233
0
        n = read(fd, p, end - p);
3234
3235
0
        if (n == 0) {
3236
0
            break;
3237
0
        }
3238
3239
0
        if (n < 0) {
3240
0
            njs_stderror("failed to read file: '%s' (%s)\n",
3241
0
                      file, strerror(errno));
3242
0
            ret = NJS_ERROR;
3243
0
            goto close_fd;
3244
0
        }
3245
3246
0
        if (p + n == end) {
3247
0
            size *= 2;
3248
3249
0
            start = realloc(content->start, size + 1);
3250
0
            if (start == NULL) {
3251
0
                njs_stderror("alloc failed while reading '%s'\n", file);
3252
0
                ret = NJS_ERROR;
3253
0
                goto close_fd;
3254
0
            }
3255
3256
0
            content->start = start;
3257
3258
0
            p = content->start + content->length;
3259
0
            end = content->start + size;
3260
0
        }
3261
3262
0
        p += n;
3263
0
        content->length += n;
3264
0
    }
3265
3266
0
    content->start[content->length] = '\0';
3267
3268
0
    ret = NJS_OK;
3269
3270
0
close_fd:
3271
3272
0
    if (fd != STDIN_FILENO) {
3273
0
        (void) close(fd);
3274
0
    }
3275
3276
0
    return ret;
3277
0
}
3278
3279
3280
static njs_int_t
3281
njs_process_file(njs_opts_t *opts)
3282
0
{
3283
0
    u_char        *p;
3284
0
    njs_int_t     ret;
3285
0
    njs_str_t     source, script;
3286
0
    njs_engine_t  *engine;
3287
3288
0
    engine = NULL;
3289
0
    source.start = NULL;
3290
3291
0
    ret = njs_read_file(opts, &source);
3292
0
    if (ret != NJS_OK) {
3293
0
        goto done;
3294
0
    }
3295
3296
0
    script = source;
3297
3298
    /* shebang */
3299
3300
0
    if (script.length > 2 && memcmp(script.start, "#!", 2) == 0) {
3301
0
        p = njs_strlchr(script.start, script.start + script.length, '\n');
3302
3303
0
        if (p != NULL) {
3304
0
            script.length -= (p + 1 - script.start);
3305
0
            script.start = p + 1;
3306
3307
0
        } else {
3308
0
            script.length = 0;
3309
0
        }
3310
0
    }
3311
3312
0
    engine = njs_create_engine(opts);
3313
0
    if (engine == NULL) {
3314
0
        ret = NJS_ERROR;
3315
0
        goto done;
3316
0
    }
3317
3318
0
    ret = njs_process_script(engine, &njs_console, &script);
3319
0
    if (ret != NJS_OK) {
3320
0
        ret = NJS_ERROR;
3321
0
        goto done;
3322
0
    }
3323
3324
0
    ret = NJS_OK;
3325
3326
0
done:
3327
3328
0
    if (engine != NULL) {
3329
0
        engine->destroy(engine);
3330
0
    }
3331
3332
0
    if (source.start != NULL) {
3333
0
        free(source.start);
3334
0
    }
3335
3336
0
    return ret;
3337
0
}
3338
3339
3340
static njs_int_t
3341
njs_process_script(njs_engine_t *engine, njs_console_t *console,
3342
    njs_str_t *script)
3343
36.2k
{
3344
36.2k
    njs_int_t   ret;
3345
3346
36.2k
    ret = engine->eval(engine, script);
3347
3348
36.2k
    if (!console->suppress_stdout) {
3349
0
        engine->output(engine, ret);
3350
0
    }
3351
3352
36.2k
    if (!console->interactive && ret == NJS_ERROR) {
3353
2.33k
        return NJS_ERROR;
3354
2.33k
    }
3355
3356
33.8k
    for ( ;; ) {
3357
290k
        for ( ;; ) {
3358
290k
            ret = engine->execute_pending_job(engine);
3359
290k
            if (ret <= NJS_OK) {
3360
33.8k
                if (ret == NJS_ERROR) {
3361
0
                    if (!console->suppress_stdout) {
3362
0
                        engine->output(engine, ret);
3363
0
                    }
3364
3365
0
                    if (!console->interactive) {
3366
0
                         return NJS_ERROR;
3367
0
                    }
3368
0
                }
3369
3370
33.8k
                break;
3371
33.8k
            }
3372
290k
        }
3373
3374
33.8k
        ret = engine->process_events(engine);
3375
33.8k
        if (njs_slow_path(ret == NJS_ERROR)) {
3376
0
            break;
3377
0
        }
3378
3379
33.8k
        if (engine->unhandled_rejection(engine)) {
3380
0
            if (!console->suppress_stdout) {
3381
0
                engine->output(engine, NJS_ERROR);
3382
0
            }
3383
3384
0
            if (!console->interactive) {
3385
0
                return NJS_ERROR;
3386
0
            }
3387
0
        }
3388
3389
33.8k
        if (ret == NJS_OK) {
3390
33.8k
            break;
3391
33.8k
        }
3392
33.8k
    }
3393
3394
33.8k
    return ret;
3395
33.8k
}
3396
3397
3398
#if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE)
3399
3400
3401
volatile sig_atomic_t njs_running;
3402
volatile sig_atomic_t njs_sigint_count;
3403
volatile sig_atomic_t njs_sigint_received;
3404
3405
3406
static void
3407
njs_cb_line_handler(char *line_in)
3408
{
3409
    njs_int_t  ret;
3410
    njs_str_t  line;
3411
3412
    if (line_in == NULL) {
3413
        njs_running = NJS_DONE;
3414
        return;
3415
    }
3416
3417
    line.start = (u_char *) line_in;
3418
    line.length = njs_strlen(line.start);
3419
3420
    if (strcmp(line_in, ".exit") == 0) {
3421
        njs_running = NJS_DONE;
3422
        goto free_line;
3423
    }
3424
3425
    njs_sigint_count = 0;
3426
3427
    if (line.length == 0) {
3428
        rl_callback_handler_install(">> ", njs_cb_line_handler);
3429
        goto free_line;
3430
    }
3431
3432
    add_history((char *) line.start);
3433
3434
    ret = njs_process_script(njs_console.engine, &njs_console, &line);
3435
    if (ret == NJS_ERROR) {
3436
        njs_running = NJS_ERROR;
3437
    }
3438
3439
    if (ret == NJS_OK) {
3440
        rl_callback_handler_install(">> ", njs_cb_line_handler);
3441
    }
3442
3443
free_line:
3444
3445
    free(line.start);
3446
}
3447
3448
3449
static njs_int_t
3450
njs_interactive_shell(njs_opts_t *opts)
3451
{
3452
    int             flags;
3453
    fd_set          fds;
3454
    njs_int_t       ret;
3455
    njs_engine_t    *engine;
3456
    struct timeval  timeout;
3457
3458
    if (njs_editline_init() != NJS_OK) {
3459
        njs_stderror("failed to init completions\n");
3460
        return NJS_ERROR;
3461
    }
3462
3463
    engine = njs_create_engine(opts);
3464
    if (engine == NULL) {
3465
        njs_stderror("njs_create_engine() failed\n");
3466
        return NJS_ERROR;
3467
    }
3468
3469
    if (!opts->quiet) {
3470
        if (engine->type == NJS_ENGINE_NJS) {
3471
            njs_printf("interactive njs (njs:%s)\n\n", NJS_VERSION);
3472
3473
#if (NJS_HAVE_QUICKJS)
3474
        } else {
3475
            njs_printf("interactive njs (QuickJS:%s)\n\n", NJS_QUICKJS_VERSION);
3476
#endif
3477
        }
3478
    }
3479
3480
    rl_callback_handler_install(">> ", njs_cb_line_handler);
3481
3482
    flags = fcntl(STDIN_FILENO, F_GETFL, 0);
3483
    fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
3484
3485
    njs_running = NJS_OK;
3486
3487
    while (njs_running == NJS_OK) {
3488
        FD_ZERO(&fds);
3489
        FD_SET(STDIN_FILENO, &fds);
3490
        timeout = (struct timeval) {1, 0};
3491
3492
        ret = select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
3493
        if (ret < 0 && errno != EINTR) {
3494
            njs_stderror("select() failed\n");
3495
            njs_running = NJS_ERROR;
3496
            break;
3497
        }
3498
3499
        if (njs_sigint_received) {
3500
            if (njs_sigint_count > 1) {
3501
                njs_running = NJS_DONE;
3502
                break;
3503
            }
3504
3505
            if (rl_end != 0) {
3506
                njs_printf("\n");
3507
3508
                njs_sigint_count = 0;
3509
3510
            } else {
3511
                njs_printf("(To exit, press Ctrl+C again or Ctrl+D "
3512
                           "or type .exit)\n");
3513
3514
                njs_sigint_count = 1;
3515
            }
3516
3517
            rl_point = rl_end = 0;
3518
            rl_on_new_line();
3519
            rl_redisplay();
3520
3521
            njs_sigint_received = 0;
3522
        }
3523
3524
        if (ret < 0) {
3525
            continue;
3526
        }
3527
3528
        if (FD_ISSET(fileno(rl_instream), &fds)) {
3529
            rl_callback_read_char();
3530
        }
3531
    }
3532
3533
    rl_callback_handler_remove();
3534
3535
    if (njs_running == NJS_DONE) {
3536
        njs_printf("exiting\n");
3537
    }
3538
3539
    engine->destroy(engine);
3540
3541
    return njs_running == NJS_DONE ? NJS_OK : njs_running;
3542
}
3543
3544
3545
static char **
3546
njs_completion_handler(const char *text, int start, int end)
3547
{
3548
    rl_attempted_completion_over = 1;
3549
3550
    return rl_completion_matches(text, njs_completion_generator);
3551
}
3552
3553
3554
static void
3555
njs_signal_handler(int signal)
3556
{
3557
    switch (signal) {
3558
    case SIGINT:
3559
        njs_sigint_received = 1;
3560
        njs_sigint_count += 1;
3561
        break;
3562
    default:
3563
        break;
3564
    }
3565
}
3566
3567
3568
static njs_int_t
3569
njs_editline_init(void)
3570
{
3571
    rl_completion_append_character = '\0';
3572
    rl_attempted_completion_function = njs_completion_handler;
3573
    rl_basic_word_break_characters = (char *) " \t\n\"\\'`@$><=;,|&{(";
3574
3575
    setlocale(LC_ALL, "");
3576
3577
    signal(SIGINT, njs_signal_handler);
3578
3579
    return NJS_OK;
3580
}
3581
3582
3583
static char *
3584
njs_completion_generator(const char *text, int state)
3585
{
3586
    njs_str_t         expression, *suffix;
3587
    njs_engine_t      *engine;
3588
    njs_completion_t  *cmpl;
3589
3590
    engine = njs_console.engine;
3591
    cmpl = &engine->completion;
3592
3593
    if (state == 0) {
3594
        cmpl->index = 0;
3595
        expression.start = (u_char *) text;
3596
        expression.length = njs_strlen(text);
3597
3598
        cmpl->suffix_completions = engine->complete(engine, &expression);
3599
        if (cmpl->suffix_completions == NULL) {
3600
            return NULL;
3601
        }
3602
    }
3603
3604
    if (cmpl->index == cmpl->suffix_completions->items) {
3605
        return NULL;
3606
    }
3607
3608
    suffix = njs_arr_item(cmpl->suffix_completions, cmpl->index++);
3609
3610
    return strndup((char *) suffix->start, suffix->length);
3611
}
3612
3613
#endif
3614
3615
3616
static njs_int_t
3617
njs_ext_console_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3618
    njs_index_t magic, njs_value_t *retval)
3619
53.8k
{
3620
53.8k
    njs_str_t        msg;
3621
53.8k
    njs_uint_t       n;
3622
53.8k
    njs_log_level_t  level;
3623
3624
53.8k
    n = 1;
3625
53.8k
    level = (njs_log_level_t) magic & NJS_LOG_MASK;
3626
3627
169k
    while (n < nargs) {
3628
115k
        if (njs_vm_value_dump(vm, &msg, njs_argument(args, n), 1,
3629
115k
                              !!(magic & NJS_LOG_DUMP))
3630
115k
            == NJS_ERROR)
3631
0
        {
3632
0
            return NJS_ERROR;
3633
0
        }
3634
3635
115k
        njs_console_logger(level, msg.start, msg.length);
3636
3637
115k
        n++;
3638
115k
    }
3639
3640
53.8k
    njs_value_undefined_set(retval);
3641
3642
53.8k
    return NJS_OK;
3643
53.8k
}
3644
3645
3646
static njs_int_t
3647
njs_ext_console_time(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3648
    njs_index_t unused, njs_value_t *retval)
3649
0
{
3650
0
    njs_int_t      ret;
3651
0
    njs_str_t      name;
3652
0
    njs_value_t    *value;
3653
0
    njs_console_t  *console;
3654
3655
0
    static const njs_str_t  default_label = njs_str("default");
3656
3657
0
    console = njs_vm_external(vm, njs_console_proto_id, njs_argument(args, 0));
3658
0
    if (njs_slow_path(console == NULL)) {
3659
0
        njs_vm_error(vm, "external value is expected");
3660
0
        return NJS_ERROR;
3661
0
    }
3662
3663
0
    name = default_label;
3664
3665
0
    value = njs_arg(args, nargs, 1);
3666
3667
0
    if (njs_slow_path(!njs_value_is_string(value))) {
3668
0
        if (!njs_value_is_undefined(value)) {
3669
0
            ret = njs_value_to_string(vm, value, value);
3670
0
            if (njs_slow_path(ret != NJS_OK)) {
3671
0
                return ret;
3672
0
            }
3673
3674
0
            njs_value_string_get(value, &name);
3675
0
        }
3676
3677
0
    } else {
3678
0
        njs_value_string_get(value, &name);
3679
0
    }
3680
3681
0
    if (njs_console_time(console, &name) != NJS_OK) {
3682
0
        njs_vm_error(vm, "failed to add timer");
3683
0
        return NJS_ERROR;
3684
0
    }
3685
3686
0
    njs_value_undefined_set(retval);
3687
3688
0
    return NJS_OK;
3689
0
}
3690
3691
3692
static njs_int_t
3693
njs_ext_console_time_end(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3694
    njs_index_t unused, njs_value_t *retval)
3695
0
{
3696
0
    uint64_t       ns;
3697
0
    njs_int_t      ret;
3698
0
    njs_str_t      name;
3699
0
    njs_value_t    *value;
3700
0
    njs_console_t  *console;
3701
3702
0
    static const njs_str_t  default_label = njs_str("default");
3703
3704
0
    ns = njs_time();
3705
3706
0
    console = njs_vm_external(vm, njs_console_proto_id, njs_argument(args, 0));
3707
0
    if (njs_slow_path(console == NULL)) {
3708
0
        njs_vm_error(vm, "external value is expected");
3709
0
        return NJS_ERROR;
3710
0
    }
3711
3712
0
    name = default_label;
3713
3714
0
    value = njs_arg(args, nargs, 1);
3715
3716
0
    if (njs_slow_path(!njs_value_is_string(value))) {
3717
0
        if (!njs_value_is_undefined(value)) {
3718
0
            ret = njs_value_to_string(vm, value, value);
3719
0
            if (njs_slow_path(ret != NJS_OK)) {
3720
0
                return ret;
3721
0
            }
3722
3723
0
            njs_value_string_get(value, &name);
3724
0
        }
3725
3726
0
    } else {
3727
0
        njs_value_string_get(value, &name);
3728
0
    }
3729
3730
0
    njs_console_time_end(console, &name, ns);
3731
3732
0
    njs_value_undefined_set(retval);
3733
3734
0
    return NJS_OK;
3735
0
}
3736
3737
3738
static njs_int_t
3739
njs_set_timer(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3740
    njs_index_t unused, njs_bool_t immediate, njs_value_t *retval)
3741
0
{
3742
0
    njs_ev_t       *ev;
3743
0
    uint64_t       delay;
3744
0
    njs_uint_t     n;
3745
0
    njs_console_t  *console;
3746
3747
0
    console = njs_vm_external_ptr(vm);
3748
3749
0
    if (njs_slow_path(nargs < 2)) {
3750
0
        njs_vm_type_error(vm, "too few arguments");
3751
0
        return NJS_ERROR;
3752
0
    }
3753
3754
0
    if (njs_slow_path(!njs_value_is_function(njs_argument(args, 1)))) {
3755
0
        njs_vm_type_error(vm, "first arg must be a function");
3756
0
        return NJS_ERROR;
3757
0
    }
3758
3759
0
    delay = 0;
3760
3761
0
    if (!immediate && nargs >= 3
3762
0
        && njs_value_is_number(njs_argument(args, 2)))
3763
0
    {
3764
0
        delay = njs_value_number(njs_argument(args, 2));
3765
0
    }
3766
3767
0
    if (delay != 0) {
3768
0
        njs_vm_internal_error(vm, "njs_set_timer(): async timers unsupported");
3769
0
        return NJS_ERROR;
3770
0
    }
3771
3772
0
    n = immediate ? 2 : 3;
3773
0
    nargs = (nargs >= n) ? nargs - n : 0;
3774
3775
0
    ev = njs_mp_alloc(njs_vm_memory_pool(vm),
3776
0
                      sizeof(njs_ev_t) + sizeof(njs_opaque_value_t) * nargs);
3777
0
    if (njs_slow_path(ev == NULL)) {
3778
0
        njs_vm_memory_error(vm);
3779
0
        return NJS_ERROR;
3780
0
    }
3781
3782
0
    ev->u.njs.function = njs_value_function(njs_argument(args, 1));
3783
0
    ev->u.njs.args = (njs_value_t *) ((u_char *) ev + sizeof(njs_ev_t));
3784
0
    ev->nargs = nargs;
3785
0
    ev->id = console->event_id++;
3786
3787
0
    if (ev->nargs != 0) {
3788
0
        memcpy(ev->u.njs.args, njs_argument(args, n),
3789
0
               sizeof(njs_opaque_value_t) * ev->nargs);
3790
0
    }
3791
3792
0
    njs_rbtree_insert(&console->events, &ev->node);
3793
3794
0
    njs_queue_insert_tail(&console->posted_events, &ev->link);
3795
3796
0
    njs_value_number_set(retval, ev->id);
3797
3798
0
    return NJS_OK;
3799
0
}
3800
3801
3802
static njs_int_t
3803
njs_set_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3804
    njs_index_t unused, njs_value_t *retval)
3805
0
{
3806
0
    return njs_set_timer(vm, args, nargs, unused, 0, retval);
3807
0
}
3808
3809
3810
static njs_int_t
3811
njs_set_immediate(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3812
    njs_index_t unused, njs_value_t *retval)
3813
0
{
3814
0
    return njs_set_timer(vm, args, nargs, unused, 1, retval);
3815
0
}
3816
3817
3818
static njs_int_t
3819
njs_clear_timeout(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
3820
    njs_index_t unused, njs_value_t *retval)
3821
0
{
3822
0
    njs_ev_t           ev_lookup, *ev;
3823
0
    njs_console_t      *console;
3824
0
    njs_rbtree_node_t  *rb;
3825
3826
0
    if (nargs < 2 || !njs_value_is_number(njs_argument(args, 1))) {
3827
0
        njs_value_undefined_set(retval);
3828
0
        return NJS_OK;
3829
0
    }
3830
3831
0
    console = njs_vm_external_ptr(vm);
3832
3833
0
    ev_lookup.id = njs_value_number(njs_argument(args, 1));
3834
3835
0
    rb = njs_rbtree_find(&console->events, &ev_lookup.node);
3836
0
    if (njs_slow_path(rb == NULL)) {
3837
0
        njs_vm_internal_error(vm, "failed to find timer");
3838
0
        return NJS_ERROR;
3839
0
    }
3840
3841
0
    ev = (njs_ev_t *) rb;
3842
0
    njs_queue_remove(&ev->link);
3843
0
    njs_rbtree_delete(&console->events, (njs_rbtree_part_t *) rb);
3844
3845
0
    njs_mp_free(njs_vm_memory_pool(vm), ev);
3846
3847
0
    njs_value_undefined_set(retval);
3848
3849
0
    return NJS_OK;
3850
0
}
3851
3852
3853
static void
3854
njs_console_log(njs_log_level_t level, const char *fmt, ...)
3855
0
{
3856
0
    u_char   *p;
3857
0
    va_list  args;
3858
0
    u_char   buf[2048];
3859
3860
0
    va_start(args, fmt);
3861
0
    p = njs_vsprintf(buf, buf + sizeof(buf), fmt, args);
3862
0
    va_end(args);
3863
3864
0
    njs_console_logger(level, buf, p - buf);
3865
0
}
3866
3867
3868
static void
3869
njs_console_logger(njs_log_level_t level, const u_char *start, size_t length)
3870
115k
{
3871
115k
    switch (level) {
3872
0
    case NJS_LOG_WARN:
3873
0
        njs_printf("W: ");
3874
0
        break;
3875
0
    case NJS_LOG_ERROR:
3876
0
        njs_printf("E: ");
3877
0
        break;
3878
115k
    case NJS_LOG_INFO:
3879
115k
        break;
3880
115k
    }
3881
3882
115k
    njs_print(start, length);
3883
115k
    njs_print("\n", 1);
3884
115k
}
3885
3886
3887
static njs_int_t
3888
njs_console_time(njs_console_t *console, njs_str_t *name)
3889
0
{
3890
0
    njs_queue_t       *labels;
3891
0
    njs_timelabel_t   *label;
3892
0
    njs_queue_link_t  *link;
3893
3894
0
    labels = &console->labels;
3895
0
    link = njs_queue_first(labels);
3896
3897
0
    while (link != njs_queue_tail(labels)) {
3898
0
        label = njs_queue_link_data(link, njs_timelabel_t, link);
3899
3900
0
        if (njs_strstr_eq(name, &label->name)) {
3901
0
            njs_console_log(NJS_LOG_INFO, "Timer \"%V\" already exists.",
3902
0
                            name);
3903
0
            return NJS_OK;
3904
0
        }
3905
3906
0
        link = njs_queue_next(link);
3907
0
    }
3908
3909
0
    label = njs_mp_alloc(console->engine->pool,
3910
0
                         sizeof(njs_timelabel_t) + name->length);
3911
0
    if (njs_slow_path(label == NULL)) {
3912
0
        return NJS_ERROR;
3913
0
    }
3914
3915
0
    label->name.start = (u_char *) label + sizeof(njs_timelabel_t);
3916
0
    memcpy(label->name.start, name->start, name->length);
3917
0
    label->name.length = name->length;
3918
0
    label->time = njs_time();
3919
3920
0
    njs_queue_insert_tail(&console->labels, &label->link);
3921
3922
0
    return NJS_OK;
3923
0
}
3924
3925
3926
static void
3927
njs_console_time_end(njs_console_t *console, njs_str_t *name, uint64_t ns)
3928
0
{
3929
0
    uint64_t          ms;
3930
0
    njs_queue_t       *labels;
3931
0
    njs_timelabel_t   *label;
3932
0
    njs_queue_link_t  *link;
3933
3934
0
    labels = &console->labels;
3935
0
    link = njs_queue_first(labels);
3936
3937
0
    for ( ;; ) {
3938
0
        if (link == njs_queue_tail(labels)) {
3939
0
            njs_console_log(NJS_LOG_INFO, "Timer \"%V\" doesn’t exist.",
3940
0
                            name);
3941
0
            return;
3942
0
        }
3943
3944
0
        label = njs_queue_link_data(link, njs_timelabel_t, link);
3945
3946
0
        if (njs_strstr_eq(name, &label->name)) {
3947
0
            njs_queue_remove(&label->link);
3948
0
            break;
3949
0
        }
3950
3951
0
        link = njs_queue_next(link);
3952
0
    }
3953
3954
0
    ns = ns - label->time;
3955
3956
0
    ms = ns / 1000000;
3957
0
    ns = ns % 1000000;
3958
3959
0
    njs_console_log(NJS_LOG_INFO, "%V: %uL.%06uLms", name, ms, ns);
3960
3961
0
    njs_mp_free(console->engine->pool, label);
3962
0
}
3963
3964
3965
static intptr_t
3966
njs_event_rbtree_compare(njs_rbtree_node_t *node1, njs_rbtree_node_t *node2)
3967
0
{
3968
0
    njs_ev_t  *ev1, *ev2;
3969
3970
0
    ev1 = (njs_ev_t *) node1;
3971
0
    ev2 = (njs_ev_t *) node2;
3972
3973
0
    if (ev1->id < ev2->id) {
3974
0
        return -1;
3975
0
    }
3976
3977
0
    if (ev1->id > ev2->id) {
3978
0
        return 1;
3979
0
    }
3980
3981
0
    return 0;
3982
0
}
3983
3984
3985
static uint64_t
3986
njs_time(void)
3987
0
{
3988
0
#if (NJS_HAVE_CLOCK_MONOTONIC)
3989
0
    struct timespec ts;
3990
3991
0
    clock_gettime(CLOCK_MONOTONIC, &ts);
3992
3993
0
    return (uint64_t) ts.tv_sec * 1000000000 + ts.tv_nsec;
3994
#else
3995
    struct timeval tv;
3996
3997
    gettimeofday(&tv, NULL);
3998
3999
    return (uint64_t) tv.tv_sec * 1000000000 + tv.tv_usec * 1000;
4000
#endif
4001
0
}