diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/stdlib/mapmacro.h | 29 | ||||
| -rw-r--r-- | src/stdlib/print.h | 29 | ||||
| -rw-r--r-- | src/stdlib/simpleparse.c | 128 | ||||
| -rw-r--r-- | src/stdlib/simpleparse.h | 51 | ||||
| -rw-r--r-- | src/stdlib/stacktrace.c | 144 | ||||
| -rw-r--r-- | src/stdlib/stacktrace.h | 2 | ||||
| -rw-r--r-- | src/stdlib/stdlib.h | 4 | ||||
| -rw-r--r-- | src/stdlib/text.c | 1 | ||||
| -rw-r--r-- | src/tomo.c | 2 |
9 files changed, 293 insertions, 97 deletions
diff --git a/src/stdlib/mapmacro.h b/src/stdlib/mapmacro.h new file mode 100644 index 00000000..5ed7a4b7 --- /dev/null +++ b/src/stdlib/mapmacro.h @@ -0,0 +1,29 @@ +#pragma once + +// This file defines a MAP_LIST(fn, ...) function that applies a function to +// every one of the varargs. +// For example: baz(MAP_LIST(foo, 1, "x")) -> baz(foo(1), foo("x")) + +#define EVAL0(...) __VA_ARGS__ +#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__))) +#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__))) +#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__))) +#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__))) +#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__))) + +#define MAP_END(...) +#define MAP_OUT +#define MAP_COMMA , + +#define MAP_GET_END2() 0, MAP_END +#define MAP_GET_END1(...) MAP_GET_END2 +#define MAP_GET_END(...) MAP_GET_END1 +#define MAP_NEXT0(test, next, ...) next MAP_OUT + +#define MAP_LIST_NEXT1(test, next) MAP_NEXT0(test, MAP_COMMA next, 0) +#define MAP_LIST_NEXT(test, next) MAP_LIST_NEXT1(MAP_GET_END test, next) + +#define MAP_LIST0(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST1)(f, peek, __VA_ARGS__) +#define MAP_LIST1(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST0)(f, peek, __VA_ARGS__) + +#define MAP_LIST(f, ...) EVAL(MAP_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) diff --git a/src/stdlib/print.h b/src/stdlib/print.h index 0b911ed6..60c9c139 100644 --- a/src/stdlib/print.h +++ b/src/stdlib/print.h @@ -24,35 +24,12 @@ #include <unistd.h> #include "datatypes.h" +#include "mapmacro.h" #ifndef PRINT_COLOR #define PRINT_COLOR 0 #endif -#define EVAL0(...) __VA_ARGS__ -#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__))) -#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__))) -#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__))) -#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__))) -#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__))) - -#define MAP_END(...) -#define MAP_OUT -#define MAP_COMMA , - -#define MAP_GET_END2() 0, MAP_END -#define MAP_GET_END1(...) MAP_GET_END2 -#define MAP_GET_END(...) MAP_GET_END1 -#define MAP_NEXT0(test, next, ...) next MAP_OUT - -#define MAP_LIST_NEXT1(test, next) MAP_NEXT0(test, MAP_COMMA next, 0) -#define MAP_LIST_NEXT(test, next) MAP_LIST_NEXT1(MAP_GET_END test, next) - -#define MAP_LIST0(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST1)(f, peek, __VA_ARGS__) -#define MAP_LIST1(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST0)(f, peek, __VA_ARGS__) - -#define MAP_LIST(f, ...) EVAL(MAP_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - // GCC lets you define macro-like functions which are always inlined and never // compiled using this combination of flags. See: https://gcc.gnu.org/onlinedocs/gcc/Inline.html #ifndef PRINT_FN @@ -108,10 +85,10 @@ PRINT_FN _print_float(FILE *f, float x) { return fprintf(f, hl("%g"), (double)x) PRINT_FN _print_double(FILE *f, double x) { return fprintf(f, hl("%g"), x); } PRINT_FN _print_pointer(FILE *f, void *p) { return fprintf(f, hl("%p"), p); } PRINT_FN _print_bool(FILE *f, bool b) { return fputs(b ? hl("yes") : hl("no"), f); } -PRINT_FN _print_str(FILE *f, const char *s) { return fputs(s, f); } +PRINT_FN _print_str(FILE *f, const char *s) { return fputs(s ? s : "(null)", f); } int _print_char(FILE *f, char c); int _print_quoted(FILE *f, quoted_t quoted); -PRINT_FN _print_string_slice(FILE *f, string_slice_t slice) { return fwrite(slice.str, 1, slice.length, f); } +PRINT_FN _print_string_slice(FILE *f, string_slice_t slice) { return slice.str ? fwrite(slice.str, 1, slice.length, f) : (size_t)fputs("(null)", f); } PRINT_FN _print_hex(FILE *f, hex_format_t hex) { return fprintf(f, hex.no_prefix ? (hex.uppercase ? hl("%0*"FMT64"X") : hl("%0*"FMT64"x")) : (hex.uppercase ? hl("0x%0*"FMT64"X") : hl("%#0*"FMT64"x")), hex.digits, hex.n); } diff --git a/src/stdlib/simpleparse.c b/src/stdlib/simpleparse.c new file mode 100644 index 00000000..1ee64036 --- /dev/null +++ b/src/stdlib/simpleparse.c @@ -0,0 +1,128 @@ +// A simple text parsing primitive: +// const char *line = "foo.txt:15"; +// const char *filename; int line; +// if (strparse(line, &filename, ":", &line)) { success...} +// or: +// FILE *f = ...; +// if (fparse(f, &filename, ":", &line)) { success... } + +#include <ctype.h> +#include <gc.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "simpleparse.h" +#include "util.h" + +static bool _match_word(const char **str, const char *target) { + size_t len = strlen(target); + if (strncasecmp(*str, target, len) == 0 && !isalnum((*str)[len]) && (*str)[len] != '_') { + *str += len; + return true; + } + return false; +} + +public const char *simpleparse(const char *str, int n, parse_type_e types[n], void *destinations[n]) +{ + for (int i = 0; i < n; i++) { + switch (types[i]) { + case PARSE_SOME_OF: { + if (destinations[i]) str += strspn(str, (char*)destinations[i]); + break; + } + case PARSE_LITERAL: { + const char *target = (const char*)destinations[i]; + if (target) { + if (strncmp(str, target, strlen(target)) != 0) + return str; + str += strlen(target); + } + break; + } + case PARSE_STRING: { + size_t len; + static const char matching_pair[256] = {[(int)'(']=')', [(int)'{']='}', [(int)'[']=']', + [(int)'"']='"', [(int)'\'']='\'', [(int)'`']='`', [(int)'<']='>'}; + if (i > 0 && i + 1 < n && types[i-1] == PARSE_LITERAL && types[i+1] == PARSE_LITERAL + && destinations[i-1] && destinations[i+1] + && strlen((char*)destinations[i-1]) == 1 && strlen((char*)destinations[i+1]) == 1 + && *(char*)destinations[i+1] == matching_pair[(int)*(char*)destinations[i-1]]) { + len = 0; + char special_characters[4] = {'\\', *(char*)destinations[i-1], *(char*)destinations[i+1], 0}; + for (int depth = 1; depth > 0; ) { + len += strcspn(str + len, special_characters); + if (str[len] == '\0') { + return str; + } else if (str[len] == '\\' + && (special_characters[1] == '"' || special_characters[1] == '\'' || special_characters[1] == '`')) { + if (str[len+1] == '\0') return str; + len += 2; + } else if (str[len] == special_characters[2]) { // Check for closing quotes before opening quotes + depth -= 1; + if (depth > 0) len += 1; + } else if (str[len] == special_characters[1]) { + depth += 1; + if (depth > 999999) return str; + len += 1; + } + } + } else if (i + 1 < n && types[i+1] == PARSE_LITERAL) { + const char *terminator = (const char*)destinations[i+1]; + if (terminator) { + const char *end = strstr(str, terminator); + if (!end) return str; + len = (size_t)((ptrdiff_t)end - (ptrdiff_t)str); + } else { + len = strlen(str); + } + } else if (i + 1 < n && types[i+1] == PARSE_SOME_OF) { + len = destinations[i+1] ? strcspn(str, (char*)destinations[i+1]) : strlen(str);; + } else { + len = strlen(str); + } + if (destinations[i]) { + char *matched = GC_MALLOC_ATOMIC(len+1); + memcpy(matched, str, len); + matched[len] = '\0'; + *(const char**)destinations[i] = matched; + } + str += len; + break; + } + case PARSE_DOUBLE: { + char *end = NULL; + double val = strtod(str, &end); + if (end == str) return str; + if (destinations[i]) *(double*)destinations[i] = val; + str = end; + break; + } + case PARSE_LONG: { + char *end = NULL; + long val = strtol(str, &end, 10); + if (end == str) return str; + if (destinations[i]) *(long*)destinations[i] = val; + str = end; + break; + } + case PARSE_BOOL: { + if (_match_word(&str, "true") || _match_word(&str, "yes") || _match_word(&str, "on") || _match_word(&str, "1")) { + if (destinations[i]) *(bool*)destinations[i] = true; + } else if (_match_word(&str, "false") || _match_word(&str, "no") || _match_word(&str, "off") || _match_word(&str, "0")) { + if (destinations[i]) *(bool*)destinations[i] = false; + } else { + return str; + } + break; + } + default: break; + } + } + return NULL; +} + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/stdlib/simpleparse.h b/src/stdlib/simpleparse.h new file mode 100644 index 00000000..1448f74e --- /dev/null +++ b/src/stdlib/simpleparse.h @@ -0,0 +1,51 @@ +#pragma once + +// This file defines some functions to make it easy to parse simply formatted +// strings **correctly** without memory bugs. +// +// strparse(input_str, format...) - parse a string +// +// Examples: +// +// const char *filename; long line_num; +// const char *err = NULL; +// if ((err=strparse("foo.c:12", &filename, ":", &line_num))) +// errx(1, "Failed to parse file:line at: ", err); +// +// const char *item1, *item2; +// if ((err=strparse("one, two", &item1, ",", PARSE_WHITESPACE, &item2))) +// errx(1, "Failed to parse items at: ", err); + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> + +#include "mapmacro.h" + +typedef struct { char c; } some_char_t; +#define PARSE_SOME_OF(chars) ((some_char_t*)chars) +#define PARSE_WHITESPACE PARSE_SOME_OF(" \t\r\n\v") +typedef enum {PARSE_LITERAL, PARSE_LONG, PARSE_DOUBLE, PARSE_BOOL, PARSE_STRING, PARSE_SOME_OF} parse_type_e; + +typedef struct { parse_type_e type; void *dest; } parse_element_t; + +#define _parse_type(dest) _Generic((dest), \ + some_char_t*: PARSE_SOME_OF, \ + const char*: PARSE_LITERAL, \ + const char**: PARSE_STRING, \ + char**: PARSE_STRING, \ + double*: PARSE_DOUBLE, \ + long*: PARSE_LONG, \ + bool*: PARSE_BOOL) + +#define as_void_star(x) ((void*)x) +#define strparse(str, ...) simpleparse(str, sizeof((const void*[]){__VA_ARGS__})/sizeof(void*), (parse_type_e[]){MAP_LIST(_parse_type, __VA_ARGS__)}, (void*[]){MAP_LIST(as_void_star, __VA_ARGS__)}) +#define fparse(file, ...) ({ char *_file_contents = NULL; size_t _file_len; \ + (void)getdelim(&_file_contents, &_file_len, '\0', file); \ + const char *_parse_err = strparse(_file_contents, __VA_ARGS__); \ + free(_file_contents); \ + _parse_err; }) + +const char *simpleparse(const char *str, int n, parse_type_e types[n], void *destinations[n]); + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/stdlib/stacktrace.c b/src/stdlib/stacktrace.c index bd867c7b..aa5dd119 100644 --- a/src/stdlib/stacktrace.c +++ b/src/stdlib/stacktrace.c @@ -1,19 +1,20 @@ -#include <backtrace.h> +#include <dlfcn.h> #include <err.h> +#include <execinfo.h> #include <gc.h> -#include <signal.h> #include <limits.h> +#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> +#include "print.h" +#include "simpleparse.h" #include "util.h" extern bool USE_COLOR; -static struct backtrace_state *bt_state = NULL; - static void fprint_context(FILE *out, const char *filename, int lineno, int context_before, int context_after) { FILE *f = fopen(filename, "r"); @@ -44,31 +45,49 @@ static void fprint_context(FILE *out, const char *filename, int lineno, int cont fclose(f); } -typedef struct stack_info_s { - const char *function, *filename; - int lineno; - struct stack_info_s *next; -} stack_info_t; - -// Simple callback to print each frame -static int print_callback(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) +static void _print_stack_frame(FILE *out, const char *cwd, const char *install_dir, const char *function, const char *filename, int lineno) { - (void)pc; - stack_info_t *info = GC_MALLOC(sizeof(stack_info_t)); - info->next = *(stack_info_t**)data; - info->function = function; - info->filename = filename; - info->lineno = lineno; - *(stack_info_t**)data = info; - return 0; + if (function == NULL) { + fprint(out, USE_COLOR ? "\033[2m...unknown function...\033[m" : "...unknown function..."); + return; + } + + function = String(string_slice(function, strcspn(function, "+"))); + if (function[0] == '\0') function = "???"; + + char *function_display = GC_MALLOC_ATOMIC(strlen(function)); + memcpy(function_display, function, strlen(function)+1); + function_display += strcspn(function_display, "$"); + function_display += strspn(function_display, "$"); + function_display += strcspn(function_display, "$"); + function_display += strspn(function_display, "$"); + for (char *p = function_display; *p; p++) { + if (*p == '$') *p = '.'; + } + + if (filename) { + if (strncmp(filename, cwd, strlen(cwd)) == 0) + filename += strlen(cwd); + + fprintf(out, USE_COLOR ? "\033[1mIn \033[33m%s()\033[37m" : "In %s()", function_display); + if (filename) { + if (install_dir[0] && strncmp(filename, install_dir, strlen(install_dir)) == 0) + fprintf(out, USE_COLOR ? " in library \033[35m%s:%d" : " in library %s:%d", + filename + strlen(install_dir), lineno); + else + fprintf(out, USE_COLOR ? " in \033[35m%s:%d" : " in %s:%d", filename, lineno); + } + fprintf(out, USE_COLOR ? "\033[m\n" : "\n"); + if (filename) + fprint_context(out, filename, lineno, 3, 1); + } else { + fprint(out, "LINE: ", function); + } } +__attribute__ ((noinline)) public void print_stacktrace(FILE *out, int offset) { - stack_info_t *backwards = NULL; - - backtrace_full(bt_state, offset, print_callback, NULL, &backwards); - char cwd[PATH_MAX]; if (getcwd(cwd, sizeof(cwd)) == NULL) errx(1, "Path too large!"); @@ -78,49 +97,42 @@ public void print_stacktrace(FILE *out, int offset) cwd[cwd_len++] = '/'; cwd[cwd_len] = '\0'; - // Skip C entrypoint stuff: - while (backwards && backwards->function == NULL) - backwards = backwards->next; - - if (backwards && strstr(backwards->function, "$parse_and_run")) - backwards = backwards->next; - - for (stack_info_t *frame = backwards; frame; frame = frame->next) { - while (frame && frame->function == NULL) { - fprintf(out, USE_COLOR ? "\033[2m... unknown function ...\033[m\n" : "... unknown function ...\n"); - if (frame->next) - fprintf(out, "\n"); - frame = frame->next; - } - if (frame == NULL) break; - - char *function_display = GC_MALLOC_ATOMIC(strlen(frame->function)); - memcpy(function_display, frame->function, strlen(frame->function)+1); - function_display += strcspn(function_display, "$"); - function_display += strspn(function_display, "$"); - function_display += strcspn(function_display, "$"); - function_display += strspn(function_display, "$"); - for (char *p = function_display; *p; p++) { - if (*p == '$') *p = '.'; + const char *install_dir = String(getenv("HOME"), "/.local/share/tomo/installed/"); + + static void *stack[1024]; + int64_t size = (int64_t)backtrace(stack, sizeof(stack)/sizeof(stack[0])); + char **strings = backtrace_symbols(stack, size); + bool start_printing = false; + for (int64_t i = size-1; i > offset; i--) { + Dl_info info; + void *call_address = stack[i]-1; + if (dladdr(call_address, &info) && info.dli_fname) { + const char *file = info.dli_fname; + uintptr_t frame_offset = (uintptr_t)call_address - (uintptr_t)info.dli_fbase; + FILE *fp = popen(String("addr2line -f -e '", file, "' ", (void*)frame_offset, " 2>/dev/null"), "r"); + if (fp) { + const char *function = NULL, *filename = NULL; + long line_num = 0; + if (fparse(fp, &function, "\n", &filename, ":", &line_num) == NULL) { + if (strstr(function, "$main$parse_and_run")) { + start_printing = true; + continue; + } + if (start_printing) + _print_stack_frame(out, cwd, install_dir, function, filename, line_num); + } else { + if (start_printing) + _print_stack_frame(out, cwd, install_dir, NULL, NULL, line_num); + } + pclose(fp); + } + } else { + if (start_printing) + _print_stack_frame(out, cwd, install_dir, NULL, NULL, 0); } - - if (strncmp(frame->filename, cwd, cwd_len) == 0) - frame->filename += cwd_len; - - fprintf(out, USE_COLOR ? "\033[1mIn \033[33m%s()\033[37m" : "In %s()", function_display); - if (frame->filename) - fprintf(out, USE_COLOR ? " in \033[35m%s:%d" : " in %s:%d", frame->filename, frame->lineno); - fprintf(out, USE_COLOR ? "\033[m\n" : "\n"); - if (frame->filename) - fprint_context(out, frame->filename, frame->lineno, 3, 1); - if (frame->next) - fprintf(out, "\n"); + if (start_printing && i - 1 > offset) fputs("\n", out); } + free(strings); } -public void initialize_stacktrace(const char *program) -{ - bt_state = backtrace_create_state(program, 1, NULL, NULL); - if (!bt_state) - errx(1, "Failed to create stacktrace state"); -} +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/stdlib/stacktrace.h b/src/stdlib/stacktrace.h index e3e04814..828bbe98 100644 --- a/src/stdlib/stacktrace.h +++ b/src/stdlib/stacktrace.h @@ -1,5 +1,5 @@ #pragma once #include <stdio.h> -void initialize_stacktrace(const char *program); +__attribute__ ((noinline)) void print_stacktrace(FILE *out, int offset); diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index 51983bc8..ae2079e3 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -32,7 +32,7 @@ void _tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, int spe fflush(stdout); \ if (USE_COLOR) fputs("\x1b[31;7m ==================== ERROR ==================== \033[m\n\n", stderr); \ else fputs("==================== ERROR ====================\n\n", stderr); \ - print_stacktrace(stderr, 2); \ + print_stacktrace(stderr, 1); \ if (USE_COLOR) fputs("\n\x1b[31;1m", stderr); \ else fputs("\n", stderr); \ fprint_inline(stderr, "Error: ", __VA_ARGS__); \ @@ -56,7 +56,7 @@ void _tomo_parse_args(int argc, char *argv[], Text_t usage, Text_t help, int spe fputs("\n", stderr); \ } \ if (USE_COLOR) fputs("\x1b[m", stderr); \ - print_stacktrace(stderr, 2); \ + print_stacktrace(stderr, 1); \ fflush(stderr); \ raise(SIGABRT); \ _exit(1); \ diff --git a/src/stdlib/text.c b/src/stdlib/text.c index 3346bf4b..21d1a3db 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -68,6 +68,7 @@ #include <unistring/version.h> #include <uniwidth.h> +#include "datatypes.h" #include "lists.h" #include "integers.h" #include "tables.h" @@ -759,9 +759,7 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t paths_str(object_files), " -x c - -o ", exe_path); CORD program = CORD_all( "extern int ", main_binding->code, "$parse_and_run(int argc, char *argv[]);\n" - "extern void initialize_stacktrace(const char *program);\n" "int main(int argc, char *argv[]) {\n" - "\tinitialize_stacktrace(argv[0]);\n" "\treturn ", main_binding->code, "$parse_and_run(argc, argv);\n" "}\n" ); |
