Skip to content

Commit 95540f9

Browse files
committed
[flang] Detect circularly defined interfaces of procedures
It's possible to define a procedure whose interface depends on a procedure which has an interface that depends on the original procedure. Such a circular definition was causing the compiler to fall into an infinite loop when resolving the name of the second procedure. It's also possible to create circular dependency chains of more than two procedures. I fixed this by adding the function HasCycle() to the class DeclarationVisitor and calling it from DeclareProcEntity() to detect procedures with such circularly defined interfaces. I marked the associated symbols of such procedures by calling SetError() on them. When processing subsequent procedures, I called HasError() before attempting to analyze their interfaces. Unfortunately, this did not work. With help from Tim, we determined that the SymbolSet used to track the erroneous symbols was instantiated using a "<" operator which was defined using the location of the name of the procedure. But the location of the procedure name was being changed by a call to ReplaceName() between the times that the calls to SetError() and HasError() were made. This caused HasError() to incorrectly report that a symbol was not in the set of erroneous symbols. I fixed this by changing SymbolSet to be an unordered set that uses the contents of the name of the symbol as the basis for its hash function. This works because the contents of the name of the symbol is preserved by ReplaceName() even though its location changes. I also fixed the error message used when reporting recursively defined dummy procedure arguments by removing extra apostrophes and sorting the list of symbols. I also added tests that will crash the compiler without this change. Note that the "<" operator is used in other contexts, for example, in the map of characterized procedures, maps of items in equivalence sets, maps of structure constructor values, ... All of these situations happen after name resolution has been completed and all calls to ReplaceName() have already happened and thus are not subject to the problem I ran into when ReplaceName() was called when processing procedure entities. Note also that the implementation of the "<" operator uses the relative location in the cooked character stream as the basis of its implementation. This is potentially problematic when symbols from diffent compilation units (for example symbols originating in .mod files) are put into the same map since their names will appear in two different source streams which may not be allocated in the same relative positions in memory. But I was unable to create a test that caused a problem. Using a direct comparison of the content of the name of the symbol in the "<" operator has problems. Symbols in enclosing or parallel scopes can have the same name. Also using the location of the symbol in the cooked character stream has the advantage that it preserves the the order of the symbols in a structure constructor constant, which makes matching the values with the symbols relatively easy. This patch supersedes D97749. Differential Revision: https://reviews.llvm.org/D97774
1 parent 253a660 commit 95540f9

File tree

5 files changed

+86
-24
lines changed

5 files changed

+86
-24
lines changed

flang/include/flang/Semantics/semantics.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ class SemanticsContext {
199199
IndexVarKind kind;
200200
};
201201
std::map<SymbolRef, const IndexVarInfo> activeIndexVars_;
202-
std::set<SymbolRef> errorSymbols_;
202+
SymbolSet errorSymbols_;
203203
std::set<std::string> tempNames_;
204204
};
205205

flang/include/flang/Semantics/symbol.h

+10-3
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
#include "flang/Common/reference.h"
1616
#include "llvm/ADT/DenseMapInfo.h"
1717
#include <array>
18+
#include <functional>
1819
#include <list>
1920
#include <optional>
20-
#include <set>
21+
#include <unordered_set>
2122
#include <vector>
2223

