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