[llvm] [flang-rt] Fix BACKSPACE crash on files without trailing newline (PR #227661)
Kaviya Rajendiran via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 30 04:22:32 PDT 2026
https://github.com/kaviya2510 created https://github.com/llvm/llvm-project/pull/227661
Calling BACKSPACE on a formatted sequential file crashes with `error: "Sequential record missing its terminator"` when the last record does not end with '\n'.
#### Root cause:
`BackspaceVariableFormattedRecord(...)` assumed that every record ends with a newline character`(\n)`. To locate the previous record, it examines the byte immediately before the current file position and expects that byte to be `'\n'`, then scans backward to find the start of the record. When the final record is unterminated, the byte before the current position belongs to the record data rather than a record terminator, it reports `IostatMissingTerminator` error.
#### Fix:
Updated `BackspaceVariableFormattedRecord(...)` to recognize end-of-file as a valid record terminator, matching the behavior of `BeginVariableFormattedInputRecord(...)`. This allows `BACKSPACE(...)` to correctly handle an unterminated final record.
Assisted by: Cursor
Fixes https://github.com/llvm/llvm-project/issues/227630
>From 9f4ce0c0cef9c8504e600a435dffef9bbbedaf69 Mon Sep 17 00:00:00 2001
From: Kaviya Rajendiran <kaviyara2000 at gmail.com>
Date: Wed, 30 Sep 2026 16:32:05 +0530
Subject: [PATCH] [flang-rt] Fix BACKSPACE crash on files without trailing
newline
---
flang-rt/lib/runtime/unit.cpp | 35 +++++++++-----
.../test/Driver/backspace-unterminated.f90 | 47 +++++++++++++++++++
2 files changed, 70 insertions(+), 12 deletions(-)
create mode 100644 flang-rt/test/Driver/backspace-unterminated.f90
diff --git a/flang-rt/lib/runtime/unit.cpp b/flang-rt/lib/runtime/unit.cpp
index cbf802d3b8225..894a9458fd32a 100644
--- a/flang-rt/lib/runtime/unit.cpp
+++ b/flang-rt/lib/runtime/unit.cpp
@@ -762,39 +762,50 @@ static RT_API_ATTRS const char *FindLastNewline(
void ExternalFileUnit::BackspaceVariableFormattedRecord(
IoErrorHandler &handler) {
// File offset of previous record's newline
- auto prevNL{
+ auto lastByte{
frameOffsetInFile_ + static_cast<std::int64_t>(recordOffsetInFrame_) - 1};
- if (prevNL < 0) {
+ if (lastByte < 0) {
handler.SignalError(IostatBackspaceAtFirstRecord);
return;
}
+ if (!(lastByte >= FrameAt() &&
+ lastByte - FrameAt() < static_cast<std::int64_t>(FrameLength()))) {
+ frameOffsetInFile_ = std::max<std::int64_t>(0, lastByte + 1 - 1024);
+ auto need{static_cast<std::size_t>(lastByte + 1 - frameOffsetInFile_)};
+ if (ReadFrame(frameOffsetInFile_, need, handler) < need) {
+ handler.SignalError(IostatShortRead);
+ return;
+ }
+ }
+ bool terminated{Frame()[lastByte - FrameAt()] == '\n'};
+ if (!terminated && knownSize().value_or(lastByte + 1) != lastByte + 1) {
+ handler.SignalError(IostatMissingTerminator);
+ return;
+ }
+ auto recordEnd{terminated ? lastByte : lastByte + 1};
while (true) {
- if (frameOffsetInFile_ < prevNL) {
+ if (frameOffsetInFile_ < recordEnd) {
if (const char *p{
- FindLastNewline(Frame(), prevNL - 1 - frameOffsetInFile_)}) {
+ FindLastNewline(Frame(), recordEnd - 1 - frameOffsetInFile_)}) {
recordOffsetInFrame_ = p - Frame() + 1;
- recordLength = prevNL - (frameOffsetInFile_ + recordOffsetInFrame_);
+ recordLength = recordEnd - (frameOffsetInFile_ + recordOffsetInFrame_);
break;
}
}
if (frameOffsetInFile_ == 0) {
recordOffsetInFrame_ = 0;
- recordLength = prevNL;
+ recordLength = recordEnd;
break;
}
frameOffsetInFile_ -= std::min<std::int64_t>(frameOffsetInFile_, 1024);
- auto need{static_cast<std::size_t>(prevNL + 1 - frameOffsetInFile_)};
+ auto need{static_cast<std::size_t>(lastByte + 1 - frameOffsetInFile_)};
auto got{ReadFrame(frameOffsetInFile_, need, handler)};
if (got < need) {
handler.SignalError(IostatShortRead);
return;
}
}
- if (Frame()[recordOffsetInFrame_ + *recordLength] != '\n') {
- handler.SignalError(IostatMissingTerminator);
- return;
- }
- if (*recordLength > 0 &&
+ if (terminated && *recordLength > 0 &&
Frame()[recordOffsetInFrame_ + *recordLength - 1] == '\r') {
--*recordLength;
}
diff --git a/flang-rt/test/Driver/backspace-unterminated.f90 b/flang-rt/test/Driver/backspace-unterminated.f90
new file mode 100644
index 0000000000000..ca468ce7f9a6b
--- /dev/null
+++ b/flang-rt/test/Driver/backspace-unterminated.f90
@@ -0,0 +1,47 @@
+! The last record of a formatted sequential file need not be terminated
+! by a newline; BACKSPACE must be able to reposition over such a record.
+
+! RUN: %flang %isysroot -L"%libdir" %s -o %t
+! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t | FileCheck %s
+
+! CHECK: PASS: unterm1
+! CHECK: PASS: unterm2
+
+program backspace_unterminated
+ implicit none
+ call check('unterm1', 'ABCDEFGHIJ', 1)
+ call check('unterm2', 'AAA' // new_line('a') // 'BBB', 2)
+ print *, 'PASS'
+contains
+ subroutine check(name, contents, nrecs)
+ character(*), intent(in) :: name, contents
+ integer, intent(in) :: nrecs
+ character(len=32) :: buf, again
+ integer :: iu, i, stat
+
+ open (newunit=iu, file=name, status='replace', action='write', &
+ form='unformatted', access='stream')
+ write (iu) contents
+ close (iu)
+
+ open (newunit=iu, file=name, status='old', action='read', &
+ form='formatted', access='sequential')
+ do i = 1, nrecs
+ read (iu, '(A)') buf
+ end do
+ backspace (iu, iostat=stat)
+ if (stat /= 0) then
+ print *, 'FAIL: ' // name // ': backspace failed'
+ stop 1
+ end if
+ read (iu, '(A)') again
+ if (again /= buf) then
+ print *, 'FAIL: ' // name // ': reread mismatch'
+ stop 1
+ end if
+ print *, 'PASS: ' // name
+ close (iu, status='delete')
+ end subroutine
+end program
+
+! CHECK: PASS
More information about the llvm-commits
mailing list