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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt