}
+/* Returns true if the symbol SYM has, through its FORMAL_NS field, a reference
+ to itself which should be eliminated for the symbol memory to be released
+ via normal reference counting.
+
+ The implementation is crucial as it controls the proper release of symbols,
+ especially (contained) procedure symbols, which can represent a lot of memory
+ through the namespace of their body.
+
+ We try to avoid freeing too much memory (causing dangling pointers), to not
+ leak too much (wasting memory), and to avoid expensive walks of the symbol
+ tree (which would be the correct way to check for a cycle). */
+
+bool
+cyclic_reference_break_needed (gfc_symbol *sym)
+{
+ /* Normal symbols don't reference themselves. */
+ if (sym->formal_ns == nullptr)
+ return false;
+
+ /* Procedures at the root of the file do have a self reference, but they don't
+ have a reference in a parent namespace preventing the release of the
+ procedure namespace, so they can use the normal reference counting. */
+ if (sym->formal_ns == sym->ns)
+ return false;
+
+ /* If sym->refs == 1, we can use normal reference counting. If sym->refs > 2,
+ the symbol won't be freed anyway, with or without cyclic reference. */
+ if (sym->refs != 2)
+ return false;
+
+ /* Procedure symbols host-associated from a module in submodules are special,
+ because the namespace of the procedure block in the submodule is different
+ from the FORMAL_NS namespace generated by host-association. So there are
+ two different namespaces representing the same procedure namespace. As
+ FORMAL_NS comes from host-association, which only imports symbols visible
+ from the outside (dummy arguments basically), we can assume there is no
+ self reference through FORMAL_NS in that case. */
+ if (sym->attr.host_assoc && sym->attr.used_in_submodule)
+ return false;
+
+ /* We can assume that contained procedures have cyclic references, because
+ the symbol of the procedure itself is accessible in the procedure body
+ namespace. So we assume that symbols with a formal namespace different
+ from the declaration namespace and two references, one of which is about
+ to be removed, are procedures with just the self reference left. At this
+ point, the symbol SYM matches that pattern, so we return true here to
+ permit the release of SYM. */
+ return true;
+}
+
+
/* Decrease the reference counter and free memory when we reach zero.
Returns true if the symbol has been freed, false otherwise. */
if (sym == NULL)
return false;
- if (sym->formal_ns != NULL && sym->refs == 2 && sym->formal_ns != sym->ns
- && (!sym->attr.entry || !sym->module))
+ if (cyclic_reference_break_needed (sym))
{
/* As formal_ns contains a reference to sym, delete formal_ns just
before the deletion of sym. */