055cc335c485aa94f48b440b0105eae2e1ecacb4 braney Thu Sep 10 09:41:34 2026 -0700 lib/fa.c: grow the FASTA read buffer to hold the line being added, refs #38320 faMixedSpeedReadNext decided to grow its buffer when bufIx + lineSize no longer fit, but then asked expandFaFastBuf to reach only lineSize. expandFaFastBuf stops doubling as soon as it meets that size, so it could hand back a buffer smaller than bufIx + lineSize. The copy loop wrote the whole line regardless. Ask for bufIx + lineSize + 1, which covers the line and the terminating NUL written after the loop. The other two calls each add one byte to a buffer that is exactly full, so doubling always covers them and they are unchanged. Doubling usually leaves enough room, which is why this took so long to show up. It needs a record whose lines vary a lot in length. faToTwoBit aborted on such a file with a corrupted heap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> diff --git src/lib/fa.c src/lib/fa.c index 61f448b5ed0..5ab33251fe4 100644 --- src/lib/fa.c +++ src/lib/fa.c @@ -459,31 +459,31 @@ else { errAbort("Expecting '>' line %d of %s", lf->lineIx, lf->fileName); } /* Read until next '>' */ for (;;) { if (!lineFileNext(lf, &line, &lineSize)) break; if (line[0] == '>') { lineFileReuse(lf); break; } if (bufIx + lineSize >= faFastBufSize) - expandFaFastBuf(bufIx, lineSize); + expandFaFastBuf(bufIx, bufIx + lineSize + 1); for (i=0; i<lineSize; ++i) { c = line[i]; if (isalpha(c) || c == '-') faFastBuf[bufIx++] = c; } } if (bufIx >= faFastBufSize) expandFaFastBuf(bufIx, 0); faFastBuf[bufIx] = 0; *retDna = faFastBuf; *retSize = bufIx; *retName = name; if (bufIx == 0) {