[flang] Allow data transfer stmt control list errors to be caught

The runtime crashes on several fundamental I/O data transfer statement
control list errors, like list I/O on a direct-access unit, or
input from a write-only unit, &c.  These errors should not be fatal
when ERR= or IOSTAT= are present.

This patch creates a new ErroneousIoStatementState class and
uses it for the state of an I/O statement that is doomed to fail
from these errors.  If there is no ERR= label or IOSTAT= variable,
the error will be raised at the end of the statement.  Data transfer
operations along the way will be no-op failures.

Differential Revision: https://reviews.llvm.org/D120745
This commit is contained in:
Peter Klausler 2022-02-16 14:55:19 -08:00
parent bc274b854d
commit df38f35acb
7 changed files with 147 additions and 85 deletions

View file

@ -56,6 +56,13 @@ enum Iostat {
IostatBackspaceAtFirstRecord,
IostatRewindNonSequential,
IostatWriteAfterEndfile,
IostatFormattedIoOnUnformattedUnit,
IostatUnformattedIoOnFormattedUnit,
IostatListIoOnDirectAccessUnit,
IostatUnformattedChildOnFormattedParent,
IostatFormattedChildOnUnformattedParent,
IostatChildInputFromOutputParent,
IostatChildOutputToInputParent,
};
const char *IostatErrorString(int);

View file

