Repository navigation
interp: do not write a pointer across an element of a typed global - #5912
Draft
davecheney wants to merge 1 commit into
Draft
davecheney wants to merge 1 commit into
davecheney wants to merge 1 commit into
Conversation
Interp keeps a pointer in memory as a series of equal pointer words. When
interp writes a changed global back to LLVM, rawValue.toLLVMValue examines
only the first word of each scalar element. If a program stores a whole
pointer at a position that is not a pointer or uintptr element of the global
type, the result is not correct:
- If the element starts with bytes, interp writes the pointer bytes as zero.
rawValue.Uint sets r.interpErr, but the writeback does not examine it.
- If the element starts with a pointer word, interp writes the full pointer
into the element, also when the element holds only a part of it.
- If the element is in a byte array, RunFunc stops the build with the error
"ptrtoint integer size does not equal pointer size".
An LLVM initializer cannot hold a part of a pointer in an integer or float
element. LLVM 18 and later do not have the shl, lshr, and, or constant
expressions.
Now interp does the store at runtime when the LLVM global type has no pointer
or uintptr element at the position of a stored pointer. The memcpy and
memmove case does the same check for the destination. The check uses the
element layout and not the alignment, because a pointer can be at an odd
offset on AVR. Interp still writes a pointer at a correct element position in
the initializer. This check does not change heap objects.
Also, rawValue.toLLVMValue now returns errInvalidPtrToIntSize for a scalar
element that cannot hold its pointer words. Thus the writeback does not write
pointer bytes as zero, and it does not set r.interpErr.
This program prints "global: true" with Go. It prints "global: false" with
TinyGo dev and with TinyGo v0.43.0:
package main
import "unsafe"
var x int
var g [2]uintptr
func store(p unsafe.Pointer) {
*(*uintptr)(unsafe.Add(p, unsafe.Sizeof(uintptr(0))/2)) = uintptr(unsafe.Pointer(&x))
}
func init() { store(unsafe.Pointer(&g)) }
//go:noinline
func rt() (v [2]uintptr) { store(unsafe.Pointer(&v)); return }
func main() {
println("global:", g == rt())
}
Tests:
- interp/testdata/writeback.ll stores and copies a pointer into a [2 x i64]
at offset 4, into a double and into a byte array. These stores stay at
runtime. Interp still writes a pointer in an i64 element, in a packed struct
at offset 1 and in a ptr element into the initializer.
- testdata/init_writeback.go does the same stores in init and at runtime, and
compares the results. The output does not change with the target. Without
this change, the build stops with "ptrtoint integer size does not equal
pointer size". If the byte array case is removed, the other cases print
false.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Interp keeps a pointer in memory as a series of equal pointer words. When interp writes a changed global back to LLVM, rawValue.toLLVMValue examines only the first word of each scalar element. If a program stores a whole pointer at a position that is not a pointer or uintptr element of the global type, the result is not correct:
An LLVM initializer cannot hold a part of a pointer in an integer or float element. LLVM 18 and later do not have the shl, lshr, and, or constant expressions.
Now interp does the store at runtime when the LLVM global type has no pointer or uintptr element at the position of a stored pointer. The memcpy and memmove case does the same check for the destination. The check uses the element layout and not the alignment, because a pointer can be at an odd offset on AVR. Interp still writes a pointer at a correct element position in the initializer. This check does not change heap objects.
Also, rawValue.toLLVMValue now returns errInvalidPtrToIntSize for a scalar element that cannot hold its pointer words. Thus the writeback does not write pointer bytes as zero, and it does not set r.interpErr.
This program prints "global: true" with Go. It prints "global: false" with TinyGo dev and with TinyGo v0.43.0:
Tests: