From 36db77acc1ff68503b2e0662d8700be9dde43bdd Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 26 Apr 2017 10:49:55 -0400 Subject: [PATCH] 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. --- include/pybind11/cast.h | 26 ++++++++++++++++++++++++-- tests/test_python_types.cpp | 3 +++ tests/test_python_types.py | 14 ++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index b99620393c..29c924b300 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -655,9 +655,9 @@ struct type_caster, enable_if_t= 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(PyUnicode_FromObject(load_src.ptr())); @@ -702,6 +702,28 @@ struct type_caster, enable_if_t= 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 + bool load_bytes(enable_if_t 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 + bool load_bytes(enable_if_t) { return false; } +#endif }; // Type caster for C-style strings. We basically use a std::string type caster, but also add the diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index 6f2080994a..18aa87a6f9 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -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; }); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index cf8c1476b8..9849bc8491 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -511,6 +511,20 @@ def toobig_message(r): 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