diff --git a/message.go b/message.go index 5bea1d375..ea5602032 100644 --- a/message.go +++ b/message.go @@ -167,6 +167,18 @@ func ParseMessageWithDataDictionary( transportDataDictionary *datadictionary.DataDictionary, appDataDictionary *datadictionary.DataDictionary, ) (err error) { + // Ensure msg sections are initialized before locking their mutexes. + // A zero-value Message has nil rwLock pointers which would panic on Lock. + if msg.Header.rwLock == nil { + msg.Header.Init() + } + if msg.Body.rwLock == nil { + msg.Body.Init() + } + if msg.Trailer.rwLock == nil { + msg.Trailer.Init() + } + // Create msgparser before we go any further. mp := &msgParser{ msg: msg, diff --git a/parse_nil_test.go b/parse_nil_test.go new file mode 100644 index 000000000..61f024343 --- /dev/null +++ b/parse_nil_test.go @@ -0,0 +1,21 @@ +package quickfix + +import ( + "bytes" + "testing" +) + +// TestParseMessageZeroValue ensures ParseMessage works on a zero-value Message +// without panicking. Prior to the fix, passing &msg (zero-value) caused a nil +// pointer dereference on rwLock because FieldMap.rwLock is a *sync.RWMutex that +// is only set by Init(). +func TestParseMessageZeroValue(t *testing.T) { + rawMsg := bytes.NewBufferString("8=FIX.4.2\x019=104\x0135=D\x0134=2\x0149=TW\x0152=20140515-19:49:56.659\x0156=ISLD\x0111=100\x0121=1\x0140=1\x0154=1\x0155=TSLA\x0160=00010101-00:00:00.000\x0110=039\x01") + + var msg Message + err := ParseMessage(&msg, rawMsg) + // A parse error is acceptable; a panic is not. + if err != nil { + t.Logf("ParseMessage returned error (acceptable): %v", err) + } +}