protobuf-go/internal/impl/legacy_extension.go

306 lines
8.0 KiB
Go
Raw Normal View History

internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package impl
import (
"reflect"
pref "github.com/golang/protobuf/v2/reflect/protoreflect"
piface "github.com/golang/protobuf/v2/runtime/protoiface"
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
)
func makeLegacyExtensionFieldsFunc(t reflect.Type) func(p *messageDataType) pref.KnownFields {
f := makeLegacyExtensionMapFunc(t)
if f == nil {
return nil
}
return func(p *messageDataType) pref.KnownFields {
if p.p.IsNil() {
return emptyExtensionFields{}
}
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
return legacyExtensionFields{p.mi, f(p)}
}
}
var extType = reflect.TypeOf(map[int32]ExtensionFieldV1{})
func makeLegacyExtensionMapFunc(t reflect.Type) func(*messageDataType) *legacyExtensionMap {
fx, _ := t.FieldByName("XXX_extensions")
if fx.Type != extType {
fx, _ = t.FieldByName("XXX_InternalExtensions")
}
if fx.Type != extType {
return nil
}
fieldOffset := offsetOf(fx)
return func(p *messageDataType) *legacyExtensionMap {
v := p.p.Apply(fieldOffset).AsValueOf(fx.Type).Interface()
return (*legacyExtensionMap)(v.(*map[int32]ExtensionFieldV1))
}
}
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
type legacyExtensionFields struct {
mi *MessageType
x *legacyExtensionMap
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
}
func (p legacyExtensionFields) Len() (n int) {
p.x.Range(func(num pref.FieldNumber, _ ExtensionFieldV1) bool {
if p.Has(pref.FieldNumber(num)) {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
n++
}
return true
})
return n
}
func (p legacyExtensionFields) Has(n pref.FieldNumber) bool {
x := p.x.Get(n)
if x.Value == nil {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
return false
}
t := extensionTypeFromDesc(x.Desc)
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
if t.Cardinality() == pref.Repeated {
return t.ValueOf(x.Value).List().Len() > 0
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
}
return true
}
func (p legacyExtensionFields) Get(n pref.FieldNumber) pref.Value {
x := p.x.Get(n)
if x.Desc == nil {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
return pref.Value{}
}
t := extensionTypeFromDesc(x.Desc)
if x.Value == nil {
// NOTE: x.Value is never nil for Lists since they are always populated
reflect/protoreflect: clarify Get semantics on unpopulated fields Clearly specify that Get on an unpopulated field: * returns the default value for scalars * returns a mutable (but empty) List for repeated fields * returns a mutable (but empty) Map for map fields * returns an invalid value for message fields The difference in semantics between List+Maps and Messages is because protobuf semantics provide no distinction between an unpopulated and empty list or map. On the other hand, there is a semantic difference between an unpopulated message and an empty message. Default values for scalars is trivial to implement with FieldDescriptor.Default. A mutable, but empty List and Map is easy to implement for known fields since known fields are generated as a slice or map field in a struct. Since struct fields are addressable, the implementation can just return a reference to the slice or map. Repeated, extension fields are a little more tricky since extension fields are implemented under the hood as a map[FieldNumber]Extension. Rather than allocating an empty list in KnownFields.Get upon first retrieval (which presents a race), delegate the work to ExtensionFieldTypes.Register, which must occur before any Get operation. Register is not a concurrent-safe operation, so that is an excellent time to initilize empty lists. The implementation of extensions will need to be careful that Clear on a repeated field simply truncates it zero instead of deleting the object. For unpopulated messages, we return an invalid value, instead of the prior behavior of returning a typed nil-pointer to the Go type for the message. The approach is problematic because it assumes that 1) all messages are always implemented on a pointer reciever 2) a typed nil-pointer is an appropriate "read-only, but empty" message These assumptions are not true of all message types (e.g., dynamic messages). Change-Id: Ie96e6744c890308d9de738b6cf01d3b19e7e7c6a Reviewed-on: https://go-review.googlesource.com/c/150319 Reviewed-by: Damien Neil <dneil@google.com>
2018-11-19 14:26:06 -08:00
// during ExtensionFieldTypes.Register.
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
if t.Kind() == pref.MessageKind || t.Kind() == pref.GroupKind {
return pref.Value{}
}
return t.Default()
}
return t.ValueOf(x.Value)
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
}
func (p legacyExtensionFields) Set(n pref.FieldNumber, v pref.Value) {
x := p.x.Get(n)
if x.Desc == nil {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
panic("no extension descriptor registered")
}
t := extensionTypeFromDesc(x.Desc)
x.Value = t.InterfaceOf(v)
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
p.x.Set(n, x)
}
func (p legacyExtensionFields) Clear(n pref.FieldNumber) {
x := p.x.Get(n)
if x.Desc == nil {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
return
}
t := extensionTypeFromDesc(x.Desc)
reflect/protoreflect: clarify Get semantics on unpopulated fields Clearly specify that Get on an unpopulated field: * returns the default value for scalars * returns a mutable (but empty) List for repeated fields * returns a mutable (but empty) Map for map fields * returns an invalid value for message fields The difference in semantics between List+Maps and Messages is because protobuf semantics provide no distinction between an unpopulated and empty list or map. On the other hand, there is a semantic difference between an unpopulated message and an empty message. Default values for scalars is trivial to implement with FieldDescriptor.Default. A mutable, but empty List and Map is easy to implement for known fields since known fields are generated as a slice or map field in a struct. Since struct fields are addressable, the implementation can just return a reference to the slice or map. Repeated, extension fields are a little more tricky since extension fields are implemented under the hood as a map[FieldNumber]Extension. Rather than allocating an empty list in KnownFields.Get upon first retrieval (which presents a race), delegate the work to ExtensionFieldTypes.Register, which must occur before any Get operation. Register is not a concurrent-safe operation, so that is an excellent time to initilize empty lists. The implementation of extensions will need to be careful that Clear on a repeated field simply truncates it zero instead of deleting the object. For unpopulated messages, we return an invalid value, instead of the prior behavior of returning a typed nil-pointer to the Go type for the message. The approach is problematic because it assumes that 1) all messages are always implemented on a pointer reciever 2) a typed nil-pointer is an appropriate "read-only, but empty" message These assumptions are not true of all message types (e.g., dynamic messages). Change-Id: Ie96e6744c890308d9de738b6cf01d3b19e7e7c6a Reviewed-on: https://go-review.googlesource.com/c/150319 Reviewed-by: Damien Neil <dneil@google.com>
2018-11-19 14:26:06 -08:00
if t.Cardinality() == pref.Repeated {
t.ValueOf(x.Value).List().Truncate(0)
reflect/protoreflect: clarify Get semantics on unpopulated fields Clearly specify that Get on an unpopulated field: * returns the default value for scalars * returns a mutable (but empty) List for repeated fields * returns a mutable (but empty) Map for map fields * returns an invalid value for message fields The difference in semantics between List+Maps and Messages is because protobuf semantics provide no distinction between an unpopulated and empty list or map. On the other hand, there is a semantic difference between an unpopulated message and an empty message. Default values for scalars is trivial to implement with FieldDescriptor.Default. A mutable, but empty List and Map is easy to implement for known fields since known fields are generated as a slice or map field in a struct. Since struct fields are addressable, the implementation can just return a reference to the slice or map. Repeated, extension fields are a little more tricky since extension fields are implemented under the hood as a map[FieldNumber]Extension. Rather than allocating an empty list in KnownFields.Get upon first retrieval (which presents a race), delegate the work to ExtensionFieldTypes.Register, which must occur before any Get operation. Register is not a concurrent-safe operation, so that is an excellent time to initilize empty lists. The implementation of extensions will need to be careful that Clear on a repeated field simply truncates it zero instead of deleting the object. For unpopulated messages, we return an invalid value, instead of the prior behavior of returning a typed nil-pointer to the Go type for the message. The approach is problematic because it assumes that 1) all messages are always implemented on a pointer reciever 2) a typed nil-pointer is an appropriate "read-only, but empty" message These assumptions are not true of all message types (e.g., dynamic messages). Change-Id: Ie96e6744c890308d9de738b6cf01d3b19e7e7c6a Reviewed-on: https://go-review.googlesource.com/c/150319 Reviewed-by: Damien Neil <dneil@google.com>
2018-11-19 14:26:06 -08:00
return
}
x.Value = nil
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
p.x.Set(n, x)
}
func (p legacyExtensionFields) WhichOneof(pref.Name) pref.FieldNumber {
return 0
}
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
func (p legacyExtensionFields) Range(f func(pref.FieldNumber, pref.Value) bool) {
p.x.Range(func(n pref.FieldNumber, x ExtensionFieldV1) bool {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
if p.Has(n) {
return f(n, p.Get(n))
}
return true
})
}
func (p legacyExtensionFields) NewMessage(n pref.FieldNumber) pref.Message {
x := p.x.Get(n)
if x.Desc == nil {
panic("no extension descriptor registered")
}
xt := extensionTypeFromDesc(x.Desc)
return xt.New().Message()
}
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
func (p legacyExtensionFields) ExtensionTypes() pref.ExtensionFieldTypes {
return legacyExtensionTypes(p)
}
type legacyExtensionTypes legacyExtensionFields
func (p legacyExtensionTypes) Len() (n int) {
p.x.Range(func(_ pref.FieldNumber, x ExtensionFieldV1) bool {
if x.Desc != nil {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
n++
}
return true
})
return n
}
func (p legacyExtensionTypes) Register(t pref.ExtensionType) {
if p.mi.PBType.FullName() != t.Extendee().FullName() {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
panic("extended type mismatch")
}
internal/fileinit: generate reflect data structures from raw descriptors This CL takes a significantly different approach to generating support for protobuf reflection. The previous approach involved generating a large number of Go literals to represent the reflection information. While that approach was correct, it resulted in too much binary bloat. The approach taken here initializes the reflection information from the raw descriptor proto, which is a relatively dense representation of the protobuf reflection information. In order to keep initialization cost low, several measures were taken: * At program init, the bare minimum is parsed in order to initialize naming information for enums, messages, extensions, and services declared in the file. This is done because those top-level declarations are often relevant for registration. * Only upon first are most of the other data structures for protobuf reflection actually initialized. * Instead of using proto.Unmarshal, a hand-written unmarshaler is used. This allows us to avoid a dependendency on the descriptor proto and also because the API for the descriptor proto is fundamentally non-performant since it requires an allocation for every primitive field. At a high-level, the new implementation lives in internal/fileinit. Several changes were made to other parts of the repository: * cmd/protoc-gen-go: * Stop compressing the raw descriptors. While compression does reduce the size of the descriptors by approximately 2x, it is a pre-mature optimization since the descriptors themselves are around 1% of the total binary bloat that is due to generated protobufs. * Seeding protobuf reflection from the raw descriptor significantly simplifies the generator implementation since it is no longer responsible for constructing a tree of Go literals to represent the same information. * We remove the generation of the shadow types and instead call protoimpl.MessageType.MessageOf. Unfortunately, this incurs an allocation for every call to ProtoReflect since we need to allocate a tuple that wraps a pointer to the message value, and a pointer to message type. * internal/impl: * We add a MessageType.GoType field and make it required that it is set prior to first use. This is done so that we can avoid calling MessageType.init except for when it is actually needed. The allows code to call (*FooMessage)(nil).ProtoReflect().Type() without fearing that the init code will run, possibly triggering a recursive deadlock (where the init code depends on getting the Type of some dependency which may be declared within the same file). * internal/cmd/generate-types: * The code to generate reflect/prototype/protofile_list_gen.go was copied and altered to generated internal/fileinit.desc_list_gen.go. At a high-level this CL adds significant technical complexity. However, this is offset by several possible future changes: * The prototype package can be drastically simplified. We can probably reimplement internal/legacy to use internal/fileinit instead, allowing us to drop another dependency on the prototype package. As a result, we can probably delete most of the constructor types in that package. * With the prototype package significantly pruned, and the fact that generated code no longer depend on depends on that package, we can consider merging what's left of prototype into protodesc. Change-Id: I6090f023f2e1b6afaf62bd3ae883566242e30715 Reviewed-on: https://go-review.googlesource.com/c/158539 Reviewed-by: Herbie Ong <herbie@google.com> Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
2019-01-18 09:32:24 -08:00
if !p.mi.PBType.ExtensionRanges().Has(t.Number()) {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
panic("invalid extension field number")
}
x := p.x.Get(t.Number())
if x.Desc != nil {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
panic("extension descriptor already registered")
}
x.Desc = extensionDescFromType(t)
reflect/protoreflect: clarify Get semantics on unpopulated fields Clearly specify that Get on an unpopulated field: * returns the default value for scalars * returns a mutable (but empty) List for repeated fields * returns a mutable (but empty) Map for map fields * returns an invalid value for message fields The difference in semantics between List+Maps and Messages is because protobuf semantics provide no distinction between an unpopulated and empty list or map. On the other hand, there is a semantic difference between an unpopulated message and an empty message. Default values for scalars is trivial to implement with FieldDescriptor.Default. A mutable, but empty List and Map is easy to implement for known fields since known fields are generated as a slice or map field in a struct. Since struct fields are addressable, the implementation can just return a reference to the slice or map. Repeated, extension fields are a little more tricky since extension fields are implemented under the hood as a map[FieldNumber]Extension. Rather than allocating an empty list in KnownFields.Get upon first retrieval (which presents a race), delegate the work to ExtensionFieldTypes.Register, which must occur before any Get operation. Register is not a concurrent-safe operation, so that is an excellent time to initilize empty lists. The implementation of extensions will need to be careful that Clear on a repeated field simply truncates it zero instead of deleting the object. For unpopulated messages, we return an invalid value, instead of the prior behavior of returning a typed nil-pointer to the Go type for the message. The approach is problematic because it assumes that 1) all messages are always implemented on a pointer reciever 2) a typed nil-pointer is an appropriate "read-only, but empty" message These assumptions are not true of all message types (e.g., dynamic messages). Change-Id: Ie96e6744c890308d9de738b6cf01d3b19e7e7c6a Reviewed-on: https://go-review.googlesource.com/c/150319 Reviewed-by: Damien Neil <dneil@google.com>
2018-11-19 14:26:06 -08:00
if t.Cardinality() == pref.Repeated {
// If the field is repeated, initialize the entry with an empty list
// so that future Get operations can return a mutable and concrete list.
x.Value = t.InterfaceOf(t.New())
reflect/protoreflect: clarify Get semantics on unpopulated fields Clearly specify that Get on an unpopulated field: * returns the default value for scalars * returns a mutable (but empty) List for repeated fields * returns a mutable (but empty) Map for map fields * returns an invalid value for message fields The difference in semantics between List+Maps and Messages is because protobuf semantics provide no distinction between an unpopulated and empty list or map. On the other hand, there is a semantic difference between an unpopulated message and an empty message. Default values for scalars is trivial to implement with FieldDescriptor.Default. A mutable, but empty List and Map is easy to implement for known fields since known fields are generated as a slice or map field in a struct. Since struct fields are addressable, the implementation can just return a reference to the slice or map. Repeated, extension fields are a little more tricky since extension fields are implemented under the hood as a map[FieldNumber]Extension. Rather than allocating an empty list in KnownFields.Get upon first retrieval (which presents a race), delegate the work to ExtensionFieldTypes.Register, which must occur before any Get operation. Register is not a concurrent-safe operation, so that is an excellent time to initilize empty lists. The implementation of extensions will need to be careful that Clear on a repeated field simply truncates it zero instead of deleting the object. For unpopulated messages, we return an invalid value, instead of the prior behavior of returning a typed nil-pointer to the Go type for the message. The approach is problematic because it assumes that 1) all messages are always implemented on a pointer reciever 2) a typed nil-pointer is an appropriate "read-only, but empty" message These assumptions are not true of all message types (e.g., dynamic messages). Change-Id: Ie96e6744c890308d9de738b6cf01d3b19e7e7c6a Reviewed-on: https://go-review.googlesource.com/c/150319 Reviewed-by: Damien Neil <dneil@google.com>
2018-11-19 14:26:06 -08:00
}
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
p.x.Set(t.Number(), x)
}
func (p legacyExtensionTypes) Remove(t pref.ExtensionType) {
internal/fileinit: generate reflect data structures from raw descriptors This CL takes a significantly different approach to generating support for protobuf reflection. The previous approach involved generating a large number of Go literals to represent the reflection information. While that approach was correct, it resulted in too much binary bloat. The approach taken here initializes the reflection information from the raw descriptor proto, which is a relatively dense representation of the protobuf reflection information. In order to keep initialization cost low, several measures were taken: * At program init, the bare minimum is parsed in order to initialize naming information for enums, messages, extensions, and services declared in the file. This is done because those top-level declarations are often relevant for registration. * Only upon first are most of the other data structures for protobuf reflection actually initialized. * Instead of using proto.Unmarshal, a hand-written unmarshaler is used. This allows us to avoid a dependendency on the descriptor proto and also because the API for the descriptor proto is fundamentally non-performant since it requires an allocation for every primitive field. At a high-level, the new implementation lives in internal/fileinit. Several changes were made to other parts of the repository: * cmd/protoc-gen-go: * Stop compressing the raw descriptors. While compression does reduce the size of the descriptors by approximately 2x, it is a pre-mature optimization since the descriptors themselves are around 1% of the total binary bloat that is due to generated protobufs. * Seeding protobuf reflection from the raw descriptor significantly simplifies the generator implementation since it is no longer responsible for constructing a tree of Go literals to represent the same information. * We remove the generation of the shadow types and instead call protoimpl.MessageType.MessageOf. Unfortunately, this incurs an allocation for every call to ProtoReflect since we need to allocate a tuple that wraps a pointer to the message value, and a pointer to message type. * internal/impl: * We add a MessageType.GoType field and make it required that it is set prior to first use. This is done so that we can avoid calling MessageType.init except for when it is actually needed. The allows code to call (*FooMessage)(nil).ProtoReflect().Type() without fearing that the init code will run, possibly triggering a recursive deadlock (where the init code depends on getting the Type of some dependency which may be declared within the same file). * internal/cmd/generate-types: * The code to generate reflect/prototype/protofile_list_gen.go was copied and altered to generated internal/fileinit.desc_list_gen.go. At a high-level this CL adds significant technical complexity. However, this is offset by several possible future changes: * The prototype package can be drastically simplified. We can probably reimplement internal/legacy to use internal/fileinit instead, allowing us to drop another dependency on the prototype package. As a result, we can probably delete most of the constructor types in that package. * With the prototype package significantly pruned, and the fact that generated code no longer depend on depends on that package, we can consider merging what's left of prototype into protodesc. Change-Id: I6090f023f2e1b6afaf62bd3ae883566242e30715 Reviewed-on: https://go-review.googlesource.com/c/158539 Reviewed-by: Herbie Ong <herbie@google.com> Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
2019-01-18 09:32:24 -08:00
if !p.mi.PBType.ExtensionRanges().Has(t.Number()) {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
return
}
x := p.x.Get(t.Number())
reflect/protoreflect: clarify Get semantics on unpopulated fields Clearly specify that Get on an unpopulated field: * returns the default value for scalars * returns a mutable (but empty) List for repeated fields * returns a mutable (but empty) Map for map fields * returns an invalid value for message fields The difference in semantics between List+Maps and Messages is because protobuf semantics provide no distinction between an unpopulated and empty list or map. On the other hand, there is a semantic difference between an unpopulated message and an empty message. Default values for scalars is trivial to implement with FieldDescriptor.Default. A mutable, but empty List and Map is easy to implement for known fields since known fields are generated as a slice or map field in a struct. Since struct fields are addressable, the implementation can just return a reference to the slice or map. Repeated, extension fields are a little more tricky since extension fields are implemented under the hood as a map[FieldNumber]Extension. Rather than allocating an empty list in KnownFields.Get upon first retrieval (which presents a race), delegate the work to ExtensionFieldTypes.Register, which must occur before any Get operation. Register is not a concurrent-safe operation, so that is an excellent time to initilize empty lists. The implementation of extensions will need to be careful that Clear on a repeated field simply truncates it zero instead of deleting the object. For unpopulated messages, we return an invalid value, instead of the prior behavior of returning a typed nil-pointer to the Go type for the message. The approach is problematic because it assumes that 1) all messages are always implemented on a pointer reciever 2) a typed nil-pointer is an appropriate "read-only, but empty" message These assumptions are not true of all message types (e.g., dynamic messages). Change-Id: Ie96e6744c890308d9de738b6cf01d3b19e7e7c6a Reviewed-on: https://go-review.googlesource.com/c/150319 Reviewed-by: Damien Neil <dneil@google.com>
2018-11-19 14:26:06 -08:00
if t.Cardinality() == pref.Repeated {
// Treat an empty repeated field as unpopulated.
v := reflect.ValueOf(x.Value)
if x.Value == nil || v.IsNil() || v.Elem().Len() == 0 {
x.Value = nil
reflect/protoreflect: clarify Get semantics on unpopulated fields Clearly specify that Get on an unpopulated field: * returns the default value for scalars * returns a mutable (but empty) List for repeated fields * returns a mutable (but empty) Map for map fields * returns an invalid value for message fields The difference in semantics between List+Maps and Messages is because protobuf semantics provide no distinction between an unpopulated and empty list or map. On the other hand, there is a semantic difference between an unpopulated message and an empty message. Default values for scalars is trivial to implement with FieldDescriptor.Default. A mutable, but empty List and Map is easy to implement for known fields since known fields are generated as a slice or map field in a struct. Since struct fields are addressable, the implementation can just return a reference to the slice or map. Repeated, extension fields are a little more tricky since extension fields are implemented under the hood as a map[FieldNumber]Extension. Rather than allocating an empty list in KnownFields.Get upon first retrieval (which presents a race), delegate the work to ExtensionFieldTypes.Register, which must occur before any Get operation. Register is not a concurrent-safe operation, so that is an excellent time to initilize empty lists. The implementation of extensions will need to be careful that Clear on a repeated field simply truncates it zero instead of deleting the object. For unpopulated messages, we return an invalid value, instead of the prior behavior of returning a typed nil-pointer to the Go type for the message. The approach is problematic because it assumes that 1) all messages are always implemented on a pointer reciever 2) a typed nil-pointer is an appropriate "read-only, but empty" message These assumptions are not true of all message types (e.g., dynamic messages). Change-Id: Ie96e6744c890308d9de738b6cf01d3b19e7e7c6a Reviewed-on: https://go-review.googlesource.com/c/150319 Reviewed-by: Damien Neil <dneil@google.com>
2018-11-19 14:26:06 -08:00
}
}
if x.Value != nil {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
panic("value for extension descriptor still populated")
}
x.Desc = nil
if len(x.Raw) == 0 {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
p.x.Clear(t.Number())
} else {
p.x.Set(t.Number(), x)
}
}
func (p legacyExtensionTypes) ByNumber(n pref.FieldNumber) pref.ExtensionType {
x := p.x.Get(n)
if x.Desc != nil {
return extensionTypeFromDesc(x.Desc)
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
}
return nil
}
func (p legacyExtensionTypes) ByName(s pref.FullName) (t pref.ExtensionType) {
p.x.Range(func(_ pref.FieldNumber, x ExtensionFieldV1) bool {
if x.Desc != nil && x.Desc.Name == string(s) {
t = extensionTypeFromDesc(x.Desc)
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
return false
}
return true
})
return t
}
func (p legacyExtensionTypes) Range(f func(pref.ExtensionType) bool) {
p.x.Range(func(_ pref.FieldNumber, x ExtensionFieldV1) bool {
if x.Desc != nil {
if !f(extensionTypeFromDesc(x.Desc)) {
internal/impl: support legacy extension fields Implement support for extension fields for messages that use the v1 data structures for extensions. The legacyExtensionFields type wraps a v1 map to implement the v2 protoreflect.KnownFields interface. Working on this change revealed a bug in the dynamic construction of message types for protobuf messages that had cyclic dependencies (e.g., message Foo has a sub-field of message Bar, and Bar has a sub-field of Foo). In such a situation, a deadlock occurs because initialization code depends on the very initialization code that is currently running. To break these cycles, we make some systematic changes listed in the following paragraphs. Generally speaking, we separate the logic for construction and wrapping, where constuction does not recursively rely on dependencies, while wrapping may recursively inspect dependencies. Promote the MessageType.MessageOf method as a standalone MessageOf function that dynamically finds the proper *MessageType to use. We make it such that MessageType only supports two forms of messages types: * Those that fully implement the v2 API. * Those that do not implement the v2 API at all. This removes support for the hybrid form that was exploited by message_test.go In impl/message_test.go, switch each message to look more like how future generated messages will look like. This is done in reaction to the fact that MessageType.MessageOf no longer exists. In value/{map,vector}.go, fix Unwrap to return a pointer since the underlying reflect.Value is addressable reference value, not a pointer value. In value/convert.go, split the logic apart so that obtaining a v2 type and wrapping a type as v2 are distinct operations. Wrapping requires further initialization than simply creating the initial message type, and calling it during initial construction would lead to a deadlock. In protoreflect/go_type.go, we switch back to a lazy initialization of GoType to avoid a deadlock since the user-provided fn may rely on the fact that prototype.GoMessage returned. Change-Id: I5dea00e36fe1a9899bd2ac0aed2c8e51d5d87420 Reviewed-on: https://go-review.googlesource.com/c/148826 Reviewed-by: Herbie Ong <herbie@google.com>
2018-11-06 13:05:20 -08:00
return false
}
}
return true
})
}
func extensionDescFromType(typ pref.ExtensionType) *piface.ExtensionDescV1 {
if xt, ok := typ.(interface {
ProtoLegacyExtensionDesc() *piface.ExtensionDescV1
}); ok {
if desc := xt.ProtoLegacyExtensionDesc(); desc != nil {
return desc
}
}
return legacyWrapper.ExtensionDescFromType(typ)
}
func extensionTypeFromDesc(desc *piface.ExtensionDescV1) pref.ExtensionType {
if desc.Type != nil {
return desc.Type
}
return legacyWrapper.ExtensionTypeFromDesc(desc)
}
type ExtensionFieldV1 struct {
// TODO: We should turn this into a type alias to an unnamed type,
// which means that v1 can have the same struct, and we no longer have to
// export this from the v2 API.
// When an extension is stored in a message using SetExtension
// only desc and value are set. When the message is marshaled
// Raw will be set to the encoded form of the message.
//
// When a message is unmarshaled and contains extensions, each
// extension will have only Raw set. When such an extension is
// accessed using GetExtension (or GetExtensions) desc and value
// will be set.
Desc *piface.ExtensionDescV1 // TODO: switch to protoreflect.ExtensionType
// Value is a concrete value for the extension field. Let the type of
// Desc.ExtensionType be the "API type" and the type of Value be the
// "storage type". The API type and storage type are the same except:
// * for scalars (except []byte), where the API type uses *T,
// while the storage type uses T.
// * for repeated fields, where the API type uses []T,
// while the storage type uses *[]T.
//
// The reason for the divergence is so that the storage type more naturally
// matches what is expected of when retrieving the values through the
// protobuf reflection APIs.
//
// The Value may only be populated if Desc is also populated.
Value interface{} // TODO: switch to protoreflect.Value
// Raw is the raw encoded bytes for the extension field.
// It is possible for Raw to be populated irrespective of whether the
// other fields are populated.
Raw []byte // TODO: remove; let this be handled by XXX_unrecognized
}
type legacyExtensionMap map[int32]ExtensionFieldV1
func (m legacyExtensionMap) Len() int {
return len(m)
}
func (m legacyExtensionMap) Has(n pref.FieldNumber) bool {
_, ok := m[int32(n)]
return ok
}
func (m legacyExtensionMap) Get(n pref.FieldNumber) ExtensionFieldV1 {
return m[int32(n)]
}
func (m *legacyExtensionMap) Set(n pref.FieldNumber, x ExtensionFieldV1) {
if *m == nil {
*m = make(map[int32]ExtensionFieldV1)
}
(*m)[int32(n)] = x
}
func (m *legacyExtensionMap) Clear(n pref.FieldNumber) {
delete(*m, int32(n))
}
func (m legacyExtensionMap) Range(f func(pref.FieldNumber, ExtensionFieldV1) bool) {
for n, x := range m {
if !f(pref.FieldNumber(n), x) {
return
}
}
}