Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -655,9 +655,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()));
Expand Down Expand Up @@ -702,6 +702,28 @@ struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_s
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
Expand Down
3 changes: 3 additions & 0 deletions tests/test_python_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; });
Expand Down
14 changes: 14 additions & 0 deletions tests/test_python_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down