See attached 5 of 5 patch

Regression tested on x86_64

Regards,

Jerry

---

fortran: [PR49802]-5 Absent optional actual argument
 passed to a VALUE dummy

The private copy made for a VALUE dummy dereferenced the actual
argument unconditionally, so passing an absent optional actual argument
to an optional VALUE dummy segfaulted.  Both the array copy and the
copy made for a CHARACTER dummy of assumed or non-constant length have
to be suppressed when the actual argument is absent.

gfc_conv_subref_array_arg already has the machinery: given the symbol
of the actual argument it suppresses its copy and passes a null pointer
instead, so the array case just has to hand it that symbol rather than
the symbol of the procedure being called.  This is what makes the two
pass_optional fixes of the first patch of this series reachable.
conv_dummy_value has no such machinery and guards its CHARACTER copy
directly, passing a null pointer and a zero length when the actual
argument is absent.

        PR fortran/49802

gcc/fortran/ChangeLog:

        * trans-expr.cc (conv_dummy_value): Suppress the CHARACTER copy
        and pass a null pointer and a zero length when the actual argument
        is an absent optional one.
        (gfc_conv_procedure_call): Give gfc_conv_subref_array_arg the
        symbol of the actual argument, so that an absent optional one
        suppresses the copy.

gcc/testsuite/ChangeLog:

        * gfortran.dg/value_18.f90: New test.
---
From 1f42c7657c9e2077c6fa5cfbbbf521ef4a07b96b Mon Sep 17 00:00:00 2001
From: Jerry DeLisle <[email protected]>
Date: Sun, 23 Aug 2026 18:30:26 -0700
Subject: [PATCH 5/5] fortran: [PR49802]-5 Absent optional actual argument
 passed to a VALUE dummy

The private copy made for a VALUE dummy dereferenced the actual
argument unconditionally, so passing an absent optional actual argument
to an optional VALUE dummy segfaulted.  Both the array copy and the
copy made for a CHARACTER dummy of assumed or non-constant length have
to be suppressed when the actual argument is absent.

gfc_conv_subref_array_arg already has the machinery: given the symbol
of the actual argument it suppresses its copy and passes a null pointer
instead, so the array case just has to hand it that symbol rather than
the symbol of the procedure being called.  This is what makes the two
pass_optional fixes of the first patch of this series reachable.
conv_dummy_value has no such machinery and guards its CHARACTER copy
directly, passing a null pointer and a zero length when the actual
argument is absent.

	PR fortran/49802

gcc/fortran/ChangeLog:

	* trans-expr.cc (conv_dummy_value): Suppress the CHARACTER copy
	and pass a null pointer and a zero length when the actual argument
	is an absent optional one.
	(gfc_conv_procedure_call): Give gfc_conv_subref_array_arg the
	symbol of the actual argument, so that an absent optional one
	suppresses the copy.

gcc/testsuite/ChangeLog:

	* gfortran.dg/value_18.f90: New test.
---
 gcc/fortran/trans-expr.cc              |  33 ++++-
 gcc/testsuite/gfortran.dg/value_18.f90 | 184 +++++++++++++++++++++++++
 2 files changed, 215 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/value_18.f90

diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc
index ff09a0f0f1f..19982b1f733 100644
--- a/gcc/fortran/trans-expr.cc
+++ b/gcc/fortran/trans-expr.cc
@@ -6979,8 +6979,23 @@ conv_dummy_value (gfc_se * parmse, gfc_expr * e, gfc_symbol * fsym,
       && (!fsym->ts.u.cl || !fsym->ts.u.cl->length
 	  || fsym->ts.u.cl->length->expr_type != EXPR_CONSTANT))
     {
+      /* An optional actual argument that is absent has nothing to copy
+	 from; pass a null pointer and a length of zero instead.  */
+      tree present = NULL_TREE;
+      if (fsym->attr.optional && e->expr_type == EXPR_VARIABLE
+	  && e->symtree->n.sym->attr.optional)
+	present = gfc_conv_expr_present (e->symtree->n.sym);
+
       gfc_conv_string_parameter (parmse);
       tree len = fold_convert (gfc_charlen_type_node, parmse->string_length);
+      if (present)
+	{
+	  len = fold_build3_loc (input_location, COND_EXPR,
+				 gfc_charlen_type_node, present, len,
+				 build_zero_cst (gfc_charlen_type_node));
+	  len = gfc_evaluate_now (len, &parmse->pre);
+	  parmse->string_length = len;
+	}
       tree chartype = gfc_get_character_type_len (fsym->ts.kind, len);
       tree val_copy = gfc_create_var (chartype, "val_copy");
       tmp = fold_build1_loc (input_location, DECL_EXPR, chartype, val_copy);
@@ -6996,10 +7011,19 @@ conv_dummy_value (gfc_se * parmse, gfc_expr * e, gfc_symbol * fsym,
 	fold_convert (pvoid_type_node,
 		      gfc_build_addr_expr (NULL_TREE, val_copy)),
 	fold_convert (pvoid_type_node, parmse->expr), bytes);
+      if (present)
+	tmp = build3_v (COND_EXPR, present, tmp,
+			build_empty_stmt (input_location));
       gfc_add_expr_to_block (&parmse->pre, tmp);
       parmse->expr = fold_convert (
 	build_pointer_type (gfc_get_char_type (fsym->ts.kind)),
 	gfc_build_addr_expr (NULL_TREE, val_copy));
+      if (present)
+	parmse->expr = fold_build3_loc (input_location, COND_EXPR,
+					TREE_TYPE (parmse->expr), present,
+					parmse->expr,
+					fold_convert (TREE_TYPE (parmse->expr),
+						      null_pointer_node));
     }
 
   /* Truncate a too long constant character actual argument.  */
