diff --git a/flang/include/flang/Runtime/io-api.h b/flang/include/flang/Runtime/io-api.h --- a/flang/include/flang/Runtime/io-api.h +++ b/flang/include/flang/Runtime/io-api.h @@ -111,6 +111,24 @@ void **scratchArea = nullptr, std::size_t scratchBytes = 0, const char *sourceFile = nullptr, int sourceLine = 0); +// External unit numbers must fit in default integers. When the integer +// provided as UNIT is of a wider type than the default integer, it could +// overflow when converted to a default integer. +// CheckUnitNumberInRange should be called to verify that a unit number of a +// wide integer type can fit in a default integer. Since it should be called +// before the BeginXXX(unit, ...) call, it has its own error handling interface. +// If handleError is false, and the unit number is out of range, the program +// will be terminated. Otherwise, if unit is out of range, a nonzero Iostat +// code is returned and ioMsg is set if it is not a nullptr. +enum Iostat IONAME(CheckUnitNumberInRange64)(std::int64_t unit, + bool handleError, char *ioMsg = nullptr, std::size_t ioMsgLength = 0, + const char *sourceFile = nullptr, int sourceLine = 0); +#ifdef __SIZEOF_INT128__ +enum Iostat IONAME(CheckUnitNumberInRange128)(common::int128_t unit, + bool handleError, char *ioMsg = nullptr, std::size_t ioMsgLength = 0, + const char *sourceFile = nullptr, int sourceLine = 0); +#endif + // External synchronous I/O initiation Cookie IONAME(BeginExternalListOutput)(ExternalUnit = DefaultUnit, const char *sourceFile = nullptr, int sourceLine = 0); diff --git a/flang/include/flang/Runtime/iostat.h b/flang/include/flang/Runtime/iostat.h --- a/flang/include/flang/Runtime/iostat.h +++ b/flang/include/flang/Runtime/iostat.h @@ -67,6 +67,7 @@ IostatMissingTerminator, IostatBadUnformattedRecord, IostatUTF8Decoding, + IostatUnitOverflow, }; const char *IostatErrorString(int); 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 @@ -1275,4 +1275,51 @@ IoStatementState &io{*cookie}; return static_cast(io.EndIoStatement()); } + +template +static enum Iostat CheckUnitNumberInRangeImpl(INT unit, bool handleError, + char *ioMsg, std::size_t ioMsgLength, const char *sourceFile, + int sourceLine) { + if (unit != static_cast(unit)) { + Terminator oom{sourceFile, sourceLine}; + IoErrorHandler errorHandler{oom}; + if (handleError) { + errorHandler.HasIoStat(); + if (ioMsg) { + errorHandler.HasIoMsg(); + } + } + // Only provide the bad unit number in the message if SignalError can print + // it accurately. Otherwise, the generic IostatUnitOverflow message will be + // used. + if (static_cast(unit) == unit) { + errorHandler.SignalError(IostatUnitOverflow, + "UNIT number %jd is out of range", static_cast(unit)); + } else { + errorHandler.SignalError(IostatUnitOverflow); + } + if (ioMsg) { + errorHandler.GetIoMsg(ioMsg, ioMsgLength); + } + return static_cast(errorHandler.GetIoStat()); + } + return IostatOk; +} + +enum Iostat IONAME(CheckUnitNumberInRange64)(std::int64_t unit, + bool handleError, char *ioMsg, std::size_t ioMsgLength, + const char *sourceFile, int sourceLine) { + return CheckUnitNumberInRangeImpl( + unit, handleError, ioMsg, ioMsgLength, sourceFile, sourceLine); +} + +#ifdef __SIZEOF_INT128__ +enum Iostat IONAME(CheckUnitNumberInRange128)(common::int128_t unit, + bool handleError, char *ioMsg, std::size_t ioMsgLength, + const char *sourceFile, int sourceLine) { + return CheckUnitNumberInRangeImpl( + unit, handleError, ioMsg, ioMsgLength, sourceFile, sourceLine); +} +#endif + } // namespace Fortran::runtime::io diff --git a/flang/runtime/iostat.cpp b/flang/runtime/iostat.cpp --- a/flang/runtime/iostat.cpp +++ b/flang/runtime/iostat.cpp @@ -77,6 +77,8 @@ return "Erroneous unformatted sequential file record structure"; case IostatUTF8Decoding: return "UTF-8 decoding error"; + case IostatUnitOverflow: + return "UNIT number is out of range"; default: return nullptr; } diff --git a/flang/unittests/Runtime/ExternalIOTest.cpp b/flang/unittests/Runtime/ExternalIOTest.cpp --- a/flang/unittests/Runtime/ExternalIOTest.cpp +++ b/flang/unittests/Runtime/ExternalIOTest.cpp @@ -895,3 +895,38 @@ ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk) << "EndIoStatement() for second CLOSE"; } + +TEST(ExternalIOTests, BigUnitNumbers) { + if (std::numeric_limits::max() < + std::numeric_limits::max()) { + std::int64_t unit64Ok = std::numeric_limits::max(); + std::int64_t unit64Bad = unit64Ok + 1; + std::int64_t unit64Bad2 = + static_cast(std::numeric_limits::min()) - 1; + EXPECT_EQ(IONAME(CheckUnitNumberInRange64)(unit64Ok, true), IostatOk); + EXPECT_EQ(IONAME(CheckUnitNumberInRange64)(unit64Ok, false), IostatOk); + EXPECT_EQ( + IONAME(CheckUnitNumberInRange64)(unit64Bad, true), IostatUnitOverflow); + EXPECT_EQ( + IONAME(CheckUnitNumberInRange64)(unit64Bad2, true), IostatUnitOverflow); + EXPECT_EQ( + IONAME(CheckUnitNumberInRange64)(unit64Bad, true), IostatUnitOverflow); + EXPECT_EQ( + IONAME(CheckUnitNumberInRange64)(unit64Bad2, true), IostatUnitOverflow); + constexpr std::size_t n{80}; + char expectedMsg[n + 1]; + expectedMsg[n] = '\0'; + std::snprintf(expectedMsg, n, "UNIT number %jd is out of range", + static_cast(unit64Bad)); + EXPECT_DEATH( + IONAME(CheckUnitNumberInRange64)(2147483648, false), expectedMsg); + for (auto i{std::strlen(expectedMsg)}; i < n; ++i) { + expectedMsg[i] = ' '; + } + char msg[n + 1]; + msg[n] = '\0'; + EXPECT_EQ(IONAME(CheckUnitNumberInRange64)(unit64Bad, true, msg, n), + IostatUnitOverflow); + EXPECT_EQ(std::strncmp(msg, expectedMsg, n), 0); + } +}