@ -148,8 +148,8 @@ Cookie IONAME(BeginInternalFormattedInput)(const char *internal,
}
template <Direction DIR, template <Direction> class STATE, typename... A>
Cookie BeginExternalListIO(const char *what, int unitNumber,
const char *sourceFile, int sourceLine, A &&...xs) {
Cookie BeginExternalListIO(
int unitNumber, const char *sourceFile, int sourceLine, A &&...xs) {
Terminator terminator{sourceFile, sourceLine};
if (unitNumber == DefaultUnit) {
unitNumber = DIR == Direction::Input ? 5 : 6;
@ -159,38 +159,48 @@ Cookie BeginExternalListIO(const char *what, int unitNumber,
if (!unit.isUnformatted.has_value()) {
unit.isUnformatted = false;
}
Iostat iostat{IostatOk};
if (*unit.isUnformatted) {
terminator.Crash("%s attempted on unformatted file", what);
return nullptr;
iostat = IostatFormattedIoOnUnformattedUnit;
}
if (ChildIo * child{unit.GetChildIo()}) {
return child->CheckFormattingAndDirection(terminator, what, false, DIR)
? &child->BeginIoStatement<ChildListIoStatementState<DIR>>(
*child, sourceFile, sourceLine)
: nullptr;
} else {
if (unit.access == Access::Direct) {
terminator.Crash("%s attempted on direct access file", what);
return nullptr;
if (iostat == IostatOk) {
iostat = child->CheckFormattingAndDirection(false, DIR);
}
if (iostat == IostatOk) {
return &child->BeginIoStatement<ChildListIoStatementState<DIR>>(
*child, sourceFile, sourceLine);
} else {
return &child->BeginIoStatement<ErroneousIoStatementState>(
iostat, sourceFile, sourceLine);
}
} else {
if (iostat == IostatOk && unit.access == Access::Direct) {
iostat = IostatListIoOnDirectAccessUnit;
}
if (iostat == IostatOk) {
iostat = unit.SetDirection(DIR);
}
if (iostat == IostatOk) {
return &unit.BeginIoStatement<STATE<DIR>>(
std::forward<A>(xs)..., unit, sourceFile, sourceLine);
} else {
return &unit.BeginIoStatement<ErroneousIoStatementState>(
iostat, sourceFile, sourceLine);
}
IoErrorHandler handler{terminator};
unit.SetDirection(DIR, handler);
IoStatementState &io{unit.BeginIoStatement<STATE<DIR>>(
std::forward<A>(xs)..., unit, sourceFile, sourceLine)};
return &io;
}
}
Cookie IONAME(BeginExternalListOutput)(
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
return BeginExternalListIO<Direction::Output, ExternalListIoStatementState>(
"List-directed output", unitNumber, sourceFile, sourceLine);
unitNumber, sourceFile, sourceLine);
}
Cookie IONAME(BeginExternalListInput)(
ExternalUnit unitNumber, const char *sourceFile, int sourceLine) {
return BeginExternalListIO<Direction::Input, ExternalListIoStatementState>(
"List-directed input", unitNumber, sourceFile, sourceLine);
unitNumber, sourceFile, sourceLine);
}
template <Direction DIR>
@ -202,28 +212,35 @@ Cookie BeginExternalFormattedIO(const char *format, std::size_t formatLength,
}
ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous(
unitNumber, DIR, false /*!unformatted*/, terminator)};
Iostat iostat{IostatOk};
if (!unit.isUnformatted.has_value()) {
unit.isUnformatted = false;
}
if (*unit.isUnformatted) {
terminator.Crash("Formatted I/O attempted on unformatted file");
return nullptr;
iostat = IostatFormattedIoOnUnformattedUnit;
}
if (ChildIo * child{unit.GetChildIo()}) {
return child->CheckFormattingAndDirection(terminator,
DIR == Direction::Output ? "formatted output"
: "formatted input",
false, DIR)
? &child->BeginIoStatement<ChildFormattedIoStatementState<DIR>>(
*child, format, formatLength, sourceFile, sourceLine)
: nullptr;
if (iostat == IostatOk) {
iostat = child->CheckFormattingAndDirection(false, DIR);
}
if (iostat == IostatOk) {
return &child->BeginIoStatement<ChildFormattedIoStatementState<DIR>>(
*child, format, formatLength, sourceFile, sourceLine);
} else {
return &child->BeginIoStatement<ErroneousIoStatementState>(
iostat, sourceFile, sourceLine);
}
} else {
IoErrorHandler handler{terminator};
unit.SetDirection(DIR, handler);
IoStatementState &io{
unit.BeginIoStatement<ExternalFormattedIoStatementState<DIR>>(
unit, format, formatLength, sourceFile, sourceLine)};
return &io;
if (iostat == IostatOk) {
iostat = unit.SetDirection(DIR);
}
if (iostat == IostatOk) {
return &unit.BeginIoStatement<ExternalFormattedIoStatementState<DIR>>(
unit, format, formatLength, sourceFile, sourceLine);
} else {
return &unit.BeginIoStatement<ErroneousIoStatementState>(
iostat, sourceFile, sourceLine);
}
}
}
@ -247,35 +264,45 @@ Cookie BeginUnformattedIO(
Terminator terminator{sourceFile, sourceLine};
ExternalFileUnit &unit{ExternalFileUnit::LookUpOrCreateAnonymous(
unitNumber, DIR, true /*unformatted*/, terminator)};
Iostat iostat{IostatOk};
if (!unit.isUnformatted.has_value()) {
unit.isUnformatted = true;
}
if (!*unit.isUnformatted) {
terminator.Crash("Unformatted I/O attempted on formatted file");
iostat = IostatUnformattedIoOnFormattedUnit;
}
if (ChildIo * child{unit.GetChildIo()}) {
return child->CheckFormattingAndDirection(terminator,
DIR == Direction::Output ? "unformatted output"
: "unformatted input",
true, DIR)
? &child->BeginIoStatement<ChildUnformattedIoStatementState<DIR>>(
*child, sourceFile, sourceLine)
: nullptr;
} else {
IoStatementState &io{
unit.BeginIoStatement<ExternalUnformattedIoStatementState<DIR>>(
unit, sourceFile, sourceLine)};
IoErrorHandler handler{terminator};
unit.SetDirection(DIR, handler);
if constexpr (DIR == Direction::Output) {
if (unit.access == Access::Sequential) {
// Create space for (sub)record header to be completed by
// ExternalFileUnit::AdvanceRecord()
unit.recordLength.reset(); // in case of prior BACKSPACE
io.Emit("\0\0\0\0", 4); // placeholder for record length header
}
if (iostat == IostatOk) {
iostat = child->CheckFormattingAndDirection(true, DIR);
}
if (iostat == IostatOk) {
return &child->BeginIoStatement<ChildUnformattedIoStatementState<DIR>>(
*child, sourceFile, sourceLine);
} else {
return &child->BeginIoStatement<ErroneousIoStatementState>(
iostat, sourceFile, sourceLine);
}
} else {
if (iostat == IostatOk) {
iostat = unit.SetDirection(DIR);
}
if (iostat == IostatOk) {
IoStatementState &io{
unit.BeginIoStatement<ExternalUnformattedIoStatementState<DIR>>(
unit, sourceFile, sourceLine)};
if constexpr (DIR == Direction::Output) {
if (unit.access == Access::Sequential) {
// Create space for (sub)record header to be completed by
// ExternalFileUnit::AdvanceRecord()
unit.recordLength.reset(); // in case of prior BACKSPACE
io.Emit("\0\0\0\0", 4); // placeholder for record length header
}
}
return &io;
} else {
return &unit.BeginIoStatement<ErroneousIoStatementState>(
iostat, sourceFile, sourceLine);
}
return &io;
}
}

View file

@ -1130,7 +1130,7 @@ bool InquireUnitState::Inquire(
} else if (unit().openRecl) {
result = *unit().openRecl;
} else {
result = std::numeric_limits<std::uint32_t>::max();
result = std::numeric_limits<std::int32_t>::max();
}
return true;
case HashInquiryKeyword("SIZE"):
@ -1360,4 +1360,9 @@ bool InquireIOLengthState::Emit(const char32_t *p, std::size_t n) {
return true;
}
int ErroneousIoStatementState::EndIoStatement() {
SignalError(iostat_);
return IoStatementBase::EndIoStatement();
}
} // namespace Fortran::runtime::io

