]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: allow conversion from slice to array ptr
authorJosh Bleecher Snyder <josharian@gmail.com>
Sun, 14 Mar 2021 21:31:50 +0000 (14:31 -0700)
committerJosh Bleecher Snyder <josharian@gmail.com>
Wed, 21 Apr 2021 00:54:39 +0000 (00:54 +0000)
Note that this removes an invariant:

v.Type().ConvertibleTo(t) might return true,
yet v.Convert(t) might panic nevertheless.

This is a fairly unavoidable consequence of the decision
to add the first-ever conversion that can panic.

ConvertibleTo describes a relationship between types,
but whether the conversion panics now depends on the value,
not just the type.

If this turns out to be a problem, we can add v.ConvertibleTo(t),
or something similar, to allow callers to avoid the panic.

This is the last of the changes needed to complete the implementation.

Fixes #395

Change-Id: I79b7e4dd87a67a47723e00a65d0b1ac6090371b7
Reviewed-on: https://go-review.googlesource.com/c/go/+/301652
Trust: Josh Bleecher Snyder <josharian@gmail.com>
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
src/reflect/all_test.go
src/reflect/type.go
src/reflect/value.go

index 3269f5ffcea76a501ab74cd6451f56042999f08e..065ff0461157e971e12b266e6d527f53fae21522 100644 (file)
@@ -3822,6 +3822,10 @@ type MyStruct2 struct {
 }
 type MyString string
 type MyBytes []byte
+type MyBytesArrayPtr0 *[0]byte
+type MyBytesArrayPtr *[4]byte
+type MyBytesArray0 [0]byte
+type MyBytesArray [4]byte
 type MyRunes []int32
 type MyFunc func()
 type MyByte byte
