xref: /freebsd/contrib/bc/src/vm.c (revision f55bd0e5798dd17251302f73904da76bdfd257b9)
1 /*
2  * *****************************************************************************
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  *
6  * Copyright (c) 2018-2021 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 <sys/types.h>
49 #include <unistd.h>
50 
51 #else // _WIN32
52 
53 #define WIN32_LEAN_AND_MEAN
54 #include <windows.h>
55 #include <io.h>
56 
57 #endif // _WIN32
58 
59 #include <status.h>
60 #include <vector.h>
61 #include <args.h>
62 #include <vm.h>
63 #include <read.h>
64 #include <bc.h>
65 
66 char output_bufs[BC_VM_BUF_SIZE];
67 BcVm vm;
68 
69 #if BC_DEBUG_CODE
70 BC_NORETURN void bc_vm_jmp(const char* f) {
71 #else // BC_DEBUG_CODE
72 BC_NORETURN void bc_vm_jmp(void) {
73 #endif
74 
75 	assert(BC_SIG_EXC);
76 
77 	BC_SIG_MAYLOCK;
78 
79 #if BC_DEBUG_CODE
80 	bc_file_puts(&vm.ferr, bc_flush_none, "Longjmp: ");
81 	bc_file_puts(&vm.ferr, bc_flush_none, f);
82 	bc_file_putchar(&vm.ferr, bc_flush_none, '\n');
83 	bc_file_flush(&vm.ferr, bc_flush_none);
84 #endif // BC_DEBUG_CODE
85 
86 #ifndef NDEBUG
87 	assert(vm.jmp_bufs.len - (size_t) vm.sig_pop);
88 #endif // NDEBUG
89 
90 	if (vm.jmp_bufs.len == 0) abort();
91 	if (vm.sig_pop) bc_vec_pop(&vm.jmp_bufs);
92 	else vm.sig_pop = 1;
93 
94 	siglongjmp(*((sigjmp_buf*) bc_vec_top(&vm.jmp_bufs)), 1);
95 }
96 
97 #if !BC_ENABLE_LIBRARY
98 static void bc_vm_sig(int sig) {
99 
100 	// There is already a signal in flight.
101 	if (vm.status == (sig_atomic_t) BC_STATUS_QUIT || vm.sig) {
102 		if (!BC_TTY || sig != SIGINT) vm.status = BC_STATUS_QUIT;
103 		return;
104 	}
105 
106 	if (BC_TTY && sig == SIGINT) {
107 
108 		int err = errno;
109 
110 		if (write(STDOUT_FILENO, vm.sigmsg, vm.siglen) != (ssize_t) vm.siglen)
111 			vm.status = BC_STATUS_ERROR_FATAL;
112 		else vm.sig = 1;
113 
114 		errno = err;
115 	}
116 	else vm.status = BC_STATUS_QUIT;
117 
118 	assert(vm.jmp_bufs.len);
119 
120 	if (!vm.sig_lock) BC_VM_JMP;
121 }
122 
123 static void bc_vm_sigaction(void) {
124 #ifndef _WIN32
125 
126 	struct sigaction sa;
127 
128 	sigemptyset(&sa.sa_mask);
129 	sa.sa_handler = bc_vm_sig;
130 	sa.sa_flags = SA_NODEFER;
131 
132 	sigaction(SIGTERM, &sa, NULL);
133 	sigaction(SIGQUIT, &sa, NULL);
134 	sigaction(SIGINT, &sa, NULL);
135 
136 #if BC_ENABLE_HISTORY
137 	if (BC_TTY) sigaction(SIGHUP, &sa, NULL);
138 #endif // BC_ENABLE_HISTORY
139 
140 #else // _WIN32
141 
142 	signal(SIGTERM, bc_vm_sig);
143 
144 #endif // _WIN32
145 }
146 
147 void bc_vm_info(const char* const help) {
148 
149 	BC_SIG_ASSERT_LOCKED;
150 
151 	bc_file_puts(&vm.fout, bc_flush_none, vm.name);
152 	bc_file_putchar(&vm.fout, bc_flush_none, ' ');
153 	bc_file_puts(&vm.fout, bc_flush_none, BC_VERSION);
154 	bc_file_putchar(&vm.fout, bc_flush_none, '\n');
155 	bc_file_puts(&vm.fout, bc_flush_none, bc_copyright);
156 
157 	if (help) {
158 		bc_file_putchar(&vm.fout, bc_flush_none, '\n');
159 		bc_file_printf(&vm.fout, help, vm.name, vm.name,
160 		               BC_VERSION, BC_BUILD_TYPE);
161 	}
162 
163 	bc_file_flush(&vm.fout, bc_flush_err);
164 }
165 #endif // !BC_ENABLE_LIBRARY
166 
167 #if !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK
168 BC_NORETURN
169 #endif // !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK
170 void bc_vm_fatalError(BcErr e) {
171 	bc_vm_err(e);
172 #if !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK
173 	abort();
174 #endif // !BC_ENABLE_LIBRARY && !BC_ENABLE_MEMCHECK
175 }
176 
177 #if BC_ENABLE_LIBRARY
178 void bc_vm_handleError(BcErr e) {
179 
180 	assert(e < BC_ERR_NELEMS);
181 	assert(!vm.sig_pop);
182 
183 	BC_SIG_LOCK;
184 
185 	if (e <= BC_ERR_MATH_DIVIDE_BY_ZERO) {
186 		vm.err = (BclError) (e - BC_ERR_MATH_NEGATIVE +
187 		                     BCL_ERROR_MATH_NEGATIVE);
188 	}
189 	else if (vm.abrt) abort();
190 	else if (e == BC_ERR_FATAL_ALLOC_ERR) vm.err = BCL_ERROR_FATAL_ALLOC_ERR;
191 	else vm.err = BCL_ERROR_FATAL_UNKNOWN_ERR;
192 
193 	BC_VM_JMP;
194 }
195 #else // BC_ENABLE_LIBRARY
196 void bc_vm_handleError(BcErr e, size_t line, ...) {
197 
198 	BcStatus s;
199 	va_list args;
200 	uchar id = bc_err_ids[e];
201 	const char* err_type = vm.err_ids[id];
202 	sig_atomic_t lock;
203 
204 	assert(e < BC_ERR_NELEMS);
205 	assert(!vm.sig_pop);
206 
207 #if BC_ENABLED
208 	if (!BC_S && e >= BC_ERR_POSIX_START) {
209 		if (BC_W) {
210 			// Make sure to not return an error.
211 			id = UCHAR_MAX;
212 			err_type = vm.err_ids[BC_ERR_IDX_WARN];
213 		}
214 		else return;
215 	}
216 #endif // BC_ENABLED
217 
218 	BC_SIG_TRYLOCK(lock);
219 
220 	// Make sure all of stdout is written first.
221 	s = bc_file_flushErr(&vm.fout, bc_flush_err);
222 
223 	if (BC_ERR(s == BC_STATUS_ERROR_FATAL)) {
224 		vm.status = (sig_atomic_t) s;
225 		BC_VM_JMP;
226 	}
227 
228 	va_start(args, line);
229 	bc_file_putchar(&vm.ferr, bc_flush_none, '\n');
230 	bc_file_puts(&vm.ferr, bc_flush_none, err_type);
231 	bc_file_putchar(&vm.ferr, bc_flush_none, ' ');
232 	bc_file_vprintf(&vm.ferr, vm.err_msgs[e], args);
233 	va_end(args);
234 
235 	if (BC_NO_ERR(vm.file)) {
236 
237 		// This is the condition for parsing vs runtime.
238 		// If line is not 0, it is parsing.
239 		if (line) {
240 			bc_file_puts(&vm.ferr, bc_flush_none, "\n    ");
241 			bc_file_puts(&vm.ferr, bc_flush_none, vm.file);
242 			bc_file_printf(&vm.ferr, bc_err_line, line);
243 		}
244 		else {
245 
246 			BcInstPtr *ip = bc_vec_item_rev(&vm.prog.stack, 0);
247 			BcFunc *f = bc_vec_item(&vm.prog.fns, ip->func);
248 
249 			bc_file_puts(&vm.ferr, bc_flush_none, "\n    ");
250 			bc_file_puts(&vm.ferr, bc_flush_none, vm.func_header);
251 			bc_file_putchar(&vm.ferr, bc_flush_none, ' ');
252 			bc_file_puts(&vm.ferr, bc_flush_none, f->name);
253 
254 #if BC_ENABLED
255 			if (BC_IS_BC && ip->func != BC_PROG_MAIN &&
256 			    ip->func != BC_PROG_READ)
257 			{
258 				bc_file_puts(&vm.ferr, bc_flush_none, "()");
259 			}
260 #endif // BC_ENABLED
261 		}
262 	}
263 
264 	bc_file_puts(&vm.ferr, bc_flush_none, "\n\n");
265 
266 	s = bc_file_flushErr(&vm.ferr, bc_flush_err);
267 
268 #if !BC_ENABLE_MEMCHECK
269 	// Because this function is called by a BC_NORETURN function when fatal
270 	// errors happen, we need to make sure to exit on fatal errors. This will
271 	// be faster anyway. This function *cannot jump when a fatal error occurs!*
272 	if (BC_ERR(id == BC_ERR_IDX_FATAL || s == BC_STATUS_ERROR_FATAL))
273 		exit(bc_vm_atexit((int) BC_STATUS_ERROR_FATAL));
274 #else // !BC_ENABLE_MEMCHECK
275 	if (BC_ERR(s == BC_STATUS_ERROR_FATAL)) vm.status = (sig_atomic_t) s;
276 	else
277 #endif // !BC_ENABLE_MEMCHECK
278 	{
279 		vm.status = (sig_atomic_t) (uchar) (id + 1);
280 	}
281 
282 	if (BC_ERR(vm.status)) BC_VM_JMP;
283 
284 	BC_SIG_TRYUNLOCK(lock);
285 }
286 
287 static void bc_vm_envArgs(const char* const env_args_name) {
288 
289 	char *env_args = bc_vm_getenv(env_args_name), *buf, *start;
290 	char instr = '\0';
291 
292 	BC_SIG_ASSERT_LOCKED;
293 
294 	if (env_args == NULL) return;
295 
296 	// Windows already allocates, so we don't need to.
297 #ifndef _WIN32
298 	start = buf = vm.env_args_buffer = bc_vm_strdup(env_args);
299 #else // _WIN32
300 	start = buf = vm.env_args_buffer = env_args;
301 #endif // _WIN32
302 
303 	assert(buf != NULL);
304 
305 	bc_vec_init(&vm.env_args, sizeof(char*), NULL);
306 	bc_vec_push(&vm.env_args, &env_args_name);
307 
308 	while (*buf) {
309 
310 		if (!isspace(*buf)) {
311 
312 			if (*buf == '"' || *buf == '\'') {
313 
314 				instr = *buf;
315 				buf += 1;
316 
317 				if (*buf == instr) {
318 					instr = '\0';
319 					buf += 1;
320 					continue;
321 				}
322 			}
323 
324 			bc_vec_push(&vm.env_args, &buf);
325 
326 			while (*buf && ((!instr && !isspace(*buf)) ||
327 			                (instr && *buf != instr)))
328 			{
329 				buf += 1;
330 			}
331 
332 			if (*buf) {
333 
334 				if (instr) instr = '\0';
335 
336 				*buf = '\0';
337 				buf += 1;
338 				start = buf;
339 			}
340 			else if (instr) bc_vm_error(BC_ERR_FATAL_OPTION, 0, start);
341 		}
342 		else buf += 1;
343 	}
344 
345 	// Make sure to push a NULL pointer at the end.
346 	buf = NULL;
347 	bc_vec_push(&vm.env_args, &buf);
348 
349 	bc_args((int) vm.env_args.len - 1, bc_vec_item(&vm.env_args, 0), false);
350 }
351 
352 static size_t bc_vm_envLen(const char *var) {
353 
354 	char *lenv = bc_vm_getenv(var);
355 	size_t i, len = BC_NUM_PRINT_WIDTH;
356 	int num;
357 
358 	if (lenv == NULL) return len;
359 
360 	len = strlen(lenv);
361 
362 	for (num = 1, i = 0; num && i < len; ++i) num = isdigit(lenv[i]);
363 
364 	if (num) {
365 		len = (size_t) atoi(lenv) - 1;
366 		if (len < 2 || len >= UINT16_MAX) len = BC_NUM_PRINT_WIDTH;
367 	}
368 	else len = BC_NUM_PRINT_WIDTH;
369 
370 	bc_vm_getenvFree(lenv);
371 
372 	return len;
373 }
374 #endif // BC_ENABLE_LIBRARY
375 
376 void bc_vm_shutdown(void) {
377 
378 	BC_SIG_ASSERT_LOCKED;
379 
380 #if BC_ENABLE_NLS
381 	if (vm.catalog != BC_VM_INVALID_CATALOG) catclose(vm.catalog);
382 #endif // BC_ENABLE_NLS
383 
384 #if BC_ENABLE_HISTORY
385 	// This must always run to ensure that the terminal is back to normal.
386 	if (BC_TTY) bc_history_free(&vm.history);
387 #endif // BC_ENABLE_HISTORY
388 
389 #ifndef NDEBUG
390 #if !BC_ENABLE_LIBRARY
391 	bc_vec_free(&vm.env_args);
392 	free(vm.env_args_buffer);
393 	bc_vec_free(&vm.files);
394 	bc_vec_free(&vm.exprs);
395 
396 	bc_program_free(&vm.prog);
397 	bc_parse_free(&vm.prs);
398 #endif // !BC_ENABLE_LIBRARY
399 
400 	bc_vm_freeTemps();
401 	bc_vec_free(&vm.temps);
402 #endif // NDEBUG
403 
404 #if !BC_ENABLE_LIBRARY
405 	bc_file_free(&vm.fout);
406 	bc_file_free(&vm.ferr);
407 #endif // !BC_ENABLE_LIBRARY
408 }
409 
410 #if !defined(NDEBUG) || BC_ENABLE_LIBRARY
411 void bc_vm_freeTemps(void) {
412 
413 	size_t i;
414 
415 	for (i = 0; i < vm.temps.len; ++i) {
416 		free(((BcNum*) bc_vec_item(&vm.temps, i))->num);
417 	}
418 }
419 #endif // !defined(NDEBUG) || BC_ENABLE_LIBRARY
420 
421 inline size_t bc_vm_arraySize(size_t n, size_t size) {
422 	size_t res = n * size;
423 	if (BC_ERR(res >= SIZE_MAX || (n != 0 && res / n != size)))
424 		bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR);
425 	return res;
426 }
427 
428 inline size_t bc_vm_growSize(size_t a, size_t b) {
429 	size_t res = a + b;
430 	if (BC_ERR(res >= SIZE_MAX || res < a || res < b))
431 		bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR);
432 	return res;
433 }
434 
435 void* bc_vm_malloc(size_t n) {
436 
437 	void* ptr;
438 
439 	BC_SIG_ASSERT_LOCKED;
440 
441 	ptr = malloc(n);
442 
443 	if (BC_ERR(ptr == NULL)) bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR);
444 
445 	return ptr;
446 }
447 
448 void* bc_vm_realloc(void *ptr, size_t n) {
449 
450 	void* temp;
451 
452 	BC_SIG_ASSERT_LOCKED;
453 
454 	temp = realloc(ptr, n);
455 
456 	if (BC_ERR(temp == NULL)) bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR);
457 
458 	return temp;
459 }
460 
461 char* bc_vm_strdup(const char *str) {
462 
463 	char *s;
464 
465 	BC_SIG_ASSERT_LOCKED;
466 
467 	s = strdup(str);
468 
469 	if (BC_ERR(s == NULL)) bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR);
470 
471 	return s;
472 }
473 
474 #if !BC_ENABLE_LIBRARY
475 void bc_vm_printf(const char *fmt, ...) {
476 
477 	va_list args;
478 
479 	BC_SIG_LOCK;
480 
481 	va_start(args, fmt);
482 	bc_file_vprintf(&vm.fout, fmt, args);
483 	va_end(args);
484 
485 	vm.nchars = 0;
486 
487 	BC_SIG_UNLOCK;
488 }
489 #endif // !BC_ENABLE_LIBRARY
490 
491 void bc_vm_putchar(int c, BcFlushType type) {
492 #if BC_ENABLE_LIBRARY
493 	bc_vec_pushByte(&vm.out, (uchar) c);
494 #else // BC_ENABLE_LIBRARY
495 	bc_file_putchar(&vm.fout, type, (uchar) c);
496 	vm.nchars = (c == '\n' ? 0 : vm.nchars + 1);
497 #endif // BC_ENABLE_LIBRARY
498 }
499 
500 char* bc_vm_getenv(const char* var) {
501 
502 	char* ret;
503 
504 #ifndef _WIN32
505 	ret = getenv(var);
506 #else // _WIN32
507 	_dupenv_s(&ret, NULL, var);
508 #endif // _WIN32
509 
510 	return ret;
511 }
512 
513 void bc_vm_getenvFree(char* var) {
514 	BC_UNUSED(var);
515 #ifdef _WIN32
516 	free(var);
517 #endif // _WIN32
518 }
519 
520 #if !BC_ENABLE_LIBRARY
521 static void bc_vm_clean(void) {
522 
523 	BcVec *fns = &vm.prog.fns;
524 	BcFunc *f = bc_vec_item(fns, BC_PROG_MAIN);
525 	BcInstPtr *ip = bc_vec_item(&vm.prog.stack, 0);
526 	bool good = ((vm.status && vm.status != BC_STATUS_QUIT) || vm.sig);
527 
528 	if (good) bc_program_reset(&vm.prog);
529 
530 #if BC_ENABLED
531 	if (good && BC_IS_BC) good = !BC_PARSE_NO_EXEC(&vm.prs);
532 #endif // BC_ENABLED
533 
534 #if DC_ENABLED
535 	if (BC_IS_DC) {
536 
537 		size_t i;
538 
539 		good = true;
540 
541 		for (i = 0; good && i < vm.prog.results.len; ++i) {
542 			BcResult *r = (BcResult*) bc_vec_item(&vm.prog.results, i);
543 			good = BC_VM_SAFE_RESULT(r);
544 		}
545 	}
546 #endif // DC_ENABLED
547 
548 	// If this condition is true, we can get rid of strings,
549 	// constants, and code. This is an idea from busybox.
550 	if (good && vm.prog.stack.len == 1 && ip->idx == f->code.len) {
551 
552 #if BC_ENABLED
553 		if (BC_IS_BC) {
554 			bc_vec_popAll(&f->labels);
555 			bc_vec_popAll(&f->strs);
556 			bc_vec_popAll(&f->consts);
557 		}
558 #endif // BC_ENABLED
559 
560 #if DC_ENABLED
561 		// Note to self: you cannot delete strings and functions. Deal with it.
562 		if (BC_IS_DC) bc_vec_popAll(vm.prog.consts);
563 #endif // DC_ENABLED
564 
565 		bc_vec_popAll(&f->code);
566 
567 		ip->idx = 0;
568 	}
569 }
570 
571 static void bc_vm_process(const char *text) {
572 
573 	bc_parse_text(&vm.prs, text);
574 
575 	do {
576 
577 #if BC_ENABLED
578 		if (vm.prs.l.t == BC_LEX_KW_DEFINE) vm.parse(&vm.prs);
579 #endif // BC_ENABLED
580 
581 		while (BC_PARSE_CAN_PARSE(vm.prs)) vm.parse(&vm.prs);
582 
583 		if(BC_IS_DC || !BC_PARSE_NO_EXEC(&vm.prs)) bc_program_exec(&vm.prog);
584 
585 		assert(BC_IS_DC || vm.prog.results.len == 0);
586 
587 		if (BC_I) bc_file_flush(&vm.fout, bc_flush_save);
588 
589 	} while (vm.prs.l.t != BC_LEX_EOF);
590 }
591 
592 #if BC_ENABLED
593 static void bc_vm_endif(void) {
594 
595 	size_t i;
596 	bool good;
597 
598 	if (BC_NO_ERR(!BC_PARSE_NO_EXEC(&vm.prs))) return;
599 
600 	good = true;
601 
602 	for (i = 0; good && i < vm.prs.flags.len; ++i) {
603 		uint16_t flag = *((uint16_t*) bc_vec_item(&vm.prs.flags, i));
604 		good = ((flag & BC_PARSE_FLAG_BRACE) != BC_PARSE_FLAG_BRACE);
605 	}
606 
607 	if (good) {
608 		while (BC_PARSE_IF_END(&vm.prs)) bc_vm_process("else {}");
609 	}
610 	else bc_parse_err(&vm.prs, BC_ERR_PARSE_BLOCK);
611 }
612 #endif // BC_ENABLED
613 
614 static void bc_vm_file(const char *file) {
615 
616 	char *data = NULL;
617 
618 	assert(!vm.sig_pop);
619 
620 	bc_lex_file(&vm.prs.l, file);
621 
622 	BC_SIG_LOCK;
623 
624 	bc_read_file(file, &data);
625 
626 	BC_SETJMP_LOCKED(err);
627 
628 	BC_SIG_UNLOCK;
629 
630 	bc_vm_process(data);
631 
632 #if BC_ENABLED
633 	if (BC_IS_BC) bc_vm_endif();
634 #endif // BC_ENABLED
635 
636 err:
637 	BC_SIG_MAYLOCK;
638 
639 	free(data);
640 	bc_vm_clean();
641 
642 	// bc_program_reset(), called by bc_vm_clean(), resets the status.
643 	// We want it to clear the sig_pop variable in case it was set.
644 	if (vm.status == (sig_atomic_t) BC_STATUS_SUCCESS) BC_LONGJMP_STOP;
645 
646 	BC_LONGJMP_CONT;
647 }
648 
649 static void bc_vm_stdin(void) {
650 
651 	BcStatus s;
652 	BcVec buf, buffer;
653 	size_t string = 0;
654 	bool comment = false, hash = false;
655 
656 	bc_lex_file(&vm.prs.l, bc_program_stdin_name);
657 
658 	BC_SIG_LOCK;
659 	bc_vec_init(&buffer, sizeof(uchar), NULL);
660 	bc_vec_init(&buf, sizeof(uchar), NULL);
661 	bc_vec_pushByte(&buffer, '\0');
662 	BC_SETJMP_LOCKED(err);
663 	BC_SIG_UNLOCK;
664 
665 restart:
666 
667 	// This loop is complex because the vm tries not to send any lines that end
668 	// with a backslash to the parser. The reason for that is because the parser
669 	// treats a backslash+newline combo as whitespace, per the bc spec. In that
670 	// case, and for strings and comments, the parser will expect more stuff.
671 	while ((!(s = bc_read_line(&buf, ">>> ")) ||
672 	        (vm.eof = (s == BC_STATUS_EOF))) && buf.len > 1)
673 	{
674 		char c2, *str = buf.v;
675 		size_t i, len = buf.len - 1;
676 
677 		for (i = 0; i < len; ++i) {
678 
679 			bool notend = len > i + 1;
680 			uchar c = (uchar) str[i];
681 
682 			hash = (!comment && !string && ((hash && c != '\n') ||
683 			                                (!hash && c == '#')));
684 
685 			if (!hash && !comment && (i - 1 > len || str[i - 1] != '\\')) {
686 				if (BC_IS_BC) string ^= (c == '"');
687 				else if (c == ']') string -= 1;
688 				else if (c == '[') string += 1;
689 			}
690 
691 			if (BC_IS_BC && !hash && !string && notend) {
692 
693 				c2 = str[i + 1];
694 
695 				if (c == '/' && !comment && c2 == '*') {
696 					comment = true;
697 					i += 1;
698 				}
699 				else if (c == '*' && comment && c2 == '/') {
700 					comment = false;
701 					i += 1;
702 				}
703 			}
704 		}
705 
706 		bc_vec_concat(&buffer, buf.v);
707 
708 		if (string || comment) continue;
709 		if (len >= 2 && str[len - 2] == '\\' && str[len - 1] == '\n') continue;
710 #if BC_ENABLE_HISTORY
711 		if (vm.history.stdin_has_data) continue;
712 #endif // BC_ENABLE_HISTORY
713 
714 		bc_vm_process(buffer.v);
715 		bc_vec_empty(&buffer);
716 
717 		if (vm.eof) break;
718 		else bc_vm_clean();
719 	}
720 
721 	if (!BC_STATUS_IS_ERROR(s)) {
722 		if (BC_ERR(comment))
723 			bc_parse_err(&vm.prs, BC_ERR_PARSE_COMMENT);
724 		else if (BC_ERR(string))
725 			bc_parse_err(&vm.prs, BC_ERR_PARSE_STRING);
726 #if BC_ENABLED
727 		else if (BC_IS_BC) bc_vm_endif();
728 #endif // BC_ENABLED
729 	}
730 
731 err:
732 	BC_SIG_MAYLOCK;
733 
734 	bc_vm_clean();
735 
736 #if !BC_ENABLE_MEMCHECK
737 	assert(vm.status != BC_STATUS_ERROR_FATAL);
738 
739 	vm.status = vm.status == BC_STATUS_QUIT || !BC_I ?
740 	            vm.status : BC_STATUS_SUCCESS;
741 #else // !BC_ENABLE_MEMCHECK
742 	vm.status = vm.status == BC_STATUS_ERROR_FATAL ||
743 	            vm.status == BC_STATUS_QUIT || !BC_I ?
744 	            vm.status : BC_STATUS_SUCCESS;
745 #endif // !BC_ENABLE_MEMCHECK
746 
747 	if (!vm.status && !vm.eof) {
748 		bc_vec_empty(&buffer);
749 		BC_LONGJMP_STOP;
750 		BC_SIG_UNLOCK;
751 		goto restart;
752 	}
753 
754 	bc_vec_free(&buf);
755 	bc_vec_free(&buffer);
756 
757 	BC_LONGJMP_CONT;
758 }
759 
760 #if BC_ENABLED
761 static void bc_vm_load(const char *name, const char *text) {
762 
763 	bc_lex_file(&vm.prs.l, name);
764 	bc_parse_text(&vm.prs, text);
765 
766 	while (vm.prs.l.t != BC_LEX_EOF) vm.parse(&vm.prs);
767 }
768 #endif // BC_ENABLED
769 
770 static void bc_vm_defaultMsgs(void) {
771 
772 	size_t i;
773 
774 	vm.func_header = bc_err_func_header;
775 
776 	for (i = 0; i < BC_ERR_IDX_NELEMS + BC_ENABLED; ++i)
777 		vm.err_ids[i] = bc_errs[i];
778 	for (i = 0; i < BC_ERR_NELEMS; ++i) vm.err_msgs[i] = bc_err_msgs[i];
779 }
780 
781 static void bc_vm_gettext(void) {
782 
783 #if BC_ENABLE_NLS
784 	uchar id = 0;
785 	int set = 1, msg = 1;
786 	size_t i;
787 
788 	if (vm.locale == NULL) {
789 		vm.catalog = BC_VM_INVALID_CATALOG;
790 		bc_vm_defaultMsgs();
791 		return;
792 	}
793 
794 	vm.catalog = catopen(BC_MAINEXEC, NL_CAT_LOCALE);
795 
796 	if (vm.catalog == BC_VM_INVALID_CATALOG) {
797 		bc_vm_defaultMsgs();
798 		return;
799 	}
800 
801 	vm.func_header = catgets(vm.catalog, set, msg, bc_err_func_header);
802 
803 	for (set += 1; msg <= BC_ERR_IDX_NELEMS + BC_ENABLED; ++msg)
804 		vm.err_ids[msg - 1] = catgets(vm.catalog, set, msg, bc_errs[msg - 1]);
805 
806 	i = 0;
807 	id = bc_err_ids[i];
808 
809 	for (set = id + 3, msg = 1; i < BC_ERR_NELEMS; ++i, ++msg) {
810 
811 		if (id != bc_err_ids[i]) {
812 			msg = 1;
813 			id = bc_err_ids[i];
814 			set = id + 3;
815 		}
816 
817 		vm.err_msgs[i] = catgets(vm.catalog, set, msg, bc_err_msgs[i]);
818 	}
819 #else // BC_ENABLE_NLS
820 	bc_vm_defaultMsgs();
821 #endif // BC_ENABLE_NLS
822 }
823 
824 static void bc_vm_exec(void) {
825 
826 	size_t i;
827 	bool has_file = false;
828 	BcVec buf;
829 
830 #if BC_ENABLED
831 	if (BC_IS_BC && (vm.flags & BC_FLAG_L)) {
832 
833 		bc_vm_load(bc_lib_name, bc_lib);
834 
835 #if BC_ENABLE_EXTRA_MATH
836 		if (!BC_IS_POSIX) bc_vm_load(bc_lib2_name, bc_lib2);
837 #endif // BC_ENABLE_EXTRA_MATH
838 
839 		bc_program_exec(&vm.prog);
840 	}
841 #endif // BC_ENABLED
842 
843 	if (vm.exprs.len) {
844 
845 		size_t len = vm.exprs.len - 1;
846 		bool more;
847 
848 		BC_SIG_LOCK;
849 		bc_vec_init(&buf, sizeof(uchar), NULL);
850 
851 #ifndef NDEBUG
852 		BC_SETJMP_LOCKED(err);
853 #endif // NDEBUG
854 
855 		BC_SIG_UNLOCK;
856 
857 		bc_lex_file(&vm.prs.l, bc_program_exprs_name);
858 
859 		do {
860 
861 			more = bc_read_buf(&buf, vm.exprs.v, &len);
862 			bc_vec_pushByte(&buf, '\0');
863 			bc_vm_process(buf.v);
864 
865 			bc_vec_popAll(&buf);
866 
867 		} while (more);
868 
869 		BC_SIG_LOCK;
870 		bc_vec_free(&buf);
871 
872 #ifndef NDEBUG
873 		BC_UNSETJMP;
874 #endif // NDEBUG
875 
876 		BC_SIG_UNLOCK;
877 
878 		if (!vm.no_exit_exprs && vm.exit_exprs) return;
879 	}
880 
881 	for (i = 0; i < vm.files.len; ++i) {
882 		char *path = *((char**) bc_vec_item(&vm.files, i));
883 		if (!strcmp(path, "")) continue;
884 		has_file = true;
885 		bc_vm_file(path);
886 	}
887 
888 #if BC_ENABLE_AFL
889 	__AFL_INIT();
890 #endif // BC_ENABLE_AFL
891 
892 	if (BC_IS_BC || !has_file) bc_vm_stdin();
893 
894 // These are all protected by ifndef NDEBUG because if these are needed, bc is
895 // going to exit anyway, and I see no reason to include this code in a release
896 // build when the OS is going to free all of the resources anyway.
897 #ifndef NDEBUG
898 	return;
899 
900 err:
901 	BC_SIG_MAYLOCK;
902 	bc_vec_free(&buf);
903 	BC_LONGJMP_CONT;
904 #endif // NDEBUG
905 }
906 
907 void bc_vm_boot(int argc, char *argv[], const char *env_len,
908                 const char* const env_args)
909 {
910 	int ttyin, ttyout, ttyerr;
911 
912 	BC_SIG_ASSERT_LOCKED;
913 
914 	ttyin = isatty(STDIN_FILENO);
915 	ttyout = isatty(STDOUT_FILENO);
916 	ttyerr = isatty(STDERR_FILENO);
917 
918 	vm.flags |= ttyin ? BC_FLAG_TTYIN : 0;
919 	vm.flags |= (ttyin != 0 && ttyout != 0 && ttyerr != 0) ? BC_FLAG_TTY : 0;
920 	vm.flags |= ttyin && ttyout ? BC_FLAG_I : 0;
921 
922 	bc_vm_sigaction();
923 
924 	bc_vm_init();
925 
926 	vm.file = NULL;
927 
928 	bc_vm_gettext();
929 
930 	bc_file_init(&vm.ferr, STDERR_FILENO, output_bufs + BC_VM_STDOUT_BUF_SIZE,
931 	             BC_VM_STDERR_BUF_SIZE);
932 	bc_file_init(&vm.fout, STDOUT_FILENO, output_bufs, BC_VM_STDOUT_BUF_SIZE);
933 	vm.buf = output_bufs + BC_VM_STDOUT_BUF_SIZE + BC_VM_STDERR_BUF_SIZE;
934 
935 	vm.line_len = (uint16_t) bc_vm_envLen(env_len);
936 
937 	bc_vec_clear(&vm.files);
938 	bc_vec_clear(&vm.exprs);
939 
940 	bc_program_init(&vm.prog);
941 	bc_parse_init(&vm.prs, &vm.prog, BC_PROG_MAIN);
942 
943 #if BC_ENABLE_HISTORY
944 	if (BC_TTY) bc_history_init(&vm.history);
945 #endif // BC_ENABLE_HISTORY
946 
947 #if BC_ENABLED
948 	if (BC_IS_BC) {
949 		char* var = bc_vm_getenv("POSIXLY_CORRECT");
950 		vm.flags |= BC_FLAG_S * (var != NULL);
951 		bc_vm_getenvFree(var);
952 	}
953 #endif // BC_ENABLED
954 
955 	bc_vm_envArgs(env_args);
956 	bc_args(argc, argv, true);
957 
958 #if BC_ENABLED
959 	if (BC_IS_POSIX) vm.flags &= ~(BC_FLAG_G);
960 #endif // BC_ENABLED
961 
962 	BC_SIG_UNLOCK;
963 
964 	bc_vm_exec();
965 }
966 #endif // !BC_ENABLE_LIBRARY
967 
968 void bc_vm_init(void) {
969 
970 	BC_SIG_ASSERT_LOCKED;
971 
972 	memcpy(vm.max_num, bc_num_bigdigMax,
973 	       bc_num_bigdigMax_size * sizeof(BcDig));
974 	memcpy(vm.max2_num, bc_num_bigdigMax2,
975 	       bc_num_bigdigMax2_size * sizeof(BcDig));
976 	bc_num_setup(&vm.max, vm.max_num, BC_NUM_BIGDIG_LOG10);
977 	bc_num_setup(&vm.max2, vm.max2_num, BC_NUM_BIGDIG_LOG10);
978 	vm.max.len = bc_num_bigdigMax_size;
979 	vm.max2.len = bc_num_bigdigMax2_size;
980 
981 	bc_vec_init(&vm.temps, sizeof(BcNum), NULL);
982 
983 	vm.maxes[BC_PROG_GLOBALS_IBASE] = BC_NUM_MAX_POSIX_IBASE;
984 	vm.maxes[BC_PROG_GLOBALS_OBASE] = BC_MAX_OBASE;
985 	vm.maxes[BC_PROG_GLOBALS_SCALE] = BC_MAX_SCALE;
986 
987 #if BC_ENABLE_EXTRA_MATH && BC_ENABLE_RAND
988 	vm.maxes[BC_PROG_MAX_RAND] = ((BcRand) 0) - 1;
989 #endif // BC_ENABLE_EXTRA_MATH && BC_ENABLE_RAND
990 
991 #if BC_ENABLED
992 #if !BC_ENABLE_LIBRARY
993 	if (BC_IS_BC && !BC_IS_POSIX)
994 #endif // !BC_ENABLE_LIBRARY
995 	{
996 		vm.maxes[BC_PROG_GLOBALS_IBASE] = BC_NUM_MAX_IBASE;
997 	}
998 #endif // BC_ENABLED
999 }
1000 
1001 #if BC_ENABLE_LIBRARY
1002 void bc_vm_atexit(void) {
1003 
1004 	bc_vm_shutdown();
1005 
1006 #ifndef NDEBUG
1007 	bc_vec_free(&vm.jmp_bufs);
1008 #endif // NDEBUG
1009 }
1010 #else // BC_ENABLE_LIBRARY
1011 int bc_vm_atexit(int status) {
1012 
1013 	int s = BC_STATUS_IS_ERROR(status) ? status : BC_STATUS_SUCCESS;
1014 
1015 	bc_vm_shutdown();
1016 
1017 #ifndef NDEBUG
1018 	bc_vec_free(&vm.jmp_bufs);
1019 #endif // NDEBUG
1020 
1021 	return s;
1022 }
1023 #endif // BC_ENABLE_LIBRARY
1024