Skip to content

Commit 925db84

Browse files
authored
[flang][runtime] Handle NAN(...) in namelist input (#153101)
The various per-type functions for list-directed (including namelist) input editing all call a common function to detect whether the next token of input is the name of a namelist item. This check simply determines whether this next token looks like an identifier followed by '=', '(', or '%', and this fails when the next item of input is a NAN with parenthesized stuff afterwards. Make the check smarter so that it ensures that any upcoming possible identifier is actually the name of an item in the namelist group. (And that's tricky too when the group has an array item named "nan" and the upcoming input is "nan("; see the newly-added unit test case.) Fixes #152538. more
1 parent 08eff57 commit 925db84

File tree

5 files changed

+97
-38
lines changed

5 files changed

+97
-38
lines changed

flang-rt/include/flang-rt/runtime/io-stmt.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,9 @@ template <>
438438
class ListDirectedStatementState<Direction::Input>
439439
: public FormattedIoStatementState<Direction::Input> {
440440
public:
441-
RT_API_ATTRS bool inNamelistSequence() const { return inNamelistSequence_; }
441+
RT_API_ATTRS const NamelistGroup *namelistGroup() const {
442+
return namelistGroup_;
443+
}
442444
RT_API_ATTRS int EndIoStatement();
443445

444446
// Skips value separators, handles repetition and null values.
@@ -451,18 +453,19 @@ class ListDirectedStatementState<Direction::Input>
451453
// input statement. This member function resets some state so that
452454
// repetition and null values work correctly for each successive
453455
// NAMELIST input item.
454-
RT_API_ATTRS void ResetForNextNamelistItem(bool inNamelistSequence) {
456+
RT_API_ATTRS void ResetForNextNamelistItem(
457+
const NamelistGroup *namelistGroup) {
455458
remaining_ = 0;
456459
if (repeatPosition_) {
457460
repeatPosition_->Cancel();
458461
}
459462
eatComma_ = false;
460463
realPart_ = imaginaryPart_ = false;
461-
inNamelistSequence_ = inNamelistSequence;
464+
namelistGroup_ = namelistGroup;
462465
}
463466

464467
protected:
465-
bool inNamelistSequence_{false};
468+
const NamelistGroup *namelistGroup_{nullptr};
466469

467470
private:
468471
int remaining_{0}; // for "r*" repetition

flang-rt/lib/runtime/edit-input.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
534534
next = io.NextInField(remaining, edit);
535535
}
536536
if (!next || *next == ')') { // NextInField fails on separators like ')'
537-
std::size_t byteCount{0};
537+
std::size_t byteCount{1};
538538
if (!next) {
539539
next = io.GetCurrentChar(byteCount);
540540
}

flang-rt/lib/runtime/io-stmt.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1086,7 +1086,7 @@ ChildListIoStatementState<DIR>::ChildListIoStatementState(
10861086
if constexpr (DIR == Direction::Input) {
10871087
if (auto *listInput{child.parent()
10881088
.get_if<ListDirectedStatementState<Direction::Input>>()}) {
1089-
this->inNamelistSequence_ = listInput->inNamelistSequence();
1089+
this->namelistGroup_ = listInput->namelistGroup();
10901090
}
10911091
}
10921092
#else

flang-rt/lib/runtime/namelist.cpp

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ bool IODEF(OutputNamelist)(Cookie cookie, const NamelistGroup &group) {
4444
if ((connection.NeedAdvance(prefixLen) &&
4545
!(io.AdvanceRecord() && EmitAscii(io, " ", 1))) ||
4646
!EmitAscii(io, prefix, prefixLen) ||
47-
(connection.NeedAdvance(
48-
Fortran::runtime::strlen(str) + (suffix != ' ')) &&
47+
(connection.NeedAdvance(runtime::strlen(str) + (suffix != ' ')) &&
4948
!(io.AdvanceRecord() && EmitAscii(io, " ", 1)))) {
5049
return false;
5150
}
@@ -102,8 +101,8 @@ static constexpr RT_API_ATTRS char NormalizeIdChar(char32_t ch) {
102101
return static_cast<char>(ch >= 'A' && ch <= 'Z' ? ch - 'A' + 'a' : ch);
103102
}
104103

105-
static RT_API_ATTRS bool GetLowerCaseName(
106-
IoStatementState &io, char buffer[], std::size_t maxLength) {
104+
static RT_API_ATTRS bool GetLowerCaseName(IoStatementState &io, char buffer[],
105+
std::size_t maxLength, bool crashIfTooLong = true) {
107106
std::size_t byteLength{0};
108107
if (auto ch{io.GetNextNonBlank(byteLength)}) {
109108
if (IsLegalIdStart(*ch)) {
@@ -117,8 +116,10 @@ static RT_API_ATTRS bool GetLowerCaseName(
117116
if (j <= maxLength) {
118117
return true;
119118
}
120-
io.GetIoErrorHandler().SignalError(
121-
"Identifier '%s...' in NAMELIST input group is too long", buffer);
119+
if (crashIfTooLong) {
120+
io.GetIoErrorHandler().SignalError(
121+
"Identifier '%s...' in NAMELIST input group is too long", buffer);
122+
}
122123
}
123124
}
124125
return false;
@@ -356,9 +357,8 @@ static RT_API_ATTRS bool HandleComponent(IoStatementState &io, Descriptor &desc,
356357
const DescriptorAddendum *addendum{source.Addendum()};
357358
if (const typeInfo::DerivedType *
358359
type{addendum ? addendum->derivedType() : nullptr}) {
359-
if (const typeInfo::Component *
360-
comp{type->FindDataComponent(
361-
compName, Fortran::runtime::strlen(compName))}) {
360+
if (const typeInfo::Component *comp{
361+
type->FindDataComponent(compName, runtime::strlen(compName))}) {
362362
bool createdDesc{false};
363363
if (comp->rank() > 0 && source.rank() > 0) {
364364
// If base and component are both arrays, the component name
@@ -484,7 +484,7 @@ bool IODEF(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
484484
handler.SignalError("NAMELIST input group has no name");
485485
return false;
486486
}
487-
if (Fortran::runtime::strcmp(group.groupName, name) == 0) {
487+
if (runtime::strcmp(group.groupName, name) == 0) {
488488
break; // found it
489489
}
490490
SkipNamelistGroup(io);
@@ -503,7 +503,7 @@ bool IODEF(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
503503
}
504504
std::size_t itemIndex{0};
505505
for (; itemIndex < group.items; ++itemIndex) {
506-
if (Fortran::runtime::strcmp(name, group.item[itemIndex].name) == 0) {
506+
if (runtime::strcmp(name, group.item[itemIndex].name) == 0) {
507507
break;
508508
}
509509
}
@@ -577,13 +577,14 @@ bool IODEF(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
577577
if (const auto *addendum{useDescriptor->Addendum()};
578578
addendum && addendum->derivedType()) {
579579
const NonTbpDefinedIoTable *table{group.nonTbpDefinedIo};
580-
listInput->ResetForNextNamelistItem(/*inNamelistSequence=*/true);
580+
listInput->ResetForNextNamelistItem(&group);
581581
if (!IONAME(InputDerivedType)(cookie, *useDescriptor, table) &&
582582
handler.InError()) {
583583
return false;
584584
}
585585
} else {
586-
listInput->ResetForNextNamelistItem(useDescriptor->rank() > 0);
586+
listInput->ResetForNextNamelistItem(
587+
useDescriptor->rank() > 0 ? &group : nullptr);
587588
if (!descr::DescriptorIO<Direction::Input>(io, *useDescriptor) &&
588589
handler.InError()) {
589590
return false;
@@ -607,27 +608,51 @@ bool IODEF(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
607608
}
608609

609610
RT_API_ATTRS bool IsNamelistNameOrSlash(IoStatementState &io) {
610-
if (auto *listInput{
611-
io.get_if<ListDirectedStatementState<Direction::Input>>()}) {
612-
if (listInput->inNamelistSequence()) {
613-
SavedPosition savedPosition{io};
614-
std::size_t byteCount{0};
615-
if (auto ch{io.GetNextNonBlank(byteCount)}) {
616-
if (IsLegalIdStart(*ch)) {
617-
do {
618-
io.HandleRelativePosition(byteCount);
619-
ch = io.GetCurrentChar(byteCount);
620-
} while (ch && IsLegalIdChar(*ch));
621-
ch = io.GetNextNonBlank(byteCount);
622-
// TODO: how to deal with NaN(...) ambiguity?
623-
return ch && (*ch == '=' || *ch == '(' || *ch == '%');
624-
} else {
625-
return *ch == '/' || *ch == '&' || *ch == '$';
626-
}
627-
}
611+
auto *listInput{io.get_if<ListDirectedStatementState<Direction::Input>>()};
612+
if (!listInput || !listInput->namelistGroup()) {
613+
return false; // not namelist
614+
}
615+
SavedPosition savedPosition{io};
616+
std::size_t byteCount{0};
617+
auto ch{io.GetNextNonBlank(byteCount)};
618+
if (!ch) {
619+
return false;
620+
} else if (!IsLegalIdStart(*ch)) {
621+
return *ch == '/' || *ch == '&' || *ch == '$';
622+
}
623+
char id[nameBufferSize];
624+
if (!GetLowerCaseName(io, id, sizeof id, /*crashIfTooLong=*/false)) {
625+
return true; // long name
626+
}
627+
// It looks like a name, but might be "inf" or "nan". Check what
628+
// follows.
629+
ch = io.GetNextNonBlank(byteCount);
630+
if (!ch) {
631+
return false;
632+
} else if (*ch == '=' || *ch == '%') {
633+
return true;
634+
} else if (*ch != '(') {
635+
return false;
636+
} else if (runtime::strcmp(id, "nan") != 0) {
637+
return true;
638+
}
639+
// "nan(" ambiguity
640+
int depth{1};
641+
while (true) {
642+
io.HandleRelativePosition(byteCount);
643+
ch = io.GetNextNonBlank(byteCount);
644+
if (depth == 0) {
645+
// nan(...) followed by '=', '%', or '('?
646+
break;
647+
} else if (!ch) {
648+
return true; // not a valid NaN(...)
649+
} else if (*ch == '(') {
650+
++depth;
651+
} else if (*ch == ')') {
652+
--depth;
628653
}
629654
}
630-
return false;
655+
return ch && (*ch == '=' || *ch == '%' || *ch == '(');
631656
}
632657

633658
RT_OFFLOAD_API_GROUP_END

flang-rt/unittests/Runtime/Namelist.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,4 +334,35 @@ TEST(NamelistTests, RealValueForInt) {
334334
EXPECT_EQ(got, expect);
335335
}
336336

337+
TEST(NamelistTests, NanInputAmbiguity) {
338+
OwningPtr<Descriptor> xDesc{// real :: x(5) = 0.
339+
MakeArray<TypeCategory::Real, static_cast<int>(sizeof(float))>(
340+
std::vector<int>{5}, std::vector<float>{{0, 0, 0, 0, 0}})};
341+
OwningPtr<Descriptor> nanDesc{// real :: nan(2) = 0.
342+
MakeArray<TypeCategory::Real, static_cast<int>(sizeof(float))>(
343+
std::vector<int>{2}, std::vector<float>{{0, 0}})};
344+
const NamelistGroup::Item items[]{{"x", *xDesc}, {"nan", *nanDesc}};
345+
const NamelistGroup group{"nml", 2, items};
346+
static char t1[]{"&nml x=1 2 nan(q) 4 nan(1)=5 nan(q)/"};
347+
StaticDescriptor<1, true> statDesc;
348+
Descriptor &internalDesc{statDesc.descriptor()};
349+
internalDesc.Establish(TypeCode{CFI_type_char},
350+
/*elementBytes=*/std::strlen(t1), t1, 0, nullptr, CFI_attribute_pointer);
351+
auto inCookie{IONAME(BeginInternalArrayListInput)(
352+
internalDesc, nullptr, 0, __FILE__, __LINE__)};
353+
ASSERT_TRUE(IONAME(InputNamelist)(inCookie, group));
354+
ASSERT_EQ(IONAME(EndIoStatement)(inCookie), IostatOk)
355+
<< "namelist real input for nans";
356+
char out[40];
357+
internalDesc.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/sizeof out,
358+
out, 0, nullptr, CFI_attribute_pointer);
359+
auto outCookie{IONAME(BeginInternalArrayListOutput)(
360+
internalDesc, nullptr, 0, __FILE__, __LINE__)};
361+
ASSERT_TRUE(IONAME(OutputNamelist)(outCookie, group));
362+
ASSERT_EQ(IONAME(EndIoStatement)(outCookie), IostatOk) << "namelist output";
363+
std::string got{out, sizeof out};
364+
static const std::string expect{" &NML X= 1. 2. NaN 4. 0.,NAN= 5. NaN/ "};
365+
EXPECT_EQ(got, expect);
366+
}
367+
337368
// TODO: Internal NAMELIST error tests

0 commit comments

Comments
 (0)