mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-01-01 03:14:16 +00:00
0e932930c8
Extensions will be kept in wire format over proto.Size and proto.Marshal. This change is a significant performance optimization for jobs that read and write Protobuf messages of the same type, but do not need to process extensions. This change is based on work by Patrik Nyblom. Note that the proto.Size semantics for lazy messages might be surprising; see https://protobuf.dev/reference/go/size/ for details. We have been running this change for about two weeks in Google, all known breakages have already been addressed with CL 579995. related to golang/protobuf#1609 Change-Id: I16be78d15304d775bb30e76356a1a61d61300b43 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/580015 Reviewed-by: Lasse Folger <lassefolger@google.com> Auto-Submit: Michael Stapelberg <stapelberg@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
142 lines
3.5 KiB
Go
142 lines
3.5 KiB
Go
// Copyright 2019 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 (
|
|
"sync"
|
|
|
|
"google.golang.org/protobuf/internal/errors"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/runtime/protoiface"
|
|
)
|
|
|
|
func (mi *MessageInfo) checkInitialized(in protoiface.CheckInitializedInput) (protoiface.CheckInitializedOutput, error) {
|
|
var p pointer
|
|
if ms, ok := in.Message.(*messageState); ok {
|
|
p = ms.pointer()
|
|
} else {
|
|
p = in.Message.(*messageReflectWrapper).pointer()
|
|
}
|
|
return protoiface.CheckInitializedOutput{}, mi.checkInitializedPointer(p)
|
|
}
|
|
|
|
func (mi *MessageInfo) checkInitializedPointer(p pointer) error {
|
|
mi.init()
|
|
if !mi.needsInitCheck {
|
|
return nil
|
|
}
|
|
if p.IsNil() {
|
|
for _, f := range mi.orderedCoderFields {
|
|
if f.isRequired {
|
|
return errors.RequiredNotSet(string(mi.Desc.Fields().ByNumber(f.num).FullName()))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
if mi.extensionOffset.IsValid() {
|
|
e := p.Apply(mi.extensionOffset).Extensions()
|
|
if err := mi.isInitExtensions(e); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, f := range mi.orderedCoderFields {
|
|
if !f.isRequired && f.funcs.isInit == nil {
|
|
continue
|
|
}
|
|
fptr := p.Apply(f.offset)
|
|
if f.isPointer && fptr.Elem().IsNil() {
|
|
if f.isRequired {
|
|
return errors.RequiredNotSet(string(mi.Desc.Fields().ByNumber(f.num).FullName()))
|
|
}
|
|
continue
|
|
}
|
|
if f.funcs.isInit == nil {
|
|
continue
|
|
}
|
|
if err := f.funcs.isInit(fptr, f); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (mi *MessageInfo) isInitExtensions(ext *map[int32]ExtensionField) error {
|
|
if ext == nil {
|
|
return nil
|
|
}
|
|
for _, x := range *ext {
|
|
ei := getExtensionFieldInfo(x.Type())
|
|
if ei.funcs.isInit == nil || x.isUnexpandedLazy() {
|
|
continue
|
|
}
|
|
v := x.Value()
|
|
if !v.IsValid() {
|
|
continue
|
|
}
|
|
if err := ei.funcs.isInit(v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
needsInitCheckMu sync.Mutex
|
|
needsInitCheckMap sync.Map
|
|
)
|
|
|
|
// needsInitCheck reports whether a message needs to be checked for partial initialization.
|
|
//
|
|
// It returns true if the message transitively includes any required or extension fields.
|
|
func needsInitCheck(md protoreflect.MessageDescriptor) bool {
|
|
if v, ok := needsInitCheckMap.Load(md); ok {
|
|
if has, ok := v.(bool); ok {
|
|
return has
|
|
}
|
|
}
|
|
needsInitCheckMu.Lock()
|
|
defer needsInitCheckMu.Unlock()
|
|
return needsInitCheckLocked(md)
|
|
}
|
|
|
|
func needsInitCheckLocked(md protoreflect.MessageDescriptor) (has bool) {
|
|
if v, ok := needsInitCheckMap.Load(md); ok {
|
|
// If has is true, we've previously determined that this message
|
|
// needs init checks.
|
|
//
|
|
// If has is false, we've previously determined that it can never
|
|
// be uninitialized.
|
|
//
|
|
// If has is not a bool, we've just encountered a cycle in the
|
|
// message graph. In this case, it is safe to return false: If
|
|
// the message does have required fields, we'll detect them later
|
|
// in the graph traversal.
|
|
has, ok := v.(bool)
|
|
return ok && has
|
|
}
|
|
needsInitCheckMap.Store(md, struct{}{}) // avoid cycles while descending into this message
|
|
defer func() {
|
|
needsInitCheckMap.Store(md, has)
|
|
}()
|
|
if md.RequiredNumbers().Len() > 0 {
|
|
return true
|
|
}
|
|
if md.ExtensionRanges().Len() > 0 {
|
|
return true
|
|
}
|
|
for i := 0; i < md.Fields().Len(); i++ {
|
|
fd := md.Fields().Get(i)
|
|
// Map keys are never messages, so just consider the map value.
|
|
if fd.IsMap() {
|
|
fd = fd.MapValue()
|
|
}
|
|
fmd := fd.Message()
|
|
if fmd != nil && needsInitCheckLocked(fmd) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|