@@ -4128,6 +4132,30 @@ var convertTests = []struct {
        {V(MyString("runes♝")), V(MyRunes("runes♝"))},
        {V(MyRunes("runes♕")), V(MyString("runes♕"))},
 
+       // slice to array pointer
+       {V([]byte(nil)), V((*[0]byte)(nil))},
+       {V([]byte{}), V(new([0]byte))},
+       {V([]byte{7}), V(&[1]byte{7})},
+       {V(MyBytes([]byte(nil))), V((*[0]byte)(nil))},
+       {V(MyBytes([]byte{})), V(new([0]byte))},
+       {V(MyBytes([]byte{9})), V(&[1]byte{9})},
+       {V([]byte(nil)), V(MyBytesArrayPtr0(nil))},
+       {V([]byte{}), V(MyBytesArrayPtr0(new([0]byte)))},
+       {V([]byte{1, 2, 3, 4}), V(MyBytesArrayPtr(&[4]byte{1, 2, 3, 4}))},
+       {V(MyBytes([]byte{})), V(MyBytesArrayPtr0(new([0]byte)))},
+       {V(MyBytes([]byte{5, 6, 7, 8})), V(MyBytesArrayPtr(&[4]byte{5, 6, 7, 8}))},
+
+       {V([]byte(nil)), V((*MyBytesArray0)(nil))},
+       {V([]byte{}), V((*MyBytesArray0)(new([0]byte)))},
+       {V([]byte{1, 2, 3, 4}), V(&MyBytesArray{1, 2, 3, 4})},
+       {V(MyBytes([]byte(nil))), V((*MyBytesArray0)(nil))},
+       {V(MyBytes([]byte{})), V((*MyBytesArray0)(new([0]byte)))},
+       {V(MyBytes([]byte{5, 6, 7, 8})), V(&MyBytesArray{5, 6, 7, 8})},
+       {V(new([0]byte)), V(new(MyBytesArray0))},
+       {V(new(MyBytesArray0)), V(new([0]byte))},
+       {V(MyBytesArrayPtr0(nil)), V((*[0]byte)(nil))},
+       {V((*[0]byte)(nil)), V(MyBytesArrayPtr0(nil))},
+
        // named types and equal underlying types
        {V(new(int)), V(new(integer))},
        {V(new(integer)), V(new(int))},
@@ -4288,6 +4316,9 @@ func TestConvert(t *testing.T) {
                if vout2.Type() != tt.out.Type() || !DeepEqual(out2, tt.out.Interface()) {
                        t.Errorf("ValueOf(%T(%[1]v)).Convert(%s) = %T(%[3]v), want %T(%[4]v)", tt.in.Interface(), t2, out2, tt.out.Interface())
                }
+               if got, want := vout2.Kind(), vout2.Type().Kind(); got != want {
+                       t.Errorf("ValueOf(%T(%[1]v)).Convert(%s) has internal kind %v want %v", tt.in.Interface(), t1, got, want)
+               }
 
                // vout3 represents a new value of the out type, set to vout2.  This makes
                // sure the converted value vout2 is really usable as a regular value.
@@ -4332,6 +4363,19 @@ func TestConvert(t *testing.T) {
        }
 }
 
+func TestConvertPanic(t *testing.T) {
+       s := make([]byte, 4)
+       p := new([8]byte)
+       v := ValueOf(s)
+       pt := TypeOf(p)
+       if !v.Type().ConvertibleTo(pt) {
+               t.Errorf("[]byte should be convertible to *[8]byte")
+       }
+       shouldPanic("reflect: cannot convert slice with length 4 to array pointer with length 8", func() {
+               _ = v.Convert(pt)
+       })
+}
+
 var gFloat32 float32
 
 func TestConvertNaNs(t *testing.T) {
index d50559e9339d83425a4ce47ec468fcb802aff4ca..9727bfe467cf7540b82b37b16cf0949b3263e316 100644 (file)
@@ -106,9 +106,11 @@ type Type interface {
        AssignableTo(u Type) bool
 
        // ConvertibleTo reports whether a value of the type is convertible to type u.
+       // Even if ConvertibleTo returns true, the conversion may still panic.
        ConvertibleTo(u Type) bool
 
        // Comparable reports whether values of this type are comparable.
+       // Even if Comparable returns true, the comparison may still panic.
        Comparable() bool
 
        // Methods applicable only to some types, depending on Kind.
index 6f1a3c02d68e4bde47515728931d09373aff56c4..418dff781f4ecc8635cb7acab44146a31758be19 100644 (file)
@@ -6,6 +6,7 @@ package reflect
 
 import (
        "internal/abi"
+       "internal/itoa"
        "internal/unsafeheader"
        "math"
        "runtime"
@@ -2770,7 +2771,7 @@ func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value
 
 // Convert returns the value v converted to type t.
 // If the usual Go conversion rules do not allow conversion
-// of the value v to type t, Convert panics.
+// of the value v to type t, or if converting v to type t panics, Convert panics.
 func (v Value) Convert(t Type) Value {
        if v.flag&flagMethod != 0 {
                v = makeMethodValue("Convert", v)
@@ -2841,6 +2842,11 @@ func convertOp(dst, src *rtype) func(Value, Type) Value {
                                return cvtRunesString
                        }
                }
+               // "x is a slice, T is a pointer-to-array type,
+               // and the slice and array types have identical element types."
+               if dst.Kind() == Ptr && dst.Elem().Kind() == Array && src.Elem() == dst.Elem().Elem() {
+                       return cvtSliceArrayPtr
+               }
 
        case Chan:
                if dst.Kind() == Chan && specialChannelAssignability(dst, src) {
@@ -3034,6 +3040,16 @@ func cvtStringRunes(v Value, t Type) Value {
        return makeRunes(v.flag.ro(), []rune(v.String()), t)
 }
 
+// convertOp: []T -> *[N]T
+func cvtSliceArrayPtr(v Value, t Type) Value {
+       n := t.Elem().Len()
+       h := (*unsafeheader.Slice)(v.ptr)
+       if n > h.Len {
+               panic("reflect: cannot convert slice with length " + itoa.Itoa(h.Len) + " to array pointer with length " + itoa.Itoa(n))
+       }
+       return Value{t.common(), h.Data, v.flag&^(flagIndir|flagAddr|flagKindMask) | flag(Ptr)}
+}
+
 // convertOp: direct copy
 func cvtDirect(v Value, typ Type) Value {
        f := v.flag