diff --git a/gdb/python/py-finishbreakpoint.c b/gdb/python/py-finishbreakpoint.c
index d4d129110e9..3b682f5ad36 100644
--- a/gdb/python/py-finishbreakpoint.c
+++ b/gdb/python/py-finishbreakpoint.c
@@ -112,6 +112,8 @@ bpfinishpy_pre_stop_hook (struct gdbpy_breakpoint_object *bp_obj)
 
   try
     {
+      scoped_value_mark free_values;
+
       struct symbol *func_symbol =
 	symbol_object_to_symbol (self_finishbp->func_symbol);
       struct value *function =
@@ -258,6 +260,8 @@ bpfinishpy_init (PyObject *self, PyObject *args, PyObject *kwargs)
 	      /* Remember only non-void return types.  */
 	      if (ret_type->code () != TYPE_CODE_VOID)
 		{
+		  scoped_value_mark free_values;
+
 		  /* Ignore Python errors at this stage.  */
 		  value *func_value = read_var_value (function, NULL, frame);
 		  self_bpfinish->function_value
diff --git a/gdb/python/py-frame.c b/gdb/python/py-frame.c
index f66d22bfa3d..ecd55d2e568 100644
--- a/gdb/python/py-frame.c
+++ b/gdb/python/py-frame.c
@@ -241,12 +241,13 @@ static PyObject *
 frapy_read_register (PyObject *self, PyObject *args)
 {
   PyObject *pyo_reg_id;
-  struct value *val = NULL;
+  PyObject *result = nullptr;
 
   if (!PyArg_UnpackTuple (args, "read_register", 1, 1, &pyo_reg_id))
     return NULL;
   try
     {
+      scoped_value_mark free_values;
       frame_info_ptr frame;
       int regnum;
 
@@ -257,17 +258,19 @@ frapy_read_register (PyObject *self, PyObject *args)
 	return nullptr;
 
       gdb_assert (regnum >= 0);
-      val = value_of_register (regnum, frame);
+      struct value *val = value_of_register (regnum, frame);
 
       if (val == NULL)
 	PyErr_SetString (PyExc_ValueError, _("Can't read register."));
+      else
+	result = value_to_value_object (val);
     }
   catch (const gdb_exception &except)
     {
       GDB_PY_HANDLE_EXCEPTION (except);
     }
 
-  return val == NULL ? NULL : value_to_value_object (val);
+  return result;
 }
 
 /* Implementation of gdb.Frame.block (self) -> gdb.Block.
@@ -482,7 +485,6 @@ frapy_read_var (PyObject *self, PyObject *args)
   PyObject *sym_obj, *block_obj = NULL;
   struct symbol *var = NULL;	/* gcc-4.3.2 false warning.  */
   const struct block *block = NULL;
-  struct value *val = NULL;
 
   if (!PyArg_ParseTuple (args, "O|O", &sym_obj, &block_obj))
     return NULL;
@@ -540,18 +542,21 @@ frapy_read_var (PyObject *self, PyObject *args)
       return NULL;
     }
 
+  PyObject *result = nullptr;
   try
     {
       FRAPY_REQUIRE_VALID (self, frame);
 
-      val = read_var_value (var, block, frame);
+      scoped_value_mark free_values;
+      struct value *val = read_var_value (var, block, frame);
+      result = value_to_value_object (val);
     }
   catch (const gdb_exception &except)
     {
       GDB_PY_HANDLE_EXCEPTION (except);
     }
 
-  return value_to_value_object (val);
+  return result;
 }
 
 /* Select this frame.  */
diff --git a/gdb/python/py-lazy-string.c b/gdb/python/py-lazy-string.c
index 7177d74c32e..9b1205463d4 100644
--- a/gdb/python/py-lazy-string.c
+++ b/gdb/python/py-lazy-string.c
@@ -104,7 +104,6 @@ static PyObject *
 stpy_convert_to_value (PyObject *self, PyObject *args)
 {
   lazy_string_object *self_string = (lazy_string_object *) self;
-  struct value *val = NULL;
 
   if (self_string->address == 0)
     {
@@ -113,10 +112,14 @@ stpy_convert_to_value (PyObject *self, PyObject *args)
       return NULL;
     }
 
+  PyObject *result = nullptr;
   try
     {
+      scoped_value_mark free_values;
+
       struct type *type = type_object_to_type (self_string->type);
       struct type *realtype;
+      struct value *val;
 
       gdb_assert (type != NULL);
       realtype = check_typedef (type);
@@ -141,13 +144,15 @@ stpy_convert_to_value (PyObject *self, PyObject *args)
 	  val = value_at_lazy (type, self_string->address);
 	  break;
 	}
+
+      result = value_to_value_object (val);
     }
   catch (const gdb_exception &except)
     {
       GDB_PY_HANDLE_EXCEPTION (except);
     }
 
