mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-11-16 12:34:43 +00:00
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:
15
gdb/eval.c
15
gdb/eval.c
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
38
gdb/expop.h
38
gdb/expop.h
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
36
gdb/testsuite/gdb.cp/eval-reference-type.cc
Normal file
36
gdb/testsuite/gdb.cp/eval-reference-type.cc
Normal 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;
|
||||
}
|
||||
46
gdb/testsuite/gdb.cp/eval-reference-type.exp
Normal file
46
gdb/testsuite/gdb.cp/eval-reference-type.exp
Normal 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"
|
||||
Reference in New Issue
Block a user