Snap for 8414339 from 778fd2fe348de8c0dad75597fcaa7aced089ed2d to tm-qpr1-release

Change-Id: Ic5c08fef98c741064794809903887783315a903f
diff --git a/METADATA b/METADATA
index 9522373..ad0c055 100644
--- a/METADATA
+++ b/METADATA
@@ -8,11 +8,11 @@
     type: GIT
     value: "https://github.com/protocolbuffers/protobuf-go"
   }
-  version: "v1.27.1"
+  version: "v1.28.0"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2021
-    month: 8
-    day: 27
+    year: 2022
+    month: 3
+    day: 29
   }
 }
diff --git a/encoding/protowire/wire.go b/encoding/protowire/wire.go
index a427f8b..9c61112 100644
--- a/encoding/protowire/wire.go
+++ b/encoding/protowire/wire.go
@@ -21,10 +21,11 @@
 type Number int32
 
 const (
-	MinValidNumber      Number = 1
-	FirstReservedNumber Number = 19000
-	LastReservedNumber  Number = 19999
-	MaxValidNumber      Number = 1<<29 - 1
+	MinValidNumber        Number = 1
+	FirstReservedNumber   Number = 19000
+	LastReservedNumber    Number = 19999
+	MaxValidNumber        Number = 1<<29 - 1
+	DefaultRecursionLimit        = 10000
 )
 
 // IsValid reports whether the field number is semantically valid.
@@ -55,6 +56,7 @@
 	errCodeOverflow
 	errCodeReserved
 	errCodeEndGroup
