[flang] Check that various variables referenced in I/O statements may be defined

A number of I/O syntax rules involve variables that will be written to,
and must therefore be definable.  This includes internal file variables,
IOSTAT= and IOMSG= specifiers, most INQUIRE statement specifiers, a few
other specifiers, and input variables.  This patch checks for
these violations, and implements several additional I/O TODO constraint
checks.

Differential Revision: https://reviews.llvm.org/D86557
This commit is contained in:
peter klausler 2020-08-25 10:34:33 -07:00
parent e713b0ecbc
commit bce7a7edf3
9 changed files with 150 additions and 34 deletions

View file

@ -155,7 +155,8 @@ void IoChecker::Enter(const parser::ConnectSpec::CharExpr &spec) {
}
}
void IoChecker::Enter(const parser::ConnectSpec::Newunit &) {
void IoChecker::Enter(const parser::ConnectSpec::Newunit &var) {
CheckForDefinableVariable(var, "NEWUNIT");
SetSpecifier(IoSpecKind::Newunit);
}
@ -266,10 +267,11 @@ void IoChecker::Enter(const parser::IdExpr &) { SetSpecifier(IoSpecKind::Id); }
void IoChecker::Enter(const parser::IdVariable &spec) {
SetSpecifier(IoSpecKind::Id);
auto expr{GetExpr(spec)};
const auto *expr{GetExpr(spec)};
if (!expr || !expr->GetType()) {
return;
}
CheckForDefinableVariable(spec, "ID");
int kind{expr->GetType()->kind()};
int defaultKind{context_.GetDefaultKind(TypeCategory::Integer)};
if (kind < defaultKind) {
@ -281,21 +283,18 @@ void IoChecker::Enter(const parser::IdVariable &spec) {
void IoChecker::Enter(const parser::InputItem &spec) {
flags_.set(Flag::DataList);
if (const parser::Variable * var{std::get_if<parser::Variable>(&spec.u)}) {
const parser::Name &name{GetLastName(*var)};
if (name.symbol) {
if (auto *details{name.symbol->detailsIf<ObjectEntityDetails>()}) {
// TODO: Determine if this check is needed at all, and if so, replace
// the false subcondition with a check for a whole array. Otherwise,
// the check incorrectly flags array element and section references.
if (details->IsAssumedSize() && false) {
// This check may be superseded by C928 or C1002.
context_.Say(name.source,
"'%s' must not be a whole assumed size array"_err_en_US,
name.source); // C1231
}
}
}
const parser::Variable *var{std::get_if<parser::Variable>(&spec.u)};
if (!var) {
return;
}
CheckForDefinableVariable(*var, "Input");
const auto &name{GetLastName(*var)};
const auto *expr{GetExpr(*var)};
if (name.symbol && IsAssumedSizeArray(*name.symbol) && expr &&
!evaluate::IsArrayElement(*GetExpr(*var))) {
context_.Say(name.source,
"Whole assumed size array '%s' may not be an input item"_err_en_US,
name.source); // C1231
}
}
@ -386,6 +385,8 @@ void IoChecker::Enter(const parser::InquireSpec::CharVar &spec) {
specKind = IoSpecKind::Dispose;
break;
}
CheckForDefinableVariable(std::get<parser::ScalarDefaultCharVariable>(spec.t),
parser::ToUpperCaseLetters(common::EnumToString(specKind)));
SetSpecifier(specKind);
}
@ -412,6 +413,8 @@ void IoChecker::Enter(const parser::InquireSpec::IntVar &spec) {
specKind = IoSpecKind::Size;
break;
}
CheckForDefinableVariable(std::get<parser::ScalarIntVariable>(spec.t),
parser::ToUpperCaseLetters(common::EnumToString(specKind)));
SetSpecifier(specKind);
}
@ -500,17 +503,23 @@ void IoChecker::Enter(const parser::IoControlSpec::Rec &) {
SetSpecifier(IoSpecKind::Rec);
}
void IoChecker::Enter(const parser::IoControlSpec::Size &) {
void IoChecker::Enter(const parser::IoControlSpec::Size &var) {
CheckForDefinableVariable(var, "SIZE");
SetSpecifier(IoSpecKind::Size);
}
void IoChecker::Enter(const parser::IoUnit &spec) {
if (const parser::Variable * var{std::get_if<parser::Variable>(&spec.u)}) {
// TODO: C1201 - internal file variable must not be an array section ...
if (auto expr{GetExpr(*var)}) {
if (!ExprTypeKindIsDefault(*expr, context_)) {
if (stmt_ == IoStmtKind::Write) {
CheckForDefinableVariable(*var, "Internal file");
}
if (const auto *expr{GetExpr(*var)}) {
if (HasVectorSubscript(*expr)) {
context_.Say(parser::FindSourceLocation(*var), // C1201
"Internal file must not have a vector subscript"_err_en_US);
} else if (!ExprTypeKindIsDefault(*expr, context_)) {
// This may be too restrictive; other kinds may be valid.
context_.Say( // C1202
context_.Say(parser::FindSourceLocation(*var), // C1202
"Invalid character kind for an internal file variable"_err_en_US);
}
}
@ -522,13 +531,26 @@ void IoChecker::Enter(const parser::IoUnit &spec) {
}
}
void IoChecker::Enter(const parser::MsgVariable &) {
void IoChecker::Enter(const parser::MsgVariable &var) {
if (stmt_ == IoStmtKind::None) {
// allocate, deallocate, image control
CheckForDefinableVariable(var, "ERRMSG");
return;
}
CheckForDefinableVariable(var, "IOMSG");
SetSpecifier(IoSpecKind::Iomsg);
}
void IoChecker::Enter(const parser::OutputItem &) {
void IoChecker::Enter(const parser::OutputItem &item) {
flags_.set(Flag::DataList);
// TODO: C1233 - output item must not be a procedure pointer
if (const auto *x{std::get_if<parser::Expr>(&item.u)}) {
if (const auto *expr{GetExpr(*x)}) {
if (IsProcedurePointer(*expr)) {
context_.Say(parser::FindSourceLocation(*x),
"Output item must not be a procedure pointer"_err_en_US); // C1233
}
}
}
}
void IoChecker::Enter(const parser::StatusExpr &spec) {
@ -555,12 +577,14 @@ void IoChecker::Enter(const parser::StatusExpr &spec) {
}
}
void IoChecker::Enter(const parser::StatVariable &) {
void IoChecker::Enter(const parser::StatVariable &var) {
if (stmt_ == IoStmtKind::None) {
// ALLOCATE & DEALLOCATE
} else {
SetSpecifier(IoSpecKind::Iostat);
// allocate, deallocate, image control
CheckForDefinableVariable(var, "STAT");
return;
}
CheckForDefinableVariable(var, "IOSTAT");
SetSpecifier(IoSpecKind::Iostat);
}
void IoChecker::Leave(const parser::BackspaceStmt &) {
@ -808,7 +832,7 @@ void IoChecker::CheckStringValue(IoSpecKind specKind, const std::string &value,
// CheckForRequiredSpecifier and CheckForProhibitedSpecifier functions
// need conditions to check, and string arguments to insert into a message.
// A IoSpecKind provides both an absence/presence condition and a string
// An IoSpecKind provides both an absence/presence condition and a string
// argument (its name). A (condition, string) pair provides an arbitrary
// condition and an arbitrary string.
@ -893,6 +917,17 @@ void IoChecker::CheckForProhibitedSpecifier(
}
}
template <typename A>
void IoChecker::CheckForDefinableVariable(
const A &var, const std::string &s) const {
const Symbol *sym{
GetFirstName(*parser::Unwrap<parser::Variable>(var)).symbol};
if (WhyNotModifiable(*sym, context_.FindScope(*context_.location()))) {
context_.Say(parser::FindSourceLocation(var),
"%s variable '%s' must be definable"_err_en_US, s, sym->name());
}
}
void IoChecker::CheckForPureSubprogram() const { // C1597
CHECK(context_.location());
if (FindPureProcedureContaining(context_.FindScope(*context_.location()))) {

View file

@ -122,6 +122,11 @@ private:
void CheckForProhibitedSpecifier(IoSpecKind, bool, const std::string &) const;
void CheckForProhibitedSpecifier(bool, const std::string &, IoSpecKind) const;
template <typename A>
void CheckForDefinableVariable(const A &var, const std::string &s) const;
void CheckForPureSubprogram() const;
void Init(IoStmtKind s) {
stmt_ = s;
specifierSet_.reset();
@ -130,8 +135,6 @@ private:
void Done() { stmt_ = IoStmtKind::None; }
void CheckForPureSubprogram() const;
SemanticsContext &context_;
IoStmtKind stmt_{IoStmtKind::None};
common::EnumSet<IoSpecKind, common::IoSpecKind_enumSize> specifierSet_;

View file

@ -21,6 +21,7 @@ Type(t),Allocatable :: x(:)
Real :: r
Integer :: s
Integer, Parameter :: const_s = 13
Integer :: e
Integer :: pi
Character(256) :: ee
@ -56,6 +57,8 @@ Deallocate(x%p)
!ERROR: STAT may not be duplicated in a DEALLOCATE statement
Deallocate(x, stat=s, stat=s)
!ERROR: STAT variable 'const_s' must be definable
Deallocate(x, stat=const_s)
!ERROR: ERRMSG may not be duplicated in a DEALLOCATE statement
Deallocate(x, errmsg=ee, errmsg=ee)
!ERROR: STAT may not be duplicated in a DEALLOCATE statement

View file

@ -21,6 +21,7 @@
integer :: unit10 = 10
integer :: unit11 = 11
integer :: n = 40
integer, parameter :: const_new_unit = 66
integer(kind=1) :: stat1
integer(kind=2) :: stat2
@ -73,6 +74,9 @@
!ERROR: If NEWUNIT appears, FILE or STATUS must also appear
open(newunit=n, newunit=nn, iostat=stat4)
!ERROR: NEWUNIT variable 'const_new_unit' must be definable
open(newunit=const_new_unit, status=cc)
!ERROR: Duplicate UNIT specifier
open(unit=100, unit=100)

View file

@ -1,6 +1,7 @@
! RUN: %S/test_errors.sh %s %t %f18
integer :: unit10 = 10
integer :: unit11 = 11
integer, parameter :: const_stat = 6666
integer(kind=1) :: stat1
integer(kind=8) :: stat8
@ -28,5 +29,8 @@
!ERROR: Invalid STATUS value 'old'
close(status='old', unit=17)
!ERROR: IOSTAT variable 'const_stat' must be definable
close(14, iostat=const_stat)
9 continue
end

View file

@ -2,13 +2,18 @@
character(kind=1,len=50) internal_file
character(kind=2,len=50) internal_file2
character(kind=4,len=50) internal_file4
character(kind=1,len=50) internal_fileA(20)
character(kind=1,len=111) msg
character(20) advance
character(20) :: cvar;
character, parameter :: const_internal_file = "(I6)"
character, parameter :: const_cvar = "Ceci n'est pas une pipe."
integer*1 stat1
integer*2 stat2, id2
integer*8 stat8
integer :: iunit = 10
integer, parameter :: junit = 11
integer, parameter :: junit = 11, const_size = 13, const_int = 15
integer :: vv(10) = 7
namelist /mmm/ mm1, mm2
namelist /nnn/ nn1, nn2
@ -29,11 +34,14 @@
read(fmt='(I4)', unit=*) jj
read(iunit, *) jj
read(junit, *) jj
read(10, *) jj
read(10, *) jj, cvar, cvar(7:17)
read(internal_file, *) jj
read(internal_fileA(3), *) jj
read(internal_fileA(4:9), *) jj
read(10, nnn)
read(internal_file, nnn)
read(internal_file, nml=nnn)
read(const_internal_file, *)
read(fmt=*, unit=internal_file)
read(nml=nnn, unit=internal_file)
read(iunit, nnn)
@ -53,6 +61,21 @@
!ERROR: Invalid character kind for an internal file variable
read(internal_file4, *) jj
!ERROR: Internal file must not have a vector subscript
read(internal_fileA(vv), *) jj
!ERROR: Input variable 'const_int' must be definable
read(11, *) const_int
!ERROR: SIZE variable 'const_size' must be definable
read(11, pos=ipos, size=const_size, end=9)
!ERROR: Input variable 'const_cvar' must be definable
read(11, *) const_cvar
!ERROR: Input variable 'const_cvar' must be definable
read(11, *) const_cvar(3:13)
!ERROR: Duplicate IOSTAT specifier
read(11, pos=ipos, iostat=stat1, iostat=stat2)
@ -136,3 +159,25 @@
9 continue
end
subroutine s(aa, n)
integer :: aa(5,*)
integer, intent(in) :: n
integer :: bb(10), vv(10)
type tt
real :: x, y, z
end type tt
type(tt) :: qq(20)
vv = 1
read(*, *) aa(n,1)
read(*, *) aa(n:n+2,2)
read(*, *) qq(2:5)%y
!ERROR: Input variable 'n' must be definable
read(*, *) n
!ERROR: Whole assumed size array 'aa' may not be an input item
read(*, *) aa
end

View file

@ -2,6 +2,7 @@
character(kind=1,len=50) internal_file
character(kind=1,len=100) msg
character(20) sign
character, parameter :: const_internal_file = "(I6)"
integer*1 stat1, id1
integer*2 stat2
integer*4 stat4
@ -9,6 +10,8 @@
integer :: iunit = 10
integer, parameter :: junit = 11
integer, pointer :: a(:)
integer, parameter :: const_id = 66666
procedure(), pointer :: procptr
namelist /nnn/ nn1, nn2
@ -66,6 +69,9 @@
!ERROR: If NML appears, a data list must not appear
write(10, nnn, rec=40, fmt=1) 'Ok'
!ERROR: Internal file variable 'const_internal_file' must be definable
write(const_internal_file, fmt=*)
!ERROR: If UNIT=* appears, POS must not appear
write(*, pos=n, nml=nnn)
@ -118,8 +124,14 @@
!ERROR: ID kind (1) is smaller than default INTEGER kind (4)
write(id=id1, unit=10, asynchronous='Yes') 'Ok'
!ERROR: ID variable 'const_id' must be definable
write(10, *, asynchronous='yes', id=const_id, iostat=stat2) 'Ok'
write(*, '(X)')
!ERROR: Output item must not be a procedure pointer
print*, n1, procptr, n2
1 format (A)
9 continue
end

View file

@ -1,10 +1,12 @@
! RUN: %S/test_errors.sh %s %t %f18
character*20 c(25), cv
character(kind=1,len=59) msg
character, parameter :: const_round = "c'est quoi?"
logical*2 v(5), lv
integer*1 stat1
integer*2 stat4
integer*8 stat8, iv
integer, parameter :: const_id = 1
inquire(10)
inquire(file='abc')
@ -22,6 +24,7 @@
exist=v(1), named=v(2), opened=v(3), pending=v(4))
inquire(pending=v(5), file='abc')
inquire(10, id=id, pending=v(5))
inquire(10, id=const_id, pending=v(5))
! using variable 'cv' multiple times seems to be allowed
inquire(file='abc', &
@ -56,5 +59,8 @@
!ERROR: If ID appears, PENDING must also appear
inquire(file='abc', id=id)
!ERROR: ROUND variable 'const_round' must be definable
inquire(file='abc', round=const_round)
9 continue
end

View file

@ -1,6 +1,7 @@
! RUN: %S/test_errors.sh %s %t %f18
character(kind=1,len=100) msg1
character(kind=2,len=200) msg2
character, parameter :: const_msg = 'doof'
integer(1) stat1
integer(2) stat2
integer(8) stat8
@ -28,6 +29,9 @@
!ERROR: Duplicate IOSTAT specifier
endfile(iostat=stat2, err=9, unit=10, iostat=stat8, iomsg=msg1)
!ERROR: IOMSG variable 'const_msg' must be definable
flush(iomsg=const_msg, unit=10, iostat=stat8, err=9)
!ERROR: REWIND statement must have a UNIT number specifier
rewind(iostat=stat2)