Skip to content

Commit 78091ec

Browse files
authored
Merge pull request #298 from passuied/bug/intermittent-mutation-shared-state-issue
Fixing intermittent error experienced with union types
2 parents 727e6cd + fa888f7 commit 78091ec

File tree

2 files changed

+46
-7
lines changed

2 files changed

+46
-7
lines changed

union.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ func buildCodecForTypeDescribedBySliceTwoWayJSON(st map[string]*Codec, enclosing
327327
}
328328

329329
func checkAll(allowedTypes []string, cr *codecInfo, buf []byte) (interface{}, []byte, error) {
330-
for _, name := range cr.allowedTypes {
330+
for _, name := range allowedTypes {
331331
if name == "null" {
332332
// skip null since we know we already got type float64
333333
continue
@@ -344,6 +344,14 @@ func checkAll(allowedTypes []string, cr *codecInfo, buf []byte) (interface{}, []
344344
}
345345
return nil, buf, fmt.Errorf("could not decode any json data in input %v", string(buf))
346346
}
347+
348+
// sortedCopy returns a new slice that is a sorted copy of the provided types.
349+
func sortedCopy(allowedTypes []string) []string {
350+
local := make([]string, len(allowedTypes))
351+
copy(local, allowedTypes)
352+
sort.Strings(local)
353+
return local
354+
}
347355
func nativeAvroFromTextualJSON(cr *codecInfo) func(buf []byte) (interface{}, []byte, error) {
348356
return func(buf []byte) (interface{}, []byte, error) {
349357

@@ -398,18 +406,14 @@ func nativeAvroFromTextualJSON(cr *codecInfo) func(buf []byte) (interface{}, []b
398406
// longNativeFromTextual
399407
// int
400408
// intNativeFromTextual
401-
402-
// sorted so it would be
403-
// double, float, int, long
404-
// that makes the priorities right by chance
405-
sort.Strings(cr.allowedTypes)
409+
allowedTypes = sortedCopy(allowedTypes)
406410

407411
case map[string]interface{}:
408412

409413
// try to decode it as a map
410414
// because a map should fail faster than a record
411415
// if that fails assume record and return it
412-
sort.Strings(cr.allowedTypes)
416+
allowedTypes = sortedCopy(allowedTypes)
413417
}
414418

415419
return checkAll(allowedTypes, cr, buf)

union_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,38 @@ func TestUnionJson(t *testing.T) {
342342
testNativeToTextualJSONPass(t, `{"type":"record","name":"kubeEvents","fields":[{"name":"field1","type":"string","default":""},{"name":"field2","type":"string"}]}`, map[string]interface{}{"field1": "", "field2": "deef"}, []byte(`{"field1":"","field2":"deef"}`))
343343

344344
}
345+
346+
func TestStandardJSONFull_SimpleUnionMutationWouldMislabel(t *testing.T) {
347+
// Minimal repro: union ["null","long"]. If allowedTypes were mutated (sorted)
348+
// after a textual decode, the subsequent binary decode could label index 1 as "null".
349+
codec, err := NewCodecForStandardJSONFull(`["null","long"]`)
350+
if err != nil {
351+
t.Fatal(err)
352+
}
353+
// Trigger textual path that previously sorted cr.allowedTypes.
354+
if _, _, err := codec.NativeFromTextual([]byte("1")); err != nil {
355+
t.Fatalf("textual decode failed: %v", err)
356+
}
357+
// Binary for union index 1 (long) with value 3: 0x02 0x06
358+
datum, rest, err := codec.NativeFromBinary([]byte{0x02, 0x06})
359+
if err != nil {
360+
t.Fatalf("binary decode failed: %v", err)
361+
}
362+
if len(rest) != 0 {
363+
t.Fatalf("unexpected trailing bytes: %d", len(rest))
364+
}
365+
m, ok := datum.(map[string]interface{})
366+
if !ok {
367+
t.Fatalf("expected union map, got %T", datum)
368+
}
369+
if _, bad := m["null"]; bad {
370+
t.Fatalf("mis-labeled union: got key 'null', want 'long': %v", m)
371+
}
372+
v, ok := m["long"]
373+
if !ok {
374+
t.Fatalf("missing 'long' key: %v", m)
375+
}
376+
if v.(int64) != int64(3) {
377+
t.Fatalf("wrong value: got %v want %v", v, int64(3))
378+
}
379+
}

0 commit comments

Comments
 (0)