Add check if `str(handle)` correctly converted the object, and throw py::error_already_set if not (bis) (#2477)

* Add check if `str(handle)` correctly converted the object, and throw py::error_already_set if not

* Fix tests on Python 3

* Apply @rwgk's fixes to cherry-picked commits from #2392
diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h
index bea34cd..00c791a 100644
--- a/include/pybind11/pytypes.h
+++ b/include/pybind11/pytypes.h
@@ -920,7 +920,7 @@
         Return a string representation of the object. This is analogous to
         the ``str()`` function in Python.
     \endrst */
-    explicit str(handle h) : object(raw_str(h.ptr()), stolen_t{}) { }
+    explicit str(handle h) : object(raw_str(h.ptr()), stolen_t{}) { if (!m_ptr) throw error_already_set(); }
 
     operator std::string() const {
         object temp = *this;
@@ -945,8 +945,8 @@
     /// Return string representation -- always returns a new reference, even if already a str
     static PyObject *raw_str(PyObject *op) {
         PyObject *str_value = PyObject_Str(op);
-        if (!str_value) throw error_already_set();
 #if PY_MAJOR_VERSION < 3
+        if (!str_value) throw error_already_set();
         PyObject *unicode = PyUnicode_FromEncodedObject(str_value, "utf-8", nullptr);
         Py_XDECREF(str_value); str_value = unicode;
 #endif
diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp
index 925d6ff..4ef1b9f 100644
--- a/tests/test_pytypes.cpp
+++ b/tests/test_pytypes.cpp
@@ -80,6 +80,7 @@
     m.def("str_from_bytes", []() { return py::str(py::bytes("boo", 3)); });
     m.def("str_from_object", [](const py::object& obj) { return py::str(obj); });
     m.def("repr_from_object", [](const py::object& obj) { return py::repr(obj); });
+    m.def("str_from_handle", [](py::handle h) { return py::str(h); });
 
     m.def("str_format", []() {
         auto s1 = "{} + {} = {}"_s.format(1, 2, 3);
diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py
index 277c170..0618cd5 100644
--- a/tests/test_pytypes.py
+++ b/tests/test_pytypes.py
@@ -104,11 +104,23 @@
 
     assert m.str_from_object(A()) == "this is a str"
     assert m.repr_from_object(A()) == "this is a repr"
+    assert m.str_from_handle(A()) == "this is a str"
 
     s1, s2 = m.str_format()
     assert s1 == "1 + 2 = 3"
     assert s1 == s2
 
+    malformed_utf8 = b"\x80"
+    assert m.str_from_object(malformed_utf8) is malformed_utf8  # To be fixed; see #2380
+    if env.PY2:
+        # with pytest.raises(UnicodeDecodeError):
+        #     m.str_from_object(malformed_utf8)
+        with pytest.raises(UnicodeDecodeError):
+            m.str_from_handle(malformed_utf8)
+    else:
+        # assert m.str_from_object(malformed_utf8) == "b'\\x80'"
+        assert m.str_from_handle(malformed_utf8) == "b'\\x80'"
+
 
 def test_bytes(doc):
     assert m.bytes_from_string().decode() == "foo"