+	errCodeRecursionDepth
 )
 
 var (
@@ -112,6 +114,10 @@
 // When parsing a group, the length includes the end group marker and
 // the end group is verified to match the starting field number.
 func ConsumeFieldValue(num Number, typ Type, b []byte) (n int) {
+	return consumeFieldValueD(num, typ, b, DefaultRecursionLimit)
+}
+
+func consumeFieldValueD(num Number, typ Type, b []byte, depth int) (n int) {
 	switch typ {
 	case VarintType:
 		_, n = ConsumeVarint(b)
@@ -126,6 +132,9 @@
 		_, n = ConsumeBytes(b)
 		return n
 	case StartGroupType:
+		if depth < 0 {
+			return errCodeRecursionDepth
+		}
 		n0 := len(b)
 		for {
 			num2, typ2, n := ConsumeTag(b)
@@ -140,7 +149,7 @@
 				return n0 - len(b)
 			}
 
-			n = ConsumeFieldValue(num2, typ2, b)
+			n = consumeFieldValueD(num2, typ2, b, depth-1)
 			if n < 0 {
 				return n // forward error code
 			}
diff --git a/go.mod b/go.mod
index 3dda8e3..8858601 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module google.golang.org/protobuf
 
-go 1.9
+go 1.11
 
 require (
 	github.com/golang/protobuf v1.5.0
diff --git a/integration_test.go b/integration_test.go
index dfcfd10..8a4f7af 100644
--- a/integration_test.go
+++ b/integration_test.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build ignore
 // +build ignore
 
 package main
@@ -37,7 +38,7 @@
 	protobufVersion = "3.15.3"
 	protobufSHA256  = "" // ignored if protobufVersion is a git hash
 
-	golangVersions = []string{"1.9.7", "1.10.8", "1.11.13", "1.12.17", "1.13.15", "1.14.15", "1.15.9", "1.16.1"}
+	golangVersions = []string{"1.11.13", "1.12.17", "1.13.15", "1.14.15", "1.15.15", "1.16.10", "1.17.3"}
 	golangLatest   = golangVersions[len(golangVersions)-1]
 
 	staticcheckVersion = "2020.1.4"
diff --git a/internal/encoding/text/decode.go b/internal/encoding/text/decode.go
index eb10ea1..3780377 100644
--- a/internal/encoding/text/decode.go
+++ b/internal/encoding/text/decode.go
@@ -381,7 +381,7 @@
 	case '[':
 		return ListOpen, ']'
 	}
-	panic(fmt.Sprintf("Decoder: openStack contains invalid byte %s", string(openCh)))
+	panic(fmt.Sprintf("Decoder: openStack contains invalid byte %c", openCh))
 }
 
 func (d *Decoder) pushOpenStack(ch byte) {
diff --git a/internal/errors/is_go112.go b/internal/errors/is_go112.go
index f90e909..fbcd349 100644
--- a/internal/errors/is_go112.go
+++ b/internal/errors/is_go112.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !go1.13
 // +build !go1.13
 
 package errors
diff --git a/internal/errors/is_go113.go b/internal/errors/is_go113.go
index dc05f41..5e72f1c 100644
--- a/internal/errors/is_go113.go
+++ b/internal/errors/is_go113.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.13
 // +build go1.13
 
 package errors
diff --git a/internal/flags/proto_legacy_disable.go b/internal/flags/proto_legacy_disable.go
index a72995f..bda8e8c 100644
--- a/internal/flags/proto_legacy_disable.go
+++ b/internal/flags/proto_legacy_disable.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !protolegacy
 // +build !protolegacy
 
 package flags
diff --git a/internal/flags/proto_legacy_enable.go b/internal/flags/proto_legacy_enable.go
index 772e2f0..6d8d9bd 100644
--- a/internal/flags/proto_legacy_enable.go
+++ b/internal/flags/proto_legacy_enable.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build protolegacy
 // +build protolegacy
 
 package flags
diff --git a/internal/fuzz/wirefuzz/fuzz.go b/internal/fuzz/wirefuzz/fuzz.go
index f7a9b74..fd27cca 100644
--- a/internal/fuzz/wirefuzz/fuzz.go
+++ b/internal/fuzz/wirefuzz/fuzz.go
@@ -41,7 +41,7 @@
 	// Unmarshal, Validate, and CheckInitialized should agree about initialization.
 	checkInit := proto.CheckInitialized(m1) == nil
 	methods := m1.ProtoReflect().ProtoMethods()
-	in := piface.UnmarshalInput{Message: mt.New(), Resolver: protoregistry.GlobalTypes}
+	in := piface.UnmarshalInput{Message: mt.New(), Resolver: protoregistry.GlobalTypes, Depth: 10000}
 	if checkInit {
 		// If the message initialized, the both Unmarshal and Validate should
 		// report it as such. False negatives are tolerated, but have a
diff --git a/internal/impl/codec_map_go111.go b/internal/impl/codec_map_go111.go
index 2706bb6..4b15493 100644
--- a/internal/impl/codec_map_go111.go
+++ b/internal/impl/codec_map_go111.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !go1.12
 // +build !go1.12
 
 package impl
diff --git a/internal/impl/codec_map_go112.go b/internal/impl/codec_map_go112.go
index 1533ef6..0b31b66 100644
--- a/internal/impl/codec_map_go112.go
+++ b/internal/impl/codec_map_go112.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.12
 // +build go1.12
 
 package impl
diff --git a/internal/impl/codec_reflect.go b/internal/impl/codec_reflect.go
index 90705e3..145c577 100644
--- a/internal/impl/codec_reflect.go
+++ b/internal/impl/codec_reflect.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build purego || appengine
 // +build purego appengine
 
 package impl
diff --git a/internal/impl/codec_unsafe.go b/internal/impl/codec_unsafe.go
index e118af1..757642e 100644
--- a/internal/impl/codec_unsafe.go
+++ b/internal/impl/codec_unsafe.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !purego && !appengine
 // +build !purego,!appengine
 
 package impl
diff --git a/internal/impl/decode.go b/internal/impl/decode.go
index 949dc49..c65b032 100644
--- a/internal/impl/decode.go
+++ b/internal/impl/decode.go
@@ -18,6 +18,7 @@
 )
 
 var errDecode = errors.New("cannot parse invalid wire-format data")
+var errRecursionDepth = errors.New("exceeded maximum recursion depth")
 
 type unmarshalOptions struct {
 	flags    protoiface.UnmarshalInputFlags
@@ -25,6 +26,7 @@
 		FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error)
 		FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error)
 	}
+	depth int
 }
 
 func (o unmarshalOptions) Options() proto.UnmarshalOptions {
@@ -44,6 +46,7 @@
 
 var lazyUnmarshalOptions = unmarshalOptions{
 	resolver: preg.GlobalTypes,
+	depth:    protowire.DefaultRecursionLimit,
 }
 
 type unmarshalOutput struct {
@@ -62,6 +65,7 @@
 	out, err := mi.unmarshalPointer(in.Buf, p, 0, unmarshalOptions{
 		flags:    in.Flags,
 		resolver: in.Resolver,
+		depth:    in.Depth,
 	})
 	var flags piface.UnmarshalOutputFlags
 	if out.initialized {
@@ -82,6 +86,10 @@
 
 func (mi *MessageInfo) unmarshalPointer(b []byte, p pointer, groupTag protowire.Number, opts unmarshalOptions) (out unmarshalOutput, err error) {
 	mi.init()
+	opts.depth--
+	if opts.depth < 0 {
+		return out, errRecursionDepth
+	}
 	if flags.ProtoLegacy && mi.isMessageSet {
 		return unmarshalMessageSet(mi, b, p, opts)
 	}
diff --git a/internal/impl/pointer_reflect.go b/internal/impl/pointer_reflect.go
index 9e3ed82..4c491bd 100644
--- a/internal/impl/pointer_reflect.go
+++ b/internal/impl/pointer_reflect.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build purego || appengine
 // +build purego appengine
 
 package impl
diff --git a/internal/impl/pointer_unsafe.go b/internal/impl/pointer_unsafe.go
index 9ecf23a..ee0e057 100644
--- a/internal/impl/pointer_unsafe.go
+++ b/internal/impl/pointer_unsafe.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !purego && !appengine
 // +build !purego,!appengine
 
 package impl
diff --git a/internal/strs/strings_pure.go b/internal/strs/strings_pure.go
index 85e074c..a1f6f33 100644
--- a/internal/strs/strings_pure.go
+++ b/internal/strs/strings_pure.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build purego || appengine
 // +build purego appengine
 
 package strs
diff --git a/internal/strs/strings_unsafe.go b/internal/strs/strings_unsafe.go
index 2160c70..56a8a4e 100644
--- a/internal/strs/strings_unsafe.go
+++ b/internal/strs/strings_unsafe.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !purego && !appengine
 // +build !purego,!appengine
 
 package strs
diff --git a/internal/testprotos/nullable/methods_test.go b/internal/testprotos/nullable/methods_test.go
index 8e22ab2..c694810 100644
--- a/internal/testprotos/nullable/methods_test.go
+++ b/internal/testprotos/nullable/methods_test.go
@@ -6,6 +6,7 @@
 // only test compatibility with the Marshal/Unmarshal functionality with
 // pure protobuf reflection since there is no support for nullable fields
 // in the table-driven implementation.
+//go:build protoreflect
 // +build protoreflect
 
 package nullable
diff --git a/internal/version/version.go b/internal/version/version.go
index 14e774f..3d40d52 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -52,8 +52,8 @@
 //	10. Send out the CL for review and submit it.
 const (
 	Major      = 1
-	Minor      = 27
-	Patch      = 1
+	Minor      = 28
+	Patch      = 0
 	PreRelease = ""
 )
 
diff --git a/internal/weakdeps/weakdeps.go b/internal/weakdeps/weakdeps.go
index 59b3475..e8261fb 100644
--- a/internal/weakdeps/weakdeps.go
+++ b/internal/weakdeps/weakdeps.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build weak_dependency
 // +build weak_dependency
 
 package weakdeps
diff --git a/proto/decode.go b/proto/decode.go
index 49f9b8c..11bf717 100644
--- a/proto/decode.go
+++ b/proto/decode.go
@@ -42,18 +42,25 @@
 		FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error)
 		FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error)
 	}
+
+	// RecursionLimit limits how deeply messages may be nested.
+	// If zero, a default limit is applied.
+	RecursionLimit int
 }
 
 // Unmarshal parses the wire-format message in b and places the result in m.
 // The provided message must be mutable (e.g., a non-nil pointer to a message).
 func Unmarshal(b []byte, m Message) error {
-	_, err := UnmarshalOptions{}.unmarshal(b, m.ProtoReflect())
+	_, err := UnmarshalOptions{RecursionLimit: protowire.DefaultRecursionLimit}.unmarshal(b, m.ProtoReflect())
 	return err
 }
 
 // Unmarshal parses the wire-format message in b and places the result in m.
 // The provided message must be mutable (e.g., a non-nil pointer to a message).
 func (o UnmarshalOptions) Unmarshal(b []byte, m Message) error {
+	if o.RecursionLimit == 0 {
+		o.RecursionLimit = protowire.DefaultRecursionLimit
+	}
 	_, err := o.unmarshal(b, m.ProtoReflect())
 	return err
 }
