This is an archive of the discontinued LLVM Phabricator instance.

[flang] Detect circularly defined interfaces of procedures
ClosedPublic

Authored by PeteSteinfeld on Feb 22 2021, 8:59 AM.

Details

Summary

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 name of the procedure. But 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 unordered.

I also added tests that will crash the compiler without this change.

Diff Detail

Event Timeline

PeteSteinfeld created this revision.Feb 22 2021, 8:59 AM
PeteSteinfeld requested review of this revision.Feb 22 2021, 8:59 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 22 2021, 8:59 AM
PeteSteinfeld added a project: Restricted Project.Feb 22 2021, 9:00 AM
klausler added inline comments.Feb 22 2021, 9:07 AM
flang/include/flang/Semantics/symbol.h
607

Heap addresses are arbitrary and cannot be expected to have relationships that are portable. This change is going to lead to spurious test failures esp. across platforms. You're going to have to order the symbols via the actual characters in their names instead.

PeteSteinfeld added inline comments.Feb 22 2021, 9:21 AM
flang/include/flang/Semantics/symbol.h
607

Good catch. Will do.

tskeith added inline comments.Feb 22 2021, 12:21 PM
flang/include/flang/Semantics/symbol.h
607

Heap addresses are arbitrary and cannot be expected to have relationships that are portable. This change is going to lead to spurious test failures esp. across platforms. You're going to have to order the symbols via the actual characters in their names instead.

An alternative is to make SymbolSet be an std::unordered_set and explicitly sort the symbols when the order matters, e.g. in .mod files.

Responding to Peter's and Tim's comments. The "<" operator is used not only
for SymbolSet, but it's also used to sort the elements in a SymbolVector in
several places. The simplest and least disruptive fix was to follow another
suggestion from Tim to record the initial value of a symbol's name and use that
initial value as the basis for the "<" operator. This change makes the result
of the "<" operator immune to calls to "ReplaceName()".

PeteSteinfeld added inline comments.Feb 24 2021, 11:43 AM
flang/include/flang/Semantics/symbol.h
607

I tried going down the path of making SymbolSet be unordered but the resulting changes were pretty messy, and I gave up in favor of another suggestion from Tim -- to record the initial value of the symbol's name and use that as the basis for the "<" operator.

tskeith accepted this revision.Feb 24 2021, 5:12 PM

Looks good to me.

flang/test/Semantics/resolve102.f90
24

Can you remove the extra quotes?

This revision is now accepted and ready to land.Feb 24 2021, 5:12 PM
PeteSteinfeld added inline comments.Feb 26 2021, 8:24 AM
flang/include/flang/Semantics/symbol.h
607

After examining several alternatives, I've concluded that making SymbolSet unordered is the least disruptive solution. More information in an update soon to follow.

I changed the solution to the problem with SymbolSet having an inconsistent "<"
operator by making it unordered.

I investigated changing the definition of the "<" operator, but it's used in
many different places as the key for map types and to sort symbols.

Peter has noted that the current implementation of the "<" operator for Symbol
relies on the fact that its arguments are in the same cooked character stream.
This assumption is violated when at least one of the symbols being compared
comes from a .mod file.

Fixed the formatting of an error message as suggested by Tim.

This revision was landed with ongoing or failed builds.Feb 26 2021, 2:49 PM
This revision was automatically updated to reflect the committed changes.
PeteSteinfeld edited the summary of this revision. (Show Details)Feb 26 2021, 2:58 PM

@PeteSteinfeld

The aarch64 flang buildbots are failing since this commit.
https://lab.llvm.org/buildbot/#/builders/32/builds/3480
The x86 buildbot is passing. https://lab.llvm.org/buildbot/#/builders/132

From the failure it looks like a test expectation issue. Not sure, but it could be that the order/start/direction of the cycle could be different for aarch64, x86.

@PeteSteinfeld

The aarch64 flang buildbots are failing since this commit.
https://lab.llvm.org/buildbot/#/builders/32/builds/3480
The x86 buildbot is passing. https://lab.llvm.org/buildbot/#/builders/132

From the failure it looks like a test expectation issue. Not sure, but it could be that the order/start/direction of the cycle could be different for aarch64, x86.

I believe that the problem stems from the switch from std::set to std::unordered_set. As a result, the order in which things are printed is implementation-defined (and will differ between platforms). However, that switch is also part of the fix here - I assume that reverting it is not desirable.

One other alternative would be to leverage FileCheck and to use regex patterns in the tests. That would make the test less fragile, but I'm under the impression that people do prefer to use test_errors.sh instead (previous discussion here). Just to demonstrate how a ported test would look, I've updated resolve102.f90 and pasted it at the bottom of this message (passes on AArch64).

@PeteSteinfeld , thank you for working on this! As this is causing a breakage in 6 out of 8 of Flang's public buildbots, I will revert this for now (I'm not sure what the fix should be). Please let me know if I can help to re-land this change!

PORTED TEST

! RUN: not %f18 %s 2>&1 | FileCheck %s

! Tests for circularly defined procedures
! CHECK: Procedure 'sub' is recursively defined.  Procedures in the cycle: '{{p2|sub}}', '{{p2|sub}}'
subroutine sub(p2)
  PROCEDURE(sub) :: p2

  call sub()
end subroutine

subroutine circular
  ! CHECK: Procedure 'p' is recursively defined.  Procedures in the cycle: '{{p|p2|sub}}', '{{p|p2|sub}}', '{{p|p2|sub}}'
  procedure(sub) :: p

  call p(sub)

  contains
    subroutine sub(p2)
      procedure(p) :: p2
    end subroutine
end subroutine circular

program iface
  ! CHECK: Procedure 'p' is recursively defined.  Procedures in the cycle: '{{p|p2|sub}}', '{{p|p2|sub}}', '{{p|p2|sub}}'
  procedure(sub) :: p
  interface
    subroutine sub(p2)
      import p
      procedure(p) :: p2
    end subroutine
  end interface
  call p(sub)
end program

Program mutual
  Procedure(sub1) :: p

  Call p(sub)

  contains
    ! CHECK:: Procedure 'sub1' is recursively defined.  Procedures in the cycle: '{{arg|p|sub1}}', '{{arg|p|sub1}}', '{{arg|p|sub1}}'
    Subroutine sub1(arg)
      procedure(sub1) :: arg
    End Subroutine

    Subroutine sub(p2)
      Procedure(sub1) :: p2
    End Subroutine
End Program

Program mutual1
  Procedure(sub1) :: p

  Call p(sub)

  contains
    ! CHECK:: Procedure 'sub1' is recursively defined.  Procedures in the cycle: '{{p2|sub|arg|p|sub1}}', '{{p2|sub|arg|p|sub1}}', '{{p2|sub|arg|p|sub1}}', '{{p2|sub|arg|p|sub1}}', '{{p2|sub|arg|p|sub1}}'
    Subroutine sub1(arg)
      procedure(sub) :: arg
    End Subroutine

    Subroutine sub(p2)
      Procedure(sub1) :: p2
    End Subroutine
End Program

program twoCycle
  ! CHECK:: The interface for procedure 'p1' is recursively defined
  ! CHECK:: The interface for procedure 'p2' is recursively defined
  procedure(p1) p2
  procedure(p2) p1
  call p1
  call p2
end program

program threeCycle
  ! CHECK:: The interface for procedure 'p1' is recursively defined
  ! CHECK:: The interface for procedure 'p2' is recursively defined
  procedure(p1) p2
  ! CHECK:: The interface for procedure 'p3' is recursively defined
  procedure(p2) p3
  procedure(p3) p1
  call p1
  call p2
  call p3
end program