Skip to content

Commit 3ac27e9

Browse files
committed
Fix bad-hash with in-place update
1 parent 181902f commit 3ac27e9

File tree

3 files changed

+61
-11
lines changed

3 files changed

+61
-11
lines changed

components/salsa-macro-rules/src/setup_tracked_struct.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,20 +141,30 @@ macro_rules! setup_tracked_struct {
141141
revisions: &mut Self::Revisions,
142142
old_fields: *mut Self::Fields<$db_lt>,
143143
new_fields: Self::Fields<$db_lt>,
144-
) {
144+
) -> bool {
145145
use $zalsa::UpdateFallback as _;
146146
unsafe {
147147
$(
148148
$crate::maybe_backdate!(
149-
$field_option,
150-
$field_ty,
151-
(*old_fields).$field_index,
152-
new_fields.$field_index,
153-
revisions[$field_index],
149+
$tracked_option,
150+
$tracked_ty,
151+
(*old_fields).$absolute_tracked_index,
152+
new_fields.$absolute_tracked_index,
153+
revisions[$absolute_tracked_index],
154154
current_revision,
155155
$zalsa,
156156
);
157-
)*
157+
)*;
158+
159+
// If any untracked field has changed, return `true`, indicating that the tracked struct
160+
// itself should be considered changed.
161+
$(
162+
$zalsa::UpdateDispatch::<$untracked_ty>::maybe_update(
163+
&mut (*old_fields).$absolute_untracked_index,
164+
new_fields.$absolute_untracked_index,
165+
)
166+
|
167+
)* false
158168
}
159169
}
160170
}

src/tracked_struct.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ pub trait Configuration: Sized + 'static {
6262
fn new_revisions(current_revision: Revision) -> Self::Revisions;
6363

6464
/// Update the field data and, if the value has changed,
65-
/// the appropriate entry in the `revisions` array.
65+
/// the appropriate entry in the `revisions` array (tracked fields only).
66+
///
67+
/// Returns `true` if any untracked field was updated and
68+
/// the struct should be considered re-created.
6669
///
6770
/// # Safety
6871
///
@@ -87,7 +90,7 @@ pub trait Configuration: Sized + 'static {
8790
revisions: &mut Self::Revisions,
8891
old_fields: *mut Self::Fields<'db>,
8992
new_fields: Self::Fields<'db>,
90-
);
93+
) -> bool;
9194
}
9295
// ANCHOR_END: Configuration
9396

@@ -535,15 +538,24 @@ where
535538
// its validity invariant and any owned content also continues
536539
// to meet its safety invariant.
537540
unsafe {
538-
C::update_fields(
541+
if C::update_fields(
539542
current_revision,
540543
&mut data.revisions,
541544
self.to_self_ptr(std::ptr::addr_of_mut!(data.fields)),
542545
fields,
543-
);
546+
) {
547+
// Consider this a new tracked-struct (even though it still uses the same id)
548+
// when any non-tracked field got updated.
549+
// This should be rare and only ever happen if there's a hash collision
550+
// which makes Salsa consider two tracked structs to still be the same
551+
// even though the fields are different.
552+
// See `tracked-struct-id-field-bad-hash` for more details.
553+
data.created_at = current_revision;
554+
}
544555
}
545556
if current_deps.durability < data.durability {
546557
data.revisions = C::new_revisions(current_revision);
558+
data.created_at = current_revision;
547559
}
548560
data.durability = current_deps.durability;
549561
let swapped_out = data.updated_at.swap(Some(current_revision));

tests/tracked-struct-id-field-bad-hash.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,31 @@ fn execute() {
5050
input.set_field(&mut db).to(false);
5151
the_fn(&db, input);
5252
}
53+
54+
#[salsa::tracked]
55+
fn create_tracked<'db>(db: &'db dyn Db, input: MyInput) -> MyTracked<'db> {
56+
MyTracked::new(db, BadHash::from(input.field(db)))
57+
}
58+
59+
#[salsa::tracked]
60+
fn with_tracked<'db>(db: &'db dyn Db, tracked: MyTracked<'db>) -> bool {
61+
tracked.field(db).field
62+
}
63+
64+
#[test]
65+
fn dependent_query() {
66+
let mut db = salsa::DatabaseImpl::new();
67+
68+
let input = MyInput::new(&db, true);
69+
let tracked = create_tracked(&db, input);
70+
assert!(with_tracked(&db, tracked));
71+
72+
input.set_field(&mut db).to(false);
73+
// We now re-run the query that creates the tracked struct.
74+
// Salsa will re-use the `MyTracked` struct from the previous revision
75+
// because it thinks it is unchanged because of `BadHash`'s bad hash function.
76+
// That's why Salsa updates the `MyTracked` struct in-place and the struct
77+
// should be considered re-created even though it still has the same id.
78+
let tracked = create_tracked(&db, input);
79+
assert!(!with_tracked(&db, tracked));
80+
}

0 commit comments

Comments
 (0)