Bug 93691 - Type bound assignment causes too many finalization of derived type when part of other type
Summary: Type bound assignment causes too many finalization of derived type when part ...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: fortran (show other bugs)
Version: 9.2.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: wrong-code
Depends on:
Blocks: Finalization
  Show dependency treegraph
 
Reported: 2020-02-11 23:41 UTC by Florian Schiffmann
Modified: 2023-03-18 17:25 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2020-06-01 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Florian Schiffmann 2020-02-11 23:41:59 UTC
Good morning,

when writing a reference counting scheme, I cam across this slightly intricate bug. I suspect this is related to a problem in the type bound assignment but I could not pin it down.

The problem:
Type(Inner) has type bound assignment and Final.
   -assignment associates output reference counter to input reference counter and incrments
   -final decrements reference counter and deallocates reference count pointer when 0

Type(outer) has type(inner) as member.

If Type(inner) is used by itself everything works correctly
However, if assignment of type(outer) to type(outer) is made, an additional finalization is invoked (the third with an unassociated reference counter).

Here is the simplest test code I could produce:

! ==============================  BEGIN TEST CODE ========================

MODULE Classes
    IMPLICIT NONE

    TYPE   :: Inner
       INTEGER, POINTER              :: icount
       CONTAINS
       PROCEDURE                     :: init
       PROCEDURE                     :: assignMe
       GENERIC                       :: assignment(=) => assignMe
       FINAL                         :: deleteIt
    END TYPE

   TYPE Outer
      TYPE(Inner)    :: ext
   END TYPE
CONTAINS
   SUBROUTINE init(self)
      CLASS(Inner), INTENT(INOUT)      ::  self

      ALLOCATE(self%icount)
      self%icount=1
   END SUBROUTINE 

! Destrutor, if data is assigned decrement counter and delete once we reach 0
   SUBROUTINE deleteIt(self)
      TYPE(Inner)          ::  self

WRITE(*,*)"FINAL CALLED with icount =", self%icount, "LOC =",LOC(self%icount)
         self%icount=self%icount-1
         IF(self%icount<=0)THEN ! usually == 0 but <=0 better shows the problem
            self%icount=-100
WRITE(*,*)"      DEALLOCATING ICOUNT at LOC=", LOC(self%icount)
            DEALLOCATE(self%icount)
         END IF
   END SUBROUTINE

! The basic assigment routine, set pointer to input data pointer and increment counter 
   SUBROUTINE assignMe(self, input)
      CLASS(Inner), INTENT(INOUT)      ::  self
      CLASS(Inner), INTENT(IN)      ::  input
         self%icount => input%icount
         self%icount=self%icount+1
   END SUBROUTINE
  

END MODULE

PROGRAM test

   USE Classes

IMPLICIT NONE

WRITE(*,*)"Direct Call on inner performs only 2 FINALIZATIONS"
   BLOCK
      TYPE(Inner)      :: inner1, inner2
      CALL inner1%init()
      inner2=inner1
   END BLOCK
WRITE(*,*)
WRITE(*,*)"Indirect Call, 3 FINALIZATIONS, last with dangling pointer on TYPE(inner)%icount"
   BLOCK
      TYPE(Outer)          :: Outer1, Outer2
      CALL Outer1%ext%init()
      Outer2=Outer1
   END BLOCK 
END 

!========================= END TEST CODE ================================


 Note: in Final comparison is for <= 0 to cause double free

best regards
Flo
Comment 1 Thomas Koenig 2020-06-01 19:13:44 UTC
Confirmed.
Comment 2 martin 2020-06-03 06:32:04 UTC
This might be related to bug 88735, at least the ingredients are very similar and final routine is called wrongly.
Comment 3 GCC Commits 2023-03-18 07:56:47 UTC
The master branch has been updated by Paul Thomas <pault@gcc.gnu.org>:

https://gcc.gnu.org/g:d7caf313525a46f200d7f5db1ba893f853774aee

