From: Stephan Rohr Date: Wed, 20 Aug 2025 13:06:23 +0000 (-0700) Subject: gdb: resolve type to target_type in expression evaluation X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3a3c3a0e7280ef8907d62ea83706a2ef3081831b;p=thirdparty%2Fbinutils-gdb.git 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 Approved-By: Tom Tromey --- diff --git a/gdb/eval.c b/gdb/eval.c index cefc8573b32..07fa0af4722 100644 --- a/gdb/eval.c +++ b/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); } } diff --git a/gdb/expop.h b/gdb/expop.h index 6f509dac324..e755df57762 100644 --- a/gdb/expop.h +++ b/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); } diff --git a/gdb/testsuite/gdb.cp/eval-reference-type.cc b/gdb/testsuite/gdb.cp/eval-reference-type.cc new file mode 100644 index 00000000000..b177cbf835c --- /dev/null +++ b/gdb/testsuite/gdb.cp/eval-reference-type.cc @@ -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 . */ + +/* 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; +} diff --git a/gdb/testsuite/gdb.cp/eval-reference-type.exp b/gdb/testsuite/gdb.cp/eval-reference-type.exp new file mode 100644 index 00000000000..4c7b3b7bfe9 --- /dev/null +++ b/gdb/testsuite/gdb.cp/eval-reference-type.exp @@ -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 . + +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"