Skip to content

Commit 64fbdbc

Browse files
committed
Optimize calls to UnmarshalCBOR() for RawTag, etc.
Currently, unreleased changes in PR #636 and #645 cause the input data to be checked twice when UnmarshalCBOR() is called internally by Unmarshal() for: - ByteString - RawTag - SimpleValue UnmarshalCBOR() checks input data because it can be called by user apps providing bad data. However, the codec already checks input data before internally calling UnmarshalCBOR() so the 2nd check is redundant. This commit avoids redundant check on the input data by having Unmarshal() call the private unmarshalCBOR() if implemented by ByteString, RawTag, SimpleValue, etc.: - Internally, the codec calls the private unmarshalCBOR() to avoid the redundant check on input data. - Externally, UnmarshalCBOR() is available as a wrapper that checks input data before calling the private unmarshalCBOR(). UnmarshalCBOR() for ByteString, RawTag, and SimpleValue are marked as deprecated and Unmarshal() should be used instead.
1 parent fe81c11 commit 64fbdbc

File tree

8 files changed

+108
-43
lines changed

8 files changed

+108
-43
lines changed

bytestring.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,18 @@ func (bs ByteString) MarshalCBOR() ([]byte, error) {
3838

3939
// UnmarshalCBOR decodes CBOR byte string (major type 2) to ByteString.
4040
// Decoding CBOR null and CBOR undefined sets ByteString to be empty.
41+
//
42+
// Deprecated: No longer used by this codec; kept for compatibility
43+
// with user apps that directly call this function.
4144
func (bs *ByteString) UnmarshalCBOR(data []byte) error {
4245
if bs == nil {
4346
return errors.New("cbor.ByteString: UnmarshalCBOR on nil pointer")
4447
}
4548

46-
// Decoding CBOR null and CBOR undefined to ByteString resets data.
47-
// This behavior is similar to decoding CBOR null and CBOR undefined to []byte.
48-
if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {
49-
*bs = ""
50-
return nil
51-
}
52-
5349
d := decoder{data: data, dm: defaultDecMode}
5450

5551
// Check well-formedness of CBOR data item.
56-
// NOTE: well-formedness check here is redundant when
57-
// Unmarshal() invokes ByteString.UnmarshalCBOR().
58-
// However, ByteString.UnmarshalCBOR() is exported, so
52+
// ByteString.UnmarshalCBOR() is exported, so
5953
// the codec needs to support same behavior for:
6054
// - Unmarshal(data, *ByteString)
6155
// - ByteString.UnmarshalCBOR(data)
@@ -64,8 +58,26 @@ func (bs *ByteString) UnmarshalCBOR(data []byte) error {
6458
return err
6559
}
6660

67-
// Restore decoder offset after well-formedness check.
68-
d.off = 0
61+
return bs.unmarshalCBOR(data)
62+
}
63+
64+
// unmarshalCBOR decodes CBOR byte string (major type 2) to ByteString.
65+
// Decoding CBOR null and CBOR undefined sets ByteString to be empty.
66+
// This function assumes data is well-formed, and does not perform bounds checking.
67+
// This function is called by Unmarshal().
68+
func (bs *ByteString) unmarshalCBOR(data []byte) error {
69+
if bs == nil {
70+
return errors.New("cbor.ByteString: UnmarshalCBOR on nil pointer")
71+
}
72+
73+
// Decoding CBOR null and CBOR undefined to ByteString resets data.
74+
// This behavior is similar to decoding CBOR null and CBOR undefined to []byte.
75+
if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {
76+
*bs = ""
77+
return nil
78+
}
79+
80+
d := decoder{data: data, dm: defaultDecMode}
6981

7082
// Check if CBOR data type is byte string
7183
if typ := d.nextCBORType(); typ != cborTypeByteString {

bytestring_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,16 @@ func TestUnmarshalByteStringOnBadData(t *testing.T) {
195195
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
196196
}
197197
}
198-
// Test Unmarshal(data, *ByteString), which calls ByteString.UnmarshalCBOR() under the hood
198+
// Test Unmarshal(data, *ByteString), which calls ByteString.unmarshalCBOR() under the hood
199199
{
200200
var v ByteString
201201

202202
err := Unmarshal(tc.data, &v)
203203
if err == nil {
204-
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
204+
t.Errorf("Unmarshal(%x) didn't return error", tc.data)
205205
}
206206
if !strings.HasPrefix(err.Error(), tc.errMsg) {
207-
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
207+
t.Errorf("Unmarshal(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
208208
}
209209
}
210210
})

cache.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type specialType int
3232
const (
3333
specialTypeNone specialType = iota
3434
specialTypeUnmarshalerIface
35+
specialTypeUnexportedUnmarshalerIface
3536
specialTypeEmptyIface
3637
specialTypeIface
3738
specialTypeTag
@@ -70,6 +71,8 @@ func newTypeInfo(t reflect.Type) *typeInfo {
7071
tInfo.spclType = specialTypeTag
7172
} else if t == typeTime {
7273
tInfo.spclType = specialTypeTime
74+
} else if reflect.PointerTo(t).Implements(typeUnexportedUnmarshaler) {
75+
tInfo.spclType = specialTypeUnexportedUnmarshalerIface
7376
} else if reflect.PointerTo(t).Implements(typeUnmarshaler) {
7477
tInfo.spclType = specialTypeUnmarshalerIface
7578
}

decode.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ type Unmarshaler interface {
151151
UnmarshalCBOR([]byte) error
152152
}
153153

154+
type unmarshaler interface {
155+
unmarshalCBOR([]byte) error
156+
}
157+
154158
// InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
155159
type InvalidUnmarshalError struct {
156160
s string
@@ -1460,6 +1464,9 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
14601464

14611465
case specialTypeUnmarshalerIface:
14621466
return d.parseToUnmarshaler(v)
1467+
1468+
case specialTypeUnexportedUnmarshalerIface:
1469+
return d.parseToUnexportedUnmarshaler(v)
14631470
}
14641471
}
14651472

@@ -1805,6 +1812,26 @@ func (d *decoder) parseToUnmarshaler(v reflect.Value) error {
18051812
return errors.New("cbor: failed to assert " + v.Type().String() + " as cbor.Unmarshaler")
18061813
}
18071814

1815+
// parseToUnexportedUnmarshaler parses CBOR data to value implementing unmarshaler interface.
1816+
// It assumes data is well-formed, and does not perform bounds checking.
1817+
func (d *decoder) parseToUnexportedUnmarshaler(v reflect.Value) error {
1818+
if d.nextCBORNil() && v.Kind() == reflect.Pointer && v.IsNil() {
1819+
d.skip()
1820+
return nil
1821+
}
1822+
1823+
if v.Kind() != reflect.Pointer && v.CanAddr() {
1824+
v = v.Addr()
1825+
}
1826+
if u, ok := v.Interface().(unmarshaler); ok {
1827+
start := d.off
1828+
d.skip()
1829+
return u.unmarshalCBOR(d.data[start:d.off])
1830+
}
1831+
d.skip()
1832+
return errors.New("cbor: failed to assert " + v.Type().String() + " as cbor.unmarshaler")
1833+
}
1834+
18081835
// parse parses CBOR data and returns value in default Go type.
18091836
// It assumes data is well-formed, and does not perform bounds checking.
18101837
func (d *decoder) parse(skipSelfDescribedTag bool) (any, error) { //nolint:gocyclo
@@ -2969,13 +2996,14 @@ func (d *decoder) nextCBORNil() bool {
29692996
}
29702997

29712998
var (
2972-
typeIntf = reflect.TypeOf([]any(nil)).Elem()
2973-
typeTime = reflect.TypeOf(time.Time{})
2974-
typeBigInt = reflect.TypeOf(big.Int{})
2975-
typeUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
2976-
typeBinaryUnmarshaler = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
2977-
typeString = reflect.TypeOf("")
2978-
typeByteSlice = reflect.TypeOf([]byte(nil))
2999+
typeIntf = reflect.TypeOf([]any(nil)).Elem()
3000+
typeTime = reflect.TypeOf(time.Time{})
3001+
typeBigInt = reflect.TypeOf(big.Int{})
3002+
typeUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
3003+
typeUnexportedUnmarshaler = reflect.TypeOf((*unmarshaler)(nil)).Elem()
3004+
typeBinaryUnmarshaler = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
3005+
typeString = reflect.TypeOf("")
3006+
typeByteSlice = reflect.TypeOf([]byte(nil))
29793007
)
29803008

29813009
func fillNil(_ cborType, v reflect.Value) error {

simplevalue.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ func (sv SimpleValue) MarshalCBOR() ([]byte, error) {
4848
}
4949

5050
// UnmarshalCBOR decodes CBOR simple value (major type 7) to SimpleValue.
51+
//
52+
// Deprecated: No longer used by this codec; kept for compatibility
53+
// with user apps that directly call this function.
5154
func (sv *SimpleValue) UnmarshalCBOR(data []byte) error {
5255
if sv == nil {
5356
return errors.New("cbor.SimpleValue: UnmarshalCBOR on nil pointer")
@@ -56,9 +59,7 @@ func (sv *SimpleValue) UnmarshalCBOR(data []byte) error {
5659
d := decoder{data: data, dm: defaultDecMode}
5760

5861
// Check well-formedness of CBOR data item.
59-
// NOTE: well-formedness check here is redundant when
60-
// Unmarshal() invokes SimpleValue.UnmarshalCBOR().
61-
// However, SimpleValue.UnmarshalCBOR() is exported, so
62+
// SimpleValue.UnmarshalCBOR() is exported, so
6263
// the codec needs to support same behavior for:
6364
// - Unmarshal(data, *SimpleValue)
6465
// - SimpleValue.UnmarshalCBOR(data)
@@ -67,8 +68,18 @@ func (sv *SimpleValue) UnmarshalCBOR(data []byte) error {
6768
return err
6869
}
6970

70-
// Restore decoder offset after well-formedness check.
71-
d.off = 0
71+
return sv.unmarshalCBOR(data)
72+
}
73+
74+
// unmarshalCBOR decodes CBOR simple value (major type 7) to SimpleValue.
75+
// This function assumes data is well-formed, and does not perform bounds checking.
76+
// This function is called by Unmarshal().
77+
func (sv *SimpleValue) unmarshalCBOR(data []byte) error {
78+
if sv == nil {
79+
return errors.New("cbor.SimpleValue: UnmarshalCBOR on nil pointer")
80+
}
81+
82+
d := decoder{data: data, dm: defaultDecMode}
7283

7384
typ, ai, val := d.getHead()
7485

simplevalue_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,16 +156,16 @@ func TestUnmarshalSimpleValueOnBadData(t *testing.T) {
156156
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
157157
}
158158
}
159-
// Test Unmarshal(data, *SimpleValue), which calls SimpleValue.UnmarshalCBOR() under the hood
159+
// Test Unmarshal(data, *SimpleValue), which calls SimpleValue.unmarshalCBOR() under the hood
160160
{
161161
var v SimpleValue
162162

163163
err := Unmarshal(tc.data, &v)
164164
if err == nil {
165-
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
165+
t.Errorf("Unmarshal(%x) didn't return error", tc.data)
166166
}
167167
if !strings.HasPrefix(err.Error(), tc.errMsg) {
168-
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
168+
t.Errorf("Unmarshal(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
169169
}
170170
}
171171
})

tag.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,18 @@ type RawTag struct {
2626
}
2727

2828
// UnmarshalCBOR sets *t with tag number and raw tag content copied from data.
29+
//
30+
// Deprecated: No longer used by this codec; kept for compatibility
31+
// with user apps that directly call this function.
2932
func (t *RawTag) UnmarshalCBOR(data []byte) error {
3033
if t == nil {
3134
return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer")
3235
}
3336

34-
// Decoding CBOR null and undefined to cbor.RawTag is no-op.
35-
if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {
36-
return nil
37-
}
38-
3937
d := decoder{data: data, dm: defaultDecMode}
4038

4139
// Check if data is a well-formed CBOR data item.
42-
// NOTE: well-formedness check here is redundant when
43-
// Unmarshal() invokes RawTag.UnmarshalCBOR().
44-
// However, RawTag.UnmarshalCBOR() is exported, so
40+
// RawTag.UnmarshalCBOR() is exported, so
4541
// the codec needs to support same behavior for:
4642
// - Unmarshal(data, *RawTag)
4743
// - RawTag.UnmarshalCBOR(data)
@@ -50,8 +46,23 @@ func (t *RawTag) UnmarshalCBOR(data []byte) error {
5046
return err
5147
}
5248

53-
// Restore decoder offset after well-formedness check.
54-
d.off = 0
49+
return t.unmarshalCBOR(data)
50+
}
51+
52+
// unmarshalCBOR sets *t with tag number and raw tag content copied from data.
53+
// This function assumes data is well-formed, and does not perform bounds checking.
54+
// This function is called by Unmarshal().
55+
func (t *RawTag) unmarshalCBOR(data []byte) error {
56+
if t == nil {
57+
return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer")
58+
}
59+
60+
// Decoding CBOR null and undefined to cbor.RawTag is no-op.
61+
if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {
62+
return nil
63+
}
64+
65+
d := decoder{data: data, dm: defaultDecMode}
5566

5667
// Unmarshal tag number.
5768
typ, _, num := d.getHead()

tag_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,16 +1646,16 @@ func TestUnmarshalRawTagOnBadData(t *testing.T) {
16461646
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
16471647
}
16481648
}
1649-
// Test Unmarshal(data, *RawTag), which calls RawTag.UnmarshalCBOR() under the hood
1649+
// Test Unmarshal(data, *RawTag), which calls RawTag.unmarshalCBOR() under the hood
16501650
{
16511651
var v RawTag
16521652

16531653
err := Unmarshal(tc.data, &v)
16541654
if err == nil {
1655-
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
1655+
t.Errorf("Unmarshal(%x) didn't return error", tc.data)
16561656
}
16571657
if !strings.HasPrefix(err.Error(), tc.errMsg) {
1658-
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
1658+
t.Errorf("Unmarshal(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
16591659
}
16601660
}
16611661
})

0 commit comments

Comments
 (0)