@@ -8321,9 +8345,14 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym,
 		       && e->rank != -1)
 		/* VALUE array dummy: pass a private copy of the actual
 		   argument.  Allocatable components are copied deeply, so
-		   that the callee cannot reach the actual argument's data.  */
+		   that the callee cannot reach the actual argument's data.
+		   The symbol passed is that of the actual argument, so that
+		   the copy is suppressed and a null pointer passed when an
+		   optional actual argument is absent.  */
 		gfc_conv_subref_array_arg (&parmse, e, nodesc_arg, INTENT_IN,
-					   false, fsym, sym->name, NULL,
+					   false, fsym, sym->name,
+					   e->expr_type == EXPR_VARIABLE
+					   ? e->symtree->n.sym : NULL,
 					   false, true);
 
 	      else if (e->expr_type == EXPR_VARIABLE
diff --git a/gcc/testsuite/gfortran.dg/value_18.f90 b/gcc/testsuite/gfortran.dg/value_18.f90
new file mode 100644
index 00000000000..f16c23f013a
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/value_18.f90
@@ -0,0 +1,184 @@
+! { dg-do run }
+! PR 49802
+! An absent OPTIONAL actual argument passed to an OPTIONAL dummy with the
+! VALUE attribute used to segfault: the private copy made for the dummy
+! dereferenced the actual argument unconditionally.  Both the array copy
+! and the copy made for a character dummy of assumed or non-constant
+! length must be suppressed when the actual argument is absent.
+
+module m
+  implicit none
+  type :: dt
+    integer, allocatable :: d(:)
+  end type
+contains
+
+  subroutine take_as (x, present_x)
+    integer, value, optional :: x(:)
+    logical, intent(in) :: present_x
+    if (present (x) .neqv. present_x) stop 1
+    if (present (x)) then
+      if (size (x) /= 3) stop 2
+      if (any (x /= [1, 2, 3])) stop 3
+      x = -1
+      if (any (x /= -1)) stop 4
+    end if
+  end subroutine take_as
+
+  subroutine take_es (n, x, present_x)
+    integer, intent(in) :: n
+    integer, value, optional :: x(n)
+    logical, intent(in) :: present_x
+    if (present (x) .neqv. present_x) stop 5
+    if (present (x)) then
+      if (size (x) /= n) stop 6
+      if (x(1) /= 1) stop 7
+      x = 0
+      if (any (x /= 0)) stop 8
+    end if
+  end subroutine take_es
+
+  subroutine take_cs (s, present_s)
+    character(len=*), value, optional :: s
+    logical, intent(in) :: present_s
+    if (present (s) .neqv. present_s) stop 9
+    if (present (s)) then
+      if (s /= 'payload') stop 10
+      s = repeat ('Z', len (s))
+      if (s /= repeat ('Z', len (s))) stop 11
+    end if
+  end subroutine take_cs
+
+  subroutine take_ca (s, present_s)
+    character(len=*), value, optional :: s(:)
+    logical, intent(in) :: present_s
+    if (present (s) .neqv. present_s) stop 12
+    if (present (s)) then
+      if (size (s) /= 2) stop 13
+      if (any (s /= ['ab', 'cd'])) stop 14
+      s = 'ZZ'
+      if (any (s /= 'ZZ')) stop 15
+    end if
+  end subroutine take_ca
+
+  ! A derived type with an allocatable component: the deep copy must be
+  ! suppressed too, not just the copy of the descriptor.
+
+  subroutine take_dt (x, present_x)
+    type(dt), value, optional :: x(:)
+    logical, intent(in) :: present_x
+    if (present (x) .neqv. present_x) stop 19
+    if (present (x)) then
+      if (size (x) /= 2) stop 20
+      if (any (x(1)%d /= [1, 1])) stop 21
+      x(1)%d = [-1, -1]
+      if (any (x(1)%d /= [-1, -1])) stop 22
+    end if
+  end subroutine take_dt
+
+  ! Relay an optional dummy on to the optional VALUE dummy.  This is what
+  ! puts a descriptor of an absent argument into the argument list.
+
+  subroutine relay_as (x, present_x)
+    integer, optional :: x(:)
+    logical, intent(in) :: present_x
+    call take_as (x, present_x)
+  end subroutine relay_as
+
+  subroutine relay_es (n, x, present_x)
+    integer, intent(in) :: n
+    integer, optional :: x(n)
+    logical, intent(in) :: present_x
+    call take_es (n, x, present_x)
+  end subroutine relay_es
+
+  ! A non-constant length is needed here: with len=* the length of an
+  ! absent actual argument is zero and the copy reads nothing.
+  subroutine relay_cs (n, s, present_s)
+    integer, intent(in) :: n
+    character(len=n), optional :: s
+    logical, intent(in) :: present_s
+    call take_cs (s, present_s)
+  end subroutine relay_cs
+
+  subroutine relay_ca (s, present_s)
+    character(len=*), optional :: s(:)
+    logical, intent(in) :: present_s
+    call take_ca (s, present_s)
+  end subroutine relay_ca
+
+  subroutine relay_dt (x, present_x)
+    type(dt), optional :: x(:)
+    logical, intent(in) :: present_x
+    call take_dt (x, present_x)
+  end subroutine relay_dt
+
+  ! An optional VALUE dummy relayed on to another optional VALUE dummy.
+
+  subroutine relay_asv (x, present_x)
+    integer, value, optional :: x(:)
+    logical, intent(in) :: present_x
+    call take_as (x, present_x)
+  end subroutine relay_asv
+
+  subroutine relay_csv (n, s, present_s)
+    integer, intent(in) :: n
+    character(len=n), value, optional :: s
+    logical, intent(in) :: present_s
+    call take_cs (s, present_s)
+  end subroutine relay_csv
+
+end module m
+
+program test
+  use m
+  implicit none
+  integer :: v(3), i
+  character(len=7) :: s
+  character(len=2) :: a(2)
+  type(dt) :: w(2)
+
+  v = [1, 2, 3]
+  s = 'payload'
+  a = ['ab', 'cd']
+  do i = 1, 2
+    allocate (w(i)%d(2), source=[i, i])
+  end do
+
+  ! Directly, with and without the actual argument.
+  call take_as (v, .true.)
+  call take_as (present_x = .false.)
+  call take_es (3, v, .true.)
+  call take_es (3, present_x = .false.)
+  call take_cs (s, .true.)
+  call take_cs (present_s = .false.)
+  call take_ca (a, .true.)
+  call take_ca (present_s = .false.)
+  call take_dt (w, .true.)
+  call take_dt (present_x = .false.)
+
+  ! Relayed through an optional dummy.
+  call relay_as (v, .true.)
+  call relay_as (present_x = .false.)
+  call relay_es (3, v, .true.)
+  call relay_es (3, present_x = .false.)
+  call relay_cs (7, s, .true.)
+  call relay_cs (7, present_s = .false.)
+  call relay_ca (a, .true.)
+  call relay_ca (present_s = .false.)
+  call relay_dt (w, .true.)
+  call relay_dt (present_x = .false.)
+
+  ! Relayed through an optional VALUE dummy.
+  call relay_asv (v, .true.)
+  call relay_asv (present_x = .false.)
+  call relay_csv (7, s, .true.)
+  call relay_csv (7, present_s = .false.)
+
+  ! None of the copies may write back to the actual arguments.
+  if (any (v /= [1, 2, 3])) stop 16
+  if (s /= 'payload') stop 17
+  if (any (a /= ['ab', 'cd'])) stop 18
+  if (any (w(1)%d /= [1, 1])) stop 23
+  if (any (w(2)%d /= [2, 2])) stop 24
+end program test
-- 
2.55.0

Reply via email to