-  return value_to_value_object (val);
+  return result;
 }
 
 static void
diff --git a/gdb/python/py-prettyprint.c b/gdb/python/py-prettyprint.c
index 1a5ce08c477..17dd648ff9e 100644
--- a/gdb/python/py-prettyprint.c
+++ b/gdb/python/py-prettyprint.c
@@ -590,7 +590,7 @@ gdbpy_apply_val_pretty_printer (const struct extension_language_defn *extlang,
 
   gdbpy_enter enter_py (gdbarch, language);
 
-  gdbpy_ref<> val_obj (value_to_value_object_no_release (value));
+  gdbpy_ref<> val_obj (value_to_value_object (value));
   if (val_obj == NULL)
     {
       print_stack_unless_memory_error (stream);
diff --git a/gdb/python/py-symbol.c b/gdb/python/py-symbol.c
index b8777966c47..e6497ed473f 100644
--- a/gdb/python/py-symbol.c
+++ b/gdb/python/py-symbol.c
@@ -267,7 +267,6 @@ sympy_value (PyObject *self, PyObject *args)
   struct symbol *symbol = NULL;
   frame_info_ptr frame_info = NULL;
   PyObject *frame_obj = NULL;
-  struct value *value = NULL;
 
   if (!PyArg_ParseTuple (args, "|O", &frame_obj))
     return NULL;
@@ -285,6 +284,7 @@ sympy_value (PyObject *self, PyObject *args)
       return NULL;
     }
 
+  PyObject *result = nullptr;
   try
     {
       if (frame_obj != NULL)
@@ -301,14 +301,16 @@ sympy_value (PyObject *self, PyObject *args)
 	 was found, so we have no block to pass to read_var_value.  This will
 	 yield an incorrect value when symbol is not local to FRAME_INFO (this
 	 can happen with nested functions).  */
-      value = read_var_value (symbol, NULL, frame_info);
+      scoped_value_mark free_values;
+      struct value *value = read_var_value (symbol, NULL, frame_info);
+      result = value_to_value_object (value);
     }
   catch (const gdb_exception &except)
     {
       GDB_PY_HANDLE_EXCEPTION (except);
     }
 
-  return value_to_value_object (value);
+  return result;
 }
 
 /* Given a symbol, and a symbol_object that has previously been
diff --git a/gdb/python/py-type.c b/gdb/python/py-type.c
index 81b595140af..051d77477a0 100644
--- a/gdb/python/py-type.c
+++ b/gdb/python/py-type.c
@@ -957,7 +957,6 @@ typy_template_argument (PyObject *self, PyObject *args)
   const struct block *block = NULL;
   PyObject *block_obj = NULL;
   struct symbol *sym;
-  struct value *val = NULL;
 
   if (! PyArg_ParseTuple (args, "i|O", &argno, &block_obj))
     return NULL;
@@ -1014,16 +1013,19 @@ typy_template_argument (PyObject *self, PyObject *args)
       return NULL;
     }
 
+  PyObject *result = nullptr;
   try
     {
-      val = value_of_variable (sym, block);
+      scoped_value_mark free_values;
+      struct value *val = value_of_variable (sym, block);
+      result = value_to_value_object (val);
     }
   catch (const gdb_exception &except)
     {
       GDB_PY_HANDLE_EXCEPTION (except);
     }
 
-  return value_to_value_object (val);
+  return result;
 }
 
 static PyObject *
@@ -1189,6 +1191,7 @@ typy_optimized_out (PyObject *self, PyObject *args)
 {
   struct type *type = ((type_object *) self)->type;
 
+  scoped_value_mark free_values;
   return value_to_value_object (allocate_optimized_out_value (type));
 }
 
diff --git a/gdb/python/py-unwind.c b/gdb/python/py-unwind.c
index e7ec660e150..bafecc23ea9 100644
--- a/gdb/python/py-unwind.c
+++ b/gdb/python/py-unwind.c
@@ -365,7 +365,6 @@ static PyObject *
 pending_framepy_read_register (PyObject *self, PyObject *args)
 {
   pending_frame_object *pending_frame = (pending_frame_object *) self;
-  struct value *val = NULL;
   int regnum;
   PyObject *pyo_reg_id;
 
@@ -380,25 +379,31 @@ pending_framepy_read_register (PyObject *self, PyObject *args)
   if (!gdbpy_parse_register_id (pending_frame->gdbarch, pyo_reg_id, &regnum))
     return nullptr;
 
+  PyObject *result = nullptr;
   try
     {
+      scoped_value_mark free_values;
+
       /* Fetch the value associated with a register, whether it's
 	 a real register or a so called "user" register, like "pc",
 	 which maps to a real register.  In the past,
 	 get_frame_register_value() was used here, which did not
 	 handle the user register case.  */
