feat: protect existing objects during read-only calls and track reads

This commit is contained in:
2026-10-21 14:17:38 +08:00
parent 44a02c9fd6
commit ff49f266a2
11 changed files with 249 additions and 6 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
| [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 |
| [number.rs](number.rs) | Integer/Decimal/Rational/BinaryFloat/Complex 的无损表示和装箱载荷 |
| [builtins.rs](builtins.rs) | String、Dict、Cell、Function、BaseException 载荷与 trace 边 |
| [heap.rs](heap.rs) | 私有 Heap、不可伪造 ObjectRef、稳定 Box、引用事务、迭代释放、循环回收及私有边界测试 |
| [heap.rs](heap.rs) | 私有 Heap、不可伪造 ObjectRef、稳定 Box、引用事务、迭代释放、循环回收、只读变更屏障/读取跟踪及私有边界测试 |
| [store.rs](store.rs) | ObjectStore、空堆创建、对象操作、retain/release 和回收入口 |
| [store_builtins.rs](store_builtins.rs) | 载荷校验、代码/函数工厂、Dict/Cell 操作及键语义 |
| [instruction.rs](instruction.rs) | Opcode、Instruction、wordcode 编解码、MakeFunction/比较参数与跳转读取接口 |
+76 -1
View File
@@ -1,5 +1,6 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::sync::Mutex;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::changes::MAX_PENDING_CHANGES;
@@ -34,6 +35,12 @@ struct Slot {
object: Option<Box<EndorObject>>,
}
#[derive(Debug)]
struct ReadOnly {
protected: BTreeSet<ObjectRef>,
reads: Mutex<BTreeSet<ObjectRef>>,
}
#[derive(Debug)]
pub(crate) struct Heap {
id: u64,
@@ -42,6 +49,7 @@ pub(crate) struct Heap {
live: usize,
changes: BTreeMap<(ObjectRef, ChangeKey), u64>,
changes_overflow: bool,
read_only: Vec<ReadOnly>,
}
impl Heap {
@@ -56,9 +64,61 @@ impl Heap {
live: 0,
changes: BTreeMap::new(),
changes_overflow: false,
read_only: vec![],
})
}
pub(crate) fn begin_read_only(&mut self) -> usize {
let protected = self
.slots
.iter()
.enumerate()
.filter_map(|(index, slot)| {
slot.object.as_ref().map(|_| ObjectRef {
heap_id: self.id,
index,
generation: slot.generation,
})
})
.collect();
self.read_only.push(ReadOnly {
protected,
reads: Mutex::new(BTreeSet::new()),
});
self.read_only.len()
}
pub(crate) fn end_read_only(&mut self, scope: usize) -> Result<Vec<ObjectRef>, VmError> {
if scope == 0 || scope != self.read_only.len() {
return Err(VmError::InvalidArgument("read-only scope nesting mismatch"));
}
Ok(self
.read_only
.pop()
.unwrap()
.reads
.into_inner()
.expect("read tracker poisoned")
.into_iter()
.collect())
}
pub(crate) fn is_read_only(&self) -> bool {
!self.read_only.is_empty()
}
pub(crate) fn record_read(&self, reference: ObjectRef) {
for scope in &self.read_only {
if scope.protected.contains(&reference) {
scope
.reads
.lock()
.expect("read tracker poisoned")
.insert(reference);
}
}
}
pub(crate) fn allocate(
&mut self,
payload: Box<dyn ObjectPayload>,
@@ -268,6 +328,16 @@ impl Heap {
}
pub(crate) fn prepare_change(&self, reference: ObjectRef) -> Result<(), VmError> {
self.get(reference)?;
if self
.read_only
.iter()
.any(|scope| scope.protected.contains(&reference))
{
return Err(VmError::UnsupportedOperation(
"mutation of existing object during read-only call",
));
}
self.get(reference)?
.header
.change_version
@@ -280,6 +350,11 @@ impl Heap {
let object = self.get_mut(reference).expect("validated change target");
object.header.change_version += 1;
let version = object.header.change_version;
// Fresh result construction has no existing observers. Keep versions,
// but do not notify application dependencies about derived allocations.
if self.is_read_only() {
return;
}
if self.changes_overflow {
return;
}
+13
View File
@@ -13,6 +13,18 @@ pub struct ObjectStore {
}
impl ObjectStore {
/// Existing objects are immutable; fresh result graphs may be constructed.
/// Scopes must end in LIFO order. Read handles are borrowed, never GC roots.
pub fn begin_read_only(&mut self) -> usize {
self.heap.begin_read_only()
}
pub fn end_read_only(&mut self, scope: usize) -> Result<Vec<ObjectRef>, VmError> {
self.heap.end_read_only(scope)
}
pub fn is_read_only(&self) -> bool {
self.heap.is_read_only()
}
pub fn downgrade(&self, reference: ObjectRef) -> Result<WeakObjectRef, VmError> {
self.object(reference)?;
Ok(WeakObjectRef(reference))
@@ -107,6 +119,7 @@ impl ObjectStore {
}
pub fn object(&self, reference: ObjectRef) -> Result<&EndorObject, VmError> {
self.heap.record_read(reference);
self.heap.get(reference)
}