[flang] Allow write after non advancing read in IO runtime

1. To avoid overwriting the part of the record read in the non advancing read,
the furtherPositionInRecord field must be set to the max of the
furtherPositionInRecord and the positionInRecord at the beginning of the
IO write.

2. To allow any further read to succeed after the write, the unit
beganReadingRecord_ must be set to false when resetting the recordLength
during the write, otherwise, recordLength will not be computed in further
read and an assert is hit (at unit.cpp(398)).

The added unit test exercises both of these scenarios.

Differential Revision: https://reviews.llvm.org/D113740
This commit is contained in:
Jean Perier 2021-11-16 14:52:35 +01:00
parent 422cf2b506
commit 2e65c8e8db
3 changed files with 107 additions and 2 deletions

View File

@ -264,7 +264,16 @@ template <Direction DIR>
ExternalIoStatementState<DIR>::ExternalIoStatementState(
ExternalFileUnit &unit, const char *sourceFile, int sourceLine)
: ExternalIoStatementBase{unit, sourceFile, sourceLine}, mutableModes_{
unit.modes} {}
unit.modes} {
if constexpr (DIR == Direction::Output) {
// If the last statement was a non advancing IO input statement, the unit
// furthestPositionInRecord was not advanced, but the positionInRecord may
// have been advanced. Advance furthestPositionInRecord here to avoid
// overwriting the part of the record that has been read with blanks.
unit.furthestPositionInRecord =
std::max(unit.furthestPositionInRecord, unit.positionInRecord);
}
}
template <Direction DIR> int ExternalIoStatementState<DIR>::EndIoStatement() {
if constexpr (DIR == Direction::Input) {

View File

@ -261,9 +261,10 @@ bool ExternalFileUnit::Emit(const char *data, std::size_t bytes,
if (recordLength) {
// It is possible for recordLength to have a value now for a
// variable-length output record if the previous operation
// was a BACKSPACE.
// was a BACKSPACE or non advancing input statement.
if (!isFixedRecordLength) {
recordLength.reset();
beganReadingRecord_ = false;
} else if (furthestAfter > *recordLength) {
handler.SignalError(IostatRecordWriteOverrun,
"Attempt to write %zd bytes to position %jd in a fixed-size record "

View File

@ -537,3 +537,98 @@ TEST(ExternalIOTests, TestNonAvancingInput) {
j++;
}
}
TEST(ExternalIOTests, TestWriteAfterNonAvancingInput) {
// OPEN(NEWUNIT=unit,ACCESS='SEQUENTIAL',ACTION='READWRITE',&
// FORM='FORMATTED',STATUS='SCRATCH')
auto *io{IONAME(BeginOpenNewUnit)(__FILE__, __LINE__)};
ASSERT_TRUE(IONAME(SetAccess)(io, "SEQUENTIAL", 10))
<< "SetAccess(SEQUENTIAL)";
ASSERT_TRUE(IONAME(SetAction)(io, "READWRITE", 9)) << "SetAction(READWRITE)";
ASSERT_TRUE(IONAME(SetForm)(io, "FORMATTED", 9)) << "SetForm(FORMATTED)";
ASSERT_TRUE(IONAME(SetStatus)(io, "SCRATCH", 7)) << "SetStatus(SCRATCH)";
int unit{-1};
ASSERT_TRUE(IONAME(GetNewUnit)(io, unit)) << "GetNewUnit()";
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
<< "EndIoStatement() for OpenNewUnit";
// Write the file to be used for the input test.
static constexpr std::string_view records[] = {"ABCDEFGHIJKLMNOPQRST"};
static constexpr std::string_view fmt{"(A)"};
for (const auto &record : records) {
// WRITE(UNIT=unit,FMT=fmt) record
io = IONAME(BeginExternalFormattedOutput)(
fmt.data(), fmt.length(), unit, __FILE__, __LINE__);
ASSERT_TRUE(IONAME(OutputAscii)(io, record.data(), record.length()))
<< "OutputAscii()";
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
<< "EndIoStatement() for OutputAscii";
}
// REWIND(UNIT=unit)
io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
<< "EndIoStatement() for Rewind";
struct TestItems {
std::string item;
int expectedIoStat;
std::string expectedItemValue;
};
// Actual non advancing input IO test
TestItems inputItems[]{
{std::string(4, '+'), IostatOk, "ABCD"},
{std::string(4, '+'), IostatOk, "EFGH"},
};
int j{0};
for (auto &inputItem : inputItems) {
// READ(UNIT=unit, FMT=fmt, ADVANCE='NO', IOSTAT=iostat) inputItem
io = IONAME(BeginExternalFormattedInput)(
fmt.data(), fmt.length(), unit, __FILE__, __LINE__);
IONAME(EnableHandlers)(io, true, false, false, false, false);
ASSERT_TRUE(IONAME(SetAdvance)(io, "NO", 2)) << "SetAdvance(NO)" << j;
ASSERT_TRUE(
IONAME(InputAscii)(io, inputItem.item.data(), inputItem.item.length()))
<< "InputAscii() " << j;
ASSERT_EQ(IONAME(EndIoStatement)(io), inputItem.expectedIoStat)
<< "EndIoStatement() for Read " << j;
ASSERT_EQ(inputItem.item, inputItem.expectedItemValue)
<< "Input-item value after non advancing read " << j;
j++;
}
// WRITE(UNIT=unit, FMT=fmt, IOSTAT=iostat) outputItem.
static constexpr std::string_view outputItem{"XYZ"};
// WRITE(UNIT=unit,FMT=fmt) record
io = IONAME(BeginExternalFormattedOutput)(
fmt.data(), fmt.length(), unit, __FILE__, __LINE__);
ASSERT_TRUE(IONAME(OutputAscii)(io, outputItem.data(), outputItem.length()))
<< "OutputAscii()";
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
<< "EndIoStatement() for OutputAscii";
// Verify that the output was written in the record read in non avdancing
// mode, after the read part, and that the end was truncated.
// REWIND(UNIT=unit)
io = IONAME(BeginRewind)(unit, __FILE__, __LINE__);
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
<< "EndIoStatement() for Rewind";
std::string resultRecord(20, '+');
std::string expectedRecord{"ABCDEFGHXYZ "};
// READ(UNIT=unit, FMT=fmt, IOSTAT=iostat) result
io = IONAME(BeginExternalFormattedInput)(
fmt.data(), fmt.length(), unit, __FILE__, __LINE__);
IONAME(EnableHandlers)(io, true, false, false, false, false);
ASSERT_TRUE(
IONAME(InputAscii)(io, resultRecord.data(), resultRecord.length()))
<< "InputAscii() ";
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)
<< "EndIoStatement() for Read ";
ASSERT_EQ(resultRecord, expectedRecord)
<< "Record after non advancing read followed by wrote";
}