Skip to content

Commit 77dc203

Browse files
committed
[flang] Detect circularly defined procedures
It's possible to define a procedure that has a procedure dummy argument which names the procedure that contains it. This was causing the compiler to fall into an infinite loop when characterizing a call to the procedure. Following a suggestion from Peter, I fixed this be maintaining a set of procedure symbols that had already been seen while characterizing a procedure. This required passing a new parameter to the functions that characterized a Procedure, a DummyArgument, and a DummyProcedure. I also added several tests that will crash the compiler without this change. Differential Revision: https://reviews.llvm.org/D96631
1 parent bfa4235 commit 77dc203

File tree

3 files changed

+204
-106
lines changed

3 files changed

+204
-106
lines changed

flang/include/flang/Evaluate/characteristics.h

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,6 @@ struct DummyProcedure {
204204
explicit DummyProcedure(Procedure &&);
205205
bool operator==(const DummyProcedure &) const;
206206
bool operator!=(const DummyProcedure &that) const { return !(*this == that); }
207-
static std::optional<DummyProcedure> Characterize(
208-
const semantics::Symbol &, FoldingContext &context);
209207
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
210208
CopyableIndirection<Procedure> procedure;
211209
common::Intent intent{common::Intent::Default};
@@ -230,8 +228,6 @@ struct DummyArgument {
230228
~DummyArgument();
231229
bool operator==(const DummyArgument &) const;
232230
bool operator!=(const DummyArgument &that) const { return !(*this == that); }
233-
static std::optional<DummyArgument> Characterize(
234-
const semantics::Symbol &, FoldingContext &);
235231
static std::optional<DummyArgument> FromActual(
236232
std::string &&, const Expr<SomeType> &, FoldingContext &);
237233
bool IsOptional() const;
@@ -290,6 +286,7 @@ struct Procedure {
290286
ENUM_CLASS(
291287
Attr, Pure, Elemental, BindC, ImplicitInterface, NullPointer, Subroutine)
292288
using Attrs = common::EnumSet<Attr, Attr_enumSize>;
289+
Procedure(){};
293290
Procedure(FunctionResult &&, DummyArguments &&, Attrs);
294291
Procedure(DummyArguments &&, Attrs); // for subroutines and NULL()
295292
DECLARE_CONSTRUCTORS_AND_ASSIGNMENTS(Procedure)
@@ -301,6 +298,7 @@ struct Procedure {
301298
// "unrestricted specific intrinsic function".
302299
static std::optional<Procedure> Characterize(
303300
const semantics::Symbol &, FoldingContext &);
301+
// This function is the initial point of entry for characterizing procedure
304302
static std::optional<Procedure> Characterize(
305303
const ProcedureDesignator &, FoldingContext &);
306304
static std::optional<Procedure> Characterize(
@@ -325,9 +323,6 @@ struct Procedure {
325323
std::optional<FunctionResult> functionResult;
326324
DummyArguments dummyArguments;
327325
Attrs attrs;
328-
329-
private:
330-
Procedure() {}
331326
};
332327
} // namespace Fortran::evaluate::characteristics
333328
#endif // FORTRAN_EVALUATE_CHARACTERISTICS_H_

flang/lib/Evaluate/characteristics.cpp

Lines changed: 137 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,136 @@ bool DummyProcedure::operator==(const DummyProcedure &that) const {
341341
procedure.value() == that.procedure.value();
342342
}
343343

344-
std::optional<DummyProcedure> DummyProcedure::Characterize(
345-
const semantics::Symbol &symbol, FoldingContext &context) {
346-
if (auto procedure{Procedure::Characterize(symbol, context)}) {
344+
static std::string GetSeenProcs(const semantics::SymbolSet &seenProcs) {
345+
std::string result;
346+
llvm::interleave(
347+
seenProcs,
348+
[&](const SymbolRef p) { result += '\'' + p->name().ToString() + '\''; },
349+
[&]() { result += ", "; });
350+
return result;
351+
}
352+
353+
// These functions with arguments of type SymbolSet are used with mutually
354+
// recursive calls when characterizing a Procedure, a DummyArgument, or a
355+
// DummyProcedure to detect circularly defined procedures as required by
356+
// 15.4.3.6, paragraph 2.
357+
static std::optional<DummyArgument> CharacterizeDummyArgument(
358+
const semantics::Symbol &symbol, FoldingContext &context,
359+
semantics::SymbolSet &seenProcs);
360+
361+
static std::optional<Procedure> CharacterizeProcedure(
362+
const semantics::Symbol &original, FoldingContext &context,
363+
semantics::SymbolSet &seenProcs) {
364+
Procedure result;
365+
const auto &symbol{original.GetUltimate()};
366+
if (seenProcs.find(symbol) != seenProcs.end()) {
367+
std::string procsList{GetSeenProcs(seenProcs)};
368+
context.messages().Say(symbol.name(),
369+
"Procedure '%s' is recursively defined. Procedures in the cycle:"
370+
" '%s'"_err_en_US,
371+
symbol.name(), procsList);
372+
return std::nullopt;
373+
}
374+
seenProcs.insert(symbol);
375+
CopyAttrs<Procedure, Procedure::Attr>(symbol, result,
376+
{
377+
{semantics::Attr::PURE, Procedure::Attr::Pure},
378+
{semantics::Attr::ELEMENTAL, Procedure::Attr::Elemental},
379+
{semantics::Attr::BIND_C, Procedure::Attr::BindC},
380+
});
381+
if (result.attrs.test(Procedure::Attr::Elemental) &&
382+
!symbol.attrs().test(semantics::Attr::IMPURE)) {
383+
result.attrs.set(Procedure::Attr::Pure); // explicitly flag pure procedures
384+
}
385+
return std::visit(
386+
common::visitors{
387+
[&](const semantics::SubprogramDetails &subp)
388+
-> std::optional<Procedure> {
389+
if (subp.isFunction()) {
390+
if (auto fr{
391+
FunctionResult::Characterize(subp.result(), context)}) {
392+
result.functionResult = std::move(fr);
393+
} else {
394+
return std::nullopt;
395+
}
396+
} else {
397+
result.attrs.set(Procedure::Attr::Subroutine);
398+
}
399+
for (const semantics::Symbol *arg : subp.dummyArgs()) {
400+
if (!arg) {
401+
result.dummyArguments.emplace_back(AlternateReturn{});
402+
} else if (auto argCharacteristics{CharacterizeDummyArgument(
403+
*arg, context, seenProcs)}) {
404+
result.dummyArguments.emplace_back(
405+
std::move(argCharacteristics.value()));
406+
} else {
407+
return std::nullopt;
408+
}
409+
}
410+
return result;
411+
},
412+
[&](const semantics::ProcEntityDetails &proc)
413+
-> std::optional<Procedure> {
414+
if (symbol.attrs().test(semantics::Attr::INTRINSIC)) {
415+
return context.intrinsics().IsSpecificIntrinsicFunction(
416+
symbol.name().ToString());
417+
}
418+
const semantics::ProcInterface &interface{proc.interface()};
419+
if (const semantics::Symbol * interfaceSymbol{interface.symbol()}) {
420+
return CharacterizeProcedure(
421+
*interfaceSymbol, context, seenProcs);
422+
} else {
423+
result.attrs.set(Procedure::Attr::ImplicitInterface);
424+
const semantics::DeclTypeSpec *type{interface.type()};
425+
if (symbol.test(semantics::Symbol::Flag::Subroutine)) {
426+
// ignore any implicit typing
427+
result.attrs.set(Procedure::Attr::Subroutine);
428+
} else if (type) {
429+
if (auto resultType{DynamicType::From(*type)}) {
430+
result.functionResult = FunctionResult{*resultType};
431+
} else {
432+
return std::nullopt;
433+
}
434+
} else if (symbol.test(semantics::Symbol::Flag::Function)) {
435+
return std::nullopt;
436+
}
437+
// The PASS name, if any, is not a characteristic.
438+
return result;
439+
}
440+
},
441+
[&](const semantics::ProcBindingDetails &binding) {
442+
if (auto result{CharacterizeProcedure(
443+
binding.symbol(), context, seenProcs)}) {
444+
if (!symbol.attrs().test(semantics::Attr::NOPASS)) {
445+
auto passName{binding.passName()};
446+
for (auto &dummy : result->dummyArguments) {
447+
if (!passName || dummy.name.c_str() == *passName) {
448+
dummy.pass = true;
449+
return result;
450+
}
451+
}
452+
DIE("PASS argument missing");
453+
}
454+
return result;
455+
} else {
456+
return std::optional<Procedure>{};
457+
}
458+
},
459+
[&](const semantics::UseDetails &use) {
460+
return CharacterizeProcedure(use.symbol(), context, seenProcs);
461+
},
462+
[&](const semantics::HostAssocDetails &assoc) {
463+
return CharacterizeProcedure(assoc.symbol(), context, seenProcs);
464+
},
465+
[](const auto &) { return std::optional<Procedure>{}; },
466+
},
467+
symbol.details());
468+
}
469+
470+
static std::optional<DummyProcedure> CharacterizeDummyProcedure(
471+
const semantics::Symbol &symbol, FoldingContext &context,
472+
semantics::SymbolSet &seenProcs) {
473+
if (auto procedure{CharacterizeProcedure(symbol, context, seenProcs)}) {
347474
// Dummy procedures may not be elemental. Elemental dummy procedure
348475
// interfaces are errors when the interface is not intrinsic, and that
349476
// error is caught elsewhere. Elemental intrinsic interfaces are
@@ -381,14 +508,16 @@ bool DummyArgument::operator==(const DummyArgument &that) const {
381508
return u == that.u; // name and passed-object usage are not characteristics
382509
}
383510

384-
std::optional<DummyArgument> DummyArgument::Characterize(
385-
const semantics::Symbol &symbol, FoldingContext &context) {
511+
static std::optional<DummyArgument> CharacterizeDummyArgument(
512+
const semantics::Symbol &symbol, FoldingContext &context,
513+
semantics::SymbolSet &seenProcs) {
386514
auto name{symbol.name().ToString()};
387515
if (symbol.has<semantics::ObjectEntityDetails>()) {
388516
if (auto obj{DummyDataObject::Characterize(symbol, context)}) {
389517
return DummyArgument{std::move(name), std::move(obj.value())};
390518
}
391-
} else if (auto proc{DummyProcedure::Characterize(symbol, context)}) {
519+
} else if (auto proc{
520+
CharacterizeDummyProcedure(symbol, context, seenProcs)}) {
392521
return DummyArgument{std::move(name), std::move(proc.value())};
393522
}
394523
return std::nullopt;
@@ -644,99 +773,8 @@ bool Procedure::CanOverride(
644773

645774
std::optional<Procedure> Procedure::Characterize(
646775
const semantics::Symbol &original, FoldingContext &context) {
647-
Procedure result;
648-
const auto &symbol{original.GetUltimate()};
649-
CopyAttrs<Procedure, Procedure::Attr>(symbol, result,
650-
{
651-
{semantics::Attr::PURE, Procedure::Attr::Pure},
652-
{semantics::Attr::ELEMENTAL, Procedure::Attr::Elemental},
653-
{semantics::Attr::BIND_C, Procedure::Attr::BindC},
654-
});
655-
if (result.attrs.test(Attr::Elemental) &&
656-
!symbol.attrs().test(semantics::Attr::IMPURE)) {
657-
result.attrs.set(Attr::Pure); // explicitly flag pure procedures
658-
}
659-
return std::visit(
660-
common::visitors{
661-
[&](const semantics::SubprogramDetails &subp)
662-
-> std::optional<Procedure> {
663-
if (subp.isFunction()) {
664-
if (auto fr{
665-
FunctionResult::Characterize(subp.result(), context)}) {
666-
result.functionResult = std::move(fr);
667-
} else {
668-
return std::nullopt;
669-
}
670-
} else {
671-
result.attrs.set(Attr::Subroutine);
672-
}
673-
for (const semantics::Symbol *arg : subp.dummyArgs()) {
674-
if (!arg) {
675-
result.dummyArguments.emplace_back(AlternateReturn{});
676-
} else if (auto argCharacteristics{
677-
DummyArgument::Characterize(*arg, context)}) {
678-
result.dummyArguments.emplace_back(
679-
std::move(argCharacteristics.value()));
680-
} else {
681-
return std::nullopt;
682-
}
683-
}
684-
return result;
685-
},
686-
[&](const semantics::ProcEntityDetails &proc)
687-
-> std::optional<Procedure> {
688-
if (symbol.attrs().test(semantics::Attr::INTRINSIC)) {
689-
return context.intrinsics().IsSpecificIntrinsicFunction(
690-
symbol.name().ToString());
691-
}
692-
const semantics::ProcInterface &interface{proc.interface()};
693-
if (const semantics::Symbol * interfaceSymbol{interface.symbol()}) {
694-
return Characterize(*interfaceSymbol, context);
695-
} else {
696-
result.attrs.set(Attr::ImplicitInterface);
697-
const semantics::DeclTypeSpec *type{interface.type()};
698-
if (symbol.test(semantics::Symbol::Flag::Subroutine)) {
699-
// ignore any implicit typing
700-
result.attrs.set(Attr::Subroutine);
701-
} else if (type) {
702-
if (auto resultType{DynamicType::From(*type)}) {
703-
result.functionResult = FunctionResult{*resultType};
704-
} else {
705-
return std::nullopt;
706-
}
707-
} else if (symbol.test(semantics::Symbol::Flag::Function)) {
708-
return std::nullopt;
709-
}
710-
// The PASS name, if any, is not a characteristic.
711-
return result;
712-
}
713-
},
714-
[&](const semantics::ProcBindingDetails &binding) {
715-
if (auto result{Characterize(binding.symbol(), context)}) {
716-
if (!symbol.attrs().test(semantics::Attr::NOPASS)) {
717-
auto passName{binding.passName()};
718-
for (auto &dummy : result->dummyArguments) {
719-
if (!passName || dummy.name.c_str() == *passName) {
720-
dummy.pass = true;
721-
return result;
722-
}
723-
}
724-
DIE("PASS argument missing");
725-
}
726-
return result;
727-
} else {
728-
return std::optional<Procedure>{};
729-
}
730-
},
731-
[&](const semantics::UseDetails &use) {
732-
return Characterize(use.symbol(), context);
733-
},
734-
[&](const semantics::HostAssocDetails &assoc) {
735-
return Characterize(assoc.symbol(), context);
736-
},
737-
[](const auto &) { return std::optional<Procedure>{}; },
738-
},
739-
symbol.details());
776+
semantics::SymbolSet seenProcs;
777+
return CharacterizeProcedure(original, context, seenProcs);
740778
}
741779

742780
std::optional<Procedure> Procedure::Characterize(

flang/test/Semantics/resolve102.f90

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
! RUN: %S/test_errors.sh %s %t %f18
2+
3+
! Tests for circularly defined procedures
4+
!ERROR: Procedure 'sub' is recursively defined. Procedures in the cycle: ''sub', 'p2''
5+
subroutine sub(p2)
6+
PROCEDURE(sub) :: p2
7+
8+
call sub()
9+
end subroutine
10+
11+
subroutine circular
12+
!ERROR: Procedure 'p' is recursively defined. Procedures in the cycle: ''p', 'sub', 'p2''
13+
procedure(sub) :: p
14+
15+
call p(sub)
16+
17+
contains
18+
subroutine sub(p2)
19+
procedure(p) :: p2
20+
end subroutine
21+
end subroutine circular
22+
23+
program iface
24+
!ERROR: Procedure 'p' is recursively defined. Procedures in the cycle: ''p', 'sub', 'p2''
25+
procedure(sub) :: p
26+
interface
27+
subroutine sub(p2)
28+
import p
29+
procedure(p) :: p2
30+
end subroutine
31+
end interface
32+
call p(sub)
33+
end program
34+
35+
Program mutual
36+
Procedure(sub1) :: p
37+
38+
Call p(sub)
39+
40+
contains
41+
!ERROR: Procedure 'sub1' is recursively defined. Procedures in the cycle: ''p', 'sub1', 'arg''
42+
Subroutine sub1(arg)
43+
procedure(sub1) :: arg
44+
End Subroutine
45+
46+
Subroutine sub(p2)
47+
Procedure(sub1) :: p2
48+
End Subroutine
49+
End Program
50+
51+
Program mutual1
52+
Procedure(sub1) :: p
53+
54+
Call p(sub)
55+
56+
contains
57+
!ERROR: Procedure 'sub1' is recursively defined. Procedures in the cycle: ''p', 'sub1', 'arg', 'sub', 'p2''
58+
Subroutine sub1(arg)
59+
procedure(sub) :: arg
60+
End Subroutine
61+
62+
Subroutine sub(p2)
63+
Procedure(sub1) :: p2
64+
End Subroutine
65+
End Program

0 commit comments

Comments
 (0)