commit r13-6747-gd7caf313525a46f200d7f5db1ba893f853774aee
Author: Paul Thomas <pault@gcc.gnu.org>
Date:   Sat Mar 18 07:56:23 2023 +0000

    Fortran: Fix bugs and missing features in finalization [PR37336]
    
    2023-03-18  Paul Thomas  <pault@gcc.gnu.org>
    
    gcc/fortran
            PR fortran/103854
            PR fortran/96122
            PR fortran/37336
            * class.cc (finalize_component): Include the missing arguments
            in the call to the component's finalizer wrapper.
            (has_finalizer_component): Do not return true for procedure
            pointer components.
            (finalizer_insert_packed_call): Remove the redundant argument
            in the call to the final subroutine.
            (generate_finalization_wrapper): Add support for assumed rank
            finalizers.
            (gfc_may_be_finalized): New helper function.
            * dump-parse-tree.cc (write_proc): Whitespace.
            * gfortran.h : Add prototype for gfc_may_be_finalized.
            * resolve.cc (resolve_function): Correct derived types that
            have an incomplete namespace.
            (resolve_where, gfc_resolve_where_code_in_forall,
            gfc_resolve_forall_body, gfc_resolve_code): Check that the op
            code is still EXEC_ASSIGN. If it is set lhs to must finalize.
            (is_finalizable_type): New function.
            (generate_component_assignments): Set must_finalize if needed.
            (gfc_resolve_finalizers): Error if assumed rank finalizer is
            not the only one. Warning on lack of scalar finalizer modified
            to account for assumed rank finalizers.
            (generate_final_call): New function.
            (generate_component_assignments): Enclose the outermost call in
            a block to capture automatic deallocation and final calls.
            Set must_finalize as required to satisfy the standards. Use an
            explicit pointer assignment for pointer components to capture
            finalization of the target. Likewise use explicit assignment
            for allocatable components. Do not use the temporary copy of
            the lhs in defined assignment if the component is allocatable.
            Put the temporary in the same namespace as the lhs symbol if
            the component may be finalized. Remove the leading assignment
            from the expansion of assignment of components that have their
            own defined assignment components. Suppress finalization of
            assignment of temporary components to the lhs. Make an explicit
            final call for the rhs function temporary if it exists.
            (gfc_resolve_code): Set must_finalize for assignments with an
            array constructor on the rhs.
            (gfc_resolve_finalizers): Ensure that an assumed rank finalizer
            is the only finalizer for that type and correct the surprising
            warning for the lack of a scalar finalizer.
            (check_defined_assignments): Handle allocatable components.
            (resolve_fl_derived): Set referenced the vtab for use
            associated symbols.
            (resolve_symbol): Set referenced an unreferenced symbol that
            will be finalized.
            * trans-array.cc (gfc_trans_array_constructor_value): Add code
            to finalize the constructor result. Warn that this feature was
            removed in F2018 and that it is suppressed by -std=2018.
            (trans_array_constructor): Add finalblock, pass to previous
            and apply to loop->post if filled.
            (gfc_add_loop_ss_code): Add se finalblock to outer loop post.
            (gfc_trans_array_cobounds, gfc_trans_array_bounds): Add any
            generated finalization code to the main block.
            (structure_alloc_comps): Add boolean argument to suppress
            finalization and use it for calls from
            gfc_deallocate_alloc_comp_no_caf. Otherwise it defaults to
            false.
            (gfc_copy_alloc_comp_no_fini): New wrapper for
            structure_alloc_comps.
            (gfc_alloc_allocatable_for_assignment): Suppress finalization
            by setting new arg in call to gfc_deallocate_alloc_comp_no_caf.
            (gfc_trans_deferred_array): Use gfc_may_be_finalized and do not
            deallocate the components of entities with a leading '_' in the
            name that are also marked as artificial.
            * trans-array.h : Add the new boolean argument to the prototype
            of gfc_deallocate_alloc_comp_no_caf with a default of false.
            Add prototype for gfc_copy_alloc_comp_no_fini.
            * trans-decl.cc(init_intent_out_dt): Tidy up the code.
            * trans-expr.cc (gfc_init_se): Initialize finalblock.
            (gfc_conv_procedure_call): Use gfc_finalize_tree_expr to
            finalize function results. Replace in-line block for class
            results with call to new function.
            (gfc_conv_expr): Finalize structure constructors for F2003 and
            F2008. Warn that this feature was deleted in F2018 and, unlike
            array constructors, is not default. Add array constructor
            finalblock to the post block.
            (gfc_trans_scalar_assign): Suppress finalization by setting new
            argument in call to gfc_deallocate_alloc_comp_no_caf. Add the
            finalization blocks to the main block.
            (gfc_trans_arrayfunc_assign): Use gfc_assignment_finalizer_call
            and ensure that finalization occurs after the evaluation of the
            rhs but using the initial value for the lhs. Finalize rhs
            function results using gfc_finalize_tree_expr.
            (trans_class_assignment, gfc_trans_assignment_1): As previous
            function, taking care to order evaluation, assignment and
            finalization correctly.
            * trans-io.cc (gfc_trans_transfer): Add the final block.
            * trans-stmt.cc (gfc_trans_call, gfc_trans_allocate): likewise.
            (trans_associate_var): Nullify derived allocatable components
            and finalize function targets with defined assignment
            components on leaving the block scope.
            (trans_allocate): Finalize source expressions, if required,
            and set init_expr artificial temporarily to suppress the
            finalization in gfc_trans_assignment.
            * trans.cc (gfc_add_finalizer_call): Do not finalize the
            temporaries generated in type assignment with defined
            assignment components.
            (gfc_assignment_finalizer_call): New function.
            (gfc_finalize_tree_expr): New function.
            * trans.h: Add finalblock to gfc_se. Add the prototypes for
            gfc_finalize_tree_expr and gfc_assignment_finalizer_call.
    
    gcc/testsuite/
            PR fortran/64290
            * gfortran.dg/finalize_38.f90 : New test.
            * gfortran.dg/finalize_38a.f90 : New test.
            * gfortran.dg/allocate_with_source_25.f90 : The number of final
            calls goes down from 6 to 4.
            * gfortran.dg/associate_25.f90 : Remove the incorrect comment.
            * gfortran.dg/auto_dealloc_2.f90 : Change the tree dump expr
            but the final count remains the same.
            * gfortran.dg/unlimited_polymorphic_8.f90 : Tree dump reveals
            foo.1.x rather than foo.0.x
    
            PR fortran/67444
            * gfortran.dg/finalize_39.f90 : New test.
    
            PR fortran/67471
            * gfortran.dg/finalize_40.f90 : New test.
    
            PR fortran/69298
            PR fortran/70863
            * gfortran.dg/finalize_41.f90 : New test.
    
            PR fortran/71798
            * gfortran.dg/finalize_42.f90 : New test.
    
            PR fortran/80524
            * gfortran.dg/finalize_43.f90 : New test.
    
            PR fortran/82996
            * gfortran.dg/finalize_44.f90 : New test.
    
            PR fortran/84472
            * gfortran.dg/finalize_45.f90 : New test.
    
            PR fortran/88735
            PR fortran/93691
            * gfortran.dg/finalize_46.f90 : New test.
    
            PR fortran/91316
            * gfortran.dg/finalize_47.f90 : New test.
    
            PR fortran/106576
            * gfortran.dg/finalize_48.f90 : New test.
    
            PR fortran/37336
            * gfortran.dg/finalize_49.f90 : New test.
            * gfortran.dg/finalize_50.f90 : New test.
            * gfortran.dg/finalize_51.f90 : New test.
Comment 4 Paul Thomas 2023-03-18 17:25:36 UTC
Hi Florian,

I am closing this as fixed, even though it runs to the third finalization and then segfaults, because both nagfor and ifort behave in the same way.

The dump of the code shows that Outer2 is finalized before the assignment and that both Outer1 and Outer2 are finalized on leaving the block scope. Thus, alll is as it should be.

Default initializing icount to NULL(), guarding for unassociated icount in deleteIt gets rid of the segfault.

Thanks for the report.

Paul