mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
interp: defer out-of-bounds loads to runtime instead of crashing
When the interpreter encountered a load that was out of bounds of the
object, it panicked with "interp: load out of bounds", crashing the
compiler. This can happen for valid Go programs, for example when
dereferencing the pointer returned by unsafe.SliceData on a
zero-capacity slice, which points to a zero-sized object:
package main
import "unsafe"
var p = unsafe.SliceData([]int{})
var v = *p
func main() {}
Return nil for an out-of-bounds load, the same as for an external
global, so the caller defers the load to runtime instead of crashing.
This matches what regular Go does, where the load reads from the
runtime zero-base at runtime.
Fixes #4214
This commit is contained in:
@@ -20,6 +20,7 @@ func TestInterp(t *testing.T) {
|
||||
"revert",
|
||||
"store",
|
||||
"alloc",
|
||||
"slicedata",
|
||||
} {
|
||||
name := name // make local to this closure
|
||||
t.Run(name, func(t *testing.T) {
|
||||
|
||||
+9
-3
@@ -299,7 +299,8 @@ func (mv *memoryView) put(index uint32, obj object) {
|
||||
}
|
||||
|
||||
// Load the value behind the given pointer. Returns nil if the pointer points to
|
||||
// an external global.
|
||||
// an external global or if the load is out of bounds of the object (in which
|
||||
// case the caller defers the load to runtime).
|
||||
func (mv *memoryView) load(p pointerValue, size uint32) value {
|
||||
if checks && mv.hasExternalStore(p) {
|
||||
panic("interp: load from object with external store")
|
||||
@@ -312,8 +313,13 @@ func (mv *memoryView) load(p pointerValue, size uint32) value {
|
||||
if p.offset() == 0 && size == obj.size {
|
||||
return obj.buffer.clone()
|
||||
}
|
||||
if checks && p.offset()+size > obj.size {
|
||||
panic("interp: load out of bounds")
|
||||
if p.offset()+size > obj.size {
|
||||
// The load is out of bounds of the object. This can happen for valid
|
||||
// Go programs, for example when dereferencing the pointer returned by
|
||||
// unsafe.SliceData on a zero-capacity slice (which points to a
|
||||
// zero-sized object). Return nil so the caller defers this load to
|
||||
// runtime instead of crashing the compiler.
|
||||
return nil
|
||||
}
|
||||
v := obj.buffer.asRawValue(mv.r)
|
||||
loadedBuf := v.buf[p.offset() : p.offset()+size]
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
; Reproduction of https://github.com/tinygo-org/tinygo/issues/4214.
|
||||
; Dereferencing the pointer returned by unsafe.SliceData on a zero-capacity
|
||||
; slice produces a load that is out of bounds of a zero-sized object. The
|
||||
; interp must defer this load to runtime instead of crashing the compiler.
|
||||
|
||||
@main.zeroSized = global {} zeroinitializer
|
||||
@main.v = global i64 0
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @main.init(ptr undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init(ptr %context) unnamed_addr {
|
||||
entry:
|
||||
%val = load i64, ptr @main.zeroSized
|
||||
store i64 %val, ptr @main.v
|
||||
ret void
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.zeroSized = local_unnamed_addr global {} zeroinitializer
|
||||
@main.v = local_unnamed_addr global i64 0
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
%val = load i64, ptr @main.zeroSized, align 8
|
||||
store i64 %val, ptr @main.v, align 8
|
||||
ret void
|
||||
}
|
||||
Reference in New Issue
Block a user