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