|  | // Copyright 2024 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_test | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "strings" | 
|  | "testing" | 
|  |  | 
|  | "google.golang.org/protobuf/encoding/protowire" | 
|  | "google.golang.org/protobuf/proto" | 
|  | "google.golang.org/protobuf/testing/protopack" | 
|  |  | 
|  | lnwtpb "google.golang.org/protobuf/internal/testprotos/lazy" | 
|  | ) | 
|  |  | 
|  | func unmarshalsTheSame(b []byte, expected *lnwtpb.FTop) error { | 
|  | unmarshaledTop := &lnwtpb.FTop{} | 
|  | if err := proto.Unmarshal(b, unmarshaledTop); err != nil { | 
|  | return err | 
|  | } | 
|  | if !proto.Equal(unmarshaledTop, expected) { | 
|  | return fmt.Errorf("!proto.Equal") | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func bytesTag(num protowire.Number) protopack.Tag { | 
|  | return protopack.Tag{ | 
|  | Number: num, | 
|  | Type:   protopack.BytesType, | 
|  | } | 
|  | } | 
|  |  | 
|  | func varintTag(num protowire.Number) protopack.Tag { | 
|  | return protopack.Tag{ | 
|  | Number: num, | 
|  | Type:   protopack.VarintType, | 
|  | } | 
|  | } | 
|  |  | 
|  | // Constructs a message encoded in denormalized (non-minimal) wire format, but | 
|  | // using two levels of nesting: A top-level message with a child message which | 
|  | // in turn has a grandchild message. | 
|  | func denormalizedTwoLevelField() ([]byte, *lnwtpb.FTop, error) { | 
|  | expectedMessage := &lnwtpb.FTop{ | 
|  | A: proto.Uint32(2342), | 
|  | Child: &lnwtpb.FSub{ | 
|  | Grandchild: &lnwtpb.FSub{ | 
|  | B: proto.Uint32(1337), | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | fullMessage := protopack.Message{ | 
|  | varintTag(1), protopack.Varint(2342), | 
|  | // Child | 
|  | bytesTag(2), protopack.LengthPrefix(protopack.Message{ | 
|  | // Grandchild | 
|  | bytesTag(4), protopack.LengthPrefix(protopack.Message{ | 
|  | // The first occurrence of B matches expectedMessage: | 
|  | varintTag(2), protopack.Varint(1337), | 
|  | // This second duplicative occurrence of B is spec'd in Protobuf: | 
|  | // https://github.com/protocolbuffers/protobuf/issues/9257 | 
|  | varintTag(2), protopack.Varint(1337), | 
|  | }), | 
|  | }), | 
|  | }.Marshal() | 
|  |  | 
|  | return fullMessage, expectedMessage, nil | 
|  | } | 
|  |  | 
|  | func TestInvalidWireFormat(t *testing.T) { | 
|  | fullMessage, expectedMessage, err := denormalizedTwoLevelField() | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | top := &lnwtpb.FTop{} | 
|  | if err := proto.Unmarshal(fullMessage, top); err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | // Access the top-level submessage, but not the grandchild. | 
|  | // This populates the size cache in the top-level message. | 
|  | top.GetChild() | 
|  |  | 
|  | marshal1, err := proto.MarshalOptions{ | 
|  | UseCachedSize: true, | 
|  | }.Marshal(top) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | if err := unmarshalsTheSame(marshal1, expectedMessage); err != nil { | 
|  | t.Error(err) | 
|  | } | 
|  |  | 
|  | // Call top.GetChild().GetGrandchild() to unmarshal the lazy message, | 
|  | // which will normalize it: the size cache shrinks from 6 bytes to 3. | 
|  | // Notably, top.GetChild()’s size cache is not updated! | 
|  | top.GetChild().GetGrandchild() | 
|  | marshal2, err := proto.MarshalOptions{ | 
|  | // GetGrandchild+UseCachedSize is one way to trigger this bug. | 
|  | // The other way is to call GetGrandchild in another goroutine, | 
|  | // after proto.Marshal has called proto.Size but | 
|  | // before proto.Marshal started encoding. | 
|  | UseCachedSize: true, | 
|  | }.Marshal(top) | 
|  | if err != nil { | 
|  | if strings.Contains(err.Error(), "size mismatch") { | 
|  | // This is the expected failure mode: proto.Marshal() detects the | 
|  | // combination of non-minimal wire format and lazy decoding and | 
|  | // returns an error, prompting the user to disable lazy decoding. | 
|  | return | 
|  | } | 
|  | t.Fatal(err) | 
|  | } | 
|  | if err := unmarshalsTheSame(marshal2, expectedMessage); err != nil { | 
|  | t.Error(err) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestIdenticalOverAccessWhenDeterministic(t *testing.T) { | 
|  | fullMessage, _, err := denormalizedTwoLevelField() | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | top := &lnwtpb.FTop{} | 
|  | if err := proto.Unmarshal(fullMessage, top); err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | deterministic := proto.MarshalOptions{ | 
|  | Deterministic: true, | 
|  | } | 
|  | marshal1, err := deterministic.Marshal(top) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | // Call top.GetChild().GetGrandchild() to unmarshal the lazy message, | 
|  | // which will normalize it. | 
|  | top.GetChild().GetGrandchild() | 
|  |  | 
|  | marshal2, err := deterministic.Marshal(top) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | if !bytes.Equal(marshal1, marshal2) { | 
|  | t.Errorf("MarshalOptions{Deterministic: true}.Marshal() not identical over accessing a non-minimal wire format lazy message:\nbefore:\n%x\nafter:\n%x", marshal1, marshal2) | 
|  | } | 
|  | } |