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 "-t\n" 55 " Enable VM execution tracing.\n\n" 56 57 "-S\n" 58 " Enable strict mode.\n\n" 59 60 "-R\n" 61 " Process source file(s) as raw script code (default).\n\n" 62 63 "-T[flag,flag,...]\n" 64 " Process the source file(s) as templates, not as raw script code.\n" 65 " Supported flags: no-lstrip (don't strip leading whitespace before\n" 66 " block tags), no-rtrim (don't strip trailing newline after block tags).\n\n" 67 68 "-D [name=]value\n" 69 " Define global variable. If `name` is omitted, a JSON dictionary is\n" 70 " expected with each property becoming a global variable set to the\n" 71 " corresponding value. If `name` is specified, it is defined as global\n" 72 " variable set to `value` parsed as JSON (or the literal `value` string\n" 73 " if JSON parsing fails).\n\n" 74 75 "-F [name=]path\n" 76 " Like `-D` but reading the value from the file in `path`. The given\n" 77 " file must contain a single, well-formed JSON dictionary.\n\n" 78 79 "-U name\n" 80 " Undefine the given global variable name.\n\n" 81 82 "-l [name=]library\n" 83 " Preload the given `library`, optionally aliased to `name`.\n\n" 84 85 "-L pattern\n" 86 " Append given `pattern` to default library search paths. If the pattern\n" 87 " contains no `*`, it is added twice, once with `/*.so` and once with\n" 88 " `/*.uc` appended to it.\n\n" 89 90 "-c[flag,flag,...]\n" 91 " Compile the given source file(s) to bytecode instead of executing them.\n" 92 " Supported flags: no-interp (omit interpreter line), interp=... (over-\n" 93 " ride interpreter line with ...)\n\n" 94 95 "-o path\n" 96 " Output file path when compiling. If omitted, the compiled byte code\n" 97 " is written to `./uc.out`. Only meaningful in conjunction with `-c`.\n\n" 98 99 "-s\n" 100 " Omit (strip) debug information when compiling files.\n" 101 " Only meaningful in conjunction with `-c`.\n\n", 102 app); 103 } 104 105 106 static int 107 compile(uc_vm_t *vm, uc_source_t *src, FILE *precompile, bool strip, char *interp) 108 { 109 uc_value_t *res = NULL; 110 uc_program_t *program; 111 int rc = 0; 112 char *err; 113 114 program = uc_compile(vm->config, src, &err); 115 116 if (!program) { 117 fprintf(stderr, "%s", err); 118 free(err); 119 rc = -1; 120 goto out; 121 } 122 123 if (precompile) { 124 if (interp) 125 fprintf(precompile, "#!%s\n", interp); 126 127 uc_program_write(program, precompile, !strip); 128 fclose(precompile); 129 goto out; 130 } 131 132 rc = uc_vm_execute(vm, program, &res); 133 134 switch (rc) { 135 case STATUS_OK: 136 rc = 0; 137 break; 138 139 case STATUS_EXIT: 140 rc = (int)ucv_int64_get(res); 141 break; 142 143 case ERROR_COMPILE: 144 rc = -1; 145 break; 146 147 case ERROR_RUNTIME: 148 rc = -2; 149 break; 150 } 151 152 out: 153 uc_program_put(program); 154 ucv_put(res); 155 156 return rc; 157 } 158 159 static uc_source_t * 160 read_stdin(void) 161 { 162 size_t rlen = 0, tlen = 0; 163 char buf[128], *p = NULL; 164 165 if (!stdin_unused) { 166 fprintf(stderr, "The stdin can only be read once\n"); 167 errno = EINVAL; 168 169 return NULL; 170 } 171 172 while (true) { 173 rlen = fread(buf, 1, sizeof(buf), stdin_unused); 174 175 if (rlen == 0) 176 break; 177 178 p = xrealloc(p, tlen + rlen); 179 memcpy(p + tlen, buf, rlen); 180 tlen += rlen; 181 } 182 183 stdin_unused = NULL; 184 185 return uc_source_new_buffer("[stdin]", p, tlen); 186 } 187 188 static void 189 parse_template_modeflags(char *opt, uc_parse_config_t *config) 190 { 191 char *p; 192 193 if (!opt) 194 return; 195 196 for (p = strtok(opt, ", "); p; p = strtok(NULL, ", ")) { 197 if (!strcmp(p, "no-lstrip")) 198 config->lstrip_blocks = false; 199 else if (!strcmp(p, "no-rtrim")) 200 config->trim_blocks = false; 201 else 202 fprintf(stderr, "Unrecognized -T flag \"%s\", ignoring\n", p); 203 } 204 } 205 206 static void 207 parse_compile_flags(char *opt, char **interp) 208 { 209 char *p, *k, *v; 210 211 if (!opt) 212 return; 213 214 for (p = strtok(opt, ","); p; p = strtok(NULL, ",")) { 215 k = p; 216 v = strchr(p, '='); 217 218 if (v) 219 *v++ = 0; 220 221 if (!strcmp(k, "no-interp")) { 222 if (v) 223 fprintf(stderr, "Compile flag \"%s\" takes no value, ignoring\n", k); 224 225 *interp = NULL; 226 } 227 else if (!strcmp(k, "interp")) { 228 if (!v) 229 fprintf(stderr, "Compile flag \"%s\" requires a value, ignoring\n", k); 230 else 231 *interp = v; 232 } 233 else { 234 fprintf(stderr, "Unrecognized -c flag \"%s\", ignoring\n", k); 235 } 236 } 237 } 238 239 static bool 240 parse_define_file(char *opt, uc_value_t *globals) 241 { 242 enum json_tokener_error err = json_tokener_continue; 243 char buf[128], *name = NULL, *p; 244 struct json_tokener *tok; 245 json_object *jso = NULL; 246 size_t rlen; 247 FILE *fp; 248 249 p = strchr(opt, '='); 250 251 if (p) { 252 name = opt; 253 *p++ = 0; 254 } 255 else { 256 p = opt; 257 } 258 259 if (!strcmp(p, "-")) { 260 if (!stdin_unused) { 261 fprintf(stderr, "The stdin can only be read once\n"); 262 263 return false; 264 } 265 266 fp = stdin_unused; 267 stdin_unused = NULL; 268 } 269 else 270 fp = fopen(p, "r"); 271 272 if (!fp) { 273 fprintf(stderr, "Unable to open definition file \"%s\": %s\n", 274 p, strerror(errno)); 275 276 return true; 277 } 278 279 tok = xjs_new_tokener(); 280 281 while (true) { 282 rlen = fread(buf, 1, sizeof(buf), fp); 283 284 if (rlen == 0) 285 break; 286 287 jso = json_tokener_parse_ex(tok, buf, rlen); 288 err = json_tokener_get_error(tok); 289 290 if (err != json_tokener_continue) 291 break; 292 } 293 294 json_tokener_free(tok); 295 fclose(fp); 296 297 if (err != json_tokener_success || !json_object_is_type(jso, json_type_object)) { 298 json_object_put(jso); 299 300 fprintf(stderr, "Invalid definition file \"%s\": %s\n", 301 p, (err != json_tokener_success) 302 ? "JSON parse failure" : "Not a valid JSON object"); 303 304 return false; 305 } 306 307 if (name && *name) { 308 ucv_object_add(globals, name, ucv_from_json(NULL, jso)); 309 } 310 else { 311 json_object_object_foreach(jso, key, val) 312 ucv_object_add(globals, key, ucv_from_json(NULL, val)); 313 } 314 315 json_object_put(jso); 316 317 return true; 318 } 319 320 static bool 321 parse_define_string(char *opt, uc_value_t *globals) 322 { 323 enum json_tokener_error err; 324 struct json_tokener *tok; 325 json_object *jso = NULL; 326 char *name = NULL, *p; 327 bool rv = false; 328 size_t len; 329 330 p = strchr(opt, '='); 331 332 if (p) { 333 name = opt; 334 *p++ = 0; 335 } 336 else { 337 p = opt; 338 } 339 340 len = strlen(p); 341 tok = xjs_new_tokener(); 342 343 /* NB: the len + 1 here is intentional to pass the terminating \0 byte 344 * to the json-c parser. This is required to work-around upstream 345 * issue #681 <https://github.com/json-c/json-c/issues/681> */ 346 jso = json_tokener_parse_ex(tok, p, len + 1); 347 348 err = json_tokener_get_error(tok); 349 350 /* Treat trailing bytes after a parsed value as error */ 351 if (err == json_tokener_success && json_tokener_get_parse_end(tok) < len) 352 err = json_tokener_error_parse_unexpected; 353 354 json_tokener_free(tok); 355 356 if (err != json_tokener_success) { 357 json_object_put(jso); 358 359 if (!name || !*name) { 360 fprintf(stderr, "Invalid -D option value \"%s\": %s\n", 361 p, json_tokener_error_desc(err)); 362 363 return false; 364 } 365 366 ucv_object_add(globals, name, ucv_string_new(p)); 367 368 return true; 369 } 370 371 if (name && *name) { 372 ucv_object_add(globals, name, ucv_from_json(NULL, jso)); 373 rv = true; 374 } 375 else if (json_object_is_type(jso, json_type_object)) { 376 json_object_object_foreach(jso, key, val) 377 ucv_object_add(globals, key, ucv_from_json(NULL, val)); 378 rv = true; 379 } 380 else { 381 fprintf(stderr, "Invalid -D option value \"%s\": Not a valid JSON object\n", p); 382 } 383 384 json_object_put(jso); 385 386 return rv; 387 } 388 389 static void 390 parse_search_path(char *pattern, uc_value_t *globals) 391 { 392 uc_value_t *rsp = ucv_object_get(globals, "REQUIRE_SEARCH_PATH", NULL); 393 size_t len; 394 char *p; 395 396 if (strchr(pattern, '*')) { 397 ucv_array_push(rsp, ucv_string_new(pattern)); 398 return; 399 } 400 401 len = strlen(pattern); 402 403 if (!len) 404 return; 405 406 while (pattern[len-1] == '/') 407 pattern[--len] = 0; 408 409 xasprintf(&p, "%s/*.so", pattern); 410 ucv_array_push(rsp, ucv_string_new(p)); 411 free(p); 412 413 xasprintf(&p, "%s/*.uc", pattern); 414 ucv_array_push(rsp, ucv_string_new(p)); 415 free(p); 416 } 417 418 static bool 419 parse_library_load(char *opt, uc_vm_t *vm) 420 { 421 char *name = NULL, *p; 422 uc_value_t *lib, *ctx; 423 424 p = strchr(opt, '='); 425 426 if (p) { 427 name = opt; 428 *p++ = 0; 429 } 430 else { 431 p = opt; 432 } 433 434 lib = ucv_string_new(p); 435 ctx = uc_vm_invoke(vm, "require", 1, lib); 436 ucv_put(lib); 437 438 if (!ctx) 439 return vm->exception.type == EXCEPTION_NONE; 440 441 ucv_object_add(uc_vm_scope_get(vm), name ? name : p, ctx); 442 443 return true; 444 } 445 446 static const char * 447 appname(const char *argv0) 448 { 449 const char *p; 450 451 if (!argv0) 452 return "ucode"; 453 454 p = strrchr(argv0, '/'); 455 456 if (p) 457 return p + 1; 458 459 return argv0; 460 } 461 462 int 463 main(int argc, char **argv) 464 { 465 char *interp = "/usr/bin/env ucode"; 466 uc_source_t *source = NULL; 467 FILE *precompile = NULL; 468 char *outfile = NULL; 469 bool strip = false; 470 uc_vm_t vm = { 0 }; 471 int opt, rv = 0; 472 const char *app; 473 uc_value_t *o; 474 int fd; 475 476 uc_parse_config_t config = { 477 .strict_declarations = false, 478 .lstrip_blocks = true, 479 .trim_blocks = true, 480 .raw_mode = true 481 }; 482 483 app = appname(argv[0]); 484 485 if (argc == 1) { 486 print_usage(app); 487 goto out; 488 } 489 490 if (!strcmp(app, "utpl")) 491 config.raw_mode = false; 492 else if (!strcmp(app, "ucc")) 493 outfile = "./uc.out"; 494 495 stdin_unused = stdin; 496 497 uc_vm_init(&vm, &config); 498 499 /* load std functions into global scope */ 500 uc_stdlib_load(uc_vm_scope_get(&vm)); 501 502 /* register ARGV array but populate it later (to allow for -U ARGV) */ 503 o = ucv_array_new(&vm); 504 505 ucv_object_add(uc_vm_scope_get(&vm), "ARGV", ucv_get(o)); 506 507 /* parse options */ 508 while ((opt = getopt(argc, argv, "he:tST::RD:F:U:l:L:c::o:s")) != -1) 509 { 510 switch (opt) { 511 case 'h': 512 print_usage(argv[0]); 513 goto out; 514 515 case 'e': 516 source = uc_source_new_buffer("[-e argument]", xstrdup(optarg), strlen(optarg)); 517 break; 518 519 case 't': 520 uc_vm_trace_set(&vm, 1); 521 break; 522 523 case 'S': 524 config.strict_declarations = true; 525 break; 526 527 case 'R': 528 config.raw_mode = true; 529 break; 530 531 case 'T': 532 config.raw_mode = false; 533 parse_template_modeflags(optarg, &config); 534 break; 535 536 case 'D': 537 if (!parse_define_string(optarg, uc_vm_scope_get(&vm))) { 538 rv = 1; 539 goto out; 540 } 541 542 break; 543 544 case 'F': 545 if (!parse_define_file(optarg, uc_vm_scope_get(&vm))) { 546 rv = 1; 547 goto out; 548 } 549 550 break; 551 552 case 'U': 553 ucv_object_delete(uc_vm_scope_get(&vm), optarg); 554 break; 555 556 case 'L': 557 parse_search_path(optarg, uc_vm_scope_get(&vm)); 558 break; 559 560 case 'l': 561 if (!parse_library_load(optarg, &vm)) { 562 rv = 1; 563 goto out; 564 } 565 566 break; 567 568 case 'c': 569 outfile = "./uc.out"; 570 parse_compile_flags(optarg, &interp); 571 break; 572 573 case 's': 574 strip = true; 575 break; 576 577 case 'o': 578 outfile = optarg; 579 break; 580 } 581 } 582 583 if (!source && argv[optind] != NULL) { 584 if (!strcmp(argv[optind], "-")) 585 source = read_stdin(); 586 else 587 source = uc_source_new_file(argv[optind]); 588 589 if (!source) { 590 fprintf(stderr, "Failed to open \"%s\": %s\n", argv[optind], strerror(errno)); 591 rv = 1; 592 goto out; 593 } 594 595 optind++; 596 } 597 598 if (!source) { 599 fprintf(stderr, "Require either -e expression or source file\n"); 600 rv = 1; 601 goto out; 602 } 603 604 if (outfile) { 605 if (!strcmp(outfile, "-")) { 606 precompile = stdout; 607 } 608 else { 609 fd = open(outfile, O_WRONLY|O_CREAT|O_TRUNC, 0777); 610 611 if (fd == -1) { 612 fprintf(stderr, "Unable to open output file %s: %s\n", 613 outfile, strerror(errno)); 614 615 rv = 1; 616 goto out; 617 } 618 619 precompile = fdopen(fd, "wb"); 620 } 621 } 622 623 /* populate ARGV array */ 624 for (; optind < argc; optind++) 625 ucv_array_push(o, ucv_string_new(argv[optind])); 626 627 ucv_put(o); 628 629 rv = compile(&vm, source, precompile, strip, interp); 630 631 out: 632 uc_source_put(source); 633 634 uc_vm_free(&vm); 635 636 return rv; 637 } 638
This page was automatically generated by LXR 0.3.1. • OpenWrt