See attached patch. This fixes a memory leak we found after committing the
original fix. We should get this in there before I forget.
Regression tested on x86_64.
OK for mainline and later backport when things have settled a bit?
Regards,
Jerry
PS I need to fix a PR reference in the asan/implied_do_alloc_comp_leak_1.f90
before the push.
---
fortran: [PR125535] Plug remaining leak in implied-do array
constructor with allocatable components
Follow-up to the PR fortran/125535 wrong-code fix (commit 1b8421e9d5b,
already pushed): that fix corrected the *values* produced by an
implied-do array constructor of derived-type function results with
allocatable components, but left a separate memory leak in the same
code path.
An array constructor whose implied-do produces function results of a
derived type with allocatable components moves each result into the
constructor temporary, so the temporary owns those components. The
per-element finalization in gfc_trans_array_ctor_element only freed
the single slot referenced by the final loop offset, leaking the
allocatable components of every other element the loop produced.
A whole-array sweep is the only way to free every slot written by an
implied-do, but it may be used only when every element is an owned
function result: a variable element is shallow-copied into the
temporary and its components are aliased rather than owned, so freeing
them would double free. Add gfc_constructor_is_owned_alloc_comp to
detect the all-owned case and, when it holds, suppress the per-element
finalization and emit a single gfc_deallocate_alloc_comp_no_caf over
the whole temporary.
Assisted by: Claude Opus 4.8
PR fortran/125535
gcc/fortran/ChangeLog:
* trans-array.cc (gfc_constructor_is_owned_alloc_comp): New function.
(gfc_trans_array_constructor_value): Add OWNED_SWEEP parameter and,
when set, suppress the per-element finalization. Pass it through the
recursive call.
(trans_array_constructor): Compute OWNED_SWEEP and, when set,
deallocate the allocatable components of the whole temporary in one
sweep.
gcc/testsuite/ChangeLog:
* gfortran.dg/asan/implied_do_alloc_comp_leak_1.f90: New test.
---From af0a8c57f4350ff403df8c506686ab044e942bfc Mon Sep 17 00:00:00 2001
From: Jerry DeLisle <[email protected]>
Date: Thu, 11 Jun 2026 18:50:37 -0700
Subject: [PATCH] fortran: [PR125535] Plug remaining leak in implied-do array
constructor with allocatable components
Follow-up to the PR fortran/125535 wrong-code fix (commit 1b8421e9d5b,
already pushed): that fix corrected the *values* produced by an
implied-do array constructor of derived-type function results with
allocatable components, but left a separate memory leak in the same
code path.
An array constructor whose implied-do produces function results of a
derived type with allocatable components moves each result into the
constructor temporary, so the temporary owns those components. The
per-element finalization in gfc_trans_array_ctor_element only freed
the single slot referenced by the final loop offset, leaking the
allocatable components of every other element the loop produced.
A whole-array sweep is the only way to free every slot written by an
implied-do, but it may be used only when every element is an owned
function result: a variable element is shallow-copied into the
temporary and its components are aliased rather than owned, so freeing
them would double free. Add gfc_constructor_is_owned_alloc_comp to
detect the all-owned case and, when it holds, suppress the per-element
finalization and emit a single gfc_deallocate_alloc_comp_no_caf over
the whole temporary.
Assisted by: Claude Opus 4.8
PR fortran/125535
gcc/fortran/ChangeLog:
* trans-array.cc (gfc_constructor_is_owned_alloc_comp): New function.
(gfc_trans_array_constructor_value): Add OWNED_SWEEP parameter and,
when set, suppress the per-element finalization. Pass it through the
recursive call.
(trans_array_constructor): Compute OWNED_SWEEP and, when set,
deallocate the allocatable components of the whole temporary in one
sweep.
gcc/testsuite/ChangeLog:
* gfortran.dg/asan/implied_do_alloc_comp_leak_1.f90: New test.
---
gcc/fortran/trans-array.cc | 70 +++++++++++++++++--
.../asan/implied_do_alloc_comp_leak_1.f90 | 45 ++++++++++++
2 files changed, 110 insertions(+), 5 deletions(-)
create mode 100644 gcc/testsuite/gfortran.dg/asan/implied_do_alloc_comp_leak_1.f90
diff --git a/gcc/fortran/trans-array.cc b/gcc/fortran/trans-array.cc
index 211ce36fe8f..9d8e913a84a 100644
--- a/gcc/fortran/trans-array.cc
+++ b/gcc/fortran/trans-array.cc
@@ -2143,17 +2143,56 @@ gfc_trans_array_constructor_subarray (stmtblock_t * pblock,
}
+/* Return true if every leaf element of an array constructor is a function
+ reference returning derived type DER, which has allocatable components.
+ Such results are moved (shallow-copied) into the constructor temporary, so
+ the temporary owns their allocatable components and they can all be freed
+ in a single sweep over the whole temporary. Returns false as soon as an
+ element is anything else - notably a variable, whose allocatable components
+ are aliased rather than owned by the temporary and must not be freed. */
+
+static bool
+gfc_constructor_is_owned_alloc_comp (gfc_constructor_base base,
+ gfc_symbol *der)
+{
+ gfc_constructor *c;
+
+ if (base == NULL)
+ return false;
+
+ for (c = gfc_constructor_first (base); c; c = gfc_constructor_next (c))
+ {
+ gfc_expr *e = c->expr;
+ if (e->expr_type == EXPR_ARRAY)
+ {
+ if (!gfc_constructor_is_owned_alloc_comp (e->value.constructor, der))
+ return false;
+ }
+ else if (!(e->expr_type == EXPR_FUNCTION
+ && e->ts.type == BT_DERIVED
+ && e->ts.u.derived == der))
+ return false;
+ }
+ return true;
+}
+
+
/* Assign the values to the elements of an array constructor. DYNAMIC
is true if descriptor DESC only contains enough data for the static
size calculated by gfc_get_array_constructor_size. When true, memory
- for the dynamic parts must be allocated using realloc. */
+ for the dynamic parts must be allocated using realloc. OWNED_SWEEP is
+ true when the caller will free the allocatable components of every
+ constructor element in one sweep over the whole temporary; in that case
+ the per-element finalization built here is suppressed to avoid a double
+ free. */
static void
gfc_trans_array_constructor_value (stmtblock_t * pblock,
stmtblock_t * finalblock,
tree type, tree desc,
gfc_constructor_base base, tree * poffset,
- tree * offsetvar, bool dynamic)
+ tree * offsetvar, bool dynamic,
+ bool owned_sweep)
{
tree tmp;
tree start = NULL_TREE;
@@ -2221,7 +2260,8 @@ gfc_trans_array_constructor_value (stmtblock_t * pblock,
/* Array constructors can be nested. */
gfc_trans_array_constructor_value (&body, finalblock, type,
desc, c->expr->value.constructor,
- poffset, offsetvar, dynamic);
+ poffset, offsetvar, dynamic,
+ owned_sweep);
}
else if (c->expr->rank > 0)
{
@@ -2257,7 +2297,11 @@ gfc_trans_array_constructor_value (stmtblock_t * pblock,
*poffset = fold_build2_loc (input_location, PLUS_EXPR,
gfc_array_index_type,
*poffset, gfc_index_one_node);
- if (finalblock)
+ /* Unless the whole temporary is being swept by the caller, add
+ the per-element finalization. The sweep is used when every
+ element is an owned function result, which is the only way to
+ correctly free elements produced inside an implied-do loop. */
+ if (finalblock && !owned_sweep)
gfc_add_block_to_block (finalblock, &se.finalblock);
}
else
@@ -2910,6 +2954,7 @@ trans_array_constructor (gfc_ss * ss, locus * where)
char *msg;
stmtblock_t finalblock;
bool finalize_required;
+ bool owned_sweep = false;
/* Save the old values for nested checking. */
old_first_len = first_len;
@@ -3095,10 +3140,25 @@ trans_array_constructor (gfc_ss * ss, locus * where)
if (IS_PDT (expr))
finalize_required = true;
+ /* If every element of the constructor is a function result with allocatable
+ components, those components are owned by the temporary and are freed in a
+ single sweep over the whole array below. This is the only way to free the
+ elements produced inside an implied-do loop, where a single compile-time
+ element stands for many runtime elements. */
+ owned_sweep = finalize_required
+ && expr->ts.type == BT_DERIVED
+ && expr->ts.u.derived->attr.alloc_comp
+ && gfc_constructor_is_owned_alloc_comp (c, expr->ts.u.derived);
+
gfc_trans_array_constructor_value (&outer_loop->pre,
finalize_required ? &finalblock : NULL,
type, desc, c, &offset, &offsetvar,
- dynamic);
+ dynamic, owned_sweep);
+
+ if (owned_sweep)
+ gfc_add_expr_to_block (&finalblock,
+ gfc_deallocate_alloc_comp_no_caf (expr->ts.u.derived,
+ desc, 1, true));
/* If the array grows dynamically, the upper bound of the loop variable
is determined by the array's final upper bound. */
diff --git a/gcc/testsuite/gfortran.dg/asan/implied_do_alloc_comp_leak_1.f90 b/gcc/testsuite/gfortran.dg/asan/implied_do_alloc_comp_leak_1.f90
new file mode 100644
index 00000000000..8d87617be00
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/asan/implied_do_alloc_comp_leak_1.f90
@@ -0,0 +1,45 @@
+!{ dg-do run }
+
+! PR fortran/XYZZY
+
+! An array constructor with an implied-do whose element is a function result
+! of derived type with allocatable components moves the result into the
+! constructor temporary, which then owns those components. The per-element
+! finalization only freed the last slot written by the loop, leaking the
+! allocatable components of every other element. Checked under -fsanitize=address.
+
+module m
+ implicit none
+ type :: t
+ real, allocatable :: v(:)
+ end type
+contains
+ pure function make (x) result (r)
+ real, intent(in) :: x(:)
+ type(t) :: r
+ r%v = x
+ end function
+end module
+
+program implied_do_alloc_comp_leak_1
+ use m
+ implicit none
+ integer, parameter :: n = 4
+ type(t), allocatable :: a(:), b(:,:)
+ real :: h(2, n)
+ integer :: i, j
+
+ do i = 1, n
+ h(:, i) = [real(i), real(2 * i)]
+ end do
+
+ ! Flat implied-do of function results.
+ a = [(make (h(:, i)), i = 1, n)]
+ if (any (a(n)%v /= h(:, n))) stop 1
+ deallocate (a)
+
+ ! Nested implied-do feeding a transformational intrinsic.
+ b = reshape ([([(make (h(:, i)), j = 1, 1)], i = 1, n)], [1, n])
+ if (any (b(1, 2)%v /= h(:, 2))) stop 2
+ deallocate (b)
+end program
--
2.54.0