diff --git a/flang/runtime/descriptor-io.h b/flang/runtime/descriptor-io.h --- a/flang/runtime/descriptor-io.h +++ b/flang/runtime/descriptor-io.h @@ -372,6 +372,10 @@ template static bool DescriptorIO(IoStatementState &io, const Descriptor &descriptor) { + IoErrorHandler &handler{io.GetIoErrorHandler()}; + if (handler.InError()) { + return false; + } if (!io.get_if>()) { io.GetIoErrorHandler().Crash( "DescriptorIO() called for wrong I/O direction"); @@ -385,7 +389,6 @@ if (!io.get_if>()) { return UnformattedDescriptorIO(io, descriptor); } - IoErrorHandler &handler{io.GetIoErrorHandler()}; if (auto catAndKind{descriptor.type().GetCategoryAndKind()}) { TypeCategory cat{catAndKind->first}; int kind{catAndKind->second}; diff --git a/flang/runtime/io-api.cpp b/flang/runtime/io-api.cpp --- a/flang/runtime/io-api.cpp +++ b/flang/runtime/io-api.cpp @@ -646,6 +646,9 @@ if (!open) { io.GetIoErrorHandler().Crash( "SetAccess() called when not in an OPEN statement"); + } else if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetAccess() called after GetNewUnit() for an OPEN statement"); } static const char *keywords[]{ "SEQUENTIAL", "DIRECT", "STREAM", "APPEND", nullptr}; @@ -675,6 +678,9 @@ if (!open) { io.GetIoErrorHandler().Crash( "SetAction() called when not in an OPEN statement"); + } else if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetAction() called after GetNewUnit() for an OPEN statement"); } std::optional action; static const char *keywords[]{"READ", "WRITE", "READWRITE", nullptr}; @@ -711,6 +717,9 @@ if (!open) { io.GetIoErrorHandler().Crash( "SetAsynchronous() called when not in an OPEN statement"); + } else if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetAsynchronous() called after GetNewUnit() for an OPEN statement"); } static const char *keywords[]{"YES", "NO", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { @@ -734,6 +743,9 @@ if (!open) { io.GetIoErrorHandler().Crash( "SetCarriageControl() called when not in an OPEN statement"); + } else if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetCarriageControl() called after GetNewUnit() for an OPEN statement"); } static const char *keywords[]{"LIST", "FORTRAN", "NONE", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { @@ -759,6 +771,9 @@ if (!open) { io.GetIoErrorHandler().Crash( "SetConvert() called when not in an OPEN statement"); + } else if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetConvert() called after GetNewUnit() for an OPEN statement"); } if (auto convert{GetConvertFromString(keyword, length)}) { open->set_convert(*convert); @@ -777,6 +792,9 @@ if (!open) { io.GetIoErrorHandler().Crash( "SetEncoding() called when not in an OPEN statement"); + } else if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetEncoding() called after GetNewUnit() for an OPEN statement"); } bool isUTF8{false}; static const char *keywords[]{"UTF-8", "DEFAULT", nullptr}; @@ -806,6 +824,9 @@ if (!open) { io.GetIoErrorHandler().Crash( "SetForm() called when not in an OPEN statement"); + } else if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetForm() called after GetNewUnit() for an OPEN statement"); } static const char *keywords[]{"FORMATTED", "UNFORMATTED", nullptr}; switch (IdentifyValue(keyword, length, keywords)) { @@ -829,6 +850,9 @@ if (!open) { io.GetIoErrorHandler().Crash( "SetPosition() called when not in an OPEN statement"); + } else if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetPosition() called after GetNewUnit() for an OPEN statement"); } static const char *positions[]{"ASIS", "REWIND", "APPEND", nullptr}; switch (IdentifyValue(keyword, length, positions)) { @@ -854,6 +878,9 @@ if (!open) { io.GetIoErrorHandler().Crash( "SetRecl() called when not in an OPEN statement"); + } else if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetRecl() called after GetNewUnit() for an OPEN statement"); } if (n <= 0) { io.GetIoErrorHandler().SignalError("RECL= must be greater than zero"); @@ -871,6 +898,10 @@ bool IONAME(SetStatus)(Cookie cookie, const char *keyword, std::size_t length) { IoStatementState &io{*cookie}; if (auto *open{io.get_if()}) { + if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetStatus() called after GetNewUnit() for an OPEN statement"); + } static const char *statuses[]{ "OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr}; switch (IdentifyValue(keyword, length, statuses)) { @@ -920,6 +951,10 @@ bool IONAME(SetFile)(Cookie cookie, const char *path, std::size_t chars) { IoStatementState &io{*cookie}; if (auto *open{io.get_if()}) { + if (open->completedOperation()) { + io.GetIoErrorHandler().Crash( + "SetFile() called after GetNewUnit() for an OPEN statement"); + } open->set_path(path, chars); return true; } @@ -934,6 +969,12 @@ if (!open) { io.GetIoErrorHandler().Crash( "GetNewUnit() called when not in an OPEN statement"); + } else if (!open->InError()) { + open->CompleteOperation(); + } + if (open->InError()) { + // A failed OPEN(NEWUNIT=n) does not modify 'n' + return false; } std::int64_t result{open->unit().unitNumber()}; if (!SetInteger(unit, kind, result)) { @@ -971,16 +1012,17 @@ bool IONAME(InputUnformattedBlock)( Cookie cookie, char *x, std::size_t length, std::size_t elementBytes) { IoStatementState &io{*cookie}; + IoErrorHandler &handler{io.GetIoErrorHandler()}; io.BeginReadingRecord(); - if (io.GetIoErrorHandler().InError()) { + if (handler.InError()) { return false; } if (auto *unf{ io.get_if>()}) { return unf->Receive(x, length, elementBytes); } - io.GetIoErrorHandler().Crash("InputUnformattedBlock() called for an I/O " - "statement that is not unformatted output"); + handler.Crash("InputUnformattedBlock() called for an I/O statement that is " + "not unformatted input"); return false; } @@ -1157,27 +1199,39 @@ std::size_t IONAME(GetSize)(Cookie cookie) { IoStatementState &io{*cookie}; + IoErrorHandler &handler{io.GetIoErrorHandler()}; + if (!handler.InError()) { + io.CompleteOperation(); + } if (const auto *formatted{ io.get_if>()}) { return formatted->GetEditDescriptorChars(); } - io.GetIoErrorHandler().Crash( + handler.Crash( "GetIoSize() called for an I/O statement that is not a formatted READ()"); return 0; } std::size_t IONAME(GetIoLength)(Cookie cookie) { IoStatementState &io{*cookie}; + IoErrorHandler &handler{io.GetIoErrorHandler()}; + if (!handler.InError()) { + io.CompleteOperation(); + } if (const auto *inq{io.get_if()}) { return inq->bytes(); } - io.GetIoErrorHandler().Crash("GetIoLength() called for an I/O statement that " - "is not INQUIRE(IOLENGTH=)"); + handler.Crash("GetIoLength() called for an I/O statement that is not " + "INQUIRE(IOLENGTH=)"); return 0; } void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) { - IoErrorHandler &handler{cookie->GetIoErrorHandler()}; + IoStatementState &io{*cookie}; + IoErrorHandler &handler{io.GetIoErrorHandler()}; + if (!handler.InError()) { + io.CompleteOperation(); + } if (handler.InError()) { // leave "msg" alone when no error handler.GetIoMsg(msg, length); } diff --git a/flang/runtime/io-error.h b/flang/runtime/io-error.h --- a/flang/runtime/io-error.h +++ b/flang/runtime/io-error.h @@ -36,7 +36,15 @@ flags_ = hasIoStat | hasErr | hasEnd | hasEor | hasIoMsg; } - bool InError() const { return ioStat_ != IostatOk; } + bool InError() const { + return ioStat_ != IostatOk || pendingError_ != IostatOk; + } + + // For I/O statements that detect fatal errors in their + // Begin...() API routines before it is known whether they + // have error handling control list items. Such statements + // have an ErroneousIoStatementState with a pending error. + void SetPendingError(int iostat) { pendingError_ = iostat; } void SignalError(int iostatOrErrno, const char *msg, ...); void SignalError(int iostatOrErrno); @@ -49,6 +57,7 @@ void SignalErrno(); // SignalError(errno) void SignalEnd(); // input only; EOF on internal write is an error void SignalEor(); // non-advancing input only; EOR on write is an error + void SignalPendingError(); int GetIoStat() const { return ioStat_; } bool GetIoMsg(char *, std::size_t); @@ -64,6 +73,7 @@ std::uint8_t flags_{0}; int ioStat_{IostatOk}; OwningPtr ioMsg_; + int pendingError_{IostatOk}; }; } // namespace Fortran::runtime::io diff --git a/flang/runtime/io-error.cpp b/flang/runtime/io-error.cpp --- a/flang/runtime/io-error.cpp +++ b/flang/runtime/io-error.cpp @@ -27,7 +27,7 @@ ioStat_ = IostatEor; // least priority } } else if (iostatOrErrno != IostatOk) { - if (flags_ & (hasIoStat | hasErr)) { + if (flags_ & (hasIoStat | hasIoMsg | hasErr)) { if (ioStat_ <= 0) { ioStat_ = iostatOrErrno; // priority over END=/EOR= if (msg && (flags_ & hasIoMsg)) { @@ -75,41 +75,57 @@ void IoErrorHandler::SignalEor() { SignalError(IostatEor); } +void IoErrorHandler::SignalPendingError() { + int error{pendingError_}; + pendingError_ = IostatOk; + SignalError(error); +} + bool IoErrorHandler::GetIoMsg(char *buffer, std::size_t bufferLength) { const char *msg{ioMsg_.get()}; if (!msg) { - msg = IostatErrorString(ioStat_); + msg = IostatErrorString(ioStat_ == IostatOk ? pendingError_ : ioStat_); } if (msg) { ToFortranDefaultCharacter(buffer, bufferLength, msg); return true; } - char *newBuf; // Following code is taken from llvm/lib/Support/Errno.cpp - // in LLVM v9.0.1 + // in LLVM v9.0.1 with inadequate modification for Fortran, + // since rectified. + bool ok{false}; #if HAVE_STRERROR_R // strerror_r is thread-safe. #if defined(__GLIBC__) && defined(_GNU_SOURCE) // glibc defines its own incompatible version of strerror_r // which may not use the buffer supplied. - newBuf = ::strerror_r(ioStat_, buffer, bufferLength); + msg = ::strerror_r(ioStat_, buffer, bufferLength); #else - return ::strerror_r(ioStat_, buffer, bufferLength) == 0; + ok = ::strerror_r(ioStat_, buffer, bufferLength) == 0; #endif #elif HAVE_DECL_STRERROR_S // "Windows Secure API" - return ::strerror_s(buffer, bufferLength, ioStat_) == 0; + ok = ::strerror_s(buffer, bufferLength, ioStat_) == 0; #elif HAVE_STRERROR // Copy the thread un-safe result of strerror into // the buffer as fast as possible to minimize impact // of collision of strerror in multiple threads. - newBuf = strerror(ioStat_); + msg = strerror(ioStat_); #else // Strange that this system doesn't even have strerror return false; #endif - ::strncpy(buffer, newBuf, bufferLength - 1); - buffer[bufferLength - 1] = '\n'; - return true; + if (msg) { + ToFortranDefaultCharacter(buffer, bufferLength, msg); + return true; + } else if (ok) { + std::size_t copied{std::strlen(buffer)}; + if (copied < bufferLength) { + std::memset(buffer + copied, ' ', bufferLength - copied); + } + return true; + } else { + return false; + } } } // namespace Fortran::runtime::io diff --git a/flang/runtime/io-stmt.h b/flang/runtime/io-stmt.h --- a/flang/runtime/io-stmt.h +++ b/flang/runtime/io-stmt.h @@ -77,7 +77,15 @@ // to interact with the state of the I/O statement in progress. // This design avoids virtual member functions and function pointers, // which may not have good support in some runtime environments. + + // CompleteOperation() is the last opportunity to raise an I/O error. + // It is called by EndIoStatement(), but it can be invoked earlier to + // catch errors for (e.g.) GetIoMsg() and GetNewUnit(). If called + // more than once, it is a no-op. + void CompleteOperation(); + // Completes an I/O statement and reclaims storage. int EndIoStatement(); + bool Emit(const char *, std::size_t, std::size_t elementBytes); bool Emit(const char *, std::size_t); bool Emit(const char16_t *, std::size_t chars); @@ -234,11 +242,16 @@ }; // Base class for all per-I/O statement state classes. -struct IoStatementBase : public IoErrorHandler { +class IoStatementBase : public IoErrorHandler { +public: using IoErrorHandler::IoErrorHandler; + bool completedOperation() const { return completedOperation_; } + + void CompleteOperation() { completedOperation_ = true; } + int EndIoStatement() { return GetIoStat(); } + // These are default no-op backstops that can be overridden by descendants. - int EndIoStatement(); bool Emit(const char *, std::size_t, std::size_t elementBytes); bool Emit(const char *, std::size_t); bool Emit(const char16_t *, std::size_t chars); @@ -260,6 +273,9 @@ bool Inquire(InquiryKeywordHash, std::int64_t &); void BadInquiryKeywordHashCrash(InquiryKeywordHash); + +protected: + bool completedOperation_{false}; }; // Common state for list-directed & NAMELIST I/O, both internal & external @@ -354,6 +370,7 @@ std::size_t formatLength, const char *sourceFile = nullptr, int sourceLine = 0); IoStatementState &ioStatementState() { return ioStatementState_; } + void CompleteOperation(); int EndIoStatement(); std::optional GetNextDataEdit( IoStatementState &, int maxRepeat = 1) { @@ -392,6 +409,7 @@ ExternalFileUnit &unit() { return unit_; } MutableModes &mutableModes(); ConnectionState &GetConnectionState(); + void CompleteOperation(); int EndIoStatement(); ExternalFileUnit *GetExternalFileUnit() const { return &unit_; } @@ -406,6 +424,7 @@ ExternalIoStatementState( ExternalFileUnit &, const char *sourceFile = nullptr, int sourceLine = 0); MutableModes &mutableModes() { return mutableModes_; } + void CompleteOperation(); int EndIoStatement(); bool Emit(const char *, std::size_t, std::size_t elementBytes); bool Emit(const char *, std::size_t); @@ -435,6 +454,7 @@ ExternalFormattedIoStatementState(ExternalFileUnit &, const CharType *format, std::size_t formatLength, const char *sourceFile = nullptr, int sourceLine = 0); + void CompleteOperation(); int EndIoStatement(); std::optional GetNextDataEdit( IoStatementState &, int maxRepeat = 1) { @@ -471,6 +491,7 @@ MutableModes &mutableModes(); ConnectionState &GetConnectionState(); ExternalFileUnit *GetExternalFileUnit() const; + void CompleteOperation(); int EndIoStatement(); bool Emit(const char *, std::size_t, std::size_t elementBytes); bool Emit(const char *, std::size_t); @@ -493,6 +514,7 @@ std::size_t formatLength, const char *sourceFile = nullptr, int sourceLine = 0); MutableModes &mutableModes() { return mutableModes_; } + void CompleteOperation(); int EndIoStatement(); bool AdvanceRecord(int = 1); std::optional GetNextDataEdit( @@ -535,6 +557,8 @@ void set_convert(Convert convert) { convert_ = convert; } // CONVERT= void set_access(Access access) { access_ = access; } // ACCESS= void set_isUnformatted(bool yes = true) { isUnformatted_ = yes; } // FORM= + + void CompleteOperation(); int EndIoStatement(); private: @@ -567,6 +591,7 @@ IoStatementState &ioStatementState() { return ioStatementState_; } MutableModes &mutableModes() { return connection_.modes; } ConnectionState &GetConnectionState() { return connection_; } + void CompleteOperation(); int EndIoStatement(); protected: @@ -674,6 +699,7 @@ ExternalMiscIoStatementState(ExternalFileUnit &unit, Which which, const char *sourceFile = nullptr, int sourceLine = 0) : ExternalIoStatementBase{unit, sourceFile, sourceLine}, which_{which} {} + void CompleteOperation(); int EndIoStatement(); private: @@ -684,13 +710,14 @@ public: explicit ErroneousIoStatementState( Iostat iostat, const char *sourceFile = nullptr, int sourceLine = 0) - : IoStatementBase{sourceFile, sourceLine}, iostat_{iostat} {} + : IoStatementBase{sourceFile, sourceLine} { + SetPendingError(iostat); + } int EndIoStatement(); ConnectionState &GetConnectionState() { return connection_; } MutableModes &mutableModes() { return connection_.modes; } private: - Iostat iostat_; ConnectionState connection_; }; diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp --- a/flang/runtime/io-stmt.cpp +++ b/flang/runtime/io-stmt.cpp @@ -19,8 +19,6 @@ namespace Fortran::runtime::io { -int IoStatementBase::EndIoStatement() { return GetIoStat(); } - bool IoStatementBase::Emit(const char *, std::size_t, std::size_t) { return false; } @@ -163,10 +161,18 @@ ioStatementState_{*this}, format_{*this, format, formatLength} {} template -int InternalFormattedIoStatementState::EndIoStatement() { - if constexpr (DIR == Direction::Output) { - format_.Finish(*this); // ignore any remaining input positioning actions +void InternalFormattedIoStatementState::CompleteOperation() { + if (!this->completedOperation()) { + if constexpr (DIR == Direction::Output) { + format_.Finish(*this); // ignore any remaining input positioning actions + } + IoStatementBase::CompleteOperation(); } +} + +template +int InternalFormattedIoStatementState::EndIoStatement() { + CompleteOperation(); return InternalIoStatementState::EndIoStatement(); } @@ -191,12 +197,19 @@ ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; } -int ExternalIoStatementBase::EndIoStatement() { - if (mutableModes().nonAdvancing) { - unit_.leftTabLimit = unit_.furthestPositionInRecord; - } else { - unit_.leftTabLimit.reset(); +void ExternalIoStatementBase::CompleteOperation() { + if (!completedOperation()) { + if (mutableModes().nonAdvancing) { + unit_.leftTabLimit = unit_.furthestPositionInRecord; + } else { + unit_.leftTabLimit.reset(); + } + IoStatementBase::CompleteOperation(); } +} + +int ExternalIoStatementBase::EndIoStatement() { + CompleteOperation(); auto result{IoStatementBase::EndIoStatement()}; unit_.EndIoStatement(); // annihilates *this in unit_.u_ return result; @@ -207,7 +220,10 @@ path_ = SaveDefaultCharacter(path, pathLength_, *this); } -int OpenStatementState::EndIoStatement() { +void OpenStatementState::CompleteOperation() { + if (completedOperation()) { + return; + } if (position_) { if (access_ && *access_ == Access::Direct) { SignalError("POSITION= may not be set with ACCESS='DIRECT'"); @@ -246,17 +262,33 @@ // Set default format (C.7.4 point 2). unit().isUnformatted = unit().access != Access::Sequential; } + if (!wasExtant_ && InError()) { + // Release the new unit on failure + unit().CloseUnit(CloseStatus::Delete, *this); + unit().DestroyClosed(); + } + IoStatementBase::CompleteOperation(); +} + +int OpenStatementState::EndIoStatement() { + CompleteOperation(); return ExternalIoStatementBase::EndIoStatement(); } int CloseStatementState::EndIoStatement() { + CompleteOperation(); int result{ExternalIoStatementBase::EndIoStatement()}; unit().CloseUnit(status_, *this); unit().DestroyClosed(); return result; } +void NoUnitIoStatementState::CompleteOperation() { + IoStatementBase::CompleteOperation(); +} + int NoUnitIoStatementState::EndIoStatement() { + CompleteOperation(); auto result{IoStatementBase::EndIoStatement()}; FreeMemory(this); return result; @@ -277,7 +309,11 @@ } } -template int ExternalIoStatementState::EndIoStatement() { +template +void ExternalIoStatementState::CompleteOperation() { + if (completedOperation()) { + return; + } if constexpr (DIR == Direction::Input) { BeginReadingRecord(); // in case there were no I/O items if (!mutableModes().nonAdvancing || GetIoStat() == IostatEor) { @@ -289,6 +325,11 @@ } unit().FlushIfTerminal(*this); } + return ExternalIoStatementBase::CompleteOperation(); +} + +template int ExternalIoStatementState::EndIoStatement() { + CompleteOperation(); return ExternalIoStatementBase::EndIoStatement(); } @@ -391,11 +432,20 @@ format_{*this, format, formatLength} {} template -int ExternalFormattedIoStatementState::EndIoStatement() { +void ExternalFormattedIoStatementState::CompleteOperation() { + if (this->completedOperation()) { + return; + } if constexpr (DIR == Direction::Input) { this->BeginReadingRecord(); // in case there were no I/O items } format_.Finish(*this); + return ExternalIoStatementState::CompleteOperation(); +} + +template +int ExternalFormattedIoStatementState::EndIoStatement() { + CompleteOperation(); return ExternalIoStatementState::EndIoStatement(); } @@ -448,6 +498,10 @@ std::visit([=](auto &x) { x.get().HandleAbsolutePosition(n); }, u_); } +void IoStatementState::CompleteOperation() { + std::visit([](auto &x) { x.get().CompleteOperation(); }, u_); +} + int IoStatementState::EndIoStatement() { return std::visit([](auto &x) { return x.get().EndIoStatement(); }, u_); } @@ -775,7 +829,12 @@ return child_.parent().GetExternalFileUnit(); } +template void ChildIoStatementState::CompleteOperation() { + IoStatementBase::CompleteOperation(); +} + template int ChildIoStatementState::EndIoStatement() { + CompleteOperation(); auto result{IoStatementBase::EndIoStatement()}; child_.EndIoStatement(); // annihilates *this in child_.u_ return result; @@ -825,9 +884,17 @@ mutableModes_{child.parent().mutableModes()}, format_{*this, format, formatLength} {} +template +void ChildFormattedIoStatementState::CompleteOperation() { + if (!this->completedOperation()) { + format_.Finish(*this); + ChildIoStatementState::CompleteOperation(); + } +} + template int ChildFormattedIoStatementState::EndIoStatement() { - format_.Finish(*this); + CompleteOperation(); return ChildIoStatementState::EndIoStatement(); } @@ -865,7 +932,10 @@ template class ChildUnformattedIoStatementState; template class ChildUnformattedIoStatementState; -int ExternalMiscIoStatementState::EndIoStatement() { +void ExternalMiscIoStatementState::CompleteOperation() { + if (completedOperation()) { + return; + } ExternalFileUnit &ext{unit()}; switch (which_) { case Flush: @@ -882,6 +952,11 @@ ext.Rewind(*this); break; } + return ExternalIoStatementBase::CompleteOperation(); +} + +int ExternalMiscIoStatementState::EndIoStatement() { + CompleteOperation(); return ExternalIoStatementBase::EndIoStatement(); } @@ -1366,7 +1441,7 @@ } int ErroneousIoStatementState::EndIoStatement() { - SignalError(iostat_); + SignalPendingError(); return IoStatementBase::EndIoStatement(); }