Fix Python 3 bytes conversion to std::string/char*

The Unicode support added in 2.1 (PR #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.
This commit is contained in:
Jason Rhinelander 2017-04-26 10:49:55 -04:00
parent ce494d65de
commit a7f704b39b
3 changed files with 41 additions and 2 deletions

View File

@ -693,9 +693,9 @@ struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_s
return false;
} else if (!PyUnicode_Check(load_src.ptr())) {
#if PY_MAJOR_VERSION >= 3
return false;
// The below is a guaranteed failure in Python 3 when PyUnicode_Check returns false
return load_bytes(load_src);
#else
// The below is a guaranteed failure in Python 3 when PyUnicode_Check returns false
if (!PYBIND11_BYTES_CHECK(load_src.ptr()))
return false;
temp = reinterpret_steal<object>(PyUnicode_FromObject(load_src.ptr()));
@ -740,6 +740,28 @@ private:
return PyUnicode_Decode(buffer, nbytes, UTF_N == 8 ? "utf-8" : UTF_N == 16 ? "utf-16" : "utf-32", nullptr);
#endif
}
#if PY_MAJOR_VERSION >= 3
// In Python 3, when loading into a std::string or char*, accept a bytes object as-is (i.e.
// without any encoding/decoding attempt). For other C++ char sizes this is a no-op. Python 2,
// which supports loading a unicode from a str, doesn't take this path.
template <typename C = CharT>
bool load_bytes(enable_if_t<sizeof(C) == 1, handle> src) {
if (PYBIND11_BYTES_CHECK(src.ptr())) {
// We were passed a Python 3 raw bytes; accept it into a std::string or char*
// without any encoding attempt.
const char *bytes = PYBIND11_BYTES_AS_STRING(src.ptr());
if (bytes) {
value = StringType(bytes, (size_t) PYBIND11_BYTES_SIZE(src.ptr()));
return true;
}
}
return false;
}
template <typename C = CharT>
bool load_bytes(enable_if_t<sizeof(C) != 1, handle>) { return false; }
#endif
};
// Type caster for C-style strings. We basically use a std::string type caster, but also add the

View File

@ -473,6 +473,9 @@ test_initializer python_types([](py::module &m) {
m.def("ord_char32", [](char32_t c) -> uint32_t { return c; });
m.def("ord_wchar", [](wchar_t c) -> int { return c; });
m.def("strlen", [](char *s) { return strlen(s); });
m.def("string_length", [](std::string s) { return s.length(); });
m.def("return_none_string", []() -> std::string * { return nullptr; });
m.def("return_none_char", []() -> const char * { return nullptr; });
m.def("return_none_bool", []() -> bool * { return nullptr; });

View File

@ -511,6 +511,20 @@ def test_single_char_arguments():
assert str(excinfo.value) == toolong_message
def test_bytes_to_string():
"""Tests the ability to pass bytes to C++ string-accepting functions. Note that this is
one-way: the only way to return bytes to Python is via the pybind11::bytes class."""
# Issue #816
from pybind11_tests import strlen, string_length
import sys
byte = bytes if sys.version_info[0] < 3 else str
assert strlen(byte("hi")) == 2
assert string_length(byte("world")) == 5
assert string_length(byte("a\x00b")) == 3
assert strlen(byte("a\x00b")) == 1 # C-string limitation
def test_builtins_cast_return_none():
"""Casters produced with PYBIND11_TYPE_CASTER() should convert nullptr to None"""
import pybind11_tests as m