[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:
parent
bc274b854d
commit
df38f35acb
|
@ -56,6 +56,13 @@ enum Iostat {
|
|||
IostatBackspaceAtFirstRecord,
|
||||
IostatRewindNonSequential,
|
||||
IostatWriteAfterEndfile,
|
||||
IostatFormattedIoOnUnformattedUnit,
|
||||
IostatUnformattedIoOnFormattedUnit,
|
||||
IostatListIoOnDirectAccessUnit,
|
||||
IostatUnformattedChildOnFormattedParent,
|
||||
IostatFormattedChildOnUnformattedParent,
|
||||
IostatChildInputFromOutputParent,
|
||||
IostatChildOutputToInputParent,
|
||||
};
|
||||
|
||||
const char *IostatErrorString(int);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 = ∈
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue