blob: 5abb1719eb67518de5eaffee4bb6bb37b555cc95 [file] [log] [blame]
/*
* Copyright (C) Dmitry Volyntsev
* Copyright (C) NGINX, Inc.
*/
#include <njs_main.h>
#ifndef NJS_FUZZER_TARGET
#include <locale.h>
#if (NJS_HAVE_EDITLINE)
#include <editline/readline.h>
#elif (NJS_HAVE_EDIT_READLINE)
#include <edit/readline/readline.h>
#else
#include <readline/readline.h>
#if (NJS_HAVE_GNU_READLINE)
#include <readline/history.h>
#endif
#endif
#endif
#if (NJS_HAVE_OPENSSL)
#include "../external/njs_webcrypto.h"
#include "../external/njs_webcrypto.c"
#endif
typedef struct {
uint8_t disassemble;
uint8_t denormals;
uint8_t interactive;
uint8_t module;
uint8_t quiet;
uint8_t silent;
uint8_t sandbox;
uint8_t safe;
uint8_t version;
uint8_t ast;
uint8_t unhandled_rejection;
char *file;
char *command;
size_t n_paths;
char **paths;
char **argv;
njs_uint_t argc;
} njs_opts_t;
typedef struct {
size_t index;
size_t length;
njs_arr_t *completions;
njs_arr_t *suffix_completions;
njs_rbtree_node_t *node;
enum {
NJS_COMPLETION_VAR = 0,
NJS_COMPLETION_SUFFIX,
NJS_COMPLETION_GLOBAL
} phase;
} njs_completion_t;
typedef struct {
njs_vm_event_t vm_event;
njs_queue_link_t link;
} njs_ev_t;
typedef struct {
njs_value_t name;
uint64_t time;
} njs_timelabel_t;
typedef struct {
njs_vm_t *vm;
njs_lvlhsh_t events; /* njs_ev_t * */
njs_queue_t posted_events;
njs_lvlhsh_t labels; /* njs_timelabel_t */
njs_completion_t completion;
} njs_console_t;
static njs_int_t njs_console_init(njs_vm_t *vm, njs_console_t *console);
static njs_int_t njs_externals_init(njs_vm_t *vm, njs_console_t *console);
static njs_vm_t *njs_create_vm(njs_opts_t *opts, njs_vm_opt_t *vm_options);
static njs_int_t njs_process_script(njs_opts_t *opts,
njs_console_t *console, const njs_str_t *script);
#ifndef NJS_FUZZER_TARGET
static njs_int_t njs_options_parse(njs_opts_t *opts, int argc, char **argv);
static void njs_options_free(njs_opts_t *opts);
static njs_int_t njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options);
static njs_int_t njs_interactive_shell(njs_opts_t *opts,
njs_vm_opt_t *vm_options);
static njs_int_t njs_editline_init(void);
static char *njs_completion_generator(const char *text, int state);
#endif
static njs_int_t njs_ext_console_log(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t indent);
static njs_int_t njs_ext_console_time(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused);
static njs_int_t njs_ext_console_time_end(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused);
static njs_host_event_t njs_console_set_timer(njs_external_ptr_t external,
uint64_t delay, njs_vm_event_t vm_event);
static void njs_console_clear_timer(njs_external_ptr_t external,
njs_host_event_t event);
static njs_int_t njs_timelabel_hash_test(njs_lvlhsh_query_t *lhq, void *data);
static njs_int_t lvlhsh_key_test(njs_lvlhsh_query_t *lhq, void *data);
static void *lvlhsh_pool_alloc(void *pool, size_t size);
static void lvlhsh_pool_free(void *pool, void *p, size_t size);
static njs_external_t njs_ext_console[] = {
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("dump"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = njs_ext_console_log,
.magic8 = 1,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("log"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = njs_ext_console_log,
}
},
{
.flags = NJS_EXTERN_PROPERTY | NJS_EXTERN_SYMBOL,
.name.symbol = NJS_SYMBOL_TO_STRING_TAG,
.u.property = {
.value = "Console",
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("time"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = njs_ext_console_time,
}
},
{
.flags = NJS_EXTERN_METHOD,
.name.string = njs_str("timeEnd"),
.writable = 1,
.configurable = 1,
.enumerable = 1,
.u.method = {
.native = njs_ext_console_time_end,
}
},
};
static const njs_lvlhsh_proto_t lvlhsh_proto njs_aligned(64) = {
NJS_LVLHSH_LARGE_SLAB,
lvlhsh_key_test,
lvlhsh_pool_alloc,
lvlhsh_pool_free,
};
static const njs_lvlhsh_proto_t njs_timelabel_hash_proto njs_aligned(64) = {
NJS_LVLHSH_DEFAULT,
njs_timelabel_hash_test,
lvlhsh_pool_alloc,
lvlhsh_pool_free,
};
static njs_vm_ops_t njs_console_ops = {
njs_console_set_timer,
njs_console_clear_timer
};
static njs_int_t njs_console_proto_id;
static njs_console_t njs_console;
#ifndef NJS_FUZZER_TARGET
int
main(int argc, char **argv)
{
char path[MAXPATHLEN], *p;
njs_vm_t *vm;
njs_int_t ret;
njs_opts_t opts;
njs_str_t command;
njs_vm_opt_t vm_options;
njs_memzero(&opts, sizeof(njs_opts_t));
opts.interactive = 1;
ret = njs_options_parse(&opts, argc, argv);
if (ret != NJS_OK) {
ret = (ret == NJS_DONE) ? NJS_OK : NJS_ERROR;
goto done;
}
if (opts.version != 0) {
njs_printf("%s\n", NJS_VERSION);
ret = NJS_OK;
goto done;
}
njs_mm_denormals(opts.denormals);
njs_vm_opt_init(&vm_options);
if (opts.file == NULL) {
p = getcwd(path, sizeof(path));
if (p == NULL) {
njs_stderror("getcwd() failed:%s\n", strerror(errno));
ret = NJS_ERROR;
goto done;
}
if (opts.command == NULL) {
memcpy(path + njs_strlen(path), "/shell", sizeof("/shell"));
} else {
memcpy(path + njs_strlen(path), "/string", sizeof("/string"));
}
opts.file = path;
}
vm_options.file.start = (u_char *) opts.file;
vm_options.file.length = njs_strlen(opts.file);
vm_options.init = 1;
vm_options.interactive = opts.interactive;
vm_options.disassemble = opts.disassemble;
vm_options.backtrace = 1;
vm_options.quiet = opts.quiet;
vm_options.sandbox = opts.sandbox;
vm_options.unsafe = !opts.safe;
vm_options.module = opts.module;
vm_options.ops = &njs_console_ops;
vm_options.external = &njs_console;
vm_options.argv = opts.argv;
vm_options.argc = opts.argc;
vm_options.ast = opts.ast;
vm_options.unhandled_rejection = opts.unhandled_rejection;
if (opts.interactive) {
ret = njs_interactive_shell(&opts, &vm_options);
} else if (opts.command) {
vm = njs_create_vm(&opts, &vm_options);
if (vm != NULL) {
command.start = (u_char *) opts.command;
command.length = njs_strlen(opts.command);
ret = njs_process_script(&opts, vm_options.external, &command);
njs_vm_destroy(vm);
}
} else {
ret = njs_process_file(&opts, &vm_options);
}
done:
njs_options_free(&opts);
return (ret == NJS_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
}
static njs_int_t
njs_options_parse(njs_opts_t *opts, int argc, char **argv)
{
char *p, **paths;
njs_int_t i, ret;
njs_uint_t n;
static const char help[] =
"Interactive njs shell.\n"
"\n"
"njs [options] [-c string | script.js | -] [script args]"
"\n"
"Options:\n"
" -a print AST.\n"
" -c specify the command to execute.\n"
" -d print disassembled code.\n"
" -f disabled denormals mode.\n"
" -p set path prefix for modules.\n"
" -q disable interactive introduction prompt.\n"
" -r ignore unhandled promise rejection.\n"
" -s sandbox mode.\n"
" -t script|module source code type (script is default).\n"
" -v print njs version and exit.\n"
" -u disable \"unsafe\" mode.\n"
" script.js | - run code from a file or stdin.\n";
ret = NJS_DONE;
opts->denormals = 1;
opts->unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_THROW;
for (i = 1; i < argc; i++) {
p = argv[i];
if (p[0] != '-' || (p[0] == '-' && p[1] == '\0')) {
opts->interactive = 0;
opts->file = argv[i];
goto done;
}
p++;
switch (*p) {
case '?':
case 'h':
(void) write(STDOUT_FILENO, help, njs_length(help));
return ret;
case 'a':
opts->ast = 1;
break;
case 'c':
opts->interactive = 0;
if (++i < argc) {
opts->command = argv[i];
goto done;
}
njs_stderror("option \"-c\" requires argument\n");
return NJS_ERROR;
case 'd':
opts->disassemble = 1;
break;
case 'f':
#if !(NJS_HAVE_DENORMALS_CONTROL)
njs_stderror("option \"-f\" is not supported\n");
return NJS_ERROR;
#endif
opts->denormals = 0;
break;
case 'p':
if (++i < argc) {
opts->n_paths++;
paths = realloc(opts->paths, opts->n_paths * sizeof(char *));
if (paths == NULL) {
njs_stderror("failed to add path\n");
return NJS_ERROR;
}
opts->paths = paths;
opts->paths[opts->n_paths - 1] = argv[i];
break;
}
njs_stderror("option \"-p\" requires directory name\n");
return NJS_ERROR;
case 'q':
opts->quiet = 1;
break;
case 'r':
opts->unhandled_rejection = NJS_VM_OPT_UNHANDLED_REJECTION_IGNORE;
break;
case 's':
opts->sandbox = 1;
break;
case 't':
if (++i < argc) {
if (strcmp(argv[i], "module") == 0) {
opts->module = 1;
} else if (strcmp(argv[i], "script") != 0) {
njs_stderror("option \"-t\" unexpected source type: %s\n",
argv[i]);
return NJS_ERROR;
}
break;
}
njs_stderror("option \"-t\" requires source type\n");
return NJS_ERROR;
case 'v':
case 'V':
opts->version = 1;
break;
case 'u':
opts->safe = 1;
break;
default:
njs_stderror("Unknown argument: \"%s\" "
"try \"%s -h\" for available options\n", argv[i],
argv[0]);
return NJS_ERROR;
}
}
done:
opts->argc = njs_max(argc - i + 1, 2);
opts->argv = malloc(sizeof(char*) * opts->argc);
if (opts->argv == NULL) {
njs_stderror("failed to alloc argv\n");
return NJS_ERROR;
}
opts->argv[0] = argv[0];
opts->argv[1] = (opts->file != NULL) ? opts->file : (char *) "";
for (n = 2; n < opts->argc; n++) {
opts->argv[n] = argv[i + n - 1];
}
return NJS_OK;
}
static void
njs_options_free(njs_opts_t *opts)
{
if (opts->paths != NULL) {
free(opts->paths);
}
if (opts->argv != NULL) {
free(opts->argv);
}
}
static njs_int_t
njs_process_file(njs_opts_t *opts, njs_vm_opt_t *vm_options)
{
int fd;
char *file;
u_char *p, *end, *start;
size_t size;
ssize_t n;
njs_vm_t *vm;
njs_int_t ret;
njs_str_t source, script;
struct stat sb;
u_char buf[4096];
file = opts->file;
if (file[0] == '-' && file[1] == '\0') {
fd = STDIN_FILENO;
} else {
fd = open(file, O_RDONLY);
if (fd == -1) {
njs_stderror("failed to open file: '%s' (%s)\n",
file, strerror(errno));
return NJS_ERROR;
}
}
if (fstat(fd, &sb) == -1) {
njs_stderror("fstat(%d) failed while reading '%s' (%s)\n",
fd, file, strerror(errno));
ret = NJS_ERROR;
goto close_fd;
}
size = sizeof(buf);
if (S_ISREG(sb.st_mode) && sb.st_size) {
size = sb.st_size;
}
vm = NULL;
source.length = 0;
source.start = realloc(NULL, size);
if (source.start == NULL) {
njs_stderror("alloc failed while reading '%s'\n", file);
ret = NJS_ERROR;
goto done;
}
p = source.start;
end = p + size;
for ( ;; ) {
n = read(fd, buf, sizeof(buf));
if (n == 0) {
break;
}
if (n < 0) {
njs_stderror("failed to read file: '%s' (%s)\n",
file, strerror(errno));
ret = NJS_ERROR;
goto done;
}
if (p + n > end) {
size *= 2;
start = realloc(source.start, size);
if (start == NULL) {
njs_stderror("alloc failed while reading '%s'\n", file);
ret = NJS_ERROR;
goto done;
}
source.start = start;
p = source.start + source.length;
end = source.start + size;
}
memcpy(p, buf, n);
p += n;
source.length += n;
}
vm = njs_create_vm(opts, vm_options);
if (vm == NULL) {
ret = NJS_ERROR;
goto done;
}
script = source;
/* shebang */
if (script.length > 2 && memcmp(script.start, "#!", 2) == 0) {
p = njs_strlchr(script.start, script.start + script.length, '\n');
if (p != NULL) {
script.length -= (p + 1 - script.start);
script.start = p + 1;
} else {
script.length = 0;
}
}
ret = njs_process_script(opts, vm_options->external, &script);
if (ret != NJS_OK) {
ret = NJS_ERROR;
goto done;
}
ret = NJS_OK;
done:
if (vm != NULL) {
njs_vm_destroy(vm);
}
if (source.start != NULL) {
free(source.start);
}
close_fd:
if (fd != STDIN_FILENO) {
(void) close(fd);
}
return ret;
}
#else
int
LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
njs_vm_t *vm;
njs_opts_t opts;
njs_str_t script;
njs_vm_opt_t vm_options;
if (size == 0) {
return 0;
}
njs_memzero(&opts, sizeof(njs_opts_t));
opts.silent = 1;
njs_vm_opt_init(&vm_options);
vm_options.init = 1;
vm_options.backtrace = 0;
vm_options.ops = &njs_console_ops;
vm_options.external = &njs_console;
vm = njs_create_vm(&opts, &vm_options);
if (njs_fast_path(vm != NULL)) {
script.length = size;
script.start = (u_char *) data;
(void) njs_process_script(&opts, vm_options.external, &script);
njs_vm_destroy(vm);
}
return 0;
}
#endif
static njs_int_t
njs_console_init(njs_vm_t *vm, njs_console_t *console)
{
console->vm = vm;
njs_lvlhsh_init(&console->events);
njs_queue_init(&console->posted_events);
njs_lvlhsh_init(&console->labels);
console->completion.completions = njs_vm_completions(vm, NULL);
if (console->completion.completions == NULL) {
return NJS_ERROR;
}
return NJS_OK;
}
static njs_int_t
njs_externals_init(njs_vm_t *vm, njs_console_t *console)
{
njs_int_t ret;
njs_value_t *value, method;
static const njs_str_t console_name = njs_str("console");
static const njs_str_t print_name = njs_str("print");
static const njs_str_t console_log = njs_str("console.log");
njs_console_proto_id = njs_vm_external_prototype(vm, njs_ext_console,
njs_nitems(njs_ext_console));
if (njs_slow_path(njs_console_proto_id < 0)) {
njs_stderror("failed to add \"console\" proto\n");
return NJS_ERROR;
}
value = njs_mp_zalloc(vm->mem_pool, sizeof(njs_opaque_value_t));
if (njs_slow_path(value == NULL)) {
return NJS_ERROR;
}
ret = njs_vm_external_create(vm, value, njs_console_proto_id, console, 0);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
ret = njs_vm_bind(vm, &console_name, value, 0);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
ret = njs_vm_value(vm, &console_log, &method);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
ret = njs_vm_bind(vm, &print_name, &method, 0);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
ret = njs_console_init(vm, console);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
#if (NJS_HAVE_OPENSSL)
ret = njs_external_webcrypto_init(vm);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
#endif
return NJS_OK;
}
static njs_vm_t *
njs_create_vm(njs_opts_t *opts, njs_vm_opt_t *vm_options)
{
u_char *p, *start;
njs_vm_t *vm;
njs_int_t ret;
njs_str_t path;
njs_uint_t i;
vm = njs_vm_create(vm_options);
if (vm == NULL) {
njs_stderror("failed to create vm\n");
return NULL;
}
if (njs_externals_init(vm, vm_options->external) != NJS_OK) {
njs_stderror("failed to add external protos\n");
return NULL;
}
for (i = 0; i < opts->n_paths; i++) {
path.start = (u_char *) opts->paths[i];
path.length = njs_strlen(opts->paths[i]);
ret = njs_vm_add_path(vm, &path);
if (ret != NJS_OK) {
njs_stderror("failed to add path\n");
return NULL;
}
}
start = (u_char *) getenv("NJS_PATH");
if (start == NULL) {
return vm;
}
for ( ;; ) {
p = njs_strchr(start, ':');
path.start = start;
path.length = (p != NULL) ? (size_t) (p - start) : njs_strlen(start);
ret = njs_vm_add_path(vm, &path);
if (ret != NJS_OK) {
njs_stderror("failed to add path\n");
return NULL;
}
if (p == NULL) {
break;
}
start = p + 1;
}
return vm;
}
static void
njs_output(njs_opts_t *opts, njs_vm_t *vm, njs_int_t ret)
{
njs_str_t out;
if (opts->silent) {
return;
}
if (ret == NJS_OK) {
if (njs_vm_retval_dump(vm, &out, 1) != NJS_OK) {
njs_stderror("Shell:failed to get retval from VM\n");
return;
}
if (vm->options.interactive) {
njs_print(out.start, out.length);
njs_print("\n", 1);
}
} else {
njs_vm_retval_string(vm, &out);
njs_stderror("Thrown:\n%V\n", &out);
}
}
static njs_int_t
njs_process_events(njs_console_t *console)
{
njs_ev_t *ev;
njs_queue_t *events;
njs_queue_link_t *link;
events = &console->posted_events;
for ( ;; ) {
link = njs_queue_first(events);
if (link == njs_queue_tail(events)) {
break;
}
ev = njs_queue_link_data(link, njs_ev_t, link);
njs_queue_remove(&ev->link);
ev->link.prev = NULL;
ev->link.next = NULL;
njs_vm_post_event(console->vm, ev->vm_event, NULL, 0);
}
return NJS_OK;
}
static njs_int_t
njs_process_script(njs_opts_t *opts, njs_console_t *console,
const njs_str_t *script)
{
u_char *start, *end;
njs_vm_t *vm;
njs_int_t ret;
vm = console->vm;
start = script->start;
end = start + script->length;
ret = njs_vm_compile(vm, &start, end);
if (ret == NJS_OK) {
if (start == end) {
ret = njs_vm_start(vm);
} else {
njs_vm_error(vm, "Extra characters at the end of the script");
ret = NJS_ERROR;
}
}
njs_output(opts, vm, ret);
if (!opts->interactive && ret == NJS_ERROR) {
return NJS_ERROR;
}
for ( ;; ) {
if (!njs_vm_pending(vm) && !njs_vm_unhandled_rejection(vm)) {
break;
}
ret = njs_process_events(console);
if (njs_slow_path(ret != NJS_OK)) {
njs_stderror("njs_process_events() failed\n");
ret = NJS_ERROR;
break;
}
if (njs_vm_waiting(vm) && !njs_vm_posted(vm)) {
/*TODO: async events. */
njs_stderror("njs_process_script(): async events unsupported\n");
ret = NJS_ERROR;
break;
}
ret = njs_vm_run(vm);
if (ret == NJS_ERROR) {
njs_output(opts, vm, ret);
if (!opts->interactive) {
return NJS_ERROR;
}
}
}
return ret;
}
#ifndef NJS_FUZZER_TARGET
static njs_int_t
njs_interactive_shell(njs_opts_t *opts, njs_vm_opt_t *vm_options)
{
njs_vm_t *vm;
njs_str_t line;
if (njs_editline_init() != NJS_OK) {
njs_stderror("failed to init completions\n");
return NJS_ERROR;
}
vm = njs_create_vm(opts, vm_options);
if (vm == NULL) {
return NJS_ERROR;
}
if (!opts->quiet) {
njs_printf("interactive njs %s\n\n", NJS_VERSION);
njs_printf("v.<Tab> -> the properties and prototype methods of v.\n\n");
}
for ( ;; ) {
line.start = (u_char *) readline(">> ");
if (line.start == NULL) {
break;
}
line.length = njs_strlen(line.start);
if (line.length != 0) {
add_history((char *) line.start);
njs_process_script(opts, vm_options->external, &line);
}
/* editline allocs a new buffer every time. */
free(line.start);
}
njs_vm_destroy(vm);
return NJS_OK;
}
static char **
njs_completion_handler(const char *text, int start, int end)
{
rl_attempted_completion_over = 1;
return rl_completion_matches(text, njs_completion_generator);
}
static njs_int_t
njs_editline_init(void)
{
rl_completion_append_character = '\0';
rl_attempted_completion_function = njs_completion_handler;
rl_basic_word_break_characters = (char *) " \t\n\"\\'`@$><=;,|&{(";
setlocale(LC_ALL, "");
return NJS_OK;
}
/* editline frees the buffer every time. */
#define njs_editline(s) strndup((char *) (s)->start, (s)->length)
#define njs_completion(c, i) &(((njs_str_t *) (c)->start)[i])
#define njs_next_phase(c) \
(c)->index = 0; \
(c)->phase++; \
goto next;
static char *
njs_completion_generator(const char *text, int state)
{
char *completion;
size_t len;
njs_str_t expression, *suffix;
njs_vm_t *vm;
const char *p;
njs_rbtree_t *variables;
njs_completion_t *cmpl;
njs_variable_node_t *var_node;
const njs_lexer_entry_t *lex_entry;
vm = njs_console.vm;
cmpl = &njs_console.completion;
if (state == 0) {
cmpl->phase = 0;
cmpl->index = 0;
cmpl->length = njs_strlen(text);
cmpl->suffix_completions = NULL;
if (vm->variables_hash != NULL) {
cmpl->node = njs_rbtree_min(vm->variables_hash);
}
}
next:
switch (cmpl->phase) {
case NJS_COMPLETION_VAR:
variables = vm->variables_hash;
if (variables == NULL) {
njs_next_phase(cmpl);
}
while (njs_rbtree_is_there_successor(variables, cmpl->node)) {
var_node = (njs_variable_node_t *) cmpl->node;
lex_entry = njs_lexer_entry(var_node->key);
if (lex_entry == NULL) {
break;
}
cmpl->node = njs_rbtree_node_successor(variables, cmpl->node);
if (lex_entry->name.length >= cmpl->length
&& njs_strncmp(text, lex_entry->name.start, cmpl->length) == 0)
{
return njs_editline(&lex_entry->name);
}
}
njs_next_phase(cmpl);
case NJS_COMPLETION_SUFFIX:
if (cmpl->length == 0) {
njs_next_phase(cmpl);
}
if (cmpl->suffix_completions == NULL) {
/* Getting the longest prefix before a '.' */
p = &text[cmpl->length - 1];
while (p > text && *p != '.') { p--; }
if (*p != '.') {
njs_next_phase(cmpl);
}
expression.start = (u_char *) text;
expression.length = p - text;
cmpl->suffix_completions = njs_vm_completions(vm, &expression);
if (cmpl->suffix_completions == NULL) {
njs_next_phase(cmpl);
}
}
/* Getting the right-most suffix after a '.' */
len = 0;
p = &text[cmpl->length - 1];
while (p > text && *p != '.') {
p--;
len++;
}
p++;
for ( ;; ) {
if (cmpl->index >= cmpl->suffix_completions->items) {
njs_next_phase(cmpl);
}
suffix = njs_completion(cmpl->suffix_completions, cmpl->index++);
if (len != 0 && njs_strncmp(suffix->start, p,
njs_min(len, suffix->length)) != 0)
{
continue;
}
len = suffix->length + (p - text) + 1;
completion = malloc(len);
if (completion == NULL) {
return NULL;
}
njs_sprintf((u_char *) completion, (u_char *) completion + len,
"%*s%V%Z", p - text, text, suffix);
return completion;
}
case NJS_COMPLETION_GLOBAL:
if (cmpl->suffix_completions != NULL) {
/* No global completions if suffixes were found. */
njs_next_phase(cmpl);
}
for ( ;; ) {
if (cmpl->index >= cmpl->completions->items) {
break;
}
suffix = njs_completion(cmpl->completions, cmpl->index++);
if (suffix->start[0] == '.' || suffix->length < cmpl->length) {
continue;
}
if (njs_strncmp(text, suffix->start, cmpl->length) == 0) {
return njs_editline(suffix);
}
}
}
return NULL;
}
#endif
static njs_int_t
njs_ext_console_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t indent)
{
njs_str_t msg;
njs_uint_t n;
n = 1;
while (n < nargs) {
if (njs_vm_value_dump(vm, &msg, njs_argument(args, n), 1, indent)
== NJS_ERROR)
{
return NJS_ERROR;
}
njs_printf("%s", (n != 1) ? " " : "");
njs_print(msg.start, msg.length);
n++;
}
if (nargs > 1) {
njs_printf("\n");
}
njs_set_undefined(&vm->retval);
return NJS_OK;
}
static const njs_value_t njs_default_label = njs_string("default");
static njs_int_t
njs_ext_console_time(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
njs_int_t ret;
njs_str_t name;
njs_value_t *value;
njs_console_t *console;
njs_timelabel_t *label;
njs_lvlhsh_query_t lhq;
console = njs_vm_external(vm, njs_console_proto_id, njs_argument(args, 0));
if (njs_slow_path(console == NULL)) {
njs_type_error(vm, "external value is expected");
return NJS_ERROR;
}
value = njs_arg(args, nargs, 1);
if (njs_slow_path(!njs_is_string(value))) {
if (njs_is_undefined(value)) {
value = njs_value_arg(&njs_default_label);
} else {
ret = njs_value_to_string(vm, value, value);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
}
njs_string_get(value, &name);
label = njs_mp_alloc(vm->mem_pool, sizeof(njs_timelabel_t));
if (njs_slow_path(label == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
lhq.replace = 0;
lhq.key = name;
lhq.key_hash = njs_djb_hash(name.start, name.length);
lhq.value = label;
lhq.pool = vm->mem_pool;
lhq.proto = &njs_timelabel_hash_proto;
ret = njs_lvlhsh_insert(&console->labels, &lhq);
if (njs_fast_path(ret == NJS_OK)) {
/* GC: retain. */
label->name = *value;
} else {
njs_mp_free(vm->mem_pool, label);
if (njs_slow_path(ret == NJS_ERROR)) {
njs_internal_error(vm, "lvlhsh insert failed");
return NJS_ERROR;
}
njs_printf("Timer \"%V\" already exists.\n", &name);
label = lhq.value;
}
label->time = njs_time();
njs_set_undefined(&vm->retval);
return NJS_OK;
}
static njs_int_t
njs_ext_console_time_end(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
uint64_t ns, ms;
njs_int_t ret;
njs_str_t name;
njs_value_t *value;
njs_console_t *console;
njs_timelabel_t *label;
njs_lvlhsh_query_t lhq;
ns = njs_time();
console = njs_vm_external(vm, njs_console_proto_id, njs_argument(args, 0));
if (njs_slow_path(console == NULL)) {
njs_type_error(vm, "external value is expected");
return NJS_ERROR;
}
value = njs_arg(args, nargs, 1);
if (njs_slow_path(!njs_is_string(value))) {
if (njs_is_undefined(value)) {
value = njs_value_arg(&njs_default_label);
} else {
ret = njs_value_to_string(vm, value, value);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
}
njs_string_get(value, &name);
lhq.key = name;
lhq.key_hash = njs_djb_hash(name.start, name.length);
lhq.pool = vm->mem_pool;
lhq.proto = &njs_timelabel_hash_proto;
ret = njs_lvlhsh_delete(&console->labels, &lhq);
if (njs_fast_path(ret == NJS_OK)) {
label = lhq.value;
ns = ns - label->time;
ms = ns / 1000000;
ns = ns % 1000000;
njs_printf("%V: %uL.%06uLms\n", &name, ms, ns);
/* GC: release. */
njs_mp_free(vm->mem_pool, label);
} else {
if (ret == NJS_ERROR) {
njs_internal_error(vm, "lvlhsh delete failed");
return NJS_ERROR;
}
njs_printf("Timer \"%V\" doesn’t exist.\n", &name);
}
njs_set_undefined(&vm->retval);
return NJS_OK;
}
static njs_host_event_t
njs_console_set_timer(njs_external_ptr_t external, uint64_t delay,
njs_vm_event_t vm_event)
{
njs_ev_t *ev;
njs_vm_t *vm;
njs_int_t ret;
njs_console_t *console;
njs_lvlhsh_query_t lhq;
if (delay != 0) {
njs_stderror("njs_console_set_timer(): async timers unsupported\n");
return NULL;
}
console = external;
vm = console->vm;
ev = njs_mp_alloc(vm->mem_pool, sizeof(njs_ev_t));
if (njs_slow_path(ev == NULL)) {
return NULL;
}
ev->vm_event = vm_event;
lhq.key.start = (u_char *) &ev->vm_event;
lhq.key.length = sizeof(njs_vm_event_t);
lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length);
lhq.replace = 0;
lhq.value = ev;
lhq.proto = &lvlhsh_proto;
lhq.pool = vm->mem_pool;
ret = njs_lvlhsh_insert(&console->events, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
return NULL;
}
njs_queue_insert_tail(&console->posted_events, &ev->link);
return (njs_host_event_t) ev;
}
static void
njs_console_clear_timer(njs_external_ptr_t external, njs_host_event_t event)
{
njs_vm_t *vm;
njs_ev_t *ev;
njs_int_t ret;
njs_console_t *console;
njs_lvlhsh_query_t lhq;
ev = event;
console = external;
vm = console->vm;
lhq.key.start = (u_char *) &ev->vm_event;
lhq.key.length = sizeof(njs_vm_event_t);
lhq.key_hash = njs_djb_hash(lhq.key.start, lhq.key.length);
lhq.proto = &lvlhsh_proto;
lhq.pool = vm->mem_pool;
if (ev->link.prev != NULL) {
njs_queue_remove(&ev->link);
}
ret = njs_lvlhsh_delete(&console->events, &lhq);
if (ret != NJS_OK) {
njs_stderror("njs_lvlhsh_delete() failed\n");
}
njs_mp_free(vm->mem_pool, ev);
}
static njs_int_t
njs_timelabel_hash_test(njs_lvlhsh_query_t *lhq, void *data)
{
njs_timelabel_t *label;
njs_str_t str;
label = data;
njs_string_get(&label->name, &str);
if (njs_strstr_eq(&lhq->key, &str)) {
return NJS_OK;
}
return NJS_DECLINED;
}
static njs_int_t
lvlhsh_key_test(njs_lvlhsh_query_t *lhq, void *data)
{
njs_ev_t *ev;
ev = data;
if (memcmp(&ev->vm_event, lhq->key.start, sizeof(njs_vm_event_t)) == 0) {
return NJS_OK;
}
return NJS_DECLINED;
}
static void *
lvlhsh_pool_alloc(void *pool, size_t size)
{
return njs_mp_align(pool, size, size);
}
static void
lvlhsh_pool_free(void *pool, void *p, size_t size)
{
njs_mp_free(pool, p);
}