feat: protect existing objects during read-only calls and track reads
This commit is contained in:
@@ -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/比较参数与跳转读取接口 |
|
||||
|
||||
@@ -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,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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user