Skip to content

Commit a58a435

Browse files
committed
Track ctypes from_buffer sources for overlapping assignments
Assisted-by: Codex:GPT-6
1 parent 48a9033 commit a58a435

3 files changed

Lines changed: 23 additions & 2 deletions

File tree

‎crates/vm/src/stdlib/_ctypes/base.rs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,8 @@ pub struct PyCData {
424424
pub buffer: PyRwLock<Cow<'static, [u8]>>,
425425
/// pointer to base object or None (b_base)
426426
pub base: PyRwLock<Option<PyObjectRef>>,
427+
/// Original from_buffer view, independent of the Python-mutable _objects dict.
428+
buffer_source: Option<PyObjectRef>,
427429
/// byte offset within base's buffer (for field access)
428430
pub base_offset: AtomicCell<usize>,
429431
/// index into base's b_objects list (b_index)
@@ -445,6 +447,7 @@ impl PyCData {
445447
Self {
446448
buffer: PyRwLock::new(Cow::Owned(vec![0u8; stg_info.size])),
447449
base: PyRwLock::new(None),
450+
buffer_source: None,
448451
base_offset: AtomicCell::new(0),
449452
index: AtomicCell::new(0),
450453
objects: PyRwLock::new(None),
@@ -458,6 +461,7 @@ impl PyCData {
458461
Self {
459462
buffer: PyRwLock::new(Cow::Owned(data)),
460463
base: PyRwLock::new(None),
464+
buffer_source: None,
461465
base_offset: AtomicCell::new(0),
462466
index: AtomicCell::new(0),
463467
objects: PyRwLock::new(objects),
@@ -475,6 +479,7 @@ impl PyCData {
475479
Self {
476480
buffer: PyRwLock::new(Cow::Owned(data)),
477481
base: PyRwLock::new(None),
482+
buffer_source: None,
478483
base_offset: AtomicCell::new(0),
479484
index: AtomicCell::new(0),
480485
objects: PyRwLock::new(objects),
@@ -496,6 +501,7 @@ impl PyCData {
496501
Self {
497502
buffer: PyRwLock::new(Cow::Borrowed(slice)),
498503
base: PyRwLock::new(None),
504+
buffer_source: None,
499505
base_offset: AtomicCell::new(0),
500506
index: AtomicCell::new(0),
501507
objects: PyRwLock::new(None),
@@ -525,6 +531,7 @@ impl PyCData {
525531
Self {
526532
buffer: PyRwLock::new(Cow::Borrowed(slice)),
527533
base: PyRwLock::new(Some(base_obj)),
534+
buffer_source: None,
528535
base_offset: AtomicCell::new(0),
529536
index: AtomicCell::new(idx),
530537
objects: PyRwLock::new(None),
@@ -554,12 +561,13 @@ impl PyCData {
554561
// Python stores the reference in a dict with key "-1" (unique_key pattern)
555562
let objects_dict = vm.ctx.new_dict();
556563
objects_dict
557-
.set_item("-1", source, vm)
564+
.set_item("-1", source.clone(), vm)
558565
.expect("Failed to store buffer reference");
559566

560567
Self {
561568
buffer: PyRwLock::new(Cow::Borrowed(slice)),
562569
base: PyRwLock::new(None),
570+
buffer_source: Some(source),
563571
base_offset: AtomicCell::new(0),
564572
index: AtomicCell::new(0),
565573
objects: PyRwLock::new(Some(objects_dict.into())),
@@ -568,6 +576,13 @@ impl PyCData {
568576
}
569577
}
570578

579+
/// Storage owner used to detect aliased buffers without consulting _objects.
580+
pub(crate) fn buffer_base(&self) -> Option<PyObjectRef> {
581+
self.buffer_source
582+
.clone()
583+
.or_else(|| self.base.read().clone())
584+
}
585+
571586
/// Common implementation for from_buffer class method.
572587
/// Validates buffer, creates memoryview, and returns PyCData sharing memory with source.
573588
///

‎crates/vm/src/stdlib/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub(crate) fn ctypes_buffer_base(_obj: &crate::PyObject) -> Option<crate::PyObje
9696
not(any(target_env = "musl", target_env = "sgx"))
9797
))]
9898
if let Some(cdata) = _obj.downcast_ref::<_ctypes::PyCData>() {
99-
return cdata.base.read().clone();
99+
return cdata.buffer_base();
100100
}
101101
None
102102
}

‎extra_tests/snippets/stdlib_ctypes.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,4 +500,10 @@ class OverlappingFields(ctypes.Union):
500500
memoryview(overlap.a).cast("B")[::2] = memoryview(overlap.b).cast("B")[:4]
501501
assert list(overlap.a) == [0, 1, 1, 3, 2, 5, 3, 7]
502502

503+
backing = bytearray(range(8))
504+
view = (ctypes.c_ubyte * 7).from_buffer(backing, 1)
505+
view._objects.clear() # Alias detection must not depend on mutable retention metadata.
506+
memoryview(view).cast("B")[::2] = memoryview(backing)[:4]
507+
assert list(backing) == [0, 0, 2, 1, 4, 2, 6, 3]
508+
503509
print("done")

0 commit comments

Comments
 (0)