@@ -63,6 +70,9 @@
 // This method permits fine-grained control over the unmarshaler.
 // Most users should use Unmarshal instead.
 func (o UnmarshalOptions) UnmarshalState(in protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) {
+	if o.RecursionLimit == 0 {
+		o.RecursionLimit = protowire.DefaultRecursionLimit
+	}
 	return o.unmarshal(in.Buf, in.Message)
 }
 
@@ -86,12 +96,17 @@
 			Message:  m,
 			Buf:      b,
 			Resolver: o.Resolver,
+			Depth:    o.RecursionLimit,
 		}
 		if o.DiscardUnknown {
 			in.Flags |= protoiface.UnmarshalDiscardUnknown
 		}
 		out, err = methods.Unmarshal(in)
 	} else {
+		o.RecursionLimit--
+		if o.RecursionLimit < 0 {
+			return out, errors.New("exceeded max recursion depth")
+		}
 		err = o.unmarshalMessageSlow(b, m)
 	}
 	if err != nil {
diff --git a/proto/methods_test.go b/proto/methods_test.go
index b1dcce3..203d42a 100644
--- a/proto/methods_test.go
+++ b/proto/methods_test.go
@@ -3,6 +3,7 @@
 // license that can be found in the LICENSE file.
 
 // The protoreflect tag disables fast-path methods, including legacy ones.
+//go:build !protoreflect
 // +build !protoreflect
 
 package proto_test
diff --git a/proto/proto_methods.go b/proto/proto_methods.go
index d8dd604..465e057 100644
--- a/proto/proto_methods.go
+++ b/proto/proto_methods.go
@@ -3,6 +3,7 @@
 // license that can be found in the LICENSE file.
 
 // The protoreflect build tag disables use of fast-path methods.
+//go:build !protoreflect
 // +build !protoreflect
 
 package proto
diff --git a/proto/proto_reflect.go b/proto/proto_reflect.go
index b103d43..494d6ce 100644
--- a/proto/proto_reflect.go
+++ b/proto/proto_reflect.go
@@ -3,6 +3,7 @@
 // license that can be found in the LICENSE file.
 
 // The protoreflect build tag disables use of fast-path methods.
+//go:build protoreflect
 // +build protoreflect
 
 package proto
diff --git a/reflect/protoreflect/methods.go b/reflect/protoreflect/methods.go
index 6be5d16..d5d5af6 100644
--- a/reflect/protoreflect/methods.go
+++ b/reflect/protoreflect/methods.go
@@ -53,6 +53,7 @@
 			FindExtensionByName(field FullName) (ExtensionType, error)
 			FindExtensionByNumber(message FullName, field FieldNumber) (ExtensionType, error)
 		}
