Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH] Add infcall support for C++ constructor-style expressions
@ 2026-03-26 19:17 Keith Seitz
  2026-03-27  5:50 ` Eli Zaretskii
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Keith Seitz @ 2026-03-26 19:17 UTC (permalink / raw)
  To: gdb-patches

This patch adds an initial try at teaching the expression parser/evaluator
to construct temporary objects requiring construction during an inferior
function call.

To accomplish this, I've chosen the route of modifying the parser to
teach it that `Type(args)' is a function call when `Type' names a
class/struct/union and is immediately followed by '(', that is, via look-
ahead). A new parser token and grammar rule have been added to deal
with this new production.

The real work is dispatched to `type_operation::evaluate_funcall',
allocating memory for the temporary and finding the most suitable constructor
with `find_overload_match'.  It then runs the inferior call, returning
the newly constructed object.

I've included many tests covering as many corner cases as I could invent,
and these tests are clang clean.  They also introduce no regressions on
x86-64 Fedora 43 with GCC 15.2.1 and RHEL 9.4 with GCC 11.5.0.

Example:
Consider a C++ frame where 'struct S { int x; S(int); ... }' is in scope

Before:
(gdb) print S(42)
❌️ A syntax error in expression, near `10)'.

After:
(gdb) print S(42)
$1 = {x = 42}

Note that no attempt has been made to deal with templates.  Hopefully
a follow-on patch can address that.
---
 gdb/NEWS                               |   5 +
 gdb/c-exp.y                            |  86 +++++++++--
 gdb/eval.c                             |  55 ++++++++
 gdb/expop.h                            |   5 +
 gdb/testsuite/gdb.cp/infcall-ctors.cc  | 128 +++++++++++++++++
 gdb/testsuite/gdb.cp/infcall-ctors.exp | 188 +++++++++++++++++++++++++
 6 files changed, 457 insertions(+), 10 deletions(-)
 create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.cc
 create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index 03f46df5400..c04727d342d 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -66,6 +66,11 @@
   automatically set to UTF-8.  (Users can use the Windows 'chcp'
   command to change the output codepage of the console.)
 
+* In C++ GDB now accepts constructor-style expressions "TYPE (ARGS)"
+  when TYPE names a class, struct, or union in the current expression
+  context.  This allows objects to be constructed directly during
+  expression evalution.
+
 * New targets
 
 GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
diff --git a/gdb/c-exp.y b/gdb/c-exp.y
index a4a910df712..e17ee2d3c12 100644
--- a/gdb/c-exp.y
+++ b/gdb/c-exp.y
@@ -199,7 +199,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value);
 #endif
 %}
 
-%type <voidval> exp exp1 type_exp start variable qualified_name lcurly function_method
+%type <voidval> exp exp1 type_exp start variable qualified_name lcurly function_method typename_for_ctor
 %type <lval> rcurly
 %type <tval> type typebase scalar_type tag_name_or_complete
 %type <tvec> nonempty_typelist func_mod parameter_typelist
@@ -230,7 +230,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value);
 %token <ssym> NAME /* BLOCKNAME defined below to give it higher precedence. */
 %token <ssym> UNKNOWN_CPP_NAME
 %token <voidval> COMPLETE
-%token <tsym> TYPENAME
+%token <tsym> TYPENAME TYPENAME_CTOR
 %token <theclass> CLASSNAME	/* ObjC Class name */
 %type <sval> name
 %type <qval> qual_field_name field_name field_name_or_complete
@@ -533,6 +533,26 @@ msgarg	:	name ':' exp
 			{ add_msglist(0, 0);   }
 	;
 
+exp	:	typename_for_ctor '('
+			{ pstate->start_arglist (); }
+		arglist ')'	%prec ARROW
+			{
+			  std::vector<operation_up> args
+			    = pstate->pop_vector (pstate->end_arglist ());
+			  operation_up type_op = pstate->pop ();
+			  pstate->push_new<funcall_operation>
+			    (std::move (type_op), std::move (args));
+			}
+	;
+
+exp	:	typename_for_ctor '(' ')'	%prec ARROW
+			{
+			  operation_up type_op = pstate->pop ();
+			  pstate->push_new<funcall_operation>
+			    (std::move (type_op), std::vector<operation_up> ());
+			}
+	;
+
 exp	:	exp '('
 			/* This is to save the value of arglist_len
 			   being accumulated by an outer function call.  */
@@ -1471,6 +1491,12 @@ scalar_type:
 						       "int"); }
 	;
 
+/* Constructor-style calls.  */
+typename_for_ctor
+	:	TYPENAME_CTOR
+			{ pstate->push_new<type_operation> ($1.type); }
+	;
+
 /* Implements (approximately): (type-qualifier)* type-specifier.
 
    When type-specifier is only ever a single word, like 'float' then these
@@ -3114,6 +3140,34 @@ static int popping;
    built up.  */
 static auto_obstack name_obstack;
 
+/* Return TYPENAME_CTOR only when the next token is '(', so this
+   token is used solely for constructor calls.  Otherwise return TYPENAME.
+   NAME_END is the character just past the name (e.g. yylval.sval.ptr +
+   yylval.sval.length).  */
+
+static int
+typename_token_for (struct parser_state *par_state, struct type *type,
+		    const char *name_end)
+{
+  if (type == nullptr
+      || par_state->language ()->la_language != language_cplus)
+    return TYPENAME;
+  type = check_typedef (type);
+  if (type->code () != TYPE_CODE_STRUCT && type->code () != TYPE_CODE_UNION)
+    return TYPENAME;
+  /* Only return TYPENAME_CTOR when followed by '('.  */
+  if (name_end == nullptr)
+    return TYPENAME;
+  {
+    const char *p = name_end;
+    while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
+      ++p;
+    if (*p != '(')
+      return TYPENAME;
+  }
+  return TYPENAME_CTOR;
+}
+
 /* Classify a NAME token.  The contents of the token are in `yylval'.
    Updates yylval and returns the new token type.  BLOCK is the block
    in which lookups start; this can be NULL to mean the global scope.
@@ -3157,7 +3211,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
 	  if (bsym.symbol != NULL)
 	    {
 	      yylval.tsym.type = bsym.symbol->type ();
-	      return TYPENAME;
+	      return typename_token_for (par_state, yylval.tsym.type,
+					 yylval.sval.ptr + yylval.sval.length);
 	    }
 	}
 
@@ -3184,7 +3239,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
   if (bsym.symbol && bsym.symbol->loc_class () == LOC_TYPEDEF)
     {
       yylval.tsym.type = bsym.symbol->type ();
-      return TYPENAME;
+      return typename_token_for (par_state, yylval.tsym.type,
+				 yylval.sval.ptr + yylval.sval.length);
     }
 
   /* See if it's an ObjC classname.  */
@@ -3270,7 +3326,9 @@ classify_inner_name (struct parser_state *par_state,
       if (base_type != NULL)
 	{
 	  yylval.tsym.type = base_type;
-	  return TYPENAME;
+	  return typename_token_for (par_state, yylval.tsym.type,
+				     yylval.ssym.stoken.ptr
+				     + yylval.ssym.stoken.length);
 	}
 
       return ERROR;
@@ -3290,14 +3348,18 @@ classify_inner_name (struct parser_state *par_state,
 	if (base_type != NULL)
 	  {
 	    yylval.tsym.type = base_type;
-	    return TYPENAME;
+	    return typename_token_for (par_state, yylval.tsym.type,
+				       yylval.ssym.stoken.ptr
+				       + yylval.ssym.stoken.length);
 	  }
       }
       return ERROR;
 
     case LOC_TYPEDEF:
       yylval.tsym.type = yylval.ssym.sym.symbol->type ();
-      return TYPENAME;
+      return typename_token_for (par_state, yylval.tsym.type,
+				 yylval.ssym.stoken.ptr
+				 + yylval.ssym.stoken.length);
 
     default:
       return NAME;
@@ -3332,7 +3394,7 @@ handle_qualified_field_name (qualified_name_token token)
       int kind = classify_inner_name (pstate,
 				      pstate->expression_context_block,
 				      type);
-      if (kind != TYPENAME)
+      if (kind != TYPENAME && kind != TYPENAME_CTOR)
 	error (_("could not find type '%s'"), accum.c_str ());
 
       type = yylval.tsym.type;
@@ -3384,7 +3446,8 @@ yylex (void)
     current.token = classify_name (pstate, pstate->expression_context_block,
 				   is_quoted_name, last_lex_was_structop);
   if (pstate->language ()->la_language != language_cplus
-      || (current.token != TYPENAME && current.token != COLONCOLON
+      || (current.token != TYPENAME && current.token != TYPENAME_CTOR
+	  && current.token != COLONCOLON
 	  && current.token != FILENAME
 	  && (cpstate->assume_classification == TYPE_CODE_UNDEF
 	      || current.token != NAME))
@@ -3430,6 +3493,7 @@ yylex (void)
   else
     {
       gdb_assert (current.token == TYPENAME
+		  || current.token == TYPENAME_CTOR
 		  || cpstate->assume_classification != TYPE_CODE_UNDEF);
       search_block = pstate->expression_context_block;
       obstack_grow (&name_obstack, current.value.sval.ptr,
@@ -3460,7 +3524,8 @@ yylex (void)
 						  context_type);
 	  /* We keep going until we either run out of names, or until
 	     we have a qualified name which is not a type.  */
-	  if (classification != TYPENAME && classification != NAME)
+	  if (classification != TYPENAME && classification != TYPENAME_CTOR
+	      && classification != NAME)
 	    break;
 
 	  /* Accept up to this token.  */
@@ -3591,6 +3656,7 @@ c_print_token (FILE *file, int type, YYSTYPE value)
       break;
 
     case TYPENAME:
+    case TYPENAME_CTOR:
       parser_fprintf (file, "tsym<type=%s, name=%s>",
 		      value.tsym.type->safe_name (),
 		      copy_name (value.tsym.stoken).c_str ());
diff --git a/gdb/eval.c b/gdb/eval.c
index 7beff554ed4..e988b954059 100644
--- a/gdb/eval.c
+++ b/gdb/eval.c
@@ -1869,6 +1869,61 @@ type_operation::evaluate (struct type *expect_type, struct expression *exp,
     error (_("Attempt to use a type name as an expression"));
 }
 
+value *
+type_operation::evaluate_funcall (struct type *expect_type,
+				  struct expression *exp,
+				  enum noside noside,
+				  const std::vector<operation_up> &args)
+{
+  struct type *type = std::get<0> (m_storage);
+  type = check_typedef (type);
+
+  /* Constructor-style call Type(args) is only for C++ aggregate types.  */
+  gdb_assert (exp->language_defn->la_language == language_cplus);
+
+  const char *name = type->name ();
+  if (name == nullptr)
+    error (_("Cannot call constructor of unnamed type"));
+
+  /* Get the constructor name from the type name.  */
+  gdb::unique_xmalloc_ptr<char> ctor_name_ptr = cp_func_name (name);
+  const char *ctor_name = (ctor_name_ptr != nullptr) ? ctor_name_ptr.get () : name;
+
+  if (!overload_resolution)
+    return operation::evaluate_funcall (expect_type, exp, noside, args);
+
+  std::vector<value *> argvec (1 + args.size ());
+  value *this_ptr;
+  if (noside == EVAL_AVOID_SIDE_EFFECTS)
+    this_ptr = value::zero (lookup_pointer_type (type), lval_memory);
+  else
+    {
+      value *alloc_val = value_allocate_space_in_inferior (type->length ());
+      this_ptr = value_from_pointer (lookup_pointer_type (type),
+				     value_as_long (alloc_val));
+    }
+  argvec[0] = this_ptr;
+  for (size_t i = 0; i < args.size (); ++i)
+    argvec[i + 1] = args[i]->evaluate_with_coercion (exp, noside);
+  gdb::array_view<value *> arg_view = argvec;
+
+  value *callee = nullptr;
+  int static_memfuncp;
+  find_overload_match (arg_view, ctor_name, METHOD,
+		       &argvec[0], nullptr, &callee, nullptr,
+		       &static_memfuncp, 0, noside);
+  if (callee == nullptr)
+    error (_("Cannot resolve constructor %s to any overloaded instance"),
+	   name);
+
+  if (noside == EVAL_AVOID_SIDE_EFFECTS)
+    return value::zero (type, not_lval);
+
+  evaluate_subexp_do_call (exp, noside, callee, arg_view,
+			   nullptr, expect_type);
+  return value_ind (this_ptr);
+}
+
 }
 
 /* A helper function for BINOP_ASSIGN_MODIFY.  */
diff --git a/gdb/expop.h b/gdb/expop.h
index c58a8d7ac37..9839e3bffa9 100644
--- a/gdb/expop.h
+++ b/gdb/expop.h
@@ -1597,6 +1597,11 @@ class type_operation
 		   struct expression *exp,
 		   enum noside noside) override;
 
+  value *evaluate_funcall (struct type *expect_type,
+			   struct expression *exp,
+			   enum noside noside,
+			   const std::vector<operation_up> &args) override;
+
   enum exp_opcode opcode () const override
   { return OP_TYPE; }
 
diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.cc b/gdb/testsuite/gdb.cp/infcall-ctors.cc
new file mode 100644
index 00000000000..053542ec5ea
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/infcall-ctors.cc
@@ -0,0 +1,128 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+struct S {
+  int x;
+  explicit S (int n = 0) : x (n) {}
+  S operator+ (int n) const { return S (x + n); }
+};
+
+typedef S S_td;
+using S_u = S;
+
+static int add (const struct S &s1, const struct S &s2) {
+  return s1.x + s2.x;
+}
+
+/* Non-trivially copyable: copy-constructing swaps the two members.  */
+struct swapcopy {
+  int lo;
+  int hi;
+  swapcopy (int l, int h) : lo (l), hi (h) {}
+  swapcopy (const swapcopy &o) : lo (o.hi), hi (o.lo) {}
+};
+
+/* Pass swapcopy by value so the call must copy-construct the argument.  */
+
+static int
+swapcopy_first_byval (swapcopy c)
+{
+  return c.lo;
+}
+
+struct Base {
+  int x;
+  Base () : x (0) {}
+  explicit Base (int n) : x (n) {}
+  Base (const Base &other) : x (other.x) {}
+};
+
+typedef Base Base_td;
+using Base_u = Base;
+
+namespace NS {
+class Derived : public Base {
+public:
+  int y;
+  Derived () : Base (), y (0) {}
+  Derived (int a, int b) : Base (a), y (b) {}
+};
+
+typedef Base NsBaseTd;
+using NsBaseU = Base;
+typedef Derived Derived_td;
+using Derived_u = Derived;
+
+union U {
+  int a;
+  U () : a (0) {}
+  explicit U (int n) : a (n) {}
+};
+
+typedef U Nu_td;
+}
+
+union U {
+  int x;
+  U () : x (0) {}
+  explicit U (int n) : x (n) {}
+};
+
+typedef U U_td;
+
+static int plus_one (U u)
+{
+  return u.x + 1;
+}
+
+int
+main (void)
+{
+  S s0;           /* default: x = 0 */
+  S s1 (42);     /* x = 42 */
+  S s2 (s1 + 2); /* x = 44 */
+  S s3 = s1;
+  NS::Derived d;	     /* Base part x=0, Derived part y=0  */
+  NS::Derived d1 (10, 20);   /* Base part x=10, Derived part y=20  */
+  Base b;		     /* x=0  */
+  Base b1 (5);               /* x=5  */
+  Base b2 (d1);		     /* x=10  */
+  U u0;                      /* default: x = 0  */
+  U u1 (42);                 /* x = 42  */
+  NS::U uv0;                /* default: a = 0  */
+  NS::U uv1 (7);            /* a = 7  */
+  S_td s_td = S_td (11);
+  S_u s_u = S_u (12);
+  Base_td b_td = Base_td (8);
+  Base_u b_u = Base_u (9);
+  NS::NsBaseTd nsb_td = NS::NsBaseTd (13);
+  NS::NsBaseU nsb_u = NS::NsBaseU (14);
+  NS::Derived_td d_td = NS::Derived_td (2, 3);
+  NS::Derived_u d_u = NS::Derived_u (4, 5);
+  U_td u_td = U_td (15);
+  NS::Nu_td nu_u = NS::Nu_td (16);
+  swapcopy swp (30, 40);
+  int result = add (s1, s2);
+  return result + d.x + d.y + b.x + b1.x + b2.x + d.x + d.y \
+	+ d1.x + d1.y + plus_one (u0) + u1.x + uv0.a + uv1.a \
+	+ s_td.x + s_u.x + b_td.x + b_u.x + nsb_td.x + nsb_u.x \
+	+ d_td.x + d_td.y + d_u.x + d_u.y + u_td.x + nu_u.a \
+	+ swapcopy_first_byval (swapcopy (100, 200)) \
+	+ swapcopy_first_byval (swp);  /* stop-here */
+}
diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.exp b/gdb/testsuite/gdb.cp/infcall-ctors.exp
new file mode 100644
index 00000000000..88297018b28
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/infcall-ctors.exp
@@ -0,0 +1,188 @@
+# Copyright (C) 2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the gdb testsuite.
+
+# Test constructor calls and casting via inferior function calls:
+# - Simple struct S with constructor (int, default 0).
+# - Base and Derived; cast derived to base, construct Base from Derived.
+# - Typedef and using aliases: ctor resolution must use the class ctor even
+#   when the expression names a typedef or alias (DWARF may name types
+#   differently from the underlying class tag).
+# - swapcopy: two ints with a user-defined copy ctor that swaps them
+#   (non-trivially copyable); pass-by-value in inferior calls must run it.
+
+require allow_cplus_tests
+
+standard_testfile .cc
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
+    return -1
+}
+
+if {![runto_main]} {
+    return
+}
+
+# Run to stop-here to ensure all locals are initialized.
+gdb_breakpoint [gdb_get_line_number "stop-here"]
+gdb_continue_to_breakpoint "stop-here"
+
+# Simple tests involving "struct S".
+gdb_test "ptype S" [multi_line \
+    {type = struct S \{} \
+    {    int x;} \
+    "" \
+    {    S\(int\);} \
+    {    S operator\+\(int\) const;} \
+    {\}}]
+
+gdb_test "print s0" " = \\{x = 0\\}" "print s0 default ctor"
+gdb_test "print s1" " = \\{x = 42\\}" "print s1 with 42"
+gdb_test "print S(99)" " = \\{x = 99\\}" "construct S(99) via inferior function call"
+
+gdb_test "ptype swapcopy" [multi_line \
+    {type = struct swapcopy \{} \
+    {    int lo;} \
+    {    int hi;} \
+    "" \
+    {    swapcopy\(int, int\);} \
+    {    swapcopy\(const swapcopy ?&\);} \
+    {\}}]
+gdb_test "print swapcopy(1, 2)" { = \{lo = 1, hi = 2\}} \
+    "construct swapcopy via inferior function call"
+gdb_test {print swapcopy($)} {= \{lo = 2, hi = 1\}} \
+    "call copy ctor on object via history"
+gdb_test "print swapcopy_first_byval(swapcopy(10, 20))" "= 20" \
+    "pass-by-value copy ctor swaps members"
+gdb_test "print swapcopy_first_byval(swp)" "= 40" \
+    "swapcopy local passed by value uses copy ctor"
+
+gdb_test "print S_td(33)" " = \\{x = 33\\}" \
+    "construct S via typedef name (not underlying struct tag)"
+gdb_test "print S_u(34)" " = \\{x = 34\\}" "construct S via using alias"
+gdb_test "print Base_td(8)" { = \{x = 8\}} "construct Base via typedef name"
+gdb_test "print Base_u(9)" { = \{x = 9\}} "construct Base via using alias"
+gdb_test "print NS::NsBaseTd(5)" { = \{x = 5\}} \
+    "construct Base via typedef in namespace"
+gdb_test "print NS::NsBaseU(6)" { = \{x = 6\}} \
+    "construct Base via using alias in namespace"
+gdb_test "print NS::Derived_td(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \
+    "construct NS::Derived via typedef name"
+gdb_test "print NS::Derived_u(3, 4)" { = \{<Base> = \{x = 3\}, y = 4\}} \
+    "construct NS::Derived via using alias"
+gdb_test "print U_td(55)" { = \{x = 55\}} \
+    "construct global union U via typedef name"
+gdb_test "print NS::Nu_td(66)" { = \{a = 66\}} \
+    "construct NS::U via typedef name"
+
+# Tests involving "Base" and "Derived".
+set base_re [multi_line \
+    {type = struct Base \{} \
+    {    int x;} \
+    "" \
+    {    Base\(void\);} \
+    {    Base\(int\);} \
+    {    Base\(const Base ?&\);} \
+    {\}}]
+gdb_test "ptype Base" $base_re
+
+set derived_re [multi_line \
+    {type = class NS::Derived : public Base \{} \
+    {  public:} \
+    {    int y;} \
+    "" \
+    {    Derived\(void\);} \
+    {    Derived\(int, int\);} \
+    {\}}]
+gdb_test "ptype NS::Derived" $derived_re
+
+gdb_test "print d" { = \{<Base> = \{x = 0\}, y = 0\}}
+gdb_test "print d1" { = \{<Base> = \{x = 10\}, y = 20\}}
+gdb_test "print b" { = \{x = 0\}}
+gdb_test "print b1" { = \{x = 5\}}
+gdb_test "print b2" { = \{x = 10\}}
+gdb_test "print (Base)(d1)" { = \{x = 10\}} "cast (Base)(d1) slices to Base"
+gdb_test "print Base(d1)" { = \{x = 10\}} "construct Base(d1) from Derived"
+gdb_test "print Base()" { = \{x = 0\}} "construct Base() default"
+gdb_test "print Base(7)" { = \{x = 7\}} "construct Base(7) with argument"
+gdb_test "print NS::Derived()" { = \{<Base> = \{x = 0\}, y = 0\}} \
+    "construct NS::Derived() via inferior function call"
+gdb_test "print NS::Derived().y" " = 0" "construct NS::Derived() and access .y"
+gdb_test "print NS::Derived(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \
+    "construct NS::Derived(1, 2) via inferior function call"
+gdb_test "print NS::Derived(1, 2).y" " = 2" \
+    "construct NS::Derived(1, 2) and access .y"
+gdb_test "print ((Base)d1).x" " = 10" "cast ((Base)d1).x"
+
+# Print the types of these "temporary" objects.
+gdb_test "ptype Base()" $base_re "ptype of Base temporary"
+gdb_test "ptype NS::Derived(15, 25)" $derived_re \
+	"ptype of NS::Derived temporary"
+
+# Tests involving unions.
+gdb_test "ptype U" [multi_line \
+    {type = union U \{} \
+    {    int x;} \
+    "" \
+    {    U\(void\);} \
+    {    U\(int\);} \
+    {\}}] \
+    "ptype U"
+
+gdb_test "ptype NS::U" [multi_line \
+    {type = union NS::U \{} \
+    {    int a;} \
+    "" \
+    {    U\(void\);} \
+    {    U\(int\);} \
+    {\}}] \
+    "ptype NS::U"
+
+gdb_test "print u0" { = \{x = 0\}}
+gdb_test "print u1" { = \{x = 42\}}
+gdb_test "print uv0" { = \{a = 0\}}
+gdb_test "print uv1" { = \{a = 7\}}
+gdb_test "print U()" { = \{x = 0\}} "construct U() via inferior function call"
+gdb_test "print U(99)" { = \{x = 99\}} "construct U(99) via inferior function call"
+gdb_test "print U(99).x" " = 99" "construct U(99) and access .x"
+gdb_test "print NS::U()" { = \{a = 0\}} \
+    "construct NS::U() via inferior function call"
+gdb_test "print NS::U(13)" { = \{a = 13\}} \
+    "construct NS::U(13) via inferior function call"
+gdb_test "print NS::U(13).a" " = 13" \
+    "construct NS::U(13) and access .a"
+
+set u_re [multi_line \
+    {type = union U \{} \
+    {    int x;} \
+    "" \
+    {    U\(void\);} \
+    {    U\(int\);} \
+    {\}}]
+set ns_u_re [multi_line \
+    {type = union NS::U \{} \
+    {    int a;} \
+    "" \
+    {    U\(void\);} \
+    {    U\(int\);} \
+    {\}}]
+gdb_test "ptype U()" $u_re "ptype of U temporary"
+gdb_test "ptype NS::U(99)" $ns_u_re "ptype of NS::U temporary"
+
+gdb_test "p plus_one(U(42))" "= 43" "temporary in function call"
+gdb_test "p add(S(1), S(20))" "= 21" "add two temporaries of S"
+gdb_test "p plus_one(U(add (S(4), S(6))))" "= 11" \
+    "nested function call using temporaries"

base-commit: 07519d531b1e858f665ff011d7f1002f38111ec8
-- 
2.53.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] Add infcall support for C++ constructor-style expressions
  2026-03-26 19:17 [PATCH] Add infcall support for C++ constructor-style expressions Keith Seitz
