See attached patch. Regression tested on x86_64.
A new test case is added to asan to catch this in the future. OK for mainline. Regards, Jerry --- fortran: Fix memory leak for array structure-constructor element [PR121972] Fix the remaining leak in pdt_86.f03. Added a new test in asan to avoid it. PR fortran/121972 gcc/fortran/ChangeLog: * trans-array.cc (has_class_alloc_comp): New helper; returns true if derived type DER has any CLASS component. (gfc_trans_array_ctor_element): Also free allocatable components when the element expression is EXPR_STRUCTURE, skipping types that contain CLASS components to avoid freeing stack-allocated _data pointers. (gfc_constructor_is_owned_alloc_comp): Likewise treat EXPR_STRUCTURE elements as owned only when the derived type has no CLASS components. * trans-expr.cc (gfc_trans_alloc_subarray_assign): Free allocatable components of the component's temporary descriptor before nulling its data pointer, for non-variable source expressions. gcc/testsuite/ChangeLog: * gfortran.dg/class_array_15.f03: Adjust expected free count. * gfortran.dg/derived_constructor_comps_6.f90: Adjust expected free count. * gfortran.dg/asan/structure_constructor_alloc_comp_leak_1.f90: New test. ---
commit 7ca9522587ad9619e61128260dbd559bbb113280 Author: Jerry DeLisle <[email protected]> Date: Fri Jun 19 14:54:00 2026 -0700 fortran: Fix memory leak for array structure-constructor element [PR121972] Fix the remaining leak in pdt_86.f03. Added a new test in asan to avoid it. PR fortran/121972 gcc/fortran/ChangeLog: * trans-array.cc (has_class_alloc_comp): New helper; returns true if derived type DER has any CLASS component. (gfc_trans_array_ctor_element): Also free allocatable components when the element expression is EXPR_STRUCTURE, skipping types that contain CLASS components to avoid freeing stack-allocated _data pointers. (gfc_constructor_is_owned_alloc_comp): Likewise treat EXPR_STRUCTURE elements as owned only when the derived type has no CLASS components. * trans-expr.cc (gfc_trans_alloc_subarray_assign): Free allocatable components of the component's temporary descriptor before nulling its data pointer, for non-variable source expressions. gcc/testsuite/ChangeLog: * gfortran.dg/class_array_15.f03: Adjust expected free count. * gfortran.dg/derived_constructor_comps_6.f90: Adjust expected free count. * gfortran.dg/asan/structure_constructor_alloc_comp_leak_1.f90: New test. diff --git a/gcc/fortran/trans-array.cc b/gcc/fortran/trans-array.cc index 9d8e913a84a..2da98cfa0fd 100644 --- a/gcc/fortran/trans-array.cc +++ b/gcc/fortran/trans-array.cc @@ -1967,6 +1967,20 @@ static bool first_len; static tree first_len_val; static bool typespec_chararray_ctor; +/* Return true if DER has any CLASS allocatable component. Such components + are initialised by VIEW_CONVERT in structure constructors (a bitwise copy + of the class descriptor), so their _data pointer may refer to a non-heap + object and must not be passed to gfc_deallocate_alloc_comp_no_caf. */ + +static bool +has_class_alloc_comp (gfc_symbol *der) +{ + for (gfc_component *c = der->components; c; c = c->next) + if (c->ts.type == BT_CLASS) + return true; + return false; +} + static void gfc_trans_array_ctor_element (stmtblock_t * pblock, tree desc, tree offset, gfc_se * se, gfc_expr * expr) @@ -1983,7 +1997,10 @@ gfc_trans_array_ctor_element (stmtblock_t * pblock, tree desc, offset_eval = gfc_evaluate_now (offset, &se->pre); tmp = gfc_build_array_ref (tmp, offset_eval, NULL); - if (expr->expr_type == EXPR_FUNCTION && expr->ts.type == BT_DERIVED + if ((expr->expr_type == EXPR_FUNCTION + || (expr->expr_type == EXPR_STRUCTURE + && !has_class_alloc_comp (expr->ts.u.derived))) + && expr->ts.type == BT_DERIVED && expr->ts.u.derived->attr.alloc_comp) gfc_add_expr_to_block (&se->finalblock, gfc_deallocate_alloc_comp_no_caf (expr->ts.u.derived, @@ -2168,7 +2185,9 @@ gfc_constructor_is_owned_alloc_comp (gfc_constructor_base base, if (!gfc_constructor_is_owned_alloc_comp (e->value.constructor, der)) return false; } - else if (!(e->expr_type == EXPR_FUNCTION + else if (!((e->expr_type == EXPR_FUNCTION + || (e->expr_type == EXPR_STRUCTURE + && !has_class_alloc_comp (e->ts.u.derived))) && e->ts.type == BT_DERIVED && e->ts.u.derived == der)) return false; diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc index 6e3661a0922..9108e92b446 100644 --- a/gcc/fortran/trans-expr.cc +++ b/gcc/fortran/trans-expr.cc @@ -9855,8 +9855,15 @@ gfc_trans_alloc_subarray_assign (tree dest, gfc_component * cm, gfc_add_block_to_block (final_block, &se.finalblock); if (expr->expr_type != EXPR_VARIABLE) - gfc_conv_descriptor_data_set (&block, se.expr, - null_pointer_node); + { + if (gfc_bt_struct (cm->ts.type) && cm->ts.u.derived->attr.alloc_comp) + { + tmp = gfc_deallocate_alloc_comp_no_caf (cm->ts.u.derived, + se.expr, cm->as->rank, true); + gfc_add_expr_to_block (&block, tmp); + } + gfc_conv_descriptor_data_set (&block, se.expr, null_pointer_node); + } /* We need to know if the argument of a conversion function is a variable, so that the correct lower bound can be used. */ diff --git a/gcc/testsuite/gfortran.dg/asan/structure_constructor_alloc_comp_leak_1.f90 b/gcc/testsuite/gfortran.dg/asan/structure_constructor_alloc_comp_leak_1.f90 new file mode 100644 index 00000000000..5144c83aa36 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/asan/structure_constructor_alloc_comp_leak_1.f90 @@ -0,0 +1,36 @@ +! { dg-do run } + +! PR fortran/121972 + +! A structure constructor used as an array-constructor element whose type has +! allocatable components leaked those components. gfc_trans_array_ctor_element +! freed alloc comps only for EXPR_FUNCTION elements; EXPR_STRUCTURE was missed. +! gfc_trans_alloc_subarray_assign also nulled the temp descriptor data pointer +! without first freeing the element alloc comps. Checked under -fsanitize=address. + +module m + implicit none + type string_t + character(len=:), allocatable :: s + end type + type file_t + type(string_t), allocatable :: lines(:) + end type +contains + subroutine check (line) + character(len=*), intent(in) :: line + type(file_t) :: f + f = file_t([string_t(line)]) + if (.not. allocated (f%lines)) stop 1 + if (size (f%lines) /= 1) stop 2 + if (.not. allocated (f%lines(1)%s)) stop 3 + if (f%lines(1)%s /= line) stop 4 + end subroutine +end module + +program structure_constructor_alloc_comp_leak_1 + use m + call check ('hello') + call check ('world') + call check ('a longer string value') +end program diff --git a/gcc/testsuite/gfortran.dg/class_array_15.f03 b/gcc/testsuite/gfortran.dg/class_array_15.f03 index f53b2356952..c726e6e8acb 100644 --- a/gcc/testsuite/gfortran.dg/class_array_15.f03 +++ b/gcc/testsuite/gfortran.dg/class_array_15.f03 @@ -115,4 +115,4 @@ subroutine pr54992 ! This test remains as the original. bh => bhGet(b,instance=2) if (loc (b) .ne. loc(bh%hostNode)) STOP 8 end -! { dg-final { scan-tree-dump-times "builtin_free" 16 "original" } } +! { dg-final { scan-tree-dump-times "builtin_free" 17 "original" } } diff --git a/gcc/testsuite/gfortran.dg/derived_constructor_comps_6.f90 b/gcc/testsuite/gfortran.dg/derived_constructor_comps_6.f90 index 406e031456f..09cb9946bbf 100644 --- a/gcc/testsuite/gfortran.dg/derived_constructor_comps_6.f90 +++ b/gcc/testsuite/gfortran.dg/derived_constructor_comps_6.f90 @@ -130,4 +130,4 @@ contains end function new_prt_spec3 end program main ! { dg-final { scan-tree-dump-times "__builtin_malloc" 16 "original" } } -! { dg-final { scan-tree-dump-times "__builtin_free" 33 "original" } } +! { dg-final { scan-tree-dump-times "__builtin_free" 35 "original" } }