View file

@ -35,6 +35,7 @@ class InquireIOLengthState;
class ExternalMiscIoStatementState;
class CloseStatementState;
class NoopStatementState; // CLOSE or FLUSH on unknown unit
class ErroneousIoStatementState;
template <Direction, typename CHAR = char>
class InternalFormattedIoStatementState;
@ -223,7 +224,8 @@ private:
std::reference_wrapper<InquireNoUnitState>,
std::reference_wrapper<InquireUnconnectedFileState>,
std::reference_wrapper<InquireIOLengthState>,
std::reference_wrapper<ExternalMiscIoStatementState>>
std::reference_wrapper<ExternalMiscIoStatementState>,
std::reference_wrapper<ErroneousIoStatementState>>
u_;
};
@ -674,5 +676,19 @@ private:
Which which_;
};
class ErroneousIoStatementState : public IoStatementBase {
public:
explicit ErroneousIoStatementState(
Iostat iostat, const char *sourceFile = nullptr, int sourceLine = 0)
: IoStatementBase{sourceFile, sourceLine}, iostat_{iostat} {}
int EndIoStatement();
ConnectionState &GetConnectionState() { return connection_; }
MutableModes &mutableModes() { return connection_.modes; }
private:
Iostat iostat_;
ConnectionState connection_;
};
} // namespace Fortran::runtime::io
#endif // FORTRAN_RUNTIME_IO_STMT_H_

View file

@ -55,6 +55,20 @@ const char *IostatErrorString(int iostat) {
return "REWIND on non-sequential file";
case IostatWriteAfterEndfile:
return "WRITE after ENDFILE";
case IostatFormattedIoOnUnformattedUnit:
return "Formatted I/O on unformatted file";
case IostatUnformattedIoOnFormattedUnit:
return "Unformatted I/O on formatted file";
case IostatListIoOnDirectAccessUnit:
return "List-directed or NAMELIST I/O on direct-access file";
case IostatUnformattedChildOnFormattedParent:
return "Unformatted child I/O on formatted parent unit";
case IostatFormattedChildOnUnformattedParent:
return "Formatted child I/O on unformatted parent unit";
case IostatChildInputFromOutputParent:
return "Child input from output parent unit";
case IostatChildOutputToInputParent:
return "Child output to input parent unit";
default:
return nullptr;
}

View file

