-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Add tests for accessing uninitialized holders #5654
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
Draft
XuehaiPan
wants to merge
16
commits into
pybind:master
Choose a base branch
from
XuehaiPan:invalid-holder-access
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+350
−1
Draft
Changes from 12 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
2cbc975
Add tests for accessing uninitialized holders
XuehaiPan edcb5ac
Enable `faulthandler` for pytest
XuehaiPan 4bc7a11
Disable GC test for non-CPython
XuehaiPan 20d23e2
Move segfault test to subprocess
XuehaiPan 2b00621
Refine tests
XuehaiPan 6c6db0e
Add `STATUS_ACCESS_VIOLATION` for Windows exitcode
XuehaiPan 2c73d2b
Update tests
XuehaiPan d103e26
Update docs
XuehaiPan 7db0a9f
Update docs
XuehaiPan 73e2615
Merge remote-tracking branch 'upstream/master' into invalid-holder-ac…
XuehaiPan a5818b6
Merge branch 'master' into invalid-holder-access
XuehaiPan 752d883
Add semi-public API: `pybind11::detail::is_holder_constructed`
XuehaiPan 110d024
Apply suggestions from code review
XuehaiPan cf8f5e6
Merge branch 'master' into invalid-holder-access
XuehaiPan cc0f9c4
Merge branch 'master' into invalid-holder-access
XuehaiPan ea42e9f
Merge branch 'master' into invalid-holder-access
XuehaiPan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
#include "pybind11_tests.h" | ||
|
||
#include <Python.h> | ||
#include <memory> | ||
#include <vector> | ||
|
||
class VecOwnsObjs { | ||
public: | ||
void append(const py::object &obj) { vec.emplace_back(obj); } | ||
|
||
void set_item(py::ssize_t i, const py::object &obj) { | ||
if (!(i >= 0 && i < size())) { | ||
throw std::out_of_range("Index out of range"); | ||
} | ||
vec[py::size_t(i)] = obj; | ||
} | ||
|
||
py::object get_item(py::ssize_t i) const { | ||
if (!(i >= 0 && i < size())) { | ||
throw std::out_of_range("Index out of range"); | ||
} | ||
return vec[py::size_t(i)]; | ||
} | ||
|
||
py::ssize_t size() const { return py::ssize_t_cast(vec.size()); } | ||
|
||
bool is_empty() const { return vec.empty(); } | ||
|
||
static int tp_traverse(PyObject *self_base, visitproc visit, void *arg) { | ||
// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse | ||
#if PY_VERSION_HEX >= 0x03090000 // Python 3.9 | ||
Py_VISIT(Py_TYPE(self_base)); | ||
#endif | ||
|
||
if (should_check_holder_initialization) { | ||
auto *const instance = reinterpret_cast<py::detail::instance *>(self_base); | ||
if (!instance->get_value_and_holder().holder_constructed()) { | ||
// The holder has not been constructed yet. Skip the traversal to avoid | ||
// segmentation faults. | ||
return 0; | ||
} | ||
} | ||
|
||
// The actual logic of the tp_traverse function goes here. | ||
auto &self = py::cast<VecOwnsObjs &>(py::handle{self_base}); | ||
for (const auto &obj : self.vec) { | ||
Py_VISIT(obj.ptr()); | ||
} | ||
return 0; | ||
} | ||
|
||
static int tp_clear(PyObject *self_base) { | ||
if (should_check_holder_initialization) { | ||
auto *const instance = reinterpret_cast<py::detail::instance *>(self_base); | ||
if (!instance->get_value_and_holder().holder_constructed()) { | ||
// The holder has not been constructed yet. Skip the traversal to avoid | ||
// segmentation faults. | ||
return 0; | ||
} | ||
} | ||
|
||
// The actual logic of the tp_clear function goes here. | ||
auto &self = py::cast<VecOwnsObjs &>(py::handle{self_base}); | ||
for (auto &obj : self.vec) { | ||
Py_CLEAR(obj.ptr()); | ||
} | ||
self.vec.clear(); | ||
return 0; | ||
} | ||
|
||
py::object get_state() const { | ||
py::list state{}; | ||
for (const auto &item : vec) { | ||
state.append(item); | ||
} | ||
return py::tuple(state); | ||
} | ||
|
||
static bool get_should_check_holder_initialization() { | ||
return should_check_holder_initialization; | ||
} | ||
|
||
static void set_should_check_holder_initialization(bool value) { | ||
should_check_holder_initialization = value; | ||
} | ||
|
||
static bool get_should_raise_error_on_set_state() { return should_raise_error_on_set_state; } | ||
|
||
static void set_should_raise_error_on_set_state(bool value) { | ||
should_raise_error_on_set_state = value; | ||
} | ||
|
||
static bool should_check_holder_initialization; | ||
static bool should_raise_error_on_set_state; | ||
|
||
private: | ||
std::vector<py::object> vec{}; | ||
}; | ||
|
||
bool VecOwnsObjs::should_check_holder_initialization = false; | ||
bool VecOwnsObjs::should_raise_error_on_set_state = false; | ||
|
||
TEST_SUBMODULE(invalid_holder_access, m) { | ||
m.doc() = "Test invalid holder access"; | ||
|
||
#if defined(PYBIND11_CPP14) | ||
m.def("create_vector", [](const py::iterable &iterable) -> std::unique_ptr<VecOwnsObjs> { | ||
auto vec = std::make_unique<VecOwnsObjs>(); | ||
for (const auto &item : iterable) { | ||
vec->append(py::reinterpret_borrow<py::object>(item)); | ||
} | ||
return vec; | ||
}); | ||
#endif | ||
|
||
py::class_<VecOwnsObjs>( | ||
m, "VecOwnsObjs", py::custom_type_setup([](PyHeapTypeObject *heap_type) -> void { | ||
auto *const type = &heap_type->ht_type; | ||
type->tp_flags |= Py_TPFLAGS_HAVE_GC; | ||
type->tp_traverse = &VecOwnsObjs::tp_traverse; | ||
type->tp_clear = &VecOwnsObjs::tp_clear; | ||
})) | ||
.def_static("set_should_check_holder_initialization", | ||
&VecOwnsObjs::set_should_check_holder_initialization, | ||
py::arg("value")) | ||
.def_static("set_should_raise_error_on_set_state", | ||
&VecOwnsObjs::set_should_raise_error_on_set_state, | ||
py::arg("value")) | ||
#if defined(PYBIND11_CPP14) | ||
.def(py::pickle([](const VecOwnsObjs &self) -> py::object { return self.get_state(); }, | ||
[](const py::object &state) -> std::unique_ptr<VecOwnsObjs> { | ||
if (!py::isinstance<py::tuple>(state)) { | ||
throw std::runtime_error("Invalid state"); | ||
} | ||
auto vec = std::make_unique<VecOwnsObjs>(); | ||
if (VecOwnsObjs::get_should_raise_error_on_set_state()) { | ||
throw std::runtime_error("raise error on set_state for testing"); | ||
} | ||
for (const auto &item : state) { | ||
vec->append(py::reinterpret_borrow<py::object>(item)); | ||
} | ||
return vec; | ||
}), | ||
py::arg("state")) | ||
#endif | ||
.def("append", &VecOwnsObjs::append, py::arg("obj")) | ||
.def("is_empty", &VecOwnsObjs::is_empty) | ||
.def("__setitem__", &VecOwnsObjs::set_item, py::arg("i"), py::arg("obj")) | ||
.def("__getitem__", &VecOwnsObjs::get_item, py::arg("i")) | ||
.def("__len__", &VecOwnsObjs::size); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will look a lot more straightforward if we simply guard the existing code with
Could you please update the code in tests/test_custom_type_setup.cpp accordingly?
Do we still need the new test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it can serve as a backlog if we implement #5654 (comment) eventually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what'll be most practical and best at this stage:
Pull out the changes to docs/advanced/classes.rst and include/pybind11/detail/value_and_holder.h into a separate PR.
Update tests/test_custom_type_setup.cpp to be 1:1 with the updated documentation in classes.rst
Merge that PR.
Then you can merge the new master here and work on this PR in the future.
The tests will need more work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened #5669.