Hi all,

As stated in the PR, I was able to verify the valgrind error and after applying the patch, confirmed the errors go away.

Regression tested on X86_64.

I plan to commit this in the next day or so unless additional comments.

Regards,

Jerry

---

fortran: Clean up charlens after rejected parameter arrays
 [PR79524]

When a parameter array declaration such as
character(*), parameter :: z(2) = [character(n) :: 'x', 'y']
is rejected, declaration-local charlen nodes from that statement can remain on
cl_list and later be resolved again.  The charlen's length expression still
references the symbol 'n' whose symtree was already freed by gfc_undo_symbols,
causing a heap-use-after-free in resolve_charlen.

Clean up those statement-local charlens at the rejection point in decl.cc,
after clearing the surviving owners in that path.

        PR fortran/79524

gcc/fortran/ChangeLog:

        PR fortran/79524
        * decl.cc (discard_pending_charlens): New helper.
        (add_init_expr_to_sym): Drop statement-local charlens when
        rejecting variable-length parameter arrays.
        (variable_decl, do_parm, enumerator_decl): Save the current
        namespace charlen list before parsing declarations with
        initializers.
        (match_procedure_decl): Adjust call to add_init_expr_to_sym.

gcc/testsuite/ChangeLog:

        PR fortran/79524
        * gfortran.dg/pr79524.f90: New test.

Signed-off-by: Christopher Albert <[email protected]>
From 3f500986c7fa6e8a437689a532ec8b5da65a530b Mon Sep 17 00:00:00 2001
From: Christopher Albert <[email protected]>
Date: Fri, 3 Apr 2026 12:45:57 +0200
Subject: [PATCH] fortran: Clean up charlens after rejected parameter arrays
 [PR79524]

When a parameter array declaration such as
character(*), parameter :: z(2) = [character(n) :: 'x', 'y']
is rejected, declaration-local charlen nodes from that statement can remain on
cl_list and later be resolved again.  The charlen's length expression still
references the symbol 'n' whose symtree was already freed by gfc_undo_symbols,
causing a heap-use-after-free in resolve_charlen.

Clean up those statement-local charlens at the rejection point in decl.cc,
after clearing the surviving owners in that path.

	PR fortran/79524

gcc/fortran/ChangeLog:

	PR fortran/79524
	* decl.cc (discard_pending_charlens): New helper.
	(add_init_expr_to_sym): Drop statement-local charlens when
	rejecting variable-length parameter arrays.
	(variable_decl, do_parm, enumerator_decl): Save the current
	namespace charlen list before parsing declarations with
	initializers.
	(match_procedure_decl): Adjust call to add_init_expr_to_sym.

gcc/testsuite/ChangeLog:

	PR fortran/79524
	* gfortran.dg/pr79524.f90: New test.

Signed-off-by: Christopher Albert <[email protected]>
---
 gcc/fortran/decl.cc                   | 54 ++++++++++++++++++++++++---
 gcc/testsuite/gfortran.dg/pr79524.f90 |  9 +++++
 2 files changed, 58 insertions(+), 5 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/pr79524.f90

diff --git a/gcc/fortran/decl.cc b/gcc/fortran/decl.cc
index f585800d9c9..6e48909c43a 100644
--- a/gcc/fortran/decl.cc
+++ b/gcc/fortran/decl.cc
@@ -131,6 +131,27 @@ discard_pending_charlen (gfc_charlen *cl)
   free (cl);
 }
 
+/* Drop the charlen nodes created while matching a declaration that is about
+   to be rejected.  Callers must clear any surviving owners before using this
+   helper, so only the statement-local nodes remain on the namespace list.  */
+
+static void
+discard_pending_charlens (gfc_charlen *saved_cl)
+{
+  if (!gfc_current_ns)
+    return;
+
+  while (gfc_current_ns->cl_list != saved_cl)
+    {
+      gfc_charlen *cl = gfc_current_ns->cl_list;
+
+      gcc_assert (cl);
+      gfc_current_ns->cl_list = cl->next;
+      gfc_free_expr (cl->length);
+      free (cl);
+    }
+}
+
 /********************* DATA statement subroutines *********************/
 
 static bool in_match_data = false;
@@ -2107,7 +2128,8 @@ fix_initializer_charlen (gfc_typespec *ts, gfc_expr *init)
    expression to a symbol.  */
 
 static bool
-add_init_expr_to_sym (const char *name, gfc_expr **initp, locus *var_locus)
+add_init_expr_to_sym (const char *name, gfc_expr **initp, locus *var_locus,
+		      gfc_charlen *saved_cl_list)
 {
   symbol_attribute attr;
   gfc_symbol *sym;
@@ -2195,6 +2217,16 @@ add_init_expr_to_sym (const char *name, gfc_expr **initp, locus *var_locus)
 					 "at %L "
 					 "with variable length elements",
 					 &sym->declared_at);
