all: consistently treat nil message interface as an empty read-only message

To assist users in migrating from github.com/golang/protobuf
to google.golang.org/protobuf, make it such that functiionality like
proto.Marshal doesn't panic on nil interfaces.

Similar to how the new implementation treats a typed nil message
as an empty message, we treat a nil interface as being equivalent
to an "untyped" empty message.

Change-Id: Ic037f386f855b122f732b34d370e524b7c0d76f1
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/228837
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Joe Tsai 2020-04-17 10:55:19 -07:00
parent d8bc21f7e1
commit 8cfc14f022
7 changed files with 41 additions and 2 deletions

View File

@ -116,6 +116,12 @@ func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
return nil, err
}
// Treat nil message interface as an empty message,
// in which case the output in an empty JSON object.
if m == nil {
return []byte("{}"), nil
}
enc := encoder{internalEnc, o}
if err := enc.marshalMessage(m.ProtoReflect()); err != nil {
return nil, err

View File

@ -106,6 +106,12 @@ func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
return nil, err
}
// Treat nil message interface as an empty message,
// in which case there is nothing to output.
if m == nil {
return []byte{}, nil
}
enc := encoder{internalEnc, o}
err = enc.marshalMessage(m.ProtoReflect(), false)
if err != nil {

View File

@ -12,6 +12,12 @@ import (
// CheckInitialized returns an error if any required fields in m are not set.
func CheckInitialized(m Message) error {
// Treat a nil message interface as an "untyped" empty message,
// which we assume to have no required fields.
if m == nil {
return nil
}
return checkInitialized(m.ProtoReflect())
}

View File

@ -74,12 +74,22 @@ type MarshalOptions struct {
// Marshal returns the wire-format encoding of m.
func Marshal(m Message) ([]byte, error) {
// Treat nil message interface as an empty message; nothing to output.
if m == nil {
return nil, nil
}
out, err := MarshalOptions{}.marshal(nil, m.ProtoReflect())
return out.Buf, err
}
// Marshal returns the wire-format encoding of m.
func (o MarshalOptions) Marshal(m Message) ([]byte, error) {
// Treat nil message interface as an empty message; nothing to output.
if m == nil {
return nil, nil
}
out, err := o.marshal(nil, m.ProtoReflect())
return out.Buf, err
}
@ -87,6 +97,11 @@ func (o MarshalOptions) Marshal(m Message) ([]byte, error) {
// MarshalAppend appends the wire-format encoding of m to b,
// returning the result.
func (o MarshalOptions) MarshalAppend(b []byte, m Message) ([]byte, error) {
// Treat nil message interface as an empty message; nothing to append.
if m == nil {
return b, nil
}
out, err := o.marshal(b, m.ProtoReflect())
return out.Buf, err
}

View File

@ -21,6 +21,9 @@ import (
// It is semantically equivalent to unmarshaling the encoded form of src
// into dst with the UnmarshalOptions.Merge option specified.
func Merge(dst, src Message) {
// TODO: Should nil src be treated as semantically equivalent to a
// untyped, read-only, empty message? What about a nil dst?
dstMsg, srcMsg := dst.ProtoReflect(), src.ProtoReflect()
if dstMsg.Descriptor() != srcMsg.Descriptor() {
panic("descriptor mismatch")

View File

@ -27,14 +27,12 @@ func TestNil(t *testing.T) {
}{{
label: "Size",
test: func() { proto.Size(nil) },
panic: true,
}, {
label: "Size",
test: func() { proto.Size(nilMsg) },
}, {
label: "Marshal",
test: func() { proto.Marshal(nil) },
panic: true,
}, {
label: "Marshal",
test: func() { proto.Marshal(nilMsg) },

View File

@ -18,6 +18,11 @@ func Size(m Message) int {
// Size returns the size in bytes of the wire-format encoding of m.
func (o MarshalOptions) Size(m Message) int {
// Treat a nil message interface as an empty message; nothing to output.
if m == nil {
return 0
}
return sizeMessage(m.ProtoReflect())
}