Coverage Report

Created: 2026-08-31 07:22

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