Read entire file into heap-allocated buffer. Returns NULL on error. * Caller must free(). Sets *out_len to byte count. *out_size receives the * on-disk size and *out_status the failure reason, so the caller can attribute * a skip to the right phase/reason (read vs oversized) instead of a silent * drop. Both out params may be NULL. */
| 38 | * a skip to the right phase/reason (read vs oversized) instead of a silent |
| 39 | * drop. Both out params may be NULL. */ |
| 40 | static char *read_file(const char *path, int *out_len, long *out_size, |
| 41 | cbm_read_status_t *out_status) { |
| 42 | if (out_size) { |
| 43 | *out_size = 0; |
| 44 | } |
| 45 | if (out_status) { |
| 46 | *out_status = CBM_READ_OK; |
| 47 | } |
| 48 | FILE *f = cbm_fopen(path, "rb"); |
| 49 | if (!f) { |
| 50 | if (out_status) { |
| 51 | *out_status = CBM_READ_OPEN_FAIL; |
| 52 | } |
| 53 | return NULL; |
| 54 | } |
| 55 | |
| 56 | (void)fseek(f, 0, SEEK_END); |
| 57 | long size = ftell(f); |
| 58 | (void)fseek(f, 0, SEEK_SET); |
| 59 | if (out_size) { |
| 60 | *out_size = size; |
| 61 | } |
| 62 | |
| 63 | if (size <= 0) { |
| 64 | (void)fclose(f); |
| 65 | if (out_status) { |
| 66 | *out_status = CBM_READ_EMPTY; |
| 67 | } |
| 68 | return NULL; |
| 69 | } |
| 70 | if (size > cbm_max_file_bytes()) { /* generous, env-configurable cap (B4) */ |
| 71 | (void)fclose(f); |
| 72 | if (out_status) { |
| 73 | *out_status = CBM_READ_OVERSIZED; |
| 74 | } |
| 75 | return NULL; |
| 76 | } |
| 77 | |
| 78 | /* +16 padding: tree-sitter's lexer peeks a few bytes past the final UTF-8 |
| 79 | * character when computing lookahead, reading beyond the logical end. |
| 80 | * Over-allocate and zero the tail so that read stays in-bounds (ASan |
| 81 | * flags it as a heap-buffer-overflow otherwise; harmless but real UB). */ |
| 82 | enum { CBM_TS_LOOKAHEAD_PAD = 16 }; |
| 83 | char *buf = malloc((size_t)size + CBM_TS_LOOKAHEAD_PAD); |
| 84 | if (!buf) { |
| 85 | (void)fclose(f); |
| 86 | if (out_status) { |
| 87 | *out_status = CBM_READ_OOM; |
| 88 | } |
| 89 | return NULL; |
| 90 | } |
| 91 | |
| 92 | size_t nread = fread(buf, SKIP_ONE, size, f); |
| 93 | (void)fclose(f); |
| 94 | |
| 95 | if (nread > (size_t)size) { |
| 96 | nread = (size_t)size; |
| 97 | } |
no test coverage detected