-      val = value_of_register (regnum, pending_frame->frame_info);
+      struct value *val = value_of_register (regnum,
+					     pending_frame->frame_info);
       if (val == NULL)
 	PyErr_Format (PyExc_ValueError,
 		      "Cannot read register %d from frame.",
 		      regnum);
+      else
+	result = value_to_value_object (val);
     }
   catch (const gdb_exception &except)
     {
       GDB_PY_HANDLE_EXCEPTION (except);
     }
 
-  return val == NULL ? NULL : value_to_value_object (val);
+  return result;
 }
 
 /* Implementation of
diff --git a/gdb/python/py-value.c b/gdb/python/py-value.c
index da2565081e6..14d5f1ad2d2 100644
--- a/gdb/python/py-value.c
+++ b/gdb/python/py-value.c
@@ -1559,18 +1559,20 @@ valpy_nonzero (PyObject *self)
 static PyObject *
 valpy_invert (PyObject *self)
 {
-  struct value *val = NULL;
+  PyObject *result = nullptr;
 
   try
     {
-      val = value_complement (((value_object *) self)->value);
+      scoped_value_mark free_values;
+      struct value *val = value_complement (((value_object *) self)->value);
+      result = value_to_value_object (val);
     }
   catch (const gdb_exception &except)
     {
       GDB_PY_HANDLE_EXCEPTION (except);
     }
 
-  return value_to_value_object (val);
+  return result;
 }
 
 /* Implements left shift for value objects.  */
@@ -1774,32 +1776,10 @@ valpy_float (PyObject *self)
   return PyFloat_FromDouble (d);
 }
 
-/* Returns an object for a value which is released from the all_values chain,
-   so its lifetime is not bound to the execution of a command.  */
-PyObject *
-value_to_value_object (struct value *val)
-{
-  value_object *val_obj;
-
-  val_obj = PyObject_New (value_object, &value_object_type);
-  if (val_obj != NULL)
-    {
-      val_obj->value = release_value (val).release ();
-      val_obj->next = nullptr;
-      val_obj->prev = nullptr;
-      val_obj->address = NULL;
-      val_obj->type = NULL;
-      val_obj->dynamic_type = NULL;
-      note_value (val_obj);
-    }
-
-  return (PyObject *) val_obj;
-}
-
-/* Returns an object for a value, but without releasing it from the
+/* Returns an object for a value, without releasing it from the
    all_values chain.  */
 PyObject *