+		Depth int
 	}
 	unmarshalOutput = struct {
 		pragma.NoUnkeyedLiterals
diff --git a/reflect/protoreflect/value_pure.go b/reflect/protoreflect/value_pure.go
index 918e685..7ced876 100644
--- a/reflect/protoreflect/value_pure.go
+++ b/reflect/protoreflect/value_pure.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build purego || appengine
 // +build purego appengine
 
 package protoreflect
diff --git a/reflect/protoreflect/value_union.go b/reflect/protoreflect/value_union.go
index 5a34147..eb7764c 100644
--- a/reflect/protoreflect/value_union.go
+++ b/reflect/protoreflect/value_union.go
@@ -41,6 +41,31 @@
 // Converting to/from a Value and a concrete Go value panics on type mismatch.
 // For example, ValueOf("hello").Int() panics because this attempts to
 // retrieve an int64 from a string.
+//
+// List, Map, and Message Values are called "composite" values.
+//
+// A composite Value may alias (reference) memory at some location,
+// such that changes to the Value updates the that location.
+// A composite value acquired with a Mutable method, such as Message.Mutable,
+// always references the source object.
+//
+// For example:
+//	// Append a 0 to a "repeated int32" field.
+//	// Since the Value returned by Mutable is guaranteed to alias
+//	// the source message, modifying the Value modifies the message.
+//	message.Mutable(fieldDesc).(List).Append(protoreflect.ValueOfInt32(0))
+//
+//	// Assign [0] to a "repeated int32" field by creating a new Value,
+//	// modifying it, and assigning it.
+//	list := message.NewField(fieldDesc).(List)
+//	list.Append(protoreflect.ValueOfInt32(0))
+//	message.Set(fieldDesc, list)
+//	// ERROR: Since it is not defined whether Set aliases the source,
+//	// appending to the List here may or may not modify the message.
+//	list.Append(protoreflect.ValueOfInt32(0))
+//
+// Some operations, such as Message.Get, may return an "empty, read-only"
+// composite Value. Modifying an empty, read-only value panics.
 type Value value
 
 // The protoreflect API uses a custom Value union type instead of interface{}
diff --git a/reflect/protoreflect/value_unsafe.go b/reflect/protoreflect/value_unsafe.go
index c45debd..702ddf2 100644
--- a/reflect/protoreflect/value_unsafe.go
+++ b/reflect/protoreflect/value_unsafe.go
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !purego && !appengine
 // +build !purego,!appengine
 
 package protoreflect
diff --git a/runtime/protoiface/methods.go b/runtime/protoiface/methods.go
index 32c04f6..44cf467 100644
--- a/runtime/protoiface/methods.go
+++ b/runtime/protoiface/methods.go
@@ -103,6 +103,7 @@
 		FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error)
 		FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error)
 	}