@ -181,25 +181,20 @@ void ExternalFileUnit::DestroyClosed() {
GetUnitMap().DestroyClosed(*this); // destroys *this
}
bool ExternalFileUnit::SetDirection(
Direction direction, IoErrorHandler &handler) {
Iostat ExternalFileUnit::SetDirection(Direction direction) {
if (direction == Direction::Input) {
if (mayRead()) {
direction_ = Direction::Input;
return true;
return IostatOk;
} else {
handler.SignalError(IostatReadFromWriteOnly,
"READ(UNIT=%d) with ACTION='WRITE'", unitNumber());
return false;
return IostatReadFromWriteOnly;
}
} else {
if (mayWrite()) {
direction_ = Direction::Output;
return true;
return IostatOk;
} else {
handler.SignalError(IostatWriteToReadOnly,
"WRITE(UNIT=%d) with ACTION='READ'", unitNumber());
return false;
return IostatWriteToReadOnly;
}
}
}
@ -220,21 +215,21 @@ UnitMap &ExternalFileUnit::GetUnitMap() {
ExternalFileUnit &out{newUnitMap->LookUpOrCreate(6, terminator, wasExtant)};
RUNTIME_CHECK(terminator, !wasExtant);
out.Predefine(1);
out.SetDirection(Direction::Output, handler);
handler.SignalError(out.SetDirection(Direction::Output));
out.isUnformatted = false;
defaultOutput = &out;
ExternalFileUnit &in{newUnitMap->LookUpOrCreate(5, terminator, wasExtant)};
RUNTIME_CHECK(terminator, !wasExtant);
in.Predefine(0);
in.SetDirection(Direction::Input, handler);
handler.SignalError(in.SetDirection(Direction::Input));
in.isUnformatted = false;
defaultInput = &in;
ExternalFileUnit &error{newUnitMap->LookUpOrCreate(0, terminator, wasExtant)};
RUNTIME_CHECK(terminator, !wasExtant);
error.Predefine(2);
error.SetDirection(Direction::Output, handler);
handler.SignalError(error.SetDirection(Direction::Output));
error.isUnformatted = false;
errorOutput = &error;
@ -872,8 +867,8 @@ void ChildIo::EndIoStatement() {
u_.emplace<std::monostate>();
}
bool ChildIo::CheckFormattingAndDirection(Terminator &terminator,
const char *what, bool unformatted, Direction direction) {
Iostat ChildIo::CheckFormattingAndDirection(
bool unformatted, Direction direction) {
bool parentIsInput{!parent_.get_if<IoDirectionState<Direction::Output>>()};
bool parentIsFormatted{parentIsInput
? parent_.get_if<FormattedIoStatementState<Direction::Input>>() !=
@ -882,15 +877,13 @@ bool ChildIo::CheckFormattingAndDirection(Terminator &terminator,
nullptr};
bool parentIsUnformatted{!parentIsFormatted};
if (unformatted != parentIsUnformatted) {
terminator.Crash("Child %s attempted on %s parent I/O unit", what,
parentIsUnformatted ? "unformatted" : "formatted");
return false;
return unformatted ? IostatUnformattedChildOnFormattedParent
: IostatFormattedChildOnUnformattedParent;
} else if (parentIsInput != (direction == Direction::Input)) {
terminator.Crash("Child %s attempted on %s parent I/O unit", what,
parentIsInput ? "input" : "output");
return false;
return parentIsInput ? IostatChildOutputToInputParent
: IostatChildInputFromOutputParent;
} else {
return true;
return IostatOk;
}
}

View file

@ -62,7 +62,7 @@ public:
void CloseUnit(CloseStatus, IoErrorHandler &);
void DestroyClosed();
bool SetDirection(Direction, IoErrorHandler &);
Iostat SetDirection(Direction);
template <typename A, typename... X>
IoStatementState &BeginIoStatement(X &&...xs) {
@ -128,7 +128,7 @@ private:
ExternalListIoStatementState<Direction::Input>,
ExternalUnformattedIoStatementState<Direction::Output>,
ExternalUnformattedIoStatementState<Direction::Input>, InquireUnitState,
ExternalMiscIoStatementState>
ExternalMiscIoStatementState, ErroneousIoStatementState>
u_;
// Points to the active alternative (if any) in u_ for use as a Cookie
@ -171,8 +171,7 @@ public:
OwningPtr<ChildIo> AcquirePrevious() { return std::move(previous_); }
bool CheckFormattingAndDirection(
Terminator &, const char *what, bool unformatted, Direction);
Iostat CheckFormattingAndDirection(bool unformatted, Direction);
private:
IoStatementState &parent_;
@ -183,7 +182,8 @@ private:
ChildListIoStatementState<Direction::Output>,
ChildListIoStatementState<Direction::Input>,
ChildUnformattedIoStatementState<Direction::Output>,
ChildUnformattedIoStatementState<Direction::Input>, InquireUnitState>
ChildUnformattedIoStatementState<Direction::Input>, InquireUnitState,
ErroneousIoStatementState>
u_;
std::optional<IoStatementState> io_;
};