internal/impl: fix race in aberrant message logic

Previously, when aberrantLoadMessageDesc returned it was guaranteed
to have initialized the current message through the use of the done signal.
However, this does not guarantee that the descriptor for a cylic reference
has also finished initialization.

Rather than add more complicated logic to wait until all cyclic references
have finished initializing, just add a global lock for the entire
aberrantLoadMessageDesc function.

This slows down performance, but is easier to reason about.

Change-Id: I4cdae8b955f71ee40fa6979f5a8d548d9749042c
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/184657
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/impl/legacy_aberrant_test.go b/internal/impl/legacy_aberrant_test.go
index 76529ec..cca88d4 100644
--- a/internal/impl/legacy_aberrant_test.go
+++ b/internal/impl/legacy_aberrant_test.go
@@ -7,12 +7,14 @@
 import (
 	"io"
 	"reflect"
+	"sync"
 	"testing"
 
 	"google.golang.org/protobuf/encoding/prototext"
 	"google.golang.org/protobuf/internal/impl"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/reflect/protodesc"
+	"google.golang.org/protobuf/reflect/protoreflect"
 	"google.golang.org/protobuf/runtime/protoiface"
 
 	"google.golang.org/protobuf/types/descriptorpb"
@@ -286,3 +288,35 @@
 		t.Errorf("mismatching descriptor:\ngot  %v\nwant %v", got, want)
 	}
 }
+
+type AberrantMessage1 struct {
+	M *AberrantMessage2 `protobuf:"bytes,1,opt,name=message"`
+}
+
+type AberrantMessage2 struct {
+	M *AberrantMessage1 `protobuf:"bytes,1,opt,name=message"`
+}
+
+func TestAberrantRace(t *testing.T) {
+	var gotMD1, wantMD1, gotMD2, wantMD2 protoreflect.MessageDescriptor
+
+	var wg sync.WaitGroup
+	wg.Add(2)
+	go func() {
+		defer wg.Done()
+		md := impl.LegacyLoadMessageDesc(reflect.TypeOf(&AberrantMessage1{}))
+		wantMD2 = md.Fields().Get(0).Message()
+		gotMD2 = wantMD2.Fields().Get(0).Message().Fields().Get(0).Message()
+	}()
+	go func() {
+		defer wg.Done()
+		md := impl.LegacyLoadMessageDesc(reflect.TypeOf(&AberrantMessage2{}))
+		wantMD1 = md.Fields().Get(0).Message()
+		gotMD1 = wantMD1.Fields().Get(0).Message().Fields().Get(0).Message()
+	}()
+	wg.Wait()
+
+	if gotMD1 != wantMD1 || gotMD2 != wantMD2 {
+		t.Errorf("mismatching exact message descriptors")
+	}
+}