Skip to content

Commit 5ea0ba2

Browse files
committed
[flang] Enforce more restrictions on I/O data list items
12.6.3p5 requires an I/O data list item to have a defined I/O procedure if it is polymorphic. (We could defer this checking to the runtime, but no other Fortran compiler does so, and we would also have to be able to catch the case of an allocatable or pointer direct component in the absence of a defined I/O subroutine.) Also includes a patch to name resolution that ensures that a SELECT TYPE construct entity is polymorphic in the domain of a CLASS IS guard. Also ensures that non-defined I/O of types with PRIVATE components is caught. Differential Revision: https://reviews.llvm.org/D139050
1 parent b469021 commit 5ea0ba2

File tree

10 files changed

+269
-67
lines changed

10 files changed

+269
-67
lines changed

flang/include/flang/Semantics/semantics.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,12 @@ class SemanticsContext {
168168
return messages_.Say(std::move(msg));
169169
}
170170
template <typename... A>
171-
void SayWithDecl(const Symbol &symbol, const parser::CharBlock &at,
172-
parser::MessageFixedText &&msg, A &&...args) {
171+
parser::Message &SayWithDecl(const Symbol &symbol,
172+
const parser::CharBlock &at, parser::MessageFixedText &&msg,
173+
A &&...args) {
173174
auto &message{Say(at, std::move(msg), args...)};
174175
evaluate::AttachDeclaration(&message, symbol);
176+
return message;
175177
}
176178

177179
const Scope &FindScope(parser::CharBlock) const;

flang/include/flang/Semantics/tools.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -610,11 +610,6 @@ std::optional<ArraySpec> ToArraySpec(
610610
// procedure.
611611
bool HasDefinedIo(
612612
GenericKind::DefinedIo, const DerivedTypeSpec &, const Scope * = nullptr);
613-
// Seeks out an allocatable or pointer ultimate component that is not
614-
// nested in a nonallocatable/nonpointer component with a specific
615-
// defined I/O procedure.
616-
const Symbol *FindUnsafeIoDirectComponent(
617-
GenericKind::DefinedIo, const DerivedTypeSpec &, const Scope * = nullptr);
618613

619614
// Some intrinsic operators have more than one name (e.g. `operator(.eq.)` and
620615
// `operator(==)`). GetAllNames() returns them all, including symbolName.

flang/lib/Semantics/check-io.cpp

Lines changed: 150 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ void IoChecker::Enter(const parser::InputItem &spec) {
323323
}
324324
CheckForDefinableVariable(*var, "Input");
325325
if (auto expr{AnalyzeExpr(context_, *var)}) {
326-
CheckForBadIoComponent(*expr,
326+
CheckForBadIoType(*expr,
327327
flags_.test(Flag::FmtOrNml) ? GenericKind::DefinedIo::ReadFormatted
328328
: GenericKind::DefinedIo::ReadUnformatted,
329329
var->GetSource());
@@ -616,7 +616,7 @@ void IoChecker::Enter(const parser::OutputItem &item) {
616616
context_.Say(parser::FindSourceLocation(*x),
617617
"Output item must not be a procedure pointer"_err_en_US); // C1233
618618
}
619-
CheckForBadIoComponent(*expr,
619+
CheckForBadIoType(*expr,
620620
flags_.test(Flag::FmtOrNml)
621621
? GenericKind::DefinedIo::WriteFormatted
622622
: GenericKind::DefinedIo::WriteUnformatted,
@@ -738,29 +738,21 @@ void IoChecker::Leave(const parser::PrintStmt &) {
738738
Done();
739739
}
740740

741-
static void CheckForDoVariableInNamelist(const Symbol &namelist,
742-
SemanticsContext &context, parser::CharBlock namelistLocation) {
743-
const auto &details{namelist.GetUltimate().get<NamelistDetails>()};
744-
for (const Symbol &object : details.objects()) {
745-
context.CheckIndexVarRedefine(namelistLocation, object);
746-
}
747-
}
748-
749-
static void CheckForDoVariableInNamelistSpec(
750-
const parser::ReadStmt &readStmt, SemanticsContext &context) {
751-
const std::list<parser::IoControlSpec> &controls{readStmt.controls};
741+
static const parser::Name *FindNamelist(
742+
const std::list<parser::IoControlSpec> &controls) {
752743
for (const auto &control : controls) {
753-
if (const auto *namelist{std::get_if<parser::Name>(&control.u)}) {
754-
if (const Symbol * symbol{namelist->symbol}) {
755-
CheckForDoVariableInNamelist(*symbol, context, namelist->source);
744+
if (const parser::Name * namelist{std::get_if<parser::Name>(&control.u)}) {
745+
if (namelist->symbol &&
746+
namelist->symbol->GetUltimate().has<NamelistDetails>()) {
747+
return namelist;
756748
}
757749
}
758750
}
751+
return nullptr;
759752
}
760753

761754
static void CheckForDoVariable(
762755
const parser::ReadStmt &readStmt, SemanticsContext &context) {
763-
CheckForDoVariableInNamelistSpec(readStmt, context);
764756
const std::list<parser::InputItem> &items{readStmt.items};
765757
for (const auto &item : items) {
766758
if (const parser::Variable *
@@ -774,6 +766,12 @@ void IoChecker::Leave(const parser::ReadStmt &readStmt) {
774766
if (!flags_.test(Flag::InternalUnit)) {
775767
CheckForPureSubprogram();
776768
}
769+
if (const parser::Name * namelist{FindNamelist(readStmt.controls)}) {
770+
if (namelist->symbol) {
771+
CheckNamelist(*namelist->symbol, GenericKind::DefinedIo::ReadFormatted,
772+
namelist->source);
773+
}
774+
}
777775
CheckForDoVariable(readStmt, context_);
778776
if (!flags_.test(Flag::IoControlList)) {
779777
Done();
@@ -807,10 +805,16 @@ void IoChecker::Leave(const parser::WaitStmt &) {
807805
Done();
808806
}
809807

810-
void IoChecker::Leave(const parser::WriteStmt &) {
808+
void IoChecker::Leave(const parser::WriteStmt &writeStmt) {
811809
if (!flags_.test(Flag::InternalUnit)) {
812810
CheckForPureSubprogram();
813811
}
812+
if (const parser::Name * namelist{FindNamelist(writeStmt.controls)}) {
813+
if (namelist->symbol) {
814+
CheckNamelist(*namelist->symbol, GenericKind::DefinedIo::WriteFormatted,
815+
namelist->source);
816+
}
817+
}
814818
LeaveReadWrite();
815819
CheckForProhibitedSpecifier(IoSpecKind::Blank); // C1213
816820
CheckForProhibitedSpecifier(IoSpecKind::End); // C1213
@@ -1030,20 +1034,139 @@ void IoChecker::CheckForPureSubprogram() const { // C1597
10301034
}
10311035
}
10321036

1033-
// Fortran 2018, 12.6.3 paragraph 7
1034-
void IoChecker::CheckForBadIoComponent(const SomeExpr &expr,
1037+
// Seeks out an allocatable or pointer ultimate component that is not
1038+
// nested in a nonallocatable/nonpointer component with a specific
1039+
// defined I/O procedure.
1040+
static const Symbol *FindUnsafeIoDirectComponent(GenericKind::DefinedIo which,
1041+
const DerivedTypeSpec &derived, const Scope &scope) {
1042+
if (HasDefinedIo(which, derived, &scope)) {
1043+
return nullptr;
1044+
}
1045+
if (const Scope * dtScope{derived.scope()}) {
1046+
for (const auto &pair : *dtScope) {
1047+
const Symbol &symbol{*pair.second};
1048+
if (IsAllocatableOrPointer(symbol)) {
1049+
return &symbol;
1050+
}
1051+
if (const auto *details{symbol.detailsIf<ObjectEntityDetails>()}) {
1052+
if (const DeclTypeSpec * type{details->type()}) {
1053+
if (type->category() == DeclTypeSpec::Category::TypeDerived) {
1054+
const DerivedTypeSpec &componentDerived{type->derivedTypeSpec()};
1055+
if (const Symbol *
1056+
bad{FindUnsafeIoDirectComponent(
1057+
which, componentDerived, scope)}) {
1058+
return bad;
1059+
}
1060+
}
1061+
}
1062+
}
1063+
}
1064+
}
1065+
return nullptr;
1066+
}
1067+
1068+
// For a type that does not have a defined I/O subroutine, finds a direct
1069+
// component that is a witness to an accessibility violation outside the module
1070+
// in which the type was defined.
1071+
static const Symbol *FindInaccessibleComponent(GenericKind::DefinedIo which,
1072+
const DerivedTypeSpec &derived, const Scope &scope) {
1073+
if (const Scope * dtScope{derived.scope()}) {
1074+
if (const Scope * module{FindModuleContaining(*dtScope)}) {
1075+
for (const auto &pair : *dtScope) {
1076+
const Symbol &symbol{*pair.second};
1077+
if (IsAllocatableOrPointer(symbol)) {
1078+
continue; // already an error
1079+
}
1080+
if (const auto *details{symbol.detailsIf<ObjectEntityDetails>()}) {
1081+
const DerivedTypeSpec *componentDerived{nullptr};
1082+
if (const DeclTypeSpec * type{details->type()}) {
1083+
if (type->category() == DeclTypeSpec::Category::TypeDerived) {
1084+
componentDerived = &type->derivedTypeSpec();
1085+
}
1086+
}
1087+
if (componentDerived &&
1088+
HasDefinedIo(which, *componentDerived, &scope)) {
1089+
continue; // this component and its descendents are fine
1090+
}
1091+
if (symbol.attrs().test(Attr::PRIVATE) &&
1092+
!symbol.test(Symbol::Flag::ParentComp)) {
1093+
if (!DoesScopeContain(module, scope)) {
1094+
return &symbol;
1095+
}
1096+
}
1097+
if (componentDerived) {
1098+
if (const Symbol *
1099+
bad{FindInaccessibleComponent(
1100+
which, *componentDerived, scope)}) {
1101+
return bad;
1102+
}
1103+
}
1104+
}
1105+
}
1106+
}
1107+
}
1108+
return nullptr;
1109+
}
1110+
1111+
// Fortran 2018, 12.6.3 paragraphs 5 & 7
1112+
parser::Message *IoChecker::CheckForBadIoType(const evaluate::DynamicType &type,
10351113
GenericKind::DefinedIo which, parser::CharBlock where) const {
1036-
if (auto type{expr.GetType()}) {
1037-
if (type->category() == TypeCategory::Derived &&
1038-
!type->IsUnlimitedPolymorphic()) {
1114+
if (type.IsUnlimitedPolymorphic()) {
1115+
return &context_.Say(
1116+
where, "I/O list item may not be unlimited polymorphic"_err_en_US);
1117+
} else if (type.category() == TypeCategory::Derived) {
1118+
const auto &derived{type.GetDerivedTypeSpec()};
1119+
const Scope &scope{context_.FindScope(where)};
1120+
if (const Symbol *
1121+
bad{FindUnsafeIoDirectComponent(which, derived, scope)}) {
1122+
return &context_.SayWithDecl(*bad, where,
1123+
"Derived type '%s' in I/O cannot have an allocatable or pointer direct component '%s' unless using defined I/O"_err_en_US,
1124+
derived.name(), bad->name());
1125+
}
1126+
if (!HasDefinedIo(which, derived, &scope)) {
1127+
if (type.IsPolymorphic()) {
1128+
return &context_.Say(where,
1129+
"Derived type '%s' in I/O may not be polymorphic unless using defined I/O"_err_en_US,
1130+
derived.name());
1131+
}
10391132
if (const Symbol *
1040-
bad{FindUnsafeIoDirectComponent(
1041-
which, type->GetDerivedTypeSpec(), &context_.FindScope(where))}) {
1042-
context_.SayWithDecl(*bad, where,
1043-
"Derived type in I/O cannot have an allocatable or pointer direct component unless using defined I/O"_err_en_US);
1133+
bad{FindInaccessibleComponent(which, derived, scope)}) {
1134+
return &context_.Say(where,
1135+
"I/O of the derived type '%s' may not be performed without defined I/O in a scope in which a direct component like '%s' is inaccessible"_err_en_US,
1136+
derived.name(), bad->name());
10441137
}
10451138
}
10461139
}
1140+
return nullptr;
1141+
}
1142+
1143+
void IoChecker::CheckForBadIoType(const SomeExpr &expr,
1144+
GenericKind::DefinedIo which, parser::CharBlock where) const {
1145+
if (auto type{expr.GetType()}) {
1146+
CheckForBadIoType(*type, which, where);
1147+
}
1148+
}
1149+
1150+
parser::Message *IoChecker::CheckForBadIoType(const Symbol &symbol,
1151+
GenericKind::DefinedIo which, parser::CharBlock where) const {
1152+
if (auto type{evaluate::DynamicType::From(symbol)}) {
1153+
if (auto *msg{CheckForBadIoType(*type, which, where)}) {
1154+
evaluate::AttachDeclaration(*msg, symbol);
1155+
return msg;
1156+
}
1157+
}
1158+
return nullptr;
1159+
}
1160+
1161+
void IoChecker::CheckNamelist(const Symbol &namelist,
1162+
GenericKind::DefinedIo which, parser::CharBlock namelistLocation) const {
1163+
const auto &details{namelist.GetUltimate().get<NamelistDetails>()};
1164+
for (const Symbol &object : details.objects()) {
1165+
context_.CheckIndexVarRedefine(namelistLocation, object);
1166+
if (auto *msg{CheckForBadIoType(object, which, namelistLocation)}) {
1167+
evaluate::AttachDeclaration(*msg, namelist);
1168+
}
1169+
}
10471170
}
10481171

10491172
} // namespace Fortran::semantics

flang/lib/Semantics/check-io.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,15 @@ class IoChecker : public virtual BaseChecker {
126126

127127
void CheckForPureSubprogram() const;
128128

129-
void CheckForBadIoComponent(
129+
parser::Message *CheckForBadIoType(const evaluate::DynamicType &,
130+
GenericKind::DefinedIo, parser::CharBlock) const;
131+
void CheckForBadIoType(
130132
const SomeExpr &, GenericKind::DefinedIo, parser::CharBlock) const;
133+
parser::Message *CheckForBadIoType(
134+
const Symbol &, GenericKind::DefinedIo, parser::CharBlock) const;
135+
136+
void CheckNamelist(
137+
const Symbol &, GenericKind::DefinedIo, parser::CharBlock) const;
131138

132139
void Init(IoStmtKind s) {
133140
stmt_ = s;

flang/lib/Semantics/resolve-names.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,7 @@ class ConstructVisitor : public virtual DeclarationVisitor {
11941194
// Creates Block scopes with neither symbol name nor symbol details.
11951195
bool Pre(const parser::SelectRankConstruct::RankCase &);
11961196
void Post(const parser::SelectRankConstruct::RankCase &);
1197+
bool Pre(const parser::TypeGuardStmt::Guard &);
11971198
void Post(const parser::TypeGuardStmt::Guard &);
11981199
void Post(const parser::SelectRankCaseStmt::Rank &);
11991200
bool Pre(const parser::ChangeTeamStmt &);
@@ -6407,6 +6408,14 @@ void ConstructVisitor::Post(const parser::SelectRankConstruct::RankCase &) {
64076408
PopScope();
64086409
}
64096410

6411+
bool ConstructVisitor::Pre(const parser::TypeGuardStmt::Guard &x) {
6412+
if (std::holds_alternative<parser::DerivedTypeSpec>(x.u)) {
6413+
// CLASS IS (t)
6414+
SetDeclTypeSpecCategory(DeclTypeSpec::Category::ClassDerived);
6415+
}
6416+
return true;
6417+
}
6418+
64106419
void ConstructVisitor::Post(const parser::TypeGuardStmt::Guard &x) {
64116420
if (auto *symbol{MakeAssocEntity()}) {
64126421
if (std::holds_alternative<parser::Default>(x.u)) {

flang/lib/Semantics/tools.cpp

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,31 +1514,4 @@ bool HasDefinedIo(GenericKind::DefinedIo which, const DerivedTypeSpec &derived,
15141514
return false;
15151515
}
15161516

1517-
const Symbol *FindUnsafeIoDirectComponent(GenericKind::DefinedIo which,
1518-
const DerivedTypeSpec &derived, const Scope *scope) {
1519-
if (HasDefinedIo(which, derived, scope)) {
1520-
return nullptr;
1521-
}
1522-
if (const Scope * dtScope{derived.scope()}) {
1523-
for (const auto &pair : *dtScope) {
1524-
const Symbol &symbol{*pair.second};
1525-
if (IsAllocatableOrPointer(symbol)) {
1526-
return &symbol;
1527-
}
1528-
if (const auto *details{symbol.detailsIf<ObjectEntityDetails>()}) {
1529-
if (const DeclTypeSpec * type{details->type()}) {
1530-
if (type->category() == DeclTypeSpec::Category::TypeDerived) {
1531-
if (const Symbol *
1532-
bad{FindUnsafeIoDirectComponent(
1533-
which, type->derivedTypeSpec(), scope)}) {
1534-
return bad;
1535-
}
1536-
}
1537-
}
1538-
}
1539-
}
1540-
}
1541-
return nullptr;
1542-
}
1543-
15441517
} // namespace Fortran::semantics

flang/test/Semantics/io12.f90

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ subroutine test3(u)
5252
type(maybeBad) :: y
5353
type(poison) :: z
5454
write(u) x ! always ok
55-
!ERROR: Derived type in I/O cannot have an allocatable or pointer direct component unless using defined I/O
55+
!ERROR: Derived type 'maybebad' in I/O cannot have an allocatable or pointer direct component 'allocatablecomponent' unless using defined I/O
5656
write(u) y ! bad here
57-
!ERROR: Derived type in I/O cannot have an allocatable or pointer direct component unless using defined I/O
57+
!ERROR: Derived type 'poison' in I/O cannot have an allocatable or pointer direct component 'allocatablecomponent' unless using defined I/O
5858
write(u) z ! bad
5959
end subroutine
6060
end module
@@ -69,7 +69,7 @@ subroutine test4(u)
6969
type(poison) :: z
7070
write(u) x ! always ok
7171
write(u) y ! ok here
72-
!ERROR: Derived type in I/O cannot have an allocatable or pointer direct component unless using defined I/O
72+
!ERROR: Derived type 'poison' in I/O cannot have an allocatable or pointer direct component 'allocatablecomponent' unless using defined I/O
7373
write(u) z ! bad
7474
end subroutine
7575
end module

flang/test/Semantics/io14.f90

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
! RUN: %python %S/test_errors.py %s %flang_fc1
2+
! Test polymorphic restrictions
3+
module m
4+
type base
5+
end type
6+
type, extends(base) :: t
7+
integer n
8+
contains
9+
procedure :: fwrite
10+
generic :: write(formatted) => fwrite
11+
end type
12+
contains
13+
subroutine fwrite(x, unit, iotype, vlist, iostat, iomsg)
14+
class(t), intent(in) :: x
15+
integer, intent(in) :: unit
16+
character(*), intent(in) :: iotype
17+
integer, intent(in) :: vlist(:)
18+
integer, intent(out) :: iostat
19+
character(*), intent(in out) :: iomsg
20+
write(unit, *, iostat=iostat, iomsg=iomsg) '(', iotype, ':', vlist, ':', x%n, ')'
21+
end subroutine
22+
subroutine subr(x, y, z)
23+
class(t), intent(in) :: x
24+
class(base), intent(in) :: y
25+
class(*), intent(in) :: z
26+
print *, x ! ok
27+
!ERROR: Derived type 'base' in I/O may not be polymorphic unless using defined I/O
28+
print *, y
29+
!ERROR: I/O list item may not be unlimited polymorphic
30+
print *, z
31+
end subroutine
32+
end
33+
34+
program main
35+
use m
36+
call subr(t(123),t(234),t(345))
37+
end

0 commit comments

Comments
 (0)