+
+			      /* This rejection path can leave several
+				 declaration-local charlens on cl_list,
+				 including the replacement symbol charlen and
+				 the array-constructor typespec charlen.
+				 Clear the surviving owners first, then drop
+				 only the nodes created by this declaration.  */
+			      sym->ts.u.cl = NULL;
+			      init->ts.u.cl = NULL;
+			      discard_pending_charlens (saved_cl_list);
 			      return false;
 			    }
 			  clen = mpz_get_si (length->value.integer);
@@ -2725,6 +2757,7 @@ variable_decl (int elem)
   gfc_array_spec *as;
   gfc_array_spec *cp_as; /* Extra copy for Cray Pointees.  */
   gfc_charlen *cl;
+  gfc_charlen *saved_cl_list;
   bool cl_deferred;
   locus var_locus;
   match m;
@@ -2735,6 +2768,7 @@ variable_decl (int elem)
   initializer = NULL;
   as = NULL;
   cp_as = NULL;
+  saved_cl_list = gfc_current_ns->cl_list;
 
   /* When we get here, we've just matched a list of attributes and
      maybe a type and a double colon.  The next thing we expect to see
@@ -3284,7 +3318,8 @@ variable_decl (int elem)
      NULL here, because we sometimes also need to check if a
      declaration *must* have an initialization expression.  */
   if (!gfc_comp_struct (gfc_current_state ()))
-    t = add_init_expr_to_sym (name, &initializer, &var_locus);
+    t = add_init_expr_to_sym (name, &initializer, &var_locus,
+			      saved_cl_list);
   else
     {
       if (current_ts.type == BT_DERIVED
@@ -7882,7 +7917,9 @@ match_procedure_decl (void)
 	  if (m != MATCH_YES)
 	    goto cleanup;
 
-	  if (!add_init_expr_to_sym (sym->name, &initializer, &gfc_current_locus))
+	  if (!add_init_expr_to_sym (sym->name, &initializer,
+				     &gfc_current_locus,
+				     gfc_current_ns->cl_list))
 	    goto cleanup;
 
 	}
@@ -10167,9 +10204,12 @@ do_parm (void)
 {
   gfc_symbol *sym;
   gfc_expr *init;
+  gfc_charlen *saved_cl_list;
   match m;
   bool t;
 
+  saved_cl_list = gfc_current_ns->cl_list;
+
   m = gfc_match_symbol (&sym, 0);
   if (m == MATCH_NO)
     gfc_error ("Expected variable name at %C in PARAMETER statement");
@@ -10210,7 +10250,8 @@ do_parm (void)
       goto cleanup;
     }
 
-  t = add_init_expr_to_sym (sym->name, &init, &gfc_current_locus);
+  t = add_init_expr_to_sym (sym->name, &init, &gfc_current_locus,
+			    saved_cl_list);
   return (t) ? MATCH_YES : MATCH_ERROR;
 
 cleanup:
@@ -11630,6 +11671,7 @@ enumerator_decl (void)
   char name[GFC_MAX_SYMBOL_LEN + 1];
   gfc_expr *initializer;
   gfc_array_spec *as = NULL;
+  gfc_charlen *saved_cl_list;
   gfc_symbol *sym;
   locus var_locus;
   match m;
@@ -11637,6 +11679,7 @@ enumerator_decl (void)
   locus old_locus;
 
   initializer = NULL;
+  saved_cl_list = gfc_current_ns->cl_list;
   old_locus = gfc_current_locus;
 
   /* When we get here, we've just matched a list of attributes and
@@ -11693,7 +11736,8 @@ enumerator_decl (void)
      to be parsed.  add_init_expr_to_sym() zeros initializer, so we
      use last_initializer below.  */
   last_initializer = initializer;
-  t = add_init_expr_to_sym (name, &initializer, &var_locus);
+  t = add_init_expr_to_sym (name, &initializer, &var_locus,
+			    saved_cl_list);
 
   /* Maintain enumerator history.  */
   gfc_find_symbol (name, NULL, 0, &sym);
diff --git a/gcc/testsuite/gfortran.dg/pr79524.f90 b/gcc/testsuite/gfortran.dg/pr79524.f90
new file mode 100644
index 00000000000..0b2bfe092ad
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/pr79524.f90
@@ -0,0 +1,9 @@
+! { dg-do compile }
+! { dg-set-target-env-var MALLOC_PERTURB_ "165" }
+! PR fortran/79524
+! Reject parameter arrays with variable-length CHARACTER elements without
+! leaving the temporary charlen from the failed declaration on cl_list.
+program p
+  character(*), parameter :: z(2) = [character(n) :: 'x', 'y'] ! { dg-error "Cannot initialize parameter array" }
+  character(*), parameter :: w(2) = [character(n+1) :: 'a', 'b'] ! { dg-error "Cannot initialize parameter array" }
+end
-- 
2.53.0

Reply via email to