Ver código fonte

ptypes: optimize Is to avoid prefix scan (#601)

jukkag 7 anos atrás
pai
commit
3fac2a27c9
2 arquivos alterados com 48 adições e 4 exclusões
  1. 6 4
      ptypes/any.go
  2. 42 0
      ptypes/any_test.go

+ 6 - 4
ptypes/any.go

@@ -130,10 +130,12 @@ func UnmarshalAny(any *any.Any, pb proto.Message) error {
 
 // Is returns true if any value contains a given message type.
 func Is(any *any.Any, pb proto.Message) bool {
-	aname, err := AnyMessageName(any)
-	if err != nil {
+	// The following is equivalent to AnyMessageName(any) == proto.MessageName(pb),
+	// but it avoids scanning TypeUrl for the slash.
+	if any == nil {
 		return false
 	}
-
-	return aname == proto.MessageName(pb)
+	name := proto.MessageName(pb)
+	prefix := len(any.TypeUrl) - len(name)
+	return prefix >= 1 && any.TypeUrl[prefix-1] == '/' && any.TypeUrl[prefix:] == name
 }

+ 42 - 0
ptypes/any_test.go

@@ -60,8 +60,13 @@ func TestIs(t *testing.T) {
 		t.Fatal(err)
 	}
 	if Is(a, &pb.DescriptorProto{}) {
+		// No spurious match for message names of different length.
 		t.Error("FileDescriptorProto is not a DescriptorProto, but Is says it is")
 	}
+	if Is(a, &pb.EnumDescriptorProto{}) {
+		// No spurious match for message names of equal length.
+		t.Error("FileDescriptorProto is not an EnumDescriptorProto, but Is says it is")
+	}
 	if !Is(a, &pb.FileDescriptorProto{}) {
 		t.Error("FileDescriptorProto is indeed a FileDescriptorProto, but Is says it is not")
 	}
@@ -75,6 +80,22 @@ func TestIsDifferentUrlPrefixes(t *testing.T) {
 	}
 }
 
+
+func TestIsCornerCases(t *testing.T) {
+	m := &pb.FileDescriptorProto{}
+	if Is(nil, m) {
+		t.Errorf("message with nil type url incorrectly claimed to be %q", proto.MessageName(m))
+	}
+	noPrefix := &any.Any{TypeUrl: proto.MessageName(m)}
+	if Is(noPrefix, m) {
+		t.Errorf("message with type url %q incorrectly claimed to be %q", noPrefix.TypeUrl, proto.MessageName(m))
+	}
+	shortPrefix := &any.Any{TypeUrl: "/" + proto.MessageName(m)}
+	if !Is(shortPrefix, m) {
+		t.Errorf("message with type url %q didn't satisfy Is for type %q", shortPrefix.TypeUrl, proto.MessageName(m))
+	}
+}
+
 func TestUnmarshalDynamic(t *testing.T) {
 	want := &pb.FileDescriptorProto{Name: proto.String("foo")}
 	a, err := MarshalAny(want)
@@ -111,3 +132,24 @@ func TestEmpty(t *testing.T) {
 		t.Errorf("got no error for an attempt to create a message of type %q, which shouldn't be linked in", a.TypeUrl)
 	}
 }
+
+func TestEmptyCornerCases(t *testing.T) {
+	_, err := Empty(nil)
+	if err == nil {
+		t.Error("expected Empty for nil to fail")
+	}
+	want := &pb.FileDescriptorProto{}
+	noPrefix := &any.Any{TypeUrl: proto.MessageName(want)}
+	_, err = Empty(noPrefix)
+	if err == nil {
+		t.Errorf("expected Empty for any type %q to fail", noPrefix.TypeUrl)
+	}
+	shortPrefix := &any.Any{TypeUrl: "/" + proto.MessageName(want)}
+	got, err := Empty(shortPrefix)
+	if err != nil {
+		t.Errorf("Empty for any type %q failed: %s", shortPrefix.TypeUrl, err)
+	}
+	if !proto.Equal(got, want) {
+		t.Errorf("Empty for any type %q differs, got %q, want %q", shortPrefix.TypeUrl, got, want)
+	}
+}