...

Source file src/google.golang.org/protobuf/encoding/protojson/fuzz_test.go

Documentation: google.golang.org/protobuf/encoding/protojson

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Go native fuzzing was added in go1.18. Remove this once we stop supporting
     6  // go1.17.
     7  //go:build go1.18
     8  
     9  package protojson_test
    10  
    11  import (
    12  	"math"
    13  	"testing"
    14  
    15  	"github.com/google/go-cmp/cmp"
    16  	"google.golang.org/protobuf/encoding/protojson"
    17  	"google.golang.org/protobuf/proto"
    18  	"google.golang.org/protobuf/reflect/protoreflect"
    19  	"google.golang.org/protobuf/testing/protocmp"
    20  
    21  	testfuzzpb "google.golang.org/protobuf/internal/testprotos/editionsfuzztest"
    22  )
    23  
    24  // roundTripAndCompareProto tests if a protojson.Marshal/Unmarshal roundtrip
    25  // preserves the contents of the message. Note: wireBytes are a protocol
    26  // buffer wire format message, not the JSON formatted proto. We do this because
    27  // a random stream of bytes (e.g. generated by the fuzz engine) is more likely
    28  // to be valid proto wire format than that it is valid json format.
    29  func roundTripAndCompareProto(t *testing.T, wireBytes []byte, messages ...proto.Message) {
    30  	for _, msg := range messages {
    31  		src := msg.ProtoReflect().Type().New().Interface()
    32  
    33  		if err := proto.Unmarshal(wireBytes, src); err != nil {
    34  			// Ignoring invalid wire format since we want to test the protojson
    35  			// implementation, not the wireformat implementation.
    36  			return
    37  		}
    38  
    39  		// Unknown fields are not marshaled by protojson so we strip them.
    40  		src.ProtoReflect().SetUnknown(nil)
    41  		var ranger func(protoreflect.FieldDescriptor, protoreflect.Value) bool
    42  		stripUnknown := func(m protoreflect.Message) {
    43  			m.SetUnknown(nil)
    44  			m.Range(ranger)
    45  		}
    46  		ranger = func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
    47  			switch {
    48  			case fd.IsMap():
    49  				if fd.MapValue().Message() != nil {
    50  					v.Map().Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool {
    51  						stripUnknown(v.Message())
    52  						return true
    53  					})
    54  				}
    55  			case fd.Message() != nil:
    56  				if fd.Cardinality() == protoreflect.Repeated {
    57  					l := v.List()
    58  					for i := 0; i < l.Len(); i++ {
    59  						stripUnknown(l.Get(i).Message())
    60  					}
    61  				} else {
    62  					stripUnknown(v.Message())
    63  				}
    64  			}
    65  			return true
    66  		}
    67  		stripUnknown(src.ProtoReflect())
    68  
    69  		jsonBytes, err := protojson.Marshal(src)
    70  		if err != nil {
    71  			t.Errorf("failed to marshal messsage to json: %v\nmessage: %v", err, src)
    72  		}
    73  		dst := msg.ProtoReflect().Type().New().Interface()
    74  
    75  		if err := protojson.Unmarshal(jsonBytes, dst); err != nil {
    76  			t.Errorf("failed to unmarshal messsage from json: %v\njson: %s", err, jsonBytes)
    77  		}
    78  
    79  		// The cmp package does not deal with NaN on its own and will report
    80  		// NaN != NaN.
    81  		optNaN64 := cmp.Comparer(func(x, y float32) bool {
    82  			return (math.IsNaN(float64(x)) && math.IsNaN(float64(y))) || x == y
    83  		})
    84  		optNaN32 := cmp.Comparer(func(x, y float64) bool {
    85  			return (math.IsNaN(x) && math.IsNaN(y)) || x == y
    86  		})
    87  		if diff := cmp.Diff(src, dst, protocmp.Transform(), optNaN64, optNaN32); diff != "" {
    88  			t.Error(diff)
    89  		}
    90  	}
    91  }
    92  
    93  func FuzzEncodeDecodeRoundTrip(f *testing.F) {
    94  	f.Add([]byte("Hello World!"))
    95  	f.Fuzz(func(t *testing.T, in []byte) {
    96  		// We cannot test proto2 because it does not have UTF-8 validation
    97  		// but the JSON spec requires valid UTF-8 and thus we might initialize
    98  		// proto2 messages with invalid UTF-8 and then fail marshalling it.
    99  		roundTripAndCompareProto(t, in, (*testfuzzpb.TestAllTypesProto3)(nil), (*testfuzzpb.TestAllTypesProto3Editions)(nil))
   100  	})
   101  }
   102  

View as plain text