123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 | // Built-in functions
#include <stdio.h> // Must be before gmp.h
#include <errno.h>
#include <fcntl.h>
#include <gc.h>
#include <gmp.h>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/param.h>
#include <time.h>
#include <unistd.h>
#include "../config.h"
#include "../util.h"
#include "bool.h"
#include "files.h"
#include "metamethods.h"
#include "number.h"
#include "optionals.h"
#include "path.h"
#include "print.h"
#include "random.h"
#include "siphash.h"
#include "stacktrace.h"
#include "stdlib.h"
#include "text.h"
#include "util.h"
public
bool USE_COLOR;
public
const char *TOMO_PATH = "/usr/local";
public
const char *TOMO_VERSION = "v0";
public
Text_t TOMO_VERSION_TEXT = Text("v0");
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <dlfcn.h>
static inline const char *get_library_path(void *func) {
static Dl_info info;
if (dladdr(func, &info) && info.dli_fname && info.dli_fname[0]) {
return info.dli_fname; // full path of the library
}
#if defined(__linux__)
// In a fully static binary there is no shared object for dladdr() to report,
// but the code that would live in libtomo is linked directly into the
// executable, so the executable's own path is the right thing to return.
static char exe_path[PATH_MAX];
ssize_t n = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
if (n > 0) {
exe_path[n] = '\0';
return exe_path;
}
#endif
return NULL;
}
#elif defined(_WIN32)
#include <windows.h>
static inline const char *get_library_path(void *func) {
static char path[MAX_PATH];
HMODULE hm = NULL;
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCSTR)func, &hm)) {
if (GetModuleFileName(hm, path, MAX_PATH)) {
return path;
}
}
return NULL;
}
#else
#error "Unsupported platform"
#endif
char *find_in_path(const char *name) {
if (strchr(name, '/')) {
// name contains a slash → treat as path
char *abs = realpath(name, NULL);
if (abs == NULL) fail("Couldn't find real path of: ", name);
char *ret = String(abs);
free(abs);
return ret;
}
char *path_env = getenv("PATH");
if (!path_env) return NULL;
char *paths = strdup(path_env);
if (!paths) return NULL;
char *token = strtok(paths, ":");
while (token) {
char candidate[PATH_MAX];
snprintf(candidate, sizeof(candidate), "%s/%s", token, name);
if (access(candidate, X_OK) == 0) {
char *abs = realpath(candidate, NULL);
free(paths);
char *ret = String(abs);
free(abs);
return ret;
}
token = strtok(NULL, ":");
}
free(paths);
return NULL; // not found
}
public
void tomo_configure(void) {
const char *p = get_library_path(get_library_path);
p = find_in_path(p);
Path_t path = Path$from_str(p);
OptionalPath_t lib_dir = Path$parent(path);
OptionalPath_t prefix = lib_dir ? Path$parent(lib_dir) : NULL;
assert(prefix); // Tomo installs as PREFIX/lib/libtomo@<version>.so
TOMO_PATH = Path$as_c_string(prefix);
OptionalText_t base_name = Path$base_name(path);
if (base_name.tag == TEXT_NONE) fail("Tomo is installed at a path that is not valid UTF-8: ", path);
TOMO_VERSION_TEXT = Text$without_suffix(
Text$without_prefix(Text$without_prefix(base_name, Text("lib")), Text("tomo@")), Text(".so"));
TOMO_VERSION = Text$as_c_string(TOMO_VERSION_TEXT);
}
static _Noreturn void signal_handler(int sig, siginfo_t *info, void *userdata) {
(void)info, (void)userdata;
if (sig == SIGILL) {
fflush(stdout);
if (USE_COLOR) fputs("\x1b[91;7m ===== ILLEGAL INSTRUCTION ===== \n\n\x1b[m", stderr);
else fputs("===== ILLEGAL INSTRUCTION =====\n\n", stderr);
print_stacktrace(stderr, 3);
fflush(stderr);
tomo_cleanup();
if (Bool$parse(Text$from_str(getenv("TOMO_CORE_DUMP")), NULL) == true) raise(SIGABRT);
raise(SIGABRT);
_exit(1);
}
// A crash skips atexit(), so run the cleanups here instead. Draining the
// list makes this idempotent, which matters because the SIGILL branch
// above raises SIGABRT and lands back in this handler.
tomo_cleanup();
raise(sig);
_exit(1);
}
// GMP allocates the limb storage behind every bignum through these hooks.
// Route them to the garbage collector so that storage is reclaimed
// automatically: otherwise every mpz result copied into a GC object (see
// Int$from_mpz) leaks its malloc'd limbs, and the temporary mpz_t locals in
// the arithmetic helpers are never cleared and leak outright. Limbs hold no
// pointers, so atomic allocation is both safe and keeps them off the GC's scan
// set (important: limb bit-patterns would otherwise look like pointers and
// falsely retain garbage). GC_REALLOC preserves the atomic kind of the block.
static void *gc_gmp_alloc(size_t size) {
return GC_MALLOC_ATOMIC(size);
}
static void *gc_gmp_realloc(void *ptr, size_t old_size, size_t new_size) {
(void)old_size;
return GC_REALLOC(ptr, new_size);
}
static void gc_gmp_free(void *ptr, size_t size) {
(void)ptr;
(void)size; // reclaimed by the GC
}
public
void tomo_init(void) {
GC_INIT();
mp_set_memory_functions(gc_gmp_alloc, gc_gmp_realloc, gc_gmp_free);
tomo_configure();
const char *color_env = getenv("COLOR");
USE_COLOR = color_env ? strcmp(color_env, "1") == 0 : isatty(STDOUT_FILENO);
const char *no_color_env = getenv("NO_COLOR");
if (no_color_env && no_color_env[0] != '\0') USE_COLOR = false;
setlocale(LC_ALL, "");
assert(getrandom(TOMO_HASH_KEY, sizeof(TOMO_HASH_KEY), 0) == sizeof(TOMO_HASH_KEY));
// Register signal handler for all fatal signals:
struct sigaction sigact;
sigact.sa_sigaction = signal_handler;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_SIGINFO | SA_NODEFER | SA_RESETHAND;
int fatal[] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGUSR1, SIGSEGV,
SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGIO, SIGSYS};
for (size_t i = 0; i < sizeof(fatal) / sizeof(fatal[0]); i++) {
(void)sigaction(fatal[i], &sigact, NULL);
}
atexit(tomo_cleanup);
}
static int _inspect_depth = 0;
static file_t *file = NULL;
__attribute__((nonnull)) public
void start_inspect(const char *filename, int64_t start, int64_t end) {
if (file == NULL || strcmp(file->filename, filename) != 0) file = load_file(filename);
if (file) {
size_t first_line_len = strcspn(file->text + start, "\r\n");
if (first_line_len > (size_t)(end - start)) first_line_len = (size_t)(end - start);
const char *slash = strrchr(filename, '/');
const char *file_base = slash ? slash + 1 : filename;
int64_t line_num = get_line_number(file, file->text + start);
if (USE_COLOR) {
print(repeated_char(' ', 3 * _inspect_depth), "\x1b[93;1m>> \x1b[m",
string_slice(file->text + start, first_line_len), " ",
repeated_char(' ', MAX(0, 35 - (int64_t)first_line_len - 3 * _inspect_depth)), "\x1b[32;2m[",
file_base, ":", line_num, "]\x1b[m");
} else {
print(repeated_char(' ', 3 * _inspect_depth), ">> ", string_slice(file->text + start, first_line_len),
" ", repeated_char(' ', MAX(0, 35 - (int64_t)first_line_len - 3 * _inspect_depth)), "[", file_base,
":", line_num, "]");
}
// For multi-line expressions, dedent each and print it on a new line with ".. " in front:
if (end > start + (int64_t)first_line_len) {
const char *line_start = get_line(file, line_num);
int64_t indent_len = (int64_t)strspn(line_start, " \t");
for (const char *line = file->text + start + first_line_len; line < file->text + end;
line += strcspn(line, "\r\n")) {
line += strspn(line, "\r\n");
if ((int64_t)strspn(line, " \t") >= indent_len) line += indent_len;
print(repeated_char(' ', 3 * _inspect_depth), USE_COLOR ? "\x1b[33m..\033[m " : ".. ",
string_slice(line, strcspn(line, "\r\n")));
}
}
}
_inspect_depth += 1;
}
public
void end_inspect(const void *expr, const TypeInfo_t *type) {
_inspect_depth -= 1;
if (type && type->metamethods.as_text) {
Text_t expr_text = generic_as_text(expr, USE_COLOR, type);
Text_t type_name = generic_as_text(NULL, false, type);
for (int i = 0; i < 3 * _inspect_depth; i++)
fputc(' ', stdout);
fprint(stdout, USE_COLOR ? "\x1b[93;1m=\x1b[0m " : "= ", expr_text, USE_COLOR ? " \x1b[33;2m: \x1b[36m" : " : ",
type_name, USE_COLOR ? "\033[m" : "");
}
}
public
void say(Text_t text, bool newline) {
Text$print(stdout, text);
if (newline) fputc('\n', stdout);
fflush(stdout);
}
public
_Noreturn void tomo_exit(Text_t text, int32_t status) {
if (text.length > 0) print(text);
exit(status);
}
public
OptionalText_t ask(Text_t prompt, bool bold, bool force_tty) {
OptionalText_t ret = NONE_TEXT;
FILE *out = stdout;
FILE *in = stdin;
bool opened_out = false, opened_in = false;
char *line = NULL;
size_t bufsize = 0;
ssize_t length = 0;
char *gc_input = NULL;
if (force_tty && !isatty(STDOUT_FILENO)) {
out = fopen("/dev/tty", "w");
if (!out) goto cleanup;
opened_out = true;
}
if (bold) fputs("\x1b[1m", out);
Text$print(out, prompt);
if (bold) fputs("\x1b[m", out);
fflush(out);
if (force_tty && !isatty(STDIN_FILENO)) {
in = fopen("/dev/tty", "r");
if (!in) {
fputs("\n", out); // finish the line, since the user can't
goto cleanup;
}
opened_in = true;
}
length = getline(&line, &bufsize, in);
if (length == -1) {
fputs("\n", out); // finish the line, since we didn't get any input
goto cleanup;
}
if (length > 0 && line[length - 1] == '\n') {
line[length - 1] = '\0';
--length;
}
gc_input = GC_MALLOC_ATOMIC((size_t)(length + 1));
memcpy(gc_input, line, (size_t)(length + 1));
ret = Text$from_strn(gc_input, (size_t)(length));
cleanup:
if (opened_out) fclose(out);
if (opened_in) fclose(in);
if (line != NULL) free(line);
return ret;
}
public
void sleep_seconds(Num_t seconds) {
double secs = number_to_double(seconds);
if (secs < 0) fail("Cannot sleep for a negative amount of time: ", secs);
else if (isnan(secs)) fail("Cannot sleep for a time that is NaN");
struct timespec ts;
ts.tv_sec = (time_t)secs;
ts.tv_nsec = (long)((secs - (double)ts.tv_sec) * 1e9);
while (nanosleep(&ts, NULL) != 0) {
if (errno == EINTR) continue;
fail("Failed to sleep for the requested time (", strerror(errno), ")");
}
}
public
OptionalText_t getenv_text(Text_t name) {
const char *val = getenv(Text$as_c_string(name));
return val ? Text$from_str(val) : NONE_TEXT;
}
public
void setenv_text(Text_t name, OptionalText_t value) {
int status;
if (value.tag == TEXT_NONE) {
status = unsetenv(Text$as_c_string(name));
} else {
status = setenv(Text$as_c_string(name), Text$as_c_string(value), 1);
}
if (status != 0) {
if (errno == EINVAL) fail("Invalid environment variable name: ", Text$quoted(name, false, Text("\"")));
else fail("Failed to set environment variable (", strerror(errno));
}
}
typedef struct cleanup_s {
Closure_t cleanup_fn;
struct cleanup_s *next;
} cleanup_t;
static cleanup_t *cleanups = NULL;
public
void tomo_at_cleanup(Closure_t fn) {
cleanup_t *new_cleanup = GC_MALLOC(sizeof(cleanup_t));
new_cleanup->cleanup_fn = fn;
new_cleanup->next = cleanups;
cleanups = new_cleanup;
}
public
void tomo_cleanup(void) {
while (cleanups) {
// NOTE: we *must* remove the cleanup function from the stack before calling it,
// otherwise it will cause an infinite loop if the cleanup function fails or exits.
void (*run_cleanup)(void *) = cleanups->cleanup_fn.fn;
void *userdata = cleanups->cleanup_fn.userdata;
cleanups = cleanups->next;
run_cleanup(userdata);
}
fflush(stdout);
fflush(stderr);
}
|