[gcc r16-9541] Fortran: [13-17 regression] Fix duplicated finalization [PR110626]
Paul Thomas via Gcc-cvs <[email protected]>
| Newsgroups | gmane.comp.gcc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://gcc.gnu.org/g:38b61f4fee1e5ca8ac64dd1c9a077e20b09e09ec commit r16-9541-g38b61f4fee1e5ca8ac64dd1c9a077e20b09e09ec Author: Christopher Albert <[email protected]> Date: Sun Aug 16 13:34:44 2026 +0100 Fortran: [13-17 regression] Fix duplicated finalization [PR110626] For a derived-type intrinsic assignment with a component defined assignment, generate_component_assignments emitted a whole-derived-type assignment and a defined-assignment call on a temporary copy of the old lhs. The whole assignment finalized the lhs. The INTENT(OUT) argument then finalized the stale temporary instead of the real component, so both finalizations saw the old value. For a scalar lhs without pointer or allocatable components, mark the whole-derived-type assignment finalize_only. It finalizes the lhs without copying the structure, then assigns each component separately. The component defined assignment acts on the real component and its INTENT(OUT) finalization sees the value left by the first finalization. Array lhs assignments remain on the existing path because their component-wise expansion needs scalarization. The allocatable-component case remains PR fortran/57696. Assisted-by: GPT-5.6-sol (OpenAI) Assisted-by: Claude (Anthropic) 2026-08-16 Christopher Albert <[email protected]> gcc/fortran PR fortran/110626 * gfortran.h : Add finalize_only bitfield to gfc_expr. * resolve.cc (generate_component_assignments): If the lhs has no pointer or allocatable components, set use_finalize_only to false. Otherwise it equals finalizable_lhs. Use the value to set the expr1 finalize_only field and add the assignment to the chain. If use_finalize_only is false, free the statements and transfer the value to the final result. * trans-expr.cc (gfc_trans_assignment_1): If expr1 is finalize_ only emit an empty statement. gcc/testsuite/ PR fortran/110626 * gfortran.dg/finalize_62.f90: New test. Signed-off-by: Christopher Albert <[email protected]> (cherry picked from commit a03222414043b7155a76f7e564fb688ac5ac7bb6) Diff: --- gcc/fortran/gfortran.h | 4 ++ gcc/fortran/resolve.cc | 48 ++++++++++++++++++++--- gcc/fortran/trans-expr.cc | 5 +++ gcc/testsuite/gfortran.dg/finalize_62.f90 | 65 +++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 5 deletions(-) diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h index e4a368bb58fa..6f387c684768 100644 --- a/gcc/fortran/gfortran.h +++ b/gcc/fortran/gfortran.h @@ -2742,6 +2742,10 @@ typedef struct gfc_expr /* Will require finalization after use. */ unsigned int must_finalize : 1; + /* For a derived-type intrinsic assignment generated by + generate_component_assignments. */ + unsigned int finalize_only : 1; + /* Set this if no range check should be performed on this expression. */ unsigned int no_bounds_check : 1; diff --git a/gcc/fortran/resolve.cc b/gcc/fortran/resolve.cc index ae33bc1b32d3..83426e98ba36 100644 --- a/gcc/fortran/resolve.cc +++ b/gcc/fortran/resolve.cc @@ -13754,6 +13754,7 @@ generate_component_assignments (gfc_code **code, gfc_namespace *ns) gfc_expr *tmp_expr = NULL; int error_count, depth; bool finalizable_lhs; + bool use_finalize_only; gfc_get_errors (NULL, &error_count); @@ -13797,6 +13798,24 @@ generate_component_assignments (gfc_code **code, gfc_namespace *ns) finalizable_lhs = is_finalizable_type ((*code)->expr1->ts); + /* When the lhs is finalized as a whole and none of its components needs the + structure copy to handle it (no pointer or allocatable components), the + copy can be done component by component. The whole-derived-type assignment + then only finalizes the lhs and a component with a defined assignment keeps + its post-finalization value for the INTENT (OUT) finalization in that + defined assignment. */ + use_finalize_only = finalizable_lhs; + if (use_finalize_only) + for (comp1 = (*code)->expr1->ts.u.derived->components; comp1; + comp1 = comp1->next) + if (comp1->attr.pointer || comp1->attr.allocatable + || comp1->attr.proc_pointer_comp || comp1->attr.class_pointer + || comp1->attr.proc_pointer) + { + use_finalize_only = false; + break; + } + /* Create a temporary so that functions get called only once. */ if ((*code)->expr2->expr_type != EXPR_VARIABLE && (*code)->expr2->expr_type != EXPR_CONSTANT) @@ -13833,6 +13852,8 @@ generate_component_assignments (gfc_code **code, gfc_namespace *ns) this_code = build_assignment (EXEC_ASSIGN, (*code)->expr1, (*code)->expr2, NULL, NULL, (*code)->loc); + if (use_finalize_only) + this_code->expr1->finalize_only = 1; add_code_to_chain (&this_code, &head, &tail); } @@ -13852,7 +13873,20 @@ generate_component_assignments (gfc_code **code, gfc_namespace *ns) || comp1->attr.proc_pointer_comp || comp1->attr.class_pointer || comp1->attr.proc_pointer) - continue; + { + /* With finalize_only the whole-derived-type assignment does not copy + the components, so emit the copy for this one here. Only plain + components reach this point, since use_finalize_only excludes + pointer and allocatable components. */ + if (use_finalize_only) + { + this_code = build_assignment (EXEC_ASSIGN, + (*code)->expr1, (*code)->expr2, + comp1, comp2, (*code)->loc); + add_code_to_chain (&this_code, &head, &tail); + } + continue; + } finalizable_comp = is_finalizable_type (comp1->ts) && !finalizable_lhs; @@ -13894,7 +13928,10 @@ generate_component_assignments (gfc_code **code, gfc_namespace *ns) && dummy_args->sym->attr.intent == INTENT_OUT; inout = dummy_args && dummy_args->sym->attr.intent == INTENT_INOUT; - if ((inout || finalizable_out) + /* With finalize_only the lhs component keeps its post-finalization + value, so the defined assignment can finalize it directly through + its INTENT (OUT) argument and no temporary is needed. */ + if ((inout || (finalizable_out && !use_finalize_only)) && !comp1->attr.allocatable) { gfc_code *temp_code; @@ -13971,10 +14008,11 @@ generate_component_assignments (gfc_code **code, gfc_namespace *ns) { /* Don't add intrinsic assignments since they are already effected by the intrinsic assignment of the structure, unless - finalization is required. */ + finalization is required or, with finalize_only, the structure + assignment does not copy the components. */ if (finalizable_comp) this_code->expr1->must_finalize = 1; - else + else if (!use_finalize_only) { gfc_free_statements (this_code); this_code = NULL; @@ -13995,7 +14033,7 @@ generate_component_assignments (gfc_code **code, gfc_namespace *ns) add_code_to_chain (&this_code, &head, &tail); - if (t1 && (inout || finalizable_out)) + if (t1 && (inout || (finalizable_out && !use_finalize_only))) { /* Transfer the value to the final result. */ this_code = build_assignment (EXEC_ASSIGN, diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc index b8c501143967..f087e803155d 100644 --- a/gcc/fortran/trans-expr.cc +++ b/gcc/fortran/trans-expr.cc @@ -13669,6 +13669,11 @@ gfc_trans_assignment_1 (gfc_expr * expr1, gfc_expr * expr2, bool init_flag, && assoc_assign) tmp = gfc_trans_pointer_assignment (expr1, expr2); + /* The finalization above is all that is wanted: the structure copy is done + component by component in generate_component_assignments. */ + if (expr1->finalize_only) + tmp = build_empty_stmt (input_location); + /* If nothing else works, do it the old fashioned way! */ if (tmp == NULL_TREE) { diff --git a/gcc/testsuite/gfortran.dg/finalize_62.f90 b/gcc/testsuite/gfortran.dg/finalize_62.f90 new file mode 100644 index 000000000000..045b127ea86a --- /dev/null +++ b/gcc/testsuite/gfortran.dg/finalize_62.f90 @@ -0,0 +1,65 @@ +! { dg-do run } +! +! PR fortran/110626 - in a derived-type intrinsic assignment, a finalizable +! component that has a defined assignment is finalized twice: once by the +! whole-derived-type finalization of the lhs and once by the INTENT(OUT) +! argument of the defined assignment. The second finalization must see the +! value left by the first one, not a stale copy, matching other compilers. +! +module pr110626 + implicit none + + type :: cell + integer :: tag = 0 + contains + final :: wipe + procedure :: copyinto + generic :: assignment(=) => copyinto + end type + + type :: box + type(cell) :: c + end type + + integer :: nf = 0 + integer :: ncopy = 0 + integer :: seen_final(4) = 0 + integer :: seen_copy = -99 + +contains + + subroutine wipe (self) + type(cell), intent(inout) :: self + nf = nf + 1 + if (nf <= size (seen_final)) seen_final(nf) = self%tag + self%tag = -1 + end subroutine + + subroutine copyinto (dst, src) + class(cell), intent(out) :: dst + type(cell), intent(in) :: src + ncopy = ncopy + 1 + seen_copy = dst%tag + dst%tag = src%tag + 1 + end subroutine + +end module + +program p + use pr110626 + implicit none + type(box) :: src, dst + + src%c%tag = 7 + dst%c%tag = 42 + + nf = 0; ncopy = 0 + dst = src + + if (nf /= 2) stop 1 ! two finalizations of the old component + if (seen_final(1) /= 42) stop 2 ! first sees the old value + if (seen_final(2) /= -1) stop 3 ! second sees the post-finalization value + if (ncopy /= 1) stop 4 ! defined assignment runs once + if (seen_copy /= 0) stop 5 ! INTENT(OUT) default-initialised before body + if (dst%c%tag /= 8) stop 6 ! result is src + 1 +end program