-value_to_value_object_no_release (struct value *val)
+value_to_value_object (struct value *val)
 {
   value_object *val_obj;
 
@@ -1926,21 +1906,23 @@ PyObject *
 gdbpy_history (PyObject *self, PyObject *args)
 {
   int i;
-  struct value *res_val = NULL;	  /* Initialize to appease gcc warning.  */
 
   if (!PyArg_ParseTuple (args, "i", &i))
     return NULL;
 
+  PyObject *result = nullptr;
   try
     {
-      res_val = access_value_history (i);
+      scoped_value_mark free_values;
+      struct value *res_val = access_value_history (i);
+      result = value_to_value_object (res_val);
     }
   catch (const gdb_exception &except)
     {
       GDB_PY_HANDLE_EXCEPTION (except);
     }
 
-  return value_to_value_object (res_val);
+  return result;
 }
 
 /* Add a gdb.Value into GDB's history, and return (as an integer) the
@@ -1988,15 +1970,23 @@ gdbpy_convenience_variable (PyObject *self, PyObject *args)
   if (!PyArg_ParseTuple (args, "s", &varname))
     return NULL;
 
+  PyObject *result = nullptr;
+  bool found = false;
   try
     {
       struct internalvar *var = lookup_only_internalvar (varname);
 
       if (var != NULL)
 	{
+	  scoped_value_mark free_values;
 	  res_val = value_of_internalvar (gdbpy_enter::get_gdbarch (), var);
 	  if (value_type (res_val)->code () == TYPE_CODE_VOID)
 	    res_val = NULL;
+	  else
+	    {
+	      found = true;
+	      result = value_to_value_object (res_val);
+	    }
 	}
     }
   catch (const gdb_exception &except)
@@ -2004,10 +1994,10 @@ gdbpy_convenience_variable (PyObject *self, PyObject *args)
       GDB_PY_HANDLE_EXCEPTION (except);
     }
 
-  if (res_val == NULL)
+  if (result == nullptr && !found)
     Py_RETURN_NONE;
 
-  return value_to_value_object (res_val);
+  return result;
 }
 
 /* Set the value of a convenience variable.  */
diff --git a/gdb/python/py-xmethods.c b/gdb/python/py-xmethods.c
index d6d607d99d4..d03a2272868 100644
--- a/gdb/python/py-xmethods.c
+++ b/gdb/python/py-xmethods.c
@@ -423,6 +423,7 @@ python_xmethod_worker::do_get_result_type (value *obj,
       return EXT_LANG_RC_OK;
     }
 
+  scoped_value_mark free_values;
   obj_type = check_typedef (value_type (obj));
   this_type = check_typedef (type_object_to_type (m_this_type));
   if (obj_type->code () == TYPE_CODE_PTR)
diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h
index c41a43bac96..39f47d6b1e2 100644
--- a/gdb/python/python-internal.h
+++ b/gdb/python/python-internal.h
@@ -437,7 +437,6 @@ PyObject *symbol_to_symbol_object (struct symbol *sym);
 PyObject *block_to_block_object (const struct block *block,
 				 struct objfile *objfile);
 PyObject *value_to_value_object (struct value *v);
-PyObject *value_to_value_object_no_release (struct value *v);
 PyObject *type_to_type_object (struct type *);
 PyObject *frame_info_to_frame_object (frame_info_ptr frame);
 PyObject *symtab_to_linetable_object (PyObject *symtab);
diff --git a/gdb/python/python.c b/gdb/python/python.c
index 54623f445ed..8c23e0cd05e 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -969,22 +969,24 @@ static PyObject *
 gdbpy_parse_and_eval (PyObject *self, PyObject *args)
 {
   const char *expr_str;
-  struct value *result = NULL;
 
   if (!PyArg_ParseTuple (args, "s", &expr_str))
     return NULL;
 
+  PyObject *result = nullptr;
   try
     {
       gdbpy_allow_threads allow_threads;
-      result = parse_and_eval (expr_str);
+      scoped_value_mark free_values;
+      struct value *val = parse_and_eval (expr_str);
+      result = value_to_value_object (val);
     }
   catch (const gdb_exception &except)
     {
       GDB_PY_HANDLE_EXCEPTION (except);
     }
 
-  return value_to_value_object (result);
+  return result;
 }
 
 /* Implementation of gdb.invalidate_cached_frames.  */
diff --git a/gdb/testsuite/gdb.python/py-pp-cast.c b/gdb/testsuite/gdb.python/py-pp-cast.c
new file mode 100644
index 00000000000..72716a79dd4
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-pp-cast.c
@@ -0,0 +1,35 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023 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/>.  */
+
+typedef int pp_int;
+
+int break_function()
+{
+  return 0;
+}
+
+struct container
+{
+  pp_int p_i;
+  int i;
+};
+
+int main()
+{
+  struct container c = { 10, 5 };
+  return break_function();
+}
diff --git a/gdb/testsuite/gdb.python/py-pp-cast.exp b/gdb/testsuite/gdb.python/py-pp-cast.exp
new file mode 100644
index 00000000000..8a6f2976046
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-pp-cast.exp
@@ -0,0 +1,40 @@
+# Copyright (C) 2023 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/>.
+
+# Test casting of a gdb.Value inside a pretty printer.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+    return -1
+}
+
+if ![runto break_function] {
+    return -1
+}
+
+set remote_python_file [gdb_remote_download host \
+			    ${srcdir}/${subdir}/${testfile}.py]
+
+gdb_test_no_output "source ${remote_python_file}" \
+    "source ${testfile}.py"
+
+gdb_test "up" "#1.*main.*"
+
+gdb_test "info locals" "c = {p_i = 10p, i = 5}"
diff --git a/gdb/testsuite/gdb.python/py-pp-cast.py b/gdb/testsuite/gdb.python/py-pp-cast.py
new file mode 100644
index 00000000000..b171a919c70
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-pp-cast.py
@@ -0,0 +1,28 @@
+# Copyright (C) 2023 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/>.
+
+
+class PpIntPrinter(object):
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        val = self.val.cast(self.val.type)
+        return "%dp" % int(val)
+
+
+pp = gdb.printing.RegexpCollectionPrettyPrinter("pp-cast")
+pp.add_printer("pp_int", "^pp_int$", PpIntPrinter)
+gdb.printing.register_pretty_printer(gdb.current_objfile(), pp)
