Don't decode CBOR tag data to interface (tinygo only)

tinygo v0.33 doesn't implement Type.AssignableTo(), which is
needed under the hood when decoding registered CBOR tag data
to Go interface.

More details in https://github.com/tinygo-org/tinygo/issues/4277.

This commit returns error of UnmarshalTypeError type when
decoding registered CBOR tag data to Go interface.

This change can be reverted after tinygo implements
Type.AssignalbeTo().
This commit is contained in:
Faye Amacker 2024-09-08 09:47:05 -05:00
parent 950ea6de1b
commit e1bc59bbec
6 changed files with 152 additions and 21 deletions

View File

@ -1386,8 +1386,7 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
registeredType := d.dm.tags.getTypeFromTagNum(tagNums)
if registeredType != nil {
if registeredType.Implements(tInfo.nonPtrType) ||
reflect.PtrTo(registeredType).Implements(tInfo.nonPtrType) {
if implements(registeredType, tInfo.nonPtrType) {
v.Set(reflect.New(registeredType))
v = v.Elem()
tInfo = getTypeInfo(registeredType)

13
decode_not_tinygo.go Normal file
View File

@ -0,0 +1,13 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build !tinygo
package cbor
import "reflect"
func implements(concreteType reflect.Type, interfaceType reflect.Type) bool {
return concreteType.Implements(interfaceType) ||
reflect.PointerTo(concreteType).Implements(interfaceType)
}

View File

@ -47,3 +47,63 @@ func TestUnmarshalDeepNesting(t *testing.T) {
&readback, root)
}
}
func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
var err error
tags := NewTagSet()
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
if err != nil {
t.Error(err)
}
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(D{}), 280)
if err != nil {
t.Error(err)
}
encMode, _ := PreferredUnsortedEncOptions().EncModeWithTags(tags)
decMode, _ := DecOptions{}.DecModeWithTags(tags)
v1 := A1{Field: &C{Field: 5}}
data1, err := encMode.Marshal(v1)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v1, err)
}
v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
data2, err := encMode.Marshal(v2)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v2, err)
}
testCases := []struct {
name string
data []byte
unmarshalToObj interface{}
wantValue interface{}
}{
{
name: "interface type",
data: data1,
unmarshalToObj: &A1{},
wantValue: &v1,
},
{
name: "slice of interface type",
data: data2,
unmarshalToObj: &A2{},
wantValue: &v2,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err = decMode.Unmarshal(tc.data, tc.unmarshalToObj)
if err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", tc.data, err)
}
if !reflect.DeepEqual(tc.unmarshalToObj, tc.wantValue) {
t.Errorf("Unmarshal(0x%x) = %v, want %v", tc.data, tc.unmarshalToObj, tc.wantValue)
}
})
}
}

View File

@ -7698,7 +7698,7 @@ type A2 struct {
Fields []B
}
func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
func TestUnmarshalRegisteredTagToConcreteType(t *testing.T) {
var err error
tags := NewTagSet()
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
@ -7719,36 +7719,18 @@ func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
t.Fatalf("Marshal(%+v) returned error %v", v1, err)
}
v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
data2, err := encMode.Marshal(v2)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v2, err)
}
testCases := []struct {
name string
data []byte
unmarshalToObj interface{}
wantValue interface{}
}{
{
name: "interface type",
data: data1,
unmarshalToObj: &A1{},
wantValue: &v1,
},
{
name: "concrete type",
data: data1,
unmarshalToObj: &A1{Field: &C{}},
wantValue: &v1,
},
{
name: "slice of interface type",
data: data2,
unmarshalToObj: &A2{},
wantValue: &v2,
},
}
for _, tc := range testCases {

18
decode_tinygo.go Normal file
View File

@ -0,0 +1,18 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build tinygo
package cbor
import "reflect"
// tinygo v0.33 doesn't implement Type.AssignableTo() and it panics.
// Type.AssignableTo() is used under the hood for Type.Implements().
//
// More details in https://github.com/tinygo-org/tinygo/issues/4277.
//
// implements() always returns false until tinygo implements Type.AssignableTo().
func implements(concreteType reflect.Type, interfaceType reflect.Type) bool {
return false
}

View File

@ -50,3 +50,62 @@ func TestUnmarshalDeepNesting(t *testing.T) {
&readback, root)
}
}
func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
var err error
tags := NewTagSet()
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
if err != nil {
t.Error(err)
}
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(D{}), 280)
if err != nil {
t.Error(err)
}
encMode, _ := PreferredUnsortedEncOptions().EncModeWithTags(tags)
decMode, _ := DecOptions{}.DecModeWithTags(tags)
v1 := A1{Field: &C{Field: 5}}
data1, err := encMode.Marshal(v1)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v1, err)
}
v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
data2, err := encMode.Marshal(v2)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v2, err)
}
testCases := []struct {
name string
data []byte
unmarshalToObj interface{}
wantValue interface{}
}{
{
name: "interface type",
data: data1,
unmarshalToObj: &A1{},
wantValue: &v1,
},
{
name: "slice of interface type",
data: data2,
unmarshalToObj: &A2{},
wantValue: &v2,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err = decMode.Unmarshal(tc.data, tc.unmarshalToObj)
if err == nil {
t.Errorf("Unmarshal(0x%x) returned no error, expect error", tc.data)
} else if _, ok := err.(*UnmarshalTypeError); !ok {
t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", tc.data, err)
}
})
}
}