ฉันพบโค้ดบางส่วนใน PyCXX ที่อาจมีข้อผิดพลาด
มันเป็นข้อผิดพลาดจริงหรือไม่ และหากเป็นเช่นนั้น มีวิธีแก้ไขที่ถูกต้องอย่างไร
นี่คือปัญหา:
struct PythonClassInstance
{
PyObject_HEAD
ExtObjBase* m_pycxx_object;
}
:
{
:
table->tp_new = extension_object_new; // PyTypeObject
:
}
:
static PyObject* extension_object_new(
PyTypeObject* subtype, PyObject* args, PyObject* kwds )
{
PythonClassInstance* o = reinterpret_cast<PythonClassInstance *>
( subtype->tp_alloc(subtype,0) );
if( ! o )
return nullptr;
o->m_pycxx_object = nullptr;
PyObject* self = reinterpret_cast<PyObject* >( o );
return self;
}
ตอนนี้ PyObject_HEAD ขยายเป็น "PyObject ob_base;" ดังนั้น PythonClassInstance จึงขยาย PyObject เล็กน้อยเพื่อให้มีตัวชี้พิเศษ (ซึ่งจะชี้ไปที่การเป็นตัวแทนของ PyCXX สำหรับ PyObject นี้)
tp_alloc จัดสรรหน่วยความจำสำหรับการจัดเก็บ PyObject
จากนั้นโค้ดจะพิมพ์ตัวชี้นี้ไปที่ PythonClassInstance โดยอ้างสิทธิ์ในไบต์พิเศษ 4 (หรือ 8?) ที่ไม่ได้เป็นเจ้าของ!
จากนั้นจะตั้งค่าหน่วยความจำพิเศษนี้เป็น 0
สิ่งนี้ดูอันตรายมาก และฉันแปลกใจที่ไม่มีใครสังเกตเห็นข้อผิดพลาดนี้เลย ความเสี่ยงคือวัตถุในอนาคตบางส่วนจะถูกวางในตำแหน่งนี้ (ซึ่งมีไว้เพื่อจัดเก็บ ExtObjBase*)
จะแก้ไขได้อย่างไร?
PythonClassInstance foo{};
PyObject* tmp = subtype->tp_alloc(subtype,0);
// !!! memcpy sizeof(PyObject) bytes starting from location tmp into location (void*)foo
แต่ฉันคิดว่าตอนนี้บางทีฉันอาจจะต้องปล่อย tmp และฉันไม่คิดว่าฉันควรจะเล่นกับหน่วยความจำโดยตรงแบบนี้ ฉันรู้สึกว่ามันอาจจะเป็นอันตรายต่อเครื่องจักรในตัวการจัดการหน่วยความจำ/การรวบรวมขยะของ Python
ตัวเลือกอื่นคือบางทีฉันสามารถชักชวน tp_alloc ให้จัดสรร 4 ไบต์พิเศษ (หรือตอนนี้เป็น 8 เพียงพอสำหรับตัวชี้) โดยข้ามใน 1 แทนที่จะเป็น 0
เอกสารระบุว่าพารามิเตอร์ตัวที่สองนี้คือ "Py_ssize_t nitems" และ:
หาก tp_itemsize ของประเภทไม่เป็นศูนย์ ฟิลด์ ob_size ของวัตถุควรเริ่มต้นเป็น nitems และความยาวของบล็อกหน่วยความจำที่จัดสรรควรเป็น tp_basicsize + nitemstp_itemsize โดยปัดเศษขึ้นเป็นจำนวนทวีคูณของ sizeof(void) ; มิฉะนั้น จะไม่ได้ใช้ nitems และความยาวของบล็อกควรเป็น tp_basicsize
ดูเหมือนว่าฉันควรจะตั้งค่า:
table->tp_itemsize = sizeof(void*);
:
PyObject* tmp = subtype->tp_alloc(subtype,1);
แก้ไข: เพิ่งลองสิ่งนี้และมันก็ทำให้เกิดข้อขัดข้อง
แต่แล้วเอกสารก็กล่าวต่อไปว่า:
อย่าใช้ฟังก์ชันนี้เพื่อเริ่มต้นอินสแตนซ์อื่นๆ แม้แต่เพื่อจัดสรรหน่วยความจำเพิ่มเติม ที่ควรทำโดย tp_new
ตอนนี้ฉันไม่แน่ใจว่ารหัสนี้เป็นของ tp_new หรือ tp_init
ที่เกี่ยวข้อง:
การส่งผ่านอาร์กิวเมนต์ไปยัง tp_new และ tp_init จากประเภทย่อยใน Python C API