internal/filedesc: move message options to L1 initialization

Avoid a deadlock when registering a legacy ExtensionType, caused by
initialization of the "internal/impl".ExtensionInfo calling IsMessageSet
on the MessageDescriptor of the type being extended.

We can avoid this deadlock either by initializing the ExtensionType
outside of the GlobalTypes mutex, or by moving IsMessageSet to L1
initialization of the MessageDescriptor so that it doesn't trigger lazy
init.

CL 204804 takes the former approach; this CL takes the latter.

Change-Id: Idfc1ed36a23a139839290ea32492142a17f68cf5
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/205957
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/internal/filedesc/desc.go b/internal/filedesc/desc.go
index 72a45e2..8185758 100644
--- a/internal/filedesc/desc.go
+++ b/internal/filedesc/desc.go
@@ -180,14 +180,14 @@
 		L2 *MessageL2 // protected by fileDesc.once
 	}
 	MessageL1 struct {
-		Enums      Enums
-		Messages   Messages
-		Extensions Extensions
+		Enums        Enums
+		Messages     Messages
+		Extensions   Extensions
+		IsMapEntry   bool // promoted from google.protobuf.MessageOptions
+		IsMessageSet bool // promoted from google.protobuf.MessageOptions
 	}
 	MessageL2 struct {
 		Options               func() pref.ProtoMessage
-		IsMapEntry            bool // promoted from google.protobuf.MessageOptions
-		IsMessageSet          bool // promoted from google.protobuf.MessageOptions
 		Fields                Fields
 		Oneofs                Oneofs
 		ReservedNames         Names
@@ -234,7 +234,7 @@
 	}
 	return descopts.Message
 }
-func (md *Message) IsMapEntry() bool                   { return md.lazyInit().IsMapEntry }
+func (md *Message) IsMapEntry() bool                   { return md.L1.IsMapEntry }
 func (md *Message) Fields() pref.FieldDescriptors      { return &md.lazyInit().Fields }
 func (md *Message) Oneofs() pref.OneofDescriptors      { return &md.lazyInit().Oneofs }
 func (md *Message) ReservedNames() pref.Names          { return &md.lazyInit().ReservedNames }
@@ -263,7 +263,7 @@
 // WARNING: This method is exempt from the compatibility promise and may be
 // removed in the future without warning.
 func (md *Message) IsMessageSet() bool {
-	return md.lazyInit().IsMessageSet
+	return md.L1.IsMessageSet
 }
 
 func (fd *Field) Options() pref.ProtoMessage {
diff --git a/internal/filedesc/desc_init.go b/internal/filedesc/desc_init.go
index 1b6daf0..249021f 100644
--- a/internal/filedesc/desc_init.go
+++ b/internal/filedesc/desc_init.go
@@ -314,6 +314,8 @@
 					posExtensions = len(b0) - len(b) - n - m
 				}
 				numExtensions++
+			case fieldnum.DescriptorProto_Options:
+				md.unmarshalSeedOptions(v)
 			}
 			prevField = num
 		default:
@@ -364,6 +366,27 @@
 	}
 }
 
+func (md *Message) unmarshalSeedOptions(b []byte) {
+	for len(b) > 0 {
+		num, typ, n := wire.ConsumeTag(b)
+		b = b[n:]
+		switch typ {
+		case wire.VarintType:
+			v, m := wire.ConsumeVarint(b)
+			b = b[m:]
+			switch num {
+			case fieldnum.MessageOptions_MapEntry:
+				md.L1.IsMapEntry = wire.DecodeBool(v)
+			case fieldnum.MessageOptions_MessageSetWireFormat:
+				md.L1.IsMessageSet = wire.DecodeBool(v)
+			}
+		default:
+			m := wire.ConsumeFieldValue(num, typ, b)
+			b = b[m:]
+		}
+	}
+}
+
 func (xd *Extension) unmarshalSeed(b []byte, sb *strs.Builder, pf *File, pd pref.Descriptor, i int) {
 	xd.L0.ParentFile = pf
 	xd.L0.Parent = pd
diff --git a/internal/filedesc/desc_lazy.go b/internal/filedesc/desc_lazy.go
index 86720a0..309d739 100644
--- a/internal/filedesc/desc_lazy.go
+++ b/internal/filedesc/desc_lazy.go
@@ -348,9 +348,9 @@
 			b = b[m:]
 			switch num {
 			case fieldnum.MessageOptions_MapEntry:
-				md.L2.IsMapEntry = wire.DecodeBool(v)
+				md.L1.IsMapEntry = wire.DecodeBool(v)
 			case fieldnum.MessageOptions_MessageSetWireFormat:
-				md.L2.IsMessageSet = wire.DecodeBool(v)
+				md.L1.IsMessageSet = wire.DecodeBool(v)
 			}
 		default:
 			m := wire.ConsumeFieldValue(num, typ, b)
diff --git a/internal/impl/legacy_message.go b/internal/impl/legacy_message.go
index dbc99ed..e078045 100644
--- a/internal/impl/legacy_message.go
+++ b/internal/impl/legacy_message.go
@@ -297,7 +297,7 @@
 				md2.L0.Parent = md
 				md2.L0.Index = n
 
-				md2.L2.IsMapEntry = true
+				md2.L1.IsMapEntry = true
 				md2.L2.Options = func() pref.ProtoMessage {
 					opts := descopts.Message.ProtoReflect().New()
 					opts.Set(opts.Descriptor().Fields().ByName("map_entry"), protoreflect.ValueOfBool(true))
diff --git a/reflect/protodesc/desc_init.go b/reflect/protodesc/desc_init.go
index 2fe19b0..6f689b1 100644
--- a/reflect/protodesc/desc_init.go
+++ b/reflect/protodesc/desc_init.go
@@ -70,8 +70,8 @@
 		if opts := md.GetOptions(); opts != nil {
 			opts = clone(opts).(*descriptorpb.MessageOptions)
 			m.L2.Options = func() protoreflect.ProtoMessage { return opts }
-			m.L2.IsMapEntry = opts.GetMapEntry()
-			m.L2.IsMessageSet = opts.GetMessageSetWireFormat()
+			m.L1.IsMapEntry = opts.GetMapEntry()
+			m.L1.IsMessageSet = opts.GetMessageSetWireFormat()
 		}
 		for _, s := range md.GetReservedName() {
 			m.L2.ReservedNames.List = append(m.L2.ReservedNames.List, protoreflect.Name(s))