2324
namespace llvm {
@@ -595,7 +596,7 @@ class Symbol {
595596
bool operator==(const Symbol &that) const { return this == &that; }
596597
bool operator!=(const Symbol &that) const { return !(*this == that); }
597598
bool operator<(const Symbol &that) const {
598-
// For sets of symbols: collate them by source location
599+
// For maps of symbols: collate them by source location
599600
return name_.begin() < that.name_.begin();
600601
}
601602

@@ -765,7 +766,13 @@ inline bool operator<(SymbolRef x, SymbolRef y) { return *x < *y; }
765766
inline bool operator<(MutableSymbolRef x, MutableSymbolRef y) {
766767
return *x < *y;
767768
}
768-
using SymbolSet = std::set<SymbolRef>;
769+
struct SymbolHash {
770+
std::size_t operator()(SymbolRef symRef) const {
771+
std::hash<std::string> hasher;
772+
return hasher(symRef->name().ToString());
773+
}
774+
};
775+
using SymbolSet = std::unordered_set<SymbolRef, SymbolHash>;
769776

770777
} // namespace Fortran::semantics
771778

flang/lib/Evaluate/characteristics.cpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,13 @@ bool DummyProcedure::operator==(const DummyProcedure &that) const {
344344
}
345345

346346
static std::string GetSeenProcs(const semantics::SymbolSet &seenProcs) {
347+
// Sort the symbols so that they appear in the same order on all platforms
348+
std::vector<SymbolRef> sorter{seenProcs.begin(), seenProcs.end()};
349+
std::sort(sorter.begin(), sorter.end());
350+
347351
std::string result;
348352
llvm::interleave(
349-
seenProcs,
353+
sorter,
350354
[&](const SymbolRef p) { result += '\'' + p->name().ToString() + '\''; },
351355
[&]() { result += ", "; });
352356
return result;
@@ -369,7 +373,7 @@ static std::optional<Procedure> CharacterizeProcedure(
369373
std::string procsList{GetSeenProcs(seenProcs)};
370374
context.messages().Say(symbol.name(),
371375
"Procedure '%s' is recursively defined. Procedures in the cycle:"
372-
" '%s'"_err_en_US,
376+
" %s"_err_en_US,
373377
symbol.name(), procsList);
374378
return std::nullopt;
375379
}

flang/lib/Semantics/resolve-names.cpp

+43-13
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,7 @@ class DeclarationVisitor : public ArraySpecVisitor,
10031003
context().SetError(symbol);
10041004
return symbol;
10051005
}
1006+
bool HasCycle(const Symbol &, const ProcInterface &);
10061007
};
10071008

