]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: resolve type to target_type in expression evaluation
authorStephan Rohr <stephan.rohr@intel.com>
Wed, 20 Aug 2025 13:06:23 +0000 (06:06 -0700)
committerStephan Rohr <stephan.rohr@intel.com>
Wed, 24 Sep 2025 08:31:52 +0000 (01:31 -0700)
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>
gdb/eval.c
gdb/expop.h
gdb/testsuite/gdb.cp/eval-reference-type.cc [new file with mode: 0644]
gdb/testsuite/gdb.cp/eval-reference-type.exp [new file with mode: 0644]

index cefc8573b320b61170a31ce8f0cf74903dc75411..07fa0af472235c29fbdb520220e24e09e42f57cc 100644 (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);
     }
 }
index 6f509dac32456da29bc292b5079b741147dbf0c8..e755df5776243f2b33d0c30a02c0986168131a57 100644 (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);
   }
 
diff --git a/gdb/testsuite/gdb.cp/eval-reference-type.cc b/gdb/testsuite/gdb.cp/eval-reference-type.cc
new file mode 100644 (file)
index 0000000..b177cbf
--- /dev/null
@@ -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;
+}
diff --git a/gdb/testsuite/gdb.cp/eval-reference-type.exp b/gdb/testsuite/gdb.cp/eval-reference-type.exp
new file mode 100644 (file)
index 0000000..4c7b3b7
--- /dev/null
@@ -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"