@ 2026-03-27  5:50 ` Eli Zaretskii
  2026-04-21 13:41 ` Andrew Burgess
  2026-04-27 16:43 ` [PATCH v2] " Keith Seitz
  2 siblings, 0 replies; 8+ messages in thread
From: Eli Zaretskii @ 2026-03-27  5:50 UTC (permalink / raw)
  To: Keith Seitz; +Cc: gdb-patches

> From: Keith Seitz <keiths@redhat.com>
> Date: Thu, 26 Mar 2026 12:17:20 -0700
> 
> This patch adds an initial try at teaching the expression parser/evaluator
> to construct temporary objects requiring construction during an inferior
> function call.
> 
> To accomplish this, I've chosen the route of modifying the parser to
> teach it that `Type(args)' is a function call when `Type' names a
> class/struct/union and is immediately followed by '(', that is, via look-
> ahead). A new parser token and grammar rule have been added to deal
> with this new production.
> 
> The real work is dispatched to `type_operation::evaluate_funcall',
> allocating memory for the temporary and finding the most suitable constructor
> with `find_overload_match'.  It then runs the inferior call, returning
> the newly constructed object.
> 
> I've included many tests covering as many corner cases as I could invent,
> and these tests are clang clean.  They also introduce no regressions on
> x86-64 Fedora 43 with GCC 15.2.1 and RHEL 9.4 with GCC 11.5.0.
> 
> Example:
> Consider a C++ frame where 'struct S { int x; S(int); ... }' is in scope
> 
> Before:
> (gdb) print S(42)
> ❌️ A syntax error in expression, near `10)'.
> 
> After:
> (gdb) print S(42)
> $1 = {x = 42}
> 
> Note that no attempt has been made to deal with templates.  Hopefully
> a follow-on patch can address that.
> ---
>  gdb/NEWS                               |   5 +
>  gdb/c-exp.y                            |  86 +++++++++--
>  gdb/eval.c                             |  55 ++++++++
>  gdb/expop.h                            |   5 +
>  gdb/testsuite/gdb.cp/infcall-ctors.cc  | 128 +++++++++++++++++
>  gdb/testsuite/gdb.cp/infcall-ctors.exp | 188 +++++++++++++++++++++++++
>  6 files changed, 457 insertions(+), 10 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.cc
>  create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.exp

The NEWS part is okay, thanks.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] Add infcall support for C++ constructor-style expressions
  2026-03-26 19:17 [PATCH] Add infcall support for C++ constructor-style expressions Keith Seitz
  2026-03-27  5:50 ` Eli Zaretskii
