Commit 215bcc60 by Ralf W. Grosse-Kunstleve Committed by Copybara-Service

Add cpp_capsule_tools/make_shared_ptr_capsule.h, shared_ptr_from_capsule.h.

Move existing raw_ptr_from_capsule.h to cpp_capsule_tools/ subdirectory, factoring out void_ptr_from_capsule.h.

PiperOrigin-RevId: 545245445
parent 2e728652
......@@ -81,8 +81,8 @@ pybind_library(
":check_status_module_imported",
":no_throw_status",
":ok_status_singleton_lib",
":raw_ptr_from_capsule",
":status_not_ok_exception",
"//pybind11_abseil/cpp_capsule_tools:raw_ptr_from_capsule",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
],
......@@ -106,19 +106,6 @@ cc_library(
visibility = ["//visibility:private"],
)
cc_library(
name = "raw_ptr_from_capsule",
srcs = ["raw_ptr_from_capsule.cc"],
hdrs = ["raw_ptr_from_capsule.h"],
visibility = ["//visibility:private"],
deps = [
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@local_config_python//:python_headers", # buildcleaner: keep
],
)
pybind_library(
name = "utils_pybind11_absl",
srcs = ["utils_pybind11_absl.cc"],
......@@ -139,10 +126,10 @@ pybind_library(
":init_from_tag",
":no_throw_status",
":ok_status_singleton_lib",
":raw_ptr_from_capsule",
":status_caster",
":status_not_ok_exception",
":utils_pybind11_absl",
"//pybind11_abseil/cpp_capsule_tools:raw_ptr_from_capsule",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
......
licenses(["notice"])
package(
default_visibility = ["//visibility:private"],
)
cc_library(
name = "void_ptr_from_capsule",
srcs = ["void_ptr_from_capsule.cc"],
hdrs = ["void_ptr_from_capsule.h"],
visibility = ["//visibility:public"],
deps = [
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@local_config_python//:python_headers", # buildcleaner: keep
],
)
cc_library(
name = "raw_ptr_from_capsule",
hdrs = ["raw_ptr_from_capsule.h"],
visibility = ["//visibility:public"],
deps = [
":void_ptr_from_capsule",
"@com_google_absl//absl/status:statusor",
"@local_config_python//:python_headers", # buildcleaner: keep
],
)
cc_library(
name = "make_shared_ptr_capsule",
hdrs = ["make_shared_ptr_capsule.h"],
visibility = ["//visibility:public"],
deps = [
"@local_config_python//:python_headers", # buildcleaner: keep
],
)
cc_library(
name = "shared_ptr_from_capsule",
hdrs = ["shared_ptr_from_capsule.h"],
visibility = ["//visibility:public"],
deps = [
":void_ptr_from_capsule",
"@com_google_absl//absl/status:statusor",
"@local_config_python//:python_headers", # buildcleaner: keep
],
)
#ifndef PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_MAKE_SHARED_PTR_CAPSULE_H_
#define PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_MAKE_SHARED_PTR_CAPSULE_H_
// Must be first include (https://docs.python.org/3/c-api/intro.html).
#include <Python.h>
#include <cassert>
#include <memory>
namespace pybind11_abseil {
namespace cpp_capsule_tools {
// Returns a capsule owning a dynamically allocated copy of the passed
// shared_ptr, or nullptr if an error occurred.
// If the return value is nullptr, the Python error indicator is set.
template <typename T>
PyObject* MakeSharedPtrCapsule(const std::shared_ptr<T>& sp, const char* name) {
using sp_t = std::shared_ptr<T>;
std::unique_ptr<sp_t> sp_heap(new sp_t(sp)); // C++11 compatibility.
PyObject* cap = PyCapsule_New(
// Portability note. The function type underlying the pointer-to-function
// type that results from implicit conversion of this lambda has not got
// C-language linkage, but seems to work (and this patterns is widely
// used in the pybind11 sources).
sp_heap.get(), name, /* PyCapsule_Destructor */ [](PyObject* self) {
// Fetch (and restore below) existing Python error, if any.
// This is to not mask errors during teardown.
PyObject *prev_err_type, *prev_err_value, *prev_err_traceback;
PyErr_Fetch(&prev_err_type, &prev_err_value, &prev_err_traceback);
const char* self_name = PyCapsule_GetName(self);
if (PyErr_Occurred()) {
// Something is critically wrong with the process if this happens.
// Skipping deallocation of the owned shared_ptr is most likely
// completely insignificant in comparison. Intentionally not
// terminating the process, to not disrupt potentially in-flight
// error reporting.
PyErr_Print();
// Intentionally after PyErr_Print(), to rescue as much information
// as possible.
assert(self_name == nullptr);
} else {
void* void_ptr = PyCapsule_GetPointer(self, self_name);
if (PyErr_Occurred()) {
PyErr_Print(); // See comments above.
assert(void_ptr == nullptr);
} else {
delete static_cast<sp_t*>(void_ptr);
}
}
PyErr_Restore(prev_err_type, prev_err_value, prev_err_traceback);
});
if (cap != nullptr) {
sp_heap.release();
}
return cap;
}
} // namespace cpp_capsule_tools
} // namespace pybind11_abseil
#endif // PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_MAKE_SHARED_PTR_CAPSULE_H_
#ifndef PYBIND11_ABSEIL_RAW_PTR_FROM_CAPSULE_H_
#define PYBIND11_ABSEIL_RAW_PTR_FROM_CAPSULE_H_
#ifndef PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_RAW_PTR_FROM_CAPSULE_H_
#define PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_RAW_PTR_FROM_CAPSULE_H_
// Must be first include (https://docs.python.org/3/c-api/intro.html).
#include <Python.h>
#include "absl/status/statusor.h"
#include "pybind11_abseil/cpp_capsule_tools/void_ptr_from_capsule.h"
namespace pybind11_abseil {
namespace raw_ptr_from_capsule {
// Prefer RawPtrFromCapsule<T>(), to minimize the use of static_casts, and
// to keep them as close as possible to the context establishing type safety
// (via the capsule name).
absl::StatusOr<void*> VoidPtrFromCapsule(PyObject* py_obj, const char* name,
const char* as_capsule_method_name);
namespace cpp_capsule_tools {
// Extract a raw pointer from a PyCapsule or return absl::InvalidArgumentError,
// with a detailed message.
// The function arguments are documented under VoidPtrFromCapsule().
// CAUTION: The returned raw pointer does (of course) not manage the lifetime
// of the pointee! It is best to use the raw pointer only for the
// duration of a function call, similar to e.g. std::string::c_str(),
// but not to store it in any way (e.g. as a data member of a
// long-lived object).
// If py_obj is a capsule, the capsule name is inspected. If it matches the
// name argument, the raw pointer is returned. Otherwise
// absl::InvalidArgumentError is returned. - Note that name can be nullptr,
// and can match a nullptr capsule name.
// If py_obj is not a capsule, and as_capsule_method_name is given (i.e. it is
// not nullptr), the py_obj method with that name will be called without
// arguments, with the expectation to receive a capsule object in return, from
// which the raw pointer is then extracted exactly as described above.
// A specific error message is generated for every possible error condition
// (most of the code in this function is for error handling).
template <typename T>
absl::StatusOr<T*> RawPtrFromCapsule(PyObject* py_obj, const char* name,
const char* as_capsule_method_name) {
absl::StatusOr<void*> statusor_void_ptr =
absl::StatusOr<std::pair<PyObject*, void*>> statusor_void_ptr =
VoidPtrFromCapsule(py_obj, name, as_capsule_method_name);
if (!statusor_void_ptr.ok()) {
return statusor_void_ptr.status();
}
return static_cast<T*>(statusor_void_ptr.value());
Py_XDECREF(statusor_void_ptr.value().first);
return static_cast<T*>(statusor_void_ptr.value().second);
}
} // namespace raw_ptr_from_capsule
} // namespace cpp_capsule_tools
} // namespace pybind11_abseil
#endif // PYBIND11_ABSEIL_RAW_PTR_FROM_CAPSULE_H_
#endif // PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_RAW_PTR_FROM_CAPSULE_H_
#ifndef PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_SHARED_PTR_FROM_CAPSULE_H_
#define PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_SHARED_PTR_FROM_CAPSULE_H_
// Must be first include (https://docs.python.org/3/c-api/intro.html).
#include <Python.h>
#include <memory>
#include <utility>
#include "absl/status/statusor.h"
#include "pybind11_abseil/cpp_capsule_tools/void_ptr_from_capsule.h"
namespace pybind11_abseil {
namespace cpp_capsule_tools {
// Extract a shared_ptr from a PyCapsule or return absl::InvalidArgumentError,
// with a detailed message.
template <typename T>
absl::StatusOr<std::shared_ptr<T>> SharedPtrFromCapsule(
PyObject* py_obj, const char* name, const char* as_capsule_method_name) {
absl::StatusOr<std::pair<PyObject*, void*>> statusor_void_ptr =
VoidPtrFromCapsule(py_obj, name, as_capsule_method_name);
if (!statusor_void_ptr.ok()) {
return statusor_void_ptr.status();
}
auto sp = *static_cast<std::shared_ptr<T>*>(statusor_void_ptr.value().second);
Py_XDECREF(statusor_void_ptr.value().first);
return sp;
}
} // namespace cpp_capsule_tools
} // namespace pybind11_abseil
#endif // PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_SHARED_PTR_FROM_CAPSULE_H_
#include "pybind11_abseil/raw_ptr_from_capsule.h"
#include "pybind11_abseil/cpp_capsule_tools/void_ptr_from_capsule.h"
#include <Python.h>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
namespace pybind11_abseil {
namespace raw_ptr_from_capsule {
namespace cpp_capsule_tools {
namespace {
......@@ -15,7 +18,7 @@ namespace {
// requirement.
// Equivalent to obj.__class__.__name__ (or obj.__name__ if obj is a class).
const char* obj_class_name(PyObject* obj) {
if (Py_TYPE(obj) == &PyType_Type) {
if (PyType_Check(obj)) {
return reinterpret_cast<PyTypeObject*>(obj)->tp_name;
}
return Py_TYPE(obj)->tp_name;
......@@ -29,8 +32,8 @@ std::string quoted_name_or_null_indicator(
} // namespace
absl::StatusOr<void*> VoidPtrFromCapsule(PyObject* py_obj, const char* name,
const char* as_capsule_method_name) {
absl::StatusOr<std::pair<PyObject*, void*>> VoidPtrFromCapsule(
PyObject* py_obj, const char* name, const char* as_capsule_method_name) {
// Note: https://docs.python.org/3/c-api/capsule.html:
// The pointer argument may not be NULL.
if (PyCapsule_CheckExact(py_obj)) {
......@@ -42,7 +45,7 @@ absl::StatusOr<void*> VoidPtrFromCapsule(PyObject* py_obj, const char* name,
quoted_name_or_null_indicator(PyCapsule_GetName(py_obj)), " but ",
quoted_name_or_null_indicator(name), " is expected."));
}
return void_ptr;
return std::pair<PyObject*, void*>(nullptr, void_ptr);
}
if (as_capsule_method_name == nullptr) {
return absl::InvalidArgumentError(
......@@ -91,8 +94,7 @@ absl::StatusOr<void*> VoidPtrFromCapsule(PyObject* py_obj, const char* name,
}
void* void_ptr = PyCapsule_GetPointer(from_method, name);
if (!PyErr_Occurred()) {
Py_DECREF(from_method);
return void_ptr;
return std::pair<PyObject*, void*>(from_method, void_ptr);
}
PyErr_Clear();
std::string capsule_name =
......@@ -104,5 +106,5 @@ absl::StatusOr<void*> VoidPtrFromCapsule(PyObject* py_obj, const char* name,
quoted_name_or_null_indicator(name), " is expected."));
}
} // namespace raw_ptr_from_capsule
} // namespace cpp_capsule_tools
} // namespace pybind11_abseil
#ifndef PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_VOID_PTR_FROM_CAPSULE_H_
#define PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_VOID_PTR_FROM_CAPSULE_H_
// Must be first include (https://docs.python.org/3/c-api/intro.html).
#include <Python.h>
#include <utility>
#include "absl/status/statusor.h"
namespace pybind11_abseil {
namespace cpp_capsule_tools {
// Helper for higher-level functions (e.g. RawPtrFromCapsule(),
// SharedPtrFromCapsule()).
//
// Arguments:
// If py_obj is a capsule, the capsule name is inspected. If it matches the
// name argument, the raw pointer is returned. Otherwise
// absl::InvalidArgumentError is returned. - Note that name can be nullptr,
// and can match a nullptr capsule name.
// If py_obj is not a capsule, and as_capsule_method_name is given (i.e. it
// is not nullptr), the py_obj method with that name will be called without
// arguments, with the expectation to receive a capsule object in return,
// from which the raw pointer is then extracted exactly as described above.
// A specific error message is generated for every possible error condition
// (most of the code in this function is for error handling).
// Return value:
// The PyObject* is nullptr if the input py_obj is a capsule.
// Otherwise PyObject* is the capsule obtained in the function call.
// IMPORTANT: It is the responsibility of the caller to call Py_XDECREF().
absl::StatusOr<std::pair<PyObject*, void*>> VoidPtrFromCapsule(
PyObject* py_obj, const char* name, const char* as_capsule_method_name);
} // namespace cpp_capsule_tools
} // namespace pybind11_abseil
#endif // PYBIND11_ABSEIL_CPP_CAPSULE_TOOLS_VOID_PTR_FROM_CAPSULE_H_
......@@ -14,10 +14,10 @@
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "pybind11_abseil/absl_casters.h"
#include "pybind11_abseil/cpp_capsule_tools/raw_ptr_from_capsule.h"
#include "pybind11_abseil/init_from_tag.h"
#include "pybind11_abseil/no_throw_status.h"
#include "pybind11_abseil/ok_status_singleton_lib.h"
#include "pybind11_abseil/raw_ptr_from_capsule.h"
#include "pybind11_abseil/status_caster.h"
#include "pybind11_abseil/status_not_ok_exception.h"
#include "pybind11_abseil/utils_pybind11_absl.h"
......@@ -103,7 +103,7 @@ void def_status_factory(
absl::StatusOr<absl::Status*> StatusRawPtrFromCapsule(
const object& obj, bool enable_as_capsule_method = true) {
return pybind11_abseil::raw_ptr_from_capsule::RawPtrFromCapsule<absl::Status>(
return pybind11_abseil::cpp_capsule_tools::RawPtrFromCapsule<absl::Status>(
obj.ptr(), "::absl::Status",
enable_as_capsule_method ? "as_absl_Status" : nullptr);
}
......
......@@ -10,9 +10,9 @@
#include "absl/status/status.h"
#include "pybind11_abseil/check_status_module_imported.h"
#include "pybind11_abseil/cpp_capsule_tools/raw_ptr_from_capsule.h"
#include "pybind11_abseil/no_throw_status.h"
#include "pybind11_abseil/ok_status_singleton_lib.h"
#include "pybind11_abseil/raw_ptr_from_capsule.h"
#include "pybind11_abseil/status_not_ok_exception.h"
namespace pybind11 {
......@@ -81,7 +81,7 @@ struct type_caster<absl::Status> : public type_caster_base<absl::Status> {
}
if (convert) {
absl::StatusOr<void*> raw_ptr =
pybind11_abseil::raw_ptr_from_capsule::RawPtrFromCapsule<void>(
pybind11_abseil::cpp_capsule_tools::RawPtrFromCapsule<void>(
src.ptr(), "::absl::Status", "as_absl_Status");
if (raw_ptr.ok()) {
value = raw_ptr.value();
......
......@@ -6,6 +6,25 @@ load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")
licenses(["notice"])
pybind_extension(
name = "cpp_capsule_tools_testing",
srcs = ["cpp_capsule_tools_testing.cc"],
deps = [
"//pybind11_abseil/cpp_capsule_tools:make_shared_ptr_capsule",
"//pybind11_abseil/cpp_capsule_tools:raw_ptr_from_capsule",
"//pybind11_abseil/cpp_capsule_tools:shared_ptr_from_capsule",
"@com_google_absl//absl/status:statusor",
],
)
py_test(
name = "cpp_capsule_tools_testing_test",
srcs = ["cpp_capsule_tools_testing_test.py"],
data = [":cpp_capsule_tools_testing.so"],
python_version = "PY3",
srcs_version = "PY3",
)
pybind_extension(
name = "absl_example",
srcs = ["absl_example.cc"],
deps = [
......
// Copyright (c) 2023 The Pybind Development Team. All rights reserved.
//
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#if true // go/pybind11_include_order
#include <pybind11/pybind11.h>
#endif
#include <memory>
#include "absl/status/statusor.h"
#include "pybind11_abseil/cpp_capsule_tools/make_shared_ptr_capsule.h"
#include "pybind11_abseil/cpp_capsule_tools/raw_ptr_from_capsule.h"
#include "pybind11_abseil/cpp_capsule_tools/shared_ptr_from_capsule.h"
PYBIND11_MODULE(cpp_capsule_tools_testing, m) {
namespace py = pybind11;
namespace cpp_capsule_tools = pybind11_abseil::cpp_capsule_tools;
m.def("make_bad_capsule", [](bool pass_name) {
// https://docs.python.org/3/c-api/capsule.html:
// The pointer argument may not be NULL.
int dummy_pointee[] = {}; // This will become a dangling pointer when this
// function returns: We don't want the pointer to be used. Hopefully if it
// is used unintentionally, one of the sanitizers will flag it.
return py::capsule(static_cast<void*>(dummy_pointee),
pass_name ? "NotGood" : nullptr);
});
m.def("make_raw_ptr_capsule", []() {
static int any_int = 890352;
return py::capsule(&any_int, "type:int");
});
m.def("get_int_from_raw_ptr_capsule",
[](py::handle py_obj, bool enable_method) {
absl::StatusOr<int*> status_or_raw_ptr =
cpp_capsule_tools::RawPtrFromCapsule<int>(
py_obj.ptr(), "type:int",
(enable_method ? "get_capsule" : nullptr));
if (!status_or_raw_ptr.ok()) {
return status_or_raw_ptr.status().ToString();
}
return std::to_string(*status_or_raw_ptr.value());
});
m.def("make_shared_ptr_capsule", []() {
return py::reinterpret_steal<py::capsule>(
cpp_capsule_tools::MakeSharedPtrCapsule(std::make_shared<int>(906069),
"type:shared_ptr<int>"));
});
m.def("get_int_from_shared_ptr_capsule",
[](py::handle py_obj, bool enable_method) {
using sp_t = std::shared_ptr<int>;
absl::StatusOr<sp_t> status_or_shared_ptr =
cpp_capsule_tools::SharedPtrFromCapsule<int>(
py_obj.ptr(), "type:shared_ptr<int>",
(enable_method ? "get_capsule" : nullptr));
if (!status_or_shared_ptr.ok()) {
return status_or_shared_ptr.status().ToString();
}
return std::to_string(*status_or_shared_ptr.value());
});
}
from absl.testing import absltest
from absl.testing import parameterized
from pybind11_abseil.tests import cpp_capsule_tools_testing as tstng
class UsingMakeCapsule:
def __init__(self, make_capsule):
self.make_capsule = make_capsule
def get_capsule(self):
return self.make_capsule()
class BadCapsule:
def __init__(self, pass_name):
self.pass_name = pass_name
def get_capsule(self):
return tstng.make_bad_capsule(self.pass_name)
class NotACapsule:
def __init__(self, not_a_capsule):
self.not_a_capsule = not_a_capsule
def get_capsule(self):
return self.not_a_capsule
class RaisingGetCapsule:
def get_capsule(self):
raise RuntimeError('from get_capsule')
class CppCapsuleToolsTest(parameterized.TestCase):
def test_raw_ptr_capsule_direct(self):
cap = tstng.make_raw_ptr_capsule()
res = tstng.get_int_from_raw_ptr_capsule(cap, False)
self.assertEqual(res, '890352')
def test_raw_ptr_capsule_method(self):
using_cap = UsingMakeCapsule(tstng.make_raw_ptr_capsule)
res = tstng.get_int_from_raw_ptr_capsule(using_cap, True)
self.assertEqual(res, '890352')
def test_shared_ptr_capsule_direct(self):
cap = tstng.make_shared_ptr_capsule()
res = tstng.get_int_from_shared_ptr_capsule(cap, False)
self.assertEqual(res, '906069')
def test_shared_ptr_capsule_method(self):
using_cap = UsingMakeCapsule(tstng.make_shared_ptr_capsule)
res = tstng.get_int_from_shared_ptr_capsule(using_cap, True)
self.assertEqual(res, '906069')
@parameterized.parameters((False, 'NULL'), (True, '"NotGood"'))
def test_raw_ptr_capsule_direct_bad_capsule(self, pass_name, quoted_name):
cap = tstng.make_bad_capsule(pass_name)
res = tstng.get_int_from_raw_ptr_capsule(cap, False)
self.assertEqual(
res,
f'INVALID_ARGUMENT: obj is a capsule with name {quoted_name} but'
' "type:int" is expected.',
)
@parameterized.parameters((False, 'NULL'), (True, '"NotGood"'))
def test_raw_ptr_capsule_method_bad_capsule(self, pass_name, quoted_name):
cap = BadCapsule(pass_name)
res = tstng.get_int_from_raw_ptr_capsule(cap, True)
self.assertEqual(
res,
'INVALID_ARGUMENT: BadCapsule.get_capsule() returned a capsule with'
f' name {quoted_name} but "type:int" is expected.',
)
@parameterized.parameters(None, '', 0)
def test_raw_ptr_capsule_direct_not_a_capsule(self, not_a_capsule):
res = tstng.get_int_from_raw_ptr_capsule(not_a_capsule, False)
self.assertEqual(
res,
f'INVALID_ARGUMENT: {not_a_capsule.__class__.__name__} object is not a'
' capsule.',
)
@parameterized.parameters(None, '', 0)
def test_raw_ptr_capsule_method_not_a_capsule(self, not_a_capsule):
cap = NotACapsule(not_a_capsule)
res = tstng.get_int_from_raw_ptr_capsule(cap, True)
self.assertEqual(
res,
'INVALID_ARGUMENT: NotACapsule.get_capsule() returned an object'
f' ({not_a_capsule.__class__.__name__}) that is not a capsule.',
)
@parameterized.parameters(
tstng.get_int_from_raw_ptr_capsule, tstng.get_int_from_shared_ptr_capsule
)
def test_raising_get_capsule(self, get_int_from_capsule):
cap = RaisingGetCapsule()
res = get_int_from_capsule(cap, True)
self.assertEqual(
res,
'INVALID_ARGUMENT: RaisingGetCapsule.get_capsule() call failed:'
' RuntimeError: from get_capsule',
)
if __name__ == '__main__':
absltest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment