interp: fix partial store aliasing with previously loaded values

The optimization to skip cloning the destination object on partial
stores when the buffer is already owned by the current memory view
mutated obj.buffer.buf in place. The previous v.buf clone only handled
the case where the store value itself aliased that buffer; it did not
cover the more common case where an earlier partial load had returned
a slice into the same buffer. The in-place store would then corrupt
the previously loaded value, breaking code such as cloudflare/bn256
where the gfP arithmetic loads, computes, and stores within the same
global during package initialization.

Fix this in load instead: when the source object is owned by the
current memory view, copy the loaded slice so the returned value is
independent of the live buffer. The v.buf clone in store is no longer
needed and is removed.

Updates the regression test added in the previous commit to reflect
the corrected behavior.
This commit is contained in:
Jake Bailey
2026-05-11 13:42:53 -07:00
committed by Ron Evans
parent c980e48ad4
commit 3a0d72457b
2 changed files with 8 additions and 8 deletions
+7 -7
View File
@@ -316,10 +316,14 @@ func (mv *memoryView) load(p pointerValue, size uint32) value {
panic("interp: load out of bounds")
}
v := obj.buffer.asRawValue(mv.r)
loadedValue := rawValue{
buf: v.buf[p.offset() : p.offset()+size],
loadedBuf := v.buf[p.offset() : p.offset()+size]
if _, writable := mv.objects[p.index()]; writable {
// This object's buffer is owned by this view, which means a later
// store may mutate it in place (see store below). Copy the loaded
// slice so the returned value is not aliased with the live buffer.
loadedBuf = append([]uint64(nil), loadedBuf...)
}
return loadedValue
return rawValue{buf: loadedBuf}
}
// Store to the value behind the given pointer. This overwrites the value in the
@@ -356,10 +360,6 @@ func (mv *memoryView) store(v value, p pointerValue) bool {
buffer := obj.buffer.asRawValue(mv.r)
obj.buffer = buffer
v := v.asRawValue(mv.r)
if writable {
// A partial load from this object may share the destination buffer.
v.buf = append([]uint64(nil), v.buf...)
}
for i := uint32(0); i < valueLen; i++ {
buffer.buf[p.offset()+i] = v.buf[i]
}
+1 -1
View File
@@ -5,7 +5,7 @@ target triple = "x86_64--linux"
@alias.src = local_unnamed_addr global [4 x i8] c"\05\06\07\08"
@alias.dst = local_unnamed_addr global [2 x i8] c"\09\07"
@reload.buf = local_unnamed_addr global [4 x i8] c"c\02\03\09"
@reload.out = local_unnamed_addr global [2 x i8] c"c\02"
@reload.out = local_unnamed_addr global [2 x i8] c"\01\02"
define void @runtime.initAll() unnamed_addr {
entry: