mirror of
https://github.com/pybind/pybind11.git
synced 2026-05-24 14:55:01 +00:00
Prevent overwriting previous declarations
Currently pybind11 doesn't check when you define a new object (e.g. a class, function, or exception) that overwrites an existing one. If the thing being overwritten is a class, this leads to a segfault (because pybind still thinks the type is defined, even though Python no longer has the type). In other cases this is harmless (e.g. replacing a function with an exception), but even in that case it's most likely a bug. This code doesn't prevent you from actively doing something harmful, like deliberately overwriting a previous definition, but detects overwriting with a run-time error if it occurs in the standard class/function/exception/def registration interfaces. All of the additions are in non-template code; the result is actually a tiny decrease in .so size compared to master without the new test code (977304 to 977272 bytes), and about 4K higher with the new tests.
This commit is contained in:
@@ -253,13 +253,19 @@ protected:
|
||||
#endif
|
||||
|
||||
detail::function_record *chain = nullptr, *chain_start = rec;
|
||||
if (rec->sibling && PyCFunction_Check(rec->sibling.ptr())) {
|
||||
capsule rec_capsule(PyCFunction_GetSelf(rec->sibling.ptr()), true);
|
||||
chain = (detail::function_record *) rec_capsule;
|
||||
/* Never append a method to an overload chain of a parent class;
|
||||
instead, hide the parent's overloads in this case */
|
||||
if (chain->class_ != rec->class_)
|
||||
chain = nullptr;
|
||||
if (rec->sibling) {
|
||||
if (PyCFunction_Check(rec->sibling.ptr())) {
|
||||
capsule rec_capsule(PyCFunction_GetSelf(rec->sibling.ptr()), true);
|
||||
chain = (detail::function_record *) rec_capsule;
|
||||
/* Never append a method to an overload chain of a parent class;
|
||||
instead, hide the parent's overloads in this case */
|
||||
if (chain->class_ != rec->class_)
|
||||
chain = nullptr;
|
||||
}
|
||||
// Don't trigger for things like the default __init__, which are wrapper_descriptors that we are intentionally replacing
|
||||
else if (!rec->sibling.is_none() && rec->name[0] != '_')
|
||||
pybind11_fail("Cannot overload existing non-function object \"" + std::string(rec->name) +
|
||||
"\" with a function of the same name");
|
||||
}
|
||||
|
||||
if (!chain) {
|
||||
@@ -546,8 +552,9 @@ public:
|
||||
module &def(const char *name_, Func &&f, const Extra& ... extra) {
|
||||
cpp_function func(std::forward<Func>(f), name(name_), scope(*this),
|
||||
sibling(getattr(*this, name_, none())), extra...);
|
||||
/* PyModule_AddObject steals a reference to 'func' */
|
||||
PyModule_AddObject(ptr(), name_, func.inc_ref().ptr());
|
||||
// NB: allow overwriting here because cpp_function sets up a chain with the intention of
|
||||
// overwriting (and has already checked internally that it isn't overwriting non-functions).
|
||||
add_object(name_, func, true /* overwrite */);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -567,6 +574,20 @@ public:
|
||||
throw import_error("Module \"" + std::string(name) + "\" not found!");
|
||||
return module(obj, false);
|
||||
}
|
||||
|
||||
// Adds an object to the module using the given name. Throws if an object with the given name
|
||||
// already exists.
|
||||
//
|
||||
// overwrite should almost always be false: attempting to overwrite objects that pybind11 has
|
||||
// established will, in most cases, break things.
|
||||
PYBIND11_NOINLINE void add_object(const char *name, object &obj, bool overwrite = false) {
|
||||
if (!overwrite && hasattr(*this, name))
|
||||
pybind11_fail("Error during initialization: multiple incompatible definitions with name \"" +
|
||||
std::string(name) + "\"");
|
||||
|
||||
obj.inc_ref(); // PyModule_AddObject() steals a reference
|
||||
PyModule_AddObject(ptr(), name, obj.ptr());
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_BEGIN(detail)
|
||||
@@ -614,6 +635,10 @@ protected:
|
||||
object name(PYBIND11_FROM_STRING(rec->name), false);
|
||||
object scope_module;
|
||||
if (rec->scope) {
|
||||
if (hasattr(rec->scope, rec->name))
|
||||
pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec->name) +
|
||||
"\": an object with that name is already defined");
|
||||
|
||||
if (hasattr(rec->scope, "__module__")) {
|
||||
scope_module = rec->scope.attr("__module__");
|
||||
} else if (hasattr(rec->scope, "__name__")) {
|
||||
@@ -1357,8 +1382,7 @@ public:
|
||||
+ std::string(".") + name;
|
||||
char* exception_name = const_cast<char*>(full_name.c_str());
|
||||
m_ptr = PyErr_NewException(exception_name, base, NULL);
|
||||
inc_ref(); // PyModule_AddObject() steals a reference
|
||||
PyModule_AddObject(m.ptr(), name.c_str(), m_ptr);
|
||||
m.add_object(name.c_str(), *this);
|
||||
}
|
||||
|
||||
// Sets the current python exception to this exception object with the given message
|
||||
|
||||
Reference in New Issue
Block a user