@ 2026-04-21 13:41 ` Andrew Burgess
  2026-04-21 18:13   ` Keith Seitz
  2026-04-27 16:43 ` [PATCH v2] " Keith Seitz
  2 siblings, 1 reply; 8+ messages in thread
From: Andrew Burgess @ 2026-04-21 13:41 UTC (permalink / raw)
  To: Keith Seitz, gdb-patches

Keith Seitz <keiths@redhat.com> writes:

> This patch adds an initial try at teaching the expression parser/evaluator
> to construct temporary objects requiring construction during an inferior
> function call.
>
> To accomplish this, I've chosen the route of modifying the parser to
> teach it that `Type(args)' is a function call when `Type' names a
> class/struct/union and is immediately followed by '(', that is, via look-
> ahead). A new parser token and grammar rule have been added to deal
> with this new production.
>
> The real work is dispatched to `type_operation::evaluate_funcall',
> allocating memory for the temporary and finding the most suitable constructor
> with `find_overload_match'.  It then runs the inferior call, returning
> the newly constructed object.
>
> I've included many tests covering as many corner cases as I could invent,
> and these tests are clang clean.  They also introduce no regressions on
> x86-64 Fedora 43 with GCC 15.2.1 and RHEL 9.4 with GCC 11.5.0.
>
> Example:
> Consider a C++ frame where 'struct S { int x; S(int); ... }' is in scope
>
> Before:
> (gdb) print S(42)
> ❌️ A syntax error in expression, near `10)'.
>
> After:
> (gdb) print S(42)
> $1 = {x = 42}
>
> Note that no attempt has been made to deal with templates.  Hopefully
> a follow-on patch can address that.
> ---
>  gdb/NEWS                               |   5 +
>  gdb/c-exp.y                            |  86 +++++++++--
>  gdb/eval.c                             |  55 ++++++++
>  gdb/expop.h                            |   5 +
>  gdb/testsuite/gdb.cp/infcall-ctors.cc  | 128 +++++++++++++++++
>  gdb/testsuite/gdb.cp/infcall-ctors.exp | 188 +++++++++++++++++++++++++
>  6 files changed, 457 insertions(+), 10 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.cc
>  create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.exp
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index 03f46df5400..c04727d342d 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -66,6 +66,11 @@
>    automatically set to UTF-8.  (Users can use the Windows 'chcp'
>    command to change the output codepage of the console.)
>  
> +* In C++ GDB now accepts constructor-style expressions "TYPE (ARGS)"
> +  when TYPE names a class, struct, or union in the current expression
> +  context.  This allows objects to be constructed directly during
> +  expression evalution.

typo: evalution -> evaluation

> +
>  * New targets
>  
>  GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
> diff --git a/gdb/c-exp.y b/gdb/c-exp.y
> index a4a910df712..e17ee2d3c12 100644
> --- a/gdb/c-exp.y
> +++ b/gdb/c-exp.y
> @@ -199,7 +199,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value);
>  #endif
>  %}
>  
> -%type <voidval> exp exp1 type_exp start variable qualified_name lcurly function_method
> +%type <voidval> exp exp1 type_exp start variable qualified_name lcurly function_method typename_for_ctor
>  %type <lval> rcurly
>  %type <tval> type typebase scalar_type tag_name_or_complete
>  %type <tvec> nonempty_typelist func_mod parameter_typelist
> @@ -230,7 +230,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value);
>  %token <ssym> NAME /* BLOCKNAME defined below to give it higher precedence. */
>  %token <ssym> UNKNOWN_CPP_NAME
>  %token <voidval> COMPLETE
> -%token <tsym> TYPENAME
> +%token <tsym> TYPENAME TYPENAME_CTOR
>  %token <theclass> CLASSNAME	/* ObjC Class name */
>  %type <sval> name
>  %type <qval> qual_field_name field_name field_name_or_complete
> @@ -533,6 +533,26 @@ msgarg	:	name ':' exp
>  			{ add_msglist(0, 0);   }
>  	;
>  
> +exp	:	typename_for_ctor '('
> +			{ pstate->start_arglist (); }
> +		arglist ')'	%prec ARROW
> +			{
> +			  std::vector<operation_up> args
> +			    = pstate->pop_vector (pstate->end_arglist ());
> +			  operation_up type_op = pstate->pop ();
> +			  pstate->push_new<funcall_operation>
> +			    (std::move (type_op), std::move (args));
> +			}
> +	;
> +
> +exp	:	typename_for_ctor '(' ')'	%prec ARROW
> +			{
> +			  operation_up type_op = pstate->pop ();
> +			  pstate->push_new<funcall_operation>
> +			    (std::move (type_op), std::vector<operation_up> ());
> +			}
> +	;
> +
>  exp	:	exp '('
>  			/* This is to save the value of arglist_len
>  			   being accumulated by an outer function call.  */
> @@ -1471,6 +1491,12 @@ scalar_type:
>  						       "int"); }
>  	;
>  
> +/* Constructor-style calls.  */
> +typename_for_ctor
> +	:	TYPENAME_CTOR
> +			{ pstate->push_new<type_operation> ($1.type); }
> +	;
> +
>  /* Implements (approximately): (type-qualifier)* type-specifier.
>  
>     When type-specifier is only ever a single word, like 'float' then these
> @@ -3114,6 +3140,34 @@ static int popping;
>     built up.  */
>  static auto_obstack name_obstack;
>  
> +/* Return TYPENAME_CTOR only when the next token is '(', so this
> +   token is used solely for constructor calls.  Otherwise return TYPENAME.
> +   NAME_END is the character just past the name (e.g. yylval.sval.ptr +
> +   yylval.sval.length).  */
> +
> +static int
> +typename_token_for (struct parser_state *par_state, struct type *type,
> +		    const char *name_end)
> +{
> +  if (type == nullptr
> +      || par_state->language ()->la_language != language_cplus)
> +    return TYPENAME;
> +  type = check_typedef (type);
> +  if (type->code () != TYPE_CODE_STRUCT && type->code () != TYPE_CODE_UNION)
> +    return TYPENAME;
> +  /* Only return TYPENAME_CTOR when followed by '('.  */
> +  if (name_end == nullptr)
> +    return TYPENAME;
> +  {
> +    const char *p = name_end;
> +    while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')

Would 'c_isspace (*p)' work here?  It's what we use in similar cases
within this file.


> +      ++p;
> +    if (*p != '(')
> +      return TYPENAME;
> +  }
> +  return TYPENAME_CTOR;
> +}
> +
>  /* Classify a NAME token.  The contents of the token are in `yylval'.
>     Updates yylval and returns the new token type.  BLOCK is the block
>     in which lookups start; this can be NULL to mean the global scope.
> @@ -3157,7 +3211,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
>  	  if (bsym.symbol != NULL)
>  	    {
>  	      yylval.tsym.type = bsym.symbol->type ();
> -	      return TYPENAME;
> +	      return typename_token_for (par_state, yylval.tsym.type,
> +					 yylval.sval.ptr + yylval.sval.length);
>  	    }
>  	}
>  
> @@ -3184,7 +3239,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
>    if (bsym.symbol && bsym.symbol->loc_class () == LOC_TYPEDEF)
>      {
>        yylval.tsym.type = bsym.symbol->type ();
> -      return TYPENAME;
> +      return typename_token_for (par_state, yylval.tsym.type,
> +				 yylval.sval.ptr + yylval.sval.length);
>      }
>  
>    /* See if it's an ObjC classname.  */
> @@ -3270,7 +3326,9 @@ classify_inner_name (struct parser_state *par_state,
>        if (base_type != NULL)
>  	{
>  	  yylval.tsym.type = base_type;
> -	  return TYPENAME;
> +	  return typename_token_for (par_state, yylval.tsym.type,
> +				     yylval.ssym.stoken.ptr
> +				     + yylval.ssym.stoken.length);
>  	}
>  
>        return ERROR;
> @@ -3290,14 +3348,18 @@ classify_inner_name (struct parser_state *par_state,
>  	if (base_type != NULL)
>  	  {
>  	    yylval.tsym.type = base_type;
> -	    return TYPENAME;
> +	    return typename_token_for (par_state, yylval.tsym.type,
> +				       yylval.ssym.stoken.ptr
> +				       + yylval.ssym.stoken.length);
>  	  }
>        }
>        return ERROR;
>  
>      case LOC_TYPEDEF:
>        yylval.tsym.type = yylval.ssym.sym.symbol->type ();
> -      return TYPENAME;
> +      return typename_token_for (par_state, yylval.tsym.type,
> +				 yylval.ssym.stoken.ptr
> +				 + yylval.ssym.stoken.length);
>  
>      default:
>        return NAME;
> @@ -3332,7 +3394,7 @@ handle_qualified_field_name (qualified_name_token token)
>        int kind = classify_inner_name (pstate,
>  				      pstate->expression_context_block,
>  				      type);
> -      if (kind != TYPENAME)
> +      if (kind != TYPENAME && kind != TYPENAME_CTOR)
>  	error (_("could not find type '%s'"), accum.c_str ());
>  
>        type = yylval.tsym.type;
> @@ -3384,7 +3446,8 @@ yylex (void)
>      current.token = classify_name (pstate, pstate->expression_context_block,
>  				   is_quoted_name, last_lex_was_structop);
>    if (pstate->language ()->la_language != language_cplus
> -      || (current.token != TYPENAME && current.token != COLONCOLON
> +      || (current.token != TYPENAME && current.token != TYPENAME_CTOR
> +	  && current.token != COLONCOLON
>  	  && current.token != FILENAME
>  	  && (cpstate->assume_classification == TYPE_CODE_UNDEF
>  	      || current.token != NAME))
> @@ -3430,6 +3493,7 @@ yylex (void)
>    else
>      {
>        gdb_assert (current.token == TYPENAME
> +		  || current.token == TYPENAME_CTOR
>  		  || cpstate->assume_classification != TYPE_CODE_UNDEF);
>        search_block = pstate->expression_context_block;
>        obstack_grow (&name_obstack, current.value.sval.ptr,
> @@ -3460,7 +3524,8 @@ yylex (void)
>  						  context_type);
>  	  /* We keep going until we either run out of names, or until
>  	     we have a qualified name which is not a type.  */
> -	  if (classification != TYPENAME && classification != NAME)
> +	  if (classification != TYPENAME && classification != TYPENAME_CTOR
> +	      && classification != NAME)
>  	    break;
>  
>  	  /* Accept up to this token.  */
> @@ -3591,6 +3656,7 @@ c_print_token (FILE *file, int type, YYSTYPE value)
>        break;
>  
>      case TYPENAME:
> +    case TYPENAME_CTOR:
>        parser_fprintf (file, "tsym<type=%s, name=%s>",
>  		      value.tsym.type->safe_name (),
>  		      copy_name (value.tsym.stoken).c_str ());
> diff --git a/gdb/eval.c b/gdb/eval.c
> index 7beff554ed4..e988b954059 100644
> --- a/gdb/eval.c
> +++ b/gdb/eval.c
> @@ -1869,6 +1869,61 @@ type_operation::evaluate (struct type *expect_type, struct expression *exp,
>      error (_("Attempt to use a type name as an expression"));
>  }
>  
> +value *
> +type_operation::evaluate_funcall (struct type *expect_type,
> +				  struct expression *exp,
> +				  enum noside noside,
> +				  const std::vector<operation_up> &args)
> +{
> +  struct type *type = std::get<0> (m_storage);
> +  type = check_typedef (type);
> +
> +  /* Constructor-style call Type(args) is only for C++ aggregate types.  */
> +  gdb_assert (exp->language_defn->la_language == language_cplus);
> +
> +  const char *name = type->name ();
> +  if (name == nullptr)
> +    error (_("Cannot call constructor of unnamed type"));

Thinking about this error was interesting.  I was wondering how we might
test this case, as I don't think this is tested right now.  I tried
writing some C++ like:

  struct { int a; int b; } global_var = { 1, 2 };

Then in GDB I tried:

  (gdb) p decltype(global_var) (3)

Which I know makes no sense, the anonymous struct doesn't even have a
constructor, but I wondered if we'd hit the anonymous type error before
we even searched for a constructor.

We didn't though, as GDB doesn't know how to parse the expression:

  (gdb) p decltype(global_var)(3)
  A syntax error in expression, near `(3)'.
  (gdb)

the decltype part is handled in c-exp.y by the rule:

  type_exp:
  	... snip ...
  	|	DECLTYPE '(' exp ')'
  			{
  			  pstate->wrap<decltype_operation> ();
  			}

And the '(3)' part of the expression is handled by your new rule:

> +exp	:	typename_for_ctor '('
> +			{ pstate->start_arglist (); }
> +		arglist ')'	%prec ARROW
> +			{

But, I wondered, what if, instead of typename_for_ctor, we just used
'type_exp', like this:

  exp	:	type_exp '('
  			{
  			  pstate->start_arglist ();
  			}
  		arglist ')'	%prec ARROW
  			{

Then I extended the type_exp rule like:

  type_exp:
  	... snip ...
  	|	TYPENAME_CTOR
  			{
  			  pstate->push_new<type_operation> ($1.type);
  			}

All of the new tests still pass...

... but the decltype example still doesn't work.  But by this time I was
curious, clearly my actual example with global_var is never going to
work, the global_var type doesn't have a constructor, but what if
global_var was a type that did have a constructor?  The decltype trick
should be workable, right?

So, I guess the question I'm circling around here is, instead of adding
typename_for_ctor, did you try just using the existing type_exp rule?
Even if we didn't get the decltype trick working in the original commit,
it feels like going through type_exp would leave that as a possibility
for the future, but going with typename_for_ctor feels like it will make
that harder.

> +
> +  /* Get the constructor name from the type name.  */
> +  gdb::unique_xmalloc_ptr<char> ctor_name_ptr = cp_func_name (name);
> +  const char *ctor_name = (ctor_name_ptr != nullptr) ? ctor_name_ptr.get () : name;

This line seems a little long, maybe wrap at the '=' ?

> +
> +  if (!overload_resolution)
> +    return operation::evaluate_funcall (expect_type, exp, noside, args);

I've pretty sure that this path, calling operation::evaluate_funcall,
will always result in an error like:

  (gdb) set overload-resolution off 
  (gdb) p U(34)
  Attempt to use a type name as an expression
  (gdb) 

I wonder if it would be clearer to the user to just do:

  if (!overload_resolution)
    error (_("Constructor calls require 'set overload-resolution on'"));

I didn't see any tests with 'set overload-resolution off' in use, so it
wasn't clear if there's maybe some path where the function call as you
have it can do anything other than throw an error?

> +
> +  std::vector<value *> argvec (1 + args.size ());
> +  value *this_ptr;
> +  if (noside == EVAL_AVOID_SIDE_EFFECTS)
> +    this_ptr = value::zero (lookup_pointer_type (type), lval_memory);
> +  else
> +    {
> +      value *alloc_val = value_allocate_space_in_inferior (type->length ());
> +      this_ptr = value_from_pointer (lookup_pointer_type (type),
> +				     value_as_long (alloc_val));
> +    }
> +  argvec[0] = this_ptr;
> +  for (size_t i = 0; i < args.size (); ++i)
> +    argvec[i + 1] = args[i]->evaluate_with_coercion (exp, noside);
> +  gdb::array_view<value *> arg_view = argvec;
> +
> +  value *callee = nullptr;
> +  int static_memfuncp;
> +  find_overload_match (arg_view, ctor_name, METHOD,
> +		       &argvec[0], nullptr, &callee, nullptr,
> +		       &static_memfuncp, 0, noside);

It would not be usual for constructors to be static, but we should
probably handle the case where static_memfuncp is true, even if it is
just:

  if (static_memfuncp)
    error (_("Constructor %s is unexpectedly marked static"), name);

Bonus points for using the DWARF assembler to exercise this error case,
but I don't think that's a hard requirement, I see this error more as a
glorified todo marker -- if we ever find a case where this is triggers
then we can understand it, and fix GDB to match.

> +  if (callee == nullptr)
> +    error (_("Cannot resolve constructor %s to any overloaded instance"),
> +	   name);

I don't think this error case is being tested.  Can you add a test case
for this?

> +
> +  if (noside == EVAL_AVOID_SIDE_EFFECTS)
> +    return value::zero (type, not_lval);
> +
> +  evaluate_subexp_do_call (exp, noside, callee, arg_view,
> +			   nullptr, expect_type);
> +  return value_ind (this_ptr);
> +}
> +
>  }
>  
>  /* A helper function for BINOP_ASSIGN_MODIFY.  */
> diff --git a/gdb/expop.h b/gdb/expop.h
> index c58a8d7ac37..9839e3bffa9 100644
> --- a/gdb/expop.h
> +++ b/gdb/expop.h
> @@ -1597,6 +1597,11 @@ class type_operation
>  		   struct expression *exp,
>  		   enum noside noside) override;
>  
> +  value *evaluate_funcall (struct type *expect_type,
> +			   struct expression *exp,
> +			   enum noside noside,
> +			   const std::vector<operation_up> &args) override;
> +
>    enum exp_opcode opcode () const override
>    { return OP_TYPE; }
>  
> diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.cc b/gdb/testsuite/gdb.cp/infcall-ctors.cc
> new file mode 100644
> index 00000000000..053542ec5ea
> --- /dev/null
> +++ b/gdb/testsuite/gdb.cp/infcall-ctors.cc
> @@ -0,0 +1,128 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +struct S {

The '{' should be on a new line.  This mistake is repeated throughout
this test, I'll not point them all out.

> +  int x;
> +  explicit S (int n = 0) : x (n) {}
> +  S operator+ (int n) const { return S (x + n); }
> +};
> +
> +typedef S S_td;
> +using S_u = S;
> +
> +static int add (const struct S &s1, const struct S &s2) {

Newline before add, opening '{' on its own line.  Unless this layout is
needed for the test?  In which case a comment is needed.

> +  return s1.x + s2.x;
> +}
> +
> +/* Non-trivially copyable: copy-constructing swaps the two members.  */
> +struct swapcopy {
> +  int lo;
> +  int hi;
> +  swapcopy (int l, int h) : lo (l), hi (h) {}
> +  swapcopy (const swapcopy &o) : lo (o.hi), hi (o.lo) {}
> +};
> +
> +/* Pass swapcopy by value so the call must copy-construct the argument.  */
> +
> +static int
> +swapcopy_first_byval (swapcopy c)
> +{
> +  return c.lo;
> +}
> +
> +struct Base {
> +  int x;
> +  Base () : x (0) {}
> +  explicit Base (int n) : x (n) {}
> +  Base (const Base &other) : x (other.x) {}
> +};
> +
> +typedef Base Base_td;
> +using Base_u = Base;
> +
> +namespace NS {
> +class Derived : public Base {
> +public:
> +  int y;
> +  Derived () : Base (), y (0) {}
> +  Derived (int a, int b) : Base (a), y (b) {}
> +};
> +
> +typedef Base NsBaseTd;
> +using NsBaseU = Base;
> +typedef Derived Derived_td;
> +using Derived_u = Derived;
> +
> +union U {
> +  int a;
> +  U () : a (0) {}
> +  explicit U (int n) : a (n) {}
> +};
> +
> +typedef U Nu_td;
> +}
> +
> +union U {
> +  int x;
> +  U () : x (0) {}
> +  explicit U (int n) : x (n) {}
> +};
> +
> +typedef U U_td;
> +
> +static int plus_one (U u)
> +{
> +  return u.x + 1;
> +}
> +
> +int
> +main (void)
> +{
> +  S s0;           /* default: x = 0 */
> +  S s1 (42);     /* x = 42 */
> +  S s2 (s1 + 2); /* x = 44 */
> +  S s3 = s1;
> +  NS::Derived d;	     /* Base part x=0, Derived part y=0  */
> +  NS::Derived d1 (10, 20);   /* Base part x=10, Derived part y=20  */
> +  Base b;		     /* x=0  */
> +  Base b1 (5);               /* x=5  */
> +  Base b2 (d1);		     /* x=10  */
> +  U u0;                      /* default: x = 0  */
> +  U u1 (42);                 /* x = 42  */
> +  NS::U uv0;                /* default: a = 0  */
> +  NS::U uv1 (7);            /* a = 7  */
> +  S_td s_td = S_td (11);
> +  S_u s_u = S_u (12);
> +  Base_td b_td = Base_td (8);
> +  Base_u b_u = Base_u (9);
> +  NS::NsBaseTd nsb_td = NS::NsBaseTd (13);
> +  NS::NsBaseU nsb_u = NS::NsBaseU (14);
> +  NS::Derived_td d_td = NS::Derived_td (2, 3);
> +  NS::Derived_u d_u = NS::Derived_u (4, 5);
> +  U_td u_td = U_td (15);
> +  NS::Nu_td nu_u = NS::Nu_td (16);
> +  swapcopy swp (30, 40);
> +  int result = add (s1, s2);
> +  return result + d.x + d.y + b.x + b1.x + b2.x + d.x + d.y \
> +	+ d1.x + d1.y + plus_one (u0) + u1.x + uv0.a + uv1.a \
> +	+ s_td.x + s_u.x + b_td.x + b_u.x + nsb_td.x + nsb_u.x \
> +	+ d_td.x + d_td.y + d_u.x + d_u.y + u_td.x + nu_u.a \
> +	+ swapcopy_first_byval (swapcopy (100, 200)) \
> +	+ swapcopy_first_byval (swp);  /* stop-here */
> +}
> diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.exp b/gdb/testsuite/gdb.cp/infcall-ctors.exp
> new file mode 100644
> index 00000000000..88297018b28
> --- /dev/null
> +++ b/gdb/testsuite/gdb.cp/infcall-ctors.exp
> @@ -0,0 +1,188 @@
> +# Copyright (C) 2026 Free Software Foundation, Inc.
> +
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test constructor calls and casting via inferior function calls:
> +# - Simple struct S with constructor (int, default 0).
> +# - Base and Derived; cast derived to base, construct Base from Derived.
> +# - Typedef and using aliases: ctor resolution must use the class ctor even
> +#   when the expression names a typedef or alias (DWARF may name types
> +#   differently from the underlying class tag).
> +# - swapcopy: two ints with a user-defined copy ctor that swaps them
> +#   (non-trivially copyable); pass-by-value in inferior calls must run it.
> +
> +require allow_cplus_tests
> +
> +standard_testfile .cc
> +
> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
> +    return -1

The -1 is not needed.

Thanks,
Andrew



> +}
> +
> +if {![runto_main]} {
> +    return
> +}
> +
> +# Run to stop-here to ensure all locals are initialized.
> +gdb_breakpoint [gdb_get_line_number "stop-here"]
> +gdb_continue_to_breakpoint "stop-here"
> +
> +# Simple tests involving "struct S".
> +gdb_test "ptype S" [multi_line \
> +    {type = struct S \{} \
> +    {    int x;} \
> +    "" \
> +    {    S\(int\);} \
> +    {    S operator\+\(int\) const;} \
> +    {\}}]
> +
> +gdb_test "print s0" " = \\{x = 0\\}" "print s0 default ctor"
> +gdb_test "print s1" " = \\{x = 42\\}" "print s1 with 42"
> +gdb_test "print S(99)" " = \\{x = 99\\}" "construct S(99) via inferior function call"
> +
> +gdb_test "ptype swapcopy" [multi_line \
> +    {type = struct swapcopy \{} \
> +    {    int lo;} \
> +    {    int hi;} \
> +    "" \
> +    {    swapcopy\(int, int\);} \
> +    {    swapcopy\(const swapcopy ?&\);} \
> +    {\}}]
> +gdb_test "print swapcopy(1, 2)" { = \{lo = 1, hi = 2\}} \
> +    "construct swapcopy via inferior function call"
> +gdb_test {print swapcopy($)} {= \{lo = 2, hi = 1\}} \
> +    "call copy ctor on object via history"
> +gdb_test "print swapcopy_first_byval(swapcopy(10, 20))" "= 20" \
> +    "pass-by-value copy ctor swaps members"
> +gdb_test "print swapcopy_first_byval(swp)" "= 40" \
> +    "swapcopy local passed by value uses copy ctor"
> +
> +gdb_test "print S_td(33)" " = \\{x = 33\\}" \
> +    "construct S via typedef name (not underlying struct tag)"
> +gdb_test "print S_u(34)" " = \\{x = 34\\}" "construct S via using alias"
> +gdb_test "print Base_td(8)" { = \{x = 8\}} "construct Base via typedef name"
> +gdb_test "print Base_u(9)" { = \{x = 9\}} "construct Base via using alias"
> +gdb_test "print NS::NsBaseTd(5)" { = \{x = 5\}} \
> +    "construct Base via typedef in namespace"
> +gdb_test "print NS::NsBaseU(6)" { = \{x = 6\}} \
> +    "construct Base via using alias in namespace"
> +gdb_test "print NS::Derived_td(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \
> +    "construct NS::Derived via typedef name"
> +gdb_test "print NS::Derived_u(3, 4)" { = \{<Base> = \{x = 3\}, y = 4\}} \
> +    "construct NS::Derived via using alias"
> +gdb_test "print U_td(55)" { = \{x = 55\}} \
> +    "construct global union U via typedef name"
> +gdb_test "print NS::Nu_td(66)" { = \{a = 66\}} \
> +    "construct NS::U via typedef name"
> +
> +# Tests involving "Base" and "Derived".
> +set base_re [multi_line \
> +    {type = struct Base \{} \
> +    {    int x;} \
> +    "" \
> +    {    Base\(void\);} \
> +    {    Base\(int\);} \
> +    {    Base\(const Base ?&\);} \
> +    {\}}]
> +gdb_test "ptype Base" $base_re
> +
> +set derived_re [multi_line \
> +    {type = class NS::Derived : public Base \{} \
> +    {  public:} \
> +    {    int y;} \
> +    "" \
> +    {    Derived\(void\);} \
> +    {    Derived\(int, int\);} \
> +    {\}}]
> +gdb_test "ptype NS::Derived" $derived_re
> +
> +gdb_test "print d" { = \{<Base> = \{x = 0\}, y = 0\}}
> +gdb_test "print d1" { = \{<Base> = \{x = 10\}, y = 20\}}
> +gdb_test "print b" { = \{x = 0\}}
> +gdb_test "print b1" { = \{x = 5\}}
> +gdb_test "print b2" { = \{x = 10\}}
> +gdb_test "print (Base)(d1)" { = \{x = 10\}} "cast (Base)(d1) slices to Base"
> +gdb_test "print Base(d1)" { = \{x = 10\}} "construct Base(d1) from Derived"
> +gdb_test "print Base()" { = \{x = 0\}} "construct Base() default"
> +gdb_test "print Base(7)" { = \{x = 7\}} "construct Base(7) with argument"
> +gdb_test "print NS::Derived()" { = \{<Base> = \{x = 0\}, y = 0\}} \
> +    "construct NS::Derived() via inferior function call"
> +gdb_test "print NS::Derived().y" " = 0" "construct NS::Derived() and access .y"
> +gdb_test "print NS::Derived(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \
> +    "construct NS::Derived(1, 2) via inferior function call"
> +gdb_test "print NS::Derived(1, 2).y" " = 2" \
> +    "construct NS::Derived(1, 2) and access .y"
> +gdb_test "print ((Base)d1).x" " = 10" "cast ((Base)d1).x"
> +
> +# Print the types of these "temporary" objects.
> +gdb_test "ptype Base()" $base_re "ptype of Base temporary"
> +gdb_test "ptype NS::Derived(15, 25)" $derived_re \
> +	"ptype of NS::Derived temporary"
> +
> +# Tests involving unions.
> +gdb_test "ptype U" [multi_line \
> +    {type = union U \{} \
> +    {    int x;} \
> +    "" \
> +    {    U\(void\);} \
> +    {    U\(int\);} \
> +    {\}}] \
> +    "ptype U"
> +
> +gdb_test "ptype NS::U" [multi_line \
> +    {type = union NS::U \{} \
> +    {    int a;} \
> +    "" \
> +    {    U\(void\);} \
> +    {    U\(int\);} \
> +    {\}}] \
> +    "ptype NS::U"
> +
> +gdb_test "print u0" { = \{x = 0\}}
> +gdb_test "print u1" { = \{x = 42\}}
> +gdb_test "print uv0" { = \{a = 0\}}
> +gdb_test "print uv1" { = \{a = 7\}}
> +gdb_test "print U()" { = \{x = 0\}} "construct U() via inferior function call"
> +gdb_test "print U(99)" { = \{x = 99\}} "construct U(99) via inferior function call"
> +gdb_test "print U(99).x" " = 99" "construct U(99) and access .x"
> +gdb_test "print NS::U()" { = \{a = 0\}} \
> +    "construct NS::U() via inferior function call"
> +gdb_test "print NS::U(13)" { = \{a = 13\}} \
> +    "construct NS::U(13) via inferior function call"
> +gdb_test "print NS::U(13).a" " = 13" \
> +    "construct NS::U(13) and access .a"
> +
> +set u_re [multi_line \
> +    {type = union U \{} \
> +    {    int x;} \
> +    "" \
> +    {    U\(void\);} \
> +    {    U\(int\);} \
> +    {\}}]
> +set ns_u_re [multi_line \
> +    {type = union NS::U \{} \
> +    {    int a;} \
> +    "" \
> +    {    U\(void\);} \
> +    {    U\(int\);} \
> +    {\}}]
> +gdb_test "ptype U()" $u_re "ptype of U temporary"
> +gdb_test "ptype NS::U(99)" $ns_u_re "ptype of NS::U temporary"
> +
> +gdb_test "p plus_one(U(42))" "= 43" "temporary in function call"
> +gdb_test "p add(S(1), S(20))" "= 21" "add two temporaries of S"
> +gdb_test "p plus_one(U(add (S(4), S(6))))" "= 11" \
> +    "nested function call using temporaries"
>
> base-commit: 07519d531b1e858f665ff011d7f1002f38111ec8
> -- 
> 2.53.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] Add infcall support for C++ constructor-style expressions
  2026-04-21 13:41 ` Andrew Burgess
@ 2026-04-21 18:13   ` Keith Seitz
  0 siblings, 0 replies; 8+ messages in thread
From: Keith Seitz @ 2026-04-21 18:13 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

Hi,

On 4/21/26 6:41 AM, Andrew Burgess wrote:
> Keith Seitz <keiths@redhat.com> writes:
>
 >> diff --git a/gdb/c-exp.y b/gdb/c-exp.y>> index 
a4a910df712..e17ee2d3c12 100644
>> --- a/gdb/c-exp.y
>> +++ b/gdb/c-exp.y
>> @@ -3114,6 +3140,34 @@ static int popping;
>>      built up.  */
>>   static auto_obstack name_obstack;
>>   
>> +/* Return TYPENAME_CTOR only when the next token is '(', so this
>> +   token is used solely for constructor calls.  Otherwise return TYPENAME.
>> +   NAME_END is the character just past the name (e.g. yylval.sval.ptr +
>> +   yylval.sval.length).  */
>> +
>> +static int
>> +typename_token_for (struct parser_state *par_state, struct type *type,
>> +		    const char *name_end)
>> +{
>> +  if (type == nullptr
>> +      || par_state->language ()->la_language != language_cplus)
>> +    return TYPENAME;
>> +  type = check_typedef (type);
>> +  if (type->code () != TYPE_CODE_STRUCT && type->code () != TYPE_CODE_UNION)
>> +    return TYPENAME;
>> +  /* Only return TYPENAME_CTOR when followed by '('.  */
>> +  if (name_end == nullptr)
>> +    return TYPENAME;
>> +  {
>> +    const char *p = name_end;
>> +    while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
> 
> Would 'c_isspace (*p)' work here?  It's what we use in similar cases
> within this file.

Absolutely!

>> diff --git a/gdb/eval.c b/gdb/eval.c
>> index 7beff554ed4..e988b954059 100644
>> --- a/gdb/eval.c
>> +++ b/gdb/eval.c
>> @@ -1869,6 +1869,61 @@ type_operation::evaluate (struct type *expect_type, struct expression *exp,
>>       error (_("Attempt to use a type name as an expression"));
>>   }
>>   
>> +value *
>> +type_operation::evaluate_funcall (struct type *expect_type,
>> +				  struct expression *exp,
>> +				  enum noside noside,
>> +				  const std::vector<operation_up> &args)
>> +{
>> +  struct type *type = std::get<0> (m_storage);
>> +  type = check_typedef (type);
>> +
>> +  /* Constructor-style call Type(args) is only for C++ aggregate types.  */
>> +  gdb_assert (exp->language_defn->la_language == language_cplus);
>> +
>> +  const char *name = type->name ();
>> +  if (name == nullptr)
>> +    error (_("Cannot call constructor of unnamed type"));
> 
> Thinking about this error was interesting.
[snip]
> Then I extended the type_exp rule like:
> 
>    type_exp:
>    	... snip ...
>    	|	TYPENAME_CTOR
>    			{
>    			  pstate->push_new<type_operation> ($1.type);
>    			}
> 
> All of the new tests still pass...
> 
> ... but the decltype example still doesn't work.  But by this time I was
> curious, clearly my actual example with global_var is never going to
> work, the global_var type doesn't have a constructor, but what if
> global_var was a type that did have a constructor?  The decltype trick
> should be workable, right?
> 
> So, I guess the question I'm circling around here is, instead of adding
> typename_for_ctor, did you try just using the existing type_exp rule?
> Even if we didn't get the decltype trick working in the original commit,
> it feels like going through type_exp would leave that as a possibility
> for the future, but going with typename_for_ctor feels like it will make
> that harder.

I did at one time use a similar approach. I think what happened is that
I ran into issues and started breaking down/isolating my changes to make
sure that it wasn't some other production messing me up. Alas, I did
not actually go back and try to integrate my working solution. I will
investigate further for v2.

> 
>> +
>> +  /* Get the constructor name from the type name.  */
>> +  gdb::unique_xmalloc_ptr<char> ctor_name_ptr = cp_func_name (name);
>> +  const char *ctor_name = (ctor_name_ptr != nullptr) ? ctor_name_ptr.get () : name;
> 
> This line seems a little long, maybe wrap at the '=' ?
> 
>> +
>> +  if (!overload_resolution)
>> +    return operation::evaluate_funcall (expect_type, exp, noside, args);
> 
> I've pretty sure that this path, calling operation::evaluate_funcall,
> will always result in an error like:
> 
>    (gdb) set overload-resolution off
>    (gdb) p U(34)
>    Attempt to use a type name as an expression
>    (gdb)
> 
> I wonder if it would be clearer to the user to just do:
> 
>    if (!overload_resolution)
>      error (_("Constructor calls require 'set overload-resolution on'"));

That is a much more user friendly approach that I will adopt in v2.

> I didn't see any tests with 'set overload-resolution off' in use, so it
> wasn't clear if there's maybe some path where the function call as you
> have it can do anything other than throw an error?

I'll add this test, too.

>> +  std::vector<value *> argvec (1 + args.size ());
>> +  value *this_ptr;
>> +  if (noside == EVAL_AVOID_SIDE_EFFECTS)
>> +    this_ptr = value::zero (lookup_pointer_type (type), lval_memory);
>> +  else
>> +    {
>> +      value *alloc_val = value_allocate_space_in_inferior (type->length ());
>> +      this_ptr = value_from_pointer (lookup_pointer_type (type),
>> +				     value_as_long (alloc_val));
>> +    }
>> +  argvec[0] = this_ptr;
>> +  for (size_t i = 0; i < args.size (); ++i)
>> +    argvec[i + 1] = args[i]->evaluate_with_coercion (exp, noside);
>> +  gdb::array_view<value *> arg_view = argvec;
>> +
>> +  value *callee = nullptr;
>> +  int static_memfuncp;
>> +  find_overload_match (arg_view, ctor_name, METHOD,
>> +		       &argvec[0], nullptr, &callee, nullptr,
>> +		       &static_memfuncp, 0, noside);
> 
> It would not be usual for constructors to be static, but we should
> probably handle the case where static_memfuncp is true, even if it is
> just:
> 
>    if (static_memfuncp)
>      error (_("Constructor %s is unexpectedly marked static"), name);
> 
> Bonus points for using the DWARF assembler to exercise this error case,
> but I don't think that's a hard requirement, I see this error more as a
> glorified todo marker -- if we ever find a case where this is triggers
> then we can understand it, and fix GDB to match.

I will add this.

> 
>> +  if (callee == nullptr)
>> +    error (_("Cannot resolve constructor %s to any overloaded instance"),
>> +	   name);
> 
> I don't think this error case is being tested.  Can you add a test case
> for this?

Boy, I've spent a lot of my day today trying to trigger this error. I am
convinced it cannot be done. I'm passing METHOD to find_overload_match, 
which will always call error() when no matching symbol is found. Thus I
think it best to replace this error check with an assertion.

> 
>> +
>> +  if (noside == EVAL_AVOID_SIDE_EFFECTS)
>> +    return value::zero (type, not_lval);
>> +
>> +  evaluate_subexp_do_call (exp, noside, callee, arg_view,
>> +			   nullptr, expect_type);
>> +  return value_ind (this_ptr);
>> +}
>> +
>>   }
>>   
>>   /* A helper function for BINOP_ASSIGN_MODIFY.  */
>> diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.cc b/gdb/testsuite/gdb.cp/infcall-ctors.cc
>> new file mode 100644
>> index 00000000000..053542ec5ea
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.cp/infcall-ctors.cc
>> @@ -0,0 +1,128 @@
>> +/* This testcase is part of GDB, the GNU debugger.
>> +
>> +   Copyright (C) 2026 Free Software Foundation, Inc.
>> +
>> +   This file is part of GDB.
>> +
>> +   This program is free software; you can redistribute it and/or modify
>> +   it under the terms of the GNU General Public License as published by
>> +   the Free Software Foundation; either version 3 of the License, or
>> +   (at your option) any later version.
>> +
>> +   This program is distributed in the hope that it will be useful,
>> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
>> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> +   GNU General Public License for more details.
>> +
>> +   You should have received a copy of the GNU General Public License
>> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
>> +
>> +struct S {
> 
> The '{' should be on a new line.  This mistake is repeated throughout
> this test, I'll not point them all out.
> 
>> +  int x;
>> +  explicit S (int n = 0) : x (n) {}
>> +  S operator+ (int n) const { return S (x + n); }
>> +};
>> +
>> +typedef S S_td;
>> +using S_u = S;
>> +
>> +static int add (const struct S &s1, const struct S &s2) {
> 
> Newline before add, opening '{' on its own line.  Unless this layout is
> needed for the test?  In which case a comment is needed.

Nope -- just like a lot of the other silly formatting issues, I simply
over-relied on my new editor to "do the right thing." I'll fix all
the errors you've mentioned.

Thank you for your review! [I'll submit a v2.]

Keith


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v2] Add infcall support for C++ constructor-style expressions
  2026-03-26 19:17 [PATCH] Add infcall support for C++ constructor-style expressions Keith Seitz
  2026-03-27  5:50 ` Eli Zaretskii
  2026-04-21 13:41 ` Andrew Burgess
@ 2026-04-27 16:43 ` Keith Seitz
  2026-06-19 17:21   ` Keith Seitz
  2026-06-27  0:06   ` Kevin Buettner
  2 siblings, 2 replies; 8+ messages in thread
From: Keith Seitz @ 2026-04-27 16:43 UTC (permalink / raw)
  To: gdb-patches

Changes in v2:
- Formatting fixes
- Use c_isspace to skip whitespace in typename_token_for()
- Merged rules for typename_for_ctor to type_exp
  [This adds ~20 shift/reduce conflicts.]
- Updated error message when overload-resolution is off
- Handle static constructors and add new tests for this case
- Added test case for when ctor is not found
- Test early return: remove integer return status
 
Thanks,
Keith

---------

This patch adds an initial try at teaching the expression parser/evaluator
to construct temporary objects requiring construction during an inferior
function call.

To accomplish this, I've chosen the route of modifying the parser to
teach it that `Type(args)' is a function call when `Type' names a
class/struct/union and is immediately followed by '(', that is, via look-
ahead). A new parser token and grammar rule have been added to deal
with this new production.

The real work is dispatched to `type_operation::evaluate_funcall',
allocating memory for the temporary and finding the most suitable constructor
with `find_overload_match'.  It then runs the inferior call, returning
the newly constructed object.

I've included many tests covering as many corner cases as I could invent,
and these tests are clang clean.  They also introduce no regressions on
x86-64 Fedora 43 with GCC 15.2.1 and RHEL 9.4 with GCC 11.5.0.

Example:
Consider a C++ frame where 'struct S { int x; S(int); ... }' is in scope

Before:
(gdb) print S(42)
❌️ A syntax error in expression, near `10)'.

After:
(gdb) print S(42)
$1 = {x = 42}

Note that no attempt has been made to deal with templates.  Hopefully
a follow-on patch can address that.
---
 gdb/NEWS                                     |   5 +
 gdb/c-exp.y                                  |  85 +++++++-
 gdb/eval.c                                   |  60 ++++++
 gdb/expop.h                                  |   5 +
 gdb/testsuite/gdb.cp/infcall-ctors.cc        | 141 +++++++++++++
 gdb/testsuite/gdb.cp/infcall-ctors.exp       | 198 +++++++++++++++++++
 gdb/testsuite/gdb.cp/static-ctor-infcall.c   |  33 ++++
 gdb/testsuite/gdb.cp/static-ctor-infcall.exp |  87 ++++++++
 8 files changed, 605 insertions(+), 9 deletions(-)
 create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.cc
 create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.exp
 create mode 100644 gdb/testsuite/gdb.cp/static-ctor-infcall.c
 create mode 100644 gdb/testsuite/gdb.cp/static-ctor-infcall.exp

diff --git a/gdb/NEWS b/gdb/NEWS
index e233906153a..24d26d29ea5 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -75,6 +75,11 @@
 * The Windows native target now supports scheduler-locking.  E.g.,
   "set scheduler-locking on" now works.  Previously it gave an error.
 
+* In C++ GDB now accepts constructor-style expressions "TYPE (ARGS)"
+  when TYPE names a class, struct, or union in the current expression
+  context.  This allows objects to be constructed directly during
+  expression evaluation.
+
 * New targets
 
 GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
diff --git a/gdb/c-exp.y b/gdb/c-exp.y
index 2829d8bccba..73f88bd84f1 100644
--- a/gdb/c-exp.y
+++ b/gdb/c-exp.y
@@ -231,7 +231,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value);
 %token <ssym> NAME /* BLOCKNAME defined below to give it higher precedence. */
 %token <ssym> UNKNOWN_CPP_NAME
 %token <voidval> COMPLETE
-%token <tsym> TYPENAME
+%token <tsym> TYPENAME TYPENAME_CTOR
 %token <theclass> CLASSNAME	/* ObjC Class name */
 %type <sval> name
 %type <qval> qual_field_name field_name field_name_or_complete
@@ -314,6 +314,13 @@ type_exp:	type
 			{
 			  pstate->push_new<type_operation> ($1);
 			}
+	|	TYPENAME_CTOR
+			{
+			  /* Constructor-style call: lexer returns TYPENAME_CTOR
+			     only when '(' follows the type name (see
+			     typename_token_for).  */
+			  pstate->push_new<type_operation> ($1.type);
+			}
 	|	TYPEOF '(' exp ')'
 			{
 			  pstate->wrap<typeof_operation> ();
@@ -534,6 +541,26 @@ msgarg	:	name ':' exp
 			{ add_msglist(0, 0);   }
 	;
 
+exp	:	type_exp '('
+			{ pstate->start_arglist (); }
+		arglist ')'	%prec ARROW
+			{
+			  std::vector<operation_up> args
+			    = pstate->pop_vector (pstate->end_arglist ());
+			  operation_up type_op = pstate->pop ();
+			  pstate->push_new<funcall_operation>
+			    (std::move (type_op), std::move (args));
+			}
+	;
+
+exp	:	type_exp '(' ')'	%prec ARROW
+			{
+			  operation_up type_op = pstate->pop ();
+			  pstate->push_new<funcall_operation>
+			    (std::move (type_op), std::vector<operation_up> ());
+			}
+	;
+
 exp	:	exp '('
 			/* This is to save the value of arglist_len
 			   being accumulated by an outer function call.  */
@@ -3118,6 +3145,34 @@ static int popping;
    built up.  */
 static auto_obstack name_obstack;
 
+/* Return TYPENAME_CTOR only when the next token is '(', so this
+   token is used solely for constructor calls.  Otherwise return TYPENAME.
+   NAME_END is the character just past the name (e.g. yylval.sval.ptr +
+   yylval.sval.length).  */
+
+static int
+typename_token_for (struct parser_state *par_state, struct type *type,
+		    const char *name_end)
+{
+  if (type == nullptr
+      || par_state->language ()->la_language != language_cplus)
+    return TYPENAME;
+  type = check_typedef (type);
+  if (type->code () != TYPE_CODE_STRUCT && type->code () != TYPE_CODE_UNION)
+    return TYPENAME;
+  /* Only return TYPENAME_CTOR when followed by '('.  */
+  if (name_end == nullptr)
+    return TYPENAME;
+  {
+    const char *p = name_end;
+    while (c_isspace (*p))
+      ++p;
+    if (*p != '(')
+      return TYPENAME;
+  }
+  return TYPENAME_CTOR;
+}
+
 /* Classify a NAME token.  The contents of the token are in `yylval'.
    Updates yylval and returns the new token type.  BLOCK is the block
    in which lookups start; this can be NULL to mean the global scope.
@@ -3161,7 +3216,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
 	  if (bsym.symbol != NULL)
 	    {
 	      yylval.tsym.type = bsym.symbol->type ();
-	      return TYPENAME;
+	      return typename_token_for (par_state, yylval.tsym.type,
+					 yylval.sval.ptr + yylval.sval.length);
 	    }
 	}
 
@@ -3188,7 +3244,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
   if (bsym.symbol && bsym.symbol->loc_class () == LOC_TYPEDEF)
     {
       yylval.tsym.type = bsym.symbol->type ();
-      return TYPENAME;
+      return typename_token_for (par_state, yylval.tsym.type,
+				 yylval.sval.ptr + yylval.sval.length);
     }
 
   /* See if it's an ObjC classname.  */
@@ -3274,7 +3331,9 @@ classify_inner_name (struct parser_state *par_state,
       if (base_type != NULL)
 	{
 	  yylval.tsym.type = base_type;
-	  return TYPENAME;
+	  return typename_token_for (par_state, yylval.tsym.type,
+				     yylval.ssym.stoken.ptr
+				     + yylval.ssym.stoken.length);
 	}
 
       return ERROR;
@@ -3294,14 +3353,18 @@ classify_inner_name (struct parser_state *par_state,
 	if (base_type != NULL)
 	  {
 	    yylval.tsym.type = base_type;
-	    return TYPENAME;
+	    return typename_token_for (par_state, yylval.tsym.type,
+				       yylval.ssym.stoken.ptr
+				       + yylval.ssym.stoken.length);
 	  }
       }
       return ERROR;
 
     case LOC_TYPEDEF:
       yylval.tsym.type = yylval.ssym.sym.symbol->type ();
-      return TYPENAME;
+      return typename_token_for (par_state, yylval.tsym.type,
+				 yylval.ssym.stoken.ptr
+				 + yylval.ssym.stoken.length);
 
     default:
       return NAME;
@@ -3336,7 +3399,7 @@ handle_qualified_field_name (qualified_name_token token)
       int kind = classify_inner_name (pstate,
 				      pstate->expression_context_block,
 				      type);
-      if (kind != TYPENAME)
+      if (kind != TYPENAME && kind != TYPENAME_CTOR)
 	error (_("could not find type '%s'"), accum.c_str ());
 
       type = yylval.tsym.type;
@@ -3388,7 +3451,8 @@ yylex (void)
     current.token = classify_name (pstate, pstate->expression_context_block,
 				   is_quoted_name, last_lex_was_structop);
   if (pstate->language ()->la_language != language_cplus
-      || (current.token != TYPENAME && current.token != COLONCOLON
+      || (current.token != TYPENAME && current.token != TYPENAME_CTOR
+	  && current.token != COLONCOLON
 	  && current.token != FILENAME
 	  && (cpstate->assume_classification == TYPE_CODE_UNDEF
 	      || current.token != NAME))
@@ -3434,6 +3498,7 @@ yylex (void)
   else
     {
       gdb_assert (current.token == TYPENAME
+		  || current.token == TYPENAME_CTOR
 		  || cpstate->assume_classification != TYPE_CODE_UNDEF);
       search_block = pstate->expression_context_block;
       obstack_grow (&name_obstack, current.value.sval.ptr,
@@ -3464,7 +3529,8 @@ yylex (void)
 						  context_type);
 	  /* We keep going until we either run out of names, or until
 	     we have a qualified name which is not a type.  */
-	  if (classification != TYPENAME && classification != NAME)
+	  if (classification != TYPENAME && classification != TYPENAME_CTOR
+	      && classification != NAME)
 	    break;
 
 	  /* Accept up to this token.  */
@@ -3595,6 +3661,7 @@ c_print_token (FILE *file, int type, YYSTYPE value)
       break;
 
     case TYPENAME:
+    case TYPENAME_CTOR:
       parser_fprintf (file, "tsym<type=%s, name=%s>",
 		      value.tsym.type->safe_name (),
 		      copy_name (value.tsym.stoken).c_str ());
diff --git a/gdb/eval.c b/gdb/eval.c
index a00774bda3d..933415ce028 100644
--- a/gdb/eval.c
+++ b/gdb/eval.c
@@ -1872,6 +1872,66 @@ type_operation::evaluate (struct type *expect_type, struct expression *exp,
     error (_("Attempt to use a type name as an expression"));
 }
 
+value *
+type_operation::evaluate_funcall (struct type *expect_type,
+				  struct expression *exp,
+				  enum noside noside,
+				  const std::vector<operation_up> &args)
+{
+  struct type *type = std::get<0> (m_storage);
+  type = check_typedef (type);
+
+  /* Constructor-style call Type(args) is only for C++ aggregate types.  */
+  gdb_assert (exp->language_defn->la_language == language_cplus);
+
+  const char *name = type->name ();
+  if (name == nullptr)
+    error (_("Cannot call constructor of unnamed type"));
+
+  /* Get the constructor name from the type name.  */
+  gdb::unique_xmalloc_ptr<char> ctor_name_ptr = cp_func_name (name);
+  const char *ctor_name =
+    (ctor_name_ptr != nullptr) ? ctor_name_ptr.get () : name;
+
+  if (!overload_resolution)
+    error (_("Constructor calls require 'overload-resolution' to be on"));
+
+  std::vector<value *> argvec (1 + args.size ());
+  value *this_ptr;
+  if (noside == EVAL_AVOID_SIDE_EFFECTS)
+    this_ptr = value::zero (lookup_pointer_type (type), lval_memory);
+  else
+    {
+      value *alloc_val = value_allocate_space_in_inferior (type->length ());
+      this_ptr = value_from_pointer (lookup_pointer_type (type),
+				     value_as_long (alloc_val));
+    }
+  argvec[0] = this_ptr;
+  for (size_t i = 0; i < args.size (); ++i)
+    argvec[i + 1] = args[i]->evaluate_with_coercion (exp, noside);
+  gdb::array_view<value *> arg_view = argvec;
+
+  value *callee = nullptr;
+  int static_memfuncp;
+  find_overload_match (arg_view, ctor_name, METHOD,
+		       &argvec[0], nullptr, &callee, nullptr,
+		       &static_memfuncp, 0, noside);
+  /* If find_overload_match fails, it will have already thrown an error.  */
+  gdb_assert (callee != nullptr);
+
+  /* C++ constructors are methods. If the found method is marked static,
+     treat it as an explicit failure until we have a real case.  */
+  if (static_memfuncp)
+    error (_("Constructor %s is unexpectedly marked static"), name);
+
+  if (noside == EVAL_AVOID_SIDE_EFFECTS)
+    return value::zero (type, not_lval);
+
+  evaluate_subexp_do_call (exp, noside, callee, arg_view,
+			   nullptr, expect_type);
+  return value_ind (this_ptr);
+}
+
 }
 
 /* A helper function for BINOP_ASSIGN_MODIFY.  */
diff --git a/gdb/expop.h b/gdb/expop.h
index c58a8d7ac37..9839e3bffa9 100644
--- a/gdb/expop.h
+++ b/gdb/expop.h
@@ -1597,6 +1597,11 @@ class type_operation
 		   struct expression *exp,
 		   enum noside noside) override;
 
+  value *evaluate_funcall (struct type *expect_type,
+			   struct expression *exp,
+			   enum noside noside,
+			   const std::vector<operation_up> &args) override;
+
   enum exp_opcode opcode () const override
   { return OP_TYPE; }
 
diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.cc b/gdb/testsuite/gdb.cp/infcall-ctors.cc
new file mode 100644
index 00000000000..5177f5de5da
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/infcall-ctors.cc
@@ -0,0 +1,141 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright (C) 2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+struct S
+{
+  int x;
+  explicit S (int n = 0) : x (n) {}
+  S operator+ (int n) const
+  {
+    return S (x + n);
+  }
+};
+
+typedef S S_td;
+using S_u = S;
+
+static int
+add (const struct S &s1, const struct S &s2)
+{
+  return s1.x + s2.x;
+}
+
+/* Non-trivially copyable: copy-constructing swaps the two members.  */
+struct swapcopy
+{
+  int lo;
+  int hi;
+  swapcopy (int l, int h) : lo (l), hi (h) {}
+  swapcopy (const swapcopy &o) : lo (o.hi), hi (o.lo) {}
+};
+
+/* Pass swapcopy by value so the call must copy-construct the argument.  */
+
+static int
+swapcopy_first_byval (swapcopy c)
+{
+  return c.lo;
+}
+
+struct Base
+{
+  int x;
+  Base () : x (0) {}
+  explicit Base (int n) : x (n) {}
+  Base (const Base &other) : x (other.x) {}
+};
+
+typedef Base Base_td;
+using Base_u = Base;
+
+namespace NS
+{
+class Derived : public Base
+{
+public:
+  int y;
+  Derived () : Base (), y (0) {}
+  Derived (int a, int b) : Base (a), y (b) {}
+};
+
+typedef Base NsBaseTd;
+using NsBaseU = Base;
+typedef Derived Derived_td;
+using Derived_u = Derived;
+
+union U
+{
+  int a;
+  U () : a (0) {}
+  explicit U (int n) : a (n) {}
+};
+
+typedef U Nu_td;
+}
+
+union U
+{
+  int x;
+  U () : x (0) {}
+  explicit U (int n) : x (n) {}
+};
+
+typedef U U_td;
+
+static int
+plus_one (U u)
+{
+  return u.x + 1;
+}
+
+int
+main (void)
+{
+  S s0;           /* default: x = 0 */
+  S s1 (42);     /* x = 42 */
+  S s2 (s1 + 2); /* x = 44 */
+  S s3 = s1;
+  NS::Derived d;	     /* Base part x=0, Derived part y=0  */
+  NS::Derived d1 (10, 20);   /* Base part x=10, Derived part y=20  */
+  Base b;		     /* x=0  */
+  Base b1 (5);               /* x=5  */
+  Base b2 (d1);		     /* x=10  */
+  U u0;                      /* default: x = 0  */
+  U u1 (42);                 /* x = 42  */
+  NS::U uv0;                /* default: a = 0  */
+  NS::U uv1 (7);            /* a = 7  */
+  S_td s_td = S_td (11);
+  S_u s_u = S_u (12);
+  Base_td b_td = Base_td (8);
+  Base_u b_u = Base_u (9);
+  NS::NsBaseTd nsb_td = NS::NsBaseTd (13);
+  NS::NsBaseU nsb_u = NS::NsBaseU (14);
+  NS::Derived_td d_td = NS::Derived_td (2, 3);
+  NS::Derived_u d_u = NS::Derived_u (4, 5);
+  U_td u_td = U_td (15);
+  NS::Nu_td nu_u = NS::Nu_td (16);
+  swapcopy swp (30, 40);
+  int result = add (s1, s2);
+  return result + d.x + d.y + b.x + b1.x + b2.x + d.x + d.y \
+	+ d1.x + d1.y + plus_one (u0) + u1.x + uv0.a + uv1.a \
+	+ s_td.x + s_u.x + b_td.x + b_u.x + nsb_td.x + nsb_u.x \
+	+ d_td.x + d_td.y + d_u.x + d_u.y + u_td.x + nu_u.a \
+	+ swapcopy_first_byval (swapcopy (100, 200)) \
+	+ swapcopy_first_byval (swp);  /* stop-here */
+}
diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.exp b/gdb/testsuite/gdb.cp/infcall-ctors.exp
new file mode 100644
index 00000000000..2a92266ccfe
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/infcall-ctors.exp
@@ -0,0 +1,198 @@
+# Copyright (C) 2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the gdb testsuite.
+
+# Test constructor calls and casting via inferior function calls:
+# - Simple struct S with constructor (int, default 0).
+# - Base and Derived; cast derived to base, construct Base from Derived.
+# - Typedef and using aliases: ctor resolution must use the class ctor even
+#   when the expression names a typedef or alias (DWARF may name types
+#   differently from the underlying class tag).
+# - swapcopy: two ints with a user-defined copy ctor that swaps them
+#   (non-trivially copyable); pass-by-value in inferior calls must run it.
+# - Constructor-style calls require overload resolution; verify the error
+#   when it is disabled.
+
+require allow_cplus_tests
+
+standard_testfile .cc
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
+    return
+}
+
+if {![runto_main]} {
+    return
+}
+
+# Run to stop-here to ensure all locals are initialized.
+gdb_breakpoint [gdb_get_line_number "stop-here"]
+gdb_continue_to_breakpoint "stop-here"
+
+# Simple tests involving "struct S".
+gdb_test "ptype S" [multi_line \
+    {type = struct S \{} \
+    {    int x;} \
+    "" \
+    {    S\(int\);} \
+    {    S operator\+\(int\) const;} \
+    {\}}]
+
+gdb_test "print s0" " = \\{x = 0\\}" "print s0 default ctor"
+gdb_test "print s1" " = \\{x = 42\\}" "print s1 with 42"
+gdb_test "print S(99)" " = \\{x = 99\\}" "construct S(99) via inferior function call"
+
+gdb_test "ptype swapcopy" [multi_line \
+    {type = struct swapcopy \{} \
+    {    int lo;} \
+    {    int hi;} \
+    "" \
+    {    swapcopy\(int, int\);} \
+    {    swapcopy\(const swapcopy ?&\);} \
+    {\}}]
+gdb_test "print swapcopy(1, 2)" { = \{lo = 1, hi = 2\}} \
+    "construct swapcopy via inferior function call"
+gdb_test {print swapcopy($)} {= \{lo = 2, hi = 1\}} \
+    "call copy ctor on object via history"
+gdb_test "print swapcopy_first_byval(swapcopy(10, 20))" "= 20" \
+    "pass-by-value copy ctor swaps members"
+gdb_test "print swapcopy_first_byval(swp)" "= 40" \
+    "swapcopy local passed by value uses copy ctor"
+
+gdb_test "print S_td(33)" " = \\{x = 33\\}" \
+    "construct S via typedef name (not underlying struct tag)"
+gdb_test "print S_u(34)" " = \\{x = 34\\}" "construct S via using alias"
+gdb_test "print Base_td(8)" { = \{x = 8\}} "construct Base via typedef name"
+gdb_test "print Base_u(9)" { = \{x = 9\}} "construct Base via using alias"
+gdb_test "print NS::NsBaseTd(5)" { = \{x = 5\}} \
+    "construct Base via typedef in namespace"
+gdb_test "print NS::NsBaseU(6)" { = \{x = 6\}} \
+    "construct Base via using alias in namespace"
+gdb_test "print NS::Derived_td(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \
+    "construct NS::Derived via typedef name"
+gdb_test "print NS::Derived_u(3, 4)" { = \{<Base> = \{x = 3\}, y = 4\}} \
+    "construct NS::Derived via using alias"
+gdb_test "print U_td(55)" { = \{x = 55\}} \
+    "construct global union U via typedef name"
+gdb_test "print NS::Nu_td(66)" { = \{a = 66\}} \
+    "construct NS::U via typedef name"
+
+# Tests involving "Base" and "Derived".
+set base_re [multi_line \
+    {type = struct Base \{} \
+    {    int x;} \
+    "" \
+    {    Base\(void\);} \
+    {    Base\(int\);} \
+    {    Base\(const Base ?&\);} \
+    {\}}]
+gdb_test "ptype Base" $base_re
+
+set derived_re [multi_line \
+    {type = class NS::Derived : public Base \{} \
+    {  public:} \
+    {    int y;} \
+    "" \
+    {    Derived\(void\);} \
+    {    Derived\(int, int\);} \
+    {\}}]
+gdb_test "ptype NS::Derived" $derived_re
+
+gdb_test "print d" { = \{<Base> = \{x = 0\}, y = 0\}}
+gdb_test "print d1" { = \{<Base> = \{x = 10\}, y = 20\}}
+gdb_test "print b" { = \{x = 0\}}
+gdb_test "print b1" { = \{x = 5\}}
+gdb_test "print b2" { = \{x = 10\}}
+gdb_test "print (Base)(d1)" { = \{x = 10\}} "cast (Base)(d1) slices to Base"
+gdb_test "print Base(d1)" { = \{x = 10\}} "construct Base(d1) from Derived"
+gdb_test "print Base()" { = \{x = 0\}} "construct Base() default"
+gdb_test "print Base(7)" { = \{x = 7\}} "construct Base(7) with argument"
+gdb_test "print NS::Derived()" { = \{<Base> = \{x = 0\}, y = 0\}} \
+    "construct NS::Derived() via inferior function call"
+gdb_test "print NS::Derived().y" " = 0" "construct NS::Derived() and access .y"
+gdb_test "print NS::Derived(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \
+    "construct NS::Derived(1, 2) via inferior function call"
+gdb_test "print NS::Derived(1, 2).y" " = 2" \
+    "construct NS::Derived(1, 2) and access .y"
+gdb_test "print ((Base)d1).x" " = 10" "cast ((Base)d1).x"
+
+# Print the types of these "temporary" objects.
+gdb_test "ptype Base()" $base_re "ptype of Base temporary"
+gdb_test "ptype NS::Derived(15, 25)" $derived_re \
+	"ptype of NS::Derived temporary"
+
+# Tests involving unions.
+gdb_test "ptype U" [multi_line \
+    {type = union U \{} \
+    {    int x;} \
+    "" \
+    {    U\(void\);} \
+    {    U\(int\);} \
+    {\}}] \
+    "ptype U"
+
+gdb_test "ptype NS::U" [multi_line \
+    {type = union NS::U \{} \
+    {    int a;} \
+    "" \
+    {    U\(void\);} \
+    {    U\(int\);} \
+    {\}}] \
+    "ptype NS::U"
+
+gdb_test "print u0" { = \{x = 0\}}
+gdb_test "print u1" { = \{x = 42\}}
+gdb_test "print uv0" { = \{a = 0\}}
+gdb_test "print uv1" { = \{a = 7\}}
+gdb_test "print U()" { = \{x = 0\}} "construct U() via inferior function call"
+gdb_test "print U(99)" { = \{x = 99\}} "construct U(99) via inferior function call"
+gdb_test "print U(99).x" " = 99" "construct U(99) and access .x"
+gdb_test "print NS::U()" { = \{a = 0\}} \
+    "construct NS::U() via inferior function call"
+gdb_test "print NS::U(13)" { = \{a = 13\}} \
+    "construct NS::U(13) via inferior function call"
+gdb_test "print NS::U(13).a" " = 13" \
+    "construct NS::U(13) and access .a"
+
+set u_re [multi_line \
+    {type = union U \{} \
+    {    int x;} \
+    "" \
+    {    U\(void\);} \
+    {    U\(int\);} \
+    {\}}]
+set ns_u_re [multi_line \
+    {type = union NS::U \{} \
+    {    int a;} \
+    "" \
+    {    U\(void\);} \
+    {    U\(int\);} \
+    {\}}]
+gdb_test "ptype U()" $u_re "ptype of U temporary"
+gdb_test "ptype NS::U(99)" $ns_u_re "ptype of NS::U temporary"
+
+gdb_test "p plus_one(U(42))" "= 43" "temporary in function call"
+gdb_test "p add(S(1), S(20))" "= 21" "add two temporaries of S"
+gdb_test "p plus_one(U(add (S(4), S(6))))" "= 11" \
+    "nested function call using temporaries"
+
+gdb_test_no_output "set overload-resolution off"
+gdb_test "print S(1)" \
+    {Constructor calls require 'overload-resolution' to be on} \
+    "constructor call fails when overload-resolution is off"
+gdb_test_no_output "set overload-resolution on"
+gdb_test "print S(1)" " = \\{x = 1\\}" \
+    "constructor call works after overload-resolution turned back on"
diff --git a/gdb/testsuite/gdb.cp/static-ctor-infcall.c b/gdb/testsuite/gdb.cp/static-ctor-infcall.c
new file mode 100644
index 00000000000..0516196e6d8
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/static-ctor-infcall.c
@@ -0,0 +1,33 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Address range for hand-written DWARF describing a static constructor.  */
+
+extern "C" void
+dwarf_static_ctor_stub (void)
+{
+  asm ("dwarf_static_ctor_stub_label: .globl dwarf_static_ctor_stub_label");
+}
+
+int
+main (void)
+{
+  asm ("main_label: .globl main_label");
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.cp/static-ctor-infcall.exp b/gdb/testsuite/gdb.cp/static-ctor-infcall.exp
new file mode 100644
index 00000000000..cdc2db170d6
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/static-ctor-infcall.exp
@@ -0,0 +1,87 @@
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Exercise the "Constructor %s is unexpectedly marked static" error in
+# type_operation::evaluate_funcall.
+
+load_lib dwarf.exp
+
+require dwarf2_support
+require allow_cplus_tests
+
+standard_testfile .c -dw.S
+
+set asm_file [standard_output_file $srcfile2]
+Dwarf::assemble $asm_file {
+    declare_labels int_label struct_label ptr_label ctor_subr_label
+
+    get_func_info dwarf_static_ctor_stub {nodebug c++}
+
+    cu {} {
+	compile_unit {
+	    DW_AT_language @DW_LANG_C_plus_plus
+	    DW_AT_name dw2-static-ctor-infcall.cc
+	    DW_AT_comp_dir /tmp
+	} {
+	    int_label: base_type {
+		DW_AT_name int
+		DW_AT_encoding @DW_ATE_signed
+		DW_AT_byte_size 4 DW_FORM_sdata
+	    }
+
+	    struct_label: structure_type {
+		DW_AT_name dwarf_static_ctor
+		DW_AT_byte_size 4 DW_FORM_sdata
+	    } {
+		member {
+		    DW_AT_name x
+		    DW_AT_type :$int_label
+		    DW_AT_data_member_location 0 data1
+		}
+		ptr_label: pointer_type {
+		    DW_AT_type :$struct_label
+		}
+		ctor_subr_label: subroutine_type {
+		    DW_AT_type :$int_label
+		} {
+		    formal_parameter {
+			DW_AT_type :$ptr_label
+		    }
+		}
+		subprogram {
+		    DW_AT_name dwarf_static_ctor
+		    DW_AT_external 1 flag
+		    DW_AT_low_pc $dwarf_static_ctor_stub_start addr
+		    DW_AT_high_pc $dwarf_static_ctor_stub_end addr
+		    DW_AT_type :$ctor_subr_label
+		}
+	    }
+	}
+    }
+}
+
+if {[prepare_for_testing "failed to prepare" ${testfile} \
+	 [list $srcfile $asm_file] {nodebug c++}]} {
+    return
+}
+
+if {![runto_main]} {
+    return
+}
+
+gdb_test_no_output "set language c++"
+
+gdb_test "print dwarf_static_ctor()" \
+    "Constructor dwarf_static_ctor is unexpectedly marked static"

base-commit: 8e78d43dbcd122e852d59abe29c8d6aeb6c04925
-- 
2.53.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2] Add infcall support for C++ constructor-style expressions
  2026-04-27 16:43 ` [PATCH v2] " Keith Seitz
@ 2026-06-19 17:21   ` Keith Seitz
  2026-06-27  0:06   ` Kevin Buettner
  1 sibling, 0 replies; 8+ messages in thread
From: Keith Seitz @ 2026-06-19 17:21 UTC (permalink / raw)
  To: gdb-patches

Ping.

On 4/27/26 9:43 AM, Keith Seitz wrote:
> Changes in v2:
> - Formatting fixes
> - Use c_isspace to skip whitespace in typename_token_for()
> - Merged rules for typename_for_ctor to type_exp
>    [This adds ~20 shift/reduce conflicts.]
> - Updated error message when overload-resolution is off
> - Handle static constructors and add new tests for this case
> - Added test case for when ctor is not found
> - Test early return: remove integer return status
>   
> Thanks,
> Keith
> 
> ---------
> 
> This patch adds an initial try at teaching the expression parser/evaluator
> to construct temporary objects requiring construction during an inferior
> function call.
> 
> To accomplish this, I've chosen the route of modifying the parser to
> teach it that `Type(args)' is a function call when `Type' names a
> class/struct/union and is immediately followed by '(', that is, via look-
> ahead). A new parser token and grammar rule have been added to deal
> with this new production.
> 
> The real work is dispatched to `type_operation::evaluate_funcall',
> allocating memory for the temporary and finding the most suitable constructor
> with `find_overload_match'.  It then runs the inferior call, returning
> the newly constructed object.
> 
> I've included many tests covering as many corner cases as I could invent,
> and these tests are clang clean.  They also introduce no regressions on
> x86-64 Fedora 43 with GCC 15.2.1 and RHEL 9.4 with GCC 11.5.0.
> 
> Example:
> Consider a C++ frame where 'struct S { int x; S(int); ... }' is in scope
> 
> Before:
> (gdb) print S(42)
> ❌️ A syntax error in expression, near `10)'.
> 
> After:
> (gdb) print S(42)
> $1 = {x = 42}
> 
> Note that no attempt has been made to deal with templates.  Hopefully
> a follow-on patch can address that.
> ---
>   gdb/NEWS                                     |   5 +
>   gdb/c-exp.y                                  |  85 +++++++-
>   gdb/eval.c                                   |  60 ++++++
>   gdb/expop.h                                  |   5 +
>   gdb/testsuite/gdb.cp/infcall-ctors.cc        | 141 +++++++++++++
>   gdb/testsuite/gdb.cp/infcall-ctors.exp       | 198 +++++++++++++++++++
>   gdb/testsuite/gdb.cp/static-ctor-infcall.c   |  33 ++++
>   gdb/testsuite/gdb.cp/static-ctor-infcall.exp |  87 ++++++++
>   8 files changed, 605 insertions(+), 9 deletions(-)
>   create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.cc
>   create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.exp
>   create mode 100644 gdb/testsuite/gdb.cp/static-ctor-infcall.c
>   create mode 100644 gdb/testsuite/gdb.cp/static-ctor-infcall.exp
> 
> diff --git a/gdb/NEWS b/gdb/NEWS
> index e233906153a..24d26d29ea5 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -75,6 +75,11 @@
>   * The Windows native target now supports scheduler-locking.  E.g.,
>     "set scheduler-locking on" now works.  Previously it gave an error.
>   
> +* In C++ GDB now accepts constructor-style expressions "TYPE (ARGS)"
> +  when TYPE names a class, struct, or union in the current expression
> +  context.  This allows objects to be constructed directly during
> +  expression evaluation.
> +
>   * New targets
>   
>   GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
> diff --git a/gdb/c-exp.y b/gdb/c-exp.y
> index 2829d8bccba..73f88bd84f1 100644
> --- a/gdb/c-exp.y
> +++ b/gdb/c-exp.y
> @@ -231,7 +231,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value);
>   %token <ssym> NAME /* BLOCKNAME defined below to give it higher precedence. */
>   %token <ssym> UNKNOWN_CPP_NAME
>   %token <voidval> COMPLETE
> -%token <tsym> TYPENAME
> +%token <tsym> TYPENAME TYPENAME_CTOR
>   %token <theclass> CLASSNAME	/* ObjC Class name */
>   %type <sval> name
>   %type <qval> qual_field_name field_name field_name_or_complete
> @@ -314,6 +314,13 @@ type_exp:	type
>   			{
>   			  pstate->push_new<type_operation> ($1);
>   			}
> +	|	TYPENAME_CTOR
> +			{
> +			  /* Constructor-style call: lexer returns TYPENAME_CTOR
> +			     only when '(' follows the type name (see
> +			     typename_token_for).  */
> +			  pstate->push_new<type_operation> ($1.type);
> +			}
>   	|	TYPEOF '(' exp ')'
>   			{
>   			  pstate->wrap<typeof_operation> ();
> @@ -534,6 +541,26 @@ msgarg	:	name ':' exp
>   			{ add_msglist(0, 0);   }
>   	;
>   
> +exp	:	type_exp '('
> +			{ pstate->start_arglist (); }
> +		arglist ')'	%prec ARROW
> +			{
> +			  std::vector<operation_up> args
> +			    = pstate->pop_vector (pstate->end_arglist ());
> +			  operation_up type_op = pstate->pop ();
> +			  pstate->push_new<funcall_operation>
> +			    (std::move (type_op), std::move (args));
> +			}
> +	;
> +
> +exp	:	type_exp '(' ')'	%prec ARROW
> +			{
> +			  operation_up type_op = pstate->pop ();
> +			  pstate->push_new<funcall_operation>
> +			    (std::move (type_op), std::vector<operation_up> ());
> +			}
> +	;
> +
>   exp	:	exp '('
>   			/* This is to save the value of arglist_len
>   			   being accumulated by an outer function call.  */
> @@ -3118,6 +3145,34 @@ static int popping;
>      built up.  */
>   static auto_obstack name_obstack;
>   
> +/* Return TYPENAME_CTOR only when the next token is '(', so this
> +   token is used solely for constructor calls.  Otherwise return TYPENAME.
> +   NAME_END is the character just past the name (e.g. yylval.sval.ptr +
> +   yylval.sval.length).  */
> +
> +static int
> +typename_token_for (struct parser_state *par_state, struct type *type,
> +		    const char *name_end)
> +{
> +  if (type == nullptr
> +      || par_state->language ()->la_language != language_cplus)
> +    return TYPENAME;
> +  type = check_typedef (type);
> +  if (type->code () != TYPE_CODE_STRUCT && type->code () != TYPE_CODE_UNION)
> +    return TYPENAME;
> +  /* Only return TYPENAME_CTOR when followed by '('.  */
> +  if (name_end == nullptr)
> +    return TYPENAME;
> +  {
> +    const char *p = name_end;
> +    while (c_isspace (*p))
> +      ++p;
> +    if (*p != '(')
> +      return TYPENAME;
> +  }
> +  return TYPENAME_CTOR;
> +}
> +
>   /* Classify a NAME token.  The contents of the token are in `yylval'.
>      Updates yylval and returns the new token type.  BLOCK is the block
>      in which lookups start; this can be NULL to mean the global scope.
> @@ -3161,7 +3216,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
>   	  if (bsym.symbol != NULL)
>   	    {
>   	      yylval.tsym.type = bsym.symbol->type ();
> -	      return TYPENAME;
> +	      return typename_token_for (par_state, yylval.tsym.type,
> +					 yylval.sval.ptr + yylval.sval.length);
>   	    }
>   	}
>   
> @@ -3188,7 +3244,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
>     if (bsym.symbol && bsym.symbol->loc_class () == LOC_TYPEDEF)
>       {
>         yylval.tsym.type = bsym.symbol->type ();
> -      return TYPENAME;
> +      return typename_token_for (par_state, yylval.tsym.type,
> +				 yylval.sval.ptr + yylval.sval.length);
>       }
>   
>     /* See if it's an ObjC classname.  */
> @@ -3274,7 +3331,9 @@ classify_inner_name (struct parser_state *par_state,
>         if (base_type != NULL)
>   	{
>   	  yylval.tsym.type = base_type;
> -	  return TYPENAME;
> +	  return typename_token_for (par_state, yylval.tsym.type,
> +				     yylval.ssym.stoken.ptr
> +				     + yylval.ssym.stoken.length);
>   	}
>   
>         return ERROR;
> @@ -3294,14 +3353,18 @@ classify_inner_name (struct parser_state *par_state,
>   	if (base_type != NULL)
>   	  {
>   	    yylval.tsym.type = base_type;
> -	    return TYPENAME;
> +	    return typename_token_for (par_state, yylval.tsym.type,
> +				       yylval.ssym.stoken.ptr
> +				       + yylval.ssym.stoken.length);
>   	  }
>         }
>         return ERROR;
>   
>       case LOC_TYPEDEF:
>         yylval.tsym.type = yylval.ssym.sym.symbol->type ();
> -      return TYPENAME;
> +      return typename_token_for (par_state, yylval.tsym.type,
> +				 yylval.ssym.stoken.ptr
> +				 + yylval.ssym.stoken.length);
>   
>       default:
>         return NAME;
> @@ -3336,7 +3399,7 @@ handle_qualified_field_name (qualified_name_token token)
>         int kind = classify_inner_name (pstate,
>   				      pstate->expression_context_block,
>   				      type);
> -      if (kind != TYPENAME)
> +      if (kind != TYPENAME && kind != TYPENAME_CTOR)
>   	error (_("could not find type '%s'"), accum.c_str ());
>   
>         type = yylval.tsym.type;
> @@ -3388,7 +3451,8 @@ yylex (void)
>       current.token = classify_name (pstate, pstate->expression_context_block,
>   				   is_quoted_name, last_lex_was_structop);
>     if (pstate->language ()->la_language != language_cplus
> -      || (current.token != TYPENAME && current.token != COLONCOLON
> +      || (current.token != TYPENAME && current.token != TYPENAME_CTOR
> +	  && current.token != COLONCOLON
>   	  && current.token != FILENAME
>   	  && (cpstate->assume_classification == TYPE_CODE_UNDEF
>   	      || current.token != NAME))
> @@ -3434,6 +3498,7 @@ yylex (void)
>     else
>       {
>         gdb_assert (current.token == TYPENAME
> +		  || current.token == TYPENAME_CTOR
>   		  || cpstate->assume_classification != TYPE_CODE_UNDEF);
>         search_block = pstate->expression_context_block;
>         obstack_grow (&name_obstack, current.value.sval.ptr,
> @@ -3464,7 +3529,8 @@ yylex (void)
>   						  context_type);
>   	  /* We keep going until we either run out of names, or until
>   	     we have a qualified name which is not a type.  */
> -	  if (classification != TYPENAME && classification != NAME)
> +	  if (classification != TYPENAME && classification != TYPENAME_CTOR
> +	      && classification != NAME)
>   	    break;
>   
>   	  /* Accept up to this token.  */
> @@ -3595,6 +3661,7 @@ c_print_token (FILE *file, int type, YYSTYPE value)
>         break;
>   
>       case TYPENAME:
> +    case TYPENAME_CTOR:
>         parser_fprintf (file, "tsym<type=%s, name=%s>",
>   		      value.tsym.type->safe_name (),
>   		      copy_name (value.tsym.stoken).c_str ());
> diff --git a/gdb/eval.c b/gdb/eval.c
> index a00774bda3d..933415ce028 100644
> --- a/gdb/eval.c
> +++ b/gdb/eval.c
> @@ -1872,6 +1872,66 @@ type_operation::evaluate (struct type *expect_type, struct expression *exp,
>       error (_("Attempt to use a type name as an expression"));
>   }
>   
> +value *
> +type_operation::evaluate_funcall (struct type *expect_type,
> +				  struct expression *exp,
> +				  enum noside noside,
> +				  const std::vector<operation_up> &args)
> +{
> +  struct type *type = std::get<0> (m_storage);
> +  type = check_typedef (type);
> +
> +  /* Constructor-style call Type(args) is only for C++ aggregate types.  */
> +  gdb_assert (exp->language_defn->la_language == language_cplus);
> +
> +  const char *name = type->name ();
> +  if (name == nullptr)
> +    error (_("Cannot call constructor of unnamed type"));
> +
> +  /* Get the constructor name from the type name.  */
> +  gdb::unique_xmalloc_ptr<char> ctor_name_ptr = cp_func_name (name);
> +  const char *ctor_name =
> +    (ctor_name_ptr != nullptr) ? ctor_name_ptr.get () : name;
> +
> +  if (!overload_resolution)
> +    error (_("Constructor calls require 'overload-resolution' to be on"));
> +
> +  std::vector<value *> argvec (1 + args.size ());
> +  value *this_ptr;
> +  if (noside == EVAL_AVOID_SIDE_EFFECTS)
> +    this_ptr = value::zero (lookup_pointer_type (type), lval_memory);
> +  else
> +    {
> +      value *alloc_val = value_allocate_space_in_inferior (type->length ());
> +      this_ptr = value_from_pointer (lookup_pointer_type (type),
> +				     value_as_long (alloc_val));
> +    }
> +  argvec[0] = this_ptr;
> +  for (size_t i = 0; i < args.size (); ++i)
> +    argvec[i + 1] = args[i]->evaluate_with_coercion (exp, noside);
> +  gdb::array_view<value *> arg_view = argvec;
> +
> +  value *callee = nullptr;
> +  int static_memfuncp;
> +  find_overload_match (arg_view, ctor_name, METHOD,
> +		       &argvec[0], nullptr, &callee, nullptr,
> +		       &static_memfuncp, 0, noside);
> +  /* If find_overload_match fails, it will have already thrown an error.  */
> +  gdb_assert (callee != nullptr);
> +
> +  /* C++ constructors are methods. If the found method is marked static,
> +     treat it as an explicit failure until we have a real case.  */
> +  if (static_memfuncp)
> +    error (_("Constructor %s is unexpectedly marked static"), name);
> +
> +  if (noside == EVAL_AVOID_SIDE_EFFECTS)
> +    return value::zero (type, not_lval);
> +
> +  evaluate_subexp_do_call (exp, noside, callee, arg_view,
> +			   nullptr, expect_type);
> +  return value_ind (this_ptr);
> +}
> +
>   }
>   
>   /* A helper function for BINOP_ASSIGN_MODIFY.  */
> diff --git a/gdb/expop.h b/gdb/expop.h
> index c58a8d7ac37..9839e3bffa9 100644
> --- a/gdb/expop.h
> +++ b/gdb/expop.h
> @@ -1597,6 +1597,11 @@ class type_operation
>   		   struct expression *exp,
>   		   enum noside noside) override;
>   
> +  value *evaluate_funcall (struct type *expect_type,
> +			   struct expression *exp,
> +			   enum noside noside,
> +			   const std::vector<operation_up> &args) override;
> +
>     enum exp_opcode opcode () const override
>     { return OP_TYPE; }
>   
> diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.cc b/gdb/testsuite/gdb.cp/infcall-ctors.cc
> new file mode 100644
> index 00000000000..5177f5de5da
> --- /dev/null
> +++ b/gdb/testsuite/gdb.cp/infcall-ctors.cc
> @@ -0,0 +1,141 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright (C) 2026 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +struct S
> +{
> +  int x;
> +  explicit S (int n = 0) : x (n) {}
> +  S operator+ (int n) const
> +  {
> +    return S (x + n);
> +  }
> +};
> +
> +typedef S S_td;
> +using S_u = S;
> +
> +static int
> +add (const struct S &s1, const struct S &s2)
> +{
> +  return s1.x + s2.x;
> +}
> +
> +/* Non-trivially copyable: copy-constructing swaps the two members.  */
> +struct swapcopy
> +{
> +  int lo;
> +  int hi;
> +  swapcopy (int l, int h) : lo (l), hi (h) {}
> +  swapcopy (const swapcopy &o) : lo (o.hi), hi (o.lo) {}
> +};
> +
> +/* Pass swapcopy by value so the call must copy-construct the argument.  */
> +
> +static int
> +swapcopy_first_byval (swapcopy c)
> +{
> +  return c.lo;
> +}
> +
> +struct Base
> +{
> +  int x;
> +  Base () : x (0) {}
> +  explicit Base (int n) : x (n) {}
> +  Base (const Base &other) : x (other.x) {}
> +};
> +
> +typedef Base Base_td;
> +using Base_u = Base;
> +
> +namespace NS
> +{
> +class Derived : public Base
> +{
> +public:
> +  int y;
> +  Derived () : Base (), y (0) {}
> +  Derived (int a, int b) : Base (a), y (b) {}
> +};
> +
> +typedef Base NsBaseTd;
> +using NsBaseU = Base;
> +typedef Derived Derived_td;
> +using Derived_u = Derived;
> +
> +union U
> +{
> +  int a;
> +  U () : a (0) {}
> +  explicit U (int n) : a (n) {}
> +};
> +
> +typedef U Nu_td;
> +}
> +
> +union U
> +{
> +  int x;
> +  U () : x (0) {}
> +  explicit U (int n) : x (n) {}
> +};
> +
> +typedef U U_td;
> +
> +static int
> +plus_one (U u)
> +{
> +  return u.x + 1;
> +}
> +
> +int
> +main (void)
> +{
> +  S s0;           /* default: x = 0 */
> +  S s1 (42);     /* x = 42 */
> +  S s2 (s1 + 2); /* x = 44 */
> +  S s3 = s1;
> +  NS::Derived d;	     /* Base part x=0, Derived part y=0  */
> +  NS::Derived d1 (10, 20);   /* Base part x=10, Derived part y=20  */
> +  Base b;		     /* x=0  */
> +  Base b1 (5);               /* x=5  */
> +  Base b2 (d1);		     /* x=10  */
> +  U u0;                      /* default: x = 0  */
> +  U u1 (42);                 /* x = 42  */
> +  NS::U uv0;                /* default: a = 0  */
> +  NS::U uv1 (7);            /* a = 7  */
> +  S_td s_td = S_td (11);
> +  S_u s_u = S_u (12);
> +  Base_td b_td = Base_td (8);
> +  Base_u b_u = Base_u (9);
> +  NS::NsBaseTd nsb_td = NS::NsBaseTd (13);
> +  NS::NsBaseU nsb_u = NS::NsBaseU (14);
> +  NS::Derived_td d_td = NS::Derived_td (2, 3);
> +  NS::Derived_u d_u = NS::Derived_u (4, 5);
> +  U_td u_td = U_td (15);
> +  NS::Nu_td nu_u = NS::Nu_td (16);
> +  swapcopy swp (30, 40);
> +  int result = add (s1, s2);
> +  return result + d.x + d.y + b.x + b1.x + b2.x + d.x + d.y \
> +	+ d1.x + d1.y + plus_one (u0) + u1.x + uv0.a + uv1.a \
> +	+ s_td.x + s_u.x + b_td.x + b_u.x + nsb_td.x + nsb_u.x \
> +	+ d_td.x + d_td.y + d_u.x + d_u.y + u_td.x + nu_u.a \
> +	+ swapcopy_first_byval (swapcopy (100, 200)) \
> +	+ swapcopy_first_byval (swp);  /* stop-here */
> +}
> diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.exp b/gdb/testsuite/gdb.cp/infcall-ctors.exp
> new file mode 100644
> index 00000000000..2a92266ccfe
> --- /dev/null
> +++ b/gdb/testsuite/gdb.cp/infcall-ctors.exp
> @@ -0,0 +1,198 @@
> +# Copyright (C) 2026 Free Software Foundation, Inc.
> +
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> +# This file is part of the gdb testsuite.
> +
> +# Test constructor calls and casting via inferior function calls:
> +# - Simple struct S with constructor (int, default 0).
> +# - Base and Derived; cast derived to base, construct Base from Derived.
> +# - Typedef and using aliases: ctor resolution must use the class ctor even
> +#   when the expression names a typedef or alias (DWARF may name types
> +#   differently from the underlying class tag).
> +# - swapcopy: two ints with a user-defined copy ctor that swaps them
> +#   (non-trivially copyable); pass-by-value in inferior calls must run it.
> +# - Constructor-style calls require overload resolution; verify the error
> +#   when it is disabled.
> +
> +require allow_cplus_tests
> +
> +standard_testfile .cc
> +
> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
> +    return
> +}
> +
> +if {![runto_main]} {
> +    return
> +}
> +
> +# Run to stop-here to ensure all locals are initialized.
> +gdb_breakpoint [gdb_get_line_number "stop-here"]
> +gdb_continue_to_breakpoint "stop-here"
> +
> +# Simple tests involving "struct S".
> +gdb_test "ptype S" [multi_line \
> +    {type = struct S \{} \
> +    {    int x;} \
> +    "" \
> +    {    S\(int\);} \
> +    {    S operator\+\(int\) const;} \
> +    {\}}]
> +
> +gdb_test "print s0" " = \\{x = 0\\}" "print s0 default ctor"
> +gdb_test "print s1" " = \\{x = 42\\}" "print s1 with 42"
> +gdb_test "print S(99)" " = \\{x = 99\\}" "construct S(99) via inferior function call"
> +
> +gdb_test "ptype swapcopy" [multi_line \
> +    {type = struct swapcopy \{} \
> +    {    int lo;} \
> +    {    int hi;} \
> +    "" \
> +    {    swapcopy\(int, int\);} \
> +    {    swapcopy\(const swapcopy ?&\);} \
> +    {\}}]
> +gdb_test "print swapcopy(1, 2)" { = \{lo = 1, hi = 2\}} \
> +    "construct swapcopy via inferior function call"
> +gdb_test {print swapcopy($)} {= \{lo = 2, hi = 1\}} \
> +    "call copy ctor on object via history"
> +gdb_test "print swapcopy_first_byval(swapcopy(10, 20))" "= 20" \
> +    "pass-by-value copy ctor swaps members"
> +gdb_test "print swapcopy_first_byval(swp)" "= 40" \
> +    "swapcopy local passed by value uses copy ctor"
> +
> +gdb_test "print S_td(33)" " = \\{x = 33\\}" \
> +    "construct S via typedef name (not underlying struct tag)"
> +gdb_test "print S_u(34)" " = \\{x = 34\\}" "construct S via using alias"
> +gdb_test "print Base_td(8)" { = \{x = 8\}} "construct Base via typedef name"
> +gdb_test "print Base_u(9)" { = \{x = 9\}} "construct Base via using alias"
> +gdb_test "print NS::NsBaseTd(5)" { = \{x = 5\}} \
> +    "construct Base via typedef in namespace"
> +gdb_test "print NS::NsBaseU(6)" { = \{x = 6\}} \
> +    "construct Base via using alias in namespace"
> +gdb_test "print NS::Derived_td(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \
> +    "construct NS::Derived via typedef name"
> +gdb_test "print NS::Derived_u(3, 4)" { = \{<Base> = \{x = 3\}, y = 4\}} \
> +    "construct NS::Derived via using alias"
> +gdb_test "print U_td(55)" { = \{x = 55\}} \
> +    "construct global union U via typedef name"
> +gdb_test "print NS::Nu_td(66)" { = \{a = 66\}} \
> +    "construct NS::U via typedef name"
> +
> +# Tests involving "Base" and "Derived".
> +set base_re [multi_line \
> +    {type = struct Base \{} \
> +    {    int x;} \
> +    "" \
> +    {    Base\(void\);} \
> +    {    Base\(int\);} \
> +    {    Base\(const Base ?&\);} \
> +    {\}}]
> +gdb_test "ptype Base" $base_re
> +
> +set derived_re [multi_line \
> +    {type = class NS::Derived : public Base \{} \
> +    {  public:} \
> +    {    int y;} \
> +    "" \
> +    {    Derived\(void\);} \
> +    {    Derived\(int, int\);} \
> +    {\}}]
> +gdb_test "ptype NS::Derived" $derived_re
> +
> +gdb_test "print d" { = \{<Base> = \{x = 0\}, y = 0\}}
> +gdb_test "print d1" { = \{<Base> = \{x = 10\}, y = 20\}}
> +gdb_test "print b" { = \{x = 0\}}
> +gdb_test "print b1" { = \{x = 5\}}
> +gdb_test "print b2" { = \{x = 10\}}
> +gdb_test "print (Base)(d1)" { = \{x = 10\}} "cast (Base)(d1) slices to Base"
> +gdb_test "print Base(d1)" { = \{x = 10\}} "construct Base(d1) from Derived"
> +gdb_test "print Base()" { = \{x = 0\}} "construct Base() default"
> +gdb_test "print Base(7)" { = \{x = 7\}} "construct Base(7) with argument"
> +gdb_test "print NS::Derived()" { = \{<Base> = \{x = 0\}, y = 0\}} \
> +    "construct NS::Derived() via inferior function call"
> +gdb_test "print NS::Derived().y" " = 0" "construct NS::Derived() and access .y"
> +gdb_test "print NS::Derived(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \
> +    "construct NS::Derived(1, 2) via inferior function call"
> +gdb_test "print NS::Derived(1, 2).y" " = 2" \
> +    "construct NS::Derived(1, 2) and access .y"
> +gdb_test "print ((Base)d1).x" " = 10" "cast ((Base)d1).x"
> +
> +# Print the types of these "temporary" objects.
> +gdb_test "ptype Base()" $base_re "ptype of Base temporary"
> +gdb_test "ptype NS::Derived(15, 25)" $derived_re \
> +	"ptype of NS::Derived temporary"
> +
> +# Tests involving unions.
> +gdb_test "ptype U" [multi_line \
> +    {type = union U \{} \
> +    {    int x;} \
> +    "" \
> +    {    U\(void\);} \
> +    {    U\(int\);} \
> +    {\}}] \
> +    "ptype U"
> +
> +gdb_test "ptype NS::U" [multi_line \
> +    {type = union NS::U \{} \
> +    {    int a;} \
> +    "" \
> +    {    U\(void\);} \
> +    {    U\(int\);} \
> +    {\}}] \
> +    "ptype NS::U"
> +
> +gdb_test "print u0" { = \{x = 0\}}
> +gdb_test "print u1" { = \{x = 42\}}
> +gdb_test "print uv0" { = \{a = 0\}}
> +gdb_test "print uv1" { = \{a = 7\}}
> +gdb_test "print U()" { = \{x = 0\}} "construct U() via inferior function call"
> +gdb_test "print U(99)" { = \{x = 99\}} "construct U(99) via inferior function call"
> +gdb_test "print U(99).x" " = 99" "construct U(99) and access .x"
> +gdb_test "print NS::U()" { = \{a = 0\}} \
> +    "construct NS::U() via inferior function call"
> +gdb_test "print NS::U(13)" { = \{a = 13\}} \
> +    "construct NS::U(13) via inferior function call"
> +gdb_test "print NS::U(13).a" " = 13" \
> +    "construct NS::U(13) and access .a"
> +
> +set u_re [multi_line \
> +    {type = union U \{} \
> +    {    int x;} \
> +    "" \
> +    {    U\(void\);} \
> +    {    U\(int\);} \
> +    {\}}]
> +set ns_u_re [multi_line \
> +    {type = union NS::U \{} \
> +    {    int a;} \
> +    "" \
> +    {    U\(void\);} \
> +    {    U\(int\);} \
> +    {\}}]
> +gdb_test "ptype U()" $u_re "ptype of U temporary"
> +gdb_test "ptype NS::U(99)" $ns_u_re "ptype of NS::U temporary"
> +
> +gdb_test "p plus_one(U(42))" "= 43" "temporary in function call"
> +gdb_test "p add(S(1), S(20))" "= 21" "add two temporaries of S"
> +gdb_test "p plus_one(U(add (S(4), S(6))))" "= 11" \
> +    "nested function call using temporaries"
> +
> +gdb_test_no_output "set overload-resolution off"
> +gdb_test "print S(1)" \
> +    {Constructor calls require 'overload-resolution' to be on} \
> +    "constructor call fails when overload-resolution is off"
> +gdb_test_no_output "set overload-resolution on"
> +gdb_test "print S(1)" " = \\{x = 1\\}" \
> +    "constructor call works after overload-resolution turned back on"
> diff --git a/gdb/testsuite/gdb.cp/static-ctor-infcall.c b/gdb/testsuite/gdb.cp/static-ctor-infcall.c
> new file mode 100644
> index 00000000000..0516196e6d8
> --- /dev/null
> +++ b/gdb/testsuite/gdb.cp/static-ctor-infcall.c
> @@ -0,0 +1,33 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2026 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   This program is free software; you can redistribute it and/or modify
> +   it under the terms of the GNU General Public License as published by
> +   the Free Software Foundation; either version 3 of the License, or
> +   (at your option) any later version.
> +
> +   This program is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +   GNU General Public License for more details.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +/* Address range for hand-written DWARF describing a static constructor.  */
> +
> +extern "C" void
> +dwarf_static_ctor_stub (void)
> +{
> +  asm ("dwarf_static_ctor_stub_label: .globl dwarf_static_ctor_stub_label");
> +}
> +
> +int
> +main (void)
> +{
> +  asm ("main_label: .globl main_label");
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.cp/static-ctor-infcall.exp b/gdb/testsuite/gdb.cp/static-ctor-infcall.exp
> new file mode 100644
> index 00000000000..cdc2db170d6
> --- /dev/null
> +++ b/gdb/testsuite/gdb.cp/static-ctor-infcall.exp
> @@ -0,0 +1,87 @@
> +# Copyright 2026 Free Software Foundation, Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 3 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +#
> +# Exercise the "Constructor %s is unexpectedly marked static" error in
> +# type_operation::evaluate_funcall.
> +
> +load_lib dwarf.exp
> +
> +require dwarf2_support
> +require allow_cplus_tests
> +
> +standard_testfile .c -dw.S
> +
> +set asm_file [standard_output_file $srcfile2]
> +Dwarf::assemble $asm_file {
> +    declare_labels int_label struct_label ptr_label ctor_subr_label
> +
> +    get_func_info dwarf_static_ctor_stub {nodebug c++}
> +
> +    cu {} {
> +	compile_unit {
> +	    DW_AT_language @DW_LANG_C_plus_plus
> +	    DW_AT_name dw2-static-ctor-infcall.cc
> +	    DW_AT_comp_dir /tmp
> +	} {
> +	    int_label: base_type {
> +		DW_AT_name int
> +		DW_AT_encoding @DW_ATE_signed
> +		DW_AT_byte_size 4 DW_FORM_sdata
> +	    }
> +
> +	    struct_label: structure_type {
> +		DW_AT_name dwarf_static_ctor
> +		DW_AT_byte_size 4 DW_FORM_sdata
> +	    } {
> +		member {
> +		    DW_AT_name x
> +		    DW_AT_type :$int_label
> +		    DW_AT_data_member_location 0 data1
> +		}
> +		ptr_label: pointer_type {
> +		    DW_AT_type :$struct_label
> +		}
> +		ctor_subr_label: subroutine_type {
> +		    DW_AT_type :$int_label
> +		} {
> +		    formal_parameter {
> +			DW_AT_type :$ptr_label
> +		    }
> +		}
> +		subprogram {
> +		    DW_AT_name dwarf_static_ctor
> +		    DW_AT_external 1 flag
> +		    DW_AT_low_pc $dwarf_static_ctor_stub_start addr
> +		    DW_AT_high_pc $dwarf_static_ctor_stub_end addr
> +		    DW_AT_type :$ctor_subr_label
> +		}
> +	    }
> +	}
> +    }
> +}
> +
> +if {[prepare_for_testing "failed to prepare" ${testfile} \
> +	 [list $srcfile $asm_file] {nodebug c++}]} {
> +    return
> +}
> +
> +if {![runto_main]} {
> +    return
> +}
> +
> +gdb_test_no_output "set language c++"
> +
> +gdb_test "print dwarf_static_ctor()" \
> +    "Constructor dwarf_static_ctor is unexpectedly marked static"
> 
> base-commit: 8e78d43dbcd122e852d59abe29c8d6aeb6c04925


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2] Add infcall support for C++ constructor-style expressions
  2026-04-27 16:43 ` [PATCH v2] " Keith Seitz
  2026-06-19 17:21   ` Keith Seitz
@ 2026-06-27  0:06   ` Kevin Buettner
  2026-07-29 17:44     ` Keith Seitz
  1 sibling, 1 reply; 8+ messages in thread
From: Kevin Buettner @ 2026-06-27  0:06 UTC (permalink / raw)
  To: Keith Seitz; +Cc: gdb-patches

Hi Keith,

On Mon, 27 Apr 2026 09:43:08 -0700
Keith Seitz <keiths@redhat.com> wrote:

> Changes in v2:
> - Formatting fixes
> - Use c_isspace to skip whitespace in typename_token_for()
> - Merged rules for typename_for_ctor to type_exp
>   [This adds ~20 shift/reduce conflicts.]
> - Updated error message when overload-resolution is off
> - Handle static constructors and add new tests for this case
> - Added test case for when ctor is not found
> - Test early return: remove integer return status

Claude found this problem...

Using this two line program, which it named /tmp/test_c.c:

struct S { int x; };
int main() { struct S s = {42}; return s.x; }

...do:

gcc -g -o /tmp/test_c /tmp/test_c.c

Then:

kevinb-claude@claude-1:bin$ ./gdb -q
(gdb) file /tmp/test_c
Reading symbols from /tmp/test_c...
(gdb) start
Temporary breakpoint 1 at 0x40044a: file /tmp/test_c.c, line 2.
Starting program: /tmp/test_c 

This GDB supports auto-downloading debuginfo from the following URLs:
  <ima:enforcing>
  <https://debuginfod.fedoraproject.org/>
  <ima:ignore>
Enable debuginfod for this session? (y or [n]) n
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Temporary breakpoint 1, main () at /tmp/test_c.c:2
2	int main() { struct S s = {42}; return s.x; }
(gdb) print typeof(struct S)(42)
/home/kevinb-claude/binutils-gdb-patch-review/gdb/eval.c:1885: internal-error: evaluate_funcall: Assertion `exp->language_defn->la_language == language_cplus' failed.
A problem internal to GDB has been detected,
further debugging may prove unreliable.
----- Backtrace -----
0x51e699 gdb_internal_backtrace_1
	/home/kevinb-claude/binutils-gdb-patch-review/gdb/bt-utils.c:122
0x51e699 _Z22gdb_internal_backtracev
	/home/kevinb-claude/binutils-gdb-patch-review/gdb/bt-utils.c:173
0x9fb874 internal_vproblem
	/home/kevinb-claude/binutils-gdb-patch-review/gdb/utils.c:434
0x9fbd38 _Z15internal_verrorPKciS0_P13__va_list_tag
	/home/kevinb-claude/binutils-gdb-patch-review/gdb/utils.c:514
0xb6cea1 _Z18internal_error_locPKciS0_z
	/home/kevinb-claude/binutils-gdb-patch-review/gdbsupport/errors.cc:57
0x67e31c _ZN4expr14type_operation16evaluate_funcallEP4typeP10expression6nosideRKSt6vectorISt10unique_ptrINS_9operationESt14default_deleteIS8_EESaISB_EE
...


Claude also complained about the 20 new shift/reduce conflicts.  It
recommends reverting to a dedicated non-terminal for TYPENAME_CTOR
instead of routing through type_exp.  (It showed some code too, but
I don't want to taint your contribution by showing you AI generated
code.)  Claude says that, if done right, that change could fix both problems
at once.

Kevin


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2] Add infcall support for C++ constructor-style expressions
  2026-06-27  0:06   ` Kevin Buettner
@ 2026-07-29 17:44     ` Keith Seitz
  0 siblings, 0 replies; 8+ messages in thread
From: Keith Seitz @ 2026-07-29 17:44 UTC (permalink / raw)
  To: Kevin Buettner; +Cc: gdb-patches

Hi,

On 6/26/26 5:06 PM, Kevin Buettner wrote:
> 
> Claude found this problem...
> > (gdb) file /tmp/test_c
> Reading symbols from /tmp/test_c...
> (gdb) start
> Temporary breakpoint 1 at 0x40044a: file /tmp/test_c.c, line 2.
> Starting program: /tmp/test_c
> 
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/lib64/libthread_db.so.1".
> 
> Temporary breakpoint 1, main () at /tmp/test_c.c:2
> 2	int main() { struct S s = {42}; return s.x; }
> (gdb) print typeof(struct S)(42)
> /home/kevinb-claude/binutils-gdb-patch-review/gdb/eval.c:1885: internal-error: evaluate_funcall: Assertion `exp->language_defn->la_language == language_cplus' failed.
> A problem internal to GDB has been detected,
> further debugging may prove unreliable.
> ----- Backtrace -----
> 0x51e699 gdb_internal_backtrace_1
> 	/home/kevinb-claude/binutils-gdb-patch-review/gdb/bt-utils.c:122
> 0x51e699 _Z22gdb_internal_backtracev
> 	/home/kevinb-claude/binutils-gdb-patch-review/gdb/bt-utils.c:173
> 0x9fb874 internal_vproblem
> 	/home/kevinb-claude/binutils-gdb-patch-review/gdb/utils.c:434
> 0x9fbd38 _Z15internal_verrorPKciS0_P13__va_list_tag
> 	/home/kevinb-claude/binutils-gdb-patch-review/gdb/utils.c:514
> 0xb6cea1 _Z18internal_error_locPKciS0_z
> 	/home/kevinb-claude/binutils-gdb-patch-review/gdbsupport/errors.cc:57
> 0x67e31c _ZN4expr14type_operation16evaluate_funcallEP4typeP10expression6nosideRKSt6vectorISt10unique_ptrINS_9operationESt14default_deleteIS8_EESaISB_EE
> ...
> 

Thanks for the report.

The root cause is that the C++-only gate lived only in the lexer, but
`typeof(T)` is a separate `type_exp` production. So in C, `typeof(struct
S)(42)` still matched the general `type_exp '(' arglist ')'` rules,
built a `funcall_operation`, and hit the `language_cplus` assert in
`type_operation::evaluate_funcall`.

The fix is to mirror the lexer gate in those parser actions: if the
language is not C++, throw an error:

(gdb) print typeof(struct S)(42)
Constructor-style casts are only valid in C++

I left the `type_exp`-based productions in place rather than splitting
out a dedicated ctor non-terminal; the language check closes the hole
Claude found without that larger grammar change. I'm happy to revisit
the shift/reduce side separately if consensus is reached. [This is the
direction a previous review steered me, and I am happy to revisit that
initial review recommendation.]

Thank you,
Keith


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-07-29 17:44 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-26 19:17 [PATCH] Add infcall support for C++ constructor-style expressions Keith Seitz
2026-03-27  5:50 ` Eli Zaretskii
2026-04-21 13:41 ` Andrew Burgess
2026-04-21 18:13   ` Keith Seitz
2026-04-27 16:43 ` [PATCH v2] " Keith Seitz
2026-06-19 17:21   ` Keith Seitz
2026-06-27  0:06   ` Kevin Buettner
2026-07-29 17:44     ` Keith Seitz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox