Skip to content

Commit 69272a8

Browse files
authored
fix: Dereferencing freed memos when verifying provisional memos (#788)
* Add test * Fix `fetch_hot` and `maybe_changed_after`
1 parent c999c71 commit 69272a8

4 files changed

Lines changed: 288 additions & 49 deletions

File tree

src/cycle.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,6 @@ impl CycleHeads {
132132
true
133133
}
134134

135-
pub(crate) fn clear(&mut self) {
136-
self.0.clear();
137-
}
138-
139135
pub(crate) fn update_iteration_count(
140136
&mut self,
141137
cycle_head_index: DatabaseKeyIndex,

src/function/fetch.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,19 +79,24 @@ where
7979
id: Id,
8080
memo_ingredient_index: MemoIngredientIndex,
8181
) -> Option<&'db Memo<C::Output<'db>>> {
82-
let memo_guard = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index);
83-
if let Some(memo) = memo_guard {
84-
let database_key_index = self.database_key_index(id);
85-
if memo.value.is_some()
86-
&& (self.validate_may_be_provisional(db, zalsa, database_key_index, memo)
87-
|| self.validate_same_iteration(db, database_key_index, memo))
88-
&& self.shallow_verify_memo(db, zalsa, database_key_index, memo)
89-
{
90-
// SAFETY: memo is present in memo_map and we have verified that it is
91-
// still valid for the current revision.
92-
return unsafe { Some(self.extend_memo_lifetime(memo)) };
93-
}
82+
let memo = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index)?;
83+
84+
memo.value.as_ref()?;
85+
86+
let database_key_index = self.database_key_index(id);
87+
88+
let shallow_update = self.shallow_verify_memo(zalsa, database_key_index, memo)?;
89+
90+
if self.validate_may_be_provisional(db, zalsa, database_key_index, memo)
91+
|| self.validate_same_iteration(db, database_key_index, memo)
92+
{
93+
self.update_shallow(db, zalsa, database_key_index, memo, shallow_update);
94+
95+
// SAFETY: memo is present in memo_map and we have verified that it is
96+
// still valid for the current revision.
97+
return unsafe { Some(self.extend_memo_lifetime(memo)) };
9498
}
99+
95100
None
96101
}
97102

@@ -120,10 +125,20 @@ where
120125
if let Some(memo) = memo_guard {
121126
if memo.value.is_some()
122127
&& memo.revisions.cycle_heads.contains(&database_key_index)
123-
&& self.shallow_verify_memo(db, zalsa, database_key_index, memo)
124128
{
125-
// SAFETY: memo is present in memo_map.
126-
return unsafe { Some(self.extend_memo_lifetime(memo)) };
129+
if let Some(shallow_update) =
130+
self.shallow_verify_memo(zalsa, database_key_index, memo)
131+
{
132+
self.update_shallow(
133+
db,
134+
zalsa,
135+
database_key_index,
136+
memo,
137+
shallow_update,
138+
);
139+
// SAFETY: memo is present in memo_map.
140+
return unsafe { Some(self.extend_memo_lifetime(memo)) };
141+
}
127142
}
128143
}
129144
// no provisional value; create/insert/return initial provisional value

src/function/maybe_changed_after.rs

Lines changed: 67 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,16 @@ where
6060

6161
// Check if we have a verified version: this is the hot path.
6262
let memo_guard = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index);
63-
if let Some(memo) = memo_guard {
64-
if self.validate_may_be_provisional(db, zalsa, database_key_index, memo)
65-
&& self.shallow_verify_memo(db, zalsa, database_key_index, memo)
66-
{
63+
let Some(memo) = memo_guard else {
64+
// No memo? Assume has changed.
65+
return VerifyResult::Changed;
66+
};
67+
68+
if let Some(shallow_update) = self.shallow_verify_memo(zalsa, database_key_index, memo)
69+
{
70+
if self.validate_provisional(db, zalsa, database_key_index, memo) {
71+
self.update_shallow(db, zalsa, database_key_index, memo, shallow_update);
72+
6773
return if memo.revisions.changed_at > revision {
6874
VerifyResult::Changed
6975
} else {
@@ -73,16 +79,14 @@ where
7379
)
7480
};
7581
}
76-
if let Some(mcs) =
77-
self.maybe_changed_after_cold(zalsa, db, id, revision, memo_ingredient_index)
78-
{
79-
return mcs;
80-
} else {
81-
// We failed to claim, have to retry.
82-
}
82+
}
83+
84+
if let Some(mcs) =
85+
self.maybe_changed_after_cold(zalsa, db, id, revision, memo_ingredient_index)
86+
{
87+
return mcs;
8388
} else {
84-
// No memo? Assume has changed.
85-
return VerifyResult::Changed;
89+
// We failed to claim, have to retry.
8690
}
8791
}
8892
}
@@ -167,7 +171,7 @@ where
167171
Some(VerifyResult::Changed)
168172
}
169173

170-
/// True if the memo's value and `changed_at` time is still valid in this revision.
174+
/// `Some` if the memo's value and `changed_at` time is still valid in this revision.
171175
/// Does only a shallow O(1) check, doesn't walk the dependencies.
172176
///
173177
/// In general, a provisional memo (from cycle iteration) does not verify. Since we don't
@@ -177,11 +181,10 @@ where
177181
#[inline]
178182
pub(super) fn shallow_verify_memo(
179183
&self,
180-
db: &C::DbView,
181184
zalsa: &Zalsa,
182185
database_key_index: DatabaseKeyIndex,
183186
memo: &Memo<C::Output<'_>>,
184-
) -> bool {
187+
) -> Option<ShallowUpdate> {
185188
tracing::debug!(
186189
"{database_key_index:?}: shallow_verify_memo(memo = {memo:#?})",
187190
memo = memo.tracing_debug()
@@ -191,7 +194,7 @@ where
191194

192195
if verified_at == revision_now {
193196
// Already verified.
194-
return true;
197+
return Some(ShallowUpdate::Verified);
195198
}
196199

197200
let last_changed = zalsa.last_changed_revision(memo.revisions.durability);
@@ -204,17 +207,31 @@ where
204207
);
205208
if last_changed <= verified_at {
206209
// No input of the suitable durability has changed since last verified.
210+
Some(ShallowUpdate::HigherDurability(revision_now))
211+
} else {
212+
None
213+
}
214+
}
215+
216+
#[inline]
217+
pub(super) fn update_shallow(
218+
&self,
219+
db: &C::DbView,
220+
zalsa: &Zalsa,
221+
database_key_index: DatabaseKeyIndex,
222+
memo: &Memo<C::Output<'_>>,
223+
update: ShallowUpdate,
224+
) {
225+
if let ShallowUpdate::HigherDurability(revision_now) = update {
207226
memo.mark_as_verified(
208227
db,
209228
revision_now,
210229
database_key_index,
211230
memo.revisions.accumulated_inputs.load(),
212231
);
232+
213233
memo.mark_outputs_as_verified(zalsa, db.as_dyn_database(), database_key_index);
214-
return true;
215234
}
216-
217-
false
218235
}
219236

220237
/// Validates this memo if it is a provisional memo. Returns true for non provisional memos or
@@ -311,10 +328,15 @@ where
311328
old_memo = old_memo.tracing_debug()
312329
);
313330

314-
if self.validate_may_be_provisional(db, zalsa, database_key_index, old_memo)
315-
&& self.shallow_verify_memo(db, zalsa, database_key_index, old_memo)
316-
{
317-
return VerifyResult::unchanged();
331+
let shallow_update = self.shallow_verify_memo(zalsa, database_key_index, old_memo);
332+
let shallow_update_possible = shallow_update.is_some();
333+
334+
if let Some(shallow_update) = shallow_update {
335+
if self.validate_provisional(db, zalsa, database_key_index, old_memo) {
336+
self.update_shallow(db, zalsa, database_key_index, old_memo, shallow_update);
337+
338+
return VerifyResult::unchanged();
339+
}
318340
}
319341

320342
match &old_memo.revisions.origin {
@@ -339,7 +361,9 @@ where
339361
VerifyResult::Changed
340362
}
341363
QueryOrigin::Derived(edges) => {
342-
if old_memo.may_be_provisional() {
364+
let is_provisional = old_memo.may_be_provisional();
365+
// If the value is from the same revision but is still provisional, consider it changed
366+
if shallow_update_possible && is_provisional {
343367
return VerifyResult::Changed;
344368
}
345369

@@ -428,15 +452,18 @@ where
428452
inputs,
429453
);
430454

455+
if is_provisional {
456+
old_memo
457+
.revisions
458+
.verified_final
459+
.store(true, Ordering::Relaxed);
460+
}
461+
431462
if in_heads {
432-
// Iterate our dependency graph again, starting from the top. We clear the
433-
// cycle heads here because we are starting a fresh traversal. (It might be
434-
// logically clearer to create a new HashSet each time, but clearing the
435-
// existing one is more efficient.)
436-
cycle_heads.clear();
437463
continue 'cycle;
438464
}
439465
}
466+
440467
break 'cycle VerifyResult::Unchanged(
441468
InputAccumulatedValues::Empty,
442469
cycle_heads,
@@ -446,3 +473,13 @@ where
446473
}
447474
}
448475
}
476+
477+
#[derive(Copy, Clone, Eq, PartialEq)]
478+
pub(super) enum ShallowUpdate {
479+
/// The memo is from this revision and has already been verified
480+
Verified,
481+
482+
/// The revision for the memo's durability hasn't changed. It can be marked as verified
483+
/// in this revision.
484+
HigherDurability(Revision),
485+
}

0 commit comments

Comments
 (0)