1 /* 2 * ***************************************************************************** 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 * 6 * Copyright (c) 2018-2024 Gavin D. Howard and contributors. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions are met: 10 * 11 * * Redistributions of source code must retain the above copyright notice, this 12 * list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright notice, 15 * this list of conditions and the following disclaimer in the documentation 16 * and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 * 30 * ***************************************************************************** 31 * 32 * Code common to all of bc and dc. 33 * 34 */ 35 36 #include <assert.h> 37 #include <ctype.h> 38 #include <errno.h> 39 #include <stdarg.h> 40 #include <string.h> 41 42 #include <signal.h> 43 44 #include <setjmp.h> 45 46 #ifndef _WIN32 47 48 #include <unistd.h> 49 #include <sys/types.h> 50 #include <unistd.h> 51 52 #else // _WIN32 53 54 #define WIN32_LEAN_AND_MEAN 55 #include <windows.h> 56 #include <io.h> 57 58 #endif // _WIN32 59 60 #include <status.h> 61 #include <vector.h> 62 #include <args.h> 63 #include <vm.h> 64 #include <read.h> 65 #include <bc.h> 66 #if BC_ENABLE_LIBRARY 67 #include <library.h> 68 #endif // BC_ENABLE_LIBRARY 69 #if BC_ENABLE_OSSFUZZ 70 #include <ossfuzz.h> 71 #endif // BC_ENABLE_OSSFUZZ 72 73 #if !BC_ENABLE_LIBRARY 74 75 // The actual globals. 76 char output_bufs[BC_VM_BUF_SIZE]; 77 BcVm vm_data; 78 BcVm* vm = &vm_data; 79 80 #endif // !BC_ENABLE_LIBRARY 81 82 #if BC_DEBUG_CODE 83 BC_NORETURN void 84 bc_vm_jmp(const char* f) 85 { 86 #else // BC_DEBUG_CODE 87 BC_NORETURN void 88 bc_vm_jmp(void) 89 { 90 #endif 91 92 #if BC_ENABLE_LIBRARY 93 BcVm* vm = bcl_getspecific(); 94 #endif // BC_ENABLE_LIBRARY 95 96 assert(BC_SIG_EXC(vm)); 97 98 BC_SIG_MAYLOCK; 99 100 #if BC_DEBUG_CODE 101 bc_file_puts(&vm->ferr, bc_flush_none, "Longjmp: "); 102 bc_file_puts(&vm->ferr, bc_flush_none, f); 103 bc_file_putchar(&vm->ferr, bc_flush_none, '\n'); 104 bc_file_flush(&vm->ferr, bc_flush_none); 105 #endif // BC_DEBUG_CODE 106 107 #if BC_DEBUG 108 assert(vm->jmp_bufs.len - (size_t) vm->sig_pop); 109 #endif // BC_DEBUG 110 111 if (vm->jmp_bufs.len == 0) abort(); 112 if (vm->sig_pop) bc_vec_pop(&vm->jmp_bufs); 113 else vm->sig_pop = 1; 114 115 siglongjmp(*((sigjmp_buf*) bc_vec_top(&vm->jmp_bufs)), 1); 116 } 117 118 #if !BC_ENABLE_LIBRARY 119 120 /** 121 * Handles signals. This is the signal handler. 122 * @param sig The signal to handle. 123 */ 124 static void 125 bc_vm_sig(int sig) 126 { 127 #if BC_ENABLE_EDITLINE 128 // Editline needs this to resize the terminal. This also needs to come first 129 // because a resize always needs to happen. 130 if (sig == SIGWINCH) 131 { 132 if (BC_TTY) 133 { 134 el_resize(vm->history.el); 135 136 // If the signal was a SIGWINCH, clear it because we don't need to 137 // print a stack trace in that case. 138 if (vm->sig == SIGWINCH) 139 { 140 vm->sig = 0; 141 } 142 } 143 144 return; 145 } 146 #endif // BC_ENABLE_EDITLINE 147 148 // There is already a signal in flight if this is true. 149 if (vm->status == (sig_atomic_t) BC_STATUS_QUIT || vm->sig != 0) 150 { 151 if (!BC_I || sig != SIGINT) vm->status = BC_STATUS_QUIT; 152 return; 153 } 154 155 // We always want to set this because a stack trace can be printed if we do. 156 vm->sig = sig; 157 158 // Only reset under these conditions; otherwise, quit. 159 if (sig == SIGINT && BC_SIGINT && BC_I) 160 { 161 int err = errno; 162 163 #if BC_ENABLE_EDITLINE 164 // Editline needs this, for some unknown reason. 165 if (write(STDOUT_FILENO, "^C", 2) != (ssize_t) 2) 166 { 167 vm->status = BC_STATUS_ERROR_FATAL; 168 } 169 #endif // BC_ENABLE_EDITLINE 170 171 // Write the message. 172 if (write(STDOUT_FILENO, vm->sigmsg, vm->siglen) != 173 (ssize_t) vm->siglen) 174 { 175 vm->status = BC_STATUS_ERROR_FATAL; 176 } 177 178 errno = err; 179 } 180 else 181 { 182 #if BC_ENABLE_EDITLINE 183 if (write(STDOUT_FILENO, "^C", 2) != (ssize_t) 2) 184 { 185 vm->status = BC_STATUS_ERROR_FATAL; 186 return; 187 } 188 #endif // BC_ENABLE_EDITLINE 189 190 vm->status = BC_STATUS_QUIT; 191 } 192 193 #if BC_ENABLE_LINE_LIB 194 // Readline and Editline need this to actually handle sigints correctly. 195 if (sig == SIGINT && bc_history_inlinelib) 196 { 197 bc_history_inlinelib = 0; 198 siglongjmp(bc_history_jmpbuf, 1); 199 } 200 #endif // BC_ENABLE_LINE_LIB 201 202 assert(vm->jmp_bufs.len); 203 204 // Only jump if signals are not locked. The jump will happen by whoever 205 // unlocks signals. 206 if (!vm->sig_lock) BC_JMP; 207 } 208 209 /** 210 * Sets up signal handling. 211 */ 212 static void 213 bc_vm_sigaction(void) 214 { 215 #ifndef _WIN32 216 217 struct sigaction sa; 218 219 sigemptyset(&sa.sa_mask); 220 sa.sa_flags = BC_ENABLE_EDITLINE ? 0 : SA_NODEFER; 221 222 // This mess is to silence a warning on Clang with regards to glibc's 223 // sigaction handler, which activates the warning here. 224 #if BC_CLANG 225 #pragma clang diagnostic ignored "-Wdisabled-macro-expansion" 226 #endif // BC_CLANG 227 sa.sa_handler = bc_vm_sig; 228 #if BC_CLANG 229 #pragma clang diagnostic warning "-Wdisabled-macro-expansion" 230 #endif // BC_CLANG 231 232 sigaction(SIGTERM, &sa, NULL); 233 sigaction(SIGQUIT, &sa, NULL); 234 sigaction(SIGINT, &sa, NULL); 235 236 #if BC_ENABLE_EDITLINE 237 // Editline needs this to resize the terminal. 238 if (BC_TTY) sigaction(SIGWINCH, &sa, NULL); 239 #endif // BC_ENABLE_EDITLINE 240 241 #if BC_ENABLE_HISTORY 242 if (BC_TTY) sigaction(SIGHUP, &sa, NULL); 243 #endif // BC_ENABLE_HISTORY 244 245 #else // _WIN32 246 247 signal(SIGTERM, bc_vm_sig); 248 signal(SIGINT, bc_vm_sig); 249 250 #endif // _WIN32 251 } 252 253 void 254 bc_vm_info(const char* const help) 255 { 256 BC_SIG_ASSERT_LOCKED; 257 258 // Print the banner. 259 bc_file_printf(&vm->fout, "%s %s\n%s", vm->name, BC_VERSION, bc_copyright); 260 261 // Print the help. 262 if (help != NULL) 263 { 264 bc_file_putchar(&vm->fout, bc_flush_none, '\n'); 265 266 #if BC_ENABLED 267 if (BC_IS_BC) 268 { 269 const char* const banner = BC_DEFAULT_BANNER ? "to" : "to not"; 270 const char* const sigint = BC_DEFAULT_SIGINT_RESET ? "to reset" : 271 "to exit"; 272 const char* const tty = BC_DEFAULT_TTY_MODE ? "enabled" : 273 "disabled"; 274 const char* const prompt = BC_DEFAULT_PROMPT ? "enabled" : 275 "disabled"; 276 const char* const expr = BC_DEFAULT_EXPR_EXIT ? "to exit" : 277 "to not exit"; 278 const char* const clamp = BC_DEFAULT_DIGIT_CLAMP ? "to clamp" : 279 "to not clamp"; 280 281 bc_file_printf(&vm->fout, help, vm->name, vm->name, BC_VERSION, 282 BC_BUILD_TYPE, banner, sigint, tty, prompt, expr, 283 clamp); 284 } 285 #endif // BC_ENABLED 286 287 #if DC_ENABLED 288 if (BC_IS_DC) 289 { 290 const char* const sigint = DC_DEFAULT_SIGINT_RESET ? "to reset" : 291 "to exit"; 292 const char* const tty = DC_DEFAULT_TTY_MODE ? "enabled" : 293 "disabled"; 294 const char* const prompt = DC_DEFAULT_PROMPT ? "enabled" : 295 "disabled"; 296 const char* const expr = DC_DEFAULT_EXPR_EXIT ? "to exit" : 297 "to not exit"; 298 const char* const clamp = DC_DEFAULT_DIGIT_CLAMP ? "to clamp" : 299 "to not clamp"; 300 301 bc_file_printf(&vm->fout, help, vm->name, vm->name, BC_VERSION, 302 BC_BUILD_TYPE, sigint, tty, prompt, expr, clamp); 303 } 304 #endif // DC_ENABLED 305 } 306 307 // Flush. 308 bc_file_flush(&vm->fout, bc_flush_none); 309 } 310 #endif // !BC_ENABLE_LIBRARY 311 312 #if !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK 313 BC_NORETURN 314 #endif // !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK 315 void 316 bc_vm_fatalError(BcErr e) 317 { 318 bc_err(e); 319 #if !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK 320 BC_UNREACHABLE 321 #if !BC_CLANG 322 abort(); 323 #endif // !BC_CLANG 324 #endif // !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK 325 } 326 327 #if BC_ENABLE_LIBRARY 328 BC_NORETURN void 329 bc_vm_handleError(BcErr e) 330 { 331 #if BC_ENABLE_LIBRARY 332 BcVm* vm = bcl_getspecific(); 333 #endif // BC_ENABLE_LIBRARY 334 335 assert(e < BC_ERR_NELEMS); 336 assert(!vm->sig_pop); 337 338 BC_SIG_LOCK; 339 340 // If we have a normal error... 341 if (e <= BC_ERR_MATH_DIVIDE_BY_ZERO) 342 { 343 // Set the error. 344 vm->err = (BclError) (e - BC_ERR_MATH_NEGATIVE + 345 BCL_ERROR_MATH_NEGATIVE); 346 } 347 // Abort if we should. 348 else if (vm->abrt) abort(); 349 else if (e == BC_ERR_FATAL_ALLOC_ERR) vm->err = BCL_ERROR_FATAL_ALLOC_ERR; 350 else vm->err = BCL_ERROR_FATAL_UNKNOWN_ERR; 351 352 BC_JMP; 353 } 354 #else // BC_ENABLE_LIBRARY 355 #if BC_DEBUG 356 void 357 bc_vm_handleError(BcErr e, const char* file, int fline, size_t line, ...) 358 #else // BC_DEBUG 359 void 360 bc_vm_handleError(BcErr e, size_t line, ...) 361 #endif // BC_DEBUG 362 { 363 BcStatus s; 364 BcStatus fout_s; 365 va_list args; 366 uchar id = bc_err_ids[e]; 367 const char* err_type = vm->err_ids[id]; 368 sig_atomic_t lock; 369 370 assert(e < BC_ERR_NELEMS); 371 assert(!vm->sig_pop); 372 373 #if BC_ENABLED 374 // Figure out if the POSIX error should be an error, a warning, or nothing. 375 if (!BC_S && e >= BC_ERR_POSIX_START) 376 { 377 if (BC_W) 378 { 379 // Make sure to not return an error. 380 id = UCHAR_MAX; 381 err_type = vm->err_ids[BC_ERR_IDX_WARN]; 382 } 383 else return; 384 } 385 #endif // BC_ENABLED 386 387 BC_SIG_TRYLOCK(lock); 388 389 // Make sure all of stdout is written first. 390 fout_s = bc_file_flushErr(&vm->fout, bc_flush_err); 391 392 // XXX: Keep the status for later. 393 394 // Print the error message. 395 va_start(args, line); 396 bc_file_putchar(&vm->ferr, bc_flush_none, '\n'); 397 bc_file_puts(&vm->ferr, bc_flush_none, err_type); 398 bc_file_putchar(&vm->ferr, bc_flush_none, ' '); 399 bc_file_vprintf(&vm->ferr, vm->err_msgs[e], args); 400 va_end(args); 401 402 // Print the extra information if we have it. 403 if (BC_NO_ERR(vm->file != NULL)) 404 { 405 // This is the condition for parsing vs runtime. 406 // If line is not 0, it is parsing. 407 if (line) 408 { 409 bc_file_puts(&vm->ferr, bc_flush_none, "\n "); 410 bc_file_puts(&vm->ferr, bc_flush_none, vm->file); 411 bc_file_printf(&vm->ferr, ":%zu\n", line); 412 } 413 else 414 { 415 // Print a stack trace. 416 bc_file_putchar(&vm->ferr, bc_flush_none, '\n'); 417 bc_program_printStackTrace(&vm->prog); 418 } 419 } 420 else 421 { 422 bc_file_putchar(&vm->ferr, bc_flush_none, '\n'); 423 } 424 425 #if BC_DEBUG 426 bc_file_printf(&vm->ferr, "\n %s:%d\n", file, fline); 427 #endif // BC_DEBUG 428 429 bc_file_puts(&vm->ferr, bc_flush_none, "\n"); 430 431 // If flushing to stdout failed, try to print *that* error, as long as that 432 // was not the error already. 433 if (fout_s == BC_STATUS_ERROR_FATAL && e != BC_ERR_FATAL_IO_ERR) 434 { 435 bc_file_putchar(&vm->ferr, bc_flush_none, '\n'); 436 bc_file_puts(&vm->ferr, bc_flush_none, 437 vm->err_ids[bc_err_ids[BC_ERR_FATAL_IO_ERR]]); 438 bc_file_putchar(&vm->ferr, bc_flush_none, ' '); 439 bc_file_puts(&vm->ferr, bc_flush_none, 440 vm->err_msgs[BC_ERR_FATAL_IO_ERR]); 441 } 442 443 s = bc_file_flushErr(&vm->ferr, bc_flush_err); 444 445 #if !BC_ENABLE_MEMCHECK 446 447 // Because this function is called by a BC_NORETURN function when fatal 448 // errors happen, we need to make sure to exit on fatal errors. This will 449 // be faster anyway. This function *cannot jump when a fatal error occurs!* 450 if (BC_ERR(id == BC_ERR_IDX_FATAL || fout_s == BC_STATUS_ERROR_FATAL || 451 s == BC_STATUS_ERROR_FATAL)) 452 { 453 exit((int) BC_STATUS_ERROR_FATAL); 454 } 455 456 #else // !BC_ENABLE_MEMCHECK 457 if (BC_ERR(fout_s == BC_STATUS_ERROR_FATAL)) 458 { 459 vm->status = (sig_atomic_t) fout_s; 460 } 461 else if (BC_ERR(s == BC_STATUS_ERROR_FATAL)) 462 { 463 vm->status = (sig_atomic_t) s; 464 } 465 else 466 #endif // !BC_ENABLE_MEMCHECK 467 { 468 vm->status = (sig_atomic_t) (uchar) (id + 1); 469 } 470 471 // Only jump if there is an error. 472 if (BC_ERR(vm->status)) BC_JMP; 473 474 BC_SIG_TRYUNLOCK(lock); 475 } 476 477 char* 478 bc_vm_getenv(const char* var) 479 { 480 char* ret; 481 482 #ifndef _WIN32 483 ret = getenv(var); 484 #else // _WIN32 485 _dupenv_s(&ret, NULL, var); 486 #endif // _WIN32 487 488 return ret; 489 } 490 491 void 492 bc_vm_getenvFree(char* val) 493 { 494 BC_UNUSED(val); 495 #ifdef _WIN32 496 free(val); 497 #endif // _WIN32 498 } 499 500 /** 501 * Sets a flag from an environment variable and the default. 502 * @param var The environment variable. 503 * @param def The default. 504 * @param flag The flag to set. 505 */ 506 static void 507 bc_vm_setenvFlag(const char* const var, int def, uint16_t flag) 508 { 509 // Get the value. 510 char* val = bc_vm_getenv(var); 511 512 // If there is no value... 513 if (val == NULL) 514 { 515 // Set the default. 516 if (def) vm->flags |= flag; 517 else vm->flags &= ~(flag); 518 } 519 // Parse the value. 520 else if (strtoul(val, NULL, 0)) vm->flags |= flag; 521 else vm->flags &= ~(flag); 522 523 bc_vm_getenvFree(val); 524 } 525 526 /** 527 * Parses the arguments in {B,D]C_ENV_ARGS. 528 * @param env_args_name The environment variable to use. 529 * @param scale A pointer to return the scale that the arguments set, 530 * if any. 531 * @param ibase A pointer to return the ibase that the arguments set, 532 * if any. 533 * @param obase A pointer to return the obase that the arguments set, 534 * if any. 535 */ 536 static void 537 bc_vm_envArgs(const char* const env_args_name, BcBigDig* scale, BcBigDig* ibase, 538 BcBigDig* obase) 539 { 540 char *env_args = bc_vm_getenv(env_args_name), *buf, *start; 541 char instr = '\0'; 542 543 BC_SIG_ASSERT_LOCKED; 544 545 if (env_args == NULL) return; 546 547 // Windows already allocates, so we don't need to. 548 #ifndef _WIN32 549 start = buf = vm->env_args_buffer = bc_vm_strdup(env_args); 550 #else // _WIN32 551 start = buf = vm->env_args_buffer = env_args; 552 #endif // _WIN32 553 554 assert(buf != NULL); 555 556 // Create two buffers for parsing. These need to stay throughout the entire 557 // execution of bc, unfortunately, because of filenames that might be in 558 // there. 559 bc_vec_init(&vm->env_args, sizeof(char*), BC_DTOR_NONE); 560 bc_vec_push(&vm->env_args, &env_args_name); 561 562 // While we haven't reached the end of the args... 563 while (*buf) 564 { 565 // If we don't have whitespace... 566 if (!isspace(*buf)) 567 { 568 // If we have the start of a string... 569 if (*buf == '"' || *buf == '\'') 570 { 571 // Set stuff appropriately. 572 instr = *buf; 573 buf += 1; 574 575 // Check for the empty string. 576 if (*buf == instr) 577 { 578 instr = '\0'; 579 buf += 1; 580 continue; 581 } 582 } 583 584 // Push the pointer to the args buffer. 585 bc_vec_push(&vm->env_args, &buf); 586 587 // Parse the string. 588 while (*buf && 589 ((!instr && !isspace(*buf)) || (instr && *buf != instr))) 590 { 591 buf += 1; 592 } 593 594 // If we did find the end of the string... 595 if (*buf) 596 { 597 if (instr) instr = '\0'; 598 599 // Reset stuff. 600 *buf = '\0'; 601 buf += 1; 602 start = buf; 603 } 604 else if (instr) bc_error(BC_ERR_FATAL_OPTION, 0, start); 605 } 606 // If we have whitespace, eat it. 607 else buf += 1; 608 } 609 610 // Make sure to push a NULL pointer at the end. 611 buf = NULL; 612 bc_vec_push(&vm->env_args, &buf); 613 614 // Parse the arguments. 615 bc_args((int) vm->env_args.len - 1, bc_vec_item(&vm->env_args, 0), false, 616 scale, ibase, obase); 617 } 618 619 /** 620 * Gets the {B,D}C_LINE_LENGTH. 621 * @param var The environment variable to pull it from. 622 * @return The line length. 623 */ 624 static size_t 625 bc_vm_envLen(const char* var) 626 { 627 char* lenv = bc_vm_getenv(var); 628 size_t i, len = BC_NUM_PRINT_WIDTH; 629 int num; 630 631 // Return the default with none. 632 if (lenv == NULL) return len; 633 634 len = strlen(lenv); 635 636 // Figure out if it's a number. 637 for (num = 1, i = 0; num && i < len; ++i) 638 { 639 num = isdigit(lenv[i]); 640 } 641 642 // If it is a number... 643 if (num) 644 { 645 // Parse it and clamp it if needed. 646 len = (size_t) strtol(lenv, NULL, 10); 647 if (len != 0) 648 { 649 len -= 1; 650 if (len < 2 || len >= UINT16_MAX) len = BC_NUM_PRINT_WIDTH; 651 } 652 } 653 // Set the default. 654 else len = BC_NUM_PRINT_WIDTH; 655 656 bc_vm_getenvFree(lenv); 657 658 return len; 659 } 660 #endif // BC_ENABLE_LIBRARY 661 662 void 663 bc_vm_shutdown(void) 664 { 665 BC_SIG_ASSERT_LOCKED; 666 667 #if BC_ENABLE_NLS 668 if (vm->catalog != BC_VM_INVALID_CATALOG) catclose(vm->catalog); 669 #endif // BC_ENABLE_NLS 670 671 #if !BC_ENABLE_LIBRARY 672 #if BC_ENABLE_HISTORY 673 // This must always run to ensure that the terminal is back to normal, i.e., 674 // has raw mode disabled. But we should only do it if we did not have a bad 675 // terminal because history was not initialized if it is a bad terminal. 676 if (BC_TTY && !vm->history.badTerm) bc_history_free(&vm->history); 677 #endif // BC_ENABLE_HISTORY 678 #endif // !BC_ENABLE_LIBRARY 679 680 #if BC_DEBUG || BC_ENABLE_MEMCHECK 681 #if !BC_ENABLE_LIBRARY 682 bc_vec_free(&vm->env_args); 683 free(vm->env_args_buffer); 684 bc_vec_free(&vm->files); 685 bc_vec_free(&vm->exprs); 686 687 if (BC_PARSE_IS_INITED(&vm->read_prs, &vm->prog)) 688 { 689 bc_vec_free(&vm->read_buf); 690 bc_parse_free(&vm->read_prs); 691 } 692 693 bc_parse_free(&vm->prs); 694 bc_program_free(&vm->prog); 695 696 bc_slabvec_free(&vm->slabs); 697 #endif // !BC_ENABLE_LIBRARY 698 699 bc_vm_freeTemps(); 700 #endif // BC_DEBUG || BC_ENABLE_MEMCHECK 701 702 #if !BC_ENABLE_LIBRARY 703 // We always want to flush. 704 bc_file_free(&vm->fout); 705 bc_file_free(&vm->ferr); 706 #endif // !BC_ENABLE_LIBRARY 707 } 708 709 void 710 bc_vm_addTemp(BcDig* num) 711 { 712 #if BC_ENABLE_LIBRARY 713 BcVm* vm = bcl_getspecific(); 714 #endif // BC_ENABLE_LIBRARY 715 716 BC_SIG_ASSERT_LOCKED; 717 718 // If we don't have room, just free. 719 if (vm->temps_len == BC_VM_MAX_TEMPS) free(num); 720 else 721 { 722 // Add to the buffer and length. 723 vm->temps_buf[vm->temps_len] = num; 724 vm->temps_len += 1; 725 } 726 } 727 728 BcDig* 729 bc_vm_takeTemp(void) 730 { 731 #if BC_ENABLE_LIBRARY 732 BcVm* vm = bcl_getspecific(); 733 #endif // BC_ENABLE_LIBRARY 734 735 BC_SIG_ASSERT_LOCKED; 736 737 if (!vm->temps_len) return NULL; 738 739 vm->temps_len -= 1; 740 741 return vm->temps_buf[vm->temps_len]; 742 } 743 744 BcDig* 745 bc_vm_getTemp(void) 746 { 747 #if BC_ENABLE_LIBRARY 748 BcVm* vm = bcl_getspecific(); 749 #endif // BC_ENABLE_LIBRARY 750 751 BC_SIG_ASSERT_LOCKED; 752 753 if (!vm->temps_len) return NULL; 754 755 return vm->temps_buf[vm->temps_len - 1]; 756 } 757 758 void 759 bc_vm_freeTemps(void) 760 { 761 size_t i; 762 #if BC_ENABLE_LIBRARY 763 BcVm* vm = bcl_getspecific(); 764 #endif // BC_ENABLE_LIBRARY 765 766 BC_SIG_ASSERT_LOCKED; 767 768 if (!vm->temps_len) return; 769 770 // Free them all... 771 for (i = 0; i < vm->temps_len; ++i) 772 { 773 free(vm->temps_buf[i]); 774 } 775 776 vm->temps_len = 0; 777 } 778 779 #if !BC_ENABLE_LIBRARY 780 781 size_t 782 bc_vm_numDigits(size_t val) 783 { 784 size_t digits = 0; 785 786 do 787 { 788 digits += 1; 789 val /= 10; 790 } 791 while (val != 0); 792 793 return digits; 794 } 795 796 #endif // !BC_ENABLE_LIBRARY 797 798 inline size_t 799 bc_vm_arraySize(size_t n, size_t size) 800 { 801 size_t res = n * size; 802 803 if (BC_ERR(BC_VM_MUL_OVERFLOW(n, size, res))) 804 { 805 bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR); 806 } 807 808 return res; 809 } 810 811 inline size_t 812 bc_vm_growSize(size_t a, size_t b) 813 { 814 size_t res = a + b; 815 816 if (BC_ERR(res >= SIZE_MAX || res < a)) 817 { 818 bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR); 819 } 820 821 return res; 822 } 823 824 void* 825 bc_vm_malloc(size_t n) 826 { 827 void* ptr; 828 829 BC_SIG_ASSERT_LOCKED; 830 831 ptr = malloc(n); 832 833 if (BC_ERR(ptr == NULL)) 834 { 835 bc_vm_freeTemps(); 836 837 ptr = malloc(n); 838 839 if (BC_ERR(ptr == NULL)) bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR); 840 } 841 842 return ptr; 843 } 844 845 void* 846 bc_vm_realloc(void* ptr, size_t n) 847 { 848 void* temp; 849 850 BC_SIG_ASSERT_LOCKED; 851 852 temp = realloc(ptr, n); 853 854 if (BC_ERR(temp == NULL)) 855 { 856 bc_vm_freeTemps(); 857 858 temp = realloc(ptr, n); 859 860 if (BC_ERR(temp == NULL)) bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR); 861 } 862 863 return temp; 864 } 865 866 char* 867 bc_vm_strdup(const char* str) 868 { 869 char* s; 870 871 BC_SIG_ASSERT_LOCKED; 872 873 s = strdup(str); 874 875 if (BC_ERR(s == NULL)) 876 { 877 bc_vm_freeTemps(); 878 879 s = strdup(str); 880 881 if (BC_ERR(s == NULL)) bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR); 882 } 883 884 return s; 885 } 886 887 #if !BC_ENABLE_LIBRARY 888 void 889 bc_vm_printf(const char* fmt, ...) 890 { 891 va_list args; 892 #if BC_ENABLE_LIBRARY 893 BcVm* vm = bcl_getspecific(); 894 #else // BC_ENABLE_LIBRARY 895 sig_atomic_t lock; 896 #endif // BC_ENABLE_LIBRARY 897 898 BC_SIG_TRYLOCK(lock); 899 900 va_start(args, fmt); 901 bc_file_vprintf(&vm->fout, fmt, args); 902 va_end(args); 903 904 vm->nchars = 0; 905 906 BC_SIG_TRYUNLOCK(lock); 907 } 908 #endif // !BC_ENABLE_LIBRARY 909 910 void 911 bc_vm_putchar(int c, BcFlushType type) 912 { 913 #if BC_ENABLE_LIBRARY 914 BcVm* vm = bcl_getspecific(); 915 bc_vec_pushByte(&vm->out, (uchar) c); 916 #else // BC_ENABLE_LIBRARY 917 bc_file_putchar(&vm->fout, type, (uchar) c); 918 vm->nchars = (c == '\n' ? 0 : vm->nchars + 1); 919 #endif // BC_ENABLE_LIBRARY 920 } 921 922 #if !BC_ENABLE_LIBRARY 923 924 #ifdef __OpenBSD__ 925 926 /** 927 * Aborts with a message. This should never be called because I have carefully 928 * made sure that the calls to pledge() and unveil() are correct, but it's here 929 * just in case. 930 * @param msg The message to print. 931 */ 932 BC_NORETURN static void 933 bc_abortm(const char* msg) 934 { 935 bc_file_puts(&vm->ferr, bc_flush_none, msg); 936 bc_file_puts(&vm->ferr, bc_flush_none, "; this is a bug"); 937 bc_file_flush(&vm->ferr, bc_flush_none); 938 abort(); 939 } 940 941 void 942 bc_pledge(const char* promises, const char* execpromises) 943 { 944 int r = pledge(promises, execpromises); 945 if (r) bc_abortm("pledge() failed"); 946 } 947 948 #if BC_ENABLE_EXTRA_MATH 949 950 /** 951 * A convenience and portability function for OpenBSD's unveil(). 952 * @param path The path. 953 * @param permissions The permissions for the path. 954 */ 955 static void 956 bc_unveil(const char* path, const char* permissions) 957 { 958 int r = unveil(path, permissions); 959 if (r) bc_abortm("unveil() failed"); 960 } 961 962 #endif // BC_ENABLE_EXTRA_MATH 963 964 #else // __OpenBSD__ 965 966 void 967 bc_pledge(const char* promises, const char* execpromises) 968 { 969 BC_UNUSED(promises); 970 BC_UNUSED(execpromises); 971 } 972 973 #if BC_ENABLE_EXTRA_MATH 974 static void 975 bc_unveil(const char* path, const char* permissions) 976 { 977 BC_UNUSED(path); 978 BC_UNUSED(permissions); 979 } 980 #endif // BC_ENABLE_EXTRA_MATH 981 982 #endif // __OpenBSD__ 983 984 /** 985 * Cleans unneeded variables, arrays, functions, strings, and constants when 986 * done executing a line of stdin. This is to prevent memory usage growing 987 * without bound. This is an idea from busybox. 988 */ 989 static void 990 bc_vm_clean(void) 991 { 992 BcVec* fns = &vm->prog.fns; 993 BcFunc* f = bc_vec_item(fns, BC_PROG_MAIN); 994 BcInstPtr* ip = bc_vec_item(&vm->prog.stack, 0); 995 bool good = ((vm->status && vm->status != BC_STATUS_QUIT) || vm->sig != 0); 996 997 BC_SIG_ASSERT_LOCKED; 998 999 // If all is good, go ahead and reset. 1000 if (good) bc_program_reset(&vm->prog); 1001 1002 #if BC_ENABLED 1003 // bc has this extra condition. If it not satisfied, it is in the middle of 1004 // a parse. 1005 if (good && BC_IS_BC) good = !BC_PARSE_NO_EXEC(&vm->prs); 1006 #endif // BC_ENABLED 1007 1008 #if DC_ENABLED 1009 // For dc, it is safe only when all of the results on the results stack are 1010 // safe, which means that they are temporaries or other things that don't 1011 // need strings or constants. 1012 if (BC_IS_DC) 1013 { 1014 size_t i; 1015 1016 good = true; 1017 1018 for (i = 0; good && i < vm->prog.results.len; ++i) 1019 { 1020 BcResult* r = (BcResult*) bc_vec_item(&vm->prog.results, i); 1021 good = BC_VM_SAFE_RESULT(r); 1022 } 1023 } 1024 #endif // DC_ENABLED 1025 1026 // If this condition is true, we can get rid of strings, 1027 // constants, and code. 1028 if (good && vm->prog.stack.len == 1 && ip->idx == f->code.len) 1029 { 1030 // XXX: Nothing can be popped in dc. Deal with it. 1031 1032 #if BC_ENABLED 1033 if (BC_IS_BC) 1034 { 1035 // XXX: you cannot delete strings, functions, or constants in bc. 1036 // Deal with it. 1037 bc_vec_popAll(&f->labels); 1038 } 1039 #endif // BC_ENABLED 1040 1041 bc_vec_popAll(&f->code); 1042 1043 ip->idx = 0; 1044 } 1045 } 1046 1047 /** 1048 * Process a bunch of text. 1049 * @param text The text to process. 1050 * @param mode The mode to process in. 1051 */ 1052 static void 1053 bc_vm_process(const char* text, BcMode mode) 1054 { 1055 // Set up the parser. 1056 bc_parse_text(&vm->prs, text, mode); 1057 1058 while (vm->prs.l.t != BC_LEX_EOF) 1059 { 1060 // Parsing requires a signal lock. We also don't parse everything; we 1061 // want to execute as soon as possible for *everything*. 1062 BC_SIG_LOCK; 1063 vm->parse(&vm->prs); 1064 BC_SIG_UNLOCK; 1065 1066 // Execute if possible. 1067 if (BC_IS_DC || !BC_PARSE_NO_EXEC(&vm->prs)) bc_program_exec(&vm->prog); 1068 1069 assert(BC_IS_DC || vm->prog.results.len == 0); 1070 1071 // Flush in interactive mode. 1072 if (BC_I) bc_file_flush(&vm->fout, bc_flush_save); 1073 } 1074 } 1075 1076 #if BC_ENABLED 1077 1078 /** 1079 * Ends a series of if statements. This is to ensure that full parses happen 1080 * when a file finishes or stdin has no more data. Without this, bc thinks that 1081 * it cannot parse any further. But if we reach the end of a file or stdin has 1082 * no more data, we know we can add an empty else clause. 1083 */ 1084 static void 1085 bc_vm_endif(void) 1086 { 1087 bc_parse_endif(&vm->prs); 1088 bc_program_exec(&vm->prog); 1089 } 1090 1091 #endif // BC_ENABLED 1092 1093 /** 1094 * Processes a file. 1095 * @param file The filename. 1096 */ 1097 static void 1098 bc_vm_file(const char* file) 1099 { 1100 char* data = NULL; 1101 #if BC_ENABLE_LIBRARY 1102 BcVm* vm = bcl_getspecific(); 1103 #endif // BC_ENABLE_LIBRARY 1104 1105 assert(!vm->sig_pop); 1106 1107 vm->mode = BC_MODE_FILE; 1108 1109 // Set up the lexer. 1110 bc_lex_file(&vm->prs.l, file); 1111 1112 BC_SIG_LOCK; 1113 1114 // Read the file. 1115 data = bc_read_file(file); 1116 1117 assert(data != NULL); 1118 1119 BC_SETJMP_LOCKED(vm, err); 1120 1121 BC_SIG_UNLOCK; 1122 1123 // Process it. 1124 bc_vm_process(data, BC_MODE_FILE); 1125 1126 #if BC_ENABLED 1127 // Make sure to end any open if statements. 1128 if (BC_IS_BC) bc_vm_endif(); 1129 #endif // BC_ENABLED 1130 1131 err: 1132 1133 BC_SIG_MAYLOCK; 1134 1135 // Cleanup. 1136 free(data); 1137 bc_vm_clean(); 1138 1139 // bc_program_reset(), called by bc_vm_clean(), resets the status. 1140 // We want it to clear the sig_pop variable in case it was set. 1141 if (vm->status == (sig_atomic_t) BC_STATUS_SUCCESS) BC_LONGJMP_STOP; 1142 1143 BC_LONGJMP_CONT(vm); 1144 } 1145 1146 #if !BC_ENABLE_OSSFUZZ 1147 1148 bool 1149 bc_vm_readLine(bool clear) 1150 { 1151 BcStatus s; 1152 bool good; 1153 1154 BC_SIG_ASSERT_NOT_LOCKED; 1155 1156 // Clear the buffer if desired. 1157 if (clear) bc_vec_empty(&vm->buffer); 1158 1159 // Empty the line buffer. 1160 bc_vec_empty(&vm->line_buf); 1161 1162 if (vm->eof) return false; 1163 1164 do 1165 { 1166 // bc_read_line() must always return either BC_STATUS_SUCCESS or 1167 // BC_STATUS_EOF. Everything else, it and whatever it calls, must jump 1168 // out instead. 1169 s = bc_read_line(&vm->line_buf, ">>> "); 1170 vm->eof = (s == BC_STATUS_EOF); 1171 } 1172 while (s == BC_STATUS_SUCCESS && !vm->eof && vm->line_buf.len < 1); 1173 1174 good = (vm->line_buf.len > 1); 1175 1176 // Concat if we found something. 1177 if (good) bc_vec_concat(&vm->buffer, vm->line_buf.v); 1178 1179 return good; 1180 } 1181 1182 /** 1183 * Processes text from stdin. 1184 */ 1185 static void 1186 bc_vm_stdin(void) 1187 { 1188 bool clear; 1189 1190 #if BC_ENABLE_LIBRARY 1191 BcVm* vm = bcl_getspecific(); 1192 #endif // BC_ENABLE_LIBRARY 1193 1194 clear = true; 1195 vm->mode = BC_MODE_STDIN; 1196 1197 // Set up the lexer. 1198 bc_lex_file(&vm->prs.l, bc_program_stdin_name); 1199 1200 // These are global so that the lexers can access them, but they are 1201 // allocated and freed in this function because they should only be used for 1202 // stdin and expressions (they are used in bc_vm_exprs() as well). So they 1203 // are tied to this function, really. Well, this and bc_vm_readLine(). These 1204 // are the reasons that we have vm->is_stdin to tell the lexers if we are 1205 // reading from stdin. Well, both lexers care. And the reason they care is 1206 // so that if a comment or a string goes across multiple lines, the lexer 1207 // can request more data from stdin until the comment or string is ended. 1208 BC_SIG_LOCK; 1209 bc_vec_init(&vm->buffer, sizeof(uchar), BC_DTOR_NONE); 1210 bc_vec_init(&vm->line_buf, sizeof(uchar), BC_DTOR_NONE); 1211 BC_SETJMP_LOCKED(vm, err); 1212 BC_SIG_UNLOCK; 1213 1214 // This label exists because errors can cause jumps to end up at the err label 1215 // below. If that happens, and the error should be cleared and execution 1216 // continue, then we need to jump back. 1217 restart: 1218 1219 // While we still read data from stdin. 1220 while (bc_vm_readLine(clear)) 1221 { 1222 size_t len = vm->buffer.len - 1; 1223 const char* str = vm->buffer.v; 1224 1225 // We don't want to clear the buffer when the line ends with a backslash 1226 // because a backslash newline is special in bc. 1227 clear = (len < 2 || str[len - 2] != '\\' || str[len - 1] != '\n'); 1228 if (!clear) continue; 1229 1230 // Process the data. 1231 bc_vm_process(vm->buffer.v, BC_MODE_STDIN); 1232 1233 if (vm->eof) break; 1234 else 1235 { 1236 BC_SIG_LOCK; 1237 bc_vm_clean(); 1238 BC_SIG_UNLOCK; 1239 } 1240 } 1241 1242 #if BC_ENABLED 1243 // End the if statements. 1244 if (BC_IS_BC) bc_vm_endif(); 1245 #endif // BC_ENABLED 1246 1247 err: 1248 1249 BC_SIG_MAYLOCK; 1250 1251 // Cleanup. 1252 bc_vm_clean(); 1253 1254 #if !BC_ENABLE_MEMCHECK 1255 assert(vm->status != BC_STATUS_ERROR_FATAL); 1256 1257 vm->status = vm->status == BC_STATUS_QUIT || !BC_I ? vm->status : 1258 BC_STATUS_SUCCESS; 1259 #else // !BC_ENABLE_MEMCHECK 1260 vm->status = vm->status == BC_STATUS_ERROR_FATAL || 1261 vm->status == BC_STATUS_QUIT || !BC_I ? 1262 vm->status : 1263 BC_STATUS_SUCCESS; 1264 #endif // !BC_ENABLE_MEMCHECK 1265 1266 if (!vm->status && !vm->eof) 1267 { 1268 bc_vec_empty(&vm->buffer); 1269 BC_LONGJMP_STOP; 1270 BC_SIG_UNLOCK; 1271 goto restart; 1272 } 1273 1274 #if BC_DEBUG 1275 // Since these are tied to this function, free them here. We only free in 1276 // debug mode because stdin is always the last thing read. 1277 bc_vec_free(&vm->line_buf); 1278 bc_vec_free(&vm->buffer); 1279 #endif // BC_DEBUG 1280 1281 BC_LONGJMP_CONT(vm); 1282 } 1283 1284 #endif // BC_ENABLE_OSSFUZZ 1285 1286 bool 1287 bc_vm_readBuf(bool clear) 1288 { 1289 size_t len = vm->exprs.len - 1; 1290 bool more; 1291 1292 BC_SIG_ASSERT_NOT_LOCKED; 1293 1294 // Clear the buffer if desired. 1295 if (clear) bc_vec_empty(&vm->buffer); 1296 1297 // We want to pop the nul byte off because that's what bc_read_buf() 1298 // expects. 1299 bc_vec_pop(&vm->buffer); 1300 1301 // Read one line of expressions. 1302 more = bc_read_buf(&vm->buffer, vm->exprs.v, &len); 1303 bc_vec_pushByte(&vm->buffer, '\0'); 1304 1305 return more; 1306 } 1307 1308 static void 1309 bc_vm_exprs(void) 1310 { 1311 bool clear; 1312 1313 #if BC_ENABLE_LIBRARY 1314 BcVm* vm = bcl_getspecific(); 1315 #endif // BC_ENABLE_LIBRARY 1316 1317 clear = true; 1318 vm->mode = BC_MODE_EXPRS; 1319 1320 // Prepare the lexer. 1321 bc_lex_file(&vm->prs.l, bc_program_exprs_name); 1322 1323 // We initialize this so that the lexer can access it in the case that it 1324 // needs more data for expressions, such as for a multiline string or 1325 // comment. See the comment on the allocation of vm->buffer above in 1326 // bc_vm_stdin() for more information. 1327 BC_SIG_LOCK; 1328 bc_vec_init(&vm->buffer, sizeof(uchar), BC_DTOR_NONE); 1329 BC_SETJMP_LOCKED(vm, err); 1330 BC_SIG_UNLOCK; 1331 1332 while (bc_vm_readBuf(clear)) 1333 { 1334 size_t len = vm->buffer.len - 1; 1335 const char* str = vm->buffer.v; 1336 1337 // We don't want to clear the buffer when the line ends with a backslash 1338 // because a backslash newline is special in bc. 1339 clear = (len < 2 || str[len - 2] != '\\' || str[len - 1] != '\n'); 1340 if (!clear) continue; 1341 1342 // Process the data. 1343 bc_vm_process(vm->buffer.v, BC_MODE_EXPRS); 1344 } 1345 1346 // If we were not supposed to clear, then we should process everything. This 1347 // makes sure that errors get reported. 1348 if (!clear) bc_vm_process(vm->buffer.v, BC_MODE_EXPRS); 1349 1350 err: 1351 1352 BC_SIG_MAYLOCK; 1353 1354 // Cleanup. 1355 bc_vm_clean(); 1356 1357 // bc_program_reset(), called by bc_vm_clean(), resets the status. 1358 // We want it to clear the sig_pop variable in case it was set. 1359 if (vm->status == (sig_atomic_t) BC_STATUS_SUCCESS) BC_LONGJMP_STOP; 1360 1361 // Since this is tied to this function, free it here. We always free it here 1362 // because bc_vm_stdin() may or may not use it later. 1363 bc_vec_free(&vm->buffer); 1364 1365 BC_LONGJMP_CONT(vm); 1366 } 1367 1368 #if BC_ENABLED 1369 1370 /** 1371 * Loads a math library. 1372 * @param name The name of the library. 1373 * @param text The text of the source code. 1374 */ 1375 static void 1376 bc_vm_load(const char* name, const char* text) 1377 { 1378 bc_lex_file(&vm->prs.l, name); 1379 bc_parse_text(&vm->prs, text, BC_MODE_FILE); 1380 1381 BC_SIG_LOCK; 1382 1383 while (vm->prs.l.t != BC_LEX_EOF) 1384 { 1385 vm->parse(&vm->prs); 1386 } 1387 1388 BC_SIG_UNLOCK; 1389 } 1390 1391 #endif // BC_ENABLED 1392 1393 /** 1394 * Loads the default error messages. 1395 */ 1396 static void 1397 bc_vm_defaultMsgs(void) 1398 { 1399 size_t i; 1400 1401 // Load the error categories. 1402 for (i = 0; i < BC_ERR_IDX_NELEMS + BC_ENABLED; ++i) 1403 { 1404 vm->err_ids[i] = bc_errs[i]; 1405 } 1406 1407 // Load the error messages. 1408 for (i = 0; i < BC_ERR_NELEMS; ++i) 1409 { 1410 vm->err_msgs[i] = bc_err_msgs[i]; 1411 } 1412 } 1413 1414 /** 1415 * Loads the error messages for the locale. If NLS is disabled, this just loads 1416 * the default messages. 1417 */ 1418 static void 1419 bc_vm_gettext(void) 1420 { 1421 #if BC_ENABLE_NLS 1422 uchar id = 0; 1423 int set, msg = 1; 1424 size_t i; 1425 1426 // If no locale, load the defaults. 1427 if (vm->locale == NULL) 1428 { 1429 vm->catalog = BC_VM_INVALID_CATALOG; 1430 bc_vm_defaultMsgs(); 1431 return; 1432 } 1433 1434 vm->catalog = catopen(BC_MAINEXEC, NL_CAT_LOCALE); 1435 1436 // If no catalog, load the defaults. 1437 if (vm->catalog == BC_VM_INVALID_CATALOG) 1438 { 1439 bc_vm_defaultMsgs(); 1440 return; 1441 } 1442 1443 // Load the error categories. 1444 for (set = 1; msg <= BC_ERR_IDX_NELEMS + BC_ENABLED; ++msg) 1445 { 1446 vm->err_ids[msg - 1] = catgets(vm->catalog, set, msg, bc_errs[msg - 1]); 1447 } 1448 1449 i = 0; 1450 id = bc_err_ids[i]; 1451 1452 // Load the error messages. In order to understand this loop, you must know 1453 // the order of messages and categories in the enum and in the locale files. 1454 for (set = id + 2, msg = 1; i < BC_ERR_NELEMS; ++i, ++msg) 1455 { 1456 if (id != bc_err_ids[i]) 1457 { 1458 msg = 1; 1459 id = bc_err_ids[i]; 1460 set = id + 2; 1461 } 1462 1463 vm->err_msgs[i] = catgets(vm->catalog, set, msg, bc_err_msgs[i]); 1464 } 1465 #else // BC_ENABLE_NLS 1466 bc_vm_defaultMsgs(); 1467 #endif // BC_ENABLE_NLS 1468 } 1469 1470 /** 1471 * Starts execution. Really, this is a function of historical accident; it could 1472 * probably be combined with bc_vm_boot(), but I don't care enough. Really, this 1473 * function starts when execution of bc or dc source code starts. 1474 */ 1475 static void 1476 bc_vm_exec(void) 1477 { 1478 size_t i; 1479 #if DC_ENABLED 1480 bool has_file = false; 1481 #endif // DC_ENABLED 1482 1483 #if BC_ENABLED 1484 // Load the math libraries. 1485 if (BC_IS_BC && (vm->flags & BC_FLAG_L)) 1486 { 1487 // Can't allow redefinitions in the builtin library. 1488 vm->no_redefine = true; 1489 1490 bc_vm_load(bc_lib_name, bc_lib); 1491 1492 #if BC_ENABLE_EXTRA_MATH 1493 if (!BC_IS_POSIX) bc_vm_load(bc_lib2_name, bc_lib2); 1494 #endif // BC_ENABLE_EXTRA_MATH 1495 1496 // Make sure to clear this. 1497 vm->no_redefine = false; 1498 1499 // Execute to ensure that all is hunky dory. Without this, scale can be 1500 // set improperly. 1501 bc_program_exec(&vm->prog); 1502 } 1503 #endif // BC_ENABLED 1504 1505 assert(!BC_ENABLE_OSSFUZZ || BC_EXPR_EXIT == 0); 1506 1507 // If there are expressions to execute... 1508 if (vm->exprs.len) 1509 { 1510 // Process the expressions. 1511 bc_vm_exprs(); 1512 1513 // Sometimes, executing expressions means we need to quit. 1514 if (vm->status != BC_STATUS_SUCCESS || 1515 (!vm->no_exprs && vm->exit_exprs && BC_EXPR_EXIT)) 1516 { 1517 return; 1518 } 1519 } 1520 1521 // Process files. 1522 for (i = 0; i < vm->files.len; ++i) 1523 { 1524 char* path = *((char**) bc_vec_item(&vm->files, i)); 1525 if (!strcmp(path, "")) continue; 1526 #if DC_ENABLED 1527 has_file = true; 1528 #endif // DC_ENABLED 1529 bc_vm_file(path); 1530 1531 if (vm->status != BC_STATUS_SUCCESS) return; 1532 } 1533 1534 #if BC_ENABLE_EXTRA_MATH 1535 // These are needed for the pseudo-random number generator. 1536 bc_unveil("/dev/urandom", "r"); 1537 bc_unveil("/dev/random", "r"); 1538 bc_unveil(NULL, NULL); 1539 #endif // BC_ENABLE_EXTRA_MATH 1540 1541 #if BC_ENABLE_HISTORY 1542 1543 // We need to keep tty if history is enabled, and we need to keep rpath for 1544 // the times when we read from /dev/urandom. 1545 if (BC_TTY && !vm->history.badTerm) bc_pledge(bc_pledge_end_history, NULL); 1546 else 1547 #endif // BC_ENABLE_HISTORY 1548 { 1549 bc_pledge(bc_pledge_end, NULL); 1550 } 1551 1552 #if BC_ENABLE_AFL 1553 // This is the thing that makes fuzzing with AFL++ so fast. If you move this 1554 // back, you won't cause any problems, but fuzzing will slow down. If you 1555 // move this forward, you won't fuzz anything because you will be skipping 1556 // the reading from stdin. 1557 __AFL_INIT(); 1558 #endif // BC_ENABLE_AFL 1559 1560 #if BC_ENABLE_OSSFUZZ 1561 1562 if (BC_VM_RUN_STDIN(has_file)) 1563 { 1564 // XXX: Yes, this is a hack to run the fuzzer for OSS-Fuzz, but it 1565 // works. 1566 bc_vm_load("<stdin>", (const char*) bc_fuzzer_data); 1567 } 1568 1569 #else // BC_ENABLE_OSSFUZZ 1570 1571 // Execute from stdin. bc always does. 1572 if (BC_VM_RUN_STDIN(has_file)) bc_vm_stdin(); 1573 1574 #endif // BC_ENABLE_OSSFUZZ 1575 } 1576 1577 BcStatus 1578 bc_vm_boot(int argc, const char* argv[]) 1579 { 1580 int ttyin, ttyout, ttyerr; 1581 bool tty; 1582 const char* const env_len = BC_VM_LINE_LENGTH_STR; 1583 const char* const env_args = BC_VM_ENV_ARGS_STR; 1584 const char* const env_exit = BC_VM_EXPR_EXIT_STR; 1585 const char* const env_clamp = BC_VM_DIGIT_CLAMP_STR; 1586 int env_exit_def = BC_VM_EXPR_EXIT_DEF; 1587 int env_clamp_def = BC_VM_DIGIT_CLAMP_DEF; 1588 BcBigDig scale = BC_NUM_BIGDIG_MAX; 1589 BcBigDig env_scale = BC_NUM_BIGDIG_MAX; 1590 BcBigDig ibase = BC_NUM_BIGDIG_MAX; 1591 BcBigDig env_ibase = BC_NUM_BIGDIG_MAX; 1592 BcBigDig obase = BC_NUM_BIGDIG_MAX; 1593 BcBigDig env_obase = BC_NUM_BIGDIG_MAX; 1594 1595 // We need to know which of stdin, stdout, and stderr are tty's. 1596 ttyin = isatty(STDIN_FILENO); 1597 ttyout = isatty(STDOUT_FILENO); 1598 ttyerr = isatty(STDERR_FILENO); 1599 tty = (ttyin != 0 && ttyout != 0 && ttyerr != 0); 1600 1601 vm->flags |= ttyin ? BC_FLAG_TTYIN : 0; 1602 vm->flags |= tty ? BC_FLAG_TTY : 0; 1603 vm->flags |= ttyin && ttyout ? BC_FLAG_I : 0; 1604 1605 // Set up signals. 1606 bc_vm_sigaction(); 1607 1608 // Initialize some vm stuff. This is separate to make things easier for the 1609 // library. 1610 bc_vm_init(); 1611 1612 // Explicitly set this in case NULL isn't all zeroes. 1613 vm->file = NULL; 1614 1615 // Set the error messages. 1616 bc_vm_gettext(); 1617 1618 #if BC_ENABLE_LINE_LIB 1619 1620 // Initialize the output file buffers. 1621 bc_file_init(&vm->ferr, stderr, true); 1622 bc_file_init(&vm->fout, stdout, false); 1623 1624 // Set the input buffer. 1625 vm->buf = output_bufs; 1626 1627 #else // BC_ENABLE_LINE_LIB 1628 1629 // Initialize the output file buffers. They each take portions of the global 1630 // buffer. stdout gets more because it will probably have more data. 1631 bc_file_init(&vm->ferr, STDERR_FILENO, output_bufs + BC_VM_STDOUT_BUF_SIZE, 1632 BC_VM_STDERR_BUF_SIZE, true); 1633 bc_file_init(&vm->fout, STDOUT_FILENO, output_bufs, BC_VM_STDOUT_BUF_SIZE, 1634 false); 1635 1636 // Set the input buffer to the rest of the global buffer. 1637 vm->buf = output_bufs + BC_VM_STDOUT_BUF_SIZE + BC_VM_STDERR_BUF_SIZE; 1638 #endif // BC_ENABLE_LINE_LIB 1639 1640 // Set the line length by environment variable. 1641 vm->line_len = (uint16_t) bc_vm_envLen(env_len); 1642 1643 bc_vm_setenvFlag(env_exit, env_exit_def, BC_FLAG_EXPR_EXIT); 1644 bc_vm_setenvFlag(env_clamp, env_clamp_def, BC_FLAG_DIGIT_CLAMP); 1645 1646 // Clear the files and expressions vectors, just in case. This marks them as 1647 // *not* allocated. 1648 bc_vec_clear(&vm->files); 1649 bc_vec_clear(&vm->exprs); 1650 1651 #if !BC_ENABLE_LIBRARY 1652 1653 // Initialize the slab vector. 1654 bc_slabvec_init(&vm->slabs); 1655 1656 #endif // !BC_ENABLE_LIBRARY 1657 1658 // Initialize the program and main parser. These have to be in this order 1659 // because the program has to be initialized first, since a pointer to it is 1660 // passed to the parser. 1661 bc_program_init(&vm->prog); 1662 bc_parse_init(&vm->prs, &vm->prog, BC_PROG_MAIN); 1663 1664 // Set defaults. 1665 vm->flags |= BC_TTY ? BC_FLAG_P | BC_FLAG_R : 0; 1666 vm->flags |= BC_I ? BC_FLAG_Q : 0; 1667 1668 #if BC_ENABLED 1669 if (BC_IS_BC) 1670 { 1671 // bc checks this environment variable to see if it should run in 1672 // standard mode. 1673 char* var = bc_vm_getenv("POSIXLY_CORRECT"); 1674 1675 vm->flags |= BC_FLAG_S * (var != NULL); 1676 bc_vm_getenvFree(var); 1677 1678 // Set whether we print the banner or not. 1679 if (BC_I) bc_vm_setenvFlag("BC_BANNER", BC_DEFAULT_BANNER, BC_FLAG_Q); 1680 } 1681 #endif // BC_ENABLED 1682 1683 // Are we in TTY mode? 1684 if (BC_TTY) 1685 { 1686 const char* const env_tty = BC_VM_TTY_MODE_STR; 1687 int env_tty_def = BC_VM_TTY_MODE_DEF; 1688 const char* const env_prompt = BC_VM_PROMPT_STR; 1689 int env_prompt_def = BC_VM_PROMPT_DEF; 1690 1691 // Set flags for TTY mode and prompt. 1692 bc_vm_setenvFlag(env_tty, env_tty_def, BC_FLAG_TTY); 1693 bc_vm_setenvFlag(env_prompt, tty ? env_prompt_def : 0, BC_FLAG_P); 1694 1695 #if BC_ENABLE_HISTORY 1696 // If TTY mode is used, activate history. 1697 if (BC_TTY) bc_history_init(&vm->history); 1698 #endif // BC_ENABLE_HISTORY 1699 } 1700 1701 // Process environment and command-line arguments. 1702 bc_vm_envArgs(env_args, &env_scale, &env_ibase, &env_obase); 1703 bc_args(argc, argv, true, &scale, &ibase, &obase); 1704 1705 // This section is here because we don't want the math library to stomp on 1706 // the user's given value for scale. And we don't want ibase affecting how 1707 // the scale is interpreted. Also, it's sectioned off just for this comment. 1708 { 1709 BC_SIG_UNLOCK; 1710 1711 scale = scale == BC_NUM_BIGDIG_MAX ? env_scale : scale; 1712 #if BC_ENABLED 1713 // Assign the library value only if it is used and no value was set. 1714 scale = scale == BC_NUM_BIGDIG_MAX && BC_L ? 20 : scale; 1715 #endif // BC_ENABLED 1716 obase = obase == BC_NUM_BIGDIG_MAX ? env_obase : obase; 1717 ibase = ibase == BC_NUM_BIGDIG_MAX ? env_ibase : ibase; 1718 1719 if (scale != BC_NUM_BIGDIG_MAX) 1720 { 1721 bc_program_assignBuiltin(&vm->prog, true, false, scale); 1722 } 1723 1724 if (obase != BC_NUM_BIGDIG_MAX) 1725 { 1726 bc_program_assignBuiltin(&vm->prog, false, true, obase); 1727 } 1728 1729 // This is last to avoid it affecting the value of the others. 1730 if (ibase != BC_NUM_BIGDIG_MAX) 1731 { 1732 bc_program_assignBuiltin(&vm->prog, false, false, ibase); 1733 } 1734 1735 BC_SIG_LOCK; 1736 } 1737 1738 // If we are in interactive mode... 1739 if (BC_I) 1740 { 1741 const char* const env_sigint = BC_VM_SIGINT_RESET_STR; 1742 int env_sigint_def = BC_VM_SIGINT_RESET_DEF; 1743 1744 // Set whether we reset on SIGINT or not. 1745 bc_vm_setenvFlag(env_sigint, env_sigint_def, BC_FLAG_SIGINT); 1746 } 1747 1748 #if BC_ENABLED 1749 // Disable global stacks in POSIX mode. 1750 if (BC_IS_POSIX) vm->flags &= ~(BC_FLAG_G); 1751 1752 // Print the banner if allowed. We have to be in bc, in interactive mode, 1753 // and not be quieted by command-line option or environment variable. 1754 if (BC_IS_BC && BC_I && (vm->flags & BC_FLAG_Q)) 1755 { 1756 bc_vm_info(NULL); 1757 bc_file_putchar(&vm->fout, bc_flush_none, '\n'); 1758 bc_file_flush(&vm->fout, bc_flush_none); 1759 } 1760 #endif // BC_ENABLED 1761 1762 BC_SIG_UNLOCK; 1763 1764 // Start executing. 1765 bc_vm_exec(); 1766 1767 BC_SIG_LOCK; 1768 1769 // Exit. 1770 return (BcStatus) vm->status; 1771 } 1772 #endif // !BC_ENABLE_LIBRARY 1773 1774 void 1775 bc_vm_init(void) 1776 { 1777 #if BC_ENABLE_LIBRARY 1778 BcVm* vm = bcl_getspecific(); 1779 #endif // BC_ENABLE_LIBRARY 1780 1781 BC_SIG_ASSERT_LOCKED; 1782 1783 #if !BC_ENABLE_LIBRARY 1784 // Set up the constant zero. 1785 bc_num_setup(&vm->zero, vm->zero_num, BC_VM_ONE_CAP); 1786 #endif // !BC_ENABLE_LIBRARY 1787 1788 // Set up more constant BcNum's. 1789 bc_num_setup(&vm->one, vm->one_num, BC_VM_ONE_CAP); 1790 bc_num_one(&vm->one); 1791 1792 // Set up more constant BcNum's. 1793 // NOLINTNEXTLINE 1794 memcpy(vm->max_num, bc_num_bigdigMax, 1795 bc_num_bigdigMax_size * sizeof(BcDig)); 1796 // NOLINTNEXTLINE 1797 memcpy(vm->max2_num, bc_num_bigdigMax2, 1798 bc_num_bigdigMax2_size * sizeof(BcDig)); 1799 bc_num_setup(&vm->max, vm->max_num, BC_NUM_BIGDIG_LOG10); 1800 bc_num_setup(&vm->max2, vm->max2_num, BC_NUM_BIGDIG_LOG10); 1801 vm->max.len = bc_num_bigdigMax_size; 1802 vm->max2.len = bc_num_bigdigMax2_size; 1803 1804 // Set up the maxes for the globals. 1805 vm->maxes[BC_PROG_GLOBALS_IBASE] = BC_NUM_MAX_POSIX_IBASE; 1806 vm->maxes[BC_PROG_GLOBALS_OBASE] = BC_MAX_OBASE; 1807 vm->maxes[BC_PROG_GLOBALS_SCALE] = BC_MAX_SCALE; 1808 1809 #if BC_ENABLE_EXTRA_MATH 1810 vm->maxes[BC_PROG_MAX_RAND] = ((BcRand) 0) - 1; 1811 #endif // BC_ENABLE_EXTRA_MATH 1812 1813 #if BC_ENABLED 1814 #if !BC_ENABLE_LIBRARY 1815 // bc has a higher max ibase when it's not in POSIX mode. 1816 if (BC_IS_BC && !BC_IS_POSIX) 1817 #endif // !BC_ENABLE_LIBRARY 1818 { 1819 vm->maxes[BC_PROG_GLOBALS_IBASE] = BC_NUM_MAX_IBASE; 1820 } 1821 #endif // BC_ENABLED 1822 } 1823 1824 #if BC_ENABLE_LIBRARY 1825 void 1826 bc_vm_atexit(void) 1827 { 1828 #if BC_DEBUG 1829 #if BC_ENABLE_LIBRARY 1830 BcVm* vm = bcl_getspecific(); 1831 #endif // BC_ENABLE_LIBRARY 1832 #endif // BC_DEBUG 1833 1834 bc_vm_shutdown(); 1835 1836 #if BC_DEBUG 1837 bc_vec_free(&vm->jmp_bufs); 1838 #endif // BC_DEBUG 1839 } 1840 #else // BC_ENABLE_LIBRARY 1841 BcStatus 1842 bc_vm_atexit(BcStatus status) 1843 { 1844 // Set the status correctly. 1845 BcStatus s = BC_STATUS_IS_ERROR(status) ? status : BC_STATUS_SUCCESS; 1846 1847 bc_vm_shutdown(); 1848 1849 #if BC_DEBUG 1850 bc_vec_free(&vm->jmp_bufs); 1851 #endif // BC_DEBUG 1852 1853 return s; 1854 } 1855 #endif // BC_ENABLE_LIBRARY 1856