See attached patch. For a parenthesized array of a type with
allocatable components, gfc_conv_expr_descriptor builds the temporary
by element assignment, so it shares the components of the variable.
After the call, gfc_conv_procedure_call deep copies that temporary into
new storage only so that it can free the copied components, and the
new storage is never freed. For a CLASS array the elements are copied
with the vtable's _copy, which is deep, and nothing frees their
components. The patch drops the copy and the free for a TYPE
temporary, which leaves the components with the variable, and calls
the vtable's finalization wrapper on a CLASS temporary, which frees the
components of each element.
Following the discussion in the PR, the copy of a parenthesized scalar
CLASS actual argument is now PR128478, and the test makes its calls
from a subroutine, so the temporaries are lost when it returns, not at
program end.
Start with gfc_conv_procedure_call in trans-expr.cc: the new condition
keeps a parenthesized TYPE variable out of the copy-and-free block, and
parenthesized_variable only names the test that was already there.
Then the gfc_conv_expr_descriptor hunk in trans-array.cc, which
finalizes the CLASS temporary. The rest is the new test.
Regression tested on x86_64.
OK for mainline?
Regards,
Jerry
---
fortran: [PR128357] Memory leaks with parenthesized alloc-comp variables
A parenthesized array of a type with allocatable components leaked on
each use as an actual argument or ASSOCIATE selector. A TYPE temporary
shares the components of the variable, yet after the call it was deep
copied into new storage so that its components could be freed, and that
storage was never freed. A CLASS temporary is a deep copy made through
the vtable, and the components of its elements were never freed. Pass
a TYPE temporary without copying or freeing its components, and
finalize a CLASS temporary through the vtable after its use.
Assisted-by: Claude Opus 5.5
PR fortran/128357
gcc/fortran/ChangeLog:
* trans-array.cc (gfc_conv_expr_descriptor): Finalize the temporary
for a CLASS operator expression through the vtable.
* trans-expr.cc (parenthesized_variable): New function.
(gfc_conv_procedure_call): Use it. Do not copy or free the
allocatable components of the temporary for a parenthesized TYPE
variable.
gcc/testsuite/ChangeLog:
* gfortran.dg/asan/paren_alloc_comp_leak_1.f90: New test.
>From df0b828dd2c9aa880e78c8d3cd67d5a019b05bd4 Mon Sep 17 00:00:00 2001
From: Jerry DeLisle <[email protected]>
Date: Mon, 19 Oct 2026 15:36:38 -0700
Subject: [PATCH] fortran: [PR128357] Memory leaks with parenthesized
alloc-comp variables
A parenthesized array of a type with allocatable components leaked on
each use as an actual argument or ASSOCIATE selector. A TYPE temporary
shares the components of the variable, yet after the call it was deep
copied into new storage so that its components could be freed, and that
storage was never freed. A CLASS temporary is a deep copy made through
the vtable, and the components of its elements were never freed. Pass
a TYPE temporary without copying or freeing its components, and
finalize a CLASS temporary through the vtable after its use.
Assisted-by: Claude Opus 5.5
PR fortran/128357
gcc/fortran/ChangeLog:
* trans-array.cc (gfc_conv_expr_descriptor): Finalize the temporary
for a CLASS operator expression through the vtable.
* trans-expr.cc (parenthesized_variable): New function.
(gfc_conv_procedure_call): Use it. Do not copy or free the
allocatable components of the temporary for a parenthesized TYPE
variable.
gcc/testsuite/ChangeLog:
* gfortran.dg/asan/paren_alloc_comp_leak_1.f90: New test.
---
gcc/fortran/trans-array.cc | 30 ++++++++++
gcc/fortran/trans-expr.cc | 24 ++++++--
.../asan/paren_alloc_comp_leak_1.f90 | 55 +++++++++++++++++++
3 files changed, 104 insertions(+), 5 deletions(-)
create mode 100644 gcc/testsuite/gfortran.dg/asan/paren_alloc_comp_leak_1.f90
diff --git a/gcc/fortran/trans-array.cc b/gcc/fortran/trans-array.cc
index 4737dc6f255..2a95784d860 100644
--- a/gcc/fortran/trans-array.cc
+++ b/gcc/fortran/trans-array.cc
@@ -8525,6 +8525,36 @@ gfc_conv_expr_descriptor (gfc_se *se, gfc_expr *expr)
gfc_trans_scalarizing_loops (&loop, &block);
desc = loop.temp_ss->info->data.array.descriptor;
+
+ /* The _copy of a CLASS element is deep, so finalize the temporary
+ through the vtable to free the components of the copies. */
+ tree ctmp = expr->ts.type == BT_CLASS && expr->expr_type == EXPR_OP
+ ? gfc_get_class_from_expr (desc) : NULL_TREE;
+ if (ctmp)
+ {
+ tree final_fn, has_final, elsize;
+
+ final_fn = gfc_evaluate_now (gfc_class_vtab_final_get (ctmp),
+ &se->post);
+ has_final = fold_build2_loc (input_location, NE_EXPR,
+ logical_type_node, final_fn,
+ fold_convert (TREE_TYPE (final_fn),
+ null_pointer_node));
+ elsize = gfc_resize_class_size_with_len (&se->post, ctmp,
+ gfc_class_vtab_size_get (ctmp));
+ /* The finalization wrapper takes the rank from the descriptor. */
+ gfc_conv_descriptor_rank_set (&se->post, desc, loop.dimen);
+ tmp = build_call_expr_loc (input_location,
+ build_fold_indirect_ref_loc (input_location,
+ final_fn),
+ 3, gfc_build_addr_expr (NULL_TREE, desc),
+ fold_convert (gfc_charlen_type_node,
+ elsize),
+ boolean_false_node);
+ tmp = build3_v (COND_EXPR, has_final, tmp,
+ build_empty_stmt (input_location));
+ gfc_add_expr_to_block (&se->post, tmp);
+ }
}
else if (expr->expr_type == EXPR_FUNCTION && !transposed_dims (ss))
{
diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
index fd454f272c9..729f3381fe4 100644
--- a/gcc/fortran/trans-expr.cc
+++ b/gcc/fortran/trans-expr.cc
@@ -4167,6 +4167,20 @@ gfc_conv_concat_op (gfc_se * se, gfc_expr * expr)
se->string_length = len;
}
+
+/* Return the variable that E puts in parentheses, or NULL. */
+
+static gfc_expr *
+parenthesized_variable (gfc_expr *e)
+{
+ if (e->expr_type == EXPR_OP
+ && e->value.op.op == INTRINSIC_PARENTHESES
+ && e->value.op.op1->expr_type == EXPR_VARIABLE)
+ return e->value.op.op1;
+ return NULL;
+}
+
+
/* Translates an op expression. Common (binary) cases are handled by this
function, others are passed on. Recursion is used in either case.
We use the fact that (op1.ts == op2.ts) (except for the power
@@ -8912,11 +8926,13 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
procedures, and array arguments with descriptor to non-elemental
procedures. As bounds information for descriptorless arrays is no
longer available here, they are dealt with in trans-array.cc
- (gfc_conv_array_parameter). */
+ (gfc_conv_array_parameter). A derived-type temporary for a
+ parenthesized variable shares the components of the variable. */
if (e && (e->ts.type == BT_DERIVED || e->ts.type == BT_CLASS)
&& e->ts.u.derived->attr.alloc_comp
&& (e->rank == 0 || elemental_proc || !nodesc_arg)
- && !expr_may_alias_variables (e, elemental_proc))
+ && !expr_may_alias_variables (e, elemental_proc)
+ && !(e->ts.type == BT_DERIVED && parenthesized_variable (e)))
{
int parm_rank;
/* It is known the e returns a structure type with at least one
@@ -8964,9 +8980,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
}
}
- if (e->expr_type == EXPR_OP
- && e->value.op.op == INTRINSIC_PARENTHESES
- && e->value.op.op1->expr_type == EXPR_VARIABLE)
+ if (parenthesized_variable (e))
{
tree local_tmp;
local_tmp = gfc_evaluate_now (tmp, &se->pre);
diff --git a/gcc/testsuite/gfortran.dg/asan/paren_alloc_comp_leak_1.f90 b/gcc/testsuite/gfortran.dg/asan/paren_alloc_comp_leak_1.f90
new file mode 100644
index 00000000000..001a559113d
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/asan/paren_alloc_comp_leak_1.f90
@@ -0,0 +1,55 @@
+! { dg-do run }
+!
+! PR 128357
+! A parenthesized variable of a type with allocatable components, passed as
+! an actual argument or used as an ASSOCIATE selector, leaked the array
+! temporary or the components of its copies. Checked under
+! -fsanitize=address.
+
+module m
+ implicit none
+ type :: t
+ integer :: c = 7
+ integer, allocatable :: v(:)
+ end type
+contains
+ subroutine s (x)
+ type(t), intent(in) :: x(:)
+ if (x(2)%v(2) /= 2) stop 1
+ end subroutine
+ subroutine s1 (x)
+ type(t), intent(in) :: x
+ if (x%v(2) /= 2) stop 2
+ end subroutine
+ subroutine sc (x)
+ class(t), intent(in) :: x(:)
+ if (x(2)%v(2) /= 2) stop 3
+ end subroutine
+ ! The temporaries live only for the call, so they leak on return.
+ subroutine work ()
+ type(t), target :: ta(2)
+ class(t), pointer :: qa(:)
+ class(t), allocatable :: ca(:)
+
+ ta(1) = t (1, [1])
+ ta(2) = t (2, [2, 2])
+ qa => ta
+ allocate (ca, source = ta)
+
+ call s ((ta))
+ call s1 ((ta(2)))
+ call sc ((qa))
+ call sc ((ca))
+ if (any (ta(2)%v /= 2) .or. any (ca(2)%v /= 2)) stop 4
+
+ associate (b => (qa))
+ if (any (b%c /= [1, 2])) stop 5
+ end associate
+ end subroutine
+end module
+
+program p
+ use m
+ implicit none
+ call work ()
+end program
--
2.56.0