10081009
// Resolve construct entities and statement entities.
@@ -2132,7 +2133,7 @@ static bool NeedsType(const Symbol &symbol) {
21322133

21332134
void ScopeHandler::ApplyImplicitRules(
21342135
Symbol &symbol, bool allowForwardReference) {
2135-
if (!NeedsType(symbol)) {
2136+
if (context().HasError(symbol) || !NeedsType(symbol)) {
21362137
return;
21372138
}
21382139
if (const DeclTypeSpec * type{GetImplicitType(symbol)}) {
@@ -3641,6 +3642,35 @@ Symbol &DeclarationVisitor::DeclareUnknownEntity(
36413642
}
36423643
}
36433644

3645+
bool DeclarationVisitor::HasCycle(
3646+
const Symbol &procSymbol, const ProcInterface &interface) {
3647+
SymbolSet procsInCycle;
3648+
procsInCycle.insert(procSymbol);
3649+
const ProcInterface *thisInterface{&interface};
3650+
bool haveInterface{true};
3651+
while (haveInterface) {
3652+
haveInterface = false;
3653+
if (const Symbol * interfaceSymbol{thisInterface->symbol()}) {
3654+
if (procsInCycle.count(*interfaceSymbol) > 0) {
3655+
for (const auto procInCycle : procsInCycle) {
3656+
Say(procInCycle->name(),
3657+
"The interface for procedure '%s' is recursively "
3658+
"defined"_err_en_US,
3659+
procInCycle->name());
3660+
context().SetError(*procInCycle);
3661+
}
3662+
return true;
3663+
} else if (const auto *procDetails{
3664+
interfaceSymbol->detailsIf<ProcEntityDetails>()}) {
3665+
haveInterface = true;
3666+
thisInterface = &procDetails->interface();
3667+
procsInCycle.insert(*interfaceSymbol);
3668+
}
3669+
}
3670+
}
3671+
return false;
3672+
}
3673+
36443674
Symbol &DeclarationVisitor::DeclareProcEntity(
36453675
const parser::Name &name, Attrs attrs, const ProcInterface &interface) {
36463676
Symbol &symbol{DeclareEntity<ProcEntityDetails>(name, attrs)};
@@ -3650,20 +3680,20 @@ Symbol &DeclarationVisitor::DeclareProcEntity(
36503680
"The interface for procedure '%s' has already been "
36513681
"declared"_err_en_US);
36523682
context().SetError(symbol);
3653-
} else {
3654-
if (interface.type()) {
3683+
} else if (HasCycle(symbol, interface)) {
3684+
return symbol;
3685+
} else if (interface.type()) {
3686+
symbol.set(Symbol::Flag::Function);
3687+
} else if (interface.symbol()) {
3688+
if (interface.symbol()->test(Symbol::Flag::Function)) {
36553689
symbol.set(Symbol::Flag::Function);
3656-
} else if (interface.symbol()) {
3657-
if (interface.symbol()->test(Symbol::Flag::Function)) {
3658-
symbol.set(Symbol::Flag::Function);
3659-
} else if (interface.symbol()->test(Symbol::Flag::Subroutine)) {
3660-
symbol.set(Symbol::Flag::Subroutine);
3661-
}
3690+
} else if (interface.symbol()->test(Symbol::Flag::Subroutine)) {
3691+
symbol.set(Symbol::Flag::Subroutine);
36623692
}
3663-
details->set_interface(interface);
3664-
SetBindNameOn(symbol);
3665-
SetPassNameOn(symbol);
36663693
}
3694+
details->set_interface(interface);
3695+
SetBindNameOn(symbol);
3696+
SetPassNameOn(symbol);
36673697
}
36683698
return symbol;
36693699
}
@@ -5005,7 +5035,7 @@ Symbol *DeclarationVisitor::NoteInterfaceName(const parser::Name &name) {
50055035

50065036
void DeclarationVisitor::CheckExplicitInterface(const parser::Name &name) {
50075037
if (const Symbol * symbol{name.symbol}) {
5008-
if (!symbol->HasExplicitInterface()) {
5038+
if (!context().HasError(*symbol) && !symbol->HasExplicitInterface()) {
50095039
Say(name,
50105040
"'%s' must be an abstract interface or a procedure with "
50115041
"an explicit interface"_err_en_US,

flang/test/Semantics/resolve102.f90

+26-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
! RUN: %S/test_errors.sh %s %t %f18
22

33
! Tests for circularly defined procedures
4-
!ERROR: Procedure 'sub' is recursively defined. Procedures in the cycle: ''sub', 'p2''
4+
!ERROR: Procedure 'sub' is recursively defined. Procedures in the cycle: 'sub', 'p2'
55
subroutine sub(p2)
66
PROCEDURE(sub) :: p2
77

88
call sub()
99
end subroutine
1010

1111
subroutine circular
12-
!ERROR: Procedure 'p' is recursively defined. Procedures in the cycle: ''p', 'sub', 'p2''
12+
!ERROR: Procedure 'p' is recursively defined. Procedures in the cycle: 'p', 'sub', 'p2'
1313
procedure(sub) :: p
1414

1515
call p(sub)
@@ -21,7 +21,7 @@ subroutine sub(p2)
2121
end subroutine circular
2222

2323
program iface
24-
!ERROR: Procedure 'p' is recursively defined. Procedures in the cycle: ''p', 'sub', 'p2''
24+
!ERROR: Procedure 'p' is recursively defined. Procedures in the cycle: 'p', 'sub', 'p2'
2525
procedure(sub) :: p
2626
interface
2727
subroutine sub(p2)
@@ -38,7 +38,7 @@ Program mutual
3838
Call p(sub)
3939

4040
contains
41-
!ERROR: Procedure 'sub1' is recursively defined. Procedures in the cycle: ''p', 'sub1', 'arg''
41+
!ERROR: Procedure 'sub1' is recursively defined. Procedures in the cycle: 'p', 'sub1', 'arg'
4242
Subroutine sub1(arg)
4343
procedure(sub1) :: arg
4444
End Subroutine
@@ -54,7 +54,7 @@ Program mutual1
5454
Call p(sub)
5555

5656
contains
57-
!ERROR: Procedure 'sub1' is recursively defined. Procedures in the cycle: ''p', 'sub1', 'arg', 'sub', 'p2''
57+
!ERROR: Procedure 'sub1' is recursively defined. Procedures in the cycle: 'p', 'sub1', 'arg', 'sub', 'p2'
5858
Subroutine sub1(arg)
5959
procedure(sub) :: arg
6060
End Subroutine
@@ -63,3 +63,24 @@ Subroutine sub(p2)
6363
Procedure(sub1) :: p2
6464
End Subroutine
6565
End Program
66+
67+
program twoCycle
68+
!ERROR: The interface for procedure 'p1' is recursively defined
69+
!ERROR: The interface for procedure 'p2' is recursively defined
70+
procedure(p1) p2
71+
procedure(p2) p1
72+
call p1
73+
call p2
74+
end program
75+
76+
program threeCycle
77+
!ERROR: The interface for procedure 'p1' is recursively defined
78+
!ERROR: The interface for procedure 'p2' is recursively defined
79+
procedure(p1) p2
80+
!ERROR: The interface for procedure 'p3' is recursively defined
81+
procedure(p2) p3
82+
procedure(p3) p1
83+
call p1
84+
call p2
85+
call p3
86+
end program

0 commit comments

Comments
 (0)