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