* [PATCH] Add infcall support for C++ constructor-style expressions
@ 2026-03-26 19:17 Keith Seitz
2026-03-27 5:50 ` Eli Zaretskii
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Keith Seitz @ 2026-03-26 19:17 UTC (permalink / raw)
To: gdb-patches
This patch adds an initial try at teaching the expression parser/evaluator
to construct temporary objects requiring construction during an inferior
function call.
To accomplish this, I've chosen the route of modifying the parser to
teach it that `Type(args)' is a function call when `Type' names a
class/struct/union and is immediately followed by '(', that is, via look-
ahead). A new parser token and grammar rule have been added to deal
with this new production.
The real work is dispatched to `type_operation::evaluate_funcall',
allocating memory for the temporary and finding the most suitable constructor
with `find_overload_match'. It then runs the inferior call, returning
the newly constructed object.
I've included many tests covering as many corner cases as I could invent,
and these tests are clang clean. They also introduce no regressions on
x86-64 Fedora 43 with GCC 15.2.1 and RHEL 9.4 with GCC 11.5.0.
Example:
Consider a C++ frame where 'struct S { int x; S(int); ... }' is in scope
Before:
(gdb) print S(42)
❌️ A syntax error in expression, near `10)'.
After:
(gdb) print S(42)
$1 = {x = 42}
Note that no attempt has been made to deal with templates. Hopefully
a follow-on patch can address that.
---
gdb/NEWS | 5 +
gdb/c-exp.y | 86 +++++++++--
gdb/eval.c | 55 ++++++++
gdb/expop.h | 5 +
gdb/testsuite/gdb.cp/infcall-ctors.cc | 128 +++++++++++++++++
gdb/testsuite/gdb.cp/infcall-ctors.exp | 188 +++++++++++++++++++++++++
6 files changed, 457 insertions(+), 10 deletions(-)
create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.cc
create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.exp
diff --git a/gdb/NEWS b/gdb/NEWS
index 03f46df5400..c04727d342d 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -66,6 +66,11 @@
automatically set to UTF-8. (Users can use the Windows 'chcp'
command to change the output codepage of the console.)
+* In C++ GDB now accepts constructor-style expressions "TYPE (ARGS)"
+ when TYPE names a class, struct, or union in the current expression
+ context. This allows objects to be constructed directly during
+ expression evalution.
+
* New targets
GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux*
diff --git a/gdb/c-exp.y b/gdb/c-exp.y
index a4a910df712..e17ee2d3c12 100644
--- a/gdb/c-exp.y
+++ b/gdb/c-exp.y
@@ -199,7 +199,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value);
#endif
%}
-%type <voidval> exp exp1 type_exp start variable qualified_name lcurly function_method
+%type <voidval> exp exp1 type_exp start variable qualified_name lcurly function_method typename_for_ctor
%type <lval> rcurly
%type <tval> type typebase scalar_type tag_name_or_complete
%type <tvec> nonempty_typelist func_mod parameter_typelist
@@ -230,7 +230,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value);
%token <ssym> NAME /* BLOCKNAME defined below to give it higher precedence. */
%token <ssym> UNKNOWN_CPP_NAME
%token <voidval> COMPLETE
-%token <tsym> TYPENAME
+%token <tsym> TYPENAME TYPENAME_CTOR
%token <theclass> CLASSNAME /* ObjC Class name */
%type <sval> name
%type <qval> qual_field_name field_name field_name_or_complete
@@ -533,6 +533,26 @@ msgarg : name ':' exp
{ add_msglist(0, 0); }
;
+exp : typename_for_ctor '('
+ { pstate->start_arglist (); }
+ arglist ')' %prec ARROW
+ {
+ std::vector<operation_up> args
+ = pstate->pop_vector (pstate->end_arglist ());
+ operation_up type_op = pstate->pop ();
+ pstate->push_new<funcall_operation>
+ (std::move (type_op), std::move (args));
+ }
+ ;
+
+exp : typename_for_ctor '(' ')' %prec ARROW
+ {
+ operation_up type_op = pstate->pop ();
+ pstate->push_new<funcall_operation>
+ (std::move (type_op), std::vector<operation_up> ());
+ }
+ ;
+
exp : exp '('
/* This is to save the value of arglist_len
being accumulated by an outer function call. */
@@ -1471,6 +1491,12 @@ scalar_type:
"int"); }
;
+/* Constructor-style calls. */
+typename_for_ctor
+ : TYPENAME_CTOR
+ { pstate->push_new<type_operation> ($1.type); }
+ ;
+
/* Implements (approximately): (type-qualifier)* type-specifier.
When type-specifier is only ever a single word, like 'float' then these
@@ -3114,6 +3140,34 @@ static int popping;
built up. */
static auto_obstack name_obstack;
+/* Return TYPENAME_CTOR only when the next token is '(', so this
+ token is used solely for constructor calls. Otherwise return TYPENAME.
+ NAME_END is the character just past the name (e.g. yylval.sval.ptr +
+ yylval.sval.length). */
+
+static int
+typename_token_for (struct parser_state *par_state, struct type *type,
+ const char *name_end)
+{
+ if (type == nullptr
+ || par_state->language ()->la_language != language_cplus)
+ return TYPENAME;
+ type = check_typedef (type);
+ if (type->code () != TYPE_CODE_STRUCT && type->code () != TYPE_CODE_UNION)
+ return TYPENAME;
+ /* Only return TYPENAME_CTOR when followed by '('. */
+ if (name_end == nullptr)
+ return TYPENAME;
+ {
+ const char *p = name_end;
+ while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
+ ++p;
+ if (*p != '(')
+ return TYPENAME;
+ }
+ return TYPENAME_CTOR;
+}
+
/* Classify a NAME token. The contents of the token are in `yylval'.
Updates yylval and returns the new token type. BLOCK is the block
in which lookups start; this can be NULL to mean the global scope.
@@ -3157,7 +3211,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
if (bsym.symbol != NULL)
{
yylval.tsym.type = bsym.symbol->type ();
- return TYPENAME;
+ return typename_token_for (par_state, yylval.tsym.type,
+ yylval.sval.ptr + yylval.sval.length);
}
}
@@ -3184,7 +3239,8 @@ classify_name (struct parser_state *par_state, const struct block *block,
if (bsym.symbol && bsym.symbol->loc_class () == LOC_TYPEDEF)
{
yylval.tsym.type = bsym.symbol->type ();
- return TYPENAME;
+ return typename_token_for (par_state, yylval.tsym.type,
+ yylval.sval.ptr + yylval.sval.length);
}
/* See if it's an ObjC classname. */
@@ -3270,7 +3326,9 @@ classify_inner_name (struct parser_state *par_state,
if (base_type != NULL)
{
yylval.tsym.type = base_type;
- return TYPENAME;
+ return typename_token_for (par_state, yylval.tsym.type,
+ yylval.ssym.stoken.ptr
+ + yylval.ssym.stoken.length);
}
return ERROR;
@@ -3290,14 +3348,18 @@ classify_inner_name (struct parser_state *par_state,
if (base_type != NULL)
{
yylval.tsym.type = base_type;
- return TYPENAME;
+ return typename_token_for (par_state, yylval.tsym.type,
+ yylval.ssym.stoken.ptr
+ + yylval.ssym.stoken.length);
}
}
return ERROR;
case LOC_TYPEDEF:
yylval.tsym.type = yylval.ssym.sym.symbol->type ();
- return TYPENAME;
+ return typename_token_for (par_state, yylval.tsym.type,
+ yylval.ssym.stoken.ptr
+ + yylval.ssym.stoken.length);
default:
return NAME;
@@ -3332,7 +3394,7 @@ handle_qualified_field_name (qualified_name_token token)
int kind = classify_inner_name (pstate,
pstate->expression_context_block,
type);
- if (kind != TYPENAME)
+ if (kind != TYPENAME && kind != TYPENAME_CTOR)
error (_("could not find type '%s'"), accum.c_str ());
type = yylval.tsym.type;
@@ -3384,7 +3446,8 @@ yylex (void)
current.token = classify_name (pstate, pstate->expression_context_block,
is_quoted_name, last_lex_was_structop);
if (pstate->language ()->la_language != language_cplus
- || (current.token != TYPENAME && current.token != COLONCOLON
+ || (current.token != TYPENAME && current.token != TYPENAME_CTOR
+ && current.token != COLONCOLON
&& current.token != FILENAME
&& (cpstate->assume_classification == TYPE_CODE_UNDEF
|| current.token != NAME))
@@ -3430,6 +3493,7 @@ yylex (void)
else
{
gdb_assert (current.token == TYPENAME
+ || current.token == TYPENAME_CTOR
|| cpstate->assume_classification != TYPE_CODE_UNDEF);
search_block = pstate->expression_context_block;
obstack_grow (&name_obstack, current.value.sval.ptr,
@@ -3460,7 +3524,8 @@ yylex (void)
context_type);
/* We keep going until we either run out of names, or until
we have a qualified name which is not a type. */
- if (classification != TYPENAME && classification != NAME)
+ if (classification != TYPENAME && classification != TYPENAME_CTOR
+ && classification != NAME)
break;
/* Accept up to this token. */
@@ -3591,6 +3656,7 @@ c_print_token (FILE *file, int type, YYSTYPE value)
break;
case TYPENAME:
+ case TYPENAME_CTOR:
parser_fprintf (file, "tsym<type=%s, name=%s>",
value.tsym.type->safe_name (),
copy_name (value.tsym.stoken).c_str ());
diff --git a/gdb/eval.c b/gdb/eval.c
index 7beff554ed4..e988b954059 100644
--- a/gdb/eval.c
+++ b/gdb/eval.c
@@ -1869,6 +1869,61 @@ type_operation::evaluate (struct type *expect_type, struct expression *exp,
error (_("Attempt to use a type name as an expression"));
}
+value *
+type_operation::evaluate_funcall (struct type *expect_type,
+ struct expression *exp,
+ enum noside noside,
+ const std::vector<operation_up> &args)
+{
+ struct type *type = std::get<0> (m_storage);
+ type = check_typedef (type);
+
+ /* Constructor-style call Type(args) is only for C++ aggregate types. */
+ gdb_assert (exp->language_defn->la_language == language_cplus);
+
+ const char *name = type->name ();
+ if (name == nullptr)
+ error (_("Cannot call constructor of unnamed type"));
+
+ /* Get the constructor name from the type name. */
+ gdb::unique_xmalloc_ptr<char> ctor_name_ptr = cp_func_name (name);
+ const char *ctor_name = (ctor_name_ptr != nullptr) ? ctor_name_ptr.get () : name;
+
+ if (!overload_resolution)
+ return operation::evaluate_funcall (expect_type, exp, noside, args);
+
+ std::vector<value *> argvec (1 + args.size ());
+ value *this_ptr;
+ if (noside == EVAL_AVOID_SIDE_EFFECTS)
+ this_ptr = value::zero (lookup_pointer_type (type), lval_memory);
+ else
+ {
+ value *alloc_val = value_allocate_space_in_inferior (type->length ());
+ this_ptr = value_from_pointer (lookup_pointer_type (type),
+ value_as_long (alloc_val));
+ }
+ argvec[0] = this_ptr;
+ for (size_t i = 0; i < args.size (); ++i)
+ argvec[i + 1] = args[i]->evaluate_with_coercion (exp, noside);
+ gdb::array_view<value *> arg_view = argvec;
+
+ value *callee = nullptr;
+ int static_memfuncp;
+ find_overload_match (arg_view, ctor_name, METHOD,
+ &argvec[0], nullptr, &callee, nullptr,
+ &static_memfuncp, 0, noside);
+ if (callee == nullptr)
+ error (_("Cannot resolve constructor %s to any overloaded instance"),
+ name);
+
+ if (noside == EVAL_AVOID_SIDE_EFFECTS)
+ return value::zero (type, not_lval);
+
+ evaluate_subexp_do_call (exp, noside, callee, arg_view,
+ nullptr, expect_type);
+ return value_ind (this_ptr);
+}
+
}
/* A helper function for BINOP_ASSIGN_MODIFY. */
diff --git a/gdb/expop.h b/gdb/expop.h
index c58a8d7ac37..9839e3bffa9 100644
--- a/gdb/expop.h
+++ b/gdb/expop.h
@@ -1597,6 +1597,11 @@ class type_operation
struct expression *exp,
enum noside noside) override;
+ value *evaluate_funcall (struct type *expect_type,
+ struct expression *exp,
+ enum noside noside,
+ const std::vector<operation_up> &args) override;
+
enum exp_opcode opcode () const override
{ return OP_TYPE; }
diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.cc b/gdb/testsuite/gdb.cp/infcall-ctors.cc
new file mode 100644
index 00000000000..053542ec5ea
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/infcall-ctors.cc
@@ -0,0 +1,128 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright (C) 2026 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct S {
+ int x;
+ explicit S (int n = 0) : x (n) {}
+ S operator+ (int n) const { return S (x + n); }
+};
+
+typedef S S_td;
+using S_u = S;
+
+static int add (const struct S &s1, const struct S &s2) {
+ return s1.x + s2.x;
+}
+
+/* Non-trivially copyable: copy-constructing swaps the two members. */
+struct swapcopy {
+ int lo;
+ int hi;
+ swapcopy (int l, int h) : lo (l), hi (h) {}
+ swapcopy (const swapcopy &o) : lo (o.hi), hi (o.lo) {}
+};
+
+/* Pass swapcopy by value so the call must copy-construct the argument. */
+
+static int
+swapcopy_first_byval (swapcopy c)
+{
+ return c.lo;
+}
+
+struct Base {
+ int x;
+ Base () : x (0) {}
+ explicit Base (int n) : x (n) {}
+ Base (const Base &other) : x (other.x) {}
+};
+
+typedef Base Base_td;
+using Base_u = Base;
+
+namespace NS {
+class Derived : public Base {
+public:
+ int y;
+ Derived () : Base (), y (0) {}
+ Derived (int a, int b) : Base (a), y (b) {}
+};
+
+typedef Base NsBaseTd;
+using NsBaseU = Base;
+typedef Derived Derived_td;
+using Derived_u = Derived;
+
+union U {
+ int a;
+ U () : a (0) {}
+ explicit U (int n) : a (n) {}
+};
+
+typedef U Nu_td;
+}
+
+union U {
+ int x;
+ U () : x (0) {}
+ explicit U (int n) : x (n) {}
+};
+
+typedef U U_td;
+
+static int plus_one (U u)
+{
+ return u.x + 1;
+}
+
+int
+main (void)
+{
+ S s0; /* default: x = 0 */
+ S s1 (42); /* x = 42 */
+ S s2 (s1 + 2); /* x = 44 */
+ S s3 = s1;
+ NS::Derived d; /* Base part x=0, Derived part y=0 */
+ NS::Derived d1 (10, 20); /* Base part x=10, Derived part y=20 */
+ Base b; /* x=0 */
+ Base b1 (5); /* x=5 */
+ Base b2 (d1); /* x=10 */
+ U u0; /* default: x = 0 */
+ U u1 (42); /* x = 42 */
+ NS::U uv0; /* default: a = 0 */
+ NS::U uv1 (7); /* a = 7 */
+ S_td s_td = S_td (11);
+ S_u s_u = S_u (12);
+ Base_td b_td = Base_td (8);
+ Base_u b_u = Base_u (9);
+ NS::NsBaseTd nsb_td = NS::NsBaseTd (13);
+ NS::NsBaseU nsb_u = NS::NsBaseU (14);
+ NS::Derived_td d_td = NS::Derived_td (2, 3);
+ NS::Derived_u d_u = NS::Derived_u (4, 5);
+ U_td u_td = U_td (15);
+ NS::Nu_td nu_u = NS::Nu_td (16);
+ swapcopy swp (30, 40);
+ int result = add (s1, s2);
+ return result + d.x + d.y + b.x + b1.x + b2.x + d.x + d.y \
+ + d1.x + d1.y + plus_one (u0) + u1.x + uv0.a + uv1.a \
+ + s_td.x + s_u.x + b_td.x + b_u.x + nsb_td.x + nsb_u.x \
+ + d_td.x + d_td.y + d_u.x + d_u.y + u_td.x + nu_u.a \
+ + swapcopy_first_byval (swapcopy (100, 200)) \
+ + swapcopy_first_byval (swp); /* stop-here */
+}
diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.exp b/gdb/testsuite/gdb.cp/infcall-ctors.exp
new file mode 100644
index 00000000000..88297018b28
--- /dev/null
+++ b/gdb/testsuite/gdb.cp/infcall-ctors.exp
@@ -0,0 +1,188 @@
+# Copyright (C) 2026 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the gdb testsuite.
+
+# Test constructor calls and casting via inferior function calls:
+# - Simple struct S with constructor (int, default 0).
+# - Base and Derived; cast derived to base, construct Base from Derived.
+# - Typedef and using aliases: ctor resolution must use the class ctor even
+# when the expression names a typedef or alias (DWARF may name types
+# differently from the underlying class tag).
+# - swapcopy: two ints with a user-defined copy ctor that swaps them
+# (non-trivially copyable); pass-by-value in inferior calls must run it.
+
+require allow_cplus_tests
+
+standard_testfile .cc
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} {
+ return -1
+}
+
+if {![runto_main]} {
+ return
+}
+
+# Run to stop-here to ensure all locals are initialized.
+gdb_breakpoint [gdb_get_line_number "stop-here"]
+gdb_continue_to_breakpoint "stop-here"
+
+# Simple tests involving "struct S".
+gdb_test "ptype S" [multi_line \
+ {type = struct S \{} \
+ { int x;} \
+ "" \
+ { S\(int\);} \
+ { S operator\+\(int\) const;} \
+ {\}}]
+
+gdb_test "print s0" " = \\{x = 0\\}" "print s0 default ctor"
+gdb_test "print s1" " = \\{x = 42\\}" "print s1 with 42"
+gdb_test "print S(99)" " = \\{x = 99\\}" "construct S(99) via inferior function call"
+
+gdb_test "ptype swapcopy" [multi_line \
+ {type = struct swapcopy \{} \
+ { int lo;} \
+ { int hi;} \
+ "" \
+ { swapcopy\(int, int\);} \
+ { swapcopy\(const swapcopy ?&\);} \
+ {\}}]
+gdb_test "print swapcopy(1, 2)" { = \{lo = 1, hi = 2\}} \
+ "construct swapcopy via inferior function call"
+gdb_test {print swapcopy($)} {= \{lo = 2, hi = 1\}} \
+ "call copy ctor on object via history"
+gdb_test "print swapcopy_first_byval(swapcopy(10, 20))" "= 20" \
+ "pass-by-value copy ctor swaps members"
+gdb_test "print swapcopy_first_byval(swp)" "= 40" \
+ "swapcopy local passed by value uses copy ctor"
+
+gdb_test "print S_td(33)" " = \\{x = 33\\}" \
+ "construct S via typedef name (not underlying struct tag)"
+gdb_test "print S_u(34)" " = \\{x = 34\\}" "construct S via using alias"
+gdb_test "print Base_td(8)" { = \{x = 8\}} "construct Base via typedef name"
+gdb_test "print Base_u(9)" { = \{x = 9\}} "construct Base via using alias"
+gdb_test "print NS::NsBaseTd(5)" { = \{x = 5\}} \
+ "construct Base via typedef in namespace"
+gdb_test "print NS::NsBaseU(6)" { = \{x = 6\}} \
+ "construct Base via using alias in namespace"
+gdb_test "print NS::Derived_td(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \
+ "construct NS::Derived via typedef name"
+gdb_test "print NS::Derived_u(3, 4)" { = \{<Base> = \{x = 3\}, y = 4\}} \
+ "construct NS::Derived via using alias"
+gdb_test "print U_td(55)" { = \{x = 55\}} \
+ "construct global union U via typedef name"
+gdb_test "print NS::Nu_td(66)" { = \{a = 66\}} \
+ "construct NS::U via typedef name"
+
+# Tests involving "Base" and "Derived".
+set base_re [multi_line \
+ {type = struct Base \{} \
+ { int x;} \
+ "" \
+ { Base\(void\);} \
+ { Base\(int\);} \
+ { Base\(const Base ?&\);} \
+ {\}}]
+gdb_test "ptype Base" $base_re
+
+set derived_re [multi_line \
+ {type = class NS::Derived : public Base \{} \
+ { public:} \
+ { int y;} \
+ "" \
+ { Derived\(void\);} \
+ { Derived\(int, int\);} \
+ {\}}]
+gdb_test "ptype NS::Derived" $derived_re
+
+gdb_test "print d" { = \{<Base> = \{x = 0\}, y = 0\}}
+gdb_test "print d1" { = \{<Base> = \{x = 10\}, y = 20\}}
+gdb_test "print b" { = \{x = 0\}}
+gdb_test "print b1" { = \{x = 5\}}
+gdb_test "print b2" { = \{x = 10\}}
+gdb_test "print (Base)(d1)" { = \{x = 10\}} "cast (Base)(d1) slices to Base"
+gdb_test "print Base(d1)" { = \{x = 10\}} "construct Base(d1) from Derived"
+gdb_test "print Base()" { = \{x = 0\}} "construct Base() default"
+gdb_test "print Base(7)" { = \{x = 7\}} "construct Base(7) with argument"
+gdb_test "print NS::Derived()" { = \{<Base> = \{x = 0\}, y = 0\}} \
+ "construct NS::Derived() via inferior function call"
+gdb_test "print NS::Derived().y" " = 0" "construct NS::Derived() and access .y"
+gdb_test "print NS::Derived(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \
+ "construct NS::Derived(1, 2) via inferior function call"
+gdb_test "print NS::Derived(1, 2).y" " = 2" \
+ "construct NS::Derived(1, 2) and access .y"
+gdb_test "print ((Base)d1).x" " = 10" "cast ((Base)d1).x"
+
+# Print the types of these "temporary" objects.
+gdb_test "ptype Base()" $base_re "ptype of Base temporary"
+gdb_test "ptype NS::Derived(15, 25)" $derived_re \
+ "ptype of NS::Derived temporary"
+
+# Tests involving unions.
+gdb_test "ptype U" [multi_line \
+ {type = union U \{} \
+ { int x;} \
+ "" \
+ { U\(void\);} \
+ { U\(int\);} \
+ {\}}] \
+ "ptype U"
+
+gdb_test "ptype NS::U" [multi_line \
+ {type = union NS::U \{} \
+ { int a;} \
+ "" \
+ { U\(void\);} \
+ { U\(int\);} \
+ {\}}] \
+ "ptype NS::U"
+
+gdb_test "print u0" { = \{x = 0\}}
+gdb_test "print u1" { = \{x = 42\}}
+gdb_test "print uv0" { = \{a = 0\}}
+gdb_test "print uv1" { = \{a = 7\}}
+gdb_test "print U()" { = \{x = 0\}} "construct U() via inferior function call"
+gdb_test "print U(99)" { = \{x = 99\}} "construct U(99) via inferior function call"
+gdb_test "print U(99).x" " = 99" "construct U(99) and access .x"
+gdb_test "print NS::U()" { = \{a = 0\}} \
+ "construct NS::U() via inferior function call"
+gdb_test "print NS::U(13)" { = \{a = 13\}} \
+ "construct NS::U(13) via inferior function call"
+gdb_test "print NS::U(13).a" " = 13" \
+ "construct NS::U(13) and access .a"
+
+set u_re [multi_line \
+ {type = union U \{} \
+ { int x;} \
+ "" \
+ { U\(void\);} \
+ { U\(int\);} \
+ {\}}]
+set ns_u_re [multi_line \
+ {type = union NS::U \{} \
+ { int a;} \
+ "" \
+ { U\(void\);} \
+ { U\(int\);} \
+ {\}}]
+gdb_test "ptype U()" $u_re "ptype of U temporary"
+gdb_test "ptype NS::U(99)" $ns_u_re "ptype of NS::U temporary"
+
+gdb_test "p plus_one(U(42))" "= 43" "temporary in function call"
+gdb_test "p add(S(1), S(20))" "= 21" "add two temporaries of S"
+gdb_test "p plus_one(U(add (S(4), S(6))))" "= 11" \
+ "nested function call using temporaries"
base-commit: 07519d531b1e858f665ff011d7f1002f38111ec8
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] Add infcall support for C++ constructor-style expressions 2026-03-26 19:17 [PATCH] Add infcall support for C++ constructor-style expressions Keith Seitz @ 2026-03-27 5:50 ` Eli Zaretskii 2026-04-21 13:41 ` Andrew Burgess 2026-04-27 16:43 ` [PATCH v2] " Keith Seitz 2 siblings, 0 replies; 8+ messages in thread From: Eli Zaretskii @ 2026-03-27 5:50 UTC (permalink / raw) To: Keith Seitz; +Cc: gdb-patches > From: Keith Seitz <keiths@redhat.com> > Date: Thu, 26 Mar 2026 12:17:20 -0700 > > This patch adds an initial try at teaching the expression parser/evaluator > to construct temporary objects requiring construction during an inferior > function call. > > To accomplish this, I've chosen the route of modifying the parser to > teach it that `Type(args)' is a function call when `Type' names a > class/struct/union and is immediately followed by '(', that is, via look- > ahead). A new parser token and grammar rule have been added to deal > with this new production. > > The real work is dispatched to `type_operation::evaluate_funcall', > allocating memory for the temporary and finding the most suitable constructor > with `find_overload_match'. It then runs the inferior call, returning > the newly constructed object. > > I've included many tests covering as many corner cases as I could invent, > and these tests are clang clean. They also introduce no regressions on > x86-64 Fedora 43 with GCC 15.2.1 and RHEL 9.4 with GCC 11.5.0. > > Example: > Consider a C++ frame where 'struct S { int x; S(int); ... }' is in scope > > Before: > (gdb) print S(42) > ❌️ A syntax error in expression, near `10)'. > > After: > (gdb) print S(42) > $1 = {x = 42} > > Note that no attempt has been made to deal with templates. Hopefully > a follow-on patch can address that. > --- > gdb/NEWS | 5 + > gdb/c-exp.y | 86 +++++++++-- > gdb/eval.c | 55 ++++++++ > gdb/expop.h | 5 + > gdb/testsuite/gdb.cp/infcall-ctors.cc | 128 +++++++++++++++++ > gdb/testsuite/gdb.cp/infcall-ctors.exp | 188 +++++++++++++++++++++++++ > 6 files changed, 457 insertions(+), 10 deletions(-) > create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.cc > create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.exp The NEWS part is okay, thanks. Reviewed-By: Eli Zaretskii <eliz@gnu.org> ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] Add infcall support for C++ constructor-style expressions 2026-03-26 19:17 [PATCH] Add infcall support for C++ constructor-style expressions Keith Seitz 2026-03-27 5:50 ` Eli Zaretskii @ 2026-04-21 13:41 ` Andrew Burgess 2026-04-21 18:13 ` Keith Seitz 2026-04-27 16:43 ` [PATCH v2] " Keith Seitz 2 siblings, 1 reply; 8+ messages in thread From: Andrew Burgess @ 2026-04-21 13:41 UTC (permalink / raw) To: Keith Seitz, gdb-patches Keith Seitz <keiths@redhat.com> writes: > This patch adds an initial try at teaching the expression parser/evaluator > to construct temporary objects requiring construction during an inferior > function call. > > To accomplish this, I've chosen the route of modifying the parser to > teach it that `Type(args)' is a function call when `Type' names a > class/struct/union and is immediately followed by '(', that is, via look- > ahead). A new parser token and grammar rule have been added to deal > with this new production. > > The real work is dispatched to `type_operation::evaluate_funcall', > allocating memory for the temporary and finding the most suitable constructor > with `find_overload_match'. It then runs the inferior call, returning > the newly constructed object. > > I've included many tests covering as many corner cases as I could invent, > and these tests are clang clean. They also introduce no regressions on > x86-64 Fedora 43 with GCC 15.2.1 and RHEL 9.4 with GCC 11.5.0. > > Example: > Consider a C++ frame where 'struct S { int x; S(int); ... }' is in scope > > Before: > (gdb) print S(42) > ❌️ A syntax error in expression, near `10)'. > > After: > (gdb) print S(42) > $1 = {x = 42} > > Note that no attempt has been made to deal with templates. Hopefully > a follow-on patch can address that. > --- > gdb/NEWS | 5 + > gdb/c-exp.y | 86 +++++++++-- > gdb/eval.c | 55 ++++++++ > gdb/expop.h | 5 + > gdb/testsuite/gdb.cp/infcall-ctors.cc | 128 +++++++++++++++++ > gdb/testsuite/gdb.cp/infcall-ctors.exp | 188 +++++++++++++++++++++++++ > 6 files changed, 457 insertions(+), 10 deletions(-) > create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.cc > create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.exp > > diff --git a/gdb/NEWS b/gdb/NEWS > index 03f46df5400..c04727d342d 100644 > --- a/gdb/NEWS > +++ b/gdb/NEWS > @@ -66,6 +66,11 @@ > automatically set to UTF-8. (Users can use the Windows 'chcp' > command to change the output codepage of the console.) > > +* In C++ GDB now accepts constructor-style expressions "TYPE (ARGS)" > + when TYPE names a class, struct, or union in the current expression > + context. This allows objects to be constructed directly during > + expression evalution. typo: evalution -> evaluation > + > * New targets > > GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux* > diff --git a/gdb/c-exp.y b/gdb/c-exp.y > index a4a910df712..e17ee2d3c12 100644 > --- a/gdb/c-exp.y > +++ b/gdb/c-exp.y > @@ -199,7 +199,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value); > #endif > %} > > -%type <voidval> exp exp1 type_exp start variable qualified_name lcurly function_method > +%type <voidval> exp exp1 type_exp start variable qualified_name lcurly function_method typename_for_ctor > %type <lval> rcurly > %type <tval> type typebase scalar_type tag_name_or_complete > %type <tvec> nonempty_typelist func_mod parameter_typelist > @@ -230,7 +230,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value); > %token <ssym> NAME /* BLOCKNAME defined below to give it higher precedence. */ > %token <ssym> UNKNOWN_CPP_NAME > %token <voidval> COMPLETE > -%token <tsym> TYPENAME > +%token <tsym> TYPENAME TYPENAME_CTOR > %token <theclass> CLASSNAME /* ObjC Class name */ > %type <sval> name > %type <qval> qual_field_name field_name field_name_or_complete > @@ -533,6 +533,26 @@ msgarg : name ':' exp > { add_msglist(0, 0); } > ; > > +exp : typename_for_ctor '(' > + { pstate->start_arglist (); } > + arglist ')' %prec ARROW > + { > + std::vector<operation_up> args > + = pstate->pop_vector (pstate->end_arglist ()); > + operation_up type_op = pstate->pop (); > + pstate->push_new<funcall_operation> > + (std::move (type_op), std::move (args)); > + } > + ; > + > +exp : typename_for_ctor '(' ')' %prec ARROW > + { > + operation_up type_op = pstate->pop (); > + pstate->push_new<funcall_operation> > + (std::move (type_op), std::vector<operation_up> ()); > + } > + ; > + > exp : exp '(' > /* This is to save the value of arglist_len > being accumulated by an outer function call. */ > @@ -1471,6 +1491,12 @@ scalar_type: > "int"); } > ; > > +/* Constructor-style calls. */ > +typename_for_ctor > + : TYPENAME_CTOR > + { pstate->push_new<type_operation> ($1.type); } > + ; > + > /* Implements (approximately): (type-qualifier)* type-specifier. > > When type-specifier is only ever a single word, like 'float' then these > @@ -3114,6 +3140,34 @@ static int popping; > built up. */ > static auto_obstack name_obstack; > > +/* Return TYPENAME_CTOR only when the next token is '(', so this > + token is used solely for constructor calls. Otherwise return TYPENAME. > + NAME_END is the character just past the name (e.g. yylval.sval.ptr + > + yylval.sval.length). */ > + > +static int > +typename_token_for (struct parser_state *par_state, struct type *type, > + const char *name_end) > +{ > + if (type == nullptr > + || par_state->language ()->la_language != language_cplus) > + return TYPENAME; > + type = check_typedef (type); > + if (type->code () != TYPE_CODE_STRUCT && type->code () != TYPE_CODE_UNION) > + return TYPENAME; > + /* Only return TYPENAME_CTOR when followed by '('. */ > + if (name_end == nullptr) > + return TYPENAME; > + { > + const char *p = name_end; > + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') Would 'c_isspace (*p)' work here? It's what we use in similar cases within this file. > + ++p; > + if (*p != '(') > + return TYPENAME; > + } > + return TYPENAME_CTOR; > +} > + > /* Classify a NAME token. The contents of the token are in `yylval'. > Updates yylval and returns the new token type. BLOCK is the block > in which lookups start; this can be NULL to mean the global scope. > @@ -3157,7 +3211,8 @@ classify_name (struct parser_state *par_state, const struct block *block, > if (bsym.symbol != NULL) > { > yylval.tsym.type = bsym.symbol->type (); > - return TYPENAME; > + return typename_token_for (par_state, yylval.tsym.type, > + yylval.sval.ptr + yylval.sval.length); > } > } > > @@ -3184,7 +3239,8 @@ classify_name (struct parser_state *par_state, const struct block *block, > if (bsym.symbol && bsym.symbol->loc_class () == LOC_TYPEDEF) > { > yylval.tsym.type = bsym.symbol->type (); > - return TYPENAME; > + return typename_token_for (par_state, yylval.tsym.type, > + yylval.sval.ptr + yylval.sval.length); > } > > /* See if it's an ObjC classname. */ > @@ -3270,7 +3326,9 @@ classify_inner_name (struct parser_state *par_state, > if (base_type != NULL) > { > yylval.tsym.type = base_type; > - return TYPENAME; > + return typename_token_for (par_state, yylval.tsym.type, > + yylval.ssym.stoken.ptr > + + yylval.ssym.stoken.length); > } > > return ERROR; > @@ -3290,14 +3348,18 @@ classify_inner_name (struct parser_state *par_state, > if (base_type != NULL) > { > yylval.tsym.type = base_type; > - return TYPENAME; > + return typename_token_for (par_state, yylval.tsym.type, > + yylval.ssym.stoken.ptr > + + yylval.ssym.stoken.length); > } > } > return ERROR; > > case LOC_TYPEDEF: > yylval.tsym.type = yylval.ssym.sym.symbol->type (); > - return TYPENAME; > + return typename_token_for (par_state, yylval.tsym.type, > + yylval.ssym.stoken.ptr > + + yylval.ssym.stoken.length); > > default: > return NAME; > @@ -3332,7 +3394,7 @@ handle_qualified_field_name (qualified_name_token token) > int kind = classify_inner_name (pstate, > pstate->expression_context_block, > type); > - if (kind != TYPENAME) > + if (kind != TYPENAME && kind != TYPENAME_CTOR) > error (_("could not find type '%s'"), accum.c_str ()); > > type = yylval.tsym.type; > @@ -3384,7 +3446,8 @@ yylex (void) > current.token = classify_name (pstate, pstate->expression_context_block, > is_quoted_name, last_lex_was_structop); > if (pstate->language ()->la_language != language_cplus > - || (current.token != TYPENAME && current.token != COLONCOLON > + || (current.token != TYPENAME && current.token != TYPENAME_CTOR > + && current.token != COLONCOLON > && current.token != FILENAME > && (cpstate->assume_classification == TYPE_CODE_UNDEF > || current.token != NAME)) > @@ -3430,6 +3493,7 @@ yylex (void) > else > { > gdb_assert (current.token == TYPENAME > + || current.token == TYPENAME_CTOR > || cpstate->assume_classification != TYPE_CODE_UNDEF); > search_block = pstate->expression_context_block; > obstack_grow (&name_obstack, current.value.sval.ptr, > @@ -3460,7 +3524,8 @@ yylex (void) > context_type); > /* We keep going until we either run out of names, or until > we have a qualified name which is not a type. */ > - if (classification != TYPENAME && classification != NAME) > + if (classification != TYPENAME && classification != TYPENAME_CTOR > + && classification != NAME) > break; > > /* Accept up to this token. */ > @@ -3591,6 +3656,7 @@ c_print_token (FILE *file, int type, YYSTYPE value) > break; > > case TYPENAME: > + case TYPENAME_CTOR: > parser_fprintf (file, "tsym<type=%s, name=%s>", > value.tsym.type->safe_name (), > copy_name (value.tsym.stoken).c_str ()); > diff --git a/gdb/eval.c b/gdb/eval.c > index 7beff554ed4..e988b954059 100644 > --- a/gdb/eval.c > +++ b/gdb/eval.c > @@ -1869,6 +1869,61 @@ type_operation::evaluate (struct type *expect_type, struct expression *exp, > error (_("Attempt to use a type name as an expression")); > } > > +value * > +type_operation::evaluate_funcall (struct type *expect_type, > + struct expression *exp, > + enum noside noside, > + const std::vector<operation_up> &args) > +{ > + struct type *type = std::get<0> (m_storage); > + type = check_typedef (type); > + > + /* Constructor-style call Type(args) is only for C++ aggregate types. */ > + gdb_assert (exp->language_defn->la_language == language_cplus); > + > + const char *name = type->name (); > + if (name == nullptr) > + error (_("Cannot call constructor of unnamed type")); Thinking about this error was interesting. I was wondering how we might test this case, as I don't think this is tested right now. I tried writing some C++ like: struct { int a; int b; } global_var = { 1, 2 }; Then in GDB I tried: (gdb) p decltype(global_var) (3) Which I know makes no sense, the anonymous struct doesn't even have a constructor, but I wondered if we'd hit the anonymous type error before we even searched for a constructor. We didn't though, as GDB doesn't know how to parse the expression: (gdb) p decltype(global_var)(3) A syntax error in expression, near `(3)'. (gdb) the decltype part is handled in c-exp.y by the rule: type_exp: ... snip ... | DECLTYPE '(' exp ')' { pstate->wrap<decltype_operation> (); } And the '(3)' part of the expression is handled by your new rule: > +exp : typename_for_ctor '(' > + { pstate->start_arglist (); } > + arglist ')' %prec ARROW > + { But, I wondered, what if, instead of typename_for_ctor, we just used 'type_exp', like this: exp : type_exp '(' { pstate->start_arglist (); } arglist ')' %prec ARROW { Then I extended the type_exp rule like: type_exp: ... snip ... | TYPENAME_CTOR { pstate->push_new<type_operation> ($1.type); } All of the new tests still pass... ... but the decltype example still doesn't work. But by this time I was curious, clearly my actual example with global_var is never going to work, the global_var type doesn't have a constructor, but what if global_var was a type that did have a constructor? The decltype trick should be workable, right? So, I guess the question I'm circling around here is, instead of adding typename_for_ctor, did you try just using the existing type_exp rule? Even if we didn't get the decltype trick working in the original commit, it feels like going through type_exp would leave that as a possibility for the future, but going with typename_for_ctor feels like it will make that harder. > + > + /* Get the constructor name from the type name. */ > + gdb::unique_xmalloc_ptr<char> ctor_name_ptr = cp_func_name (name); > + const char *ctor_name = (ctor_name_ptr != nullptr) ? ctor_name_ptr.get () : name; This line seems a little long, maybe wrap at the '=' ? > + > + if (!overload_resolution) > + return operation::evaluate_funcall (expect_type, exp, noside, args); I've pretty sure that this path, calling operation::evaluate_funcall, will always result in an error like: (gdb) set overload-resolution off (gdb) p U(34) Attempt to use a type name as an expression (gdb) I wonder if it would be clearer to the user to just do: if (!overload_resolution) error (_("Constructor calls require 'set overload-resolution on'")); I didn't see any tests with 'set overload-resolution off' in use, so it wasn't clear if there's maybe some path where the function call as you have it can do anything other than throw an error? > + > + std::vector<value *> argvec (1 + args.size ()); > + value *this_ptr; > + if (noside == EVAL_AVOID_SIDE_EFFECTS) > + this_ptr = value::zero (lookup_pointer_type (type), lval_memory); > + else > + { > + value *alloc_val = value_allocate_space_in_inferior (type->length ()); > + this_ptr = value_from_pointer (lookup_pointer_type (type), > + value_as_long (alloc_val)); > + } > + argvec[0] = this_ptr; > + for (size_t i = 0; i < args.size (); ++i) > + argvec[i + 1] = args[i]->evaluate_with_coercion (exp, noside); > + gdb::array_view<value *> arg_view = argvec; > + > + value *callee = nullptr; > + int static_memfuncp; > + find_overload_match (arg_view, ctor_name, METHOD, > + &argvec[0], nullptr, &callee, nullptr, > + &static_memfuncp, 0, noside); It would not be usual for constructors to be static, but we should probably handle the case where static_memfuncp is true, even if it is just: if (static_memfuncp) error (_("Constructor %s is unexpectedly marked static"), name); Bonus points for using the DWARF assembler to exercise this error case, but I don't think that's a hard requirement, I see this error more as a glorified todo marker -- if we ever find a case where this is triggers then we can understand it, and fix GDB to match. > + if (callee == nullptr) > + error (_("Cannot resolve constructor %s to any overloaded instance"), > + name); I don't think this error case is being tested. Can you add a test case for this? > + > + if (noside == EVAL_AVOID_SIDE_EFFECTS) > + return value::zero (type, not_lval); > + > + evaluate_subexp_do_call (exp, noside, callee, arg_view, > + nullptr, expect_type); > + return value_ind (this_ptr); > +} > + > } > > /* A helper function for BINOP_ASSIGN_MODIFY. */ > diff --git a/gdb/expop.h b/gdb/expop.h > index c58a8d7ac37..9839e3bffa9 100644 > --- a/gdb/expop.h > +++ b/gdb/expop.h > @@ -1597,6 +1597,11 @@ class type_operation > struct expression *exp, > enum noside noside) override; > > + value *evaluate_funcall (struct type *expect_type, > + struct expression *exp, > + enum noside noside, > + const std::vector<operation_up> &args) override; > + > enum exp_opcode opcode () const override > { return OP_TYPE; } > > diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.cc b/gdb/testsuite/gdb.cp/infcall-ctors.cc > new file mode 100644 > index 00000000000..053542ec5ea > --- /dev/null > +++ b/gdb/testsuite/gdb.cp/infcall-ctors.cc > @@ -0,0 +1,128 @@ > +/* This testcase is part of GDB, the GNU debugger. > + > + Copyright (C) 2026 Free Software Foundation, Inc. > + > + This file is part of GDB. > + > + This program is free software; you can redistribute it and/or modify > + it under the terms of the GNU General Public License as published by > + the Free Software Foundation; either version 3 of the License, or > + (at your option) any later version. > + > + This program is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + GNU General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with this program. If not, see <http://www.gnu.org/licenses/>. */ > + > +struct S { The '{' should be on a new line. This mistake is repeated throughout this test, I'll not point them all out. > + int x; > + explicit S (int n = 0) : x (n) {} > + S operator+ (int n) const { return S (x + n); } > +}; > + > +typedef S S_td; > +using S_u = S; > + > +static int add (const struct S &s1, const struct S &s2) { Newline before add, opening '{' on its own line. Unless this layout is needed for the test? In which case a comment is needed. > + return s1.x + s2.x; > +} > + > +/* Non-trivially copyable: copy-constructing swaps the two members. */ > +struct swapcopy { > + int lo; > + int hi; > + swapcopy (int l, int h) : lo (l), hi (h) {} > + swapcopy (const swapcopy &o) : lo (o.hi), hi (o.lo) {} > +}; > + > +/* Pass swapcopy by value so the call must copy-construct the argument. */ > + > +static int > +swapcopy_first_byval (swapcopy c) > +{ > + return c.lo; > +} > + > +struct Base { > + int x; > + Base () : x (0) {} > + explicit Base (int n) : x (n) {} > + Base (const Base &other) : x (other.x) {} > +}; > + > +typedef Base Base_td; > +using Base_u = Base; > + > +namespace NS { > +class Derived : public Base { > +public: > + int y; > + Derived () : Base (), y (0) {} > + Derived (int a, int b) : Base (a), y (b) {} > +}; > + > +typedef Base NsBaseTd; > +using NsBaseU = Base; > +typedef Derived Derived_td; > +using Derived_u = Derived; > + > +union U { > + int a; > + U () : a (0) {} > + explicit U (int n) : a (n) {} > +}; > + > +typedef U Nu_td; > +} > + > +union U { > + int x; > + U () : x (0) {} > + explicit U (int n) : x (n) {} > +}; > + > +typedef U U_td; > + > +static int plus_one (U u) > +{ > + return u.x + 1; > +} > + > +int > +main (void) > +{ > + S s0; /* default: x = 0 */ > + S s1 (42); /* x = 42 */ > + S s2 (s1 + 2); /* x = 44 */ > + S s3 = s1; > + NS::Derived d; /* Base part x=0, Derived part y=0 */ > + NS::Derived d1 (10, 20); /* Base part x=10, Derived part y=20 */ > + Base b; /* x=0 */ > + Base b1 (5); /* x=5 */ > + Base b2 (d1); /* x=10 */ > + U u0; /* default: x = 0 */ > + U u1 (42); /* x = 42 */ > + NS::U uv0; /* default: a = 0 */ > + NS::U uv1 (7); /* a = 7 */ > + S_td s_td = S_td (11); > + S_u s_u = S_u (12); > + Base_td b_td = Base_td (8); > + Base_u b_u = Base_u (9); > + NS::NsBaseTd nsb_td = NS::NsBaseTd (13); > + NS::NsBaseU nsb_u = NS::NsBaseU (14); > + NS::Derived_td d_td = NS::Derived_td (2, 3); > + NS::Derived_u d_u = NS::Derived_u (4, 5); > + U_td u_td = U_td (15); > + NS::Nu_td nu_u = NS::Nu_td (16); > + swapcopy swp (30, 40); > + int result = add (s1, s2); > + return result + d.x + d.y + b.x + b1.x + b2.x + d.x + d.y \ > + + d1.x + d1.y + plus_one (u0) + u1.x + uv0.a + uv1.a \ > + + s_td.x + s_u.x + b_td.x + b_u.x + nsb_td.x + nsb_u.x \ > + + d_td.x + d_td.y + d_u.x + d_u.y + u_td.x + nu_u.a \ > + + swapcopy_first_byval (swapcopy (100, 200)) \ > + + swapcopy_first_byval (swp); /* stop-here */ > +} > diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.exp b/gdb/testsuite/gdb.cp/infcall-ctors.exp > new file mode 100644 > index 00000000000..88297018b28 > --- /dev/null > +++ b/gdb/testsuite/gdb.cp/infcall-ctors.exp > @@ -0,0 +1,188 @@ > +# Copyright (C) 2026 Free Software Foundation, Inc. > + > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 3 of the License, or > +# (at your option) any later version. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program. If not, see <http://www.gnu.org/licenses/>. > + > +# This file is part of the gdb testsuite. > + > +# Test constructor calls and casting via inferior function calls: > +# - Simple struct S with constructor (int, default 0). > +# - Base and Derived; cast derived to base, construct Base from Derived. > +# - Typedef and using aliases: ctor resolution must use the class ctor even > +# when the expression names a typedef or alias (DWARF may name types > +# differently from the underlying class tag). > +# - swapcopy: two ints with a user-defined copy ctor that swaps them > +# (non-trivially copyable); pass-by-value in inferior calls must run it. > + > +require allow_cplus_tests > + > +standard_testfile .cc > + > +if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} { > + return -1 The -1 is not needed. Thanks, Andrew > +} > + > +if {![runto_main]} { > + return > +} > + > +# Run to stop-here to ensure all locals are initialized. > +gdb_breakpoint [gdb_get_line_number "stop-here"] > +gdb_continue_to_breakpoint "stop-here" > + > +# Simple tests involving "struct S". > +gdb_test "ptype S" [multi_line \ > + {type = struct S \{} \ > + { int x;} \ > + "" \ > + { S\(int\);} \ > + { S operator\+\(int\) const;} \ > + {\}}] > + > +gdb_test "print s0" " = \\{x = 0\\}" "print s0 default ctor" > +gdb_test "print s1" " = \\{x = 42\\}" "print s1 with 42" > +gdb_test "print S(99)" " = \\{x = 99\\}" "construct S(99) via inferior function call" > + > +gdb_test "ptype swapcopy" [multi_line \ > + {type = struct swapcopy \{} \ > + { int lo;} \ > + { int hi;} \ > + "" \ > + { swapcopy\(int, int\);} \ > + { swapcopy\(const swapcopy ?&\);} \ > + {\}}] > +gdb_test "print swapcopy(1, 2)" { = \{lo = 1, hi = 2\}} \ > + "construct swapcopy via inferior function call" > +gdb_test {print swapcopy($)} {= \{lo = 2, hi = 1\}} \ > + "call copy ctor on object via history" > +gdb_test "print swapcopy_first_byval(swapcopy(10, 20))" "= 20" \ > + "pass-by-value copy ctor swaps members" > +gdb_test "print swapcopy_first_byval(swp)" "= 40" \ > + "swapcopy local passed by value uses copy ctor" > + > +gdb_test "print S_td(33)" " = \\{x = 33\\}" \ > + "construct S via typedef name (not underlying struct tag)" > +gdb_test "print S_u(34)" " = \\{x = 34\\}" "construct S via using alias" > +gdb_test "print Base_td(8)" { = \{x = 8\}} "construct Base via typedef name" > +gdb_test "print Base_u(9)" { = \{x = 9\}} "construct Base via using alias" > +gdb_test "print NS::NsBaseTd(5)" { = \{x = 5\}} \ > + "construct Base via typedef in namespace" > +gdb_test "print NS::NsBaseU(6)" { = \{x = 6\}} \ > + "construct Base via using alias in namespace" > +gdb_test "print NS::Derived_td(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \ > + "construct NS::Derived via typedef name" > +gdb_test "print NS::Derived_u(3, 4)" { = \{<Base> = \{x = 3\}, y = 4\}} \ > + "construct NS::Derived via using alias" > +gdb_test "print U_td(55)" { = \{x = 55\}} \ > + "construct global union U via typedef name" > +gdb_test "print NS::Nu_td(66)" { = \{a = 66\}} \ > + "construct NS::U via typedef name" > + > +# Tests involving "Base" and "Derived". > +set base_re [multi_line \ > + {type = struct Base \{} \ > + { int x;} \ > + "" \ > + { Base\(void\);} \ > + { Base\(int\);} \ > + { Base\(const Base ?&\);} \ > + {\}}] > +gdb_test "ptype Base" $base_re > + > +set derived_re [multi_line \ > + {type = class NS::Derived : public Base \{} \ > + { public:} \ > + { int y;} \ > + "" \ > + { Derived\(void\);} \ > + { Derived\(int, int\);} \ > + {\}}] > +gdb_test "ptype NS::Derived" $derived_re > + > +gdb_test "print d" { = \{<Base> = \{x = 0\}, y = 0\}} > +gdb_test "print d1" { = \{<Base> = \{x = 10\}, y = 20\}} > +gdb_test "print b" { = \{x = 0\}} > +gdb_test "print b1" { = \{x = 5\}} > +gdb_test "print b2" { = \{x = 10\}} > +gdb_test "print (Base)(d1)" { = \{x = 10\}} "cast (Base)(d1) slices to Base" > +gdb_test "print Base(d1)" { = \{x = 10\}} "construct Base(d1) from Derived" > +gdb_test "print Base()" { = \{x = 0\}} "construct Base() default" > +gdb_test "print Base(7)" { = \{x = 7\}} "construct Base(7) with argument" > +gdb_test "print NS::Derived()" { = \{<Base> = \{x = 0\}, y = 0\}} \ > + "construct NS::Derived() via inferior function call" > +gdb_test "print NS::Derived().y" " = 0" "construct NS::Derived() and access .y" > +gdb_test "print NS::Derived(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \ > + "construct NS::Derived(1, 2) via inferior function call" > +gdb_test "print NS::Derived(1, 2).y" " = 2" \ > + "construct NS::Derived(1, 2) and access .y" > +gdb_test "print ((Base)d1).x" " = 10" "cast ((Base)d1).x" > + > +# Print the types of these "temporary" objects. > +gdb_test "ptype Base()" $base_re "ptype of Base temporary" > +gdb_test "ptype NS::Derived(15, 25)" $derived_re \ > + "ptype of NS::Derived temporary" > + > +# Tests involving unions. > +gdb_test "ptype U" [multi_line \ > + {type = union U \{} \ > + { int x;} \ > + "" \ > + { U\(void\);} \ > + { U\(int\);} \ > + {\}}] \ > + "ptype U" > + > +gdb_test "ptype NS::U" [multi_line \ > + {type = union NS::U \{} \ > + { int a;} \ > + "" \ > + { U\(void\);} \ > + { U\(int\);} \ > + {\}}] \ > + "ptype NS::U" > + > +gdb_test "print u0" { = \{x = 0\}} > +gdb_test "print u1" { = \{x = 42\}} > +gdb_test "print uv0" { = \{a = 0\}} > +gdb_test "print uv1" { = \{a = 7\}} > +gdb_test "print U()" { = \{x = 0\}} "construct U() via inferior function call" > +gdb_test "print U(99)" { = \{x = 99\}} "construct U(99) via inferior function call" > +gdb_test "print U(99).x" " = 99" "construct U(99) and access .x" > +gdb_test "print NS::U()" { = \{a = 0\}} \ > + "construct NS::U() via inferior function call" > +gdb_test "print NS::U(13)" { = \{a = 13\}} \ > + "construct NS::U(13) via inferior function call" > +gdb_test "print NS::U(13).a" " = 13" \ > + "construct NS::U(13) and access .a" > + > +set u_re [multi_line \ > + {type = union U \{} \ > + { int x;} \ > + "" \ > + { U\(void\);} \ > + { U\(int\);} \ > + {\}}] > +set ns_u_re [multi_line \ > + {type = union NS::U \{} \ > + { int a;} \ > + "" \ > + { U\(void\);} \ > + { U\(int\);} \ > + {\}}] > +gdb_test "ptype U()" $u_re "ptype of U temporary" > +gdb_test "ptype NS::U(99)" $ns_u_re "ptype of NS::U temporary" > + > +gdb_test "p plus_one(U(42))" "= 43" "temporary in function call" > +gdb_test "p add(S(1), S(20))" "= 21" "add two temporaries of S" > +gdb_test "p plus_one(U(add (S(4), S(6))))" "= 11" \ > + "nested function call using temporaries" > > base-commit: 07519d531b1e858f665ff011d7f1002f38111ec8 > -- > 2.53.0 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] Add infcall support for C++ constructor-style expressions 2026-04-21 13:41 ` Andrew Burgess @ 2026-04-21 18:13 ` Keith Seitz 0 siblings, 0 replies; 8+ messages in thread From: Keith Seitz @ 2026-04-21 18:13 UTC (permalink / raw) To: Andrew Burgess, gdb-patches Hi, On 4/21/26 6:41 AM, Andrew Burgess wrote: > Keith Seitz <keiths@redhat.com> writes: > >> diff --git a/gdb/c-exp.y b/gdb/c-exp.y>> index a4a910df712..e17ee2d3c12 100644 >> --- a/gdb/c-exp.y >> +++ b/gdb/c-exp.y >> @@ -3114,6 +3140,34 @@ static int popping; >> built up. */ >> static auto_obstack name_obstack; >> >> +/* Return TYPENAME_CTOR only when the next token is '(', so this >> + token is used solely for constructor calls. Otherwise return TYPENAME. >> + NAME_END is the character just past the name (e.g. yylval.sval.ptr + >> + yylval.sval.length). */ >> + >> +static int >> +typename_token_for (struct parser_state *par_state, struct type *type, >> + const char *name_end) >> +{ >> + if (type == nullptr >> + || par_state->language ()->la_language != language_cplus) >> + return TYPENAME; >> + type = check_typedef (type); >> + if (type->code () != TYPE_CODE_STRUCT && type->code () != TYPE_CODE_UNION) >> + return TYPENAME; >> + /* Only return TYPENAME_CTOR when followed by '('. */ >> + if (name_end == nullptr) >> + return TYPENAME; >> + { >> + const char *p = name_end; >> + while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') > > Would 'c_isspace (*p)' work here? It's what we use in similar cases > within this file. Absolutely! >> diff --git a/gdb/eval.c b/gdb/eval.c >> index 7beff554ed4..e988b954059 100644 >> --- a/gdb/eval.c >> +++ b/gdb/eval.c >> @@ -1869,6 +1869,61 @@ type_operation::evaluate (struct type *expect_type, struct expression *exp, >> error (_("Attempt to use a type name as an expression")); >> } >> >> +value * >> +type_operation::evaluate_funcall (struct type *expect_type, >> + struct expression *exp, >> + enum noside noside, >> + const std::vector<operation_up> &args) >> +{ >> + struct type *type = std::get<0> (m_storage); >> + type = check_typedef (type); >> + >> + /* Constructor-style call Type(args) is only for C++ aggregate types. */ >> + gdb_assert (exp->language_defn->la_language == language_cplus); >> + >> + const char *name = type->name (); >> + if (name == nullptr) >> + error (_("Cannot call constructor of unnamed type")); > > Thinking about this error was interesting. [snip] > Then I extended the type_exp rule like: > > type_exp: > ... snip ... > | TYPENAME_CTOR > { > pstate->push_new<type_operation> ($1.type); > } > > All of the new tests still pass... > > ... but the decltype example still doesn't work. But by this time I was > curious, clearly my actual example with global_var is never going to > work, the global_var type doesn't have a constructor, but what if > global_var was a type that did have a constructor? The decltype trick > should be workable, right? > > So, I guess the question I'm circling around here is, instead of adding > typename_for_ctor, did you try just using the existing type_exp rule? > Even if we didn't get the decltype trick working in the original commit, > it feels like going through type_exp would leave that as a possibility > for the future, but going with typename_for_ctor feels like it will make > that harder. I did at one time use a similar approach. I think what happened is that I ran into issues and started breaking down/isolating my changes to make sure that it wasn't some other production messing me up. Alas, I did not actually go back and try to integrate my working solution. I will investigate further for v2. > >> + >> + /* Get the constructor name from the type name. */ >> + gdb::unique_xmalloc_ptr<char> ctor_name_ptr = cp_func_name (name); >> + const char *ctor_name = (ctor_name_ptr != nullptr) ? ctor_name_ptr.get () : name; > > This line seems a little long, maybe wrap at the '=' ? > >> + >> + if (!overload_resolution) >> + return operation::evaluate_funcall (expect_type, exp, noside, args); > > I've pretty sure that this path, calling operation::evaluate_funcall, > will always result in an error like: > > (gdb) set overload-resolution off > (gdb) p U(34) > Attempt to use a type name as an expression > (gdb) > > I wonder if it would be clearer to the user to just do: > > if (!overload_resolution) > error (_("Constructor calls require 'set overload-resolution on'")); That is a much more user friendly approach that I will adopt in v2. > I didn't see any tests with 'set overload-resolution off' in use, so it > wasn't clear if there's maybe some path where the function call as you > have it can do anything other than throw an error? I'll add this test, too. >> + std::vector<value *> argvec (1 + args.size ()); >> + value *this_ptr; >> + if (noside == EVAL_AVOID_SIDE_EFFECTS) >> + this_ptr = value::zero (lookup_pointer_type (type), lval_memory); >> + else >> + { >> + value *alloc_val = value_allocate_space_in_inferior (type->length ()); >> + this_ptr = value_from_pointer (lookup_pointer_type (type), >> + value_as_long (alloc_val)); >> + } >> + argvec[0] = this_ptr; >> + for (size_t i = 0; i < args.size (); ++i) >> + argvec[i + 1] = args[i]->evaluate_with_coercion (exp, noside); >> + gdb::array_view<value *> arg_view = argvec; >> + >> + value *callee = nullptr; >> + int static_memfuncp; >> + find_overload_match (arg_view, ctor_name, METHOD, >> + &argvec[0], nullptr, &callee, nullptr, >> + &static_memfuncp, 0, noside); > > It would not be usual for constructors to be static, but we should > probably handle the case where static_memfuncp is true, even if it is > just: > > if (static_memfuncp) > error (_("Constructor %s is unexpectedly marked static"), name); > > Bonus points for using the DWARF assembler to exercise this error case, > but I don't think that's a hard requirement, I see this error more as a > glorified todo marker -- if we ever find a case where this is triggers > then we can understand it, and fix GDB to match. I will add this. > >> + if (callee == nullptr) >> + error (_("Cannot resolve constructor %s to any overloaded instance"), >> + name); > > I don't think this error case is being tested. Can you add a test case > for this? Boy, I've spent a lot of my day today trying to trigger this error. I am convinced it cannot be done. I'm passing METHOD to find_overload_match, which will always call error() when no matching symbol is found. Thus I think it best to replace this error check with an assertion. > >> + >> + if (noside == EVAL_AVOID_SIDE_EFFECTS) >> + return value::zero (type, not_lval); >> + >> + evaluate_subexp_do_call (exp, noside, callee, arg_view, >> + nullptr, expect_type); >> + return value_ind (this_ptr); >> +} >> + >> } >> >> /* A helper function for BINOP_ASSIGN_MODIFY. */ >> diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.cc b/gdb/testsuite/gdb.cp/infcall-ctors.cc >> new file mode 100644 >> index 00000000000..053542ec5ea >> --- /dev/null >> +++ b/gdb/testsuite/gdb.cp/infcall-ctors.cc >> @@ -0,0 +1,128 @@ >> +/* This testcase is part of GDB, the GNU debugger. >> + >> + Copyright (C) 2026 Free Software Foundation, Inc. >> + >> + This file is part of GDB. >> + >> + This program is free software; you can redistribute it and/or modify >> + it under the terms of the GNU General Public License as published by >> + the Free Software Foundation; either version 3 of the License, or >> + (at your option) any later version. >> + >> + This program is distributed in the hope that it will be useful, >> + but WITHOUT ANY WARRANTY; without even the implied warranty of >> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >> + GNU General Public License for more details. >> + >> + You should have received a copy of the GNU General Public License >> + along with this program. If not, see <http://www.gnu.org/licenses/>. */ >> + >> +struct S { > > The '{' should be on a new line. This mistake is repeated throughout > this test, I'll not point them all out. > >> + int x; >> + explicit S (int n = 0) : x (n) {} >> + S operator+ (int n) const { return S (x + n); } >> +}; >> + >> +typedef S S_td; >> +using S_u = S; >> + >> +static int add (const struct S &s1, const struct S &s2) { > > Newline before add, opening '{' on its own line. Unless this layout is > needed for the test? In which case a comment is needed. Nope -- just like a lot of the other silly formatting issues, I simply over-relied on my new editor to "do the right thing." I'll fix all the errors you've mentioned. Thank you for your review! [I'll submit a v2.] Keith ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2] Add infcall support for C++ constructor-style expressions 2026-03-26 19:17 [PATCH] Add infcall support for C++ constructor-style expressions Keith Seitz 2026-03-27 5:50 ` Eli Zaretskii 2026-04-21 13:41 ` Andrew Burgess @ 2026-04-27 16:43 ` Keith Seitz 2026-06-19 17:21 ` Keith Seitz 2026-06-27 0:06 ` Kevin Buettner 2 siblings, 2 replies; 8+ messages in thread From: Keith Seitz @ 2026-04-27 16:43 UTC (permalink / raw) To: gdb-patches Changes in v2: - Formatting fixes - Use c_isspace to skip whitespace in typename_token_for() - Merged rules for typename_for_ctor to type_exp [This adds ~20 shift/reduce conflicts.] - Updated error message when overload-resolution is off - Handle static constructors and add new tests for this case - Added test case for when ctor is not found - Test early return: remove integer return status Thanks, Keith --------- This patch adds an initial try at teaching the expression parser/evaluator to construct temporary objects requiring construction during an inferior function call. To accomplish this, I've chosen the route of modifying the parser to teach it that `Type(args)' is a function call when `Type' names a class/struct/union and is immediately followed by '(', that is, via look- ahead). A new parser token and grammar rule have been added to deal with this new production. The real work is dispatched to `type_operation::evaluate_funcall', allocating memory for the temporary and finding the most suitable constructor with `find_overload_match'. It then runs the inferior call, returning the newly constructed object. I've included many tests covering as many corner cases as I could invent, and these tests are clang clean. They also introduce no regressions on x86-64 Fedora 43 with GCC 15.2.1 and RHEL 9.4 with GCC 11.5.0. Example: Consider a C++ frame where 'struct S { int x; S(int); ... }' is in scope Before: (gdb) print S(42) ❌️ A syntax error in expression, near `10)'. After: (gdb) print S(42) $1 = {x = 42} Note that no attempt has been made to deal with templates. Hopefully a follow-on patch can address that. --- gdb/NEWS | 5 + gdb/c-exp.y | 85 +++++++- gdb/eval.c | 60 ++++++ gdb/expop.h | 5 + gdb/testsuite/gdb.cp/infcall-ctors.cc | 141 +++++++++++++ gdb/testsuite/gdb.cp/infcall-ctors.exp | 198 +++++++++++++++++++ gdb/testsuite/gdb.cp/static-ctor-infcall.c | 33 ++++ gdb/testsuite/gdb.cp/static-ctor-infcall.exp | 87 ++++++++ 8 files changed, 605 insertions(+), 9 deletions(-) create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.cc create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.exp create mode 100644 gdb/testsuite/gdb.cp/static-ctor-infcall.c create mode 100644 gdb/testsuite/gdb.cp/static-ctor-infcall.exp diff --git a/gdb/NEWS b/gdb/NEWS index e233906153a..24d26d29ea5 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -75,6 +75,11 @@ * The Windows native target now supports scheduler-locking. E.g., "set scheduler-locking on" now works. Previously it gave an error. +* In C++ GDB now accepts constructor-style expressions "TYPE (ARGS)" + when TYPE names a class, struct, or union in the current expression + context. This allows objects to be constructed directly during + expression evaluation. + * New targets GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux* diff --git a/gdb/c-exp.y b/gdb/c-exp.y index 2829d8bccba..73f88bd84f1 100644 --- a/gdb/c-exp.y +++ b/gdb/c-exp.y @@ -231,7 +231,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value); %token <ssym> NAME /* BLOCKNAME defined below to give it higher precedence. */ %token <ssym> UNKNOWN_CPP_NAME %token <voidval> COMPLETE -%token <tsym> TYPENAME +%token <tsym> TYPENAME TYPENAME_CTOR %token <theclass> CLASSNAME /* ObjC Class name */ %type <sval> name %type <qval> qual_field_name field_name field_name_or_complete @@ -314,6 +314,13 @@ type_exp: type { pstate->push_new<type_operation> ($1); } + | TYPENAME_CTOR + { + /* Constructor-style call: lexer returns TYPENAME_CTOR + only when '(' follows the type name (see + typename_token_for). */ + pstate->push_new<type_operation> ($1.type); + } | TYPEOF '(' exp ')' { pstate->wrap<typeof_operation> (); @@ -534,6 +541,26 @@ msgarg : name ':' exp { add_msglist(0, 0); } ; +exp : type_exp '(' + { pstate->start_arglist (); } + arglist ')' %prec ARROW + { + std::vector<operation_up> args + = pstate->pop_vector (pstate->end_arglist ()); + operation_up type_op = pstate->pop (); + pstate->push_new<funcall_operation> + (std::move (type_op), std::move (args)); + } + ; + +exp : type_exp '(' ')' %prec ARROW + { + operation_up type_op = pstate->pop (); + pstate->push_new<funcall_operation> + (std::move (type_op), std::vector<operation_up> ()); + } + ; + exp : exp '(' /* This is to save the value of arglist_len being accumulated by an outer function call. */ @@ -3118,6 +3145,34 @@ static int popping; built up. */ static auto_obstack name_obstack; +/* Return TYPENAME_CTOR only when the next token is '(', so this + token is used solely for constructor calls. Otherwise return TYPENAME. + NAME_END is the character just past the name (e.g. yylval.sval.ptr + + yylval.sval.length). */ + +static int +typename_token_for (struct parser_state *par_state, struct type *type, + const char *name_end) +{ + if (type == nullptr + || par_state->language ()->la_language != language_cplus) + return TYPENAME; + type = check_typedef (type); + if (type->code () != TYPE_CODE_STRUCT && type->code () != TYPE_CODE_UNION) + return TYPENAME; + /* Only return TYPENAME_CTOR when followed by '('. */ + if (name_end == nullptr) + return TYPENAME; + { + const char *p = name_end; + while (c_isspace (*p)) + ++p; + if (*p != '(') + return TYPENAME; + } + return TYPENAME_CTOR; +} + /* Classify a NAME token. The contents of the token are in `yylval'. Updates yylval and returns the new token type. BLOCK is the block in which lookups start; this can be NULL to mean the global scope. @@ -3161,7 +3216,8 @@ classify_name (struct parser_state *par_state, const struct block *block, if (bsym.symbol != NULL) { yylval.tsym.type = bsym.symbol->type (); - return TYPENAME; + return typename_token_for (par_state, yylval.tsym.type, + yylval.sval.ptr + yylval.sval.length); } } @@ -3188,7 +3244,8 @@ classify_name (struct parser_state *par_state, const struct block *block, if (bsym.symbol && bsym.symbol->loc_class () == LOC_TYPEDEF) { yylval.tsym.type = bsym.symbol->type (); - return TYPENAME; + return typename_token_for (par_state, yylval.tsym.type, + yylval.sval.ptr + yylval.sval.length); } /* See if it's an ObjC classname. */ @@ -3274,7 +3331,9 @@ classify_inner_name (struct parser_state *par_state, if (base_type != NULL) { yylval.tsym.type = base_type; - return TYPENAME; + return typename_token_for (par_state, yylval.tsym.type, + yylval.ssym.stoken.ptr + + yylval.ssym.stoken.length); } return ERROR; @@ -3294,14 +3353,18 @@ classify_inner_name (struct parser_state *par_state, if (base_type != NULL) { yylval.tsym.type = base_type; - return TYPENAME; + return typename_token_for (par_state, yylval.tsym.type, + yylval.ssym.stoken.ptr + + yylval.ssym.stoken.length); } } return ERROR; case LOC_TYPEDEF: yylval.tsym.type = yylval.ssym.sym.symbol->type (); - return TYPENAME; + return typename_token_for (par_state, yylval.tsym.type, + yylval.ssym.stoken.ptr + + yylval.ssym.stoken.length); default: return NAME; @@ -3336,7 +3399,7 @@ handle_qualified_field_name (qualified_name_token token) int kind = classify_inner_name (pstate, pstate->expression_context_block, type); - if (kind != TYPENAME) + if (kind != TYPENAME && kind != TYPENAME_CTOR) error (_("could not find type '%s'"), accum.c_str ()); type = yylval.tsym.type; @@ -3388,7 +3451,8 @@ yylex (void) current.token = classify_name (pstate, pstate->expression_context_block, is_quoted_name, last_lex_was_structop); if (pstate->language ()->la_language != language_cplus - || (current.token != TYPENAME && current.token != COLONCOLON + || (current.token != TYPENAME && current.token != TYPENAME_CTOR + && current.token != COLONCOLON && current.token != FILENAME && (cpstate->assume_classification == TYPE_CODE_UNDEF || current.token != NAME)) @@ -3434,6 +3498,7 @@ yylex (void) else { gdb_assert (current.token == TYPENAME + || current.token == TYPENAME_CTOR || cpstate->assume_classification != TYPE_CODE_UNDEF); search_block = pstate->expression_context_block; obstack_grow (&name_obstack, current.value.sval.ptr, @@ -3464,7 +3529,8 @@ yylex (void) context_type); /* We keep going until we either run out of names, or until we have a qualified name which is not a type. */ - if (classification != TYPENAME && classification != NAME) + if (classification != TYPENAME && classification != TYPENAME_CTOR + && classification != NAME) break; /* Accept up to this token. */ @@ -3595,6 +3661,7 @@ c_print_token (FILE *file, int type, YYSTYPE value) break; case TYPENAME: + case TYPENAME_CTOR: parser_fprintf (file, "tsym<type=%s, name=%s>", value.tsym.type->safe_name (), copy_name (value.tsym.stoken).c_str ()); diff --git a/gdb/eval.c b/gdb/eval.c index a00774bda3d..933415ce028 100644 --- a/gdb/eval.c +++ b/gdb/eval.c @@ -1872,6 +1872,66 @@ type_operation::evaluate (struct type *expect_type, struct expression *exp, error (_("Attempt to use a type name as an expression")); } +value * +type_operation::evaluate_funcall (struct type *expect_type, + struct expression *exp, + enum noside noside, + const std::vector<operation_up> &args) +{ + struct type *type = std::get<0> (m_storage); + type = check_typedef (type); + + /* Constructor-style call Type(args) is only for C++ aggregate types. */ + gdb_assert (exp->language_defn->la_language == language_cplus); + + const char *name = type->name (); + if (name == nullptr) + error (_("Cannot call constructor of unnamed type")); + + /* Get the constructor name from the type name. */ + gdb::unique_xmalloc_ptr<char> ctor_name_ptr = cp_func_name (name); + const char *ctor_name = + (ctor_name_ptr != nullptr) ? ctor_name_ptr.get () : name; + + if (!overload_resolution) + error (_("Constructor calls require 'overload-resolution' to be on")); + + std::vector<value *> argvec (1 + args.size ()); + value *this_ptr; + if (noside == EVAL_AVOID_SIDE_EFFECTS) + this_ptr = value::zero (lookup_pointer_type (type), lval_memory); + else + { + value *alloc_val = value_allocate_space_in_inferior (type->length ()); + this_ptr = value_from_pointer (lookup_pointer_type (type), + value_as_long (alloc_val)); + } + argvec[0] = this_ptr; + for (size_t i = 0; i < args.size (); ++i) + argvec[i + 1] = args[i]->evaluate_with_coercion (exp, noside); + gdb::array_view<value *> arg_view = argvec; + + value *callee = nullptr; + int static_memfuncp; + find_overload_match (arg_view, ctor_name, METHOD, + &argvec[0], nullptr, &callee, nullptr, + &static_memfuncp, 0, noside); + /* If find_overload_match fails, it will have already thrown an error. */ + gdb_assert (callee != nullptr); + + /* C++ constructors are methods. If the found method is marked static, + treat it as an explicit failure until we have a real case. */ + if (static_memfuncp) + error (_("Constructor %s is unexpectedly marked static"), name); + + if (noside == EVAL_AVOID_SIDE_EFFECTS) + return value::zero (type, not_lval); + + evaluate_subexp_do_call (exp, noside, callee, arg_view, + nullptr, expect_type); + return value_ind (this_ptr); +} + } /* A helper function for BINOP_ASSIGN_MODIFY. */ diff --git a/gdb/expop.h b/gdb/expop.h index c58a8d7ac37..9839e3bffa9 100644 --- a/gdb/expop.h +++ b/gdb/expop.h @@ -1597,6 +1597,11 @@ class type_operation struct expression *exp, enum noside noside) override; + value *evaluate_funcall (struct type *expect_type, + struct expression *exp, + enum noside noside, + const std::vector<operation_up> &args) override; + enum exp_opcode opcode () const override { return OP_TYPE; } diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.cc b/gdb/testsuite/gdb.cp/infcall-ctors.cc new file mode 100644 index 00000000000..5177f5de5da --- /dev/null +++ b/gdb/testsuite/gdb.cp/infcall-ctors.cc @@ -0,0 +1,141 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +struct S +{ + int x; + explicit S (int n = 0) : x (n) {} + S operator+ (int n) const + { + return S (x + n); + } +}; + +typedef S S_td; +using S_u = S; + +static int +add (const struct S &s1, const struct S &s2) +{ + return s1.x + s2.x; +} + +/* Non-trivially copyable: copy-constructing swaps the two members. */ +struct swapcopy +{ + int lo; + int hi; + swapcopy (int l, int h) : lo (l), hi (h) {} + swapcopy (const swapcopy &o) : lo (o.hi), hi (o.lo) {} +}; + +/* Pass swapcopy by value so the call must copy-construct the argument. */ + +static int +swapcopy_first_byval (swapcopy c) +{ + return c.lo; +} + +struct Base +{ + int x; + Base () : x (0) {} + explicit Base (int n) : x (n) {} + Base (const Base &other) : x (other.x) {} +}; + +typedef Base Base_td; +using Base_u = Base; + +namespace NS +{ +class Derived : public Base +{ +public: + int y; + Derived () : Base (), y (0) {} + Derived (int a, int b) : Base (a), y (b) {} +}; + +typedef Base NsBaseTd; +using NsBaseU = Base; +typedef Derived Derived_td; +using Derived_u = Derived; + +union U +{ + int a; + U () : a (0) {} + explicit U (int n) : a (n) {} +}; + +typedef U Nu_td; +} + +union U +{ + int x; + U () : x (0) {} + explicit U (int n) : x (n) {} +}; + +typedef U U_td; + +static int +plus_one (U u) +{ + return u.x + 1; +} + +int +main (void) +{ + S s0; /* default: x = 0 */ + S s1 (42); /* x = 42 */ + S s2 (s1 + 2); /* x = 44 */ + S s3 = s1; + NS::Derived d; /* Base part x=0, Derived part y=0 */ + NS::Derived d1 (10, 20); /* Base part x=10, Derived part y=20 */ + Base b; /* x=0 */ + Base b1 (5); /* x=5 */ + Base b2 (d1); /* x=10 */ + U u0; /* default: x = 0 */ + U u1 (42); /* x = 42 */ + NS::U uv0; /* default: a = 0 */ + NS::U uv1 (7); /* a = 7 */ + S_td s_td = S_td (11); + S_u s_u = S_u (12); + Base_td b_td = Base_td (8); + Base_u b_u = Base_u (9); + NS::NsBaseTd nsb_td = NS::NsBaseTd (13); + NS::NsBaseU nsb_u = NS::NsBaseU (14); + NS::Derived_td d_td = NS::Derived_td (2, 3); + NS::Derived_u d_u = NS::Derived_u (4, 5); + U_td u_td = U_td (15); + NS::Nu_td nu_u = NS::Nu_td (16); + swapcopy swp (30, 40); + int result = add (s1, s2); + return result + d.x + d.y + b.x + b1.x + b2.x + d.x + d.y \ + + d1.x + d1.y + plus_one (u0) + u1.x + uv0.a + uv1.a \ + + s_td.x + s_u.x + b_td.x + b_u.x + nsb_td.x + nsb_u.x \ + + d_td.x + d_td.y + d_u.x + d_u.y + u_td.x + nu_u.a \ + + swapcopy_first_byval (swapcopy (100, 200)) \ + + swapcopy_first_byval (swp); /* stop-here */ +} diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.exp b/gdb/testsuite/gdb.cp/infcall-ctors.exp new file mode 100644 index 00000000000..2a92266ccfe --- /dev/null +++ b/gdb/testsuite/gdb.cp/infcall-ctors.exp @@ -0,0 +1,198 @@ +# Copyright (C) 2026 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# This file is part of the gdb testsuite. + +# Test constructor calls and casting via inferior function calls: +# - Simple struct S with constructor (int, default 0). +# - Base and Derived; cast derived to base, construct Base from Derived. +# - Typedef and using aliases: ctor resolution must use the class ctor even +# when the expression names a typedef or alias (DWARF may name types +# differently from the underlying class tag). +# - swapcopy: two ints with a user-defined copy ctor that swaps them +# (non-trivially copyable); pass-by-value in inferior calls must run it. +# - Constructor-style calls require overload resolution; verify the error +# when it is disabled. + +require allow_cplus_tests + +standard_testfile .cc + +if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} { + return +} + +if {![runto_main]} { + return +} + +# Run to stop-here to ensure all locals are initialized. +gdb_breakpoint [gdb_get_line_number "stop-here"] +gdb_continue_to_breakpoint "stop-here" + +# Simple tests involving "struct S". +gdb_test "ptype S" [multi_line \ + {type = struct S \{} \ + { int x;} \ + "" \ + { S\(int\);} \ + { S operator\+\(int\) const;} \ + {\}}] + +gdb_test "print s0" " = \\{x = 0\\}" "print s0 default ctor" +gdb_test "print s1" " = \\{x = 42\\}" "print s1 with 42" +gdb_test "print S(99)" " = \\{x = 99\\}" "construct S(99) via inferior function call" + +gdb_test "ptype swapcopy" [multi_line \ + {type = struct swapcopy \{} \ + { int lo;} \ + { int hi;} \ + "" \ + { swapcopy\(int, int\);} \ + { swapcopy\(const swapcopy ?&\);} \ + {\}}] +gdb_test "print swapcopy(1, 2)" { = \{lo = 1, hi = 2\}} \ + "construct swapcopy via inferior function call" +gdb_test {print swapcopy($)} {= \{lo = 2, hi = 1\}} \ + "call copy ctor on object via history" +gdb_test "print swapcopy_first_byval(swapcopy(10, 20))" "= 20" \ + "pass-by-value copy ctor swaps members" +gdb_test "print swapcopy_first_byval(swp)" "= 40" \ + "swapcopy local passed by value uses copy ctor" + +gdb_test "print S_td(33)" " = \\{x = 33\\}" \ + "construct S via typedef name (not underlying struct tag)" +gdb_test "print S_u(34)" " = \\{x = 34\\}" "construct S via using alias" +gdb_test "print Base_td(8)" { = \{x = 8\}} "construct Base via typedef name" +gdb_test "print Base_u(9)" { = \{x = 9\}} "construct Base via using alias" +gdb_test "print NS::NsBaseTd(5)" { = \{x = 5\}} \ + "construct Base via typedef in namespace" +gdb_test "print NS::NsBaseU(6)" { = \{x = 6\}} \ + "construct Base via using alias in namespace" +gdb_test "print NS::Derived_td(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \ + "construct NS::Derived via typedef name" +gdb_test "print NS::Derived_u(3, 4)" { = \{<Base> = \{x = 3\}, y = 4\}} \ + "construct NS::Derived via using alias" +gdb_test "print U_td(55)" { = \{x = 55\}} \ + "construct global union U via typedef name" +gdb_test "print NS::Nu_td(66)" { = \{a = 66\}} \ + "construct NS::U via typedef name" + +# Tests involving "Base" and "Derived". +set base_re [multi_line \ + {type = struct Base \{} \ + { int x;} \ + "" \ + { Base\(void\);} \ + { Base\(int\);} \ + { Base\(const Base ?&\);} \ + {\}}] +gdb_test "ptype Base" $base_re + +set derived_re [multi_line \ + {type = class NS::Derived : public Base \{} \ + { public:} \ + { int y;} \ + "" \ + { Derived\(void\);} \ + { Derived\(int, int\);} \ + {\}}] +gdb_test "ptype NS::Derived" $derived_re + +gdb_test "print d" { = \{<Base> = \{x = 0\}, y = 0\}} +gdb_test "print d1" { = \{<Base> = \{x = 10\}, y = 20\}} +gdb_test "print b" { = \{x = 0\}} +gdb_test "print b1" { = \{x = 5\}} +gdb_test "print b2" { = \{x = 10\}} +gdb_test "print (Base)(d1)" { = \{x = 10\}} "cast (Base)(d1) slices to Base" +gdb_test "print Base(d1)" { = \{x = 10\}} "construct Base(d1) from Derived" +gdb_test "print Base()" { = \{x = 0\}} "construct Base() default" +gdb_test "print Base(7)" { = \{x = 7\}} "construct Base(7) with argument" +gdb_test "print NS::Derived()" { = \{<Base> = \{x = 0\}, y = 0\}} \ + "construct NS::Derived() via inferior function call" +gdb_test "print NS::Derived().y" " = 0" "construct NS::Derived() and access .y" +gdb_test "print NS::Derived(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \ + "construct NS::Derived(1, 2) via inferior function call" +gdb_test "print NS::Derived(1, 2).y" " = 2" \ + "construct NS::Derived(1, 2) and access .y" +gdb_test "print ((Base)d1).x" " = 10" "cast ((Base)d1).x" + +# Print the types of these "temporary" objects. +gdb_test "ptype Base()" $base_re "ptype of Base temporary" +gdb_test "ptype NS::Derived(15, 25)" $derived_re \ + "ptype of NS::Derived temporary" + +# Tests involving unions. +gdb_test "ptype U" [multi_line \ + {type = union U \{} \ + { int x;} \ + "" \ + { U\(void\);} \ + { U\(int\);} \ + {\}}] \ + "ptype U" + +gdb_test "ptype NS::U" [multi_line \ + {type = union NS::U \{} \ + { int a;} \ + "" \ + { U\(void\);} \ + { U\(int\);} \ + {\}}] \ + "ptype NS::U" + +gdb_test "print u0" { = \{x = 0\}} +gdb_test "print u1" { = \{x = 42\}} +gdb_test "print uv0" { = \{a = 0\}} +gdb_test "print uv1" { = \{a = 7\}} +gdb_test "print U()" { = \{x = 0\}} "construct U() via inferior function call" +gdb_test "print U(99)" { = \{x = 99\}} "construct U(99) via inferior function call" +gdb_test "print U(99).x" " = 99" "construct U(99) and access .x" +gdb_test "print NS::U()" { = \{a = 0\}} \ + "construct NS::U() via inferior function call" +gdb_test "print NS::U(13)" { = \{a = 13\}} \ + "construct NS::U(13) via inferior function call" +gdb_test "print NS::U(13).a" " = 13" \ + "construct NS::U(13) and access .a" + +set u_re [multi_line \ + {type = union U \{} \ + { int x;} \ + "" \ + { U\(void\);} \ + { U\(int\);} \ + {\}}] +set ns_u_re [multi_line \ + {type = union NS::U \{} \ + { int a;} \ + "" \ + { U\(void\);} \ + { U\(int\);} \ + {\}}] +gdb_test "ptype U()" $u_re "ptype of U temporary" +gdb_test "ptype NS::U(99)" $ns_u_re "ptype of NS::U temporary" + +gdb_test "p plus_one(U(42))" "= 43" "temporary in function call" +gdb_test "p add(S(1), S(20))" "= 21" "add two temporaries of S" +gdb_test "p plus_one(U(add (S(4), S(6))))" "= 11" \ + "nested function call using temporaries" + +gdb_test_no_output "set overload-resolution off" +gdb_test "print S(1)" \ + {Constructor calls require 'overload-resolution' to be on} \ + "constructor call fails when overload-resolution is off" +gdb_test_no_output "set overload-resolution on" +gdb_test "print S(1)" " = \\{x = 1\\}" \ + "constructor call works after overload-resolution turned back on" diff --git a/gdb/testsuite/gdb.cp/static-ctor-infcall.c b/gdb/testsuite/gdb.cp/static-ctor-infcall.c new file mode 100644 index 00000000000..0516196e6d8 --- /dev/null +++ b/gdb/testsuite/gdb.cp/static-ctor-infcall.c @@ -0,0 +1,33 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2026 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* Address range for hand-written DWARF describing a static constructor. */ + +extern "C" void +dwarf_static_ctor_stub (void) +{ + asm ("dwarf_static_ctor_stub_label: .globl dwarf_static_ctor_stub_label"); +} + +int +main (void) +{ + asm ("main_label: .globl main_label"); + return 0; +} diff --git a/gdb/testsuite/gdb.cp/static-ctor-infcall.exp b/gdb/testsuite/gdb.cp/static-ctor-infcall.exp new file mode 100644 index 00000000000..cdc2db170d6 --- /dev/null +++ b/gdb/testsuite/gdb.cp/static-ctor-infcall.exp @@ -0,0 +1,87 @@ +# Copyright 2026 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Exercise the "Constructor %s is unexpectedly marked static" error in +# type_operation::evaluate_funcall. + +load_lib dwarf.exp + +require dwarf2_support +require allow_cplus_tests + +standard_testfile .c -dw.S + +set asm_file [standard_output_file $srcfile2] +Dwarf::assemble $asm_file { + declare_labels int_label struct_label ptr_label ctor_subr_label + + get_func_info dwarf_static_ctor_stub {nodebug c++} + + cu {} { + compile_unit { + DW_AT_language @DW_LANG_C_plus_plus + DW_AT_name dw2-static-ctor-infcall.cc + DW_AT_comp_dir /tmp + } { + int_label: base_type { + DW_AT_name int + DW_AT_encoding @DW_ATE_signed + DW_AT_byte_size 4 DW_FORM_sdata + } + + struct_label: structure_type { + DW_AT_name dwarf_static_ctor + DW_AT_byte_size 4 DW_FORM_sdata + } { + member { + DW_AT_name x + DW_AT_type :$int_label + DW_AT_data_member_location 0 data1 + } + ptr_label: pointer_type { + DW_AT_type :$struct_label + } + ctor_subr_label: subroutine_type { + DW_AT_type :$int_label + } { + formal_parameter { + DW_AT_type :$ptr_label + } + } + subprogram { + DW_AT_name dwarf_static_ctor + DW_AT_external 1 flag + DW_AT_low_pc $dwarf_static_ctor_stub_start addr + DW_AT_high_pc $dwarf_static_ctor_stub_end addr + DW_AT_type :$ctor_subr_label + } + } + } + } +} + +if {[prepare_for_testing "failed to prepare" ${testfile} \ + [list $srcfile $asm_file] {nodebug c++}]} { + return +} + +if {![runto_main]} { + return +} + +gdb_test_no_output "set language c++" + +gdb_test "print dwarf_static_ctor()" \ + "Constructor dwarf_static_ctor is unexpectedly marked static" base-commit: 8e78d43dbcd122e852d59abe29c8d6aeb6c04925 -- 2.53.0 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] Add infcall support for C++ constructor-style expressions 2026-04-27 16:43 ` [PATCH v2] " Keith Seitz @ 2026-06-19 17:21 ` Keith Seitz 2026-06-27 0:06 ` Kevin Buettner 1 sibling, 0 replies; 8+ messages in thread From: Keith Seitz @ 2026-06-19 17:21 UTC (permalink / raw) To: gdb-patches Ping. On 4/27/26 9:43 AM, Keith Seitz wrote: > Changes in v2: > - Formatting fixes > - Use c_isspace to skip whitespace in typename_token_for() > - Merged rules for typename_for_ctor to type_exp > [This adds ~20 shift/reduce conflicts.] > - Updated error message when overload-resolution is off > - Handle static constructors and add new tests for this case > - Added test case for when ctor is not found > - Test early return: remove integer return status > > Thanks, > Keith > > --------- > > This patch adds an initial try at teaching the expression parser/evaluator > to construct temporary objects requiring construction during an inferior > function call. > > To accomplish this, I've chosen the route of modifying the parser to > teach it that `Type(args)' is a function call when `Type' names a > class/struct/union and is immediately followed by '(', that is, via look- > ahead). A new parser token and grammar rule have been added to deal > with this new production. > > The real work is dispatched to `type_operation::evaluate_funcall', > allocating memory for the temporary and finding the most suitable constructor > with `find_overload_match'. It then runs the inferior call, returning > the newly constructed object. > > I've included many tests covering as many corner cases as I could invent, > and these tests are clang clean. They also introduce no regressions on > x86-64 Fedora 43 with GCC 15.2.1 and RHEL 9.4 with GCC 11.5.0. > > Example: > Consider a C++ frame where 'struct S { int x; S(int); ... }' is in scope > > Before: > (gdb) print S(42) > ❌️ A syntax error in expression, near `10)'. > > After: > (gdb) print S(42) > $1 = {x = 42} > > Note that no attempt has been made to deal with templates. Hopefully > a follow-on patch can address that. > --- > gdb/NEWS | 5 + > gdb/c-exp.y | 85 +++++++- > gdb/eval.c | 60 ++++++ > gdb/expop.h | 5 + > gdb/testsuite/gdb.cp/infcall-ctors.cc | 141 +++++++++++++ > gdb/testsuite/gdb.cp/infcall-ctors.exp | 198 +++++++++++++++++++ > gdb/testsuite/gdb.cp/static-ctor-infcall.c | 33 ++++ > gdb/testsuite/gdb.cp/static-ctor-infcall.exp | 87 ++++++++ > 8 files changed, 605 insertions(+), 9 deletions(-) > create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.cc > create mode 100644 gdb/testsuite/gdb.cp/infcall-ctors.exp > create mode 100644 gdb/testsuite/gdb.cp/static-ctor-infcall.c > create mode 100644 gdb/testsuite/gdb.cp/static-ctor-infcall.exp > > diff --git a/gdb/NEWS b/gdb/NEWS > index e233906153a..24d26d29ea5 100644 > --- a/gdb/NEWS > +++ b/gdb/NEWS > @@ -75,6 +75,11 @@ > * The Windows native target now supports scheduler-locking. E.g., > "set scheduler-locking on" now works. Previously it gave an error. > > +* In C++ GDB now accepts constructor-style expressions "TYPE (ARGS)" > + when TYPE names a class, struct, or union in the current expression > + context. This allows objects to be constructed directly during > + expression evaluation. > + > * New targets > > GNU/Linux/MicroBlaze (gdbserver) microblazeel-*linux* > diff --git a/gdb/c-exp.y b/gdb/c-exp.y > index 2829d8bccba..73f88bd84f1 100644 > --- a/gdb/c-exp.y > +++ b/gdb/c-exp.y > @@ -231,7 +231,7 @@ static void c_print_token (FILE *file, int type, YYSTYPE value); > %token <ssym> NAME /* BLOCKNAME defined below to give it higher precedence. */ > %token <ssym> UNKNOWN_CPP_NAME > %token <voidval> COMPLETE > -%token <tsym> TYPENAME > +%token <tsym> TYPENAME TYPENAME_CTOR > %token <theclass> CLASSNAME /* ObjC Class name */ > %type <sval> name > %type <qval> qual_field_name field_name field_name_or_complete > @@ -314,6 +314,13 @@ type_exp: type > { > pstate->push_new<type_operation> ($1); > } > + | TYPENAME_CTOR > + { > + /* Constructor-style call: lexer returns TYPENAME_CTOR > + only when '(' follows the type name (see > + typename_token_for). */ > + pstate->push_new<type_operation> ($1.type); > + } > | TYPEOF '(' exp ')' > { > pstate->wrap<typeof_operation> (); > @@ -534,6 +541,26 @@ msgarg : name ':' exp > { add_msglist(0, 0); } > ; > > +exp : type_exp '(' > + { pstate->start_arglist (); } > + arglist ')' %prec ARROW > + { > + std::vector<operation_up> args > + = pstate->pop_vector (pstate->end_arglist ()); > + operation_up type_op = pstate->pop (); > + pstate->push_new<funcall_operation> > + (std::move (type_op), std::move (args)); > + } > + ; > + > +exp : type_exp '(' ')' %prec ARROW > + { > + operation_up type_op = pstate->pop (); > + pstate->push_new<funcall_operation> > + (std::move (type_op), std::vector<operation_up> ()); > + } > + ; > + > exp : exp '(' > /* This is to save the value of arglist_len > being accumulated by an outer function call. */ > @@ -3118,6 +3145,34 @@ static int popping; > built up. */ > static auto_obstack name_obstack; > > +/* Return TYPENAME_CTOR only when the next token is '(', so this > + token is used solely for constructor calls. Otherwise return TYPENAME. > + NAME_END is the character just past the name (e.g. yylval.sval.ptr + > + yylval.sval.length). */ > + > +static int > +typename_token_for (struct parser_state *par_state, struct type *type, > + const char *name_end) > +{ > + if (type == nullptr > + || par_state->language ()->la_language != language_cplus) > + return TYPENAME; > + type = check_typedef (type); > + if (type->code () != TYPE_CODE_STRUCT && type->code () != TYPE_CODE_UNION) > + return TYPENAME; > + /* Only return TYPENAME_CTOR when followed by '('. */ > + if (name_end == nullptr) > + return TYPENAME; > + { > + const char *p = name_end; > + while (c_isspace (*p)) > + ++p; > + if (*p != '(') > + return TYPENAME; > + } > + return TYPENAME_CTOR; > +} > + > /* Classify a NAME token. The contents of the token are in `yylval'. > Updates yylval and returns the new token type. BLOCK is the block > in which lookups start; this can be NULL to mean the global scope. > @@ -3161,7 +3216,8 @@ classify_name (struct parser_state *par_state, const struct block *block, > if (bsym.symbol != NULL) > { > yylval.tsym.type = bsym.symbol->type (); > - return TYPENAME; > + return typename_token_for (par_state, yylval.tsym.type, > + yylval.sval.ptr + yylval.sval.length); > } > } > > @@ -3188,7 +3244,8 @@ classify_name (struct parser_state *par_state, const struct block *block, > if (bsym.symbol && bsym.symbol->loc_class () == LOC_TYPEDEF) > { > yylval.tsym.type = bsym.symbol->type (); > - return TYPENAME; > + return typename_token_for (par_state, yylval.tsym.type, > + yylval.sval.ptr + yylval.sval.length); > } > > /* See if it's an ObjC classname. */ > @@ -3274,7 +3331,9 @@ classify_inner_name (struct parser_state *par_state, > if (base_type != NULL) > { > yylval.tsym.type = base_type; > - return TYPENAME; > + return typename_token_for (par_state, yylval.tsym.type, > + yylval.ssym.stoken.ptr > + + yylval.ssym.stoken.length); > } > > return ERROR; > @@ -3294,14 +3353,18 @@ classify_inner_name (struct parser_state *par_state, > if (base_type != NULL) > { > yylval.tsym.type = base_type; > - return TYPENAME; > + return typename_token_for (par_state, yylval.tsym.type, > + yylval.ssym.stoken.ptr > + + yylval.ssym.stoken.length); > } > } > return ERROR; > > case LOC_TYPEDEF: > yylval.tsym.type = yylval.ssym.sym.symbol->type (); > - return TYPENAME; > + return typename_token_for (par_state, yylval.tsym.type, > + yylval.ssym.stoken.ptr > + + yylval.ssym.stoken.length); > > default: > return NAME; > @@ -3336,7 +3399,7 @@ handle_qualified_field_name (qualified_name_token token) > int kind = classify_inner_name (pstate, > pstate->expression_context_block, > type); > - if (kind != TYPENAME) > + if (kind != TYPENAME && kind != TYPENAME_CTOR) > error (_("could not find type '%s'"), accum.c_str ()); > > type = yylval.tsym.type; > @@ -3388,7 +3451,8 @@ yylex (void) > current.token = classify_name (pstate, pstate->expression_context_block, > is_quoted_name, last_lex_was_structop); > if (pstate->language ()->la_language != language_cplus > - || (current.token != TYPENAME && current.token != COLONCOLON > + || (current.token != TYPENAME && current.token != TYPENAME_CTOR > + && current.token != COLONCOLON > && current.token != FILENAME > && (cpstate->assume_classification == TYPE_CODE_UNDEF > || current.token != NAME)) > @@ -3434,6 +3498,7 @@ yylex (void) > else > { > gdb_assert (current.token == TYPENAME > + || current.token == TYPENAME_CTOR > || cpstate->assume_classification != TYPE_CODE_UNDEF); > search_block = pstate->expression_context_block; > obstack_grow (&name_obstack, current.value.sval.ptr, > @@ -3464,7 +3529,8 @@ yylex (void) > context_type); > /* We keep going until we either run out of names, or until > we have a qualified name which is not a type. */ > - if (classification != TYPENAME && classification != NAME) > + if (classification != TYPENAME && classification != TYPENAME_CTOR > + && classification != NAME) > break; > > /* Accept up to this token. */ > @@ -3595,6 +3661,7 @@ c_print_token (FILE *file, int type, YYSTYPE value) > break; > > case TYPENAME: > + case TYPENAME_CTOR: > parser_fprintf (file, "tsym<type=%s, name=%s>", > value.tsym.type->safe_name (), > copy_name (value.tsym.stoken).c_str ()); > diff --git a/gdb/eval.c b/gdb/eval.c > index a00774bda3d..933415ce028 100644 > --- a/gdb/eval.c > +++ b/gdb/eval.c > @@ -1872,6 +1872,66 @@ type_operation::evaluate (struct type *expect_type, struct expression *exp, > error (_("Attempt to use a type name as an expression")); > } > > +value * > +type_operation::evaluate_funcall (struct type *expect_type, > + struct expression *exp, > + enum noside noside, > + const std::vector<operation_up> &args) > +{ > + struct type *type = std::get<0> (m_storage); > + type = check_typedef (type); > + > + /* Constructor-style call Type(args) is only for C++ aggregate types. */ > + gdb_assert (exp->language_defn->la_language == language_cplus); > + > + const char *name = type->name (); > + if (name == nullptr) > + error (_("Cannot call constructor of unnamed type")); > + > + /* Get the constructor name from the type name. */ > + gdb::unique_xmalloc_ptr<char> ctor_name_ptr = cp_func_name (name); > + const char *ctor_name = > + (ctor_name_ptr != nullptr) ? ctor_name_ptr.get () : name; > + > + if (!overload_resolution) > + error (_("Constructor calls require 'overload-resolution' to be on")); > + > + std::vector<value *> argvec (1 + args.size ()); > + value *this_ptr; > + if (noside == EVAL_AVOID_SIDE_EFFECTS) > + this_ptr = value::zero (lookup_pointer_type (type), lval_memory); > + else > + { > + value *alloc_val = value_allocate_space_in_inferior (type->length ()); > + this_ptr = value_from_pointer (lookup_pointer_type (type), > + value_as_long (alloc_val)); > + } > + argvec[0] = this_ptr; > + for (size_t i = 0; i < args.size (); ++i) > + argvec[i + 1] = args[i]->evaluate_with_coercion (exp, noside); > + gdb::array_view<value *> arg_view = argvec; > + > + value *callee = nullptr; > + int static_memfuncp; > + find_overload_match (arg_view, ctor_name, METHOD, > + &argvec[0], nullptr, &callee, nullptr, > + &static_memfuncp, 0, noside); > + /* If find_overload_match fails, it will have already thrown an error. */ > + gdb_assert (callee != nullptr); > + > + /* C++ constructors are methods. If the found method is marked static, > + treat it as an explicit failure until we have a real case. */ > + if (static_memfuncp) > + error (_("Constructor %s is unexpectedly marked static"), name); > + > + if (noside == EVAL_AVOID_SIDE_EFFECTS) > + return value::zero (type, not_lval); > + > + evaluate_subexp_do_call (exp, noside, callee, arg_view, > + nullptr, expect_type); > + return value_ind (this_ptr); > +} > + > } > > /* A helper function for BINOP_ASSIGN_MODIFY. */ > diff --git a/gdb/expop.h b/gdb/expop.h > index c58a8d7ac37..9839e3bffa9 100644 > --- a/gdb/expop.h > +++ b/gdb/expop.h > @@ -1597,6 +1597,11 @@ class type_operation > struct expression *exp, > enum noside noside) override; > > + value *evaluate_funcall (struct type *expect_type, > + struct expression *exp, > + enum noside noside, > + const std::vector<operation_up> &args) override; > + > enum exp_opcode opcode () const override > { return OP_TYPE; } > > diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.cc b/gdb/testsuite/gdb.cp/infcall-ctors.cc > new file mode 100644 > index 00000000000..5177f5de5da > --- /dev/null > +++ b/gdb/testsuite/gdb.cp/infcall-ctors.cc > @@ -0,0 +1,141 @@ > +/* This testcase is part of GDB, the GNU debugger. > + > + Copyright (C) 2026 Free Software Foundation, Inc. > + > + This file is part of GDB. > + > + This program is free software; you can redistribute it and/or modify > + it under the terms of the GNU General Public License as published by > + the Free Software Foundation; either version 3 of the License, or > + (at your option) any later version. > + > + This program is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + GNU General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with this program. If not, see <http://www.gnu.org/licenses/>. */ > + > +struct S > +{ > + int x; > + explicit S (int n = 0) : x (n) {} > + S operator+ (int n) const > + { > + return S (x + n); > + } > +}; > + > +typedef S S_td; > +using S_u = S; > + > +static int > +add (const struct S &s1, const struct S &s2) > +{ > + return s1.x + s2.x; > +} > + > +/* Non-trivially copyable: copy-constructing swaps the two members. */ > +struct swapcopy > +{ > + int lo; > + int hi; > + swapcopy (int l, int h) : lo (l), hi (h) {} > + swapcopy (const swapcopy &o) : lo (o.hi), hi (o.lo) {} > +}; > + > +/* Pass swapcopy by value so the call must copy-construct the argument. */ > + > +static int > +swapcopy_first_byval (swapcopy c) > +{ > + return c.lo; > +} > + > +struct Base > +{ > + int x; > + Base () : x (0) {} > + explicit Base (int n) : x (n) {} > + Base (const Base &other) : x (other.x) {} > +}; > + > +typedef Base Base_td; > +using Base_u = Base; > + > +namespace NS > +{ > +class Derived : public Base > +{ > +public: > + int y; > + Derived () : Base (), y (0) {} > + Derived (int a, int b) : Base (a), y (b) {} > +}; > + > +typedef Base NsBaseTd; > +using NsBaseU = Base; > +typedef Derived Derived_td; > +using Derived_u = Derived; > + > +union U > +{ > + int a; > + U () : a (0) {} > + explicit U (int n) : a (n) {} > +}; > + > +typedef U Nu_td; > +} > + > +union U > +{ > + int x; > + U () : x (0) {} > + explicit U (int n) : x (n) {} > +}; > + > +typedef U U_td; > + > +static int > +plus_one (U u) > +{ > + return u.x + 1; > +} > + > +int > +main (void) > +{ > + S s0; /* default: x = 0 */ > + S s1 (42); /* x = 42 */ > + S s2 (s1 + 2); /* x = 44 */ > + S s3 = s1; > + NS::Derived d; /* Base part x=0, Derived part y=0 */ > + NS::Derived d1 (10, 20); /* Base part x=10, Derived part y=20 */ > + Base b; /* x=0 */ > + Base b1 (5); /* x=5 */ > + Base b2 (d1); /* x=10 */ > + U u0; /* default: x = 0 */ > + U u1 (42); /* x = 42 */ > + NS::U uv0; /* default: a = 0 */ > + NS::U uv1 (7); /* a = 7 */ > + S_td s_td = S_td (11); > + S_u s_u = S_u (12); > + Base_td b_td = Base_td (8); > + Base_u b_u = Base_u (9); > + NS::NsBaseTd nsb_td = NS::NsBaseTd (13); > + NS::NsBaseU nsb_u = NS::NsBaseU (14); > + NS::Derived_td d_td = NS::Derived_td (2, 3); > + NS::Derived_u d_u = NS::Derived_u (4, 5); > + U_td u_td = U_td (15); > + NS::Nu_td nu_u = NS::Nu_td (16); > + swapcopy swp (30, 40); > + int result = add (s1, s2); > + return result + d.x + d.y + b.x + b1.x + b2.x + d.x + d.y \ > + + d1.x + d1.y + plus_one (u0) + u1.x + uv0.a + uv1.a \ > + + s_td.x + s_u.x + b_td.x + b_u.x + nsb_td.x + nsb_u.x \ > + + d_td.x + d_td.y + d_u.x + d_u.y + u_td.x + nu_u.a \ > + + swapcopy_first_byval (swapcopy (100, 200)) \ > + + swapcopy_first_byval (swp); /* stop-here */ > +} > diff --git a/gdb/testsuite/gdb.cp/infcall-ctors.exp b/gdb/testsuite/gdb.cp/infcall-ctors.exp > new file mode 100644 > index 00000000000..2a92266ccfe > --- /dev/null > +++ b/gdb/testsuite/gdb.cp/infcall-ctors.exp > @@ -0,0 +1,198 @@ > +# Copyright (C) 2026 Free Software Foundation, Inc. > + > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 3 of the License, or > +# (at your option) any later version. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program. If not, see <http://www.gnu.org/licenses/>. > + > +# This file is part of the gdb testsuite. > + > +# Test constructor calls and casting via inferior function calls: > +# - Simple struct S with constructor (int, default 0). > +# - Base and Derived; cast derived to base, construct Base from Derived. > +# - Typedef and using aliases: ctor resolution must use the class ctor even > +# when the expression names a typedef or alias (DWARF may name types > +# differently from the underlying class tag). > +# - swapcopy: two ints with a user-defined copy ctor that swaps them > +# (non-trivially copyable); pass-by-value in inferior calls must run it. > +# - Constructor-style calls require overload resolution; verify the error > +# when it is disabled. > + > +require allow_cplus_tests > + > +standard_testfile .cc > + > +if {[prepare_for_testing "failed to prepare" $testfile $srcfile {debug c++}]} { > + return > +} > + > +if {![runto_main]} { > + return > +} > + > +# Run to stop-here to ensure all locals are initialized. > +gdb_breakpoint [gdb_get_line_number "stop-here"] > +gdb_continue_to_breakpoint "stop-here" > + > +# Simple tests involving "struct S". > +gdb_test "ptype S" [multi_line \ > + {type = struct S \{} \ > + { int x;} \ > + "" \ > + { S\(int\);} \ > + { S operator\+\(int\) const;} \ > + {\}}] > + > +gdb_test "print s0" " = \\{x = 0\\}" "print s0 default ctor" > +gdb_test "print s1" " = \\{x = 42\\}" "print s1 with 42" > +gdb_test "print S(99)" " = \\{x = 99\\}" "construct S(99) via inferior function call" > + > +gdb_test "ptype swapcopy" [multi_line \ > + {type = struct swapcopy \{} \ > + { int lo;} \ > + { int hi;} \ > + "" \ > + { swapcopy\(int, int\);} \ > + { swapcopy\(const swapcopy ?&\);} \ > + {\}}] > +gdb_test "print swapcopy(1, 2)" { = \{lo = 1, hi = 2\}} \ > + "construct swapcopy via inferior function call" > +gdb_test {print swapcopy($)} {= \{lo = 2, hi = 1\}} \ > + "call copy ctor on object via history" > +gdb_test "print swapcopy_first_byval(swapcopy(10, 20))" "= 20" \ > + "pass-by-value copy ctor swaps members" > +gdb_test "print swapcopy_first_byval(swp)" "= 40" \ > + "swapcopy local passed by value uses copy ctor" > + > +gdb_test "print S_td(33)" " = \\{x = 33\\}" \ > + "construct S via typedef name (not underlying struct tag)" > +gdb_test "print S_u(34)" " = \\{x = 34\\}" "construct S via using alias" > +gdb_test "print Base_td(8)" { = \{x = 8\}} "construct Base via typedef name" > +gdb_test "print Base_u(9)" { = \{x = 9\}} "construct Base via using alias" > +gdb_test "print NS::NsBaseTd(5)" { = \{x = 5\}} \ > + "construct Base via typedef in namespace" > +gdb_test "print NS::NsBaseU(6)" { = \{x = 6\}} \ > + "construct Base via using alias in namespace" > +gdb_test "print NS::Derived_td(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \ > + "construct NS::Derived via typedef name" > +gdb_test "print NS::Derived_u(3, 4)" { = \{<Base> = \{x = 3\}, y = 4\}} \ > + "construct NS::Derived via using alias" > +gdb_test "print U_td(55)" { = \{x = 55\}} \ > + "construct global union U via typedef name" > +gdb_test "print NS::Nu_td(66)" { = \{a = 66\}} \ > + "construct NS::U via typedef name" > + > +# Tests involving "Base" and "Derived". > +set base_re [multi_line \ > + {type = struct Base \{} \ > + { int x;} \ > + "" \ > + { Base\(void\);} \ > + { Base\(int\);} \ > + { Base\(const Base ?&\);} \ > + {\}}] > +gdb_test "ptype Base" $base_re > + > +set derived_re [multi_line \ > + {type = class NS::Derived : public Base \{} \ > + { public:} \ > + { int y;} \ > + "" \ > + { Derived\(void\);} \ > + { Derived\(int, int\);} \ > + {\}}] > +gdb_test "ptype NS::Derived" $derived_re > + > +gdb_test "print d" { = \{<Base> = \{x = 0\}, y = 0\}} > +gdb_test "print d1" { = \{<Base> = \{x = 10\}, y = 20\}} > +gdb_test "print b" { = \{x = 0\}} > +gdb_test "print b1" { = \{x = 5\}} > +gdb_test "print b2" { = \{x = 10\}} > +gdb_test "print (Base)(d1)" { = \{x = 10\}} "cast (Base)(d1) slices to Base" > +gdb_test "print Base(d1)" { = \{x = 10\}} "construct Base(d1) from Derived" > +gdb_test "print Base()" { = \{x = 0\}} "construct Base() default" > +gdb_test "print Base(7)" { = \{x = 7\}} "construct Base(7) with argument" > +gdb_test "print NS::Derived()" { = \{<Base> = \{x = 0\}, y = 0\}} \ > + "construct NS::Derived() via inferior function call" > +gdb_test "print NS::Derived().y" " = 0" "construct NS::Derived() and access .y" > +gdb_test "print NS::Derived(1, 2)" { = \{<Base> = \{x = 1\}, y = 2\}} \ > + "construct NS::Derived(1, 2) via inferior function call" > +gdb_test "print NS::Derived(1, 2).y" " = 2" \ > + "construct NS::Derived(1, 2) and access .y" > +gdb_test "print ((Base)d1).x" " = 10" "cast ((Base)d1).x" > + > +# Print the types of these "temporary" objects. > +gdb_test "ptype Base()" $base_re "ptype of Base temporary" > +gdb_test "ptype NS::Derived(15, 25)" $derived_re \ > + "ptype of NS::Derived temporary" > + > +# Tests involving unions. > +gdb_test "ptype U" [multi_line \ > + {type = union U \{} \ > + { int x;} \ > + "" \ > + { U\(void\);} \ > + { U\(int\);} \ > + {\}}] \ > + "ptype U" > + > +gdb_test "ptype NS::U" [multi_line \ > + {type = union NS::U \{} \ > + { int a;} \ > + "" \ > + { U\(void\);} \ > + { U\(int\);} \ > + {\}}] \ > + "ptype NS::U" > + > +gdb_test "print u0" { = \{x = 0\}} > +gdb_test "print u1" { = \{x = 42\}} > +gdb_test "print uv0" { = \{a = 0\}} > +gdb_test "print uv1" { = \{a = 7\}} > +gdb_test "print U()" { = \{x = 0\}} "construct U() via inferior function call" > +gdb_test "print U(99)" { = \{x = 99\}} "construct U(99) via inferior function call" > +gdb_test "print U(99).x" " = 99" "construct U(99) and access .x" > +gdb_test "print NS::U()" { = \{a = 0\}} \ > + "construct NS::U() via inferior function call" > +gdb_test "print NS::U(13)" { = \{a = 13\}} \ > + "construct NS::U(13) via inferior function call" > +gdb_test "print NS::U(13).a" " = 13" \ > + "construct NS::U(13) and access .a" > + > +set u_re [multi_line \ > + {type = union U \{} \ > + { int x;} \ > + "" \ > + { U\(void\);} \ > + { U\(int\);} \ > + {\}}] > +set ns_u_re [multi_line \ > + {type = union NS::U \{} \ > + { int a;} \ > + "" \ > + { U\(void\);} \ > + { U\(int\);} \ > + {\}}] > +gdb_test "ptype U()" $u_re "ptype of U temporary" > +gdb_test "ptype NS::U(99)" $ns_u_re "ptype of NS::U temporary" > + > +gdb_test "p plus_one(U(42))" "= 43" "temporary in function call" > +gdb_test "p add(S(1), S(20))" "= 21" "add two temporaries of S" > +gdb_test "p plus_one(U(add (S(4), S(6))))" "= 11" \ > + "nested function call using temporaries" > + > +gdb_test_no_output "set overload-resolution off" > +gdb_test "print S(1)" \ > + {Constructor calls require 'overload-resolution' to be on} \ > + "constructor call fails when overload-resolution is off" > +gdb_test_no_output "set overload-resolution on" > +gdb_test "print S(1)" " = \\{x = 1\\}" \ > + "constructor call works after overload-resolution turned back on" > diff --git a/gdb/testsuite/gdb.cp/static-ctor-infcall.c b/gdb/testsuite/gdb.cp/static-ctor-infcall.c > new file mode 100644 > index 00000000000..0516196e6d8 > --- /dev/null > +++ b/gdb/testsuite/gdb.cp/static-ctor-infcall.c > @@ -0,0 +1,33 @@ > +/* This testcase is part of GDB, the GNU debugger. > + > + Copyright 2026 Free Software Foundation, Inc. > + > + This file is part of GDB. > + > + This program is free software; you can redistribute it and/or modify > + it under the terms of the GNU General Public License as published by > + the Free Software Foundation; either version 3 of the License, or > + (at your option) any later version. > + > + This program is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + GNU General Public License for more details. > + > + You should have received a copy of the GNU General Public License > + along with this program. If not, see <http://www.gnu.org/licenses/>. */ > + > +/* Address range for hand-written DWARF describing a static constructor. */ > + > +extern "C" void > +dwarf_static_ctor_stub (void) > +{ > + asm ("dwarf_static_ctor_stub_label: .globl dwarf_static_ctor_stub_label"); > +} > + > +int > +main (void) > +{ > + asm ("main_label: .globl main_label"); > + return 0; > +} > diff --git a/gdb/testsuite/gdb.cp/static-ctor-infcall.exp b/gdb/testsuite/gdb.cp/static-ctor-infcall.exp > new file mode 100644 > index 00000000000..cdc2db170d6 > --- /dev/null > +++ b/gdb/testsuite/gdb.cp/static-ctor-infcall.exp > @@ -0,0 +1,87 @@ > +# Copyright 2026 Free Software Foundation, Inc. > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; either version 3 of the License, or > +# (at your option) any later version. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program. If not, see <http://www.gnu.org/licenses/>. > +# > +# Exercise the "Constructor %s is unexpectedly marked static" error in > +# type_operation::evaluate_funcall. > + > +load_lib dwarf.exp > + > +require dwarf2_support > +require allow_cplus_tests > + > +standard_testfile .c -dw.S > + > +set asm_file [standard_output_file $srcfile2] > +Dwarf::assemble $asm_file { > + declare_labels int_label struct_label ptr_label ctor_subr_label > + > + get_func_info dwarf_static_ctor_stub {nodebug c++} > + > + cu {} { > + compile_unit { > + DW_AT_language @DW_LANG_C_plus_plus > + DW_AT_name dw2-static-ctor-infcall.cc > + DW_AT_comp_dir /tmp > + } { > + int_label: base_type { > + DW_AT_name int > + DW_AT_encoding @DW_ATE_signed > + DW_AT_byte_size 4 DW_FORM_sdata > + } > + > + struct_label: structure_type { > + DW_AT_name dwarf_static_ctor > + DW_AT_byte_size 4 DW_FORM_sdata > + } { > + member { > + DW_AT_name x > + DW_AT_type :$int_label > + DW_AT_data_member_location 0 data1 > + } > + ptr_label: pointer_type { > + DW_AT_type :$struct_label > + } > + ctor_subr_label: subroutine_type { > + DW_AT_type :$int_label > + } { > + formal_parameter { > + DW_AT_type :$ptr_label > + } > + } > + subprogram { > + DW_AT_name dwarf_static_ctor > + DW_AT_external 1 flag > + DW_AT_low_pc $dwarf_static_ctor_stub_start addr > + DW_AT_high_pc $dwarf_static_ctor_stub_end addr > + DW_AT_type :$ctor_subr_label > + } > + } > + } > + } > +} > + > +if {[prepare_for_testing "failed to prepare" ${testfile} \ > + [list $srcfile $asm_file] {nodebug c++}]} { > + return > +} > + > +if {![runto_main]} { > + return > +} > + > +gdb_test_no_output "set language c++" > + > +gdb_test "print dwarf_static_ctor()" \ > + "Constructor dwarf_static_ctor is unexpectedly marked static" > > base-commit: 8e78d43dbcd122e852d59abe29c8d6aeb6c04925 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] Add infcall support for C++ constructor-style expressions 2026-04-27 16:43 ` [PATCH v2] " Keith Seitz 2026-06-19 17:21 ` Keith Seitz @ 2026-06-27 0:06 ` Kevin Buettner 2026-07-29 17:44 ` Keith Seitz 1 sibling, 1 reply; 8+ messages in thread From: Kevin Buettner @ 2026-06-27 0:06 UTC (permalink / raw) To: Keith Seitz; +Cc: gdb-patches Hi Keith, On Mon, 27 Apr 2026 09:43:08 -0700 Keith Seitz <keiths@redhat.com> wrote: > Changes in v2: > - Formatting fixes > - Use c_isspace to skip whitespace in typename_token_for() > - Merged rules for typename_for_ctor to type_exp > [This adds ~20 shift/reduce conflicts.] > - Updated error message when overload-resolution is off > - Handle static constructors and add new tests for this case > - Added test case for when ctor is not found > - Test early return: remove integer return status Claude found this problem... Using this two line program, which it named /tmp/test_c.c: struct S { int x; }; int main() { struct S s = {42}; return s.x; } ...do: gcc -g -o /tmp/test_c /tmp/test_c.c Then: kevinb-claude@claude-1:bin$ ./gdb -q (gdb) file /tmp/test_c Reading symbols from /tmp/test_c... (gdb) start Temporary breakpoint 1 at 0x40044a: file /tmp/test_c.c, line 2. Starting program: /tmp/test_c This GDB supports auto-downloading debuginfo from the following URLs: <ima:enforcing> <https://debuginfod.fedoraproject.org/> <ima:ignore> Enable debuginfod for this session? (y or [n]) n Debuginfod has been disabled. To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit. [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". Temporary breakpoint 1, main () at /tmp/test_c.c:2 2 int main() { struct S s = {42}; return s.x; } (gdb) print typeof(struct S)(42) /home/kevinb-claude/binutils-gdb-patch-review/gdb/eval.c:1885: internal-error: evaluate_funcall: Assertion `exp->language_defn->la_language == language_cplus' failed. A problem internal to GDB has been detected, further debugging may prove unreliable. ----- Backtrace ----- 0x51e699 gdb_internal_backtrace_1 /home/kevinb-claude/binutils-gdb-patch-review/gdb/bt-utils.c:122 0x51e699 _Z22gdb_internal_backtracev /home/kevinb-claude/binutils-gdb-patch-review/gdb/bt-utils.c:173 0x9fb874 internal_vproblem /home/kevinb-claude/binutils-gdb-patch-review/gdb/utils.c:434 0x9fbd38 _Z15internal_verrorPKciS0_P13__va_list_tag /home/kevinb-claude/binutils-gdb-patch-review/gdb/utils.c:514 0xb6cea1 _Z18internal_error_locPKciS0_z /home/kevinb-claude/binutils-gdb-patch-review/gdbsupport/errors.cc:57 0x67e31c _ZN4expr14type_operation16evaluate_funcallEP4typeP10expression6nosideRKSt6vectorISt10unique_ptrINS_9operationESt14default_deleteIS8_EESaISB_EE ... Claude also complained about the 20 new shift/reduce conflicts. It recommends reverting to a dedicated non-terminal for TYPENAME_CTOR instead of routing through type_exp. (It showed some code too, but I don't want to taint your contribution by showing you AI generated code.) Claude says that, if done right, that change could fix both problems at once. Kevin ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v2] Add infcall support for C++ constructor-style expressions 2026-06-27 0:06 ` Kevin Buettner @ 2026-07-29 17:44 ` Keith Seitz 0 siblings, 0 replies; 8+ messages in thread From: Keith Seitz @ 2026-07-29 17:44 UTC (permalink / raw) To: Kevin Buettner; +Cc: gdb-patches Hi, On 6/26/26 5:06 PM, Kevin Buettner wrote: > > Claude found this problem... > > (gdb) file /tmp/test_c > Reading symbols from /tmp/test_c... > (gdb) start > Temporary breakpoint 1 at 0x40044a: file /tmp/test_c.c, line 2. > Starting program: /tmp/test_c > > [Thread debugging using libthread_db enabled] > Using host libthread_db library "/lib64/libthread_db.so.1". > > Temporary breakpoint 1, main () at /tmp/test_c.c:2 > 2 int main() { struct S s = {42}; return s.x; } > (gdb) print typeof(struct S)(42) > /home/kevinb-claude/binutils-gdb-patch-review/gdb/eval.c:1885: internal-error: evaluate_funcall: Assertion `exp->language_defn->la_language == language_cplus' failed. > A problem internal to GDB has been detected, > further debugging may prove unreliable. > ----- Backtrace ----- > 0x51e699 gdb_internal_backtrace_1 > /home/kevinb-claude/binutils-gdb-patch-review/gdb/bt-utils.c:122 > 0x51e699 _Z22gdb_internal_backtracev > /home/kevinb-claude/binutils-gdb-patch-review/gdb/bt-utils.c:173 > 0x9fb874 internal_vproblem > /home/kevinb-claude/binutils-gdb-patch-review/gdb/utils.c:434 > 0x9fbd38 _Z15internal_verrorPKciS0_P13__va_list_tag > /home/kevinb-claude/binutils-gdb-patch-review/gdb/utils.c:514 > 0xb6cea1 _Z18internal_error_locPKciS0_z > /home/kevinb-claude/binutils-gdb-patch-review/gdbsupport/errors.cc:57 > 0x67e31c _ZN4expr14type_operation16evaluate_funcallEP4typeP10expression6nosideRKSt6vectorISt10unique_ptrINS_9operationESt14default_deleteIS8_EESaISB_EE > ... > Thanks for the report. The root cause is that the C++-only gate lived only in the lexer, but `typeof(T)` is a separate `type_exp` production. So in C, `typeof(struct S)(42)` still matched the general `type_exp '(' arglist ')'` rules, built a `funcall_operation`, and hit the `language_cplus` assert in `type_operation::evaluate_funcall`. The fix is to mirror the lexer gate in those parser actions: if the language is not C++, throw an error: (gdb) print typeof(struct S)(42) Constructor-style casts are only valid in C++ I left the `type_exp`-based productions in place rather than splitting out a dedicated ctor non-terminal; the language check closes the hole Claude found without that larger grammar change. I'm happy to revisit the shift/reduce side separately if consensus is reached. [This is the direction a previous review steered me, and I am happy to revisit that initial review recommendation.] Thank you, Keith ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-07-29 17:44 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-03-26 19:17 [PATCH] Add infcall support for C++ constructor-style expressions Keith Seitz 2026-03-27 5:50 ` Eli Zaretskii 2026-04-21 13:41 ` Andrew Burgess 2026-04-21 18:13 ` Keith Seitz 2026-04-27 16:43 ` [PATCH v2] " Keith Seitz 2026-06-19 17:21 ` Keith Seitz 2026-06-27 0:06 ` Kevin Buettner 2026-07-29 17:44 ` Keith Seitz
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox