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