gdb: resolve type to target_type in expression evaluation

If an expression is evaluated with 'EVAL_AVOID_SIDE_EFFECTS', we're
essentially interested in compatibility of the operands.  If there is an
operand of reference type, this would give us a memory value that would
cause a failure if GDB attempts to access the contents.

GDB fails to evaluate binary expressions for the following example:

  struct
  {
    int &get () { return x; };

    int x = 1;
  } v_struct;

The GDB output is:

  (gdb) print v_struct3.get () == 1 && v_struct3.get () == 2
  Cannot access memory at address 0x0
  (gdb) print v_struct3.get () == 1 || v_struct3.get () == 2
  Cannot access memory at address 0x0

Likewise, GDB fails to resolve the type for some expressions:

  (gdb) ptype v_struct.get ()
  type = int &
  (gdb) ptype v_struct.get () == 1
  Cannot access memory at address 0x0
  (gdb) ptype v_struct.get () + 1
  Cannot access memory at address 0x0
  (gdb) ptype v_struct.get () && 1
  Cannot access memory at address 0x0
  (gdb) ptype v_struct.get () || 1
  Cannot access memory at address 0x0
  (gdb) ptype !v_struct.get ()
  Cannot access memory at address 0x0
  (gdb) ptype v_struct.get () ? 2 : 3
  Cannot access memory at address 0x0
  (gdb) ptype v_struct.get () | 1
  Cannot access memory at address 0x0

Expression evaluation uses helper functions such as 'value_equal',
'value_logical_not', etc.  These helper functions do not take a 'noside'
argument and if one of their value arguments was created from a function
call that returns a reference type when noside == EVAL_AVOID_SIDE_EFFECTS,
GDB attempts to read from an invalid memory location.  Consider the
following call stack of the 'ptype v_struct.get () + 1' command at the time
of assertion when the memory error is raised:

  #0  memory_error (err=TARGET_XFER_E_IO, memaddr=0) at gdb/corefile.c:114
  #1  read_value_memory (val=.., bit_offset=0, stack=false, memaddr=0,
      buffer=.. "", length=4) at gdb/valops.c:1075
  #2  value::fetch_lazy_memory (this=..) at gdb/value.c:3996
  #3  value::fetch_lazy (this=..) at gdb/value.c:4135
  #4  value::contents_writeable (this=..) at gdb/value.c:1329
  #5  value::contents (this=..) at gdb/value.c:1319
  #6  value_as_mpz (val=..) at gdb/value.c:2685
  #7  scalar_binop (arg1=.., arg2=.., op=BINOP_ADD) at gdb/valarith.c:1240
  #8  value_binop (arg1=.., arg2=.., op=BINOP_ADD) at gdb/valarith.c:1489
  #9  eval_op_add (expect_type=0x0, exp=.., noside=EVAL_AVOID_SIDE_EFFECTS,
      arg1=.., arg2=..) at gdb/eval.c:1333
  #10 expr::add_operation::evaluate (this=.., expect_type=0x0, exp=..,
      noside=EVAL_AVOID_SIDE_EFFECTS) at gdb/expop.h:1209
  #11 expression::evaluate (this=.., expect_type=0x0,
      noside=EVAL_AVOID_SIDE_EFFECTS) at gdb/eval.c:110
  #12 expression::evaluate_type (this=..) at gdb/expression.h:242

'add_operation::evaluate' calls the helper 'eval_op_add' which attempts
to read from the unresolved memory location.  Convert to the target type
to avoid such problems.  The patch is implemented in 'expop.h' for the
following reasons:

  * Support templated classes without explicit helpers, e.g.,
    'binop_operation' and 'comparison_operation'.
  * Stripping references in 'binop_promote' requires additional
    refactoring beyond this patch as we would need to carry on the
    'noside' parameter.

The above failures are resolved with the patch:

  (gdb) print v_struct.get () == 1 && v_struct3.get () == 2
  $1 = false
  (gdb) print v_struct.get () == 1 || v_struct3.get () == 2
  $2 = true
  (gdb) ptype v_struct.get ()
  type = int &
  (gdb) ptype v_struct.get () == 1
  type = bool
  (gdb) ptype v_struct.get () + 1
  type = int
  (gdb) ptype v_struct.get () && 1
  type = bool
  (gdb) ptype v_struct.get () || 1
  type = bool
  (gdb) ptype !v_struct.get ()
  type = bool
  (gdb) ptype v_struct.get () ? 2 : 3
  type = int
  (gdb) ptype v_struct.get () | 1
  type = int

Co-Authored-By: Tankut Baris Aktemur <tankut.baris.aktemur@intel.com>
Approved-By: Tom Tromey <tom@tromey.com>
This commit is contained in:
Stephan Rohr
2025-08-20 06:06:23 -07:00
parent 4c47471ba4
commit 3a3c3a0e72
4 changed files with 130 additions and 5 deletions

View File

