From 1a8cf8cfb31f585450cc4dea0e2f490d0894d2f8 Mon Sep 17 00:00:00 2001 From: Jamie Holdstock Date: Tue, 22 Mar 2022 13:46:43 +0000 Subject: [PATCH] Fix assignment to nil map. Ensure that Tickets loaded from the database are returned with empty maps instead of nil maps. To be back-ported to 1.1.0 release. --- database/helpers.go | 28 +++++++++++++++ database/helpers_test.go | 78 ++++++++++++++++++++++++++++++++++++++++ database/ticket.go | 28 +++++---------- 3 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 database/helpers.go create mode 100644 database/helpers_test.go diff --git a/database/helpers.go b/database/helpers.go new file mode 100644 index 00000000..099d46e7 --- /dev/null +++ b/database/helpers.go @@ -0,0 +1,28 @@ +// Copyright (c) 2022 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package database + +import ( + "encoding/json" +) + +func bytesToStringMap(bytes []byte) (map[string]string, error) { + if bytes == nil { + return make(map[string]string), nil + } + + var stringMap map[string]string + err := json.Unmarshal(bytes, &stringMap) + if err != nil { + return nil, err + } + + // stringMap can still be nil here, eg. if bytes == "null". + if stringMap == nil { + stringMap = make(map[string]string) + } + + return stringMap, nil +} diff --git a/database/helpers_test.go b/database/helpers_test.go new file mode 100644 index 00000000..b12f53dd --- /dev/null +++ b/database/helpers_test.go @@ -0,0 +1,78 @@ +// Copyright (c) 2022 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package database + +import ( + "reflect" + "testing" +) + +func TestBytesToStringMap(t *testing.T) { + t.Parallel() + + var tests = []struct { + name string + input []byte + expect map[string]string + expectErr bool + }{ + { + name: "Empty map on nil bytes", + input: nil, + expect: map[string]string{}, + expectErr: false, + }, + { + name: "Empty map on empty json map", + input: []byte("{}"), + expect: map[string]string{}, + expectErr: false, + }, + { + name: "Empty map on null", + input: []byte("null"), + expect: map[string]string{}, + expectErr: false, + }, + { + name: "Correct values with valid json", + input: []byte("{\"key\":\"value\"}"), + expect: map[string]string{"key": "value"}, + expectErr: false, + }, + { + name: "Error on no bytes", + input: []byte(""), + expect: nil, + expectErr: true, + }, + { + name: "Error on invalid json", + input: []byte("invalid json"), + expect: nil, + expectErr: true, + }, + { + name: "Error on non-map json", + input: []byte("[\"not a map\"]"), + expect: nil, + expectErr: true, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + result, err := bytesToStringMap(test.input) + if !reflect.DeepEqual(test.expect, result) { + t.Fatalf("expected %v, got %v", test.expect, result) + } + if test.expectErr != (err != nil) { + t.Fatalf("expected err=%t, got %v", test.expectErr, err) + } + }) + } +} diff --git a/database/ticket.go b/database/ticket.go index 18c7443b..e3df2d83 100644 --- a/database/ticket.go +++ b/database/ticket.go @@ -223,29 +223,19 @@ func getTicketFromBkt(bkt *bolt.Bucket) (Ticket, error) { ticket.Confirmed = bytesToBool(bkt.Get(confirmedK)) var err error - - voteChoicesB := bkt.Get(voteChoicesK) - if voteChoicesB != nil { - err = json.Unmarshal(voteChoicesB, &ticket.VoteChoices) - if err != nil { - return ticket, fmt.Errorf("unmarshal VoteChoices err: %w", err) - } + ticket.VoteChoices, err = bytesToStringMap(bkt.Get(voteChoicesK)) + if err != nil { + return ticket, fmt.Errorf("unmarshal VoteChoices err: %w", err) } - tSpendPolicyB := bkt.Get(tSpendPolicyK) - if tSpendPolicyB != nil { - err = json.Unmarshal(tSpendPolicyB, &ticket.TSpendPolicy) - if err != nil { - return ticket, fmt.Errorf("unmarshal TSpendPolicy err: %w", err) - } + ticket.TSpendPolicy, err = bytesToStringMap(bkt.Get(tSpendPolicyK)) + if err != nil { + return ticket, fmt.Errorf("unmarshal TSpendPolicy err: %w", err) } - treasuryPolicyB := bkt.Get(treasuryPolicyK) - if treasuryPolicyB != nil { - err = json.Unmarshal(treasuryPolicyB, &ticket.TreasuryPolicy) - if err != nil { - return ticket, fmt.Errorf("unmarshal TreasuryPolicy err: %w", err) - } + ticket.TreasuryPolicy, err = bytesToStringMap(bkt.Get(treasuryPolicyK)) + if err != nil { + return ticket, fmt.Errorf("unmarshal TreasuryPolicy err: %w", err) } return ticket, nil