Skip to content

Commit c87fbff

Browse files
committed
fix: cache collision for nested collection types
1 parent 92373c2 commit c87fbff

File tree

6 files changed

+225
-3
lines changed

6 files changed

+225
-3
lines changed

internal/reflect/decoder_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,16 @@ func TestDecode(t *testing.T) {
131131
p0.M1 = map[int32]string{vInt32 - 2: "hello", 1: "2"}
132132
p0.M2 = map[int32]*Msg{vInt32 - 3: nil, 1: {Type: 2}}
133133
p0.M3 = map[string]*Msg{"hello": {Type: vInt32 - 4}}
134+
p0.ML = map[int32][]int32{100: {1, 2, 3}}
135+
p0.MS = map[int32][]int32{101: {4, 5, 6}}
134136
},
135137
test: func(t *testing.T, p1 *TestTypes) {
136138
assert.Equal(t, map[int32]int32{vInt32: vInt32 - 1, 1: 2}, p1.M0)
137139
assert.Equal(t, map[int32]string{vInt32 - 2: "hello", 1: "2"}, p1.M1)
138140
assert.Equal(t, map[int32]*Msg{vInt32 - 3: {}, 1: {Type: 2}}, p1.M2)
139141
assert.Equal(t, map[string]*Msg{"hello": {Type: vInt32 - 4}}, p1.M3)
142+
assert.Equal(t, map[int32][]int32{100: {1, 2, 3}}, p1.ML)
143+
assert.Equal(t, map[int32][]int32{101: {4, 5, 6}}, p1.MS)
140144
},
141145
},
142146
{

internal/reflect/desc_test.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* Copyright 2024 CloudWeGo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package reflect
18+
19+
import (
20+
"reflect"
21+
"testing"
22+
23+
"github.com/cloudwego/frugal/internal/defs"
24+
"github.com/stretchr/testify/require"
25+
)
26+
27+
func TestStructDescFieldProperties(t *testing.T) {
28+
rv := reflect.ValueOf(&TestTypes{})
29+
desc, err := getOrcreateStructDesc(rv)
30+
require.NoError(t, err)
31+
32+
// Field 1: FBool - bool, required
33+
f1 := desc.GetField(1)
34+
require.NotNil(t, f1)
35+
require.Equal(t, tBOOL, f1.Type.T)
36+
require.Equal(t, defs.Required, f1.Spec)
37+
require.Equal(t, 1, f1.Type.FixedSize)
38+
39+
// Field 2: FByte - int8, default
40+
f2 := desc.GetField(2)
41+
require.NotNil(t, f2)
42+
require.Equal(t, tBYTE, f2.Type.T)
43+
require.Equal(t, defs.Default, f2.Spec)
44+
require.Equal(t, 1, f2.Type.FixedSize)
45+
46+
// Field 3: I8 - int8, default
47+
f3 := desc.GetField(3)
48+
require.NotNil(t, f3)
49+
require.Equal(t, tBYTE, f3.Type.T)
50+
require.Equal(t, defs.Default, f3.Spec)
51+
52+
// Field 4: I16 - int16, default
53+
f4 := desc.GetField(4)
54+
require.NotNil(t, f4)
55+
require.Equal(t, tI16, f4.Type.T)
56+
require.Equal(t, defs.Default, f4.Spec)
57+
require.Equal(t, 2, f4.Type.FixedSize)
58+
59+
// Field 5: I32 - int32, default
60+
f5 := desc.GetField(5)
61+
require.NotNil(t, f5)
62+
require.Equal(t, tI32, f5.Type.T)
63+
require.Equal(t, defs.Default, f5.Spec)
64+
require.Equal(t, 4, f5.Type.FixedSize)
65+
66+
// Field 6: I64 - int64, default
67+
f6 := desc.GetField(6)
68+
require.NotNil(t, f6)
69+
require.Equal(t, tI64, f6.Type.T)
70+
require.Equal(t, defs.Default, f6.Spec)
71+
require.Equal(t, 8, f6.Type.FixedSize)
72+
73+
// Field 7: Double - float64, default
74+
f7 := desc.GetField(7)
75+
require.NotNil(t, f7)
76+
require.Equal(t, tDOUBLE, f7.Type.T)
77+
require.Equal(t, defs.Default, f7.Spec)
78+
require.Equal(t, 8, f7.Type.FixedSize)
79+
80+
// Field 8: String_ - string, default
81+
f8 := desc.GetField(8)
82+
require.NotNil(t, f8)
83+
require.Equal(t, tSTRING, f8.Type.T)
84+
require.Equal(t, defs.Default, f8.Spec)
85+
require.Equal(t, 0, f8.Type.FixedSize)
86+
87+
// Field 9: Binary - []byte, default (represented as tSTRING in wire format)
88+
f9 := desc.GetField(9)
89+
require.NotNil(t, f9)
90+
require.Equal(t, tSTRING, f9.Type.T)
91+
require.Equal(t, defs.Default, f9.Spec)
92+
require.Equal(t, 0, f9.Type.FixedSize)
93+
94+
// Field 10: Enum - Numberz, default
95+
f10 := desc.GetField(10)
96+
require.NotNil(t, f10)
97+
require.Equal(t, tENUM, f10.Type.T)
98+
require.Equal(t, defs.Default, f10.Spec)
99+
100+
// Field 11: UID - UserID (int64), default
101+
f11 := desc.GetField(11)
102+
require.NotNil(t, f11)
103+
require.Equal(t, tI64, f11.Type.T)
104+
require.Equal(t, defs.Default, f11.Spec)
105+
106+
// Field 12: S - *Msg, default
107+
f12 := desc.GetField(12)
108+
require.NotNil(t, f12)
109+
require.Equal(t, tSTRUCT, f12.Type.T)
110+
require.Equal(t, defs.Default, f12.Spec)
111+
require.True(t, f12.Type.IsPointer)
112+
require.NotNil(t, f12.Type.Sd)
113+
114+
// Field 20: M0 - map[int32]int32, required
115+
f20 := desc.GetField(20)
116+
require.NotNil(t, f20)
117+
require.Equal(t, tMAP, f20.Type.T)
118+
require.Equal(t, defs.Required, f20.Spec)
119+
require.NotNil(t, f20.Type.K)
120+
require.Equal(t, tI32, f20.Type.K.T)
121+
require.NotNil(t, f20.Type.V)
122+
require.Equal(t, tI32, f20.Type.V.T)
123+
124+
// Field 21: M1 - map[int32]string, default
125+
f21 := desc.GetField(21)
126+
require.NotNil(t, f21)
127+
require.Equal(t, tMAP, f21.Type.T)
128+
require.Equal(t, defs.Default, f21.Spec)
129+
require.Equal(t, tI32, f21.Type.K.T)
130+
require.Equal(t, tSTRING, f21.Type.V.T)
131+
132+
// Field 22: M2 - map[int32]*Msg, default
133+
f22 := desc.GetField(22)
134+
require.NotNil(t, f22)
135+
require.Equal(t, tMAP, f22.Type.T)
136+
require.Equal(t, tI32, f22.Type.K.T)
137+
require.Equal(t, tSTRUCT, f22.Type.V.T)
138+
require.True(t, f22.Type.V.IsPointer)
139+
140+
// Field 23: M3 - map[string]*Msg, default
141+
f23 := desc.GetField(23)
142+
require.NotNil(t, f23)
143+
require.Equal(t, tMAP, f23.Type.T)
144+
require.Equal(t, tSTRING, f23.Type.K.T)
145+
require.Equal(t, tSTRUCT, f23.Type.V.T)
146+
147+
// Field 30: L0 - []int32, required
148+
f30 := desc.GetField(30)
149+
require.NotNil(t, f30)
150+
require.Equal(t, tLIST, f30.Type.T)
151+
require.Equal(t, defs.Required, f30.Spec)
152+
require.Equal(t, tI32, f30.Type.V.T)
153+
154+
// Field 31: L1 - []string, default
155+
f31 := desc.GetField(31)
156+
require.NotNil(t, f31)
157+
require.Equal(t, tLIST, f31.Type.T)
158+
require.Equal(t, defs.Default, f31.Spec)
159+
require.Equal(t, tSTRING, f31.Type.V.T)
160+
161+
// Field 32: L2 - []*Msg, default
162+
f32 := desc.GetField(32)
163+
require.NotNil(t, f32)
164+
require.Equal(t, tLIST, f32.Type.T)
165+
require.Equal(t, tSTRUCT, f32.Type.V.T)
166+
require.True(t, f32.Type.V.IsPointer)
167+
168+
// Field 40: S0 - []int32, required (set)
169+
f40 := desc.GetField(40)
170+
require.NotNil(t, f40)
171+
require.Equal(t, tSET, f40.Type.T)
172+
require.Equal(t, defs.Required, f40.Spec)
173+
require.Equal(t, tI32, f40.Type.V.T)
174+
175+
// Field 41: S1 - []string, default (set)
176+
f41 := desc.GetField(41)
177+
require.NotNil(t, f41)
178+
require.Equal(t, tSET, f41.Type.T)
179+
require.Equal(t, defs.Default, f41.Spec)
180+
require.Equal(t, tSTRING, f41.Type.V.T)
181+
182+
// Field 50: LM - []map[int32]int32, default (list of maps)
183+
f50 := desc.GetField(50)
184+
require.NotNil(t, f50)
185+
require.Equal(t, tLIST, f50.Type.T)
186+
require.Equal(t, defs.Default, f50.Spec)
187+
require.Equal(t, tMAP, f50.Type.V.T)
188+
// Check nested map types
189+
require.Equal(t, tI32, f50.Type.V.K.T)
190+
require.Equal(t, tI32, f50.Type.V.V.T)
191+
192+
// Field 60: ML - map[int32][]int32, default (map with list values)
193+
f60 := desc.GetField(60)
194+
require.NotNil(t, f60)
195+
require.Equal(t, tMAP, f60.Type.T)
196+
require.Equal(t, defs.Default, f60.Spec)
197+
require.Equal(t, tI32, f60.Type.K.T)
198+
require.Equal(t, tLIST, f60.Type.V.T)
199+
// Check nested list element type
200+
require.Equal(t, tI32, f60.Type.V.V.T)
201+
202+
// Field 61: MS - map[int32][]int32, default (map with set values)
203+
f61 := desc.GetField(61)
204+
require.NotNil(t, f61)
205+
require.Equal(t, tMAP, f61.Type.T)
206+
require.Equal(t, defs.Default, f61.Spec)
207+
require.Equal(t, tI32, f61.Type.K.T)
208+
require.Equal(t, tSET, f61.Type.V.T)
209+
// Check nested set element type
210+
require.Equal(t, tI32, f61.Type.V.V.T)
211+
}

internal/reflect/encoder_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
const encodedMsgSize = 15
2828

2929
// NewTestTypes().BLength()
30-
const encodedTestTypesSize = 176
30+
const encodedTestTypesSize = 185
3131

3232
// NewTestTypesOptional().BLength()
3333
const encodedTestTypesOptionalSize = 1

internal/reflect/testdata.thrift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ struct TestTypes {
3737
41: set<string> S1;
3838
50: list<map<i32, i32>> LM;
3939
60: map<i32, list<i32>> ML;
40+
61: map<i32, set<i32>> MS;
4041
}
4142

4243
struct TestTypesOptional {

internal/reflect/testdata_test.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/reflect/ttype.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,17 @@ func (t *tType) Equal(p0, p1 unsafe.Pointer) bool {
143143
}
144144

145145
type ttypesK struct {
146-
T defs.Tag
146+
T string
147147
S reflect.Type
148148
}
149149

150150
var ttypes = map[ttypesK]*tType{} // cache for less in-use objects
151151

152152
func newTType(x *defs.Type) *tType {
153-
k := ttypesK{T: x.T, S: x.S}
153+
// Cache key must distinguish between types that have identical Go reflect.Type but different Thrift semantics.
154+
// For example, map<i32, set<i32>> and map<i32, list<i32>> both map to map[int32][]int32 in Go,
155+
// but represent different wire formats (SET vs LIST). Using x.String() ensures unique cache entries.
156+
k := ttypesK{T: x.String(), S: x.S}
154157
if t := ttypes[k]; t != nil {
155158
return t
156159
}

0 commit comments

Comments
 (0)