Skip to content

Commit 37ea42b

Browse files
authored
[flang][runtime] Enforce proper termination of list-directed input va… (#66244)
…lues Emit an error at runtime when a list-directed input value is not followed by a value separator or end of record. Previously, the runtime I/O library was consuming as many input characters that were valid for the type of the value, and leaving any remaining characters for the next input edit, if any.
1 parent 15c8a76 commit 37ea42b

File tree

3 files changed

+110
-32
lines changed

3 files changed

+110
-32
lines changed

flang/include/flang/Runtime/iostat.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ enum Iostat {
8484
IostatBadFlushUnit,
8585
IostatBadOpOnChildUnit,
8686
IostatBadNewUnit,
87+
IostatBadListDirectedInputSeparator,
8788
};
8889

8990
const char *IostatErrorString(int);

flang/runtime/edit-input.cpp

Lines changed: 107 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,49 @@
1616

1717
namespace Fortran::runtime::io {
1818

19+
// Checks that a list-directed input value has been entirely consumed and
20+
// doesn't contain unparsed characters before the next value separator.
21+
static inline bool IsCharValueSeparator(const DataEdit &edit, char32_t ch) {
22+
char32_t comma{
23+
edit.modes.editingFlags & decimalComma ? char32_t{';'} : char32_t{','}};
24+
return ch == ' ' || ch == '\t' || ch == '/' || ch == comma;
25+
}
26+
27+
static inline bool IsListDirectedFieldComplete(
28+
IoStatementState &io, const DataEdit &edit) {
29+
std::size_t byteCount;
30+
if (auto ch{io.GetCurrentChar(byteCount)}) {
31+
return IsCharValueSeparator(edit, *ch);
32+
} else {
33+
return true; // end of record: ok
34+
}
35+
}
36+
37+
static bool CheckCompleteListDirectedField(
38+
IoStatementState &io, const DataEdit &edit) {
39+
if (edit.IsListDirected()) {
40+
std::size_t byteCount;
41+
if (auto ch{io.GetCurrentChar(byteCount)}) {
42+
if (IsCharValueSeparator(edit, *ch)) {
43+
return true;
44+
} else {
45+
const auto &connection{io.GetConnectionState()};
46+
io.GetIoErrorHandler().SignalError(IostatBadListDirectedInputSeparator,
47+
"invalid character (0x%x) after list-directed input value, "
48+
"at column %d in record %d",
49+
static_cast<unsigned>(*ch),
50+
static_cast<int>(connection.positionInRecord + 1),
51+
static_cast<int>(connection.currentRecordNumber));
52+
return false;
53+
}
54+
} else {
55+
return true; // end of record: ok
56+
}
57+
} else {
58+
return true;
59+
}
60+
}
61+
1962
template <int LOG2_BASE>
2063
static bool EditBOZInput(
2164
IoStatementState &io, const DataEdit &edit, void *n, std::size_t bytes) {
@@ -89,28 +132,29 @@ static bool EditBOZInput(
89132
*data |= digit << shift;
90133
shift -= LOG2_BASE;
91134
}
92-
return true;
135+
return CheckCompleteListDirectedField(io, edit);
93136
}
94137

95138
static inline char32_t GetDecimalPoint(const DataEdit &edit) {
96139
return edit.modes.editingFlags & decimalComma ? char32_t{','} : char32_t{'.'};
97140
}
98141

99-
// Prepares input from a field, and consumes the sign, if any.
100-
// Returns true if there's a '-' sign.
101-
static bool ScanNumericPrefix(IoStatementState &io, const DataEdit &edit,
142+
// Prepares input from a field, and returns the sign, if any, else '\0'.
143+
static char ScanNumericPrefix(IoStatementState &io, const DataEdit &edit,
102144
std::optional<char32_t> &next, std::optional<int> &remaining) {
103145
remaining = io.CueUpInput(edit);
104146
next = io.NextInField(remaining, edit);
105-
bool negative{false};
147+
char sign{'\0'};
106148
if (next) {
107-
negative = *next == '-';
108-
if (negative || *next == '+') {
109-
io.SkipSpaces(remaining);
149+
if (*next == '-' || *next == '+') {
150+
sign = *next;
151+
if (!edit.IsListDirected()) {
152+
io.SkipSpaces(remaining);
153+
}
110154
next = io.NextInField(remaining, edit);
111155
}
112156
}
113-
return negative;
157+
return sign;
114158
}
115159

116160
bool EditIntegerInput(
@@ -141,9 +185,9 @@ bool EditIntegerInput(
141185
}
142186
std::optional<int> remaining;
143187
std::optional<char32_t> next;
144-
bool negate{ScanNumericPrefix(io, edit, next, remaining)};
188+
char sign{ScanNumericPrefix(io, edit, next, remaining)};
145189
common::UnsignedInt128 value{0};
146-
bool any{negate};
190+
bool any{!!sign};
147191
bool overflow{false};
148192
for (; next; next = io.NextInField(remaining, edit)) {
149193
char32_t ch{*next};
@@ -178,13 +222,13 @@ bool EditIntegerInput(
178222
return false;
179223
}
180224
auto maxForKind{common::UnsignedInt128{1} << ((8 * kind) - 1)};
181-
overflow |= value >= maxForKind && (value > maxForKind || !negate);
225+
overflow |= value >= maxForKind && (value > maxForKind || sign != '-');
182226
if (overflow) {
183227
io.GetIoErrorHandler().SignalError(IostatIntegerInputOverflow,
184228
"Decimal input overflows INTEGER(%d) variable", kind);
185229
return false;
186230
}
187-
if (negate) {
231+
if (sign == '-') {
188232
value = -value;
189233
}
190234
if (any || !io.GetConnectionState().IsAtEOF()) {
@@ -212,13 +256,17 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
212256
}
213257
++got;
214258
}};
215-
if (ScanNumericPrefix(io, edit, next, remaining)) {
259+
char sign{ScanNumericPrefix(io, edit, next, remaining)};
260+
if (sign == '-') {
216261
Put('-');
217262
}
218263
bool bzMode{(edit.modes.editingFlags & blankZero) != 0};
219-
if (!next || (!bzMode && *next == ' ')) { // empty/blank field means zero
220-
remaining.reset();
221-
if (!io.GetConnectionState().IsAtEOF()) {
264+
if (!next || (!bzMode && *next == ' ')) {
265+
if (!edit.IsListDirected() && !io.GetConnectionState().IsAtEOF()) {
266+
// An empty/blank field means zero when not list-directed.
267+
// A fixed-width field containing only a sign is also zero;
268+
// this behavior isn't standard-conforming in F'2023 but it is
269+
// required to pass FCVS.
222270
Put('0');
223271
}
224272
return got;
@@ -286,6 +334,10 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
286334
// the FCVS suite.
287335
Put('0'); // emit at least one digit
288336
}
337+
// In list-directed input, a bad exponent is not consumed.
338+
auto nextBeforeExponent{next};
339+
auto startExponent{io.GetConnectionState().positionInRecord};
340+
bool hasGoodExponent{false};
289341
if (next &&
290342
(*next == 'e' || *next == 'E' || *next == 'd' || *next == 'D' ||
291343
*next == 'q' || *next == 'Q')) {
@@ -306,11 +358,13 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
306358
}
307359
for (exponent = 0; next; next = io.NextInField(remaining, edit)) {
308360
if (*next >= '0' && *next <= '9') {
361+
hasGoodExponent = true;
309362
if (exponent < 10000) {
310363
exponent = 10 * exponent + *next - '0';
311364
}
312365
} else if (*next == ' ' || *next == '\t') {
313366
if (bzMode) {
367+
hasGoodExponent = true;
314368
exponent = 10 * exponent;
315369
}
316370
} else {
@@ -321,6 +375,11 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
321375
exponent = -exponent;
322376
}
323377
}
378+
if (!hasGoodExponent) {
379+
// There isn't a good exponent; do not consume it.
380+
next = nextBeforeExponent;
381+
io.HandleAbsolutePosition(startExponent);
382+
}
324383
if (decimalPoint) {
325384
exponent += *decimalPoint;
326385
} else {
@@ -339,6 +398,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
339398
// input value.
340399
if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) {
341400
if (next && (*next == ' ' || *next == '\t')) {
401+
io.SkipSpaces(remaining);
342402
next = io.NextInField(remaining, edit);
343403
}
344404
if (!next) { // NextInField fails on separators like ')'
@@ -423,19 +483,26 @@ static bool TryFastPathRealInput(
423483
return false;
424484
}
425485
}
426-
for (; p < limit && (*p == ' ' || *p == '\t'); ++p) {
427-
}
428486
if (edit.descriptor == DataEdit::ListDirectedImaginaryPart) {
429-
// Need to consume a trailing ')' and any white space after
430-
if (p >= limit || *p != ')') {
487+
// Need to consume a trailing ')', possibly with leading spaces
488+
for (; p < limit && (*p == ' ' || *p == '\t'); ++p) {
489+
}
490+
if (p < limit && *p == ')') {
491+
++p;
492+
} else {
493+
return false;
494+
}
495+
} else if (edit.IsListDirected()) {
496+
if (p < limit && !IsCharValueSeparator(edit, *p)) {
431497
return false;
432498
}
433-
for (++p; p < limit && (*p == ' ' || *p == '\t'); ++p) {
499+
} else {
500+
for (; p < limit && (*p == ' ' || *p == '\t'); ++p) {
501+
}
502+
if (edit.width && p < str + *edit.width) {
503+
return false; // unconverted characters remain in fixed width field
434504
}
435505
}
436-
if (edit.width && p < str + *edit.width) {
437-
return false; // unconverted characters remain in fixed width field
438-
}
439506
// Success on the fast path!
440507
*reinterpret_cast<decimal::BinaryFloatingPointNumber<PRECISION> *>(n) =
441508
converted.binary;
@@ -451,7 +518,7 @@ template <int KIND>
451518
bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
452519
constexpr int binaryPrecision{common::PrecisionOfRealKind(KIND)};
453520
if (TryFastPathRealInput<binaryPrecision>(io, edit, n)) {
454-
return true;
521+
return CheckCompleteListDirectedField(io, edit);
455522
}
456523
// Fast path wasn't available or didn't work; go the more general route
457524
static constexpr int maxDigits{
@@ -465,7 +532,11 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
465532
return false;
466533
}
467534
if (got == 0) {
468-
io.GetIoErrorHandler().SignalError(IostatBadRealInput);
535+
const auto &connection{io.GetConnectionState()};
536+
io.GetIoErrorHandler().SignalError(IostatBadRealInput,
537+
"Bad real input data at column %d of record %d",
538+
static_cast<int>(connection.positionInRecord + 1),
539+
static_cast<int>(connection.currentRecordNumber));
469540
return false;
470541
}
471542
bool hadExtra{got > maxDigits};
@@ -512,7 +583,11 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
512583
converted.flags | decimal::Inexact);
513584
}
514585
if (*p) { // unprocessed junk after value
515-
io.GetIoErrorHandler().SignalError(IostatBadRealInput);
586+
const auto &connection{io.GetConnectionState()};
587+
io.GetIoErrorHandler().SignalError(IostatBadRealInput,
588+
"Trailing characters after real input data at column %d of record %d",
589+
static_cast<int>(connection.positionInRecord + 1),
590+
static_cast<int>(connection.currentRecordNumber));
516591
return false;
517592
}
518593
*reinterpret_cast<decimal::BinaryFloatingPointNumber<binaryPrecision> *>(n) =
@@ -525,7 +600,7 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
525600
}
526601
RaiseFPExceptions(converted.flags);
527602
}
528-
return true;
603+
return CheckCompleteListDirectedField(io, edit);
529604
}
530605

531606
template <int KIND>
@@ -602,13 +677,13 @@ bool EditLogicalInput(IoStatementState &io, const DataEdit &edit, bool &x) {
602677
"Bad character '%lc' in LOGICAL input field", *next);
603678
return false;
604679
}
605-
if (remaining) { // ignore the rest of the field
680+
if (remaining) { // ignore the rest of a fixed-width field
606681
io.HandleRelativePosition(*remaining);
607682
} else if (edit.descriptor == DataEdit::ListDirected) {
608683
while (io.NextInField(remaining, edit)) { // discard rest of field
609684
}
610685
}
611-
return true;
686+
return CheckCompleteListDirectedField(io, edit);
612687
}
613688

614689
// See 13.10.3.1 paragraphs 7-9 in Fortran 2018
@@ -800,7 +875,7 @@ bool EditCharacterInput(
800875
}
801876
// Pad the remainder of the input variable, if any.
802877
std::fill_n(x, length, ' ');
803-
return true;
878+
return CheckCompleteListDirectedField(io, edit);
804879
}
805880

806881
template bool EditRealInput<2>(IoStatementState &, const DataEdit &, void *);

flang/runtime/iostat.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ const char *IostatErrorString(int iostat) {
113113
return "Impermissible I/O statement on child I/O unit";
114114
case IostatBadNewUnit:
115115
return "NEWUNIT= without FILE= or STATUS='SCRATCH'";
116+
case IostatBadListDirectedInputSeparator:
117+
return "List-directed input value has trailing unused characters";
116118
default:
117119
return nullptr;
118120
}

0 commit comments

Comments
 (0)