• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/main.c

  1 /*
  2  * Copyright (C) 2020-2021 Jo-Philipp Wich <jo@mein.io>
  3  *
  4  * Permission to use, copy, modify, and/or distribute this software for any
  5  * purpose with or without fee is hereby granted, provided that the above
  6  * copyright notice and this permission notice appear in all copies.
  7  *
  8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15  */
 16 
 17 #include <stdio.h>
 18 #include <stdbool.h>
 19 #include <stdint.h>
 20 #include <unistd.h>
 21 #include <errno.h>
 22 #include <ctype.h>
 23 #include <fcntl.h>
 24 #include <sys/stat.h>
 25 #include <sys/types.h>
 26 
 27 #include "json-c-compat.h"
 28 
 29 #include "ucode/compiler.h"
 30 #include "ucode/lexer.h"
 31 #include "ucode/lib.h"
 32 #include "ucode/vm.h"
 33 #include "ucode/source.h"
 34 #include "ucode/program.h"
 35 
 36 static FILE *stdin_unused;
 37 
 38 static void
 39 print_usage(const char *app)
 40 {
 41         printf(
 42         "Usage:\n"
 43         "  %1$s -h\n"
 44         "  %1$s -e \"expression\"\n"
 45         "  %1$s input.uc [input2.uc ...]\n"
 46         "  %1$s -c [-s] [-o output.uc] input.uc [input2.uc ...]\n\n"
 47 
 48         "-h\n"
 49         "  Help display this help.\n\n"
 50 
 51         "-e \"expression\"\n"
 52         "  Execute the given expression as ucode program.\n\n"
 53 
 54         "-p \"expression\"\n"
 55         "  Like `-e` but print the result of expression.\n\n"
 56 
 57         "-t\n"
 58         "  Enable VM execution tracing.\n\n"
 59 
 60         "-g interval\n"
 61         "  Perform periodic garbage collection every `interval` object\n"
 62         "  allocations.\n\n"
 63 
 64         "-S\n"
 65         "  Enable strict mode.\n\n"
 66 
 67         "-R\n"
 68         "  Process source file(s) as raw script code (default).\n\n"
 69 
 70         "-T[flag,flag,...]\n"
 71         "  Process the source file(s) as templates, not as raw script code.\n"
 72         "  Supported flags: no-lstrip (don't strip leading whitespace before\n"
 73         "  block tags), no-rtrim (don't strip trailing newline after block tags).\n\n"
 74 
 75         "-D [name=]value\n"
 76         "  Define global variable. If `name` is omitted, a JSON dictionary is\n"
 77         "  expected with each property becoming a global variable set to the\n"
 78         "  corresponding value. If `name` is specified, it is defined as global\n"
 79         "  variable set to `value` parsed as JSON (or the literal `value` string\n"
 80         "  if JSON parsing fails).\n\n"
 81 
 82         "-F [name=]path\n"
 83         "  Like `-D` but reading the value from the file in `path`. The given\n"
 84         "  file must contain a single, well-formed JSON dictionary.\n\n"
 85 
 86         "-U name\n"
 87         "  Undefine the given global variable name.\n\n"
 88 
 89         "-l [name=]library\n"
 90         "  Preload the given `library`, optionally aliased to `name`.\n\n"
 91 
 92         "-L pattern\n"
 93         "  Prepend given `pattern` to default library search paths. If the pattern\n"
 94         "  contains no `*`, it is added twice, once with `/*.so` and once with\n"
 95         "  `/*.uc` appended to it.\n\n"
 96 
 97         "-c[flag,flag,...]\n"
 98         "  Compile the given source file(s) to bytecode instead of executing them.\n"
 99         "  Supported flags: no-interp (omit interpreter line), interp=... (over-\n"
100         "  ride interpreter line with ...), dynlink=... (force import from ... to\n"
101         "  be treated as shared extensions loaded at runtime).\n\n"
102 
103         "-o path\n"
104         "  Output file path when compiling. If omitted, the compiled byte code\n"
105         "  is written to `./uc.out`. Only meaningful in conjunction with `-c`.\n\n"
106 
107         "-s\n"
108         "  Omit (strip) debug information when compiling files.\n"
109         "  Only meaningful in conjunction with `-c`.\n\n",
110                 app);
111 }
112 
113 
114 static int
115 compile(uc_vm_t *vm, uc_source_t *src, FILE *precompile, bool strip, char *interp, bool print_result)
116 {
117         uc_value_t *res = NULL;
118         uc_program_t *program;
119         int rc = 0;
120         char *err;
121 
122         program = uc_compile(vm->config, src, &err);
123 
124         if (!program) {
125                 fprintf(stderr, "%s", err);
126                 free(err);
127                 rc = -1;
128                 goto out;
129         }
130 
131         if (precompile) {
132                 if (interp)
133                         fprintf(precompile, "#!%s\n", interp);
134 
135                 uc_program_write(program, precompile, !strip);
136                 fclose(precompile);
137                 goto out;
138         }
139 
140         if (vm->gc_interval)
141                 uc_vm_gc_start(vm, vm->gc_interval);
142 
143         rc = uc_vm_execute(vm, program, &res);
144 
145         switch (rc) {
146         case STATUS_OK:
147                 if (print_result) {
148                         if (ucv_type(res) == UC_STRING) {
149                                 fwrite(ucv_string_get(res), ucv_string_length(res), 1, stdout);
150                         }
151                         else {
152                                 uc_stringbuf_t *pb = xprintbuf_new();
153 
154                                 ucv_to_stringbuf_formatted(vm, pb, res, 0, '\t', 1);
155                                 fwrite(pb->buf, pb->bpos, 1, stdout);
156                                 printbuf_free(pb);
157                         }
158                 }
159 
160                 rc = 0;
161                 break;
162 
163         case STATUS_EXIT:
164                 rc = (int)ucv_int64_get(res);
165                 break;
166 
167         case ERROR_COMPILE:
168                 rc = -1;
169                 break;
170 
171         case ERROR_RUNTIME:
172                 rc = -2;
173                 break;
174         }
175 
176 out:
177         uc_program_put(program);
178         ucv_put(res);
179 
180         return rc;
181 }
182 
183 static uc_source_t *
184 read_stdin(void)
185 {
186         size_t rlen = 0, tlen = 0;
187         char buf[128], *p = NULL;
188 
189         if (!stdin_unused) {
190                 fprintf(stderr, "The stdin can only be read once\n");
191                 errno = EINVAL;
192 
193                 return NULL;
194         }
195 
196         while (true) {
197                 rlen = fread(buf, 1, sizeof(buf), stdin_unused);
198 
199                 if (rlen == 0)
200                         break;
201 
202                 p = xrealloc(p, tlen + rlen);
203                 memcpy(p + tlen, buf, rlen);
204                 tlen += rlen;
205         }
206 
207         stdin_unused = NULL;
208 
209         /* On empty stdin, provide a dummy buffer and ensure that it is
210          * at least one byte long, due to
211          * https://github.com/google/sanitizers/issues/627 */
212         if (p == NULL) {
213                 p = xstrdup("\n");
214                 tlen = 1;
215         }
216 
217         return uc_source_new_buffer("[stdin]", p, tlen);
218 }
219 
220 static void
221 parse_template_modeflags(char *opt, uc_parse_config_t *config)
222 {
223         char *p;
224 
225         if (!opt)
226                 return;
227 
228         for (p = strtok(opt, ", "); p; p = strtok(NULL, ", ")) {
229                 if (!strcmp(p, "no-lstrip"))
230                         config->lstrip_blocks = false;
231                 else if (!strcmp(p, "no-rtrim"))
232                         config->trim_blocks = false;
233                 else
234                         fprintf(stderr, "Unrecognized -T flag \"%s\", ignoring\n", p);
235         }
236 }
237 
238 static void
239 parse_compile_flags(char *opt, char **interp, uc_search_path_t *dynlink_list)
240 {
241         char *p, *k, *v;
242 
243         if (!opt)
244                 return;
245 
246         for (p = strtok(opt, ","); p; p = strtok(NULL, ",")) {
247                 k = p;
248                 v = strchr(p, '=');
249 
250                 if (v)
251                         *v++ = 0;
252 
253                 if (!strcmp(k, "no-interp")) {
254                         if (v)
255                                 fprintf(stderr, "Compile flag \"%s\" takes no value, ignoring\n", k);
256 
257                         *interp = NULL;
258                 }
259                 else if (!strcmp(k, "interp")) {
260                         if (!v)
261                                 fprintf(stderr, "Compile flag \"%s\" requires a value, ignoring\n", k);
262                         else
263                                 *interp = v;
264                 }
265                 else if (!strcmp(k, "dynlink")) {
266                         if (!v)
267                                 fprintf(stderr, "Compile flag \"%s\" requires a value, ignoring\n", k);
268                         else
269                                 uc_vector_push(dynlink_list, v);
270                 }
271                 else {
272                         fprintf(stderr, "Unrecognized -c flag \"%s\", ignoring\n", k);
273                 }
274         }
275 }
276 
277 static bool
278 parse_define_file(char *opt, uc_value_t *globals)
279 {
280         enum json_tokener_error err = json_tokener_continue;
281         char buf[128], *name = NULL, *p;
282         struct json_tokener *tok;
283         json_object *jso = NULL;
284         size_t rlen;
285         FILE *fp;
286 
287         p = strchr(opt, '=');
288 
289         if (p) {
290                 name = opt;
291                 *p++ = 0;
292         }
293         else {
294                 p = opt;
295         }
296 
297         if (!strcmp(p, "-")) {
298                 if (!stdin_unused) {
299                         fprintf(stderr, "The stdin can only be read once\n");
300 
301                         return false;
302                 }
303 
304                 fp = stdin_unused;
305                 stdin_unused = NULL;
306         }
307         else
308                 fp = fopen(p, "r");
309 
310         if (!fp) {
311                 fprintf(stderr, "Unable to open definition file \"%s\": %s\n",
312                         p, strerror(errno));
313 
314                 return true;
315         }
316 
317         tok = xjs_new_tokener();
318 
319         while (true) {
320                 rlen = fread(buf, 1, sizeof(buf), fp);
321 
322                 if (rlen == 0)
323                         break;
324 
325                 jso = json_tokener_parse_ex(tok, buf, rlen);
326                 err = json_tokener_get_error(tok);
327 
328                 if (err != json_tokener_continue)
329                         break;
330         }
331 
332         json_tokener_free(tok);
333         fclose(fp);
334 
335         if (err != json_tokener_success || !json_object_is_type(jso, json_type_object)) {
336                 json_object_put(jso);
337 
338                 fprintf(stderr, "Invalid definition file \"%s\": %s\n",
339                         p, (err != json_tokener_success)
340                              ? "JSON parse failure" : "Not a valid JSON object");
341 
342                 return false;
343         }
344 
345         if (name && *name) {
346                 ucv_object_add(globals, name, ucv_from_json(NULL, jso));
347         }
348         else {
349                 json_object_object_foreach(jso, key, val)
350                         ucv_object_add(globals, key, ucv_from_json(NULL, val));
351         }
352 
353         json_object_put(jso);
354 
355         return true;
356 }
357 
358 static bool
359 parse_define_string(char *opt, uc_value_t *globals)
360 {
361         enum json_tokener_error err;
362         struct json_tokener *tok;
363         json_object *jso = NULL;
364         char *name = NULL, *p;
365         bool rv = false;
366         size_t len;
367 
368         p = strchr(opt, '=');
369 
370         if (p) {
371                 name = opt;
372                 *p++ = 0;
373         }
374         else {
375                 p = opt;
376         }
377 
378         len = strlen(p);
379         tok = xjs_new_tokener();
380 
381         /* NB: the len + 1 here is intentional to pass the terminating \0 byte
382          * to the json-c parser. This is required to work-around upstream
383          * issue #681 <https://github.com/json-c/json-c/issues/681> */
384         jso = json_tokener_parse_ex(tok, p, len + 1);
385 
386         err = json_tokener_get_error(tok);
387 
388         /* Treat trailing bytes after a parsed value as error */
389         if (err == json_tokener_success && json_tokener_get_parse_end(tok) < len)
390                 err = json_tokener_error_parse_unexpected;
391 
392         json_tokener_free(tok);
393 
394         if (err != json_tokener_success) {
395                 json_object_put(jso);
396 
397                 if (!name || !*name) {
398                         fprintf(stderr, "Invalid -D option value \"%s\": %s\n",
399                                 p, json_tokener_error_desc(err));
400 
401                         return false;
402                 }
403 
404                 ucv_object_add(globals, name, ucv_string_new(p));
405 
406                 return true;
407         }
408 
409         if (name && *name) {
410                 ucv_object_add(globals, name, ucv_from_json(NULL, jso));
411                 rv = true;
412         }
413         else if (json_object_is_type(jso, json_type_object)) {
414                 json_object_object_foreach(jso, key, val)
415                         ucv_object_add(globals, key, ucv_from_json(NULL, val));
416                 rv = true;
417         }
418         else {
419                 fprintf(stderr, "Invalid -D option value \"%s\": Not a valid JSON object\n", p);
420         }
421 
422         json_object_put(jso);
423 
424         return rv;
425 }
426 
427 static void
428 parse_search_path(char *pattern, uc_parse_config_t *config)
429 {
430         size_t len;
431         char *p;
432 
433         if (strchr(pattern, '*')) {
434                 uc_search_path_add(&config->module_search_path, pattern);
435                 return;
436         }
437 
438         len = strlen(pattern);
439 
440         if (!len)
441                 return;
442 
443         while (pattern[len-1] == '/')
444                 pattern[--len] = 0;
445 
446         xasprintf(&p, "%s/*.so", pattern);
447         uc_search_path_add(&config->module_search_path, p);
448         free(p);
449 
450         xasprintf(&p, "%s/*.uc", pattern);
451         uc_search_path_add(&config->module_search_path, p);
452         free(p);
453 }
454 
455 static bool
456 parse_library_load(char *opt, uc_vm_t *vm)
457 {
458         char *name = NULL, *p;
459         uc_value_t *lib, *ctx;
460 
461         p = strchr(opt, '=');
462 
463         if (p) {
464                 name = opt;
465                 *p++ = 0;
466         }
467         else {
468                 p = opt;
469         }
470 
471         lib = ucv_string_new(p);
472         ctx = uc_vm_invoke(vm, "require", 1, lib);
473         ucv_put(lib);
474 
475         if (!ctx)
476                 return vm->exception.type == EXCEPTION_NONE;
477 
478         ucv_object_add(uc_vm_scope_get(vm), name ? name : p, ctx);
479 
480         return true;
481 }
482 
483 static const char *
484 appname(const char *argv0)
485 {
486         const char *p;
487 
488         if (!argv0)
489                 return "ucode";
490 
491         p = strrchr(argv0, '/');
492 
493         if (p)
494                 return p + 1;
495 
496         return argv0;
497 }
498 
499 int
500 main(int argc, char **argv)
501 {
502         const char *optspec = "he:p:tg:ST::RD:F:U:l:L:c::o:s";
503         bool strip = false, print_result = false;
504         char *interp = "/usr/bin/env ucode";
505         uc_source_t *source = NULL;
506         FILE *precompile = NULL;
507         char *outfile = NULL;
508         uc_vm_t vm = { 0 };
509         const char *argv0;
510         int opt, rv = 0;
511         const char *app;
512         uc_value_t *o;
513         int fd;
514 
515         uc_parse_config_t config = {
516                 .strict_declarations = false,
517                 .lstrip_blocks = true,
518                 .trim_blocks = true,
519                 .raw_mode = true,
520                 .setup_signal_handlers = true
521         };
522 
523         app = appname(argv[0]);
524 
525         if (argc == 1) {
526                 print_usage(app);
527                 goto out;
528         }
529 
530         if (!strcmp(app, "utpl"))
531                 config.raw_mode = false;
532         else if (!strcmp(app, "ucc"))
533                 outfile = "./uc.out";
534 
535         stdin_unused = stdin;
536 
537         /* parse options iteration 1: parse config related options */
538         while ((opt = getopt(argc, argv, optspec)) != -1)
539         {
540                 switch (opt) {
541                 case 'L':
542                         parse_search_path(optarg, &config);
543                         break;
544 
545                 case 'S':
546                         config.strict_declarations = true;
547                         break;
548 
549                 case 'R':
550                         config.raw_mode = true;
551                         break;
552 
553                 case 'T':
554                         config.raw_mode = false;
555                         parse_template_modeflags(optarg, &config);
556                         break;
557                 }
558         }
559 
560         uc_search_path_init(&config.module_search_path);
561 
562         argv0 = argv[optind];
563         optind = 1;
564 
565         uc_vm_init(&vm, &config);
566 
567         /* load std functions into global scope */
568         uc_stdlib_load(uc_vm_scope_get(&vm));
569 
570         /* register ARGV array but populate it later (to allow for -U ARGV) */
571         o = ucv_array_new(&vm);
572 
573         ucv_object_add(uc_vm_scope_get(&vm), "ARGV", ucv_get(o));
574         if (argv0)
575                 ucv_object_add(uc_vm_scope_get(&vm), "SCRIPT_NAME", ucv_string_new(argv0));
576 
577         /* parse options iteration 2: process remaining options */
578         while ((opt = getopt(argc, argv, optspec)) != -1)
579         {
580                 switch (opt) {
581                 case 'h':
582                         print_usage(argv[0]);
583                         goto out;
584 
585                 case 'e':
586                         source = uc_source_new_buffer("[-e argument]", xstrdup(optarg), strlen(optarg));
587                         break;
588 
589                 case 'p':
590                         source = uc_source_new_buffer("[-p argument]", xstrdup(optarg), strlen(optarg));
591                         print_result = true;
592                         break;
593 
594                 case 't':
595                         uc_vm_trace_set(&vm, 1);
596                         break;
597 
598                 case 'g':
599                         vm.gc_interval = atoi(optarg);
600                         break;
601 
602                 case 'D':
603                         if (!parse_define_string(optarg, uc_vm_scope_get(&vm))) {
604                                 rv = 1;
605                                 goto out;
606                         }
607 
608                         break;
609 
610                 case 'F':
611                         if (!parse_define_file(optarg, uc_vm_scope_get(&vm))) {
612                                 rv = 1;
613                                 goto out;
614                         }
615 
616                         break;
617 
618                 case 'U':
619                         ucv_object_delete(uc_vm_scope_get(&vm), optarg);
620                         break;
621 
622                 case 'l':
623                         if (!parse_library_load(optarg, &vm)) {
624                                 rv = 1;
625                                 goto out;
626                         }
627 
628                         break;
629 
630                 case 'c':
631                         outfile = "./uc.out";
632                         parse_compile_flags(optarg, &interp, &config.force_dynlink_list);
633                         break;
634 
635                 case 's':
636                         strip = true;
637                         break;
638 
639                 case 'o':
640                         outfile = optarg;
641                         break;
642                 }
643         }
644 
645         if (!source && argv[optind] != NULL) {
646                 if (!strcmp(argv[optind], "-"))
647                         source = read_stdin();
648                 else
649                         source = uc_source_new_file(argv[optind]);
650 
651                 if (!source) {
652                         fprintf(stderr, "Failed to open \"%s\": %s\n", argv[optind], strerror(errno));
653                         rv = 1;
654                         goto out;
655                 }
656 
657                 optind++;
658         }
659 
660         if (!source) {
661                 fprintf(stderr, "Require either -e/-p expression or source file\n");
662                 rv = 1;
663                 goto out;
664         }
665 
666         if (outfile) {
667                 if (!strcmp(outfile, "-")) {
668                         precompile = stdout;
669                 }
670                 else {
671                         fd = open(outfile, O_WRONLY|O_CREAT|O_TRUNC, 0777);
672 
673                         if (fd == -1) {
674                                 fprintf(stderr, "Unable to open output file %s: %s\n",
675                                         outfile, strerror(errno));
676 
677                                 rv = 1;
678                                 goto out;
679                         }
680 
681                         precompile = fdopen(fd, "wb");
682                 }
683         }
684 
685         /* populate ARGV array */
686         for (; optind < argc; optind++)
687                 ucv_array_push(o, ucv_string_new(argv[optind]));
688 
689         ucv_put(o);
690 
691         rv = compile(&vm, source, precompile, strip, interp, print_result);
692 
693 out:
694         uc_search_path_free(&config.module_search_path);
695         uc_vector_clear(&config.force_dynlink_list);
696 
697         uc_source_put(source);
698 
699         uc_vm_free(&vm);
700 
701         return rv;
702 }
703 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt