diff --git a/flang/include/flang/Runtime/iostat.h b/flang/include/flang/Runtime/iostat.h index 6d6be7c7b8d57..ddb82ce0ca238 100644 --- a/flang/include/flang/Runtime/iostat.h +++ b/flang/include/flang/Runtime/iostat.h @@ -82,6 +82,7 @@ enum Iostat { IostatBadBackspaceUnit, IostatBadUnitNumber, IostatBadFlushUnit, + IostatBadOpOnChildUnit, }; const char *IostatErrorString(int); diff --git a/flang/runtime/descriptor-io.cpp b/flang/runtime/descriptor-io.cpp index c6b57bf5f088d..4a17a53b495a5 100644 --- a/flang/runtime/descriptor-io.cpp +++ b/flang/runtime/descriptor-io.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "descriptor-io.h" +#include "flang/Common/restorer.h" namespace Fortran::runtime::io::descr { @@ -47,6 +48,8 @@ std::optional DefinedFormattedIo(IoStatementState &io, external = &ExternalFileUnit::NewUnit(handler, true); } ChildIo &child{external->PushChildIo(io)}; + // Child formatted I/O is nonadvancing by definition (F'2018 12.6.2.4). + auto restorer{common::ScopedSet(io.mutableModes().nonAdvancing, true)}; int unit{external->unitNumber()}; int ioStat{IostatOk}; char ioMsg[100]; diff --git a/flang/runtime/format-implementation.h b/flang/runtime/format-implementation.h index 630fbf68082e4..89c700eaa4b2d 100644 --- a/flang/runtime/format-implementation.h +++ b/flang/runtime/format-implementation.h @@ -64,11 +64,14 @@ FormatControl::FormatControl(const Terminator &terminator, template int FormatControl::GetIntField( - IoErrorHandler &handler, CharType firstCh) { + IoErrorHandler &handler, CharType firstCh, bool *hadError) { CharType ch{firstCh ? firstCh : PeekNext()}; if (ch != '-' && ch != '+' && (ch < '0' || ch > '9')) { handler.SignalError(IostatErrorInFormat, "Invalid FORMAT: integer expected at '%c'", static_cast(ch)); + if (hadError) { + *hadError = true; + } return 0; } int result{0}; @@ -86,6 +89,9 @@ int FormatControl::GetIntField( std::numeric_limits::max() / 10 - (static_cast(ch) - '0')) { handler.SignalError( IostatErrorInFormat, "FORMAT integer field out of range"); + if (hadError) { + *hadError = true; + } return result; } result = 10 * result + ch - '0'; @@ -99,6 +105,9 @@ int FormatControl::GetIntField( if (negate && (result *= -1) > 0) { handler.SignalError( IostatErrorInFormat, "FORMAT integer field out of range"); + if (hadError) { + *hadError = true; + } } return result; } @@ -401,7 +410,7 @@ int FormatControl::CueUpNextDataEdit(Context &context, bool stop) { // Returns the next data edit descriptor template -DataEdit FormatControl::GetNextDataEdit( +std::optional FormatControl::GetNextDataEdit( Context &context, int maxRepeat) { int repeat{CueUpNextDataEdit(context)}; auto start{offset_}; @@ -420,7 +429,7 @@ DataEdit FormatControl::GetNextDataEdit( if (auto quote{static_cast(PeekNext())}; quote == '\'' || quote == '"') { // Capture the quoted 'iotype' - bool ok{false}, tooLong{false}; + bool ok{false}; for (++offset_; offset_ < formatLength_;) { auto ch{static_cast(format_[offset_++])}; if (ch == quote && @@ -428,31 +437,36 @@ DataEdit FormatControl::GetNextDataEdit( static_cast(format_[offset_]) != quote)) { ok = true; break; // that was terminating quote - } else if (edit.ioTypeChars >= edit.maxIoTypeChars) { - tooLong = true; - } else { - edit.ioType[edit.ioTypeChars++] = ch; - if (ch == quote) { - ++offset_; - } + } + if (edit.ioTypeChars >= edit.maxIoTypeChars) { + ReportBadFormat(context, "Excessive DT'iotype' in FORMAT", start); + return std::nullopt; + } + edit.ioType[edit.ioTypeChars++] = ch; + if (ch == quote) { + ++offset_; } } if (!ok) { ReportBadFormat(context, "Unclosed DT'iotype' in FORMAT", start); - } else if (tooLong) { - ReportBadFormat(context, "Excessive DT'iotype' in FORMAT", start); + return std::nullopt; } } if (PeekNext() == '(') { // Capture the v_list arguments - bool ok{false}, tooLong{false}; + bool ok{false}; for (++offset_; offset_ < formatLength_;) { - int n{GetIntField(context)}; + bool hadError{false}; + int n{GetIntField(context, '\0', &hadError)}; + if (hadError) { + ok = false; + break; + } if (edit.vListEntries >= edit.maxVListEntries) { - tooLong = true; - } else { - edit.vList[edit.vListEntries++] = n; + ReportBadFormat(context, "Excessive DT(v_list) in FORMAT", start); + return std::nullopt; } + edit.vList[edit.vListEntries++] = n; auto ch{static_cast(GetNextChar(context))}; if (ch != ',') { ok = ch == ')'; @@ -461,8 +475,7 @@ DataEdit FormatControl::GetNextDataEdit( } if (!ok) { ReportBadFormat(context, "Unclosed DT(v_list) in FORMAT", start); - } else if (tooLong) { - ReportBadFormat(context, "Excessive DT(v_list) in FORMAT", start); + return std::nullopt; } } } diff --git a/flang/runtime/format.h b/flang/runtime/format.h index 718a2677c14c9..9077a849eaec6 100644 --- a/flang/runtime/format.h +++ b/flang/runtime/format.h @@ -102,7 +102,7 @@ template class FormatControl { // Extracts the next data edit descriptor, handling control edit descriptors // along the way. If maxRepeat==0, this is a peek at the next data edit // descriptor. - DataEdit GetNextDataEdit(Context &, int maxRepeat = 1); + std::optional GetNextDataEdit(Context &, int maxRepeat = 1); // Emit any remaining character literals after the last data item (on output) // and perform remaining record positioning actions. @@ -142,7 +142,8 @@ template class FormatControl { } return format_[offset_++]; } - int GetIntField(IoErrorHandler &, CharType firstCh = '\0'); + int GetIntField( + IoErrorHandler &, CharType firstCh = '\0', bool *hadError = nullptr); // Advances through the FORMAT until the next data edit // descriptor has been found; handles control edit descriptors diff --git a/flang/runtime/io-api.cpp b/flang/runtime/io-api.cpp index 197cfcc2f7f3d..faf062cd8f596 100644 --- a/flang/runtime/io-api.cpp +++ b/flang/runtime/io-api.cpp @@ -374,8 +374,14 @@ Cookie IONAME(BeginOpenUnit)( // OPEN(without NEWUNIT=) if (ExternalFileUnit * unit{ExternalFileUnit::LookUpOrCreate( unitNumber, terminator, wasExtant)}) { - return &unit->BeginIoStatement( - terminator, *unit, wasExtant, sourceFile, sourceLine); + if (ChildIo * child{unit->GetChildIo()}) { + return &child->BeginIoStatement( + IostatBadOpOnChildUnit, nullptr /* no unit */, sourceFile, + sourceLine); + } else { + return &unit->BeginIoStatement( + terminator, *unit, wasExtant, sourceFile, sourceLine); + } } else { return NoopUnit(terminator, unitNumber, IostatBadUnitNumber); } @@ -414,6 +420,13 @@ Cookie IONAME(BeginWaitAll)( Cookie IONAME(BeginClose)( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; + if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) { + if (ChildIo * child{unit->GetChildIo()}) { + return &child->BeginIoStatement( + IostatBadOpOnChildUnit, nullptr /* no unit */, sourceFile, + sourceLine); + } + } if (ExternalFileUnit * unit{ExternalFileUnit::LookUpForClose(unitNumber)}) { return &unit->BeginIoStatement( terminator, *unit, sourceFile, sourceLine); @@ -427,8 +440,13 @@ Cookie IONAME(BeginFlush)( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) { - return &unit->BeginIoStatement(terminator, - *unit, ExternalMiscIoStatementState::Flush, sourceFile, sourceLine); + if (ChildIo * child{unit->GetChildIo()}) { + return &child->BeginIoStatement( + *unit, ExternalMiscIoStatementState::Flush, sourceFile, sourceLine); + } else { + return &unit->BeginIoStatement(terminator, + *unit, ExternalMiscIoStatementState::Flush, sourceFile, sourceLine); + } } else { // FLUSH(UNIT=bad unit) is an error; an unconnected unit is a no-op return NoopUnit(terminator, unitNumber, @@ -440,8 +458,15 @@ Cookie IONAME(BeginBackspace)( ExternalUnit unitNumber, const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; if (ExternalFileUnit * unit{ExternalFileUnit::LookUp(unitNumber)}) { - return &unit->BeginIoStatement(terminator, - *unit, ExternalMiscIoStatementState::Backspace, sourceFile, sourceLine); + if (ChildIo * child{unit->GetChildIo()}) { + return &child->BeginIoStatement( + IostatBadOpOnChildUnit, nullptr /* no unit */, sourceFile, + sourceLine); + } else { + return &unit->BeginIoStatement(terminator, + *unit, ExternalMiscIoStatementState::Backspace, sourceFile, + sourceLine); + } } else { return NoopUnit(terminator, unitNumber, IostatBadBackspaceUnit); } @@ -454,8 +479,14 @@ Cookie IONAME(BeginEndfile)( if (ExternalFileUnit * unit{GetOrCreateUnit(unitNumber, Direction::Output, std::nullopt, terminator, errorCookie)}) { - return &unit->BeginIoStatement(terminator, - *unit, ExternalMiscIoStatementState::Endfile, sourceFile, sourceLine); + if (ChildIo * child{unit->GetChildIo()}) { + return &child->BeginIoStatement( + IostatBadOpOnChildUnit, nullptr /* no unit */, sourceFile, + sourceLine); + } else { + return &unit->BeginIoStatement(terminator, + *unit, ExternalMiscIoStatementState::Endfile, sourceFile, sourceLine); + } } else { return errorCookie; } @@ -468,8 +499,14 @@ Cookie IONAME(BeginRewind)( if (ExternalFileUnit * unit{GetOrCreateUnit(unitNumber, Direction::Input, std::nullopt, terminator, errorCookie)}) { - return &unit->BeginIoStatement(terminator, - *unit, ExternalMiscIoStatementState::Rewind, sourceFile, sourceLine); + if (ChildIo * child{unit->GetChildIo()}) { + return &child->BeginIoStatement( + IostatBadOpOnChildUnit, nullptr /* no unit */, sourceFile, + sourceLine); + } else { + return &unit->BeginIoStatement(terminator, + *unit, ExternalMiscIoStatementState::Rewind, sourceFile, sourceLine); + } } else { return errorCookie; } @@ -504,8 +541,13 @@ Cookie IONAME(BeginInquireFile)(const char *path, std::size_t pathLength, unit{ExternalFileUnit::LookUp( trimmed.get(), std::strlen(trimmed.get()))}) { // INQUIRE(FILE=) to a connected unit - return &unit->BeginIoStatement( - terminator, *unit, sourceFile, sourceLine); + if (ChildIo * child{unit->GetChildIo()}) { + return &child->BeginIoStatement( + *unit, sourceFile, sourceLine); + } else { + return &unit->BeginIoStatement( + terminator, *unit, sourceFile, sourceLine); + } } else { return &New{terminator}( std::move(trimmed), sourceFile, sourceLine) @@ -566,7 +608,12 @@ bool IONAME(SetAdvance)( if (nonAdvancing && io.GetConnectionState().access == Access::Direct) { handler.SignalError("Non-advancing I/O attempted on direct access file"); } else { - io.mutableModes().nonAdvancing = nonAdvancing; + auto *unit{io.GetExternalFileUnit()}; + if (unit && unit->GetChildIo()) { + // ADVANCE= is ignored for child I/O (12.6.4.8.3 p3) + } else { + io.mutableModes().nonAdvancing = nonAdvancing; + } } return !handler.InError(); } @@ -648,7 +695,12 @@ bool IONAME(SetRec)(Cookie cookie, std::int64_t rec) { IoStatementState &io{*cookie}; IoErrorHandler &handler{io.GetIoErrorHandler()}; if (auto *unit{io.GetExternalFileUnit()}) { - unit->SetDirectRec(rec, handler); + if (unit->GetChildIo()) { + handler.SignalError( + IostatBadOpOnChildUnit, "REC= specifier on child I/O"); + } else { + unit->SetDirectRec(rec, handler); + } } else if (!io.get_if()) { handler.Crash("SetRec() called on internal unit"); } diff --git a/flang/runtime/io-error.cpp b/flang/runtime/io-error.cpp index 790c579e1c43c..e333f6d75bc4d 100644 --- a/flang/runtime/io-error.cpp +++ b/flang/runtime/io-error.cpp @@ -59,13 +59,12 @@ void IoErrorHandler::SignalError(int iostatOrErrno) { void IoErrorHandler::Forward( int ioStatOrErrno, const char *msg, std::size_t length) { - if (ioStat_ != IostatOk && msg && (flags_ & hasIoMsg)) { - ioMsg_ = SaveDefaultCharacter(msg, length, *this); - } - if (ioStatOrErrno != IostatOk && msg) { - SignalError(ioStatOrErrno, "%.*s", static_cast(length), msg); - } else { - SignalError(ioStatOrErrno); + if (ioStatOrErrno != IostatOk) { + if (msg) { + SignalError(ioStatOrErrno, "%.*s", static_cast(length), msg); + } else { + SignalError(ioStatOrErrno); + } } } diff --git a/flang/runtime/iostat.cpp b/flang/runtime/iostat.cpp index 747a776aae7cc..f34bde28be9d0 100644 --- a/flang/runtime/iostat.cpp +++ b/flang/runtime/iostat.cpp @@ -109,6 +109,8 @@ const char *IostatErrorString(int iostat) { return "Negative unit number is not allowed"; case IostatBadFlushUnit: return "FLUSH attempted on a bad or unconnected unit number"; + case IostatBadOpOnChildUnit: + return "Impermissible I/O statement on child I/O unit"; default: return nullptr; } diff --git a/flang/runtime/unit.h b/flang/runtime/unit.h index c49f479830338..aad896afce513 100644 --- a/flang/runtime/unit.h +++ b/flang/runtime/unit.h @@ -203,7 +203,7 @@ class ChildIo { ChildListIoStatementState, ChildUnformattedIoStatementState, ChildUnformattedIoStatementState, InquireUnitState, - ErroneousIoStatementState> + ErroneousIoStatementState, ExternalMiscIoStatementState> u_; std::optional io_; }; diff --git a/flang/unittests/Runtime/Format.cpp b/flang/unittests/Runtime/Format.cpp index 970373043131a..1f55b39d59053 100644 --- a/flang/unittests/Runtime/Format.cpp +++ b/flang/unittests/Runtime/Format.cpp @@ -10,6 +10,7 @@ #include "../runtime/connection.h" #include "../runtime/format-implementation.h" #include "../runtime/io-error.h" +#include #include #include #include @@ -29,7 +30,7 @@ class TestFormatContext : public IoErrorHandler { bool AdvanceRecord(int = 1); void HandleRelativePosition(std::int64_t); void HandleAbsolutePosition(std::int64_t); - void Report(const DataEdit &); + void Report(const std::optional &); ResultsTy results; MutableModes &mutableModes() { return mutableModes_; } ConnectionState &GetConnectionState() { return connectionState_; } @@ -64,25 +65,29 @@ void TestFormatContext::HandleRelativePosition(std::int64_t n) { } } -void TestFormatContext::Report(const DataEdit &edit) { - std::string str{edit.descriptor}; - if (edit.repeat != 1) { - str = std::to_string(edit.repeat) + '*' + str; - } - if (edit.variation) { - str += edit.variation; - } - if (edit.width) { - str += std::to_string(*edit.width); - } - if (edit.digits) { - str += "."s + std::to_string(*edit.digits); - } - if (edit.expoDigits) { - str += "E"s + std::to_string(*edit.expoDigits); +void TestFormatContext::Report(const std::optional &edit) { + if (edit) { + std::string str{edit->descriptor}; + if (edit->repeat != 1) { + str = std::to_string(edit->repeat) + '*' + str; + } + if (edit->variation) { + str += edit->variation; + } + if (edit->width) { + str += std::to_string(*edit->width); + } + if (edit->digits) { + str += "."s + std::to_string(*edit->digits); + } + if (edit->expoDigits) { + str += "E"s + std::to_string(*edit->expoDigits); + } + // modes? + results.push_back(str); + } else { + results.push_back("(nullopt)"s); } - // modes? - results.push_back(str); } struct FormatTests : public CrashHandlerFixture {};