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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt