diff --git a/c/eval.c b/c/eval.c index f26c333..6b861fc 100644 --- a/c/eval.c +++ b/c/eval.c @@ -1636,13 +1636,36 @@ void load_file(const char *path, Env *env) { FILE *f = fopen(path, "r"); if (!f) lisp_error("file not found: %s", path); - fseek(f, 0, SEEK_END); - long sz = ftell(f); - fseek(f, 0, SEEK_SET); - - char *src = (char *)ul_malloc(sz + 1); - size_t nread = fread(src, 1, sz, f); - src[nread] = '\0'; + /* Seekable files (regular): size up-front, single read. + * Non-seekable files (pipes, FIFOs, sockets — e.g. bash process + * substitution <(...) yields /proc/self/fd/N pointing at a pipe): + * ftell returns -1, which silently casts to SIZE_MAX inside fread + * and overruns the heap. Stream-read into a growable buffer instead. */ + char *src = NULL; + long sz = -1; + if (fseek(f, 0, SEEK_END) == 0) { + sz = ftell(f); + if (fseek(f, 0, SEEK_SET) != 0) sz = -1; + } + if (sz >= 0) { + src = (char *)ul_malloc((size_t)sz + 1); + size_t nread = fread(src, 1, (size_t)sz, f); + src[nread] = '\0'; + } else { + size_t cap = 4096; + size_t len = 0; + src = (char *)ul_malloc(cap); + for (;;) { + if (len + 1 >= cap) { + cap *= 2; + src = (char *)ul_realloc(src, cap); + } + size_t got = fread(src + len, 1, cap - 1 - len, f); + len += got; + if (got == 0) break; + } + src[len] = '\0'; + } fclose(f); int count;