Add infcall support for C++ constructor-style expressions
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_gdb_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gdb_check--master-arm |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 |
success
|
Test passed
|
Commit Message
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
base-commit: 07519d531b1e858f665ff011d7f1002f38111ec8
Comments
> 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>
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
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
@@ -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*
@@ -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 ());
@@ -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. */
@@ -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; }
new file mode 100644
@@ -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 */
+}
new file mode 100644
@@ -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"