+	Depth int
 }
 
 // UnmarshalOutput is output from the Unmarshal method.
diff --git a/testing/protocmp/reflect.go b/testing/protocmp/reflect.go
index 5b92cb8..0a5e474 100644
--- a/testing/protocmp/reflect.go
+++ b/testing/protocmp/reflect.go
@@ -68,7 +68,7 @@
 	}
 
 	// Range over populated extension fields.
-	for _, xd := range m[messageTypeKey].(messageType).xds {
+	for _, xd := range m[messageTypeKey].(messageMeta).xds {
 		if m.Has(xd) && !f(xd, m.Get(xd)) {
 			return
 		}
@@ -91,7 +91,7 @@
 			return protoreflect.ValueOfMap(reflectMap{})
 		case fd.Message() != nil:
 			return protoreflect.ValueOfMessage(reflectMessage{
-				messageTypeKey: messageType{md: m.Descriptor()},
+				messageTypeKey: messageMeta{md: fd.Message()},
 			})
 		default:
 			return fd.Default()
diff --git a/testing/protocmp/util.go b/testing/protocmp/util.go
index ac6237e..79f3072 100644
--- a/testing/protocmp/util.go
+++ b/testing/protocmp/util.go
@@ -297,11 +297,11 @@
 		return true // treat missing fields as already filtered
 	}
 	var fd protoreflect.FieldDescriptor
-	switch mt := m[messageTypeKey].(messageType); {
+	switch mm := m[messageTypeKey].(messageMeta); {
 	case protoreflect.Name(k).IsValid():
-		fd = mt.md.Fields().ByTextName(k)
+		fd = mm.md.Fields().ByTextName(k)
 	default:
-		fd = mt.xds[k]
+		fd = mm.xds[k]
 	}
 	if fd != nil {
 		return f.names[fd.FullName()]
@@ -376,11 +376,11 @@
 	}
 
 	var fd protoreflect.FieldDescriptor
-	switch mt := m[messageTypeKey].(messageType); {
+	switch mm := m[messageTypeKey].(messageMeta); {
 	case protoreflect.Name(k).IsValid():
-		fd = mt.md.Fields().ByTextName(k)
+		fd = mm.md.Fields().ByTextName(k)
 	default:
-		fd = mt.xds[k]
+		fd = mm.xds[k]
 	}
 	if fd == nil || !fd.Default().IsValid() {
 		return false
diff --git a/testing/protocmp/xform.go b/testing/protocmp/xform.go
index 5a47d0f..7a32e2d 100644
--- a/testing/protocmp/xform.go
+++ b/testing/protocmp/xform.go
@@ -68,20 +68,28 @@
 }
 
 const (
-	messageTypeKey    = "@type"
+	// messageTypeKey indicates the protobuf message type.
+	// The value type is always messageMeta.
+	// From the public API, it presents itself as only the type, but the
+	// underlying data structure holds arbitrary metadata about the message.
+	messageTypeKey = "@type"
+
+	// messageInvalidKey indicates that the message is invalid.
+	// The value is always the boolean "true".
 	messageInvalidKey = "@invalid"
 )
 
-type messageType struct {
+type messageMeta struct {
+	m   proto.Message
 	md  protoreflect.MessageDescriptor
 	xds map[string]protoreflect.ExtensionDescriptor
 }
 
-func (t messageType) String() string {
+func (t messageMeta) String() string {
 	return string(t.md.FullName())
 }
 
-func (t1 messageType) Equal(t2 messageType) bool {
+func (t1 messageMeta) Equal(t2 messageMeta) bool {
 	return t1.md.FullName() == t2.md.FullName()
 }
 
@@ -109,11 +117,18 @@
 // Message values must not be created by or mutated by users.
 type Message map[string]interface{}
 
+// Unwrap returns the original message value.
+// It returns nil if this Message was not constructed from another message.
+func (m Message) Unwrap() proto.Message {
+	mm, _ := m[messageTypeKey].(messageMeta)
+	return mm.m
+}
+
 // Descriptor return the message descriptor.
 // It returns nil for a zero Message value.
 func (m Message) Descriptor() protoreflect.MessageDescriptor {
-	mt, _ := m[messageTypeKey].(messageType)
-	return mt.md
+	mm, _ := m[messageTypeKey].(messageMeta)
+	return mm.md
 }
 
 // ProtoReflect returns a reflective view of m.
@@ -201,7 +216,7 @@
 		case m == nil:
 			return nil
 		case !m.IsValid():
-			return Message{messageTypeKey: messageType{md: m.Descriptor()}, messageInvalidKey: true}
+			return Message{messageTypeKey: messageMeta{m: m.Interface(), md: m.Descriptor()}, messageInvalidKey: true}
 		default:
 			return transformMessage(m)
 		}
@@ -218,7 +233,7 @@
 
 func transformMessage(m protoreflect.Message) Message {
 	mx := Message{}
-	mt := messageType{md: m.Descriptor(), xds: make(map[string]protoreflect.FieldDescriptor)}
+	mt := messageMeta{m: m.Interface(), md: m.Descriptor(), xds: make(map[string]protoreflect.FieldDescriptor)}
 
 	// Handle known and extension fields.
 	m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
diff --git a/testing/protocmp/xform_test.go b/testing/protocmp/xform_test.go
index c6355d7..a0f7e24 100644
--- a/testing/protocmp/xform_test.go
+++ b/testing/protocmp/xform_test.go
@@ -40,7 +40,7 @@
 			OptionalNestedMessage: &testpb.TestAllTypes_NestedMessage{A: proto.Int32(5)},
 		},
 		want: Message{
-			messageTypeKey:            messageTypeOf(&testpb.TestAllTypes{}),
+			messageTypeKey:            messageMetaOf(&testpb.TestAllTypes{}),
 			"optional_bool":           bool(false),
 			"optional_int32":          int32(-32),
 			"optional_int64":          int64(-64),
@@ -51,7 +51,7 @@
 			"optional_string":         string("string"),
 			"optional_bytes":          []byte("bytes"),
 			"optional_nested_enum":    enumOf(testpb.TestAllTypes_NEG),
-			"optional_nested_message": Message{messageTypeKey: messageTypeOf(&testpb.TestAllTypes_NestedMessage{}), "a": int32(5)},
+			"optional_nested_message": Message{messageTypeKey: messageMetaOf(&testpb.TestAllTypes_NestedMessage{}), "a": int32(5)},
 		},
 	}, {
 		in: &testpb.TestAllTypes{
@@ -74,7 +74,7 @@
 			},
 		},
 		want: Message{
-			messageTypeKey:    messageTypeOf(&testpb.TestAllTypes{}),
+			messageTypeKey:    messageMetaOf(&testpb.TestAllTypes{}),
 			"repeated_bool":   []bool{false, true},
 			"repeated_int32":  []int32{32, -32},
 			"repeated_int64":  []int64{64, -64},
@@ -89,8 +89,8 @@
 				enumOf(testpb.TestAllTypes_BAR),
 			},
 			"repeated_nested_message": []Message{
-				{messageTypeKey: messageTypeOf(&testpb.TestAllTypes_NestedMessage{}), "a": int32(5)},
-				{messageTypeKey: messageTypeOf(&testpb.TestAllTypes_NestedMessage{}), "a": int32(-5)},
+				{messageTypeKey: messageMetaOf(&testpb.TestAllTypes_NestedMessage{}), "a": int32(5)},
+				{messageTypeKey: messageMetaOf(&testpb.TestAllTypes_NestedMessage{}), "a": int32(-5)},
 			},
 		},
 	}, {
@@ -112,7 +112,7 @@
 			},
 		},
 		want: Message{
-			messageTypeKey:      messageTypeOf(&testpb.TestAllTypes{}),
+			messageTypeKey:      messageMetaOf(&testpb.TestAllTypes{}),
 			"map_bool_bool":     map[bool]bool{true: false},
 			"map_int32_int32":   map[int32]int32{-32: 32},
 			"map_int64_int64":   map[int64]int64{-64: 64},
@@ -126,7 +126,7 @@
 				"k": enumOf(testpb.TestAllTypes_FOO),
 			},
 			"map_string_nested_message": map[string]Message{
-				"k": {messageTypeKey: messageTypeOf(&testpb.TestAllTypes_NestedMessage{}), "a": int32(5)},
+				"k": {messageTypeKey: messageMetaOf(&testpb.TestAllTypes_NestedMessage{}), "a": int32(5)},
 			},
 		},
 	}, {
@@ -146,7 +146,7 @@
 			return m
 		}(),
 		want: Message{
-			messageTypeKey:                                 messageTypeOf(&testpb.TestAllExtensions{}),
+			messageTypeKey:                                 messageMetaOf(&testpb.TestAllExtensions{}),
 			"[goproto.proto.test.optional_bool]":           bool(false),
 			"[goproto.proto.test.optional_int32]":          int32(-32),
 			"[goproto.proto.test.optional_int64]":          int64(-64),
@@ -157,7 +157,7 @@
 			"[goproto.proto.test.optional_string]":         string("string"),
 			"[goproto.proto.test.optional_bytes]":          []byte("bytes"),
 			"[goproto.proto.test.optional_nested_enum]":    enumOf(testpb.TestAllTypes_NEG),
-			"[goproto.proto.test.optional_nested_message]": Message{messageTypeKey: messageTypeOf(&testpb.TestAllExtensions_NestedMessage{}), "a": int32(5)},
+			"[goproto.proto.test.optional_nested_message]": Message{messageTypeKey: messageMetaOf(&testpb.TestAllExtensions_NestedMessage{}), "a": int32(5)},
 		},
 	}, {
 		in: func() proto.Message {
@@ -182,7 +182,7 @@
 			return m
 		}(),
 		want: Message{
-			messageTypeKey:                         messageTypeOf(&testpb.TestAllExtensions{}),
+			messageTypeKey:                         messageMetaOf(&testpb.TestAllExtensions{}),
 			"[goproto.proto.test.repeated_bool]":   []bool{false, true},
 			"[goproto.proto.test.repeated_int32]":  []int32{32, -32},
 			"[goproto.proto.test.repeated_int64]":  []int64{64, -64},
@@ -197,8 +197,8 @@
 				enumOf(testpb.TestAllTypes_BAR),
 			},
 			"[goproto.proto.test.repeated_nested_message]": []Message{
-				{messageTypeKey: messageTypeOf(&testpb.TestAllExtensions_NestedMessage{}), "a": int32(5)},
-				{messageTypeKey: messageTypeOf(&testpb.TestAllExtensions_NestedMessage{}), "a": int32(-5)},
+				{messageTypeKey: messageMetaOf(&testpb.TestAllExtensions_NestedMessage{}), "a": int32(5)},
+				{messageTypeKey: messageMetaOf(&testpb.TestAllExtensions_NestedMessage{}), "a": int32(-5)},
 			},
 		},
 	}, {
@@ -229,7 +229,7 @@
 			return m
 		}(),
 		want: Message{
-			messageTypeKey: messageTypeOf(&testpb.TestAllTypes{}),
+			messageTypeKey: messageMetaOf(&testpb.TestAllTypes{}),
 			"50000":        protoreflect.RawFields(protopack.Message{protopack.Tag{Number: 50000, Type: protopack.VarintType}, protopack.Uvarint(100)}.Marshal()),
 			"50001":        protoreflect.RawFields(protopack.Message{protopack.Tag{Number: 50001, Type: protopack.Fixed32Type}, protopack.Uint32(200)}.Marshal()),
 			"50002":        protoreflect.RawFields(protopack.Message{protopack.Tag{Number: 50002, Type: protopack.Fixed64Type}, protopack.Uint64(300)}.Marshal()),
@@ -258,6 +258,9 @@
 			if diff := cmp.Diff(tt.want, got); diff != "" {
 				t.Errorf("Transform() mismatch (-want +got):\n%v", diff)
 			}
+			if got.Unwrap() != tt.in {
+				t.Errorf("got.Unwrap() = %p, want %p", got.Unwrap(), tt.in)
+			}
 		})
 	}
 }
@@ -266,6 +269,6 @@
 	return Enum{e.Number(), e.Descriptor()}
 }
 
-func messageTypeOf(m protoreflect.ProtoMessage) messageType {
-	return messageType{md: m.ProtoReflect().Descriptor()}
+func messageMetaOf(m protoreflect.ProtoMessage) messageMeta {
+	return messageMeta{m: m, md: m.ProtoReflect().Descriptor()}
 }
diff --git a/testing/prototest/message.go b/testing/prototest/message.go
index e495628..c104605 100644
--- a/testing/prototest/message.go
+++ b/testing/prototest/message.go
@@ -643,7 +643,7 @@
 		if err != nil {
 			return fmt.Sprintf("<%v>", err)
 		}
-		return fmt.Sprintf("%v{%v}", v.Descriptor().FullName(), string(b))
+		return fmt.Sprintf("%v{%s}", v.Descriptor().FullName(), b)
 	case string:
 		return fmt.Sprintf("%q", v)
 	default: