Skip to content

Commit 291c1f9

Browse files
committed
Fix Python 3 bytes conversion to std::string/char*
The Unicode support added in 2.1 (PR pybind#624) inadvertently broke accepting `bytes` as std::string/char* arguments. This restores it with a separate path that does a plain conversion (i.e. completely bypassing all the encoding/decoding code), but only for single-byte string types.
1 parent deb85f4 commit 291c1f9

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

include/pybind11/cast.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -655,9 +655,9 @@ struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_s
655655
return false;
656656
} else if (!PyUnicode_Check(load_src.ptr())) {
657657
#if PY_MAJOR_VERSION >= 3
658-
return false;
659-
// The below is a guaranteed failure in Python 3 when PyUnicode_Check returns false
658+
return load_bytes(load_src);
660659
#else
660+
// The below is a guaranteed failure in Python 3 when PyUnicode_Check returns false
661661
if (!PYBIND11_BYTES_CHECK(load_src.ptr()))
662662
return false;
663663
temp = reinterpret_steal<object>(PyUnicode_FromObject(load_src.ptr()));
@@ -702,6 +702,28 @@ struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_s
702702
return PyUnicode_Decode(buffer, nbytes, UTF_N == 8 ? "utf-8" : UTF_N == 16 ? "utf-16" : "utf-32", nullptr);
703703
#endif
704704
}
705+
706+
#if PY_MAJOR_VERSION >= 3
707+
// In Python 3, when loading into a std::string or char*, accept a bytes object as-is (i.e.
708+
// without any encoding/decoding attempt). For other C++ char sizes this is a no-op. Python 2,
709+
// which supports loading a unicode from a str, doesn't take this path.
710+
template <typename C = CharT>
711+
bool load_bytes(enable_if_t<sizeof(C) == 1, handle> src) {
712+
if (PYBIND11_BYTES_CHECK(src.ptr())) {
713+
// We were passed a Python 3 raw bytes; accept it into a std::string or char*
714+
// without any encoding attempt.
715+
const char *bytes = PYBIND11_BYTES_AS_STRING(src.ptr());
716+
if (bytes) {
717+
value = StringType(bytes, PYBIND11_BYTES_SIZE(src.ptr()));
718+
return true;
719+
}
720+
}
721+
722+
return false;
723+
}
724+
template <typename C = CharT>
725+
bool load_bytes(enable_if_t<sizeof(C) != 1, handle>) { return false; }
726+
#endif
705727
};
706728

707729
// Type caster for C-style strings. We basically use a std::string type caster, but also add the

tests/test_python_types.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,9 @@ test_initializer python_types([](py::module &m) {
473473
m.def("ord_char32", [](char32_t c) -> uint32_t { return c; });
474474
m.def("ord_wchar", [](wchar_t c) -> int { return c; });
475475

476+
m.def("strlen", [](char *s) { return strlen(s); });
477+
m.def("string_length", [](std::string s) { return s.length(); });
478+
476479
m.def("return_none_string", []() -> std::string * { return nullptr; });
477480
m.def("return_none_char", []() -> const char * { return nullptr; });
478481
m.def("return_none_bool", []() -> bool * { return nullptr; });

tests/test_python_types.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,20 @@ def toobig_message(r):
511511
assert str(excinfo.value) == toolong_message
512512

513513

514+
def test_bytes_to_string():
515+
"""Tests the ability to pass bytes to C++ string-accepting functions. Note that this is
516+
one-way: the only way to return bytes to Python is via the pybind11::bytes class."""
517+
# Issue #816
518+
from pybind11_tests import strlen, string_length
519+
import sys
520+
byte = bytes if sys.version_info[0] < 3 else str
521+
522+
assert strlen(byte("hi")) == 2
523+
assert string_length(byte("world")) == 5
524+
assert string_length(byte("a\x00b")) == 3
525+
assert strlen(byte("a\x00b")) == 1 # C-string limitation
526+
527+
514528
def test_builtins_cast_return_none():
515529
"""Casters produced with PYBIND11_TYPE_CASTER() should convert nullptr to None"""
516530
import pybind11_tests as m

0 commit comments

Comments
 (0)