aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCherry Zhang <cherryyz@google.com>2017-08-31 22:02:37 -0400
committerRuss Cox <rsc@golang.org>2017-10-25 20:23:00 +0000
commitff8289f87936bd840daf6e704885f62399d01126 (patch)
tree1b7e5f68fc7e0b499ca2b5db7977c838e354b5c4
parentbd34e74134645b7a7109dbf0361eb1ceb1c3d1ba (diff)
downloadgo-ff8289f87936bd840daf6e704885f62399d01126.tar.gz
go-ff8289f87936bd840daf6e704885f62399d01126.zip
[release-branch.go1.9] reflect: fix pointer past-the-end in Call with zero-sized return value
If a function with nonzero frame but zero-sized return value is Call'd, we may write a past-the-end pointer in preparing the return Values. Fix by return the zero value for zero-sized return value. Fixes #21717. Change-Id: I5351cd86d898467170a888b4c3fc9392f0e7aa3b Reviewed-on: https://go-review.googlesource.com/60811 Run-TryBot: Cherry Zhang <cherryyz@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Austin Clements <austin@google.com> Reviewed-on: https://go-review.googlesource.com/70971 Run-TryBot: Russ Cox <rsc@golang.org> Reviewed-by: Cherry Zhang <cherryyz@google.com>
-rw-r--r--src/reflect/all_test.go25
-rw-r--r--src/reflect/value.go10
2 files changed, 33 insertions, 2 deletions
diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go
index 5a5c91b751..33694fd10e 100644
--- a/src/reflect/all_test.go
+++ b/src/reflect/all_test.go
@@ -19,6 +19,7 @@ import (
"strconv"
"strings"
"sync"
+ "sync/atomic"
"testing"
"time"
"unicode"
@@ -1546,6 +1547,30 @@ func TestCallWithStruct(t *testing.T) {
}
}
+func TestCallReturnsEmpty(t *testing.T) {
+ // Issue 21717: past-the-end pointer write in Call with
+ // nonzero-sized frame and zero-sized return value.
+ runtime.GC()
+ var finalized uint32
+ f := func() (emptyStruct, *int) {
+ i := new(int)
+ runtime.SetFinalizer(i, func(*int) { atomic.StoreUint32(&finalized, 1) })
+ return emptyStruct{}, i
+ }
+ v := ValueOf(f).Call(nil)[0] // out[0] should not alias out[1]'s memory, so the finalizer should run.
+ timeout := time.After(5 * time.Second)
+ for atomic.LoadUint32(&finalized) == 0 {
+ select {
+ case <-timeout:
+ t.Fatal("finalizer did not run")
+ default:
+ }
+ runtime.Gosched()
+ runtime.GC()
+ }
+ runtime.KeepAlive(v)
+}
+
func BenchmarkCall(b *testing.B) {
fv := ValueOf(func(a, b string) {})
b.ReportAllocs()
diff --git a/src/reflect/value.go b/src/reflect/value.go
index 8488e8dec1..2b0ca05c70 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -456,8 +456,14 @@ func (v Value) call(op string, in []Value) []Value {
tv := t.Out(i)
a := uintptr(tv.Align())
off = (off + a - 1) &^ (a - 1)
- fl := flagIndir | flag(tv.Kind())
- ret[i] = Value{tv.common(), unsafe.Pointer(uintptr(args) + off), fl}
+ if tv.Size() != 0 {
+ fl := flagIndir | flag(tv.Kind())
+ ret[i] = Value{tv.common(), unsafe.Pointer(uintptr(args) + off), fl}
+ } else {
+ // For zero-sized return value, args+off may point to the next object.
+ // In this case, return the zero value instead.
+ ret[i] = Zero(tv)
+ }
off += tv.Size()
}
}