Skip to content

Commit

Permalink
[flang] Allow data transfer stmt control list errors to be caught
Browse files Browse the repository at this point in the history
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
  • Loading branch information
klausler committed Mar 1, 2022
1 parent bc274b8 commit df38f35
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 82 deletions.
7 changes: 7 additions & 0 deletions flang/include/flang/Runtime/iostat.h
Expand Up @@ -56,6 +56,13 @@ enum Iostat {
IostatBackspaceAtFirstRecord,
IostatRewindNonSequential,
IostatWriteAfterEndfile,
IostatFormattedIoOnUnformattedUnit,
IostatUnformattedIoOnFormattedUnit,
IostatListIoOnDirectAccessUnit,
IostatUnformattedChildOnFormattedParent,
IostatFormattedChildOnUnformattedParent,
IostatChildInputFromOutputParent,
IostatChildOutputToInputParent,
};

const char *IostatErrorString(int);
Expand Down
133 changes: 80 additions & 53 deletions flang/runtime/io-api.cpp
Expand Up @@ -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;
Expand All @@ -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;
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 (unit.access == Access::Direct) {
terminator.Crash("%s attempted on direct access file", what);
return nullptr;
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>
Expand All @@ -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);
}
}
}

Expand All @@ -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;
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 {
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 = 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;
}
}

Expand Down
7 changes: 6 additions & 1 deletion flang/runtime/io-stmt.cpp
Expand Up @@ -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"):
Expand Down Expand Up @@ -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
18 changes: 17 additions & 1 deletion flang/runtime/io-stmt.h
Expand Up @@ -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;
Expand Down Expand Up @@ -223,7 +224,8 @@ class IoStatementState {
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_;
};

Expand Down Expand Up @@ -674,5 +676,19 @@ class ExternalMiscIoStatementState : public ExternalIoStatementBase {
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_
14 changes: 14 additions & 0 deletions flang/runtime/iostat.cpp
Expand Up @@ -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;
}
Expand Down
37 changes: 15 additions & 22 deletions flang/runtime/unit.cpp
Expand Up @@ -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;
}
}
}
Expand All @@ -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;

Expand Down Expand Up @@ -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>>() !=
Expand All @@ -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;
}
}

Expand Down

0 comments on commit df38f35

Please sign in to comment.