@@ -2284,14 +2284,18 @@ logical_and_operation::evaluate (struct type *expect_type,
}
else
{
type *type = language_bool_type (exp->language_defn,
exp->gdbarch);
if (noside == EVAL_AVOID_SIDE_EFFECTS)
return value::zero (type, not_lval);
bool tem = value_logical_not (arg1);
if (!tem)
{
arg2 = std::get<1> (m_storage)->evaluate (nullptr, exp, noside);
tem = value_logical_not (arg2);
}
struct type *type = language_bool_type (exp->language_defn,
exp->gdbarch);
return value_from_longest (type, !tem);
}
}
@@ -2313,6 +2317,11 @@ logical_or_operation::evaluate (struct type *expect_type,
}
else
{
type *type = language_bool_type (exp->language_defn,
exp->gdbarch);
if (noside == EVAL_AVOID_SIDE_EFFECTS)
return value::zero (type, not_lval);
bool tem = value_logical_not (arg1);
if (tem)
{
@@ -2320,8 +2329,6 @@ logical_or_operation::evaluate (struct type *expect_type,
tem = value_logical_not (arg2);
}
struct type *type = language_bool_type (exp->language_defn,
exp->gdbarch);
return value_from_longest (type, !tem);
}
}

View File

@@ -308,6 +308,25 @@ dump_for_expression (struct ui_file *stream, int depth,
op->dump (stream, depth);
}
/* If evaluating with noside == EVAL_AVOID_SIDE_EFFECTS, we are essentially
interested in the type of ARG. However, if ARG is of reference type,
this would give us a memory value that would cause a failure if GDB
attempts to access the contents. Convert to the target type to avoid
such problems. */
static value *
convert_reference_to_target_type (value *arg, enum noside noside)
{
struct type *type = check_typedef (arg->type ());
if (noside == EVAL_AVOID_SIDE_EFFECTS && TYPE_IS_REFERENCE (type))
{
struct type *target_type = check_typedef (type->target_type ());
return value::zero (target_type, not_lval);
}
return arg;
}
extern void dump_for_expression (struct ui_file *stream, int depth,
enum exp_opcode op);
extern void dump_for_expression (struct ui_file *stream, int depth,
@@ -953,7 +972,7 @@ public:
struct value *val
= std::get<0> (m_storage)->evaluate (nullptr, exp, noside);
if (value_logical_not (val))
if (noside != EVAL_AVOID_SIDE_EFFECTS && value_logical_not (val))
return std::get<2> (m_storage)->evaluate (nullptr, exp, noside);
return std::get<1> (m_storage)->evaluate (nullptr, exp, noside);
}
@@ -1187,6 +1206,10 @@ public:
= std::get<0> (m_storage)->evaluate_with_coercion (exp, noside);
value *rhs
= std::get<1> (m_storage)->evaluate_with_coercion (exp, noside);
lhs = convert_reference_to_target_type (lhs, noside);
rhs = convert_reference_to_target_type (rhs, noside);
return eval_op_add (expect_type, exp, noside, lhs, rhs);
}
@@ -1223,6 +1246,10 @@ public:
= std::get<0> (m_storage)->evaluate_with_coercion (exp, noside);
value *rhs
= std::get<1> (m_storage)->evaluate_with_coercion (exp, noside);
lhs = convert_reference_to_target_type (lhs, noside);
rhs = convert_reference_to_target_type (rhs, noside);
return eval_op_sub (expect_type, exp, noside, lhs, rhs);
}
@@ -1265,6 +1292,10 @@ public:
= std::get<0> (m_storage)->evaluate (nullptr, exp, noside);
value *rhs
= std::get<1> (m_storage)->evaluate (nullptr, exp, noside);
lhs = convert_reference_to_target_type (lhs, noside);
rhs = convert_reference_to_target_type (rhs, noside);
return FUNC (expect_type, exp, noside, OP, lhs, rhs);
}
@@ -1340,6 +1371,10 @@ public:
value *rhs
= std::get<1> (this->m_storage)->evaluate (lhs->type (), exp,
noside);
lhs = convert_reference_to_target_type (lhs, noside);
rhs = convert_reference_to_target_type (rhs, noside);
return FUNC (expect_type, exp, noside, OP, lhs, rhs);
}
};
@@ -1434,6 +1469,7 @@ public:
enum noside noside) override
{
value *val = std::get<0> (m_storage)->evaluate (nullptr, exp, noside);
val = convert_reference_to_target_type (val, noside);
return FUNC (expect_type, exp, noside, OP, val);
}

View File

@@ -0,0 +1,36 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2025 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/>. */
/* Tests that expression evaluation does not return an error if one of
the operands is of reference type. */
struct
{
int &get ()
{
return x;
};
int x = 1;
} v_struct;
int
main (void)
{
v_struct.get () = 2;
return 0;
}

View File

@@ -0,0 +1,46 @@
# Copyright 2025 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/>.
standard_testfile .cc
if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
return
}
runto_main
# Test for reference type.
gdb_test "print v_struct.get () == 1 && v_struct.get () == 2" \
" = false"
gdb_test "print v_struct.get () == 1 || v_struct.get () == 2" \
" = true"
# Test for correct type resolution using 'ptype'.
gdb_test "ptype v_struct.get ()" \
"type = int &"
gdb_test "ptype v_struct.get () == 1" \
"type = bool"
gdb_test "ptype v_struct.get () + 1" \
"type = int"
gdb_test "ptype v_struct.get () && 1" \
"type = bool"
gdb_test "ptype v_struct.get () || 1" \
"type = bool"
gdb_test "ptype !v_struct.get ()" \
"type = bool"
gdb_test "ptype v_struct.get () ? 2 : 3" \
"type = int"
gdb_test "ptype v_struct.get () | 1" \
"type = int"