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; 11+ 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] 11+ 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; 11+ 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] 11+ 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; 11+ 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] 11+ 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; 11+ 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] 11+ 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
                     ` (2 more replies)
  2 siblings, 3 replies; 11+ 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] 11+ 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-09-17 15:10   ` Andrew Burgess
  2 siblings, 0 replies; 11+ 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] 11+ 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
  2026-09-17 15:10   ` Andrew Burgess
  2 siblings, 1 reply; 11+ 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] 11+ 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
  2026-09-17 14:41       ` Andrew Burgess
  0 siblings, 1 reply; 11+ 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] 11+ messages in thread

* Re: [PATCH v2] Add infcall support for C++ constructor-style expressions
  2026-07-29 17:44     ` Keith Seitz
@ 2026-09-17 14:41       ` Andrew Burgess
  2026-09-17 16:05         ` Keith Seitz
  0 siblings, 1 reply; 11+ messages in thread
From: Andrew Burgess @ 2026-09-17 14:41 UTC (permalink / raw)
  To: Keith Seitz, Kevin Buettner; +Cc: gdb-patches

Keith Seitz <keiths@redhat.com> writes:

> 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.]

If that was my review, then please do feel free to ignore it.  My review
comments are not always based on tested code, usually just my thoughts
when looking through the patch, that doesn't always mean the thoughts
are any good :)

Thanks,
Andrew


^ permalink raw reply	[flat|nested] 11+ 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-09-17 15:10   ` Andrew Burgess
  2 siblings, 0 replies; 11+ messages in thread
From: Andrew Burgess @ 2026-09-17 15:10 UTC (permalink / raw)
  To: Keith Seitz, gdb-patches

Keith Seitz <keiths@redhat.com> writes:

> 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
>
> ---------

I've only got trivial things to add over Kevin's existing review.

>
> 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)'.

I suspect a typo here, the error says '10', but the input is '42'.

> 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)"

Need a command after 'In C++' here.

> 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 =

This trailing '=' should be moved to the start of the next line.

Thanks,
Andrew


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

* Re: [PATCH v2] Add infcall support for C++ constructor-style expressions
  2026-09-17 14:41       ` Andrew Burgess
@ 2026-09-17 16:05         ` Keith Seitz
  0 siblings, 0 replies; 11+ messages in thread
From: Keith Seitz @ 2026-09-17 16:05 UTC (permalink / raw)
  To: Andrew Burgess, Kevin Buettner; +Cc: gdb-patches

On 9/17/26 7:41 AM, Andrew Burgess wrote:
> 
> If that was my review, then please do feel free to ignore it.  My review
> comments are not always based on tested code, usually just my thoughts
> when looking through the patch, that doesn't always mean the thoughts
> are any good :)

In order to solve the additional SR conflicts, I've had to redesign
a good chunk of the patch. It's been a struggle, but I am finally
testing a new patch which I think will cover all the bases.

Thanks for the review.

Keith


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

end of thread, other threads:[~2026-09-17 16:06 UTC | newest]

Thread overview: 11+ 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
2026-09-17 14:41       ` Andrew Burgess
2026-09-17 16:05         ` Keith Seitz
2026-09-17 15:10   ` Andrew Burgess

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